From effc87beff875c75d7ce6290b787d9b62d996986 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 6 Sep 2021 05:34:36 -0400 Subject: [PATCH 01/43] [Osquery] Fix support for disabled security (#110547) (#111250) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patryk KopyciƄski --- package.json | 2 +- .../action_results/use_action_privileges.tsx | 12 --- .../osquery/public/common/page_paths.ts | 2 - .../plugins/osquery/public/components/app.tsx | 9 ++ .../osquery/public/components/empty_state.tsx | 86 +++++++++++++++++++ .../components/manage_integration_link.tsx | 16 ++-- .../public/live_queries/form/index.tsx | 16 ++-- .../public/packs/common/add_pack_query.tsx | 4 +- .../osquery/public/packs/common/pack_form.tsx | 4 +- .../public/routes/saved_queries/edit/form.tsx | 5 +- .../public/routes/saved_queries/new/form.tsx | 5 +- .../scheduled_query_groups/edit/index.tsx | 5 +- .../osquery/public/saved_queries/constants.ts | 1 + .../saved_queries/saved_query_flyout.tsx | 3 +- .../public/saved_queries/use_saved_query.ts | 3 +- .../saved_queries/use_update_saved_query.ts | 3 +- .../scheduled_query_groups/form/index.tsx | 6 +- .../queries/ecs_mapping_editor_field.tsx | 3 +- .../queries/query_flyout.tsx | 7 +- ...duled_query_group_queries_status_table.tsx | 70 ++++++++++++--- .../use_scheduled_query_group_query_errors.ts | 9 +- ...cheduled_query_group_query_last_results.ts | 84 +++++++++++------- .../privileges_check_route.ts | 32 +++---- yarn.lock | 8 +- 24 files changed, 280 insertions(+), 115 deletions(-) create mode 100644 x-pack/plugins/osquery/public/components/empty_state.tsx diff --git a/package.json b/package.json index 79a2e66ed91b8..a8f44c5e8bcdb 100644 --- a/package.json +++ b/package.json @@ -349,7 +349,7 @@ "react-moment-proptypes": "^1.7.0", "react-monaco-editor": "^0.41.2", "react-popper-tooltip": "^2.10.1", - "react-query": "^3.21.0", + "react-query": "^3.21.1", "react-redux": "^7.2.0", "react-resizable": "^1.7.5", "react-resize-detector": "^4.2.0", diff --git a/x-pack/plugins/osquery/public/action_results/use_action_privileges.tsx b/x-pack/plugins/osquery/public/action_results/use_action_privileges.tsx index 2c80c874e89fa..6d0477b22edee 100644 --- a/x-pack/plugins/osquery/public/action_results/use_action_privileges.tsx +++ b/x-pack/plugins/osquery/public/action_results/use_action_privileges.tsx @@ -6,28 +6,16 @@ */ import { useQuery } from 'react-query'; - -import { i18n } from '@kbn/i18n'; import { useKibana } from '../common/lib/kibana'; -import { useErrorToast } from '../common/hooks/use_error_toast'; export const useActionResultsPrivileges = () => { const { http } = useKibana().services; - const setErrorToast = useErrorToast(); return useQuery( ['actionResultsPrivileges'], () => http.get('/internal/osquery/privileges_check'), { keepPreviousData: true, - select: (response) => response?.has_all_requested ?? false, - onSuccess: () => setErrorToast(), - onError: (error: Error) => - setErrorToast(error, { - title: i18n.translate('xpack.osquery.action_results_privileges.fetchError', { - defaultMessage: 'Error while fetching action results privileges', - }), - }), } ); }; diff --git a/x-pack/plugins/osquery/public/common/page_paths.ts b/x-pack/plugins/osquery/public/common/page_paths.ts index 0e0d8310ae8be..8df1006da181a 100644 --- a/x-pack/plugins/osquery/public/common/page_paths.ts +++ b/x-pack/plugins/osquery/public/common/page_paths.ts @@ -27,8 +27,6 @@ export interface DynamicPagePathValues { [key: string]: string; } -export const BASE_PATH = '/app/fleet'; - // If routing paths are changed here, please also check to see if // `pagePathGetters()`, below, needs any modifications export const PAGE_ROUTING_PATHS = { diff --git a/x-pack/plugins/osquery/public/components/app.tsx b/x-pack/plugins/osquery/public/components/app.tsx index 44407139ab492..33fb6ac6a2adf 100644 --- a/x-pack/plugins/osquery/public/components/app.tsx +++ b/x-pack/plugins/osquery/public/components/app.tsx @@ -5,6 +5,8 @@ * 2.0. */ +/* eslint-disable react-hooks/rules-of-hooks */ + import React, { useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiTabs, EuiTab } from '@elastic/eui'; @@ -14,10 +16,17 @@ import { Container, Nav, Wrapper } from './layouts'; import { OsqueryAppRoutes } from '../routes'; import { useRouterNavigate } from '../common/lib/kibana'; import { ManageIntegrationLink } from './manage_integration_link'; +import { useOsqueryIntegrationStatus } from '../common/hooks'; +import { OsqueryAppEmptyState } from './empty_state'; const OsqueryAppComponent = () => { const location = useLocation(); const section = useMemo(() => location.pathname.split('/')[1] ?? 'overview', [location.pathname]); + const { data: osqueryIntegration, isFetched } = useOsqueryIntegrationStatus(); + + if (isFetched && osqueryIntegration.install_status !== 'installed') { + return ; + } return ( diff --git a/x-pack/plugins/osquery/public/components/empty_state.tsx b/x-pack/plugins/osquery/public/components/empty_state.tsx new file mode 100644 index 0000000000000..1ee0d496c0ddc --- /dev/null +++ b/x-pack/plugins/osquery/public/components/empty_state.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useMemo } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiButton } from '@elastic/eui'; + +import { KibanaPageTemplate } from '../../../../../src/plugins/kibana_react/public'; +import { INTEGRATIONS_PLUGIN_ID } from '../../../fleet/common'; +import { pagePathGetters } from '../../../fleet/public'; +import { isModifiedEvent, isLeftClickEvent, useKibana } from '../common/lib/kibana'; +import { OsqueryIcon } from './osquery_icon'; +import { useBreadcrumbs } from '../common/hooks/use_breadcrumbs'; +import { OSQUERY_INTEGRATION_NAME } from '../../common'; + +const OsqueryAppEmptyStateComponent = () => { + useBreadcrumbs('base'); + + const { + application: { getUrlForApp, navigateToApp }, + } = useKibana().services; + + const integrationHref = useMemo(() => { + return getUrlForApp(INTEGRATIONS_PLUGIN_ID, { + path: pagePathGetters.integration_details_overview({ + pkgkey: OSQUERY_INTEGRATION_NAME, + })[1], + }); + }, [getUrlForApp]); + + const integrationClick = useCallback( + (event) => { + if (!isModifiedEvent(event) && isLeftClickEvent(event)) { + event.preventDefault(); + return navigateToApp(INTEGRATIONS_PLUGIN_ID, { + path: pagePathGetters.integration_details_overview({ + pkgkey: OSQUERY_INTEGRATION_NAME, + })[1], + }); + } + }, + [navigateToApp] + ); + + const pageHeader = useMemo( + () => ({ + iconType: OsqueryIcon, + pageTitle: ( + + ), + description: ( + + ), + rightSideItems: [ + // eslint-disable-next-line @elastic/eui/href-or-on-click + + + , + ], + }), + [integrationClick, integrationHref] + ); + + return ; +}; + +export const OsqueryAppEmptyState = React.memo(OsqueryAppEmptyStateComponent); diff --git a/x-pack/plugins/osquery/public/components/manage_integration_link.tsx b/x-pack/plugins/osquery/public/components/manage_integration_link.tsx index 44b923860e1a8..32779ded46c50 100644 --- a/x-pack/plugins/osquery/public/components/manage_integration_link.tsx +++ b/x-pack/plugins/osquery/public/components/manage_integration_link.tsx @@ -24,11 +24,9 @@ const ManageIntegrationLinkComponent = () => { const integrationHref = useMemo(() => { if (osqueryIntegration) { return getUrlForApp(INTEGRATIONS_PLUGIN_ID, { - path: - '#' + - pagePathGetters.integration_details_policies({ - pkgkey: `${osqueryIntegration.name}-${osqueryIntegration.version}`, - })[1], + path: pagePathGetters.integration_details_policies({ + pkgkey: `${osqueryIntegration.name}-${osqueryIntegration.version}`, + })[1], }); } }, [getUrlForApp, osqueryIntegration]); @@ -39,11 +37,9 @@ const ManageIntegrationLinkComponent = () => { event.preventDefault(); if (osqueryIntegration) { return navigateToApp(INTEGRATIONS_PLUGIN_ID, { - path: - '#' + - pagePathGetters.integration_details_policies({ - pkgkey: `${osqueryIntegration.name}-${osqueryIntegration.version}`, - })[1], + path: pagePathGetters.integration_details_policies({ + pkgkey: `${osqueryIntegration.name}-${osqueryIntegration.version}`, + })[1], }); } } diff --git a/x-pack/plugins/osquery/public/live_queries/form/index.tsx b/x-pack/plugins/osquery/public/live_queries/form/index.tsx index 987be904c87e6..69b02dee8b9f7 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/index.tsx @@ -114,7 +114,7 @@ const LiveQueryFormComponent: React.FC = ({ ), }); - const { setFieldValue, submit } = form; + const { setFieldValue, submit, isSubmitting } = form; const actionId = useMemo(() => data?.actions[0].action_id, [data?.actions]); const agentIds = useMemo(() => data?.actions[0].agents, [data?.actions]); @@ -185,7 +185,10 @@ const LiveQueryFormComponent: React.FC = ({ )} - + = ({ ), [ - agentSelected, - permissions.writeSavedQueries, - handleShowSaveQueryFlout, queryComponentProps, + singleAgentMode, + permissions.writeSavedQueries, + agentSelected, queryValueProvided, resultsStatus, - singleAgentMode, + handleShowSaveQueryFlout, + isSubmitting, submit, ] ); diff --git a/x-pack/plugins/osquery/public/packs/common/add_pack_query.tsx b/x-pack/plugins/osquery/public/packs/common/add_pack_query.tsx index 2d58e2dfe9522..d1115898b4e40 100644 --- a/x-pack/plugins/osquery/public/packs/common/add_pack_query.tsx +++ b/x-pack/plugins/osquery/public/packs/common/add_pack_query.tsx @@ -51,7 +51,7 @@ const AddPackQueryFormComponent = ({ handleSubmit }) => { }, }, }); - const { submit } = form; + const { submit, isSubmitting } = form; const createSavedQueryMutation = useMutation( (payload) => http.post(`/internal/osquery/saved_query`, { body: JSON.stringify(payload) }), @@ -108,7 +108,7 @@ const AddPackQueryFormComponent = ({ handleSubmit }) => { - + {'Add query'} diff --git a/x-pack/plugins/osquery/public/packs/common/pack_form.tsx b/x-pack/plugins/osquery/public/packs/common/pack_form.tsx index 86d4d8dff6ba6..ab0984e808943 100644 --- a/x-pack/plugins/osquery/public/packs/common/pack_form.tsx +++ b/x-pack/plugins/osquery/public/packs/common/pack_form.tsx @@ -40,7 +40,7 @@ const PackFormComponent = ({ data, handleSubmit }) => { }, }, }); - const { submit } = form; + const { submit, isSubmitting } = form; return (
@@ -50,7 +50,7 @@ const PackFormComponent = ({ data, handleSubmit }) => { - + {'Save pack'} diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/edit/form.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/edit/form.tsx index a7596575b90c4..617d83821d08d 100644 --- a/x-pack/plugins/osquery/public/routes/saved_queries/edit/form.tsx +++ b/x-pack/plugins/osquery/public/routes/saved_queries/edit/form.tsx @@ -38,6 +38,7 @@ const EditSavedQueryFormComponent: React.FC = ({ defaultValue, handleSubmit, }); + const { submit, isSubmitting } = form; return (
@@ -58,12 +59,12 @@ const EditSavedQueryFormComponent: React.FC = ({ = ({ defaultValue, handleSubmit, }); + const { submit, isSubmitting } = form; return ( @@ -54,12 +55,12 @@ const NewSavedQueryFormComponent: React.FC = ({ { const { data } = useScheduledQueryGroup({ scheduledQueryGroupId }); - useBreadcrumbs('scheduled_query_group_edit', { scheduledQueryGroupName: data?.name ?? '' }); + useBreadcrumbs('scheduled_query_group_edit', { + scheduledQueryGroupId: data?.id ?? '', + scheduledQueryGroupName: data?.name ?? '', + }); const LeftColumn = useMemo( () => ( diff --git a/x-pack/plugins/osquery/public/saved_queries/constants.ts b/x-pack/plugins/osquery/public/saved_queries/constants.ts index 69ca805e3e8fa..8edcfd00d1788 100644 --- a/x-pack/plugins/osquery/public/saved_queries/constants.ts +++ b/x-pack/plugins/osquery/public/saved_queries/constants.ts @@ -6,3 +6,4 @@ */ export const SAVED_QUERIES_ID = 'savedQueryList'; +export const SAVED_QUERY_ID = 'savedQuery'; diff --git a/x-pack/plugins/osquery/public/saved_queries/saved_query_flyout.tsx b/x-pack/plugins/osquery/public/saved_queries/saved_query_flyout.tsx index 6d14943a6bc84..8c35a359a9baf 100644 --- a/x-pack/plugins/osquery/public/saved_queries/saved_query_flyout.tsx +++ b/x-pack/plugins/osquery/public/saved_queries/saved_query_flyout.tsx @@ -42,6 +42,7 @@ const SavedQueryFlyoutComponent: React.FC = ({ defaultValue defaultValue, handleSubmit, }); + const { submit, isSubmitting } = form; return ( @@ -72,7 +73,7 @@ const SavedQueryFlyoutComponent: React.FC = ({ defaultValue - + { queryClient.invalidateQueries(SAVED_QUERIES_ID); + queryClient.invalidateQueries([SAVED_QUERY_ID, { savedQueryId }]); navigateToApp(PLUGIN_ID, { path: pagePathGetters.saved_queries() }); toasts.addSuccess( i18n.translate('xpack.osquery.editSavedQuery.successToastMessageText', { diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/form/index.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/form/index.tsx index 685960ecd202e..3598a9fd2e44c 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/form/index.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/form/index.tsx @@ -88,7 +88,7 @@ const ScheduledQueryGroupFormComponent: React.FC = `scheduled_query_groups/${editMode ? defaultValue?.id : ''}` ); - const { isLoading, mutateAsync } = useMutation( + const { mutateAsync } = useMutation( (payload: Record) => editMode && defaultValue?.id ? http.put(packagePolicyRouteService.getUpdatePath(defaultValue.id), { @@ -248,7 +248,7 @@ const ScheduledQueryGroupFormComponent: React.FC = ), }); - const { setFieldValue, submit } = form; + const { setFieldValue, submit, isSubmitting } = form; const policyIdEuiFieldProps = useMemo( () => ({ isDisabled: !!defaultValue, options: agentPolicyOptions }), @@ -368,7 +368,7 @@ const ScheduledQueryGroupFormComponent: React.FC = ( ) )(args); - if (fieldRequiredError && (!!(!editForm && args.formData.value?.field.length) || editForm)) { + // @ts-expect-error update types + if (fieldRequiredError && ((!editForm && args.formData['value.field'].length) || editForm)) { return fieldRequiredError; } diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx index cae9711694f29..d38c1b2118f24 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { isEmpty } from 'lodash'; import { EuiCallOut, EuiFlyout, @@ -66,7 +67,7 @@ const QueryFlyoutComponent: React.FC = ({ if (isValid && ecsFieldValue) { onSave({ ...payload, - ecs_mapping: ecsFieldValue, + ...(isEmpty(ecsFieldValue) ? {} : { ecs_mapping: ecsFieldValue }), }); onClose(); } @@ -81,7 +82,7 @@ const QueryFlyoutComponent: React.FC = ({ [integrationPackageVersion] ); - const { submit, setFieldValue, reset } = form; + const { submit, setFieldValue, reset, isSubmitting } = form; const [{ query }] = useFormData({ form, @@ -245,7 +246,7 @@ const QueryFlyoutComponent: React.FC = ({ - + = ({ toggleErrors, expanded, }) => { + const data = useKibana().services.data; + const [logsIndexPattern, setLogsIndexPattern] = useState(undefined); + const { data: lastResultsData, isFetched } = useScheduledQueryGroupQueryLastResults({ actionId, agentIds, interval, + logsIndexPattern, }); const { data: errorsData, isFetched: errorsFetched } = useScheduledQueryGroupQueryErrors({ actionId, agentIds, interval, + logsIndexPattern, }); const handleErrorsToggle = useCallback(() => toggleErrors({ queryId, interval }), [ @@ -409,20 +414,41 @@ const ScheduledQueryLastResults: React.FC = ({ toggleErrors, ]); + useEffect(() => { + const fetchLogsIndexPattern = async () => { + const indexPattern = await data.indexPatterns.find('logs-*'); + + setLogsIndexPattern(indexPattern[0]); + }; + fetchLogsIndexPattern(); + }, [data.indexPatterns]); + if (!isFetched || !errorsFetched) { return ; } - if (!lastResultsData) { + if (!lastResultsData && !errorsData?.total) { return <>{'-'}; } return ( - {lastResultsData.first_event_ingested_time?.value ? ( - - <>{moment(lastResultsData.first_event_ingested_time?.value).fromNow()} + {lastResultsData?.['@timestamp'] ? ( + + {' '} + + + } + > + ) : ( '-' @@ -432,10 +458,17 @@ const ScheduledQueryLastResults: React.FC = ({ - {lastResultsData?.doc_count ?? 0} + {lastResultsData?.docCount ?? 0} - {'Documents'} + + + @@ -443,10 +476,17 @@ const ScheduledQueryLastResults: React.FC = ({ - {lastResultsData?.unique_agents?.value ?? 0} + {lastResultsData?.uniqueAgentsCount ?? 0} - {'Agents'} + + + @@ -458,7 +498,15 @@ const ScheduledQueryLastResults: React.FC = ({ - {'Errors'} + + {' '} + + { const data = useKibana().services.data; @@ -28,9 +30,8 @@ export const useScheduledQueryGroupQueryErrors = ({ return useQuery( ['scheduledQueryErrors', { actionId, interval }], async () => { - const indexPattern = await data.indexPatterns.find('logs-*'); const searchSource = await data.search.searchSource.create({ - index: indexPattern[0], + index: logsIndexPattern, fields: ['*'], sort: [ { @@ -80,7 +81,7 @@ export const useScheduledQueryGroupQueryErrors = ({ }, { keepPreviousData: true, - enabled: !!(!skip && actionId && interval && agentIds?.length), + enabled: !!(!skip && actionId && interval && agentIds?.length && logsIndexPattern), select: (response) => response.rawResponse.hits ?? [], refetchOnReconnect: false, refetchOnWindowFocus: false, diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group_query_last_results.ts b/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group_query_last_results.ts index f972640e25986..7cfd6be461e05 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group_query_last_results.ts +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group_query_last_results.ts @@ -6,13 +6,14 @@ */ import { useQuery } from 'react-query'; - +import { IndexPattern } from '../../../../../src/plugins/data/common'; import { useKibana } from '../common/lib/kibana'; interface UseScheduledQueryGroupQueryLastResultsProps { actionId: string; agentIds?: string[]; interval: number; + logsIndexPattern?: IndexPattern; skip?: boolean; } @@ -20,6 +21,7 @@ export const useScheduledQueryGroupQueryLastResults = ({ actionId, agentIds, interval, + logsIndexPattern, skip = false, }: UseScheduledQueryGroupQueryLastResultsProps) => { const data = useKibana().services.data; @@ -27,23 +29,9 @@ export const useScheduledQueryGroupQueryLastResults = ({ return useQuery( ['scheduledQueryLastResults', { actionId }], async () => { - const indexPattern = await data.indexPatterns.find('logs-*'); - const searchSource = await data.search.searchSource.create({ - index: indexPattern[0], - size: 0, - aggs: { - runs: { - terms: { - field: 'response_id', - order: { first_event_ingested_time: 'desc' }, - size: 1, - }, - aggs: { - first_event_ingested_time: { min: { field: '@timestamp' } }, - unique_agents: { cardinality: { field: 'agent.id' } }, - }, - }, - }, + const lastResultsSearchSource = await data.search.searchSource.create({ + index: logsIndexPattern, + size: 1, query: { // @ts-expect-error update types bool: { @@ -59,26 +47,62 @@ export const useScheduledQueryGroupQueryLastResults = ({ action_id: actionId, }, }, - { - range: { - '@timestamp': { - gte: `now-${interval * 2}s`, - lte: 'now', - }, - }, - }, ], }, }, }); - return searchSource.fetch$().toPromise(); + const lastResultsResponse = await lastResultsSearchSource.fetch$().toPromise(); + + const responseId = lastResultsResponse.rawResponse?.hits?.hits[0]?._source?.response_id; + + if (responseId) { + const aggsSearchSource = await data.search.searchSource.create({ + index: logsIndexPattern, + size: 0, + aggs: { + unique_agents: { cardinality: { field: 'agent.id' } }, + }, + query: { + // @ts-expect-error update types + bool: { + should: agentIds?.map((agentId) => ({ + match_phrase: { + 'agent.id': agentId, + }, + })), + minimum_should_match: 1, + filter: [ + { + match_phrase: { + action_id: actionId, + }, + }, + { + match_phrase: { + response_id: responseId, + }, + }, + ], + }, + }, + }); + + const aggsResponse = await aggsSearchSource.fetch$().toPromise(); + + return { + '@timestamp': lastResultsResponse.rawResponse?.hits?.hits[0]?.fields?.['@timestamp'], + // @ts-expect-error update types + uniqueAgentsCount: aggsResponse.rawResponse.aggregations?.unique_agents?.value, + docCount: aggsResponse.rawResponse?.hits?.total, + }; + } + + return null; }, { keepPreviousData: true, - enabled: !!(!skip && actionId && interval && agentIds?.length), - // @ts-expect-error update types - select: (response) => response.rawResponse.aggregations?.runs?.buckets[0] ?? [], + enabled: !!(!skip && actionId && interval && agentIds?.length && logsIndexPattern), refetchOnReconnect: false, refetchOnWindowFocus: false, } diff --git a/x-pack/plugins/osquery/server/routes/privileges_check/privileges_check_route.ts b/x-pack/plugins/osquery/server/routes/privileges_check/privileges_check_route.ts index 80c335c1c46d3..d9683d23deb13 100644 --- a/x-pack/plugins/osquery/server/routes/privileges_check/privileges_check_route.ts +++ b/x-pack/plugins/osquery/server/routes/privileges_check/privileges_check_route.ts @@ -9,7 +9,6 @@ import { OSQUERY_INTEGRATION_NAME, PLUGIN_ID } from '../../../common'; import { IRouter } from '../../../../../../src/core/server'; import { OsqueryAppContext } from '../../lib/osquery_app_context_services'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars export const privilegesCheckRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => { router.get( { @@ -20,23 +19,26 @@ export const privilegesCheckRoute = (router: IRouter, osqueryContext: OsqueryApp }, }, async (context, request, response) => { - const esClient = context.core.elasticsearch.client.asCurrentUser; - - const privileges = ( - await esClient.security.hasPrivileges({ - body: { - index: [ - { - names: [`logs-${OSQUERY_INTEGRATION_NAME}.result*`], - privileges: ['read'], - }, - ], + if (osqueryContext.security.authz.mode.useRbacForRequest(request)) { + const checkPrivileges = osqueryContext.security.authz.checkPrivilegesDynamicallyWithRequest( + request + ); + const { hasAllRequested } = await checkPrivileges({ + elasticsearch: { + cluster: [], + index: { + [`logs-${OSQUERY_INTEGRATION_NAME}.result*`]: ['read'], + }, }, - }) - ).body; + }); + + return response.ok({ + body: `${hasAllRequested}`, + }); + } return response.ok({ - body: privileges, + body: 'true', }); } ); diff --git a/yarn.lock b/yarn.lock index f1c813d37fc2d..237969e06fa9e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23399,10 +23399,10 @@ react-popper@^2.2.4: react-fast-compare "^3.0.1" warning "^4.0.2" -react-query@^3.21.0: - version "3.21.0" - resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.21.0.tgz#2e099a7906c38eeeb750e8b9b12121a21fa8d9ef" - integrity sha512-5rY5J8OD9f4EdkytjSsdCO+pqbJWKwSIMETfh/UyxqyjLURHE0IhlB+IPNPrzzu/dzK0rRxi5p0IkcCdSfizDQ== +react-query@^3.21.1: + version "3.21.1" + resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.21.1.tgz#8fe4df90bf6c6a93e0552ea9baff211d1b28f6e0" + integrity sha512-aKFLfNJc/m21JBXJk7sR9tDUYPjotWA4EHAKvbZ++GgxaY+eI0tqBxXmGBuJo0Pisis1W4pZWlZgoRv9yE8yjA== dependencies: "@babel/runtime" "^7.5.5" broadcast-channel "^3.4.1" From 42b95d44f4b4e138cd80f968371b25f130412a11 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 6 Sep 2021 06:31:10 -0400 Subject: [PATCH 02/43] [Canvas] `TagCloud` (#106858) (#111257) * Added `tagCloud` to canvas. * Added `icon` to the `tagCloud` element. * Added column name support at `tag_cloud`. * Added condition to `vis_dimension` not to pass invalid index. Added check of accessor index, if such column exists at vis_dimension. Removed checks of column existance from TagCloudChart. Added test for accessing data by column name in addition to a column number. Updated tag_cloud element in Canvas. Fixed types. Removed almost all `any` and `as` types. * Added test suites for `vis_dimension` function. * Added tests for DatatableColumn accessors at tag_cloud_fn and to_ast. * Refactored metrics, tagcloud and tests. Added valid functional tests to metrics and tag_cloud. Fixed types of metrics_vis. Added handling of empty data at tag_cloud renderer. * Added storybook ( still doesn't work ). * Fixed some mistakes. * Added working storybook with mocks. * Added clear storybook for tag_cloud_vis_renderer. * Updated the location of vis_dimension test after movement of the function. * Fixed unused type. * Fixed tests and added handling of the column name at `visualizations/**/*/prepare_log_table.ts` * Reduced the complexity of checking the accessor at `tag_cloud_chart.tsx` * Added comments at unclear places of code. * Added the logic for disabling elements for renderers from disabled plugins. * removed garbage from `kibana.yml`. * Fixed element_strings.test error. * Made changes, based on nits. * Fixed mistake. * Removed `disabled` flag for `expression_*` plugins. * recovered lost comments at the unclear places. * removed dead code. * fixed test errors. * Fixed test error, I hope. * fixed more tests. * fixed code, based on nits. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Yaroslav Kuznietsov --- src/dev/storybook/aliases.ts | 1 + .../expression_tagcloud/.storybook/main.js | 23 +++ .../tagcloud_function.test.ts.snap | 102 +++++++++++- .../tagcloud_function.test.ts | 63 ++++++- .../expression_functions/tagcloud_function.ts | 6 +- .../common/types/expression_functions.ts | 17 +- .../public/__mocks__/format_service.ts | 13 ++ .../public/__mocks__/palettes.ts | 52 ++++++ .../__stories__/tagcloud_renderer.stories.tsx | 125 ++++++++++++++ .../public/components/tag_cloud.scss | 2 + .../components/tagcloud_component.test.tsx | 155 +++++++++++------- .../public/components/tagcloud_component.tsx | 44 +++-- .../tagcloud_renderer.tsx | 44 +++-- .../public/{services.ts => format_service.ts} | 0 .../expression_tagcloud/public/plugin.ts | 2 +- .../components/metric_vis_component.tsx | 19 ++- .../vis_types/metric/public/metric_vis_fn.ts | 21 +-- src/plugins/vis_types/metric/public/types.ts | 6 +- .../public/__snapshots__/to_ast.test.ts.snap | 104 +++++++++++- .../vis_types/tagcloud/public/to_ast.test.ts | 57 ++++++- .../vis_types/tagcloud/public/types.ts | 14 +- .../vis_dimension.test.ts | 70 ++++++++ .../expression_functions/vis_dimension.ts | 14 +- .../common/prepare_log_table.test.ts | 5 +- .../common/prepare_log_table.ts | 23 ++- .../screenshots/baseline/metric_all_data.png | Bin 22339 -> 29776 bytes .../baseline/metric_empty_data.png | Bin 0 -> 5163 bytes .../baseline/metric_invalid_data.png | Bin 3763 -> 1993 bytes .../baseline/tagcloud_empty_data.png | Bin 0 -> 4467 bytes .../metric_empty_data.json} | 2 +- .../baseline/metric_invalid_data.json | 2 +- .../tagcloud_empty_data.json} | 2 +- .../baseline/tagcloud_invalid_data.json | 2 +- .../session/tagcloud_empty_data.json | 1 + .../session/tagcloud_invalid_data.json | 2 +- .../test_suites/run_pipeline/metric.ts | 16 +- .../test_suites/run_pipeline/tag_cloud.ts | 34 ++-- .../canvas_plugin_src/elements/index.ts | 3 +- .../elements/tag_cloud/index.ts | 21 +++ .../canvas/i18n/elements/element_strings.ts | 8 + x-pack/plugins/canvas/public/plugin.tsx | 1 + .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 43 files changed, 891 insertions(+), 187 deletions(-) create mode 100644 src/plugins/chart_expressions/expression_tagcloud/.storybook/main.js create mode 100644 src/plugins/chart_expressions/expression_tagcloud/public/__mocks__/format_service.ts create mode 100644 src/plugins/chart_expressions/expression_tagcloud/public/__mocks__/palettes.ts create mode 100644 src/plugins/chart_expressions/expression_tagcloud/public/__stories__/tagcloud_renderer.stories.tsx rename src/plugins/chart_expressions/expression_tagcloud/public/{services.ts => format_service.ts} (100%) create mode 100644 src/plugins/visualizations/common/expression_functions/vis_dimension.test.ts create mode 100644 test/interpreter_functional/screenshots/baseline/metric_empty_data.png create mode 100644 test/interpreter_functional/screenshots/baseline/tagcloud_empty_data.png rename test/interpreter_functional/snapshots/{session/metric_single_metric_data.json => baseline/metric_empty_data.json} (89%) rename test/interpreter_functional/snapshots/{session/partial_test_1.json => baseline/tagcloud_empty_data.json} (65%) create mode 100644 test/interpreter_functional/snapshots/session/tagcloud_empty_data.json create mode 100644 x-pack/plugins/canvas/canvas_plugin_src/elements/tag_cloud/index.ts diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index 965a716098f33..9395c5fdf8834 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -23,6 +23,7 @@ export const storybookAliases = { expression_repeat_image: 'src/plugins/expression_repeat_image/.storybook', expression_reveal_image: 'src/plugins/expression_reveal_image/.storybook', expression_shape: 'src/plugins/expression_shape/.storybook', + expression_tagcloud: 'src/plugins/chart_expressions/expression_tagcloud/.storybook', infra: 'x-pack/plugins/infra/.storybook', security_solution: 'x-pack/plugins/security_solution/.storybook', ui_actions_enhanced: 'x-pack/plugins/ui_actions_enhanced/.storybook', diff --git a/src/plugins/chart_expressions/expression_tagcloud/.storybook/main.js b/src/plugins/chart_expressions/expression_tagcloud/.storybook/main.js new file mode 100644 index 0000000000000..cb483d5394285 --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/.storybook/main.js @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { defaultConfig } from '@kbn/storybook'; +import webpackMerge from 'webpack-merge'; +import { resolve } from 'path'; + +const mockConfig = { + resolve: { + alias: { + '../format_service': resolve(__dirname, '../public/__mocks__/format_service.ts'), + }, + }, +}; + +module.exports = { + ...defaultConfig, + webpackFinal: (config) => webpackMerge(config, mockConfig), +}; diff --git a/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/__snapshots__/tagcloud_function.test.ts.snap b/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/__snapshots__/tagcloud_function.test.ts.snap index 56b24f0ae004f..da116bc50f370 100644 --- a/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/__snapshots__/tagcloud_function.test.ts.snap +++ b/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/__snapshots__/tagcloud_function.test.ts.snap @@ -4,23 +4,35 @@ exports[`interpreter/functions#tagcloud logs correct datatable to inspector 1`] Object { "columns": Array [ Object { - "id": "col-0-1", + "id": "Count", "meta": Object { "dimensionName": "Tag size", }, "name": "Count", }, + Object { + "id": "country", + "meta": Object { + "dimensionName": "Tags", + }, + "name": "country", + }, ], "rows": Array [ Object { - "col-0-1": 0, + "Count": 0, + "country": "US", + }, + Object { + "Count": 10, + "country": "UK", }, ], "type": "datatable", } `; -exports[`interpreter/functions#tagcloud returns an object with the correct structure 1`] = ` +exports[`interpreter/functions#tagcloud returns an object with the correct structure for number accessors 1`] = ` Object { "as": "tagcloud", "type": "render", @@ -29,13 +41,22 @@ Object { "visData": Object { "columns": Array [ Object { - "id": "col-0-1", + "id": "Count", "name": "Count", }, + Object { + "id": "country", + "name": "country", + }, ], "rows": Array [ Object { - "col-0-1": 0, + "Count": 0, + "country": "US", + }, + Object { + "Count": 10, + "country": "UK", }, ], "type": "datatable", @@ -43,16 +64,81 @@ Object { "visParams": Object { "bucket": Object { "accessor": 1, + }, + "maxFontSize": 72, + "metric": Object { + "accessor": 0, + }, + "minFontSize": 18, + "orientation": "single", + "palette": Object { + "name": "default", + "type": "palette", + }, + "scale": "linear", + "showLabel": true, + }, + "visType": "tagcloud", + }, +} +`; + +exports[`interpreter/functions#tagcloud returns an object with the correct structure for string accessors 1`] = ` +Object { + "as": "tagcloud", + "type": "render", + "value": Object { + "syncColors": false, + "visData": Object { + "columns": Array [ + Object { + "id": "Count", + "name": "Count", + }, + Object { + "id": "country", + "name": "country", + }, + ], + "rows": Array [ + Object { + "Count": 0, + "country": "US", + }, + Object { + "Count": 10, + "country": "UK", + }, + ], + "type": "datatable", + }, + "visParams": Object { + "bucket": Object { + "accessor": Object { + "id": "country", + "meta": Object { + "type": "string", + }, + "name": "country", + }, "format": Object { - "id": "number", + "params": Object {}, }, + "type": "vis_dimension", }, "maxFontSize": 72, "metric": Object { - "accessor": 0, + "accessor": Object { + "id": "Count", + "meta": Object { + "type": "number", + }, + "name": "Count", + }, "format": Object { - "id": "number", + "params": Object {}, }, + "type": "vis_dimension", }, "minFontSize": 18, "orientation": "single", diff --git a/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.test.ts b/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.test.ts index 2c6e021b5107a..8abdc36704b45 100644 --- a/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.test.ts +++ b/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.test.ts @@ -9,14 +9,23 @@ import { tagcloudFunction } from './tagcloud_function'; import { functionWrapper } from '../../../../expressions/common/expression_functions/specs/tests/utils'; +import { ExpressionValueVisDimension } from '../../../../visualizations/public'; import { Datatable } from '../../../../expressions/common/expression_types/specs'; describe('interpreter/functions#tagcloud', () => { const fn = functionWrapper(tagcloudFunction()); + const column1 = 'Count'; + const column2 = 'country'; const context = { type: 'datatable', - rows: [{ 'col-0-1': 0 }], - columns: [{ id: 'col-0-1', name: 'Count' }], + columns: [ + { id: column1, name: column1 }, + { id: column2, name: column2 }, + ], + rows: [ + { [column1]: 0, [column2]: 'US' }, + { [column1]: 10, [column2]: 'UK' }, + ], }; const visConfig = { scale: 'linear', @@ -24,12 +33,52 @@ describe('interpreter/functions#tagcloud', () => { minFontSize: 18, maxFontSize: 72, showLabel: true, - metric: { accessor: 0, format: { id: 'number' } }, - bucket: { accessor: 1, format: { id: 'number' } }, }; - it('returns an object with the correct structure', () => { - const actual = fn(context, visConfig, undefined); + const numberAccessors = { + metric: { accessor: 0 }, + bucket: { accessor: 1 }, + }; + + const stringAccessors: { + metric: ExpressionValueVisDimension; + bucket: ExpressionValueVisDimension; + } = { + metric: { + type: 'vis_dimension', + accessor: { + id: column1, + name: column1, + meta: { + type: 'number', + }, + }, + format: { + params: {}, + }, + }, + bucket: { + type: 'vis_dimension', + accessor: { + id: column2, + name: column2, + meta: { + type: 'string', + }, + }, + format: { + params: {}, + }, + }, + }; + + it('returns an object with the correct structure for number accessors', () => { + const actual = fn(context, { ...visConfig, ...numberAccessors }, undefined); + expect(actual).toMatchSnapshot(); + }); + + it('returns an object with the correct structure for string accessors', () => { + const actual = fn(context, { ...visConfig, ...stringAccessors }, undefined); expect(actual).toMatchSnapshot(); }); @@ -44,7 +93,7 @@ describe('interpreter/functions#tagcloud', () => { }, }, }; - await fn(context, visConfig, handlers as any); + await fn(context, { ...visConfig, ...numberAccessors }, handlers as any); expect(loggedTable!).toMatchSnapshot(); }); diff --git a/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.ts b/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.ts index c3553c4660ce9..2ce50e94aeda3 100644 --- a/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.ts +++ b/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { prepareLogTable, Dimension } from '../../../../visualizations/common/prepare_log_table'; -import { TagCloudVisParams } from '../types'; +import { TagCloudRendererParams } from '../types'; import { ExpressionTagcloudFunction } from '../types'; import { EXPRESSION_NAME } from '../constants'; @@ -125,7 +125,7 @@ export const tagcloudFunction: ExpressionTagcloudFunction = () => { }, }, fn(input, args, handlers) { - const visParams = { + const visParams: TagCloudRendererParams = { scale: args.scale, orientation: args.orientation, minFontSize: args.minFontSize, @@ -139,7 +139,7 @@ export const tagcloudFunction: ExpressionTagcloudFunction = () => { type: 'palette', name: args.palette, }, - } as TagCloudVisParams; + }; if (handlers?.inspectorAdapters?.tables) { const argsTable: Dimension[] = [[[args.metric], dimension.tagSize]]; diff --git a/src/plugins/chart_expressions/expression_tagcloud/common/types/expression_functions.ts b/src/plugins/chart_expressions/expression_tagcloud/common/types/expression_functions.ts index b1aba30380b59..1ee0434e1603e 100644 --- a/src/plugins/chart_expressions/expression_tagcloud/common/types/expression_functions.ts +++ b/src/plugins/chart_expressions/expression_tagcloud/common/types/expression_functions.ts @@ -10,19 +10,10 @@ import { Datatable, ExpressionFunctionDefinition, ExpressionValueRender, - SerializedFieldFormat, } from '../../../../expressions'; import { ExpressionValueVisDimension } from '../../../../visualizations/common'; import { EXPRESSION_NAME } from '../constants'; -interface Dimension { - accessor: number; - format: { - id?: string; - params?: SerializedFieldFormat; - }; -} - interface TagCloudCommonParams { scale: 'linear' | 'log' | 'square root'; orientation: 'single' | 'right angled' | 'multiple'; @@ -36,16 +27,16 @@ export interface TagCloudVisConfig extends TagCloudCommonParams { bucket?: ExpressionValueVisDimension; } -export interface TagCloudVisParams extends TagCloudCommonParams { +export interface TagCloudRendererParams extends TagCloudCommonParams { palette: PaletteOutput; - metric: Dimension; - bucket?: Dimension; + metric: ExpressionValueVisDimension; + bucket?: ExpressionValueVisDimension; } export interface TagcloudRendererConfig { visType: typeof EXPRESSION_NAME; visData: Datatable; - visParams: TagCloudVisParams; + visParams: TagCloudRendererParams; syncColors: boolean; } diff --git a/src/plugins/chart_expressions/expression_tagcloud/public/__mocks__/format_service.ts b/src/plugins/chart_expressions/expression_tagcloud/public/__mocks__/format_service.ts new file mode 100644 index 0000000000000..77f6d8eb0bf37 --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/public/__mocks__/format_service.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const getFormatService = () => ({ + deserialize: (target: any) => ({ + convert: (text: string, format: string) => text, + }), +}); diff --git a/src/plugins/chart_expressions/expression_tagcloud/public/__mocks__/palettes.ts b/src/plugins/chart_expressions/expression_tagcloud/public/__mocks__/palettes.ts new file mode 100644 index 0000000000000..7ca00b58f5624 --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/public/__mocks__/palettes.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PaletteDefinition, SeriesLayer } from '../../../../charts/public'; +import { random } from 'lodash'; + +export const getPaletteRegistry = () => { + const colors = [ + '#54B399', + '#6092C0', + '#D36086', + '#9170B8', + '#CA8EAE', + '#D6BF57', + '#B9A888', + '#DA8B45', + '#AA6556', + '#E7664C', + ]; + const mockPalette: PaletteDefinition = { + id: 'default', + title: 'My Palette', + getCategoricalColor: (_: SeriesLayer[]) => colors[random(0, colors.length - 1)], + getCategoricalColors: (num: number) => colors, + toExpression: () => ({ + type: 'expression', + chain: [ + { + type: 'function', + function: 'system_palette', + arguments: { + name: ['default'], + }, + }, + ], + }), + }; + + return { + get: (name: string) => mockPalette, + getAll: () => [mockPalette], + }; +}; + +export const palettes = { + getPalettes: async () => getPaletteRegistry(), +}; diff --git a/src/plugins/chart_expressions/expression_tagcloud/public/__stories__/tagcloud_renderer.stories.tsx b/src/plugins/chart_expressions/expression_tagcloud/public/__stories__/tagcloud_renderer.stories.tsx new file mode 100644 index 0000000000000..1e0dc2600d1a1 --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/public/__stories__/tagcloud_renderer.stories.tsx @@ -0,0 +1,125 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { tagcloudRenderer } from '../expression_renderers'; +import { Render } from '../../../../presentation_util/public/__stories__'; +import { TagcloudRendererConfig } from '../../common/types'; +import { palettes } from '../__mocks__/palettes'; + +const config: TagcloudRendererConfig = { + visType: 'tagcloud', + visData: { + type: 'datatable', + rows: [ + { country: 'US', Count: 14 }, + { country: 'JP', Count: 13 }, + { country: 'UK', Count: 13 }, + { country: 'CN', Count: 8 }, + { country: 'TZ', Count: 14 }, + { country: 'NL', Count: 11 }, + { country: 'AZ', Count: 14 }, + { country: 'BR', Count: 11 }, + { country: 'DE', Count: 16 }, + { country: 'SA', Count: 11 }, + { country: 'RU', Count: 9 }, + { country: 'IN', Count: 9 }, + { country: 'PH', Count: 7 }, + ], + columns: [ + { id: 'country', name: 'country', meta: { type: 'string' } }, + { id: 'Count', name: 'Count', meta: { type: 'number' } }, + ], + }, + visParams: { + scale: 'linear', + orientation: 'single', + minFontSize: 18, + maxFontSize: 72, + showLabel: true, + metric: { + type: 'vis_dimension', + accessor: { id: 'Count', name: 'Count', meta: { type: 'number' } }, + format: { id: 'string', params: {} }, + }, + bucket: { + type: 'vis_dimension', + accessor: { id: 'country', name: 'country', meta: { type: 'string' } }, + format: { id: 'string', params: {} }, + }, + palette: { type: 'palette', name: 'default' }, + }, + syncColors: false, +}; + +const containerSize = { + width: '700px', + height: '700px', +}; + +storiesOf('renderers/tag_cloud_vis', module) + .add('Default', () => { + return ( + tagcloudRenderer({ palettes })} config={config} {...containerSize} /> + ); + }) + .add('With log scale', () => { + return ( + tagcloudRenderer({ palettes })} + config={{ ...config, visParams: { ...config.visParams, scale: 'log' } }} + {...containerSize} + /> + ); + }) + .add('With square root scale', () => { + return ( + tagcloudRenderer({ palettes })} + config={{ ...config, visParams: { ...config.visParams, scale: 'square root' } }} + {...containerSize} + /> + ); + }) + .add('With right angled orientation', () => { + return ( + tagcloudRenderer({ palettes })} + config={{ ...config, visParams: { ...config.visParams, orientation: 'right angled' } }} + {...containerSize} + /> + ); + }) + .add('With multiple orientations', () => { + return ( + tagcloudRenderer({ palettes })} + config={{ ...config, visParams: { ...config.visParams, orientation: 'multiple' } }} + {...containerSize} + /> + ); + }) + .add('With hidden label', () => { + return ( + tagcloudRenderer({ palettes })} + config={{ ...config, visParams: { ...config.visParams, showLabel: false } }} + {...containerSize} + /> + ); + }) + .add('With empty results', () => { + return ( + tagcloudRenderer({ palettes })} + config={{ ...config, visData: { ...config.visData, rows: [] } }} + {...containerSize} + /> + ); + }); diff --git a/src/plugins/chart_expressions/expression_tagcloud/public/components/tag_cloud.scss b/src/plugins/chart_expressions/expression_tagcloud/public/components/tag_cloud.scss index 51b5e9dedd844..8a017150fe195 100644 --- a/src/plugins/chart_expressions/expression_tagcloud/public/components/tag_cloud.scss +++ b/src/plugins/chart_expressions/expression_tagcloud/public/components/tag_cloud.scss @@ -9,6 +9,8 @@ flex: 1 1 0; display: flex; flex-direction: column; + // it is used for rendering at `Canvas`. + height: 100%; } .tgcChart__wrapper text { diff --git a/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.test.tsx b/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.test.tsx index 542a9c1cd9bf7..f65630e422cce 100644 --- a/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.test.tsx +++ b/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.test.tsx @@ -6,15 +6,15 @@ * Side Public License, v 1. */ import React from 'react'; -import { Wordcloud, Settings } from '@elastic/charts'; +import { Wordcloud, Settings, WordcloudSpec } from '@elastic/charts'; import { chartPluginMock } from '../../../../charts/public/mocks'; import type { Datatable } from '../../../../expressions/public'; import { mount } from 'enzyme'; import { findTestSubject } from '@elastic/eui/lib/test'; import TagCloudChart, { TagCloudChartProps } from './tagcloud_component'; -import { TagCloudVisParams } from '../../common/types'; +import { TagCloudRendererParams } from '../../common/types'; -jest.mock('../services', () => ({ +jest.mock('../format_service', () => ({ getFormatService: jest.fn(() => { return { deserialize: jest.fn(), @@ -23,29 +23,34 @@ jest.mock('../services', () => ({ })); const palettesRegistry = chartPluginMock.createPaletteRegistry(); -const visData = ({ +const geoDestId = 'geo.dest'; +const countId = 'Count'; +const visData: Datatable = { + type: 'datatable', columns: [ { - id: 'col-0', - name: 'geo.dest: Descending', + id: geoDestId, + name: `${geoDestId}: Descending`, + meta: { type: 'string' }, }, { - id: 'col-1', + id: countId, name: 'Count', + meta: { type: 'number' }, }, ], rows: [ - { 'col-0': 'CN', 'col-1': 26 }, - { 'col-0': 'IN', 'col-1': 17 }, - { 'col-0': 'US', 'col-1': 6 }, - { 'col-0': 'DE', 'col-1': 4 }, - { 'col-0': 'BR', 'col-1': 3 }, + { [geoDestId]: 'CN', [countId]: 26 }, + { [geoDestId]: 'IN', [countId]: 17 }, + { [geoDestId]: 'US', [countId]: 6 }, + { [geoDestId]: 'DE', [countId]: 4 }, + { [geoDestId]: 'BR', [countId]: 3 }, ], -} as unknown) as Datatable; +}; -const visParams = { - bucket: { accessor: 0, format: {} }, - metric: { accessor: 1, format: {} }, +const visParams: TagCloudRendererParams = { + bucket: { type: 'vis_dimension', accessor: 0, format: { params: {} } }, + metric: { type: 'vis_dimension', accessor: 1, format: { params: {} } }, scale: 'linear', orientation: 'single', palette: { @@ -55,13 +60,42 @@ const visParams = { minFontSize: 12, maxFontSize: 70, showLabel: true, -} as TagCloudVisParams; +}; + +const formattedData: WordcloudSpec['data'] = [ + { + color: 'black', + text: 'CN', + weight: 1, + }, + { + color: 'black', + text: 'IN', + weight: 0.6086956521739131, + }, + { + color: 'black', + text: 'US', + weight: 0.13043478260869565, + }, + { + color: 'black', + text: 'DE', + weight: 0.043478260869565216, + }, + { + color: 'black', + text: 'BR', + weight: 0, + }, +]; describe('TagCloudChart', function () { - let wrapperProps: TagCloudChartProps; + let wrapperPropsWithIndexes: TagCloudChartProps; + let wrapperPropsWithColumnNames: TagCloudChartProps; beforeAll(() => { - wrapperProps = { + wrapperPropsWithIndexes = { visData, visParams, palettesRegistry, @@ -70,68 +104,77 @@ describe('TagCloudChart', function () { syncColors: false, visType: 'tagcloud', }; + + wrapperPropsWithColumnNames = { + visData, + visParams: { + ...visParams, + bucket: { + type: 'vis_dimension', + accessor: { + id: geoDestId, + name: geoDestId, + meta: { type: 'string' }, + }, + format: { id: 'string', params: {} }, + }, + metric: { + type: 'vis_dimension', + accessor: { + id: countId, + name: countId, + meta: { type: 'number' }, + }, + format: { id: 'number', params: {} }, + }, + }, + palettesRegistry, + fireEvent: jest.fn(), + renderComplete: jest.fn(), + syncColors: false, + visType: 'tagcloud', + }; }); - it('renders the Wordcloud component', async () => { - const component = mount(); + it('renders the Wordcloud component with', async () => { + const component = mount(); expect(component.find(Wordcloud).length).toBe(1); }); it('renders the label correctly', async () => { - const component = mount(); + const component = mount(); const label = findTestSubject(component, 'tagCloudLabel'); expect(label.text()).toEqual('geo.dest: Descending - Count'); }); it('not renders the label if showLabel setting is off', async () => { const newVisParams = { ...visParams, showLabel: false }; - const newProps = { ...wrapperProps, visParams: newVisParams }; + const newProps = { ...wrapperPropsWithIndexes, visParams: newVisParams }; const component = mount(); const label = findTestSubject(component, 'tagCloudLabel'); expect(label.length).toBe(0); }); - it('receives the data on the correct format', () => { - const component = mount(); - expect(component.find(Wordcloud).prop('data')).toStrictEqual([ - { - color: 'black', - text: 'CN', - weight: 1, - }, - { - color: 'black', - text: 'IN', - weight: 0.6086956521739131, - }, - { - color: 'black', - text: 'US', - weight: 0.13043478260869565, - }, - { - color: 'black', - text: 'DE', - weight: 0.043478260869565216, - }, - { - color: 'black', - text: 'BR', - weight: 0, - }, - ]); + it('receives the data in the correct format for bucket and metric accessors of type number', () => { + const component = mount(); + expect(component.find(Wordcloud).prop('data')).toStrictEqual(formattedData); + }); + + it('receives the data in the correct format for bucket and metric accessors of type DatatableColumn', () => { + const component = mount(); + expect(component.find(Wordcloud).prop('data')).toStrictEqual(formattedData); }); it('sets the angles correctly', async () => { - const newVisParams = { ...visParams, orientation: 'right angled' } as TagCloudVisParams; - const newProps = { ...wrapperProps, visParams: newVisParams }; + const newVisParams: TagCloudRendererParams = { ...visParams, orientation: 'right angled' }; + const newProps = { ...wrapperPropsWithIndexes, visParams: newVisParams }; const component = mount(); expect(component.find(Wordcloud).prop('endAngle')).toBe(90); expect(component.find(Wordcloud).prop('angleCount')).toBe(2); }); it('calls filter callback', () => { - const component = mount(); + const component = mount(); component.find(Settings).prop('onElementClick')!([ [ { @@ -145,6 +188,6 @@ describe('TagCloudChart', function () { }, ], ]); - expect(wrapperProps.fireEvent).toHaveBeenCalled(); + expect(wrapperPropsWithIndexes.fireEvent).toHaveBeenCalled(); }); }); diff --git a/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.tsx b/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.tsx index 163a2e8ce38ac..b7d38c71f5867 100644 --- a/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.tsx +++ b/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.tsx @@ -12,8 +12,13 @@ import { throttle } from 'lodash'; import { EuiIconTip, EuiResizeObserver } from '@elastic/eui'; import { Chart, Settings, Wordcloud, RenderChangeListener } from '@elastic/charts'; import type { PaletteRegistry } from '../../../../charts/public'; -import type { IInterpreterRenderHandlers } from '../../../../expressions/public'; -import { getFormatService } from '../services'; +import { + Datatable, + DatatableColumn, + IInterpreterRenderHandlers, +} from '../../../../expressions/public'; +import { getFormatService } from '../format_service'; +import { ExpressionValueVisDimension } from '../../../../visualizations/public'; import { TagcloudRendererConfig } from '../../common/types'; import './tag_cloud.scss'; @@ -68,6 +73,17 @@ const ORIENTATIONS = { }, }; +const getColumn = ( + accessor: ExpressionValueVisDimension['accessor'], + columns: Datatable['columns'] +): DatatableColumn => { + if (typeof accessor === 'number') { + return columns[accessor]; + } + + return columns.filter(({ id }) => id === accessor.id)[0]; +}; + export const TagCloudChart = ({ visData, visParams, @@ -81,18 +97,18 @@ export const TagCloudChart = ({ const bucketFormatter = bucket ? getFormatService().deserialize(bucket.format) : null; const tagCloudData = useMemo(() => { - const tagColumn = bucket ? visData.columns[bucket.accessor].id : -1; - const metricColumn = visData.columns[metric.accessor]?.id; + const tagColumn = bucket ? getColumn(bucket.accessor, visData.columns).id : null; + const metricColumn = getColumn(metric.accessor, visData.columns).id; const metrics = visData.rows.map((row) => row[metricColumn]); - const values = bucket ? visData.rows.map((row) => row[tagColumn]) : []; + const values = bucket && tagColumn !== null ? visData.rows.map((row) => row[tagColumn]) : []; const maxValue = Math.max(...metrics); const minValue = Math.min(...metrics); return visData.rows.map((row) => { - const tag = row[tagColumn] === undefined ? 'all' : row[tagColumn]; + const tag = tagColumn === null ? 'all' : row[tagColumn]; return { - text: (bucketFormatter ? bucketFormatter.convert(tag, 'text') : tag) as string, + text: bucketFormatter ? bucketFormatter.convert(tag, 'text') : tag, weight: tag === 'all' || visData.rows.length <= 1 ? 1 @@ -112,7 +128,9 @@ export const TagCloudChart = ({ ]); const label = bucket - ? `${visData.columns[bucket.accessor].name} - ${visData.columns[metric.accessor].name}` + ? `${getColumn(bucket.accessor, visData.columns).name} - ${ + getColumn(metric.accessor, visData.columns).name + }` : ''; const onRenderChange = useCallback( @@ -133,17 +151,17 @@ export const TagCloudChart = ({ ); const handleWordClick = useCallback( - (d) => { + (elements) => { if (!bucket) { return; } - const termsBucket = visData.columns[bucket.accessor]; - const clickedValue = d[0][0].text; + const termsBucketId = getColumn(bucket.accessor, visData.columns).id; + const clickedValue = elements[0][0].text; const rowIndex = visData.rows.findIndex((row) => { const formattedValue = bucketFormatter - ? bucketFormatter.convert(row[termsBucket.id], 'text') - : row[termsBucket.id]; + ? bucketFormatter.convert(row[termsBucketId], 'text') + : row[termsBucketId]; return formattedValue === clickedValue; }); diff --git a/src/plugins/chart_expressions/expression_tagcloud/public/expression_renderers/tagcloud_renderer.tsx b/src/plugins/chart_expressions/expression_tagcloud/public/expression_renderers/tagcloud_renderer.tsx index 58e177dac6775..294371b3a5703 100644 --- a/src/plugins/chart_expressions/expression_tagcloud/public/expression_renderers/tagcloud_renderer.tsx +++ b/src/plugins/chart_expressions/expression_tagcloud/public/expression_renderers/tagcloud_renderer.tsx @@ -5,15 +5,16 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ + import React, { lazy } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; +import { ClassNames } from '@emotion/react'; import { i18n } from '@kbn/i18n'; -import { ExpressionRenderDefinition } from '../../../../expressions/common'; import { VisualizationContainer } from '../../../../visualizations/public'; -import { withSuspense } from '../../../../presentation_util/public'; -import { TagcloudRendererConfig } from '../../common/types'; +import { ExpressionRenderDefinition } from '../../../../expressions/common/expression_renderers'; import { ExpressioTagcloudRendererDependencies } from '../plugin'; +import { TagcloudRendererConfig } from '../../common/types'; import { EXPRESSION_NAME } from '../../common'; export const strings = { @@ -27,8 +28,11 @@ export const strings = { }), }; -const LazyTagcloudComponent = lazy(() => import('../components/tagcloud_component')); -const TagcloudComponent = withSuspense(LazyTagcloudComponent); +const tagCloudVisClass = { + height: '100%', +}; + +const TagCloudChart = lazy(() => import('../components/tagcloud_component')); export const tagcloudRenderer: ( deps: ExpressioTagcloudRendererDependencies @@ -43,17 +47,29 @@ export const tagcloudRenderer: ( }); const palettesRegistry = await palettes.getPalettes(); + const showNoResult = config.visData.rows.length === 0; + render( - - - + + {({ css, cx }) => ( + + + + )} + , domNode ); diff --git a/src/plugins/chart_expressions/expression_tagcloud/public/services.ts b/src/plugins/chart_expressions/expression_tagcloud/public/format_service.ts similarity index 100% rename from src/plugins/chart_expressions/expression_tagcloud/public/services.ts rename to src/plugins/chart_expressions/expression_tagcloud/public/format_service.ts diff --git a/src/plugins/chart_expressions/expression_tagcloud/public/plugin.ts b/src/plugins/chart_expressions/expression_tagcloud/public/plugin.ts index 7cbc9ac7c6706..9ffb910bde213 100644 --- a/src/plugins/chart_expressions/expression_tagcloud/public/plugin.ts +++ b/src/plugins/chart_expressions/expression_tagcloud/public/plugin.ts @@ -12,7 +12,7 @@ import { ChartsPluginSetup } from '../../../charts/public'; import { tagcloudRenderer } from './expression_renderers'; import { tagcloudFunction } from '../common/expression_functions'; import { FieldFormatsStart } from '../../../field_formats/public'; -import { setFormatService } from './services'; +import { setFormatService } from './format_service'; interface SetupDeps { expressions: ExpressionsSetup; diff --git a/src/plugins/vis_types/metric/public/components/metric_vis_component.tsx b/src/plugins/vis_types/metric/public/components/metric_vis_component.tsx index c3735bdc0d79a..837ec5ff60dc5 100644 --- a/src/plugins/vis_types/metric/public/components/metric_vis_component.tsx +++ b/src/plugins/vis_types/metric/public/components/metric_vis_component.tsx @@ -16,7 +16,7 @@ import { Datatable } from '../../../../expressions/public'; import { getHeatmapColors } from '../../../../charts/public'; import { VisParams, MetricVisMetric } from '../types'; import { getFormatService } from '../services'; -import { SchemaConfig } from '../../../../visualizations/public'; +import { ExpressionValueVisDimension } from '../../../../visualizations/public'; import { Range } from '../../../../expressions/public'; import './metric_vis.scss'; @@ -98,6 +98,16 @@ class MetricVisComponent extends Component { return fieldFormatter.convert(value, format); }; + private getColumn( + accessor: ExpressionValueVisDimension['accessor'], + columns: Datatable['columns'] = [] + ) { + if (typeof accessor === 'number') { + return columns[accessor]; + } + return columns.filter(({ id }) => accessor.id === id)[0]; + } + private processTableGroups(table: Datatable) { const config = this.props.visParams.metric; const dimensions = this.props.visParams.dimensions; @@ -112,13 +122,12 @@ class MetricVisComponent extends Component { let bucketFormatter: IFieldFormat; if (dimensions.bucket) { - bucketColumnId = table.columns[dimensions.bucket.accessor].id; + bucketColumnId = this.getColumn(dimensions.bucket.accessor, table.columns).id; bucketFormatter = getFormatService().deserialize(dimensions.bucket.format); } - dimensions.metrics.forEach((metric: SchemaConfig) => { - const columnIndex = metric.accessor; - const column = table?.columns[columnIndex]; + dimensions.metrics.forEach((metric: ExpressionValueVisDimension) => { + const column = this.getColumn(metric.accessor, table?.columns); const formatter = getFormatService().deserialize(metric.format); table.rows.forEach((row, rowIndex) => { let title = column.name; diff --git a/src/plugins/vis_types/metric/public/metric_vis_fn.ts b/src/plugins/vis_types/metric/public/metric_vis_fn.ts index 9a144defed4e7..210552732bc0a 100644 --- a/src/plugins/vis_types/metric/public/metric_vis_fn.ts +++ b/src/plugins/vis_types/metric/public/metric_vis_fn.ts @@ -15,9 +15,10 @@ import { Render, Style, } from '../../../expressions/public'; -import { visType, DimensionsVisParam, VisParams } from './types'; +import { visType, VisParams } from './types'; import { prepareLogTable, Dimension } from '../../../visualizations/public'; import { ColorSchemas, vislibColorMaps, ColorMode } from '../../../charts/public'; +import { ExpressionValueVisDimension } from '../../../visualizations/public'; export type Input = Datatable; @@ -32,8 +33,8 @@ interface Arguments { subText: string; colorRange: Range[]; font: Style; - metric: any[]; // these aren't typed yet - bucket: any; // these aren't typed yet + metric: ExpressionValueVisDimension[]; + bucket: ExpressionValueVisDimension; } export interface MetricVisRenderValue { @@ -150,14 +151,6 @@ export const createMetricVisFn = (): MetricVisExpressionFunctionDefinition => ({ }, }, fn(input, args, handlers) { - const dimensions: DimensionsVisParam = { - metrics: args.metric, - }; - - if (args.bucket) { - dimensions.bucket = args.bucket; - } - if (args.percentageMode && (!args.colorRange || args.colorRange.length === 0)) { throw new Error('colorRange must be provided when using percentageMode'); } @@ -184,6 +177,7 @@ export const createMetricVisFn = (): MetricVisExpressionFunctionDefinition => ({ const logTable = prepareLogTable(input, argsTable); handlers.inspectorAdapters.tables.logDatatable('default', logTable); } + return { type: 'render', as: 'metric_vis', @@ -209,7 +203,10 @@ export const createMetricVisFn = (): MetricVisExpressionFunctionDefinition => ({ fontSize, }, }, - dimensions, + dimensions: { + metrics: args.metric, + ...(args.bucket ? { bucket: args.bucket } : {}), + }, }, }, }; diff --git a/src/plugins/vis_types/metric/public/types.ts b/src/plugins/vis_types/metric/public/types.ts index 1baaa25959f31..8e86c0217bba6 100644 --- a/src/plugins/vis_types/metric/public/types.ts +++ b/src/plugins/vis_types/metric/public/types.ts @@ -7,14 +7,14 @@ */ import { Range } from '../../../expressions/public'; -import { SchemaConfig } from '../../../visualizations/public'; +import { ExpressionValueVisDimension } from '../../../visualizations/public'; import { ColorMode, Labels, Style, ColorSchemas } from '../../../charts/public'; export const visType = 'metric'; export interface DimensionsVisParam { - metrics: SchemaConfig[]; - bucket?: SchemaConfig; + metrics: ExpressionValueVisDimension[]; + bucket?: ExpressionValueVisDimension; } export interface MetricVisParam { diff --git a/src/plugins/vis_types/tagcloud/public/__snapshots__/to_ast.test.ts.snap b/src/plugins/vis_types/tagcloud/public/__snapshots__/to_ast.test.ts.snap index fed6fb54288f2..9e4c3071db8d6 100644 --- a/src/plugins/vis_types/tagcloud/public/__snapshots__/to_ast.test.ts.snap +++ b/src/plugins/vis_types/tagcloud/public/__snapshots__/to_ast.test.ts.snap @@ -1,6 +1,108 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`tagcloud vis toExpressionAst function should match snapshot params fulfilled 1`] = ` +exports[`tagcloud vis toExpressionAst function should match snapshot params fulfilled with DatatableColumn vis_dimension.accessor at metric 1`] = ` +Object { + "chain": Array [ + Object { + "arguments": Object { + "aggs": Array [], + "index": Array [ + Object { + "chain": Array [ + Object { + "arguments": Object { + "id": Array [ + "123", + ], + }, + "function": "indexPatternLoad", + "type": "function", + }, + ], + "type": "expression", + }, + ], + "metricsAtAllLevels": Array [ + false, + ], + "partialRows": Array [ + false, + ], + }, + "function": "esaggs", + "type": "function", + }, + Object { + "arguments": Object { + "bucket": Array [ + Object { + "chain": Array [ + Object { + "arguments": Object { + "accessor": Array [ + 0, + ], + "format": Array [ + "terms", + ], + "formatParams": Array [ + "{\\"id\\":\\"string\\",\\"otherBucketLabel\\":\\"Other\\",\\"missingBucketLabel\\":\\"Missing\\"}", + ], + }, + "function": "visdimension", + "type": "function", + }, + ], + "type": "expression", + }, + ], + "maxFontSize": Array [ + 15, + ], + "metric": Array [ + Object { + "chain": Array [ + Object { + "arguments": Object { + "accessor": Array [ + 1, + ], + "format": Array [ + "number", + ], + }, + "function": "visdimension", + "type": "function", + }, + ], + "type": "expression", + }, + ], + "minFontSize": Array [ + 5, + ], + "orientation": Array [ + "single", + ], + "palette": Array [ + "default", + ], + "scale": Array [ + "linear", + ], + "showLabel": Array [ + true, + ], + }, + "function": "tagcloud", + "type": "function", + }, + ], + "type": "expression", +} +`; + +exports[`tagcloud vis toExpressionAst function should match snapshot params fulfilled with number vis_dimension.accessor at metric 1`] = ` Object { "chain": Array [ Object { diff --git a/src/plugins/vis_types/tagcloud/public/to_ast.test.ts b/src/plugins/vis_types/tagcloud/public/to_ast.test.ts index c70448ab113cb..6de1d4fb3e75d 100644 --- a/src/plugins/vis_types/tagcloud/public/to_ast.test.ts +++ b/src/plugins/vis_types/tagcloud/public/to_ast.test.ts @@ -6,11 +6,11 @@ * Side Public License, v 1. */ -import { Vis } from 'src/plugins/visualizations/public'; +import { Vis, VisToExpressionAstParams } from '../../../visualizations/public'; import { toExpressionAst } from './to_ast'; import { TagCloudVisParams } from './types'; -const mockSchemas = { +const mockedSchemas = { metric: [{ accessor: 1, format: { id: 'number' }, params: {}, label: 'Count', aggType: 'count' }], segment: [ { @@ -31,14 +31,14 @@ const mockSchemas = { }; jest.mock('../../../visualizations/public', () => ({ - getVisSchemas: () => mockSchemas, + getVisSchemas: () => mockedSchemas, })); describe('tagcloud vis toExpressionAst function', () => { let vis: Vis; beforeEach(() => { - vis = { + vis = ({ isHierarchical: () => false, type: {}, params: { @@ -51,15 +51,15 @@ describe('tagcloud vis toExpressionAst function', () => { aggs: [], }, }, - } as any; + } as unknown) as Vis; }); it('should match snapshot without params', () => { - const actual = toExpressionAst(vis, {} as any); + const actual = toExpressionAst(vis, {} as VisToExpressionAstParams); expect(actual).toMatchSnapshot(); }); - it('should match snapshot params fulfilled', () => { + it('should match snapshot params fulfilled with number vis_dimension.accessor at metric', () => { vis.params = { scale: 'linear', orientation: 'single', @@ -70,9 +70,48 @@ describe('tagcloud vis toExpressionAst function', () => { type: 'palette', name: 'default', }, - metric: { accessor: 0, format: { id: 'number' } }, + metric: { + type: 'vis_dimension', + accessor: 0, + format: { + id: 'number', + params: { + id: 'number', + }, + }, + }, + }; + const actual = toExpressionAst(vis, {} as VisToExpressionAstParams); + expect(actual).toMatchSnapshot(); + }); + + it('should match snapshot params fulfilled with DatatableColumn vis_dimension.accessor at metric', () => { + vis.params = { + scale: 'linear', + orientation: 'single', + minFontSize: 5, + maxFontSize: 15, + showLabel: true, + palette: { + type: 'palette', + name: 'default', + }, + metric: { + type: 'vis_dimension', + accessor: { + id: 'count', + name: 'count', + meta: { type: 'number' }, + }, + format: { + id: 'number', + params: { + id: 'number', + }, + }, + }, }; - const actual = toExpressionAst(vis, {} as any); + const actual = toExpressionAst(vis, {} as VisToExpressionAstParams); expect(actual).toMatchSnapshot(); }); }); diff --git a/src/plugins/vis_types/tagcloud/public/types.ts b/src/plugins/vis_types/tagcloud/public/types.ts index 28a7c6506eb31..996555ae99f83 100644 --- a/src/plugins/vis_types/tagcloud/public/types.ts +++ b/src/plugins/vis_types/tagcloud/public/types.ts @@ -6,15 +6,7 @@ * Side Public License, v 1. */ import type { ChartsPluginSetup, PaletteOutput } from '../../../charts/public'; -import type { SerializedFieldFormat } from '../../../expressions/public'; - -interface Dimension { - accessor: number; - format: { - id?: string; - params?: SerializedFieldFormat; - }; -} +import { ExpressionValueVisDimension } from '../../../visualizations/public'; interface TagCloudCommonParams { scale: 'linear' | 'log' | 'square root'; @@ -26,8 +18,8 @@ interface TagCloudCommonParams { export interface TagCloudVisParams extends TagCloudCommonParams { palette: PaletteOutput; - metric: Dimension; - bucket?: Dimension; + metric: ExpressionValueVisDimension; + bucket?: ExpressionValueVisDimension; } export interface TagCloudTypeProps { diff --git a/src/plugins/visualizations/common/expression_functions/vis_dimension.test.ts b/src/plugins/visualizations/common/expression_functions/vis_dimension.test.ts new file mode 100644 index 0000000000000..249c796afeac3 --- /dev/null +++ b/src/plugins/visualizations/common/expression_functions/vis_dimension.test.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Arguments, visDimension } from './vis_dimension'; +import { functionWrapper } from '../../../expressions/common/expression_functions/specs/tests/utils'; +import { Datatable } from '../../../expressions/common'; +import moment from 'moment'; + +describe('interpreter/functions#vis_dimension', () => { + const fn = functionWrapper(visDimension()); + const column1 = 'username'; + const column2 = '@timestamp'; + + const input: Datatable = { + type: 'datatable', + columns: [ + { id: column1, name: column1, meta: { type: 'string' } }, + { id: column2, name: column2, meta: { type: 'date' } }, + ], + rows: [ + { [column1]: 'user1', [column2]: moment().toISOString() }, + { [column1]: 'user2', [column2]: moment().toISOString() }, + ], + }; + + it('should return vis_dimension accessor in number format when type of the passed accessor is number', () => { + const accessor = 0; + const args: Arguments = { accessor }; + + const result = fn(input, args); + expect(result).toHaveProperty('type', 'vis_dimension'); + expect(result).toHaveProperty('accessor', accessor); + expect(result).toHaveProperty('format'); + expect(result.format).toBeDefined(); + expect(typeof result.format === 'object').toBeTruthy(); + }); + + it('should return vis_dimension accessor in DatatableColumn format when type of the passed accessor is string', () => { + const accessor = column2; + const args: Arguments = { accessor }; + const searchingObject = input.columns.filter(({ id }) => id === accessor)[0]; + + const result = fn(input, args); + expect(result).toHaveProperty('type', 'vis_dimension'); + expect(result).toHaveProperty('accessor'); + expect(result.accessor).toMatchObject(searchingObject); + expect(result).toHaveProperty('format'); + expect(result.format).toBeDefined(); + expect(typeof result.format === 'object').toBeTruthy(); + }); + + it('should throw error when the passed number accessor is out of columns array boundary', () => { + const accessor = input.columns.length; + const args: Arguments = { accessor }; + + expect(() => fn(input, args)).toThrowError('Column name or index provided is invalid'); + }); + + it("should throw error when the passed column doesn't exist in columns", () => { + const accessor = column1 + '_modified'; + const args: Arguments = { accessor }; + + expect(() => fn(input, args)).toThrowError('Column name or index provided is invalid'); + }); +}); diff --git a/src/plugins/visualizations/common/expression_functions/vis_dimension.ts b/src/plugins/visualizations/common/expression_functions/vis_dimension.ts index 6886fa94f878e..60d3fc78ac553 100644 --- a/src/plugins/visualizations/common/expression_functions/vis_dimension.ts +++ b/src/plugins/visualizations/common/expression_functions/vis_dimension.ts @@ -14,7 +14,7 @@ import { DatatableColumn, } from '../../../expressions/common'; -interface Arguments { +export interface Arguments { accessor: string | number; format?: string; formatParams?: string; @@ -31,6 +31,12 @@ export type ExpressionValueVisDimension = ExpressionValueBoxed< } >; +const getAccessorByIndex = (accessor: number, columns: Datatable['columns']) => + columns.length > accessor ? accessor : undefined; + +const getAccessorById = (accessor: DatatableColumn['id'], columns: Datatable['columns']) => + columns.find((c) => c.id === accessor); + export const visDimension = (): ExpressionFunctionDefinition< 'visdimension', Datatable, @@ -69,13 +75,13 @@ export const visDimension = (): ExpressionFunctionDefinition< fn: (input, args) => { const accessor = typeof args.accessor === 'number' - ? args.accessor - : input.columns.find((c) => c.id === args.accessor); + ? getAccessorByIndex(args.accessor, input.columns) + : getAccessorById(args.accessor, input.columns); if (accessor === undefined) { throw new Error( i18n.translate('visualizations.function.visDimension.error.accessor', { - defaultMessage: 'Column name provided is invalid', + defaultMessage: 'Column name or index provided is invalid', }) ); } diff --git a/src/plugins/visualizations/common/prepare_log_table.test.ts b/src/plugins/visualizations/common/prepare_log_table.test.ts index dc02adbd458ee..7176ba46c40ec 100644 --- a/src/plugins/visualizations/common/prepare_log_table.test.ts +++ b/src/plugins/visualizations/common/prepare_log_table.test.ts @@ -19,13 +19,14 @@ describe('prepareLogTable', () => { meta: {}, }, { + id: 'd3', meta: {}, }, ], }; const logTable = prepareLogTable(datatable as any, [ [[{ accessor: 0 } as any], 'dimension1'], - [[{ accessor: 2 } as any], 'dimension3'], + [[{ accessor: { id: 'd3' } } as any], 'dimension3'], [[{ accessor: 1 } as any], 'dimension2'], ]); expect(logTable).toMatchInlineSnapshot( @@ -42,6 +43,7 @@ describe('prepareLogTable', () => { }, }, { + id: 'd3', meta: { dimensionName: 'dimension3', }, @@ -62,6 +64,7 @@ describe('prepareLogTable', () => { }, }, Object { + "id": "d3", "meta": Object { "dimensionName": "dimension3", }, diff --git a/src/plugins/visualizations/common/prepare_log_table.ts b/src/plugins/visualizations/common/prepare_log_table.ts index 0018a18ce7f10..b3f74c8611af5 100644 --- a/src/plugins/visualizations/common/prepare_log_table.ts +++ b/src/plugins/visualizations/common/prepare_log_table.ts @@ -8,16 +8,31 @@ import { ExpressionValueVisDimension } from './expression_functions/vis_dimension'; import { ExpressionValueXYDimension } from './expression_functions/xy_dimension'; -import { Datatable } from '../../expressions/common/expression_types/specs'; +import { Datatable, DatatableColumn } from '../../expressions/common/expression_types/specs'; export type Dimension = [ Array | undefined, string ]; -const getDimensionName = (columnIndex: number, dimensions: Dimension[]) => { +const isColumnEqualToAccessor = ( + column: DatatableColumn, + columnIndex: number, + accessor: ExpressionValueVisDimension['accessor'] | ExpressionValueXYDimension['accessor'] +) => { + if (typeof accessor === 'number') { + return accessor === columnIndex; + } + return accessor.id === column.id; +}; + +const getDimensionName = ( + column: DatatableColumn, + columnIndex: number, + dimensions: Dimension[] +) => { for (const dimension of dimensions) { - if (dimension[0]?.find((d) => d.accessor === columnIndex)) { + if (dimension[0]?.find((d) => isColumnEqualToAccessor(column, columnIndex, d.accessor))) { return dimension[1]; } } @@ -31,7 +46,7 @@ export const prepareLogTable = (datatable: Datatable, dimensions: Dimension[]) = ...column, meta: { ...column.meta, - dimensionName: getDimensionName(columnIndex, dimensions), + dimensionName: getDimensionName(column, columnIndex, dimensions), }, }; }), diff --git a/test/interpreter_functional/screenshots/baseline/metric_all_data.png b/test/interpreter_functional/screenshots/baseline/metric_all_data.png index 54ee1f4da6684ab4f12fd87bc84d3439aa0dd763..66357a371a5be96013f5a3cc4f33400f78f4392d 100644 GIT binary patch literal 29776 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 D<^8a} literal 22339 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 literal 0 HcmV?d00001 diff --git a/test/interpreter_functional/screenshots/baseline/metric_invalid_data.png b/test/interpreter_functional/screenshots/baseline/metric_invalid_data.png index b1448cd7cb2effd015e309ddc666fde950297f6b..b8ffa6e8576fe7a717bdcba664b67cb77f05438f 100644 GIT binary patch literal 1993 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�?&fuO)Z3IwJ3&5(5+a^f+0aMR+7TBC^y56`k9PSr4~mQ4xh{F(L?MvRU* z6WpGHvuXlizij1f1wnhy!VVx24Tk|@I5L@RYac@6a5(G|0S4vx%{MoY#uB-$au|u_ zKll!RSJrt@_$LTDTn!=|`m!&tT-Xwq!GeRldDC4^%^Fr8xj;-gywr3SJCJskf#J># z6bc&2A}s{Dy1}tJ+S-X|BO~={pN;-&xvk;d*;MzKXsfZ|U?jYZs1P-C6# zafApvo6jvj?i3MGCeXl}0<2L48Sh2F6cosvm9VSJpkA4-N5YQq!2qyg3Wn1W#<0d8 z`!WBwN=7tr1!N~~3DCtwNX63*0#obX1$E_N@8aU;fSdr(pYDTTC-(z@>g%~+pGW6m zz5SQ^t`zqG`sC);!al_PzQ_M0N;s}4HxT6Br_H}5aTE^dBMLas%K$fTHRjP@1ydAw zesEn-&5$v4tzuD6}hoMU#Ffh)(&>l}_6}fy-+9RXg4jMXS=kYu=n+U-1`Y} zxjLn%xU?)jxO{z;qvOOLn4!`A56+ZaKPb*;wktbau&a|)1$MO`Z4zjN%`vG~M5ATv zT#Lvb1YNag4RyT|KGjr-#A}C-*dE|*M4eG(7QJlAh~ceJ+g2syM>*}I8WxA7t@j}{KUX%i z3I^QoGly;$hm{5t9*&y7x4bdlgO`2#HA#MfynXuMj8`e_$!AQKGgdKuyE3n|K$@3< zGZ}loP}Ton6{TIF3Ogwj-?Yb$TXd7jK6N^`_H$Ak1m*1lD)agbHcKB9;{g0<{oKZpu$H5I8Meq2T8*tJVgie;UnHBiY014ca@K~R zf?pA0stF->TQmTmRArr})gwO3YQt1UmK)Zm9%Zpy5>YKHN9zjPR7C2mPlSg2FV`WK+}iJ zXsx>Y%*q68-)n@+sx@s|^ zQl7qTokj6AQeEa!(T4g=MN2{?z?Ss3czZ}WEXBt13x0ZB8vlz^>1tiyES_8&Ry@U zRhs(Td2pv%jrlvLQRw_Fv^kGvRw?_rtz!DVck8%0wkr+nNh`bTzdsC?fh+yUV z;AG}N77(@%3axHYa?b~a71EDShzqm#2NSRhJSxy~w5+!*VW>lr{yHuwWJ|aL%+ZDu z==}MiqH39@&Y?e|tap^aMQ1wl@uO7%w#p?J=HOKFvZ|;NGgLu-QYk<1nyBELb?SXi z2_0sZJ(7HZ6^eF6drHzX(pOm-WUJm>+r9;C-6k>Jv~f0QH#B?tkppz)(4#QZR^LYV z9+vr&SCRdhevJY*?vBW^w*#!2dt4(zppKq|sJ zLzjX8WFs19NeSE>7qa)6Yu5yt7L~Zv9Af0!Uo^4X8T(@qYK-+14qs z6I}+W>PsfuFlx~5rsMhpQ;Fk>};!o}%R4Xj>_;hTVc|&T?|! z!M(uq?kbrH3!bIxpd1S8r8VqJ-FAq-MwGQin@vXVzlDePirQNX{XJrbFEQx1cE*Xc zkM@WAs?6#@Dh)iD-sa=SYo$r9c@@W|4mY>Ju_ literal 0 HcmV?d00001 diff --git a/test/interpreter_functional/snapshots/session/metric_single_metric_data.json b/test/interpreter_functional/snapshots/baseline/metric_empty_data.json similarity index 89% rename from test/interpreter_functional/snapshots/session/metric_single_metric_data.json rename to test/interpreter_functional/snapshots/baseline/metric_empty_data.json index f4a8cd1f14e18..c318121535c8f 100644 --- a/test/interpreter_functional/snapshots/session/metric_single_metric_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_empty_data.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metric_vis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_invalid_data.json b/test/interpreter_functional/snapshots/baseline/metric_invalid_data.json index c7b4a0325dc91..f23b9b0915774 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_invalid_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_invalid_data.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[],"meta":{},"rows":[],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +"[metricVis] > [visdimension] > Column name or index provided is invalid" \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/partial_test_1.json b/test/interpreter_functional/snapshots/baseline/tagcloud_empty_data.json similarity index 65% rename from test/interpreter_functional/snapshots/session/partial_test_1.json rename to test/interpreter_functional/snapshots/baseline/tagcloud_empty_data.json index 082c7b934c17c..6dd90a4a6ca03 100644 --- a/test/interpreter_functional/snapshots/session/partial_test_1.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_empty_data.json @@ -1 +1 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_invalid_data.json b/test/interpreter_functional/snapshots/baseline/tagcloud_invalid_data.json index 3e594380588dc..b5ae1a2cb59fc 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_invalid_data.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_invalid_data.json @@ -1 +1 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[],"meta":{},"rows":[],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +"[tagcloud] > [visdimension] > Column name or index provided is invalid" \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_empty_data.json b/test/interpreter_functional/snapshots/session/tagcloud_empty_data.json new file mode 100644 index 0000000000000..6dd90a4a6ca03 --- /dev/null +++ b/test/interpreter_functional/snapshots/session/tagcloud_empty_data.json @@ -0,0 +1 @@ +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_invalid_data.json b/test/interpreter_functional/snapshots/session/tagcloud_invalid_data.json index 3e594380588dc..b5ae1a2cb59fc 100644 --- a/test/interpreter_functional/snapshots/session/tagcloud_invalid_data.json +++ b/test/interpreter_functional/snapshots/session/tagcloud_invalid_data.json @@ -1 +1 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[],"meta":{},"rows":[],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +"[tagcloud] > [visdimension] > Column name or index provided is invalid" \ No newline at end of file diff --git a/test/interpreter_functional/test_suites/run_pipeline/metric.ts b/test/interpreter_functional/test_suites/run_pipeline/metric.ts index bbaf0486f4fbb..5483e09d6671b 100644 --- a/test/interpreter_functional/test_suites/run_pipeline/metric.ts +++ b/test/interpreter_functional/test_suites/run_pipeline/metric.ts @@ -30,10 +30,13 @@ export default function ({ dataContext = await expectExpression('partial_metric_test', expression).getResponse(); }); - it('with invalid data', async () => { + it('with empty data', async () => { const expression = 'metricVis metric={visdimension 0}'; await ( - await expectExpression('metric_invalid_data', expression).toMatchSnapshot() + await expectExpression('metric_empty_data', expression, { + ...dataContext, + rows: [], + }).toMatchSnapshot() ).toMatchScreenshot(); }); @@ -78,5 +81,14 @@ export default function ({ ).toMatchScreenshot(); }); }); + + describe('throws error at metric', () => { + it('with invalid data', async () => { + const expression = 'metricVis metric={visdimension 0}'; + await ( + await expectExpression('metric_invalid_data', expression).toMatchSnapshot() + ).toMatchScreenshot(); + }); + }); }); } diff --git a/test/interpreter_functional/test_suites/run_pipeline/tag_cloud.ts b/test/interpreter_functional/test_suites/run_pipeline/tag_cloud.ts index 05bbd33fedad7..3358e45dc02d4 100644 --- a/test/interpreter_functional/test_suites/run_pipeline/tag_cloud.ts +++ b/test/interpreter_functional/test_suites/run_pipeline/tag_cloud.ts @@ -15,24 +15,25 @@ export default function ({ }: FtrProviderContext & { updateBaselines: boolean }) { let expectExpression: ExpectExpression; describe('tag cloud pipeline expression tests', () => { - before(() => { + let dataContext: ExpressionResult; + before(async () => { expectExpression = expectExpressionProvider({ getService, updateBaselines }); + + const expression = `kibana | kibana_context | esaggs index={indexPatternLoad id='logstash-*'} + aggs={aggCount id="1" enabled=true schema="metric"} + aggs={aggTerms id="2" enabled=true schema="segment" field="response.raw" size=4 order="desc" orderBy="1"}`; + // we execute the part of expression that fetches the data and store its response + dataContext = await expectExpression('partial_tagcloud_test', expression).getResponse(); }); describe('correctly renders tagcloud', () => { - let dataContext: ExpressionResult; - before(async () => { - const expression = `kibana | kibana_context | esaggs index={indexPatternLoad id='logstash-*'} - aggs={aggCount id="1" enabled=true schema="metric"} - aggs={aggTerms id="2" enabled=true schema="segment" field="response.raw" size=4 order="desc" orderBy="1"}`; - // we execute the part of expression that fetches the data and store its response - dataContext = await expectExpression('partial_tagcloud_test', expression).getResponse(); - }); - - it('with invalid data', async () => { + it('with empty data', async () => { const expression = 'tagcloud metric={visdimension 0}'; await ( - await expectExpression('tagcloud_invalid_data', expression).toMatchSnapshot() + await expectExpression('tagcloud_empty_data', expression, { + ...dataContext, + rows: [], + }).toMatchSnapshot() ).toMatchScreenshot(); }); @@ -66,5 +67,14 @@ export default function ({ ).toMatchScreenshot(); }); }); + + describe('throws error at tagcloud', () => { + it('with invalid data', async () => { + const expression = 'tagcloud metric={visdimension 0}'; + await ( + await expectExpression('tagcloud_invalid_data', expression).toMatchSnapshot() + ).toMatchScreenshot(); + }); + }); }); } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/elements/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/elements/index.ts index 4925849045216..667854bf3e7e2 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/elements/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/elements/index.ts @@ -31,6 +31,7 @@ import { timeFilter } from './time_filter'; import { verticalBarChart } from './vert_bar_chart'; import { verticalProgressBar } from './vertical_progress_bar'; import { verticalProgressPill } from './vertical_progress_pill'; +import { tagCloud } from './tag_cloud'; import { SetupInitializer } from '../plugin'; import { ElementFactory } from '../../types'; @@ -60,6 +61,7 @@ const elementSpecs = [ verticalBarChart, verticalProgressBar, verticalProgressPill, + tagCloud, ]; const initializeElementFactories = [metricElementInitializer]; @@ -69,6 +71,5 @@ export const initializeElements: SetupInitializer = (core, plu ...elementSpecs, ...initializeElementFactories.map((factory) => factory(core, plugins)), ]; - return applyElementStrings(specs); }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/elements/tag_cloud/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/elements/tag_cloud/index.ts new file mode 100644 index 0000000000000..a0b464390fa22 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/elements/tag_cloud/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ElementFactory } from '../../../types'; + +export const tagCloud: ElementFactory = () => ({ + name: 'tagCloud', + displayName: 'Tag Cloud', + type: 'chart', + help: 'Tagcloud visualization', + icon: 'visTagCloud', + expression: `filters + | demodata + | head 150 + | ply by="country" expression={math "count(country)" | as "Count"} + | tagcloud metric={visdimension "Count"} bucket={visdimension "country"} + | render`, +}); diff --git a/x-pack/plugins/canvas/i18n/elements/element_strings.ts b/x-pack/plugins/canvas/i18n/elements/element_strings.ts index 87879c4c753c9..e1540572f4af6 100644 --- a/x-pack/plugins/canvas/i18n/elements/element_strings.ts +++ b/x-pack/plugins/canvas/i18n/elements/element_strings.ts @@ -222,4 +222,12 @@ export const getElementStrings = (): ElementStringDict => ({ defaultMessage: 'Displays progress as a portion of a vertical pill', }), }, + tagCloud: { + displayName: i18n.translate('xpack.canvas.elements.tagCloudDisplayName', { + defaultMessage: 'Tag Cloud', + }), + help: i18n.translate('xpack.canvas.elements.tagCloudHelpText', { + defaultMessage: 'Tagcloud visualization', + }), + }, }); diff --git a/x-pack/plugins/canvas/public/plugin.tsx b/x-pack/plugins/canvas/public/plugin.tsx index c149c67544865..b8f0b17f814d8 100644 --- a/x-pack/plugins/canvas/public/plugin.tsx +++ b/x-pack/plugins/canvas/public/plugin.tsx @@ -105,6 +105,7 @@ export class CanvasPlugin mount: async (params: AppMountParameters) => { const { CanvasSrcPlugin } = await import('../canvas_plugin_src/plugin'); const srcPlugin = new CanvasSrcPlugin(); + srcPlugin.setup(coreSetup, { canvas: canvasApi }); setupExpressions({ coreSetup, setupPlugins }); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d80f58e6899ab..0c683dae841d1 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -5121,7 +5121,6 @@ "visualizations.function.range.help": "範ć›Čă‚Șăƒ–ă‚žă‚§ă‚Żăƒˆă‚’ç”Ÿæˆă—ăŸă™", "visualizations.function.range.to.help": "範ć›Čた甂äș†", "visualizations.function.visDimension.accessor.help": "äœżç”šă™ă‚‹ăƒ‡ăƒŒă‚żă‚»ăƒƒăƒˆć†…ăźćˆ— (ćˆ—ă‚€ăƒłăƒ‡ăƒƒă‚Żă‚čăŸăŸăŻćˆ—ć) ", - "visualizations.function.visDimension.error.accessor": "ć…„ćŠ›ă•ă‚ŒăŸćˆ—ćăŻç„ĄćŠčです。", "visualizations.function.visDimension.format.help": "ăƒ•ă‚©ăƒŒăƒžăƒƒăƒˆ", "visualizations.function.visDimension.formatParams.help": "ăƒ•ă‚©ăƒŒăƒžăƒƒăƒˆăƒ‘ăƒ©ăƒĄăƒŒă‚żăƒŒ", "visualizations.function.visDimension.help": "visConfig ăƒ‡ă‚ŁăƒĄăƒłă‚·ăƒ§ăƒłă‚Șăƒ–ă‚žă‚§ă‚Żăƒˆă‚’ç”Ÿæˆă—ăŸă™", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 6c451b94b896c..c1ea4ad8ecef3 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -5142,7 +5142,6 @@ "visualizations.function.range.help": "ç”ŸæˆèŒƒć›ŽćŻčè±Ą", "visualizations.function.range.to.help": "èŒƒć›Žç»“æŸ", "visualizations.function.visDimension.accessor.help": "èŠäœżç”šçš„æ•°æźé›†ćˆ—ïŒˆćˆ—çŽąćŒ•æˆ–ćˆ—ćç§°ïŒ‰", - "visualizations.function.visDimension.error.accessor": "æäŸ›çš„ćˆ—ćç§°æ— æ•ˆ", "visualizations.function.visDimension.format.help": "æ ŒćŒ", "visualizations.function.visDimension.formatParams.help": "æ ŒćŒć‚æ•°", "visualizations.function.visDimension.help": "生成 visConfig 绎ćșŠćŻčè±Ą", From b4444c1519a51682d93b154e5ef724dd895d1067 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 6 Sep 2021 08:38:45 -0400 Subject: [PATCH 03/43] [7.x] [RAC] Persistent timeline fields fix (#110685) (#110996) * [RAC] Persistent timeline fields fix (#110685) * fix stringify circular ref crash and default columns on createTimeline * rollback reset buton fix to split PR * adding fields to the storage cleaning * tests fixed * test fix * Skip cypress test Co-authored-by: Sergi Massaneda Co-authored-by: Christos Nasikas --- .../detection_rules/custom_query_rule.spec.ts | 3 +- .../public/common/mock/global_state.ts | 3 + .../public/common/mock/timeline_results.ts | 6 ++ .../components/alerts_table/actions.test.tsx | 3 + .../components/open_timeline/helpers.test.ts | 24 +++++++ .../containers/local_storage/index.test.ts | 67 +++++++++++-------- .../containers/local_storage/index.tsx | 18 ++++- .../timelines/store/timeline/defaults.ts | 3 + .../timelines/store/timeline/epic.test.ts | 3 + .../public/timelines/store/timeline/model.ts | 6 ++ .../timelines/store/timeline/reducer.test.ts | 3 + .../timelines/public/store/t_grid/model.ts | 7 ++ 12 files changed, 116 insertions(+), 30 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts index 9ee80636fab2e..4567d043ad0be 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts @@ -358,7 +358,8 @@ describe('Custom detection rules deletion and edition', () => { }); }); - it('Allows a rule to be edited', () => { + // TODO: Remove when https://github.com/elastic/kibana/pull/111251 is merged + it.skip('Allows a rule to be edited', () => { editFirstRule(); waitForKibana(); diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts index fb772986bc679..cb6536d585c1e 100644 --- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts +++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts @@ -242,6 +242,9 @@ export const mockGlobalState: State = { activeTab: TimelineTabs.query, prevActiveTab: TimelineTabs.notes, deletedEventIds: [], + documentType: '', + queryFields: [], + selectAll: false, id: 'test', savedObjectId: null, columns: defaultHeaders, diff --git a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts index 1c16fda54b90a..b601e531d4991 100644 --- a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts +++ b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts @@ -1979,6 +1979,7 @@ export const mockTimelineModel: TimelineModel = { }, deletedEventIds: [], description: 'This is a sample rule description', + documentType: '', eqlOptions: { eventCategoryField: 'event.category', tiebreakerField: 'event.sequence', @@ -2017,6 +2018,7 @@ export const mockTimelineModel: TimelineModel = { kqlQuery: { filterQuery: null, }, + queryFields: [], itemsPerPage: 25, itemsPerPageOptions: [10, 25, 50, 100], loadingEventIds: [], @@ -2024,6 +2026,7 @@ export const mockTimelineModel: TimelineModel = { pinnedEventIds: {}, pinnedEventsSaveObject: {}, savedObjectId: 'ef579e40-jibber-jabber', + selectAll: false, selectedEventIds: {}, show: false, showCheckboxes: false, @@ -2110,6 +2113,7 @@ export const defaultTimelineProps: CreateTimelineProps = { dateRange: { end: '2018-11-05T19:03:25.937Z', start: '2018-11-05T18:58:25.937Z' }, deletedEventIds: [], description: '', + documentType: '', eqlOptions: { eventCategoryField: 'event.category', query: '', @@ -2141,7 +2145,9 @@ export const defaultTimelineProps: CreateTimelineProps = { noteIds: [], pinnedEventIds: {}, pinnedEventsSaveObject: {}, + queryFields: [], savedObjectId: null, + selectAll: false, selectedEventIds: {}, show: false, showCheckboxes: false, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx index 69160d90a011e..e7a8ba91cff8f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx @@ -148,6 +148,7 @@ describe('alert actions', () => { }, deletedEventIds: [], description: 'This is a sample rule description', + documentType: '', eqlOptions: { eventCategoryField: 'event.category', query: '', @@ -204,7 +205,9 @@ describe('alert actions', () => { noteIds: [], pinnedEventIds: {}, pinnedEventsSaveObject: {}, + queryFields: [], savedObjectId: null, + selectAll: false, selectedEventIds: {}, show: true, showCheckboxes: false, diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts index ae15768d26e70..37bdfd38bf8bc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts @@ -296,6 +296,7 @@ describe('helpers', () => { dataProviders: [], dateRange: { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z' }, description: '', + documentType: '', deletedEventIds: [], eqlOptions: { eventCategoryField: 'event.category', @@ -328,7 +329,9 @@ describe('helpers', () => { noteIds: [], pinnedEventIds: {}, pinnedEventsSaveObject: {}, + queryFields: [], savedObjectId: 'savedObject-1', + selectAll: false, selectedEventIds: {}, show: false, showCheckboxes: false, @@ -366,6 +369,7 @@ describe('helpers', () => { dataProviders: [], dateRange: { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z' }, description: '', + documentType: '', deletedEventIds: [], eqlOptions: { eventCategoryField: 'event.category', @@ -398,7 +402,9 @@ describe('helpers', () => { noteIds: [], pinnedEventIds: {}, pinnedEventsSaveObject: {}, + queryFields: [], savedObjectId: 'savedObject-1', + selectAll: false, selectedEventIds: {}, show: false, showCheckboxes: false, @@ -436,6 +442,7 @@ describe('helpers', () => { dataProviders: [], dateRange: { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z' }, description: '', + documentType: '', deletedEventIds: [], eqlOptions: { eventCategoryField: 'event.category', @@ -468,7 +475,9 @@ describe('helpers', () => { noteIds: [], pinnedEventIds: {}, pinnedEventsSaveObject: {}, + queryFields: [], savedObjectId: 'savedObject-1', + selectAll: false, selectedEventIds: {}, show: false, showCheckboxes: false, @@ -504,6 +513,7 @@ describe('helpers', () => { dataProviders: [], dateRange: { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z' }, description: '', + documentType: '', deletedEventIds: [], eqlOptions: { eventCategoryField: 'event.category', @@ -536,7 +546,9 @@ describe('helpers', () => { noteIds: [], pinnedEventIds: {}, pinnedEventsSaveObject: {}, + queryFields: [], savedObjectId: 'savedObject-1', + selectAll: false, selectedEventIds: {}, show: false, showCheckboxes: false, @@ -577,6 +589,7 @@ describe('helpers', () => { dataProviders: [], dateRange: { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z' }, description: '', + documentType: '', deletedEventIds: [], eqlOptions: { eventCategoryField: 'event.category', @@ -612,6 +625,8 @@ describe('helpers', () => { noteIds: [], pinnedEventIds: {}, pinnedEventsSaveObject: {}, + queryFields: [], + selectAll: false, selectedEventIds: {}, show: false, showCheckboxes: false, @@ -680,6 +695,7 @@ describe('helpers', () => { dateRange: { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z' }, dataProviders: [], description: '', + documentType: '', deletedEventIds: [], eqlOptions: { eventCategoryField: 'event.category', @@ -758,6 +774,8 @@ describe('helpers', () => { noteIds: [], pinnedEventIds: {}, pinnedEventsSaveObject: {}, + queryFields: [], + selectAll: false, selectedEventIds: {}, show: false, showCheckboxes: false, @@ -791,6 +809,7 @@ describe('helpers', () => { dataProviders: [], dateRange: { end: '2020-10-28T11:37:31.655Z', start: '2020-10-27T11:37:31.655Z' }, description: '', + documentType: '', deletedEventIds: [], eqlOptions: { eventCategoryField: 'event.category', @@ -823,7 +842,9 @@ describe('helpers', () => { noteIds: [], pinnedEventIds: {}, pinnedEventsSaveObject: {}, + queryFields: [], savedObjectId: 'savedObject-1', + selectAll: false, selectedEventIds: {}, show: false, showCheckboxes: false, @@ -861,6 +882,7 @@ describe('helpers', () => { dataProviders: [], dateRange: { end: '2020-07-08T08:20:18.966Z', start: '2020-07-07T08:20:18.966Z' }, description: '', + documentType: '', deletedEventIds: [], eqlOptions: { eventCategoryField: 'event.category', @@ -893,7 +915,9 @@ describe('helpers', () => { noteIds: [], pinnedEventIds: {}, pinnedEventsSaveObject: {}, + queryFields: [], savedObjectId: 'savedObject-1', + selectAll: false, selectedEventIds: {}, show: false, showCheckboxes: false, diff --git a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.test.ts b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.test.ts index f1b5f6a944678..8fbb330d51231 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.test.ts @@ -28,6 +28,17 @@ const useKibanaMock = useKibana as jest.Mocked; const getExpectedColumns = (model: TimelineModel) => model.columns.map(migrateColumnWidthToInitialWidth).map(migrateColumnLabelToDisplayAsText); +const { + documentType, + filterManager, + isLoading, + loadingText, + queryFields, + selectAll, + unit, + ...timelineToStore +} = mockTimelineModel; + describe('SiemLocalStorage', () => { const { localStorage, storage } = createSecuritySolutionStorageMock(); @@ -41,7 +52,7 @@ describe('SiemLocalStorage', () => { const timelineStorage = useTimelinesStorage(); timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel); expect(JSON.parse(localStorage.getItem(LOCAL_STORAGE_TIMELINE_KEY))).toEqual({ - [TimelineId.hostsPageEvents]: mockTimelineModel, + [TimelineId.hostsPageEvents]: timelineToStore, }); }); @@ -50,8 +61,8 @@ describe('SiemLocalStorage', () => { timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel); timelineStorage.addTimeline(TimelineId.hostsPageExternalAlerts, mockTimelineModel); expect(JSON.parse(localStorage.getItem(LOCAL_STORAGE_TIMELINE_KEY))).toEqual({ - [TimelineId.hostsPageEvents]: mockTimelineModel, - [TimelineId.hostsPageExternalAlerts]: mockTimelineModel, + [TimelineId.hostsPageEvents]: timelineToStore, + [TimelineId.hostsPageExternalAlerts]: timelineToStore, }); }); }); @@ -63,8 +74,8 @@ describe('SiemLocalStorage', () => { timelineStorage.addTimeline(TimelineId.hostsPageExternalAlerts, mockTimelineModel); const timelines = timelineStorage.getAllTimelines(); expect(timelines).toEqual({ - [TimelineId.hostsPageEvents]: mockTimelineModel, - [TimelineId.hostsPageExternalAlerts]: mockTimelineModel, + [TimelineId.hostsPageEvents]: timelineToStore, + [TimelineId.hostsPageExternalAlerts]: timelineToStore, }); }); @@ -80,7 +91,7 @@ describe('SiemLocalStorage', () => { const timelineStorage = useTimelinesStorage(); timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel); const timeline = timelineStorage.getTimelineById(TimelineId.hostsPageEvents); - expect(timeline).toEqual(mockTimelineModel); + expect(timeline).toEqual(timelineToStore); }); }); @@ -94,8 +105,8 @@ describe('SiemLocalStorage', () => { TimelineId.hostsPageExternalAlerts, ]); expect(timelines).toEqual({ - [TimelineId.hostsPageEvents]: mockTimelineModel, - [TimelineId.hostsPageExternalAlerts]: mockTimelineModel, + [TimelineId.hostsPageEvents]: timelineToStore, + [TimelineId.hostsPageExternalAlerts]: timelineToStore, }); }); @@ -126,7 +137,7 @@ describe('SiemLocalStorage', () => { TimelineId.hostsPageExternalAlerts, ]); expect(timelines).toEqual({ - [TimelineId.hostsPageEvents]: mockTimelineModel, + [TimelineId.hostsPageEvents]: timelineToStore, }); }); @@ -152,8 +163,8 @@ describe('SiemLocalStorage', () => { // all legacy `width` values are migrated to `initialWidth`: expect(timelines).toStrictEqual({ [TimelineId.hostsPageEvents]: { - ...mockTimelineModel, - columns: mockTimelineModel.columns.map((c) => ({ + ...timelineToStore, + columns: timelineToStore.columns.map((c) => ({ ...c, displayAsText: undefined, initialWidth: 98765, @@ -161,7 +172,7 @@ describe('SiemLocalStorage', () => { })), }, [TimelineId.hostsPageExternalAlerts]: { - ...mockTimelineModel, + ...timelineToStore, columns: getExpectedColumns(mockTimelineModel), }, }); @@ -187,8 +198,8 @@ describe('SiemLocalStorage', () => { expect(timelines).toStrictEqual({ [TimelineId.hostsPageEvents]: { - ...mockTimelineModel, - columns: mockTimelineModel.columns.map((c) => ({ + ...timelineToStore, + columns: timelineToStore.columns.map((c) => ({ ...c, displayAsText: undefined, initialWidth: c.initialWidth, // initialWidth is unchanged @@ -196,7 +207,7 @@ describe('SiemLocalStorage', () => { })), }, [TimelineId.hostsPageExternalAlerts]: { - ...mockTimelineModel, + ...timelineToStore, columns: getExpectedColumns(mockTimelineModel), }, }); @@ -223,15 +234,15 @@ describe('SiemLocalStorage', () => { // all legacy `label` values are migrated to `displayAsText`: expect(timelines).toStrictEqual({ [TimelineId.hostsPageEvents]: { - ...mockTimelineModel, - columns: mockTimelineModel.columns.map((c, i) => ({ + ...timelineToStore, + columns: timelineToStore.columns.map((c, i) => ({ ...c, displayAsText: `A legacy label ${i}`, label: `A legacy label ${i}`, })), }, [TimelineId.hostsPageExternalAlerts]: { - ...mockTimelineModel, + ...timelineToStore, columns: getExpectedColumns(mockTimelineModel), }, }); @@ -259,8 +270,8 @@ describe('SiemLocalStorage', () => { expect(timelines).toStrictEqual({ [TimelineId.hostsPageEvents]: { - ...mockTimelineModel, - columns: mockTimelineModel.columns.map((c, i) => ({ + ...timelineToStore, + columns: timelineToStore.columns.map((c, i) => ({ ...c, displayAsText: 'Label will NOT be migrated to displayAsText, because displayAsText already has a value', @@ -268,7 +279,7 @@ describe('SiemLocalStorage', () => { })), }, [TimelineId.hostsPageExternalAlerts]: { - ...mockTimelineModel, + ...timelineToStore, columns: getExpectedColumns(mockTimelineModel), }, }); @@ -293,11 +304,11 @@ describe('SiemLocalStorage', () => { expect(timelines).toStrictEqual({ [TimelineId.hostsPageEvents]: { - ...mockTimelineModel, + ...timelineToStore, columns: 'this is NOT an array', }, [TimelineId.hostsPageExternalAlerts]: { - ...mockTimelineModel, + ...timelineToStore, columns: getExpectedColumns(mockTimelineModel), }, }); @@ -311,8 +322,8 @@ describe('SiemLocalStorage', () => { timelineStorage.addTimeline(TimelineId.hostsPageExternalAlerts, mockTimelineModel); const timelines = getAllTimelinesInStorage(storage); expect(timelines).toEqual({ - [TimelineId.hostsPageEvents]: mockTimelineModel, - [TimelineId.hostsPageExternalAlerts]: mockTimelineModel, + [TimelineId.hostsPageEvents]: timelineToStore, + [TimelineId.hostsPageExternalAlerts]: timelineToStore, }); }); @@ -326,7 +337,7 @@ describe('SiemLocalStorage', () => { it('adds a timeline when storage is empty', () => { addTimelineInStorage(storage, TimelineId.hostsPageEvents, mockTimelineModel); expect(JSON.parse(localStorage.getItem(LOCAL_STORAGE_TIMELINE_KEY))).toEqual({ - [TimelineId.hostsPageEvents]: mockTimelineModel, + [TimelineId.hostsPageEvents]: timelineToStore, }); }); @@ -334,8 +345,8 @@ describe('SiemLocalStorage', () => { addTimelineInStorage(storage, TimelineId.hostsPageEvents, mockTimelineModel); addTimelineInStorage(storage, TimelineId.hostsPageExternalAlerts, mockTimelineModel); expect(JSON.parse(localStorage.getItem(LOCAL_STORAGE_TIMELINE_KEY))).toEqual({ - [TimelineId.hostsPageEvents]: mockTimelineModel, - [TimelineId.hostsPageExternalAlerts]: mockTimelineModel, + [TimelineId.hostsPageEvents]: timelineToStore, + [TimelineId.hostsPageExternalAlerts]: timelineToStore, }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.tsx index 99f45c7d9a4b4..dd60656933ba8 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.tsx @@ -85,13 +85,29 @@ export const addTimelineInStorage = ( id: TimelineIdLiteral, timeline: TimelineModel ) => { + const timelineToStore = cleanStorageTimeline(timeline); const timelines = getAllTimelinesInStorage(storage); storage.set(LOCAL_STORAGE_TIMELINE_KEY, { ...timelines, - [id]: timeline, + [id]: timelineToStore, }); }; +const cleanStorageTimeline = (timeline: TimelineModel) => { + // discard unneeded fields to make sure the object serialization works + const { + documentType, + filterManager, + isLoading, + loadingText, + queryFields, + selectAll, + unit, + ...timelineToStore + } = timeline; + return timelineToStore; +}; + export const useTimelinesStorage = (): TimelinesStorage => { const { storage } = useKibana().services; diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts index f411c6ffac9b7..0ba3f91173d0a 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts @@ -19,6 +19,7 @@ export const timelineDefaults: SubsetTimelineModel & activeTab: TimelineTabs.query, prevActiveTab: TimelineTabs.query, columns: defaultHeaders, + documentType: '', defaultColumns: defaultHeaders, dataProviders: [], dateRange: { start, end }, @@ -51,6 +52,7 @@ export const timelineDefaults: SubsetTimelineModel & filterQuery: null, }, loadingEventIds: [], + queryFields: [], title: '', timelineType: TimelineType.default, templateTimelineId: null, @@ -59,6 +61,7 @@ export const timelineDefaults: SubsetTimelineModel & pinnedEventIds: {}, pinnedEventsSaveObject: {}, savedObjectId: null, + selectAll: false, selectedEventIds: {}, show: false, showCheckboxes: false, diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts index 8b40febbfe993..686c8220f677b 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts @@ -92,6 +92,7 @@ describe('Epic Timeline', () => { ], deletedEventIds: [], description: '', + documentType: '', eqlOptions: { eventCategoryField: 'event.category', tiebreakerField: '', @@ -146,6 +147,8 @@ describe('Epic Timeline', () => { }, }, loadingEventIds: [], + queryFields: [], + selectAll: false, title: 'saved', timelineType: TimelineType.default, templateTimelineId: null, diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts index a2d7e2300d171..b53da997c08cb 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts @@ -85,10 +85,12 @@ export type SubsetTimelineModel = Readonly< | 'dataProviders' | 'deletedEventIds' | 'description' + | 'documentType' | 'eventType' | 'eventIdToNoteIds' | 'excludedRowRendererIds' | 'expandedDetail' + | 'footerText' | 'graphEventId' | 'highlightedDropAndProviderId' | 'historyIds' @@ -100,15 +102,18 @@ export type SubsetTimelineModel = Readonly< | 'itemsPerPageOptions' | 'kqlMode' | 'kqlQuery' + | 'queryFields' | 'title' | 'timelineType' | 'templateTimelineId' | 'templateTimelineVersion' | 'loadingEventIds' + | 'loadingText' | 'noteIds' | 'pinnedEventIds' | 'pinnedEventsSaveObject' | 'dateRange' + | 'selectAll' | 'selectedEventIds' | 'show' | 'showCheckboxes' @@ -116,6 +121,7 @@ export type SubsetTimelineModel = Readonly< | 'isSaving' | 'isLoading' | 'savedObjectId' + | 'unit' | 'version' | 'status' > diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts index 96ae11cb8afdc..c0dcba6920b60 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts @@ -88,6 +88,7 @@ const basicTimeline: TimelineModel = { }, deletedEventIds: [], description: '', + documentType: '', eqlOptions: { eventCategoryField: 'event.category', tiebreakerField: '', @@ -113,7 +114,9 @@ const basicTimeline: TimelineModel = { noteIds: [], pinnedEventIds: {}, pinnedEventsSaveObject: {}, + queryFields: [], savedObjectId: null, + selectAll: false, selectedEventIds: {}, show: true, showCheckboxes: false, diff --git a/x-pack/plugins/timelines/public/store/t_grid/model.ts b/x-pack/plugins/timelines/public/store/t_grid/model.ts index 0972189b38b30..dc6945d3fe3ad 100644 --- a/x-pack/plugins/timelines/public/store/t_grid/model.ts +++ b/x-pack/plugins/timelines/public/store/t_grid/model.ts @@ -92,11 +92,15 @@ export type TGridModelForTimeline = Pick< | 'dataProviders' | 'dateRange' | 'deletedEventIds' + | 'documentType' | 'excludedRowRendererIds' | 'expandedDetail' | 'filters' + | 'filterManager' + | 'footerText' | 'graphEventId' | 'kqlQuery' + | 'queryFields' | 'id' | 'indexNames' | 'isLoading' @@ -104,11 +108,14 @@ export type TGridModelForTimeline = Pick< | 'itemsPerPage' | 'itemsPerPageOptions' | 'loadingEventIds' + | 'loadingText' + | 'selectAll' | 'showCheckboxes' | 'sort' | 'selectedEventIds' | 'savedObjectId' | 'title' + | 'unit' | 'version' >; From 4eb8b6f9c0bf1d87cb9fa798fe16533e9a050a30 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 6 Sep 2021 10:05:27 -0400 Subject: [PATCH 04/43] [Canvas/Reporting] Migrate Canvas to V2 reporting (#109860) (#111272) * first iteration of canvas reporting using v2 PDF generator * updated jest test * made v2 report URLs compatible with spaces and simplified some code * remove non-existent import * updated import of lib Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Jean-Louis Leysens --- src/plugins/share/public/index.ts | 1 + .../url_service/redirect/redirect_manager.ts | 12 +- x-pack/plugins/canvas/common/index.ts | 2 + x-pack/plugins/canvas/common/locator.ts | 45 ++++++ x-pack/plugins/canvas/kibana.json | 3 +- .../workpad_header/share_menu/share_menu.tsx | 4 +- .../workpad_header/share_menu/utils.test.ts | 132 +++++++++++++++--- .../workpad_header/share_menu/utils.ts | 25 ++-- x-pack/plugins/canvas/public/plugin.tsx | 11 +- .../public/services/kibana/reporting.ts | 2 +- .../canvas/public/services/reporting.ts | 2 +- x-pack/plugins/canvas/tsconfig.json | 11 +- .../reporting/common/build_kibana_path.ts | 18 +++ x-pack/plugins/reporting/common/constants.ts | 8 +- .../public/redirect/redirect_app.tsx | 2 +- .../chromium/driver/chromium_driver.ts | 2 +- .../common/v2/get_full_redirect_app_url.ts | 33 +++++ .../export_types/common/v2/get_full_urls.ts | 34 ----- .../server/export_types/png_v2/execute_job.ts | 7 +- .../printable_pdf_v2/execute_job.ts | 2 +- .../printable_pdf_v2/lib/generate_pdf.ts | 10 +- 21 files changed, 269 insertions(+), 97 deletions(-) create mode 100644 x-pack/plugins/canvas/common/locator.ts create mode 100644 x-pack/plugins/reporting/common/build_kibana_path.ts create mode 100644 x-pack/plugins/reporting/server/export_types/common/v2/get_full_redirect_app_url.ts delete mode 100644 x-pack/plugins/reporting/server/export_types/common/v2/get_full_urls.ts diff --git a/src/plugins/share/public/index.ts b/src/plugins/share/public/index.ts index 1f999b59ddb61..74e849948d418 100644 --- a/src/plugins/share/public/index.ts +++ b/src/plugins/share/public/index.ts @@ -31,6 +31,7 @@ export { UrlGeneratorsService, } from './url_generators'; +export { RedirectOptions } from './url_service'; export { useLocatorUrl } from '../common/url_service/locators/use_locator_url'; import { SharePlugin } from './plugin'; diff --git a/src/plugins/share/public/url_service/redirect/redirect_manager.ts b/src/plugins/share/public/url_service/redirect/redirect_manager.ts index cc45e0d3126af..a5d895c7cbcf0 100644 --- a/src/plugins/share/public/url_service/redirect/redirect_manager.ts +++ b/src/plugins/share/public/url_service/redirect/redirect_manager.ts @@ -15,7 +15,15 @@ import type { UrlService } from '../../../common/url_service'; import { render } from './render'; import { parseSearchParams } from './util/parse_search_params'; -export interface RedirectOptions { +/** + * @public + * Serializable locator parameters that can be used by the redirect service to navigate to a location + * in Kibana. + * + * When passed to the public {@link SharePluginSetup['navigate']} function, locator params will also be + * migrated. + */ +export interface RedirectOptions

{ /** Locator ID. */ id: string; @@ -23,7 +31,7 @@ export interface RedirectOptions { version: string; /** Locator params. */ - params: unknown & SerializableRecord; + params: P; } export interface RedirectManagerDependencies { diff --git a/x-pack/plugins/canvas/common/index.ts b/x-pack/plugins/canvas/common/index.ts index 51a53586dee3c..5bae69e8601b2 100644 --- a/x-pack/plugins/canvas/common/index.ts +++ b/x-pack/plugins/canvas/common/index.ts @@ -8,3 +8,5 @@ export const UI_SETTINGS = { ENABLE_LABS_UI: 'labs:canvas:enable_ui', }; + +export { CANVAS_APP_LOCATOR, CanvasAppLocator, CanvasAppLocatorParams } from './locator'; diff --git a/x-pack/plugins/canvas/common/locator.ts b/x-pack/plugins/canvas/common/locator.ts new file mode 100644 index 0000000000000..147e4fd860982 --- /dev/null +++ b/x-pack/plugins/canvas/common/locator.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { LocatorDefinition, LocatorPublic } from 'src/plugins/share/common'; + +import { CANVAS_APP } from './lib/constants'; + +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +export type CanvasAppLocatorParams = { + view: 'workpadPDF'; + id: string; + page: number; +}; + +export type CanvasAppLocator = LocatorPublic; + +export const CANVAS_APP_LOCATOR = 'CANVAS_APP_LOCATOR'; + +export class CanvasAppLocatorDefinition implements LocatorDefinition { + id = CANVAS_APP_LOCATOR; + + public async getLocation(params: CanvasAppLocatorParams) { + const app = CANVAS_APP; + + if (params.view === 'workpadPDF') { + const { id, page } = params; + + return { + app, + path: `#/export/workpad/pdf/${id}/page/${page}`, + state: {}, + }; + } + + return { + app, + path: '#/', + state: {}, + }; + } +} diff --git a/x-pack/plugins/canvas/kibana.json b/x-pack/plugins/canvas/kibana.json index 201fb5ab8f78f..772c030e11539 100644 --- a/x-pack/plugins/canvas/kibana.json +++ b/x-pack/plugins/canvas/kibana.json @@ -25,7 +25,8 @@ "features", "inspector", "presentationUtil", - "uiActions" + "uiActions", + "share" ], "optionalPlugins": ["home", "reporting", "usageCollection"], "requiredBundles": [ diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.tsx b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.tsx index ca8f5fd4e3e45..50a3890673ffa 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.tsx @@ -46,9 +46,7 @@ export const ShareMenu = () => { ReportingPanelPDFComponent !== null ? ({ onClose }: { onClose: () => void }) => ( - getPdfJobParams(sharingData, platformService.getBasePathInterface()) - } + getJobParams={() => getPdfJobParams(sharingData, platformService.getKibanaVersion())} layoutOption="canvas" onClose={onClose} /> diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.test.ts b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.test.ts index 51d1b9abc5664..18c348aec18ea 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.test.ts +++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.test.ts @@ -9,17 +9,11 @@ jest.mock('../../../../common/lib/fetch'); import { getPdfJobParams } from './utils'; import { workpads } from '../../../../__fixtures__/workpads'; -import { IBasePath } from 'kibana/public'; -const basePath = ({ - prepend: jest.fn().mockImplementation((s) => `basepath/s/spacey/${s}`), - get: () => 'basepath/s/spacey', - serverBasePath: `basepath`, -} as unknown) as IBasePath; const workpadSharingData = { workpad: workpads[0], pageCount: 12 }; test('getPdfJobParams returns the correct job params for canvas layout', () => { - const jobParams = getPdfJobParams(workpadSharingData, basePath); + const jobParams = getPdfJobParams(workpadSharingData, 'v99.99.99'); expect(jobParams).toMatchInlineSnapshot(` Object { "layout": Object { @@ -29,21 +23,117 @@ test('getPdfJobParams returns the correct job params for canvas layout', () => { }, "id": "canvas", }, - "objectType": "canvas workpad", - "relativeUrls": Array [ - "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/1", - "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/2", - "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/3", - "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/4", - "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/5", - "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/6", - "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/7", - "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/8", - "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/9", - "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/10", - "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/11", - "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/12", + "locatorParams": Array [ + Object { + "id": "CANVAS_APP_LOCATOR", + "params": Object { + "id": "base-workpad", + "page": 1, + "view": "workpadPDF", + }, + "version": "v99.99.99", + }, + Object { + "id": "CANVAS_APP_LOCATOR", + "params": Object { + "id": "base-workpad", + "page": 2, + "view": "workpadPDF", + }, + "version": "v99.99.99", + }, + Object { + "id": "CANVAS_APP_LOCATOR", + "params": Object { + "id": "base-workpad", + "page": 3, + "view": "workpadPDF", + }, + "version": "v99.99.99", + }, + Object { + "id": "CANVAS_APP_LOCATOR", + "params": Object { + "id": "base-workpad", + "page": 4, + "view": "workpadPDF", + }, + "version": "v99.99.99", + }, + Object { + "id": "CANVAS_APP_LOCATOR", + "params": Object { + "id": "base-workpad", + "page": 5, + "view": "workpadPDF", + }, + "version": "v99.99.99", + }, + Object { + "id": "CANVAS_APP_LOCATOR", + "params": Object { + "id": "base-workpad", + "page": 6, + "view": "workpadPDF", + }, + "version": "v99.99.99", + }, + Object { + "id": "CANVAS_APP_LOCATOR", + "params": Object { + "id": "base-workpad", + "page": 7, + "view": "workpadPDF", + }, + "version": "v99.99.99", + }, + Object { + "id": "CANVAS_APP_LOCATOR", + "params": Object { + "id": "base-workpad", + "page": 8, + "view": "workpadPDF", + }, + "version": "v99.99.99", + }, + Object { + "id": "CANVAS_APP_LOCATOR", + "params": Object { + "id": "base-workpad", + "page": 9, + "view": "workpadPDF", + }, + "version": "v99.99.99", + }, + Object { + "id": "CANVAS_APP_LOCATOR", + "params": Object { + "id": "base-workpad", + "page": 10, + "view": "workpadPDF", + }, + "version": "v99.99.99", + }, + Object { + "id": "CANVAS_APP_LOCATOR", + "params": Object { + "id": "base-workpad", + "page": 11, + "view": "workpadPDF", + }, + "version": "v99.99.99", + }, + Object { + "id": "CANVAS_APP_LOCATOR", + "params": Object { + "id": "base-workpad", + "page": 12, + "view": "workpadPDF", + }, + "version": "v99.99.99", + }, ], + "objectType": "canvas workpad", "title": "base workpad", } `); diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.ts b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.ts index bbd6b42a38333..311ef73e1c973 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.ts +++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.ts @@ -5,8 +5,9 @@ * 2.0. */ -import { IBasePath } from 'kibana/public'; -import rison from 'rison-node'; +import type { RedirectOptions } from 'src/plugins/share/public'; +import { CANVAS_APP_LOCATOR } from '../../../../common/locator'; +import { CanvasAppLocatorParams } from '../../../../common/locator'; import { CanvasWorkpad } from '../../../../types'; export interface CanvasWorkpadSharingData { @@ -16,11 +17,8 @@ export interface CanvasWorkpadSharingData { export function getPdfJobParams( { workpad: { id, name: title, width, height }, pageCount }: CanvasWorkpadSharingData, - basePath: IBasePath + version: string ) { - const urlPrefix = basePath.get().replace(basePath.serverBasePath, ''); // for Spaces prefix, which is included in basePath.get() - const canvasEntry = `${urlPrefix}/app/canvas#`; - // The viewport in Reporting by specifying the dimensions. In order for things to work, // we need a viewport that will include all of the pages in the workpad. The viewport // also needs to include any offset values from the 0,0 position, otherwise the cropped @@ -32,9 +30,18 @@ export function getPdfJobParams( // viewport size. // build a list of all page urls for exporting, they are captured one at a time - const workpadUrls = []; + + const locatorParams: Array> = []; for (let i = 1; i <= pageCount; i++) { - workpadUrls.push(rison.encode(`${canvasEntry}/export/workpad/pdf/${id}/page/${i}`)); + locatorParams.push({ + id: CANVAS_APP_LOCATOR, + version, + params: { + view: 'workpadPDF', + id, + page: i, + }, + }); } return { @@ -43,7 +50,7 @@ export function getPdfJobParams( id: 'canvas', }, objectType: 'canvas workpad', - relativeUrls: workpadUrls, + locatorParams, title, }; } diff --git a/x-pack/plugins/canvas/public/plugin.tsx b/x-pack/plugins/canvas/public/plugin.tsx index b8f0b17f814d8..3b4a6b6f1ee4b 100644 --- a/x-pack/plugins/canvas/public/plugin.tsx +++ b/x-pack/plugins/canvas/public/plugin.tsx @@ -6,6 +6,7 @@ */ import { BehaviorSubject } from 'rxjs'; +import type { SharePluginSetup } from 'src/plugins/share/public'; import { ChartsPluginSetup, ChartsPluginStart } from 'src/plugins/charts/public'; import { ReportingStart } from '../../reporting/public'; import { @@ -20,7 +21,8 @@ import { import { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; import { initLoadingIndicator } from './lib/loading_indicator'; import { getSessionStorage } from './lib/storage'; -import { SESSIONSTORAGE_LASTPATH } from '../common/lib/constants'; +import { SESSIONSTORAGE_LASTPATH, CANVAS_APP } from '../common/lib/constants'; +import { CanvasAppLocatorDefinition } from '../common/locator'; import { featureCatalogueEntry } from './feature_catalogue_entry'; import { ExpressionsSetup, ExpressionsStart } from '../../../../src/plugins/expressions/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public'; @@ -43,6 +45,7 @@ export { CoreStart, CoreSetup }; // This interface will be built out as we require other plugins for setup export interface CanvasSetupDeps { data: DataPublicPluginSetup; + share: SharePluginSetup; expressions: ExpressionsSetup; home?: HomePublicPluginSetup; usageCollection?: UsageCollectionSetup; @@ -97,7 +100,7 @@ export class CanvasPlugin coreSetup.application.register({ category: DEFAULT_APP_CATEGORIES.kibana, - id: 'canvas', + id: CANVAS_APP, title: 'Canvas', euiIconType: 'logoKibana', order: 3000, @@ -145,6 +148,10 @@ export class CanvasPlugin setupPlugins.home.featureCatalogue.register(featureCatalogueEntry); } + if (setupPlugins.share) { + setupPlugins.share.url.locators.create(new CanvasAppLocatorDefinition()); + } + canvasApi.addArgumentUIs(async () => { // @ts-expect-error const { argTypeSpecs } = await import('./expression_types/arg_types'); diff --git a/x-pack/plugins/canvas/public/services/kibana/reporting.ts b/x-pack/plugins/canvas/public/services/kibana/reporting.ts index 432fe5675b22f..02611acdea4da 100644 --- a/x-pack/plugins/canvas/public/services/kibana/reporting.ts +++ b/x-pack/plugins/canvas/public/services/kibana/reporting.ts @@ -22,7 +22,7 @@ export const reportingServiceFactory: CanvasReportingServiceFactory = ({ const { reporting } = startPlugins; const reportingEnabled = () => ({ - getReportingPanelPDFComponent: () => reporting?.components.ReportingPanelPDF || null, + getReportingPanelPDFComponent: () => reporting?.components.ReportingPanelPDFV2 || null, }); const reportingDisabled = () => ({ getReportingPanelPDFComponent: () => null }); diff --git a/x-pack/plugins/canvas/public/services/reporting.ts b/x-pack/plugins/canvas/public/services/reporting.ts index 5369dab32bf68..9ec5bd6a06a4c 100644 --- a/x-pack/plugins/canvas/public/services/reporting.ts +++ b/x-pack/plugins/canvas/public/services/reporting.ts @@ -7,7 +7,7 @@ import { ReportingStart } from '../../../reporting/public'; -type ReportingPanelPDFComponent = ReportingStart['components']['ReportingPanelPDF']; +type ReportingPanelPDFComponent = ReportingStart['components']['ReportingPanelPDFV2']; export interface CanvasReportingService { getReportingPanelPDFComponent: () => ReportingPanelPDFComponent | null; } diff --git a/x-pack/plugins/canvas/tsconfig.json b/x-pack/plugins/canvas/tsconfig.json index 87fabe2730c16..5a5a1883240b7 100644 --- a/x-pack/plugins/canvas/tsconfig.json +++ b/x-pack/plugins/canvas/tsconfig.json @@ -7,7 +7,7 @@ "declarationMap": true, // the plugin contains some heavy json files - "resolveJsonModule": false, + "resolveJsonModule": false }, "include": [ "../../../typings/**/*", @@ -20,13 +20,14 @@ "shareable_runtime/**/*", "storybook/**/*", "tasks/mocks/*", - "types/**/*", + "types/**/*" ], "references": [ { "path": "../../../src/core/tsconfig.json" }, - { "path": "../../../src/plugins/bfetch/tsconfig.json"}, + { "path": "../../../src/plugins/bfetch/tsconfig.json" }, { "path": "../../../src/plugins/charts/tsconfig.json" }, - { "path": "../../../src/plugins/data/tsconfig.json"}, + { "path": "../../../src/plugins/data/tsconfig.json" }, + { "path": "../../../src/plugins/share/tsconfig.json" }, { "path": "../../../src/plugins/discover/tsconfig.json" }, { "path": "../../../src/plugins/embeddable/tsconfig.json" }, { "path": "../../../src/plugins/expressions/tsconfig.json" }, @@ -48,6 +49,6 @@ { "path": "../features/tsconfig.json" }, { "path": "../lens/tsconfig.json" }, { "path": "../maps/tsconfig.json" }, - { "path": "../reporting/tsconfig.json" }, + { "path": "../reporting/tsconfig.json" } ] } diff --git a/x-pack/plugins/reporting/common/build_kibana_path.ts b/x-pack/plugins/reporting/common/build_kibana_path.ts new file mode 100644 index 0000000000000..2cb37013300ca --- /dev/null +++ b/x-pack/plugins/reporting/common/build_kibana_path.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +interface Args { + basePath: string; + appPath: string; + spaceId?: string; +} + +export const buildKibanaPath = ({ basePath, appPath, spaceId }: Args) => { + return spaceId === undefined || spaceId.toLowerCase() === 'default' + ? `${basePath}${appPath}` + : `${basePath}/s/${spaceId}${appPath}`; +}; diff --git a/x-pack/plugins/reporting/common/constants.ts b/x-pack/plugins/reporting/common/constants.ts index 0e7d8f1ffe9ca..9224a23fcb33f 100644 --- a/x-pack/plugins/reporting/common/constants.ts +++ b/x-pack/plugins/reporting/common/constants.ts @@ -110,12 +110,10 @@ export const REPORTING_REDIRECT_LOCATOR_STORE_KEY = '__REPORTING_REDIRECT_LOCATO /** * A way to get the client side route for the reporting redirect app. * - * This route currently expects a job ID and a locator that to use from that job so that it can redirect to the - * correct page. - * - * TODO: Accommodate 'forceNow' value that some visualizations may rely on + * TODO: Add a job ID and a locator to use so that we can redirect without expecting state to + * be injected to the page */ -export const getRedirectAppPathHome = () => { +export const getRedirectAppPath = () => { return '/app/management/insightsAndAlerting/reporting/r'; }; diff --git a/x-pack/plugins/reporting/public/redirect/redirect_app.tsx b/x-pack/plugins/reporting/public/redirect/redirect_app.tsx index 60b51c0f07895..3024404dc07bf 100644 --- a/x-pack/plugins/reporting/public/redirect/redirect_app.tsx +++ b/x-pack/plugins/reporting/public/redirect/redirect_app.tsx @@ -49,7 +49,7 @@ export const RedirectApp: FunctionComponent = ({ share }) => { ]; if (!locatorParams) { - throw new Error('Could not find locator for report'); + throw new Error('Could not find locator params for report'); } share.navigate(locatorParams); diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts index f14bb249524e2..df91b6fe0ba47 100644 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts +++ b/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts @@ -121,7 +121,7 @@ export class HeadlessChromiumDriver { (key: string, value: unknown) => { Object.defineProperty(window, key, { configurable: false, - writable: false, + writable: true, enumerable: true, value, }); diff --git a/x-pack/plugins/reporting/server/export_types/common/v2/get_full_redirect_app_url.ts b/x-pack/plugins/reporting/server/export_types/common/v2/get_full_redirect_app_url.ts new file mode 100644 index 0000000000000..bb640eff667e9 --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/common/v2/get_full_redirect_app_url.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { format } from 'url'; +import { ReportingConfig } from '../../..'; +import { getRedirectAppPath } from '../../../../common/constants'; +import { buildKibanaPath } from '../../../../common/build_kibana_path'; + +export function getFullRedirectAppUrl(config: ReportingConfig, spaceId?: string) { + const [basePath, protocol, hostname, port] = [ + config.kbnConfig.get('server', 'basePath'), + config.get('kibanaServer', 'protocol'), + config.get('kibanaServer', 'hostname'), + config.get('kibanaServer', 'port'), + ] as string[]; + + const path = buildKibanaPath({ + basePath, + spaceId, + appPath: getRedirectAppPath(), + }); + + return format({ + protocol, + hostname, + port, + pathname: path, + }); +} diff --git a/x-pack/plugins/reporting/server/export_types/common/v2/get_full_urls.ts b/x-pack/plugins/reporting/server/export_types/common/v2/get_full_urls.ts deleted file mode 100644 index bcfb06784a6dc..0000000000000 --- a/x-pack/plugins/reporting/server/export_types/common/v2/get_full_urls.ts +++ /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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { parse as urlParse, UrlWithStringQuery } from 'url'; -import { ReportingConfig } from '../../../'; -import { getAbsoluteUrlFactory } from '../get_absolute_url'; -import { validateUrls } from '../validate_urls'; - -export function getFullUrls(config: ReportingConfig, relativeUrls: string[]) { - const [basePath, protocol, hostname, port] = [ - config.kbnConfig.get('server', 'basePath'), - config.get('kibanaServer', 'protocol'), - config.get('kibanaServer', 'hostname'), - config.get('kibanaServer', 'port'), - ] as string[]; - const getAbsoluteUrl = getAbsoluteUrlFactory({ basePath, protocol, hostname, port }); - - validateUrls(relativeUrls); - - const urls = relativeUrls.map((relativeUrl) => { - const parsedRelative: UrlWithStringQuery = urlParse(relativeUrl); - return getAbsoluteUrl({ - path: parsedRelative.pathname === null ? undefined : parsedRelative.pathname, - hash: parsedRelative.hash === null ? undefined : parsedRelative.hash, - search: parsedRelative.search === null ? undefined : parsedRelative.search, - }); - }); - - return urls; -} diff --git a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts index 06fdcd93e497c..5e3b3117f4bb5 100644 --- a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts +++ b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts @@ -8,7 +8,7 @@ import apm from 'elastic-apm-node'; import * as Rx from 'rxjs'; import { catchError, finalize, map, mergeMap, takeUntil, tap } from 'rxjs/operators'; -import { PNG_JOB_TYPE_V2, getRedirectAppPathHome } from '../../../common/constants'; +import { PNG_JOB_TYPE_V2 } from '../../../common/constants'; import { TaskRunResult } from '../../lib/tasks'; import { RunTaskFn, RunTaskFnFactory } from '../../types'; import { @@ -18,7 +18,7 @@ import { generatePngObservableFactory, setForceNow, } from '../common'; -import { getFullUrls } from '../common/v2/get_full_urls'; +import { getFullRedirectAppUrl } from '../common/v2/get_full_redirect_app_url'; import { TaskPayloadPNGV2 } from './types'; export const runTaskFnFactory: RunTaskFnFactory< @@ -39,8 +39,7 @@ export const runTaskFnFactory: RunTaskFnFactory< map((decryptedHeaders) => omitBlockedHeaders(decryptedHeaders)), map((filteredHeaders) => getConditionalHeaders(config, filteredHeaders)), mergeMap((conditionalHeaders) => { - const relativeUrl = getRedirectAppPathHome(); - const [url] = getFullUrls(config, [relativeUrl]); + const url = getFullRedirectAppUrl(config, job.spaceId); const [locatorParams] = job.locatorParams.map(setForceNow(job.forceNow)); apmGetAssets?.end(); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts index 2211e7df08770..f2cf8026c901e 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts @@ -49,7 +49,7 @@ export const runTaskFnFactory: RunTaskFnFactory< apmGeneratePdf = apmTrans?.startSpan('generate_pdf_pipeline', 'execute'); return generatePdfObservable( jobLogger, - jobId, + job, title, locatorParams.map(setForceNow(job.forceNow)), browserTimezone, diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts index 9be95223a8864..424a347876a1d 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts @@ -9,14 +9,14 @@ import { groupBy, zip } from 'lodash'; import * as Rx from 'rxjs'; import { mergeMap } from 'rxjs/operators'; import { ReportingCore } from '../../../'; -import { getRedirectAppPathHome } from '../../../../common/constants'; import { LocatorParams, UrlOrUrlLocatorTuple } from '../../../../common/types'; import { LevelLogger } from '../../../lib'; import { createLayout, LayoutParams } from '../../../lib/layouts'; import { getScreenshots$, ScreenshotResults } from '../../../lib/screenshots'; import { ConditionalHeaders } from '../../common'; import { PdfMaker } from '../../common/pdf'; -import { getFullUrls } from '../../common/v2/get_full_urls'; +import { getFullRedirectAppUrl } from '../../common/v2/get_full_redirect_app_url'; +import type { TaskPayloadPDFV2 } from '../types'; import { getTracker } from './tracker'; const getTimeRange = (urlScreenshots: ScreenshotResults[]) => { @@ -36,7 +36,7 @@ export async function generatePdfObservableFactory(reporting: ReportingCore) { return function generatePdfObservable( logger: LevelLogger, - jobId: string, + job: TaskPayloadPDFV2, title: string, locatorParams: LocatorParams[], browserTimezone: string | undefined, @@ -56,9 +56,7 @@ export async function generatePdfObservableFactory(reporting: ReportingCore) { /** * For each locator we get the relative URL to the redirect app */ - const relativeUrls = locatorParams.map(() => getRedirectAppPathHome()); - const urls = getFullUrls(reporting.getConfig(), relativeUrls); - + const urls = locatorParams.map(() => getFullRedirectAppUrl(reporting.getConfig(), job.spaceId)); const screenshots$ = getScreenshots$(captureConfig, browserDriverFactory, { logger, urlsOrUrlLocatorTuples: zip(urls, locatorParams) as UrlOrUrlLocatorTuple[], From 25de6438383f86f9cffcd87f5359634cb8048029 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Mon, 6 Sep 2021 07:34:34 -0700 Subject: [PATCH 05/43] Update UA to consume snapshotsUrl as provided by the Cloud plugin. (#111239) --- .../application/components/overview/backup_step/backup_step.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/backup_step/backup_step.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/backup_step/backup_step.tsx index 0e595dcbad1d6..b85f166e413f5 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/backup_step/backup_step.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/backup_step/backup_step.tsx @@ -30,7 +30,7 @@ export const getBackupStep = ({ cloud, cloudBackupStatusResponse }: Props): EuiS children: ( ), }; From d971470e08bf0e70d027978b75bc41345250375b Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 6 Sep 2021 10:48:57 -0400 Subject: [PATCH 06/43] [Lens] Reverse colors should not reverse palette picker previews (#110455) (#111276) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Marco Liberati --- .../coloring/palette_configuration.test.tsx | 30 ++ .../coloring/palette_configuration.tsx | 37 +-- .../coloring/palette_picker.tsx | 2 +- .../embeddable/lens_embeddable_factory.ts | 10 + .../server/migrations/common_migrations.ts | 57 +++- .../saved_object_migrations.test.ts | 277 +++++++++++++++++- .../migrations/saved_object_migrations.ts | 11 + .../plugins/lens/server/migrations/types.ts | 16 +- .../api_integration/apis/maps/migrations.js | 2 +- 9 files changed, 414 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.test.tsx b/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.test.tsx index cda891871168e..33f6ac379cd80 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.test.tsx +++ b/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.test.tsx @@ -14,6 +14,7 @@ import { ReactWrapper } from 'enzyme'; import type { CustomPaletteParams } from '../../../common'; import { applyPaletteParams } from './utils'; import { CustomizablePalette } from './palette_configuration'; +import { CUSTOM_PALETTE } from './constants'; import { act } from 'react-dom/test-utils'; // mocking random id generator function @@ -129,6 +130,21 @@ describe('palette panel', () => { }); }); + it('should restore the reverse initial state on transitioning', () => { + const instance = mountWithIntl(); + + changePaletteIn(instance, 'negative'); + + expect(props.setPalette).toHaveBeenCalledWith({ + type: 'palette', + name: 'negative', + params: expect.objectContaining({ + name: 'negative', + reverse: false, + }), + }); + }); + it('should rewrite the min/max range values on palette change', () => { const instance = mountWithIntl(); @@ -175,6 +191,20 @@ describe('palette panel', () => { }) ); }); + + it('should transition a predefined palette to a custom one on reverse click', () => { + const instance = mountWithIntl(); + + toggleReverse(instance, true); + + expect(props.setPalette).toHaveBeenCalledWith( + expect.objectContaining({ + params: expect.objectContaining({ + name: CUSTOM_PALETTE, + }), + }) + ); + }); }); describe('percentage / number modes', () => { diff --git a/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.tsx b/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.tsx index 1d1e212b87c0c..019e83fb0aa59 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.tsx +++ b/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.tsx @@ -106,6 +106,7 @@ export function CustomizablePalette({ ...activePalette.params, name: newPalette.name, colorStops: undefined, + reverse: false, // restore the reverse flag }; const newColorStops = getColorStops(palettes, [], activePalette, dataBounds); @@ -317,28 +318,20 @@ export function CustomizablePalette({ className="lnsPalettePanel__reverseButton" data-test-subj="lnsPalettePanel_dynamicColoring_reverse" onClick={() => { - const params: CustomPaletteParams = { reverse: !activePalette.params?.reverse }; - if (isCurrentPaletteCustom) { - params.colorStops = reversePalette(colorStopsToShow); - params.stops = getPaletteStops( - palettes, - { - ...(activePalette?.params || {}), - colorStops: params.colorStops, - }, - { dataBounds } - ); - } else { - params.stops = reversePalette( - activePalette?.params?.stops || - getPaletteStops( - palettes, - { ...activePalette.params, ...params }, - { prevPalette: activePalette.name, dataBounds } - ) - ); - } - setPalette(mergePaletteParams(activePalette, params)); + // when reversing a palette, the palette is automatically transitioned to a custom palette + const newParams = getSwitchToCustomParams( + palettes, + activePalette, + { + colorStops: reversePalette(colorStopsToShow), + steps: activePalette.params?.steps || DEFAULT_COLOR_STEPS, + reverse: !activePalette.params?.reverse, // Store the reverse state + rangeMin: colorStopsToShow[0]?.stop, + rangeMax: colorStopsToShow[colorStopsToShow.length - 1]?.stop, + }, + dataBounds + ); + setPalette(newParams); }} > diff --git a/x-pack/plugins/lens/public/shared_components/coloring/palette_picker.tsx b/x-pack/plugins/lens/public/shared_components/coloring/palette_picker.tsx index 2a415cd178925..b21b732820eaa 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/palette_picker.tsx +++ b/x-pack/plugins/lens/public/shared_components/coloring/palette_picker.tsx @@ -83,7 +83,7 @@ export function PalettePicker({ value: id, title, type: FIXED_PROGRESSION, - palette: activePalette?.params?.reverse ? colors.reverse() : colors, + palette: colors, 'data-test-subj': `${id}-palette`, }; }); diff --git a/x-pack/plugins/lens/server/embeddable/lens_embeddable_factory.ts b/x-pack/plugins/lens/server/embeddable/lens_embeddable_factory.ts index 86a3a600b58ab..4423d9e659119 100644 --- a/x-pack/plugins/lens/server/embeddable/lens_embeddable_factory.ts +++ b/x-pack/plugins/lens/server/embeddable/lens_embeddable_factory.ts @@ -9,6 +9,7 @@ import { EmbeddableRegistryDefinition } from 'src/plugins/embeddable/server'; import type { SerializableRecord } from '@kbn/utility-types'; import { DOC_TYPE } from '../../common'; import { + commonMakeReversePaletteAsCustom, commonRemoveTimezoneDateHistogramParam, commonRenameOperationsForFormula, commonUpdateVisLayerType, @@ -17,6 +18,7 @@ import { LensDocShape713, LensDocShape715, LensDocShapePre712, + VisState716, VisStatePre715, } from '../migrations/types'; import { extract, inject } from '../../common/embeddable_factory'; @@ -50,6 +52,14 @@ export const lensEmbeddableFactory = (): EmbeddableRegistryDefinition => { attributes: migratedLensState, } as unknown) as SerializableRecord; }, + '7.16.0': (state) => { + const lensState = (state as unknown) as { attributes: LensDocShape715 }; + const migratedLensState = commonMakeReversePaletteAsCustom(lensState.attributes); + return ({ + ...lensState, + attributes: migratedLensState, + } as unknown) as SerializableRecord; + }, }, extract, inject, diff --git a/x-pack/plugins/lens/server/migrations/common_migrations.ts b/x-pack/plugins/lens/server/migrations/common_migrations.ts index fda4300e03ea9..5755416957440 100644 --- a/x-pack/plugins/lens/server/migrations/common_migrations.ts +++ b/x-pack/plugins/lens/server/migrations/common_migrations.ts @@ -6,6 +6,7 @@ */ import { cloneDeep } from 'lodash'; +import { PaletteOutput } from 'src/plugins/charts/common'; import { LensDocShapePre712, OperationTypePre712, @@ -15,8 +16,9 @@ import { LensDocShape715, VisStatePost715, VisStatePre715, + VisState716, } from './types'; -import { layerTypes } from '../../common'; +import { CustomPaletteParams, layerTypes } from '../../common'; export const commonRenameOperationsForFormula = ( attributes: LensDocShapePre712 @@ -98,3 +100,56 @@ export const commonUpdateVisLayerType = ( } return newAttributes as LensDocShape715; }; + +function moveDefaultPaletteToPercentCustomInPlace(palette?: PaletteOutput) { + if (palette?.params?.reverse && palette.params.name !== 'custom' && palette.params.stops) { + // change to palette type to custom and migrate to a percentage type of mode + palette.name = 'custom'; + palette.params.name = 'custom'; + // we can make strong assumptions here: + // because it was a default palette reversed it means that stops were the default ones + // so when migrating, because there's no access to active data, we could leverage the + // percent rangeType to define colorStops in percent. + // + // Stops should be defined, but reversed, as the previous code was rewriting them on reverse. + // + // The only change the user should notice should be the mode changing from number to percent + // but the final result *must* be identical + palette.params.rangeType = 'percent'; + const steps = palette.params.stops.length; + palette.params.rangeMin = 0; + palette.params.rangeMax = 80; + palette.params.steps = steps; + palette.params.colorStops = palette.params.stops.map(({ color }, index) => ({ + color, + stop: (index * 100) / steps, + })); + palette.params.stops = palette.params.stops.map(({ color }, index) => ({ + color, + stop: ((1 + index) * 100) / steps, + })); + } +} + +export const commonMakeReversePaletteAsCustom = ( + attributes: LensDocShape715 +): LensDocShape715 => { + const newAttributes = cloneDeep(attributes); + const vizState = (newAttributes as LensDocShape715).state.visualization; + if ( + attributes.visualizationType !== 'lnsDatatable' && + attributes.visualizationType !== 'lnsHeatmap' + ) { + return newAttributes; + } + if ('columns' in vizState) { + for (const column of vizState.columns) { + if (column.colorMode && column.colorMode !== 'none') { + moveDefaultPaletteToPercentCustomInPlace(column.palette); + } + } + } else { + moveDefaultPaletteToPercentCustomInPlace(vizState.palette); + } + return newAttributes; +}; diff --git a/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts b/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts index afc6e6c6a590c..c16c5b5169ac5 100644 --- a/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts +++ b/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts @@ -12,8 +12,9 @@ import { SavedObjectMigrationFn, SavedObjectUnsanitizedDoc, } from 'src/core/server'; -import { LensDocShape715, VisStatePost715, VisStatePre715 } from './types'; -import { layerTypes } from '../../common'; +import { LensDocShape715, VisState716, VisStatePost715, VisStatePre715 } from './types'; +import { CustomPaletteParams, layerTypes } from '../../common'; +import { PaletteOutput } from 'src/plugins/charts/common'; describe('Lens migrations', () => { describe('7.7.0 missing dimensions in XY', () => { @@ -1129,4 +1130,276 @@ describe('Lens migrations', () => { } }); }); + + describe('7.16.0 move reversed default palette to custom palette', () => { + const context = ({ log: { warning: () => {} } } as unknown) as SavedObjectMigrationContext; + const example = ({ + type: 'lens', + id: 'mocked-saved-object-id', + attributes: { + savedObjectId: '1', + title: 'MyRenamedOps', + description: '', + visualizationType: null, + state: { + datasourceMetaData: { + filterableIndexPatterns: [], + }, + datasourceStates: { + indexpattern: { + currentIndexPatternId: 'logstash-*', + layers: { + '2': { + columns: { + '3': { + label: '@timestamp', + dataType: 'date', + operationType: 'date_histogram', + sourceField: '@timestamp', + isBucketed: true, + scale: 'interval', + params: { interval: 'auto', timeZone: 'Europe/Berlin' }, + }, + '4': { + label: '@timestamp', + dataType: 'date', + operationType: 'date_histogram', + sourceField: '@timestamp', + isBucketed: true, + scale: 'interval', + params: { interval: 'auto' }, + }, + '5': { + label: '@timestamp', + dataType: 'date', + operationType: 'my_unexpected_operation', + isBucketed: true, + scale: 'interval', + params: { timeZone: 'do not delete' }, + }, + }, + columnOrder: ['3', '4', '5'], + }, + }, + }, + }, + visualization: {}, + query: { query: '', language: 'kuery' }, + filters: [], + }, + }, + } as unknown) as SavedObjectUnsanitizedDoc>; + + it('should just return the same document for XY, partition and metric visualization types', () => { + for (const vizType of ['lnsXY', 'lnsPie', 'lnsMetric']) { + const exampleCopy = cloneDeep(example); + exampleCopy.attributes.visualizationType = vizType; + // add datatable state here, even with another viz (manual change?) + (exampleCopy.attributes as LensDocShape715).state.visualization = ({ + columns: [ + { palette: { type: 'palette', name: 'temperature' }, colorMode: 'cell' }, + { palette: { type: 'palette', name: 'temperature' }, colorMode: 'text' }, + { + palette: { type: 'palette', name: 'temperature', params: { reverse: false } }, + colorMode: 'cell', + }, + ], + } as unknown) as VisState716; + const result = migrations['7.16.0'](exampleCopy, context) as ReturnType< + SavedObjectMigrationFn + >; + expect(result).toEqual(exampleCopy); + } + }); + + it('should not change non reversed default palettes in datatable', () => { + const datatableExample = cloneDeep(example); + datatableExample.attributes.visualizationType = 'lnsDatatable'; + (datatableExample.attributes as LensDocShape715).state.visualization = ({ + columns: [ + { palette: { type: 'palette', name: 'temperature' }, colorMode: 'cell' }, + { palette: { type: 'palette', name: 'temperature' }, colorMode: 'text' }, + { + palette: { type: 'palette', name: 'temperature', params: { reverse: false } }, + colorMode: 'cell', + }, + ], + } as unknown) as VisState716; + const result = migrations['7.16.0'](datatableExample, context) as ReturnType< + SavedObjectMigrationFn + >; + expect(result).toEqual(datatableExample); + }); + + it('should not change custom palettes in datatable', () => { + const datatableExample = cloneDeep(example); + datatableExample.attributes.visualizationType = 'lnsDatatable'; + (datatableExample.attributes as LensDocShape715).state.visualization = ({ + columns: [ + { palette: { type: 'palette', name: 'custom' }, colorMode: 'cell' }, + { palette: { type: 'palette', name: 'custom' }, colorMode: 'text' }, + { + palette: { type: 'palette', name: 'custom', params: { reverse: true } }, + colorMode: 'cell', + }, + ], + } as unknown) as VisState716; + const result = migrations['7.16.0'](datatableExample, context) as ReturnType< + SavedObjectMigrationFn + >; + expect(result).toEqual(datatableExample); + }); + + it('should not change a datatable with no conditional coloring', () => { + const datatableExample = cloneDeep(example); + datatableExample.attributes.visualizationType = 'lnsDatatable'; + (datatableExample.attributes as LensDocShape715).state.visualization = ({ + columns: [{ colorMode: 'none' }, {}], + } as unknown) as VisState716; + const result = migrations['7.16.0'](datatableExample, context) as ReturnType< + SavedObjectMigrationFn + >; + expect(result).toEqual(datatableExample); + }); + + it('should not change default palette if the colorMode is set to "none" in datatable', () => { + const datatableExample = cloneDeep(example); + datatableExample.attributes.visualizationType = 'lnsDatatable'; + (datatableExample.attributes as LensDocShape715).state.visualization = ({ + columns: [ + { palette: { type: 'palette', name: 'temperature' }, colorMode: 'none' }, + { palette: { type: 'palette', name: 'temperature' }, colorMode: 'none' }, + { + palette: { type: 'palette', name: 'temperature', params: { reverse: true } }, + colorMode: 'cell', + }, + ], + } as unknown) as VisState716; + const result = migrations['7.16.0'](datatableExample, context) as ReturnType< + SavedObjectMigrationFn + >; + expect(result).toEqual(datatableExample); + }); + + it('should change a default palette reversed in datatable', () => { + const datatableExample = cloneDeep(example); + datatableExample.attributes.visualizationType = 'lnsDatatable'; + (datatableExample.attributes as LensDocShape715).state.visualization = ({ + columns: [ + { + colorMode: 'cell', + palette: { + type: 'palette', + name: 'temperature1', + params: { + reverse: true, + rangeType: 'number', + stops: [ + { color: 'red', stop: 10 }, + { color: 'blue', stop: 20 }, + { color: 'pink', stop: 50 }, + { color: 'green', stop: 60 }, + { color: 'yellow', stop: 70 }, + ], + }, + }, + }, + { + colorMode: 'text', + palette: { + type: 'palette', + name: 'temperature2', + params: { + reverse: true, + rangeType: 'number', + stops: [ + { color: 'red', stop: 10 }, + { color: 'blue', stop: 20 }, + { color: 'pink', stop: 50 }, + { color: 'green', stop: 60 }, + { color: 'yellow', stop: 70 }, + ], + }, + }, + }, + ], + } as unknown) as VisState716; + const result = migrations['7.16.0'](datatableExample, context) as ReturnType< + SavedObjectMigrationFn + >; + const state = (result.attributes as LensDocShape715< + Extract + >).state.visualization; + for (const column of state.columns) { + expect(column.palette!.name).toBe('custom'); + expect(column.palette!.params!.name).toBe('custom'); + expect(column.palette!.params!.rangeMin).toBe(0); + expect(column.palette!.params!.rangeMax).toBe(80); + expect(column.palette!.params!.reverse).toBeTruthy(); // still true + expect(column.palette!.params!.rangeType).toBe('percent'); + expect(column.palette!.params!.stops).toEqual([ + { color: 'red', stop: 20 }, + { color: 'blue', stop: 40 }, + { color: 'pink', stop: 60 }, + { color: 'green', stop: 80 }, + { color: 'yellow', stop: 100 }, + ]); + expect(column.palette!.params!.colorStops).toEqual([ + { color: 'red', stop: 0 }, + { color: 'blue', stop: 20 }, + { color: 'pink', stop: 40 }, + { color: 'green', stop: 60 }, + { color: 'yellow', stop: 80 }, + ]); + } + }); + + it('should change a default palette reversed in heatmap', () => { + const datatableExample = cloneDeep(example); + datatableExample.attributes.visualizationType = 'lnsHeatmap'; + (datatableExample.attributes as LensDocShape715).state.visualization = ({ + palette: { + type: 'palette', + name: 'temperature1', + params: { + reverse: true, + rangeType: 'number', + stops: [ + { color: 'red', stop: 10 }, + { color: 'blue', stop: 20 }, + { color: 'pink', stop: 50 }, + { color: 'green', stop: 60 }, + { color: 'yellow', stop: 70 }, + ], + }, + }, + } as unknown) as VisState716; + const result = migrations['7.16.0'](datatableExample, context) as ReturnType< + SavedObjectMigrationFn + >; + const state = (result.attributes as LensDocShape715< + Extract }> + >).state.visualization; + expect(state.palette!.name).toBe('custom'); + expect(state.palette!.params!.name).toBe('custom'); + expect(state.palette!.params!.rangeMin).toBe(0); + expect(state.palette!.params!.rangeMax).toBe(80); + expect(state.palette!.params!.reverse).toBeTruthy(); // still true + expect(state.palette!.params!.rangeType).toBe('percent'); + expect(state.palette!.params!.stops).toEqual([ + { color: 'red', stop: 20 }, + { color: 'blue', stop: 40 }, + { color: 'pink', stop: 60 }, + { color: 'green', stop: 80 }, + { color: 'yellow', stop: 100 }, + ]); + expect(state.palette!.params!.colorStops).toEqual([ + { color: 'red', stop: 0 }, + { color: 'blue', stop: 20 }, + { color: 'pink', stop: 40 }, + { color: 'green', stop: 60 }, + { color: 'yellow', stop: 80 }, + ]); + }); + }); }); diff --git a/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts b/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts index 7d08e76841cf5..901f0b5d6e684 100644 --- a/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts +++ b/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts @@ -23,11 +23,13 @@ import { LensDocShape715, VisStatePost715, VisStatePre715, + VisState716, } from './types'; import { commonRenameOperationsForFormula, commonRemoveTimezoneDateHistogramParam, commonUpdateVisLayerType, + commonMakeReversePaletteAsCustom, } from './common_migrations'; interface LensDocShapePre710 { @@ -430,6 +432,14 @@ const addLayerTypeToVisualization: SavedObjectMigrationFn< return { ...newDoc, attributes: commonUpdateVisLayerType(newDoc.attributes) }; }; +const moveDefaultReversedPaletteToCustom: SavedObjectMigrationFn< + LensDocShape715, + LensDocShape715 +> = (doc) => { + const newDoc = cloneDeep(doc); + return { ...newDoc, attributes: commonMakeReversePaletteAsCustom(newDoc.attributes) }; +}; + export const migrations: SavedObjectMigrationMap = { '7.7.0': removeInvalidAccessors, // The order of these migrations matter, since the timefield migration relies on the aggConfigs @@ -442,4 +452,5 @@ export const migrations: SavedObjectMigrationMap = { '7.13.1': renameOperationsForFormula, // duplicate this migration in case a broken by value panel is added to the library '7.14.0': removeTimezoneDateHistogramParam, '7.15.0': addLayerTypeToVisualization, + '7.16.0': moveDefaultReversedPaletteToCustom, }; diff --git a/x-pack/plugins/lens/server/migrations/types.ts b/x-pack/plugins/lens/server/migrations/types.ts index 09b460ff8b8cd..2e6e66aed9949 100644 --- a/x-pack/plugins/lens/server/migrations/types.ts +++ b/x-pack/plugins/lens/server/migrations/types.ts @@ -5,8 +5,9 @@ * 2.0. */ +import type { PaletteOutput } from 'src/plugins/charts/common'; import { Query, Filter } from 'src/plugins/data/public'; -import type { LayerType } from '../../common'; +import type { CustomPaletteParams, LayerType } from '../../common'; export type OperationTypePre712 = | 'avg' @@ -192,3 +193,16 @@ export interface LensDocShape715 { filters: Filter[]; }; } + +export type VisState716 = + // Datatable + | { + columns: Array<{ + palette?: PaletteOutput; + colorMode?: 'none' | 'cell' | 'text'; + }>; + } + // Heatmap + | { + palette?: PaletteOutput; + }; diff --git a/x-pack/test/api_integration/apis/maps/migrations.js b/x-pack/test/api_integration/apis/maps/migrations.js index b1f09616fc827..01a53f334b868 100644 --- a/x-pack/test/api_integration/apis/maps/migrations.js +++ b/x-pack/test/api_integration/apis/maps/migrations.js @@ -72,7 +72,7 @@ export default function ({ getService }) { } expect(panels.length).to.be(1); expect(panels[0].type).to.be('map'); - expect(panels[0].version).to.be('7.15.0'); + expect(panels[0].version).to.be('7.16.0'); }); }); }); From 17d06c8a07681e27aef3d13e08add2dc6c677742 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20Zolt=C3=A1n=20Szab=C3=B3?= Date: Mon, 6 Sep 2021 17:15:17 +0200 Subject: [PATCH 07/43] [APM] Uses doc link service instead of ElasticDocsLink for linking metadata (#110992) (#111282) --- .../kibana-plugin-core-public.doclinksstart.links.md | 1 + .../public/kibana-plugin-core-public.doclinksstart.md | 2 +- src/core/public/doc_links/doc_links_service.ts | 3 +++ src/core/public/public.api.md | 1 + .../apm/public/components/shared/MetadataTable/index.tsx | 8 +++++--- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index bc6075176cd22..253b0671cdd52 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -12,6 +12,7 @@ readonly links: { readonly apm: { readonly kibanaSettings: string; readonly supportedServiceMaps: string; + readonly metaData: string; }; readonly canvas: { readonly guide: string; diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md index aa3f958018041..7e409f23790f0 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md @@ -17,5 +17,5 @@ export interface DocLinksStart | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string | | -| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
readonly ecs: {
readonly guide: string;
};
} | | +| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly apm: {
readonly kibanaSettings: string;
readonly supportedServiceMaps: string;
readonly metaData: string;
};
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
readonly ecs: {
readonly guide: string;
};
} | | diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index fa895390978b1..86d35f88445a9 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -24,6 +24,7 @@ export class DocLinksService { const KIBANA_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/`; const FLEET_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/fleet/${DOC_LINK_VERSION}/`; const PLUGIN_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/plugins/${DOC_LINK_VERSION}/`; + const APM_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/apm/`; return deepFreeze({ DOC_LINK_VERSION, @@ -33,6 +34,7 @@ export class DocLinksService { apm: { kibanaSettings: `${KIBANA_DOCS}apm-settings-in-kibana.html`, supportedServiceMaps: `${KIBANA_DOCS}service-maps.html#service-maps-supported`, + metaData: `${APM_DOCS}get-started/${DOC_LINK_VERSION}/metadata.html`, }, canvas: { guide: `${KIBANA_DOCS}canvas.html`, @@ -460,6 +462,7 @@ export interface DocLinksStart { readonly apm: { readonly kibanaSettings: string; readonly supportedServiceMaps: string; + readonly metaData: string; }; readonly canvas: { readonly guide: string; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 827c509a21ed9..1a02bedd06ff4 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -477,6 +477,7 @@ export interface DocLinksStart { readonly apm: { readonly kibanaSettings: string; readonly supportedServiceMaps: string; + readonly metaData: string; }; readonly canvas: { readonly guide: string; diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/index.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/index.tsx index 938d5f4519431..45be525512d0a 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/index.tsx @@ -10,6 +10,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, + EuiLink, EuiSpacer, EuiText, EuiTitle, @@ -19,11 +20,11 @@ import { isEmpty } from 'lodash'; import React, { useCallback } from 'react'; import { useHistory, useLocation } from 'react-router-dom'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; -import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; import { HeightRetainer } from '../HeightRetainer'; import { fromQuery, toQuery } from '../Links/url_helpers'; import { filterSectionsByTerm, SectionsWithRows } from './helper'; import { Section } from './Section'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; interface Props { sections: SectionsWithRows; @@ -34,6 +35,7 @@ export function MetadataTable({ sections }: Props) { const location = useLocation(); const { urlParams } = useUrlParams(); const { searchTerm = '' } = urlParams; + const { docLinks } = useApmPluginContext().core; const filteredSections = filterSectionsByTerm(sections, searchTerm); @@ -55,11 +57,11 @@ export function MetadataTable({ sections }: Props) { - + How to add labels and other data - + Date: Mon, 6 Sep 2021 11:16:34 -0400 Subject: [PATCH 08/43] [Discover] Add permissions for `context size` test (#109391) (#111281) * [Discover] add permissions for flaky context test * [Discover] apply test_logstash_reader role Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Dmitry Tomashevich <39378793+Dmitriynj@users.noreply.github.com> --- test/functional/apps/context/_size.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/functional/apps/context/_size.ts b/test/functional/apps/context/_size.ts index b11af7cd5c72f..52b16d2b9abe5 100644 --- a/test/functional/apps/context/_size.ts +++ b/test/functional/apps/context/_size.ts @@ -15,6 +15,7 @@ const TEST_STEP_SIZE = 2; export default function ({ getService, getPageObjects }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); + const security = getService('security'); const retry = getService('retry'); const docTable = getService('docTable'); const browser = getService('browser'); @@ -23,6 +24,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('context size', function contextSize() { before(async function () { + await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader']); await kibanaServer.uiSettings.update({ 'context:defaultSize': `${TEST_DEFAULT_CONTEXT_SIZE}`, 'context:step': `${TEST_STEP_SIZE}`, From df03f8bbfbfee48258eeff6e6479b3af6cc779c4 Mon Sep 17 00:00:00 2001 From: Gloria Hornero Date: Mon, 6 Sep 2021 17:37:17 +0200 Subject: [PATCH 09/43] adds missing field when creating the email connector (#111251) (#111265) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/security_solution/cypress/objects/connector.ts | 2 ++ .../security_solution/cypress/screens/create_new_rule.ts | 2 ++ .../plugins/security_solution/cypress/tasks/create_new_rule.ts | 2 ++ 3 files changed, 6 insertions(+) diff --git a/x-pack/plugins/security_solution/cypress/objects/connector.ts b/x-pack/plugins/security_solution/cypress/objects/connector.ts index a5244583bf494..5c2abeab06026 100644 --- a/x-pack/plugins/security_solution/cypress/objects/connector.ts +++ b/x-pack/plugins/security_solution/cypress/objects/connector.ts @@ -12,6 +12,7 @@ export interface EmailConnector { port: string; user: string; password: string; + service: string; } export const getEmailConnector = (): EmailConnector => ({ @@ -21,4 +22,5 @@ export const getEmailConnector = (): EmailConnector => ({ port: '80', user: 'username', password: 'password', + service: 'Other', }); diff --git a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts index 551857ca3bfca..4748a48dbeb11 100644 --- a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts @@ -40,6 +40,8 @@ export const EMAIL_CONNECTOR_USER_INPUT = '[data-test-subj="emailUserInput"]'; export const EMAIL_CONNECTOR_PASSWORD_INPUT = '[data-test-subj="emailPasswordInput"]'; +export const EMAIL_CONNECTOR_SERVICE_SELECTOR = '[data-test-subj="emailServiceSelectInput"]'; + export const ADD_FALSE_POSITIVE_BTN = '[data-test-subj="detectionEngineStepAboutRuleFalsePositives"] .euiButtonEmpty__text'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index e2d27a11ed717..c1210bf457b69 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -92,6 +92,7 @@ import { EMAIL_CONNECTOR_PORT_INPUT, EMAIL_CONNECTOR_USER_INPUT, EMAIL_CONNECTOR_PASSWORD_INPUT, + EMAIL_CONNECTOR_SERVICE_SELECTOR, } from '../screens/create_new_rule'; import { LOADING_INDICATOR } from '../screens/security_header'; import { TOAST_ERROR } from '../screens/shared'; @@ -402,6 +403,7 @@ export const fillIndexAndIndicatorIndexPattern = ( export const fillEmailConnectorForm = (connector: EmailConnector = getEmailConnector()) => { cy.get(CONNECTOR_NAME_INPUT).type(connector.name); + cy.get(EMAIL_CONNECTOR_SERVICE_SELECTOR).select(connector.service); cy.get(EMAIL_CONNECTOR_FROM_INPUT).type(connector.from); cy.get(EMAIL_CONNECTOR_HOST_INPUT).type(connector.host); cy.get(EMAIL_CONNECTOR_PORT_INPUT).type(connector.port); From df091d28fb0ea1d729cc8745823931d2a9f98953 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 6 Sep 2021 12:41:47 -0400 Subject: [PATCH 10/43] Add test service to manage observability test users (#110849) (#111287) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Felix StĂŒrmer --- .../observability_security.ts | 96 ++++--------------- x-pack/test/functional/services/index.ts | 2 + .../services/observability/index.ts | 17 ++++ .../services/observability/users.ts | 92 ++++++++++++++++++ 4 files changed, 130 insertions(+), 77 deletions(-) create mode 100644 x-pack/test/functional/services/observability/index.ts create mode 100644 x-pack/test/functional/services/observability/users.ts diff --git a/x-pack/test/functional/apps/observability/feature_controls/observability_security.ts b/x-pack/test/functional/apps/observability/feature_controls/observability_security.ts index 1d52088ede3da..69bf995c49ae4 100644 --- a/x-pack/test/functional/apps/observability/feature_controls/observability_security.ts +++ b/x-pack/test/functional/apps/observability/feature_controls/observability_security.ts @@ -10,7 +10,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); - const security = getService('security'); + const observability = getService('observability'); const PageObjects = getPageObjects([ 'common', 'observability', @@ -20,6 +20,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ]); const appsMenu = getService('appsMenu'); const testSubjects = getService('testSubjects'); + describe('observability security feature controls', function () { this.tags(['skipFirefox']); before(async () => { @@ -32,39 +33,20 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('observability cases all privileges', () => { before(async () => { - await security.role.create('cases_observability_all_role', { - elasticsearch: { cluster: [], indices: [], run_as: [] }, - kibana: [ - { spaces: ['*'], base: [], feature: { observabilityCases: ['all'], logs: ['all'] } }, - ], - }); - - await security.user.create('cases_observability_all_user', { - password: 'cases_observability_all_user-password', - roles: ['cases_observability_all_role'], - full_name: 'test user', - }); - - await PageObjects.security.forceLogout(); - - await PageObjects.security.login( - 'cases_observability_all_user', - 'cases_observability_all_user-password', - { - expectSpaceSelector: false, - } + await observability.users.setTestUserRole( + observability.users.defineBasicObservabilityRole({ + observabilityCases: ['all'], + logs: ['all'], + }) ); }); after(async () => { - await PageObjects.security.forceLogout(); - await Promise.all([ - security.role.delete('cases_observability_all_role'), - security.user.delete('cases_observability_all_user'), - ]); + await observability.users.restoreDefaultTestUserRole(); }); it('shows observability/cases navlink', async () => { + await PageObjects.common.navigateToActualUrl('observability'); const navLinks = (await appsMenu.readLinks()).map((link) => link.text); expect(navLinks).to.contain('Cases'); }); @@ -101,38 +83,20 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('observability cases read-only privileges', () => { before(async () => { - await security.role.create('cases_observability_read_role', { - elasticsearch: { cluster: [], indices: [], run_as: [] }, - kibana: [ - { - spaces: ['*'], - base: [], - feature: { observabilityCases: ['read'], logs: ['all'] }, - }, - ], - }); - - await security.user.create('cases_observability_read_user', { - password: 'cases_observability_read_user-password', - roles: ['cases_observability_read_role'], - full_name: 'test user', - }); - - await PageObjects.security.login( - 'cases_observability_read_user', - 'cases_observability_read_user-password', - { - expectSpaceSelector: false, - } + await observability.users.setTestUserRole( + observability.users.defineBasicObservabilityRole({ + observabilityCases: ['read'], + logs: ['all'], + }) ); }); after(async () => { - await security.role.delete('cases_observability_read_role'); - await security.user.delete('cases_observability_read_user'); + await observability.users.restoreDefaultTestUserRole(); }); it('shows observability/cases navlink', async () => { + await PageObjects.common.navigateToActualUrl('observability'); const navLinks = (await appsMenu.readLinks()).map((link) => link.text); expect(navLinks).to.contain('Cases'); }); @@ -170,36 +134,14 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('no observability privileges', () => { before(async () => { - await security.role.create('no_observability_privileges_role', { + await observability.users.setTestUserRole({ elasticsearch: { cluster: [], indices: [], run_as: [] }, - kibana: [ - { - feature: { - discover: ['all'], - }, - spaces: ['*'], - }, - ], + kibana: [{ spaces: ['*'], base: [], feature: { discover: ['all'] } }], }); - - await security.user.create('no_observability_privileges_user', { - password: 'no_observability_privileges_user-password', - roles: ['no_observability_privileges_role'], - full_name: 'test user', - }); - - await PageObjects.security.login( - 'no_observability_privileges_user', - 'no_observability_privileges_user-password', - { - expectSpaceSelector: false, - } - ); }); after(async () => { - await security.role.delete('no_observability_privileges_role'); - await security.user.delete('no_observability_privileges_user'); + await observability.users.restoreDefaultTestUserRole(); }); it(`returns a 403`, async () => { diff --git a/x-pack/test/functional/services/index.ts b/x-pack/test/functional/services/index.ts index 273db212400ab..5e40eb040178b 100644 --- a/x-pack/test/functional/services/index.ts +++ b/x-pack/test/functional/services/index.ts @@ -60,6 +60,7 @@ import { DashboardPanelTimeRangeProvider, } from './dashboard'; import { SearchSessionsService } from './search_sessions'; +import { ObservabilityProvider } from './observability'; // define the name and providers for services that should be // available to your tests. If you don't specify anything here @@ -110,4 +111,5 @@ export const services = { dashboardPanelTimeRange: DashboardPanelTimeRangeProvider, reporting: ReportingFunctionalProvider, searchSessions: SearchSessionsService, + observability: ObservabilityProvider, }; diff --git a/x-pack/test/functional/services/observability/index.ts b/x-pack/test/functional/services/observability/index.ts new file mode 100644 index 0000000000000..14f931d93b56f --- /dev/null +++ b/x-pack/test/functional/services/observability/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; +import { ObservabilityUsersProvider } from './users'; + +export function ObservabilityProvider(context: FtrProviderContext) { + const users = ObservabilityUsersProvider(context); + + return { + users, + }; +} diff --git a/x-pack/test/functional/services/observability/users.ts b/x-pack/test/functional/services/observability/users.ts new file mode 100644 index 0000000000000..78e8b3346cc67 --- /dev/null +++ b/x-pack/test/functional/services/observability/users.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Role } from '../../../../plugins/security/common/model'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +type CreateRolePayload = Pick; + +const OBSERVABILITY_TEST_ROLE_NAME = 'observability-functional-test-role'; + +export function ObservabilityUsersProvider({ getPageObject, getService }: FtrProviderContext) { + const security = getService('security'); + const commonPageObject = getPageObject('common'); + + /** + * Creates a test role and set it as the test user's role. Performs a page + * reload to apply the role change, but doesn't require a re-login. + * + * @arg roleDefinition - the privileges of the test role + */ + const setTestUserRole = async (roleDefinition: CreateRolePayload) => { + // return to neutral grounds to avoid running into permission problems on reload + await commonPageObject.navigateToActualUrl('kibana'); + + await security.role.create(OBSERVABILITY_TEST_ROLE_NAME, roleDefinition); + + await security.testUser.setRoles([OBSERVABILITY_TEST_ROLE_NAME]); // performs a page reload + }; + + /** + * Deletes the test role and restores thedefault test user role. Performs a + * page reload to apply the role change, but doesn't require a re-login. + */ + const restoreDefaultTestUserRole = async () => { + await Promise.all([ + security.role.delete(OBSERVABILITY_TEST_ROLE_NAME), + security.testUser.restoreDefaults(), + ]); + }; + + return { + defineBasicObservabilityRole, + restoreDefaultTestUserRole, + setTestUserRole, + }; +} + +/** + * Generates a combination of Elasticsearch and Kibana privileges for given + * observability features. + */ +const defineBasicObservabilityRole = ( + features: Partial<{ + observabilityCases: string[]; + apm: string[]; + logs: string[]; + infrastructure: string[]; + uptime: string[]; + }> +): CreateRolePayload => { + return { + elasticsearch: { + cluster: ['all'], + indices: [ + ...((features.logs?.length ?? 0) > 0 + ? [{ names: ['filebeat-*', 'logs-*'], privileges: ['all'] }] + : []), + ...((features.infrastructure?.length ?? 0) > 0 + ? [{ names: ['metricbeat-*', 'metrics-*'], privileges: ['all'] }] + : []), + ...((features.apm?.length ?? 0) > 0 ? [{ names: ['apm-*'], privileges: ['all'] }] : []), + ...((features.uptime?.length ?? 0) > 0 + ? [{ names: ['heartbeat-*,synthetics-*'], privileges: ['all'] }] + : []), + ], + run_as: [], + }, + kibana: [ + { + spaces: ['*'], + base: [], + // @ts-expect-error TypeScript doesn't distinguish between missing and + // undefined props yet + feature: features, + }, + ], + }; +}; From 395844ea25542a707b62d811f18155d44cf4586b Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 6 Sep 2021 13:08:04 -0400 Subject: [PATCH 11/43] [Security Solution] Update protection names in Policy config (#111202) (#111291) Co-authored-by: Kevin Logan <56395104+kevinlog@users.noreply.github.com> --- .../pages/policy/view/policy_forms/protections/behavior.tsx | 4 ++-- .../pages/policy/view/policy_forms/protections/memory.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/behavior.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/behavior.tsx index f52d8a4c70706..06cf666f2950e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/behavior.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/behavior.tsx @@ -28,13 +28,13 @@ export const BehaviorProtection = React.memo(() => { const protectionLabel = i18n.translate( 'xpack.securitySolution.endpoint.policy.protections.behavior', { - defaultMessage: 'Behavior', + defaultMessage: 'Malicious behavior protections', } ); return ( { const protectionLabel = i18n.translate( 'xpack.securitySolution.endpoint.policy.protections.memory', { - defaultMessage: 'Memory manipulation', + defaultMessage: 'Memory threat protections', } ); return ( Date: Mon, 6 Sep 2021 13:11:06 -0400 Subject: [PATCH 12/43] Support the warning state for crawler validation steps (#110864) (#110991) Co-authored-by: Byron Hulcher --- .../crawler/components/add_domain/utils.ts | 4 ++- .../add_domain/validation_state_icon.test.tsx | 6 +++++ .../add_domain/validation_state_icon.tsx | 13 +++++++--- .../add_domain/validation_step_panel.test.tsx | 25 ++++++++++++++++--- .../add_domain/validation_step_panel.tsx | 6 +++-- .../app_search/components/crawler/types.ts | 2 +- .../components/crawler/utils.test.ts | 2 +- .../app_search/components/crawler/utils.ts | 2 +- 8 files changed, 48 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/utils.ts index fb72c1da0a6b1..b0dc8418c4ca9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/utils.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/utils.ts @@ -62,10 +62,12 @@ export const getDomainWithProtocol = async (domain: string) => { export const domainValidationStateToPanelColor = ( state: CrawlerDomainValidationStepState -): 'success' | 'danger' | 'subdued' => { +): 'success' | 'warning' | 'danger' | 'subdued' => { switch (state) { case 'valid': return 'success'; + case 'warning': + return 'warning'; case 'invalid': return 'danger'; default: diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_state_icon.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_state_icon.test.tsx index 2c27e99e02ef6..8bb82f93e3ec4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_state_icon.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_state_icon.test.tsx @@ -20,6 +20,12 @@ describe('ValidationStateIcon', () => { expect(wrapper.find(EuiIcon).prop('color')).toEqual('success'); }); + it('shows a warning icon when warning', () => { + const wrapper = shallow(); + + expect(wrapper.find(EuiIcon).prop('color')).toEqual('warning'); + }); + it('shows a danger icon when invalid', () => { const wrapper = shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_state_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_state_icon.tsx index 0c3d5329c47b7..3d85455ec7a8a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_state_icon.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_state_icon.tsx @@ -14,7 +14,14 @@ import { CrawlerDomainValidationStepState } from '../../types'; export const ValidationStateIcon: React.FC<{ state: CrawlerDomainValidationStepState }> = ({ state, }) => { - if (state === 'valid') return ; - if (state === 'invalid') return ; - return ; + switch (state) { + case 'valid': + return ; + case 'warning': + return ; + case 'invalid': + return ; + default: + return ; + } }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_step_panel.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_step_panel.test.tsx index a02a29c9854dd..c022b09d4638c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_step_panel.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_step_panel.test.tsx @@ -19,12 +19,15 @@ describe('ValidationStepPanel', () => { const wrapper = shallow( ); + it('passed the correct color to the EuiPanel', () => { expect(wrapper.find(EuiPanel).prop('color')).toEqual('success'); }); + it('contains a validation state icon', () => { expect(wrapper.find(ValidationStateIcon)).toHaveLength(1); }); + it('renders a label', () => { expect(wrapper.find('h3').text()).toEqual('Initial validation'); }); @@ -32,11 +35,26 @@ describe('ValidationStepPanel', () => { describe('invalid messages and actions', () => { const errorMessage = 'Error message'; const action =

; + it('displays the passed error message and action is invalid', () => { const wrapper = shallow( + ); + expect(wrapper.find('[data-test-subj="errorMessage"]').dive().text()).toContain( + 'Error message' + ); + expect(wrapper.find('[data-test-subj="action"]')).toHaveLength(1); + }); + + it('displays the passed error message and action when state is warning', () => { + const wrapper = shallow( + ); @@ -45,11 +63,12 @@ describe('ValidationStepPanel', () => { ); expect(wrapper.find('[data-test-subj="action"]')).toHaveLength(1); }); - it('does not display the passed error message or action when state is not invalid', () => { + + it('does not display the passed error message or action when state is loading', () => { const wrapper = shallow( ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_step_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_step_panel.tsx index 8d3faed1fbc58..804c2d86ca099 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_step_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_step_panel.tsx @@ -25,11 +25,13 @@ export const ValidationStepPanel: React.FC = ({ label, action, }) => { + const showErrorMessage = step.state === 'invalid' || step.state === 'warning'; + return ( - + @@ -37,7 +39,7 @@ export const ValidationStepPanel: React.FC = ({ - {step.state === 'invalid' && ( + {showErrorMessage && ( <>

{step.message}

diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/types.ts index 932af7a6ac93b..8cfbce6c10315 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/types.ts @@ -135,7 +135,7 @@ export interface CrawlerDomainValidationResultFromServer { }>; } -export type CrawlerDomainValidationStepState = '' | 'loading' | 'valid' | 'invalid'; +export type CrawlerDomainValidationStepState = '' | 'loading' | 'valid' | 'warning' | 'invalid'; export interface CrawlerDomainValidationStep { state: CrawlerDomainValidationStepState; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/utils.test.ts index 1844932bac926..b679a7cc9c12c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/utils.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/utils.test.ts @@ -203,7 +203,7 @@ describe('crawlDomainValidationToResult', () => { expect(crawlDomainValidationToResult(data)).toEqual({ blockingFailure: false, - state: 'invalid', + state: 'warning', message: 'A warning, not failure', } as CrawlerDomainValidationStep); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/utils.ts index 1f54db12a0217..e44e6c0e652fa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/utils.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/utils.ts @@ -99,7 +99,7 @@ export function crawlDomainValidationToResult( if (warningResult) { return { - state: 'invalid', + state: 'warning', blockingFailure: !data.valid, message: warningResult.comment, }; From ee257a2adc0157313ad72220374268959edc5359 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 6 Sep 2021 13:11:20 -0400 Subject: [PATCH 13/43] [Security Solution] Add Windows kernel advanced policy options for 7.15 (#111182) (#111290) Co-authored-by: Kevin Logan <56395104+kevinlog@users.noreply.github.com> --- .../policy/models/advanced_policy_schema.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts b/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts index 1add8bb9d6f76..4d7ca74ca19f8 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts @@ -584,6 +584,28 @@ export const AdvancedPolicySchema: AdvancedPolicySchemaType[] = [ } ), }, + { + key: 'windows.advanced.kernel.fileaccess', + first_supported_version: '7.15', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.kernel.fileaccess', + { + defaultMessage: + 'Report limited file access (read) events. Paths are not user-configurable. Default value is true.', + } + ), + }, + { + key: 'windows.advanced.kernel.registryaccess', + first_supported_version: '7.15', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.kernel.registryaccess', + { + defaultMessage: + 'Report limited registry access (queryvalue, savekey) events. Paths are not user-configurable. Default value is true.', + } + ), + }, { key: 'windows.advanced.diagnostic.enabled', first_supported_version: '7.11', From 4542e15b8acf345266132eea2030fd0233b0521a Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 6 Sep 2021 13:31:49 -0400 Subject: [PATCH 14/43] [APM] Missing transaction type error when creating Latency threshold Alert (#110336) (#111292) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * redirect to page adding transaction type * skipping transaction type Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: CauĂȘ Marcondes <55978943+cauemarcondes@users.noreply.github.com> --- .../alerting/alerting_flyout/index.tsx | 2 +- .../service_inventory/service_list/index.tsx | 4 +- .../components/app/service_overview/index.tsx | 20 ++++++-- .../app/transaction_details/index.tsx | 21 ++++++-- .../app/transaction_overview/index.tsx | 49 ++++++------------- .../app/transaction_overview/useRedirect.ts | 20 -------- 6 files changed, 52 insertions(+), 64 deletions(-) delete mode 100644 x-pack/plugins/apm/public/components/app/transaction_overview/useRedirect.ts diff --git a/x-pack/plugins/apm/public/components/alerting/alerting_flyout/index.tsx b/x-pack/plugins/apm/public/components/alerting/alerting_flyout/index.tsx index 7cf3100046d57..fa56c44d8d374 100644 --- a/x-pack/plugins/apm/public/components/alerting/alerting_flyout/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/alerting_flyout/index.tsx @@ -60,7 +60,7 @@ export function AlertingFlyout(props: Props) { metadata: { environment, serviceName, - transactionType, + ...(alertType === AlertType.ErrorCount ? {} : { transactionType }), start, end, } as AlertMetadata, diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx index 8732084e6331e..a3820622f8c9d 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx @@ -99,14 +99,14 @@ export function getServiceColumns({ }), width: '40%', sortable: true, - render: (_, { serviceName, agentName }) => ( + render: (_, { serviceName, agentName, transactionType }) => ( } diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index 9af296e8a20b4..45372188994c7 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -8,6 +8,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; +import { useHistory } from 'react-router-dom'; import { isRumAgentName, isIosAgentName } from '../../../../common/agent_name'; import { AnnotationsContextProvider } from '../../../context/annotations/annotations_context'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; @@ -26,6 +27,7 @@ import { useFallbackToTransactionsFetcher } from '../../../hooks/use_fallback_to import { AggregatedTransactionsBadge } from '../../shared/aggregated_transactions_badge'; import { useApmRouter } from '../../../hooks/use_apm_router'; import { useTimeRange } from '../../../hooks/use_time_range'; +import { replace } from '../../shared/Links/url_helpers'; /** * The height a chart should be if it's next to a table with 5 rows and a title. @@ -34,17 +36,29 @@ import { useTimeRange } from '../../../hooks/use_time_range'; export const chartHeight = 288; export function ServiceOverview() { - const { agentName, serviceName } = useApmServiceContext(); + const { agentName, serviceName, transactionType } = useApmServiceContext(); const { query, - query: { environment, kuery, rangeFrom, rangeTo }, + query: { + environment, + kuery, + rangeFrom, + rangeTo, + transactionType: transactionTypeFromUrl, + }, } = useApmParams('/services/:serviceName/overview'); const { fallbackToTransactions } = useFallbackToTransactionsFetcher({ kuery, }); - const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + const history = useHistory(); + + // redirect to first transaction type + if (!transactionTypeFromUrl && transactionType) { + replace(history, { query: { transactionType } }); + } + // The default EuiFlexGroup breaks at 768, but we want to break at 992, so we // observe the window width and set the flex directions of rows accordingly const { isMedium } = useBreakPoints(); diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx index 06acaeeb5dd3b..ab59b60333e38 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx @@ -7,6 +7,8 @@ import { EuiSpacer, EuiTitle } from '@elastic/eui'; import React from 'react'; +import { useHistory } from 'react-router-dom'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { useBreadcrumb } from '../../../context/breadcrumbs/use_breadcrumb'; import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; import { useApmParams } from '../../../hooks/use_apm_params'; @@ -15,18 +17,29 @@ import { useTimeRange } from '../../../hooks/use_time_range'; import { useFallbackToTransactionsFetcher } from '../../../hooks/use_fallback_to_transactions_fetcher'; import { AggregatedTransactionsBadge } from '../../shared/aggregated_transactions_badge'; import { TransactionCharts } from '../../shared/charts/transaction_charts'; - +import { replace } from '../../shared/Links/url_helpers'; import { TransactionDetailsTabs } from './transaction_details_tabs'; export function TransactionDetails() { const { path, query } = useApmParams( '/services/:serviceName/transactions/view' ); - const { transactionName, rangeFrom, rangeTo } = query; - + const { + transactionName, + rangeFrom, + rangeTo, + transactionType: transactionTypeFromUrl, + } = query; const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - const apmRouter = useApmRouter(); + const { transactionType } = useApmServiceContext(); + + const history = useHistory(); + + // redirect to first transaction type + if (!transactionTypeFromUrl && transactionType) { + replace(history, { query: { transactionType } }); + } useBreadcrumb({ title: transactionName, diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx index be12522920740..571ba99d9bf08 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx @@ -5,48 +5,27 @@ * 2.0. */ -import { EuiPanel, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { Location } from 'history'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer } from '@elastic/eui'; import React from 'react'; -import { useLocation } from 'react-router-dom'; +import { useHistory } from 'react-router-dom'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; -import type { ApmUrlParams } from '../../../context/url_params_context/types'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { useApmParams } from '../../../hooks/use_apm_params'; import { useFallbackToTransactionsFetcher } from '../../../hooks/use_fallback_to_transactions_fetcher'; import { useTimeRange } from '../../../hooks/use_time_range'; import { AggregatedTransactionsBadge } from '../../shared/aggregated_transactions_badge'; import { TransactionCharts } from '../../shared/charts/transaction_charts'; -import { fromQuery, toQuery } from '../../shared/Links/url_helpers'; +import { replace } from '../../shared/Links/url_helpers'; import { TransactionsTable } from '../../shared/transactions_table'; -import { useRedirect } from './useRedirect'; - -function getRedirectLocation({ - location, - transactionType, - urlParams, -}: { - location: Location; - transactionType?: string; - urlParams: ApmUrlParams; -}): Location | undefined { - const transactionTypeFromUrlParams = urlParams.transactionType; - - if (!transactionTypeFromUrlParams && transactionType) { - return { - ...location, - search: fromQuery({ - ...toQuery(location.search), - transactionType, - }), - }; - } -} - export function TransactionOverview() { const { - query: { environment, kuery, rangeFrom, rangeTo }, + query: { + environment, + kuery, + rangeFrom, + rangeTo, + transactionType: transactionTypeFromUrl, + }, } = useApmParams('/services/:serviceName/transactions'); const { start, end } = useTimeRange({ rangeFrom, rangeTo }); @@ -54,12 +33,14 @@ export function TransactionOverview() { const { fallbackToTransactions } = useFallbackToTransactionsFetcher({ kuery, }); - const location = useLocation(); - const { urlParams } = useUrlParams(); const { transactionType, serviceName } = useApmServiceContext(); + const history = useHistory(); + // redirect to first transaction type - useRedirect(getRedirectLocation({ location, transactionType, urlParams })); + if (!transactionTypeFromUrl && transactionType) { + replace(history, { query: { transactionType } }); + } // TODO: improve urlParams typings. // `serviceName` or `transactionType` will never be undefined here, and this check should not be needed diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/useRedirect.ts b/x-pack/plugins/apm/public/components/app/transaction_overview/useRedirect.ts deleted file mode 100644 index fae80eec42f9b..0000000000000 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/useRedirect.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { Location } from 'history'; -import { useEffect } from 'react'; -import { useHistory } from 'react-router-dom'; - -export function useRedirect(redirectLocation?: Location) { - const history = useHistory(); - - useEffect(() => { - if (redirectLocation) { - history.replace(redirectLocation); - } - }, [history, redirectLocation]); -} From 2cace2f7ec06d7a3f46e8df2d848903c4c69b5a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20Zolt=C3=A1n=20Szab=C3=B3?= Date: Mon, 6 Sep 2021 19:35:12 +0200 Subject: [PATCH 15/43] [APM] Uses doc link service instead of ElasticDocsLink for linking upgrading info (#111155) (#111293) --- .../kibana-plugin-core-public.doclinksstart.links.md | 1 + .../kibana-plugin-core-public.doclinksstart.md | 1 + src/core/public/doc_links/doc_links_service.ts | 2 ++ src/core/public/public.api.md | 1 + .../components/app/service_node_metrics/index.tsx | 12 +++++------- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index 253b0671cdd52..94809d3ddfa96 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -12,6 +12,7 @@ readonly links: { readonly apm: { readonly kibanaSettings: string; readonly supportedServiceMaps: string; + readonly upgrading: string; readonly metaData: string; }; readonly canvas: { diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md index 7e409f23790f0..4b6e1ad2631b4 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md @@ -17,5 +17,6 @@ export interface DocLinksStart | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string | | +| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly apm: {
readonly kibanaSettings: string;
readonly supportedServiceMaps: string;
readonly upgrading: string;
};
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
readonly ecs: {
readonly guide: string;
};
} | | | [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly apm: {
readonly kibanaSettings: string;
readonly supportedServiceMaps: string;
readonly metaData: string;
};
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
readonly ecs: {
readonly guide: string;
};
} | | diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 86d35f88445a9..e51a3c42062f9 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -34,6 +34,7 @@ export class DocLinksService { apm: { kibanaSettings: `${KIBANA_DOCS}apm-settings-in-kibana.html`, supportedServiceMaps: `${KIBANA_DOCS}service-maps.html#service-maps-supported`, + upgrading: `${APM_DOCS}server/${DOC_LINK_VERSION}/upgrading.html`, metaData: `${APM_DOCS}get-started/${DOC_LINK_VERSION}/metadata.html`, }, canvas: { @@ -462,6 +463,7 @@ export interface DocLinksStart { readonly apm: { readonly kibanaSettings: string; readonly supportedServiceMaps: string; + readonly upgrading: string; readonly metaData: string; }; readonly canvas: { diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 1a02bedd06ff4..d074877337458 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -477,6 +477,7 @@ export interface DocLinksStart { readonly apm: { readonly kibanaSettings: string; readonly supportedServiceMaps: string; + readonly upgrading: string; readonly metaData: string; }; readonly canvas: { diff --git a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx index c0578514ff9ad..29bc639ee9832 100644 --- a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx @@ -10,6 +10,7 @@ import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, + EuiLink, EuiPanel, EuiSpacer, EuiStat, @@ -24,6 +25,7 @@ import { SERVICE_NODE_NAME_MISSING, } from '../../../../common/service_nodes'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { useBreadcrumb } from '../../../context/breadcrumbs/use_breadcrumb'; import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; import { useApmParams } from '../../../hooks/use_apm_params'; @@ -33,7 +35,6 @@ import { useServiceMetricChartsFetcher } from '../../../hooks/use_service_metric import { useTimeRange } from '../../../hooks/use_time_range'; import { truncate, unit } from '../../../utils/style'; import { MetricsChart } from '../../shared/charts/metrics_chart'; -import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; const INITIAL_DATA = { host: '', @@ -99,6 +100,7 @@ export function ServiceNodeMetrics() { [kuery, serviceName, serviceNodeName, start, end] ); + const { docLinks } = useApmPluginContext().core; const isLoading = status === FETCH_STATUS.LOADING; const isAggregatedData = serviceNodeName === SERVICE_NODE_NAME_MISSING; @@ -120,16 +122,12 @@ export function ServiceNodeMetrics() { defaultMessage="We could not identify which JVMs these metrics belong to. This is likely caused by running a version of APM Server that is older than 7.5. Upgrading to APM Server 7.5 or higher should resolve this issue. For more information on upgrading, see the {link}. As an alternative, you can use the Kibana Query bar to filter by hostname, container ID or other fields." values={{ link: ( - + {i18n.translate( 'xpack.apm.serviceNodeMetrics.unidentifiedServiceNodesWarningDocumentationLink', { defaultMessage: 'documentation of APM Server' } )} - + ), }} /> From 5d599f1e6a997d64df87bc92f4cca14ee6d2e86e Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 6 Sep 2021 14:08:05 -0400 Subject: [PATCH 16/43] Fix "Expression produces a union type that is too complex to represent" TS error (#111111) (#111153) Co-authored-by: Dmitry Shevchenko --- .../rule_registry_log_client.ts | 126 +++++++++--------- 1 file changed, 64 insertions(+), 62 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/rule_registry_log_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/rule_registry_log_client.ts index f0da8dad16ab0..a5515f8db8552 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/rule_registry_log_client.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/rule_registry_log_client.ts @@ -142,76 +142,78 @@ export class RuleRegistryLogClient implements IRuleRegistryLogClient { invariant(result.aggregations, 'Search response should contain aggregations'); return Object.fromEntries( - result.aggregations.rules.buckets.map((bucket) => [ - bucket.key, - bucket.most_recent_logs.hits.hits.map((event) => { - const logEntry = parseRuleExecutionLog(event._source); - invariant( - logEntry[ALERT_RULE_UUID] ?? '', - 'Malformed execution log entry: rule.id field not found' - ); + result.aggregations.rules.buckets.map<[ruleId: string, logs: IRuleStatusSOAttributes[]]>( + (bucket) => [ + bucket.key as string, + bucket.most_recent_logs.hits.hits.map((event) => { + const logEntry = parseRuleExecutionLog(event._source); + invariant( + logEntry[ALERT_RULE_UUID] ?? '', + 'Malformed execution log entry: rule.id field not found' + ); - const lastFailure = bucket.last_failure.event.hits.hits[0] - ? parseRuleExecutionLog(bucket.last_failure.event.hits.hits[0]._source) - : undefined; + const lastFailure = bucket.last_failure.event.hits.hits[0] + ? parseRuleExecutionLog(bucket.last_failure.event.hits.hits[0]._source) + : undefined; - const lastSuccess = bucket.last_success.event.hits.hits[0] - ? parseRuleExecutionLog(bucket.last_success.event.hits.hits[0]._source) - : undefined; + const lastSuccess = bucket.last_success.event.hits.hits[0] + ? parseRuleExecutionLog(bucket.last_success.event.hits.hits[0]._source) + : undefined; - const lookBack = bucket.indexing_lookback.event.hits.hits[0] - ? parseRuleExecutionLog(bucket.indexing_lookback.event.hits.hits[0]._source) - : undefined; + const lookBack = bucket.indexing_lookback.event.hits.hits[0] + ? parseRuleExecutionLog(bucket.indexing_lookback.event.hits.hits[0]._source) + : undefined; - const executionGap = bucket.execution_gap.event.hits.hits[0] - ? parseRuleExecutionLog(bucket.execution_gap.event.hits.hits[0]._source)[ - getMetricField(ExecutionMetric.executionGap) - ] - : undefined; + const executionGap = bucket.execution_gap.event.hits.hits[0] + ? parseRuleExecutionLog(bucket.execution_gap.event.hits.hits[0]._source)[ + getMetricField(ExecutionMetric.executionGap) + ] + : undefined; - const searchDuration = bucket.search_duration_max.event.hits.hits[0] - ? parseRuleExecutionLog(bucket.search_duration_max.event.hits.hits[0]._source)[ - getMetricField(ExecutionMetric.searchDurationMax) - ] - : undefined; + const searchDuration = bucket.search_duration_max.event.hits.hits[0] + ? parseRuleExecutionLog(bucket.search_duration_max.event.hits.hits[0]._source)[ + getMetricField(ExecutionMetric.searchDurationMax) + ] + : undefined; - const indexingDuration = bucket.indexing_duration_max.event.hits.hits[0] - ? parseRuleExecutionLog(bucket.indexing_duration_max.event.hits.hits[0]._source)[ - getMetricField(ExecutionMetric.indexingDurationMax) - ] - : undefined; + const indexingDuration = bucket.indexing_duration_max.event.hits.hits[0] + ? parseRuleExecutionLog(bucket.indexing_duration_max.event.hits.hits[0]._source)[ + getMetricField(ExecutionMetric.indexingDurationMax) + ] + : undefined; - const alertId = logEntry[ALERT_RULE_UUID] ?? ''; - const statusDate = logEntry[TIMESTAMP]; - const lastFailureAt = lastFailure?.[TIMESTAMP]; - const lastFailureMessage = lastFailure?.[MESSAGE]; - const lastSuccessAt = lastSuccess?.[TIMESTAMP]; - const lastSuccessMessage = lastSuccess?.[MESSAGE]; - const status = (logEntry[RULE_STATUS] as RuleExecutionStatus) || null; - const lastLookBackDate = lookBack?.[getMetricField(ExecutionMetric.indexingLookback)]; - const gap = executionGap ? moment.duration(executionGap).humanize() : null; - const bulkCreateTimeDurations = indexingDuration - ? [makeFloatString(indexingDuration)] - : null; - const searchAfterTimeDurations = searchDuration - ? [makeFloatString(searchDuration)] - : null; + const alertId = logEntry[ALERT_RULE_UUID] ?? ''; + const statusDate = logEntry[TIMESTAMP]; + const lastFailureAt = lastFailure?.[TIMESTAMP]; + const lastFailureMessage = lastFailure?.[MESSAGE]; + const lastSuccessAt = lastSuccess?.[TIMESTAMP]; + const lastSuccessMessage = lastSuccess?.[MESSAGE]; + const status = (logEntry[RULE_STATUS] as RuleExecutionStatus) || null; + const lastLookBackDate = lookBack?.[getMetricField(ExecutionMetric.indexingLookback)]; + const gap = executionGap ? moment.duration(executionGap).humanize() : null; + const bulkCreateTimeDurations = indexingDuration + ? [makeFloatString(indexingDuration)] + : null; + const searchAfterTimeDurations = searchDuration + ? [makeFloatString(searchDuration)] + : null; - return { - alertId, - statusDate, - lastFailureAt, - lastFailureMessage, - lastSuccessAt, - lastSuccessMessage, - status, - lastLookBackDate, - gap, - bulkCreateTimeDurations, - searchAfterTimeDurations, - }; - }), - ]) + return { + alertId, + statusDate, + lastFailureAt, + lastFailureMessage, + lastSuccessAt, + lastSuccessMessage, + status, + lastLookBackDate, + gap, + bulkCreateTimeDurations, + searchAfterTimeDurations, + }; + }), + ] + ) ); } From 3a53492f2997371171fa17fd50e3947a6ac15610 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 6 Sep 2021 14:09:54 -0400 Subject: [PATCH 17/43] Fix exceptions page table pagination (#111000) (#111209) Co-authored-by: Dmitry Shevchenko --- .../src/typescript_types/index.ts | 2 +- .../src/use_exception_lists/index.ts | 123 +++++++++--------- .../found_exception_list_schema.mock.ts | 2 +- .../hooks/use_exception_lists.test.ts | 82 ++++++------ .../rules/all/exceptions/columns.tsx | 3 +- .../rules/all/exceptions/exceptions_table.tsx | 46 +++++-- .../rules/all/exceptions/types.ts | 13 ++ 7 files changed, 156 insertions(+), 115 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/types.ts diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts b/packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts index 1909bcb1bcc2e..31f763101c258 100644 --- a/packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts +++ b/packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts @@ -40,7 +40,7 @@ export interface UseExceptionListsProps { http: HttpStart; namespaceTypes: NamespaceType[]; notifications: NotificationsStart; - pagination?: Pagination; + initialPagination?: Pagination; showTrustedApps: boolean; showEventFilters: boolean; } diff --git a/packages/kbn-securitysolution-list-hooks/src/use_exception_lists/index.ts b/packages/kbn-securitysolution-list-hooks/src/use_exception_lists/index.ts index 0bd4c6c705668..722a7918c4127 100644 --- a/packages/kbn-securitysolution-list-hooks/src/use_exception_lists/index.ts +++ b/packages/kbn-securitysolution-list-hooks/src/use_exception_lists/index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { useEffect, useMemo, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import type { ExceptionListSchema, UseExceptionListsProps, @@ -17,7 +17,19 @@ import { fetchExceptionLists } from '@kbn/securitysolution-list-api'; import { getFilters } from '@kbn/securitysolution-list-utils'; export type Func = () => void; -export type ReturnExceptionLists = [boolean, ExceptionListSchema[], Pagination, Func | null]; +export type ReturnExceptionLists = [ + loading: boolean, + exceptionLists: ExceptionListSchema[], + pagination: Pagination, + setPagination: React.Dispatch>, + fetchLists: Func | null +]; + +const DEFAULT_PAGINATION = { + page: 1, + perPage: 20, + total: 0, +}; /** * Hook for fetching ExceptionLists @@ -29,17 +41,13 @@ export type ReturnExceptionLists = [boolean, ExceptionListSchema[], Pagination, * @param notifications kibana service for displaying toasters * @param showTrustedApps boolean - include/exclude trusted app lists * @param showEventFilters boolean - include/exclude event filters lists - * @param pagination + * @param initialPagination * */ export const useExceptionLists = ({ errorMessage, http, - pagination = { - page: 1, - perPage: 20, - total: 0, - }, + initialPagination = DEFAULT_PAGINATION, filterOptions = {}, namespaceTypes, notifications, @@ -47,9 +55,9 @@ export const useExceptionLists = ({ showEventFilters = false, }: UseExceptionListsProps): ReturnExceptionLists => { const [exceptionLists, setExceptionLists] = useState([]); - const [paginationInfo, setPagination] = useState(pagination); + const [pagination, setPagination] = useState(initialPagination); const [loading, setLoading] = useState(true); - const fetchExceptionListsRef = useRef(null); + const abortCtrlRef = useRef(); const namespaceTypesAsString = useMemo(() => namespaceTypes.join(','), [namespaceTypes]); const filters = useMemo( @@ -58,66 +66,57 @@ export const useExceptionLists = ({ [namespaceTypes, filterOptions, showTrustedApps, showEventFilters] ); - useEffect(() => { - let isSubscribed = true; - const abortCtrl = new AbortController(); + const fetchData = useCallback(async (): Promise => { + try { + setLoading(true); - const fetchData = async (): Promise => { - try { - setLoading(true); + abortCtrlRef.current = new AbortController(); - const { page, per_page: perPage, total, data } = await fetchExceptionLists({ - filters, - http, - namespaceTypes: namespaceTypesAsString, - pagination: { - page: pagination.page, - perPage: pagination.perPage, - }, - signal: abortCtrl.signal, - }); + const { page, per_page: perPage, total, data } = await fetchExceptionLists({ + filters, + http, + namespaceTypes: namespaceTypesAsString, + pagination: { + page: pagination.page, + perPage: pagination.perPage, + }, + signal: abortCtrlRef.current.signal, + }); - if (isSubscribed) { - setPagination({ - page, - perPage, - total, - }); - setExceptionLists(data); - setLoading(false); - } - } catch (error) { - if (isSubscribed) { - notifications.toasts.addError(error, { - title: errorMessage, - }); - setExceptionLists([]); - setPagination({ - page: 1, - perPage: 20, - total: 0, - }); - setLoading(false); - } + setPagination({ + page, + perPage, + total, + }); + setExceptionLists(data); + setLoading(false); + } catch (error) { + if (error.name !== 'AbortError') { + notifications.toasts.addError(error, { + title: errorMessage, + }); + setExceptionLists([]); + setPagination(DEFAULT_PAGINATION); + setLoading(false); } - }; - - fetchData(); - - fetchExceptionListsRef.current = fetchData; - return (): void => { - isSubscribed = false; - abortCtrl.abort(); - }; + } }, [ errorMessage, - notifications, - pagination.page, - pagination.perPage, filters, - namespaceTypesAsString, http, + namespaceTypesAsString, + notifications.toasts, + pagination.page, + pagination.perPage, ]); - return [loading, exceptionLists, paginationInfo, fetchExceptionListsRef.current]; + useEffect(() => { + fetchData(); + + return (): void => { + abortCtrlRef.current?.abort(); + }; + }, [fetchData]); + + return [loading, exceptionLists, pagination, setPagination, fetchData]; }; diff --git a/x-pack/plugins/lists/common/schemas/response/found_exception_list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/response/found_exception_list_schema.mock.ts index e3611120348f4..e5e41b5fe4a85 100644 --- a/x-pack/plugins/lists/common/schemas/response/found_exception_list_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/response/found_exception_list_schema.mock.ts @@ -12,6 +12,6 @@ import { getExceptionListSchemaMock } from './exception_list_schema.mock'; export const getFoundExceptionListSchemaMock = (): FoundExceptionListSchema => ({ data: [getExceptionListSchemaMock()], page: 1, - per_page: 1, + per_page: 20, total: 1, }); diff --git a/x-pack/plugins/lists/public/exceptions/hooks/use_exception_lists.test.ts b/x-pack/plugins/lists/public/exceptions/hooks/use_exception_lists.test.ts index 4987de321c556..810fcaa15494f 100644 --- a/x-pack/plugins/lists/public/exceptions/hooks/use_exception_lists.test.ts +++ b/x-pack/plugins/lists/public/exceptions/hooks/use_exception_lists.test.ts @@ -41,13 +41,13 @@ describe('useExceptionLists', () => { errorMessage: 'Uh oh', filterOptions: {}, http: mockKibanaHttpService, - namespaceTypes: ['single', 'agnostic'], - notifications: mockKibanaNotificationsService, - pagination: { + initialPagination: { page: 1, perPage: 20, total: 0, }, + namespaceTypes: ['single', 'agnostic'], + notifications: mockKibanaNotificationsService, showEventFilters: false, showTrustedApps: false, }) @@ -62,7 +62,8 @@ describe('useExceptionLists', () => { perPage: 20, total: 0, }, - null, + expect.any(Function), + expect.any(Function), ]); }); }); @@ -77,13 +78,13 @@ describe('useExceptionLists', () => { errorMessage: 'Uh oh', filterOptions: {}, http: mockKibanaHttpService, - namespaceTypes: ['single', 'agnostic'], - notifications: mockKibanaNotificationsService, - pagination: { + initialPagination: { page: 1, perPage: 20, total: 0, }, + namespaceTypes: ['single', 'agnostic'], + notifications: mockKibanaNotificationsService, showEventFilters: false, showTrustedApps: false, }) @@ -100,10 +101,11 @@ describe('useExceptionLists', () => { expectedListItemsResult, { page: 1, - perPage: 1, + perPage: 20, total: 1, }, - result.current[3], + expect.any(Function), + expect.any(Function), ]); }); }); @@ -117,13 +119,13 @@ describe('useExceptionLists', () => { errorMessage: 'Uh oh', filterOptions: {}, http: mockKibanaHttpService, - namespaceTypes: ['single', 'agnostic'], - notifications: mockKibanaNotificationsService, - pagination: { + initialPagination: { page: 1, perPage: 20, total: 0, }, + namespaceTypes: ['single', 'agnostic'], + notifications: mockKibanaNotificationsService, showEventFilters: false, showTrustedApps: true, }) @@ -153,13 +155,13 @@ describe('useExceptionLists', () => { errorMessage: 'Uh oh', filterOptions: {}, http: mockKibanaHttpService, - namespaceTypes: ['single', 'agnostic'], - notifications: mockKibanaNotificationsService, - pagination: { + initialPagination: { page: 1, perPage: 20, total: 0, }, + namespaceTypes: ['single', 'agnostic'], + notifications: mockKibanaNotificationsService, showEventFilters: false, showTrustedApps: false, }) @@ -189,13 +191,13 @@ describe('useExceptionLists', () => { errorMessage: 'Uh oh', filterOptions: {}, http: mockKibanaHttpService, - namespaceTypes: ['single', 'agnostic'], - notifications: mockKibanaNotificationsService, - pagination: { + initialPagination: { page: 1, perPage: 20, total: 0, }, + namespaceTypes: ['single', 'agnostic'], + notifications: mockKibanaNotificationsService, showEventFilters: true, showTrustedApps: false, }) @@ -225,13 +227,13 @@ describe('useExceptionLists', () => { errorMessage: 'Uh oh', filterOptions: {}, http: mockKibanaHttpService, - namespaceTypes: ['single', 'agnostic'], - notifications: mockKibanaNotificationsService, - pagination: { + initialPagination: { page: 1, perPage: 20, total: 0, }, + namespaceTypes: ['single', 'agnostic'], + notifications: mockKibanaNotificationsService, showEventFilters: false, showTrustedApps: false, }) @@ -264,13 +266,13 @@ describe('useExceptionLists', () => { name: 'Sample Endpoint', }, http: mockKibanaHttpService, - namespaceTypes: ['single', 'agnostic'], - notifications: mockKibanaNotificationsService, - pagination: { + initialPagination: { page: 1, perPage: 20, total: 0, }, + namespaceTypes: ['single', 'agnostic'], + notifications: mockKibanaNotificationsService, showEventFilters: false, showTrustedApps: false, }) @@ -302,9 +304,9 @@ describe('useExceptionLists', () => { errorMessage, filterOptions, http, + initialPagination, namespaceTypes, notifications, - pagination, showEventFilters, showTrustedApps, }) => @@ -312,9 +314,9 @@ describe('useExceptionLists', () => { errorMessage, filterOptions, http, + initialPagination, namespaceTypes, notifications, - pagination, showEventFilters, showTrustedApps, }), @@ -323,13 +325,13 @@ describe('useExceptionLists', () => { errorMessage: 'Uh oh', filterOptions: {}, http: mockKibanaHttpService, - namespaceTypes: ['single'], - notifications: mockKibanaNotificationsService, - pagination: { + initialPagination: { page: 1, perPage: 20, total: 0, }, + namespaceTypes: ['single'], + notifications: mockKibanaNotificationsService, showEventFilters: false, showTrustedApps: false, }, @@ -344,13 +346,13 @@ describe('useExceptionLists', () => { errorMessage: 'Uh oh', filterOptions: {}, http: mockKibanaHttpService, - namespaceTypes: ['single', 'agnostic'], - notifications: mockKibanaNotificationsService, - pagination: { + initialPagination: { page: 1, perPage: 20, total: 0, }, + namespaceTypes: ['single', 'agnostic'], + notifications: mockKibanaNotificationsService, showEventFilters: false, showTrustedApps: false, }); @@ -372,13 +374,13 @@ describe('useExceptionLists', () => { errorMessage: 'Uh oh', filterOptions: {}, http: mockKibanaHttpService, - namespaceTypes: ['single', 'agnostic'], - notifications: mockKibanaNotificationsService, - pagination: { + initialPagination: { page: 1, perPage: 20, total: 0, }, + namespaceTypes: ['single', 'agnostic'], + notifications: mockKibanaNotificationsService, showEventFilters: false, showTrustedApps: false, }) @@ -390,8 +392,8 @@ describe('useExceptionLists', () => { expect(typeof result.current[3]).toEqual('function'); - if (result.current[3] != null) { - result.current[3](); + if (result.current[4] != null) { + result.current[4](); } // NOTE: Only need one call here because hook already initilaized await waitForNextUpdate(); @@ -411,13 +413,13 @@ describe('useExceptionLists', () => { errorMessage: 'Uh oh', filterOptions: {}, http: mockKibanaHttpService, - namespaceTypes: ['single', 'agnostic'], - notifications: mockKibanaNotificationsService, - pagination: { + initialPagination: { page: 1, perPage: 20, total: 0, }, + namespaceTypes: ['single', 'agnostic'], + notifications: mockKibanaNotificationsService, showEventFilters: false, showTrustedApps: false, }) diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx index 582ca0252604c..1ef3c3d3c5414 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx @@ -15,8 +15,9 @@ import { FormatUrl } from '../../../../../../common/components/link_to'; import * as i18n from './translations'; import { ExceptionListInfo } from './use_all_exception_lists'; import { ExceptionOverflowDisplay } from './exceptions_overflow_display'; +import { ExceptionsTableItem } from './types'; -export type AllExceptionListsColumns = EuiBasicTableColumn; +export type AllExceptionListsColumns = EuiBasicTableColumn; export const getAllExceptionListsColumns = ( onExport: (arg: { id: string; listId: string; namespaceType: NamespaceType }) => () => void, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx index 206976e6c0c1a..23bf634cb1081 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx @@ -7,6 +7,7 @@ import React, { useMemo, useEffect, useCallback, useState } from 'react'; import { + CriteriaWithPagination, EuiBasicTable, EuiEmptyPrompt, EuiLoadingContent, @@ -37,6 +38,7 @@ import { SecurityPageName } from '../../../../../../../common/constants'; import { useUserData } from '../../../../../components/user_info'; import { userHasPermissions } from '../../helpers'; import { useListsConfig } from '../../../../../containers/detection_engine/lists/use_lists_config'; +import { ExceptionsTableItem } from './types'; export type Func = () => Promise; @@ -74,7 +76,13 @@ export const ExceptionListsTable = React.memo(() => { exceptionReferenceModalInitialState ); const [filters, setFilters] = useState(undefined); - const [loadingExceptions, exceptions, pagination, refreshExceptions] = useExceptionLists({ + const [ + loadingExceptions, + exceptions, + pagination, + setPagination, + refreshExceptions, + ] = useExceptionLists({ errorMessage: i18n.ERROR_EXCEPTION_LISTS, filterOptions: filters, http, @@ -125,7 +133,7 @@ export const ExceptionListsTable = React.memo(() => { try { setDeletingListIds((ids) => [...ids, id]); if (refreshExceptions != null) { - await refreshExceptions(); + refreshExceptions(); } if (exceptionsListsRef[id] != null && exceptionsListsRef[id].rules.length === 0) { @@ -153,7 +161,7 @@ export const ExceptionListsTable = React.memo(() => { } catch (error) { handleDeleteError(error); } finally { - setDeletingListIds((ids) => [...ids.filter((_id) => _id !== id)]); + setDeletingListIds((ids) => ids.filter((_id) => _id !== id)); } }, [ @@ -326,11 +334,27 @@ export const ExceptionListsTable = React.memo(() => { setExportDownload({}); }, []); - const tableItems = (exceptionListsWithRuleRefs ?? []).map((item) => ({ - ...item, - isDeleting: deletingListIds.includes(item.id), - isExporting: exportingListIds.includes(item.id), - })); + const tableItems = useMemo( + () => + (exceptionListsWithRuleRefs ?? []).map((item) => ({ + ...item, + isDeleting: deletingListIds.includes(item.id), + isExporting: exportingListIds.includes(item.id), + })), + [deletingListIds, exceptionListsWithRuleRefs, exportingListIds] + ); + + const handlePaginationChange = useCallback( + (criteria: CriteriaWithPagination) => { + const { index, size } = criteria.page; + setPagination((currentPagination) => ({ + ...currentPagination, + perPage: size, + page: index + 1, + })); + }, + [setPagination] + ); return ( <> @@ -367,14 +391,14 @@ export const ExceptionListsTable = React.memo(() => { numberSelectedItems={0} onRefresh={handleRefresh} /> - data-test-subj="exceptions-table" columns={exceptionsColumns} isSelectable={hasPermissions} itemId="id" items={tableItems} noItemsMessage={emptyPrompt} - onChange={() => {}} + onChange={handlePaginationChange} pagination={paginationMemo} /> @@ -400,3 +424,5 @@ export const ExceptionListsTable = React.memo(() => { ); }); + +ExceptionListsTable.displayName = 'ExceptionListsTable'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/types.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/types.ts new file mode 100644 index 0000000000000..d7cbb924071f2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/types.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ExceptionListInfo } from './use_all_exception_lists'; + +export interface ExceptionsTableItem extends ExceptionListInfo { + isDeleting: boolean; + isExporting: boolean; +} From b66a37cd9a3c6644cfaf18293e472976b7d8fb5b Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 6 Sep 2021 15:37:49 -0400 Subject: [PATCH 18/43] [Security Solution][Endpoint] Trim Activity Log comments (#111163) (#111274) * trim comments so empty comments do not show up fixes elastic/kibana/issues/111106 * not exclusive test * update test to be more specific Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Ashokaditya --- .../view/details/components/log_entry.tsx | 6 +-- .../pages/endpoint_hosts/view/index.test.tsx | 43 ++++++++++++++++++- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry.tsx index 4fe70039d1251..b15c6b9ba0020 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry.tsx @@ -45,7 +45,7 @@ const useLogEntryUIProps = ( if (logEntry.type === 'action') { avatarSize = 'm'; commentType = 'regular'; - commentText = logEntry.item.data.data.comment ?? ''; + commentText = logEntry.item.data.data.comment?.trim() ?? ''; displayResponseEvent = false; iconType = 'lockOpen'; username = logEntry.item.data.user_id; @@ -55,7 +55,7 @@ const useLogEntryUIProps = ( iconType = 'lock'; isIsolateAction = true; } - if (data.comment) { + if (commentText) { displayComment = true; } } @@ -154,7 +154,7 @@ export const LogEntry = memo(({ logEntry }: { logEntry: Immutable {displayComment ? ( - +

{commentText}

) : undefined} diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index ea999334ee771..d053da18ce502 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -832,6 +832,15 @@ describe('when on the endpoint list page', () => { }); const actionData = fleetActionGenerator.generate({ agents: [agentId], + data: { + comment: 'some comment', + }, + }); + const isolatedActionData = fleetActionGenerator.generateIsolateAction({ + agents: [agentId], + data: { + comment: ' ', // has space for comment, + }, }); getMockData = () => ({ @@ -854,6 +863,13 @@ describe('when on the endpoint list page', () => { data: actionData, }, }, + { + type: 'action', + item: { + id: 'some_id_3', + data: isolatedActionData, + }, + }, ], }); @@ -890,7 +906,7 @@ describe('when on the endpoint list page', () => { dispatchEndpointDetailsActivityLogChanged('success', getMockData()); }); const logEntries = await renderResult.queryAllByTestId('timelineEntry'); - expect(logEntries.length).toEqual(2); + expect(logEntries.length).toEqual(3); expect(`${logEntries[0]} .euiCommentTimeline__icon--update`).not.toBe(null); expect(`${logEntries[1]} .euiCommentTimeline__icon--regular`).not.toBe(null); }); @@ -947,7 +963,7 @@ describe('when on the endpoint list page', () => { dispatchEndpointDetailsActivityLogChanged('success', getMockData()); }); const logEntries = await renderResult.queryAllByTestId('timelineEntry'); - expect(logEntries.length).toEqual(2); + expect(logEntries.length).toEqual(3); }); it('should display a callout message if no log data', async () => { @@ -975,6 +991,29 @@ describe('when on the endpoint list page', () => { const activityLogCallout = await renderResult.findByTestId('activityLogNoDataCallout'); expect(activityLogCallout).not.toBeNull(); }); + + it('should correctly display non-empty comments only for actions', async () => { + const userChangedUrlChecker = middlewareSpy.waitForAction('userChangedUrl'); + reactTestingLibrary.act(() => { + history.push( + `${MANAGEMENT_PATH}/endpoints?page_index=0&page_size=10&selected_endpoint=1&show=activity_log` + ); + }); + const changedUrlAction = await userChangedUrlChecker; + expect(changedUrlAction.payload.search).toEqual( + '?page_index=0&page_size=10&selected_endpoint=1&show=activity_log' + ); + await middlewareSpy.waitForAction('endpointDetailsActivityLogChanged'); + reactTestingLibrary.act(() => { + dispatchEndpointDetailsActivityLogChanged('success', getMockData()); + }); + const commentTexts = await renderResult.queryAllByTestId('activityLogCommentText'); + expect(commentTexts.length).toEqual(1); + expect(commentTexts[0].textContent).toEqual('some comment'); + expect(commentTexts[0].parentElement?.parentElement?.className).toContain( + 'euiCommentEvent--regular' + ); + }); }); describe('when showing host Policy Response panel', () => { From 174be85ae39c46d8318ceed2b1e5ab5bede62f03 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 6 Sep 2021 17:52:09 -0400 Subject: [PATCH 19/43] [ML] Fixes REST API docs for results service routes (#111298) (#111302) * [ML] Fixes REST API docs for results service routes * [ML] Edits following review * [ML] Fixed error in modules REST API doc route Co-authored-by: Pete Harverson --- .../ml/server/routes/anomaly_detectors.ts | 2 +- x-pack/plugins/ml/server/routes/filters.ts | 4 ++-- .../ml/server/routes/job_audit_messages.ts | 2 +- x-pack/plugins/ml/server/routes/modules.ts | 2 +- .../ml/server/routes/results_service.ts | 18 ++++++++++-------- .../plugins/ml/server/routes/saved_objects.ts | 12 ++++++------ 6 files changed, 21 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/ml/server/routes/anomaly_detectors.ts b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts index 1403ce2a7b4db..30aae3c0fb550 100644 --- a/x-pack/plugins/ml/server/routes/anomaly_detectors.ts +++ b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts @@ -680,7 +680,7 @@ export function jobRoutes({ router, routeGuard }: RouteInitialization) { /** * @apiGroup AnomalyDetectors * - * @api {post} /api/ml/anomaly_detectors/:jobId/model_snapshots/:snapshotId/_update update model snapshot by snapshot ID + * @api {post} /api/ml/anomaly_detectors/:jobId/model_snapshots/:snapshotId/_update Update model snapshot by snapshot ID * @apiName UpdateModelSnapshotsById * @apiDescription Updates the model snapshot for the specified snapshot ID * diff --git a/x-pack/plugins/ml/server/routes/filters.ts b/x-pack/plugins/ml/server/routes/filters.ts index 1535b12f335a6..b4c7d5bf5b109 100644 --- a/x-pack/plugins/ml/server/routes/filters.ts +++ b/x-pack/plugins/ml/server/routes/filters.ts @@ -47,9 +47,9 @@ export function filtersRoutes({ router, routeGuard }: RouteInitialization) { /** * @apiGroup Filters * - * @api {get} /api/ml/filters Gets filters - size limit has been explicitly set to 1000 + * @api {get} /api/ml/filters Get filters * @apiName GetFilters - * @apiDescription Retrieves the list of filters which are used for custom rules in anomaly detection. + * @apiDescription Retrieves the list of filters which are used for custom rules in anomaly detection. Sets the size limit explicitly to return a maximum of 1000. * * @apiSuccess {Boolean} success * @apiSuccess {Object[]} filters list of filters diff --git a/x-pack/plugins/ml/server/routes/job_audit_messages.ts b/x-pack/plugins/ml/server/routes/job_audit_messages.ts index cdef5a9c20dae..d28effae5ca2b 100644 --- a/x-pack/plugins/ml/server/routes/job_audit_messages.ts +++ b/x-pack/plugins/ml/server/routes/job_audit_messages.ts @@ -101,7 +101,7 @@ export function jobAuditMessagesRoutes({ router, routeGuard }: RouteInitializati /** * @apiGroup JobAuditMessages * - * @api {put} /api/ml/job_audit_messages/clear_messages Index annotation + * @api {put} /api/ml/job_audit_messages/clear_messages Clear messages * @apiName ClearJobAuditMessages * @apiDescription Clear the job audit messages. * diff --git a/x-pack/plugins/ml/server/routes/modules.ts b/x-pack/plugins/ml/server/routes/modules.ts index e327d601555ab..097f3f8d67652 100644 --- a/x-pack/plugins/ml/server/routes/modules.ts +++ b/x-pack/plugins/ml/server/routes/modules.ts @@ -533,7 +533,7 @@ export function dataRecognizer({ router, routeGuard }: RouteInitialization) { /** * @apiGroup Modules * - * @api {post} /api/ml/modules/jobs_exist/:moduleId Check if module jobs exist + * @api {get} /api/ml/modules/jobs_exist/:moduleId Check if module jobs exist * @apiName CheckExistingModuleJobs * @apiDescription Check whether the jobs in the module with the specified ID exist in the * current list of jobs. The check runs a test to see if any of the jobs in existence diff --git a/x-pack/plugins/ml/server/routes/results_service.ts b/x-pack/plugins/ml/server/routes/results_service.ts index 2cb34ce357fea..fe1a759caf8e6 100644 --- a/x-pack/plugins/ml/server/routes/results_service.ts +++ b/x-pack/plugins/ml/server/routes/results_service.ts @@ -106,9 +106,9 @@ export function resultsServiceRoutes({ router, routeGuard }: RouteInitialization /** * @apiGroup ResultsService * - * @api {post} /api/ml/results/anomalies_table_data Prepare anomalies records for table display + * @api {post} /api/ml/results/anomalies_table_data Get anomalies records for table display * @apiName GetAnomaliesTableData - * @apiDescription Retrieves anomaly records for an anomaly detection job and formats them for anomalies table display + * @apiDescription Retrieves anomaly records for an anomaly detection job and formats them for anomalies table display. * * @apiSchema (body) anomaliesTableDataSchema */ @@ -138,7 +138,7 @@ export function resultsServiceRoutes({ router, routeGuard }: RouteInitialization /** * @apiGroup ResultsService * - * @api {post} /api/ml/results/category_definition Returns category definition + * @api {post} /api/ml/results/category_definition Get category definition * @apiName GetCategoryDefinition * @apiDescription Returns the definition of the category with the specified ID and job ID * @@ -170,7 +170,7 @@ export function resultsServiceRoutes({ router, routeGuard }: RouteInitialization /** * @apiGroup ResultsService * - * @api {post} /api/ml/results/max_anomaly_score Returns the maximum anomaly_score + * @api {post} /api/ml/results/max_anomaly_score Get the maximum anomaly_score * @apiName GetMaxAnomalyScore * @apiDescription Returns the maximum anomaly score of the bucket results for the request job ID(s) and time range * @@ -202,7 +202,7 @@ export function resultsServiceRoutes({ router, routeGuard }: RouteInitialization /** * @apiGroup ResultsService * - * @api {post} /api/ml/results/category_examples Returns category examples + * @api {post} /api/ml/results/category_examples Get category examples * @apiName GetCategoryExamples * @apiDescription Returns examples for the categories with the specified IDs from the job with the supplied ID * @@ -266,8 +266,10 @@ export function resultsServiceRoutes({ router, routeGuard }: RouteInitialization /** * @apiGroup ResultsService * - * @api {post} /api/ml/results/anomaly_search Performs a search on the anomaly results index + * @api {post} /api/ml/results/anomaly_search Run a search on the anomaly results index * @apiName AnomalySearch + * @apiDescription Runs the supplied query against the anomaly results index for the specified job IDs. + * @apiSchema (body) anomalySearchSchema */ router.post( { @@ -295,7 +297,7 @@ export function resultsServiceRoutes({ router, routeGuard }: RouteInitialization /** * @apiGroup ResultsService * - * @api {get} /api/ml/results/:jobId/categorizer_stats + * @api {get} /api/ml/results/:jobId/categorizer_stats Return categorizer statistics * @apiName GetCategorizerStats * @apiDescription Returns the categorizer stats for the specified job ID * @apiSchema (params) jobIdSchema @@ -327,7 +329,7 @@ export function resultsServiceRoutes({ router, routeGuard }: RouteInitialization /** * @apiGroup ResultsService * - * @api {get} /api/ml/results/category_stopped_partitions + * @api {post} /api/ml/results/category_stopped_partitions Get partitions that have stopped being categorized * @apiName GetCategoryStoppedPartitions * @apiDescription Returns information on the partitions that have stopped being categorized due to the categorization status changing from ok to warn. Can return either the list of stopped partitions for each job, or just the list of job IDs. * @apiSchema (body) getCategorizerStoppedPartitionsSchema diff --git a/x-pack/plugins/ml/server/routes/saved_objects.ts b/x-pack/plugins/ml/server/routes/saved_objects.ts index e9fb748a4c7f8..24140c9253cda 100644 --- a/x-pack/plugins/ml/server/routes/saved_objects.ts +++ b/x-pack/plugins/ml/server/routes/saved_objects.ts @@ -59,10 +59,10 @@ export function savedObjectsRoutes( * * @api {get} /api/ml/saved_objects/sync Sync job saved objects * @apiName SyncJobSavedObjects - * @apiDescription Create saved objects for jobs which are missing them. - * Delete saved objects for jobs which no longer exist. - * Update missing datafeed ids in saved objects for datafeeds which exist. - * Remove datafeed ids for datafeeds which no longer exist. + * @apiDescription Synchronizes saved objects for jobs. Saved objects will be created for jobs which are missing them, + * and saved objects will be deleted for jobs which no longer exist. + * Updates missing datafeed IDs in saved objects for datafeeds which exist, and + * removes datafeed IDs for datafeeds which no longer exist. * */ router.get( @@ -217,9 +217,9 @@ export function savedObjectsRoutes( /** * @apiGroup JobSavedObjects * - * @api {get} /api/ml/saved_objects/jobs_spaces All spaces in all jobs + * @api {get} /api/ml/saved_objects/jobs_spaces Get all jobs and their spaces * @apiName JobsSpaces - * @apiDescription List all jobs and their spaces + * @apiDescription List all jobs and their spaces. * */ router.get( From bc33c01b23e49f9fa0963b3cb0916123bce864ab Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 6 Sep 2021 20:58:42 -0400 Subject: [PATCH 20/43] [APM] Mark old api test client as legacy (#110974) (#111307) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Sþren Louv-Jansen --- .../common/apm_api_supertest.ts | 14 ++-- .../common/authentication.ts | 12 +-- .../test/apm_api_integration/common/config.ts | 76 ++++++++++++++----- .../apm_api_integration/common/registry.ts | 2 +- .../test/apm_api_integration/configs/index.ts | 17 +++-- .../tests/alerts/chart_preview.ts | 15 ++-- .../tests/alerts/rule_registry.ts | 2 +- .../errors_failed_transactions.ts | 2 +- .../tests/correlations/errors_overall.ts | 35 ++++----- .../tests/correlations/failed_transactions.ts | 2 +- .../tests/correlations/latency.ts | 2 +- .../tests/correlations/latency_overall.ts | 2 +- .../correlations/latency_slow_transactions.ts | 2 +- .../tests/csm/csm_services.ts | 2 +- .../tests/csm/has_rum_data.ts | 2 +- .../tests/csm/js_errors.ts | 2 +- .../tests/csm/long_task_metrics.ts | 2 +- .../tests/csm/page_load_dist.ts | 2 +- .../tests/csm/page_views.ts | 2 +- .../tests/csm/url_search.ts | 2 +- .../tests/csm/web_core_vitals.ts | 2 +- .../tests/feature_controls.ts | 2 +- .../tests/inspect/inspect.ts | 12 +-- .../tests/metrics_charts/metrics_charts.ts | 2 +- .../tests/observability_overview/has_data.ts | 11 ++- .../observability_overview.ts | 2 +- .../tests/service_maps/service_maps.ts | 6 +- .../service_overview/dependencies/index.ts | 9 +-- .../service_overview/instance_details.ts | 8 +- .../instances_detailed_statistics.ts | 6 +- .../instances_main_statistics.ts | 12 +-- .../tests/services/agent.ts | 2 +- .../tests/services/annotations.ts | 4 +- .../tests/services/derived_annotations.ts | 6 +- .../error_groups_detailed_statistics.ts | 6 +- .../services/error_groups_main_statistics.ts | 2 +- .../tests/services/service_details.ts | 2 +- .../tests/services/service_icons.ts | 2 +- .../services/services_detailed_statistics.ts | 2 +- .../tests/services/throughput.ts | 11 ++- .../tests/services/top_services.ts | 6 +- .../tests/services/transaction_types.ts | 2 +- .../tests/settings/agent_configuration.ts | 21 +++-- .../tests/settings/anomaly_detection/basic.ts | 6 +- .../anomaly_detection/no_access_user.ts | 2 +- .../settings/anomaly_detection/read_user.ts | 2 +- .../settings/anomaly_detection/write_user.ts | 14 ++-- .../tests/settings/custom_link.ts | 16 ++-- .../tests/traces/top_traces.ts | 2 +- .../tests/transactions/breakdown.ts | 2 +- .../tests/transactions/error_rate.ts | 2 +- .../tests/transactions/latency.ts | 2 +- .../tests/transactions/trace_samples.ts | 2 +- ...transactions_groups_detailed_statistics.ts | 2 +- .../transactions_groups_main_statistics.ts | 2 +- 55 files changed, 218 insertions(+), 171 deletions(-) diff --git a/x-pack/test/apm_api_integration/common/apm_api_supertest.ts b/x-pack/test/apm_api_integration/common/apm_api_supertest.ts index 5034d4fae8fbc..f930cab3b0568 100644 --- a/x-pack/test/apm_api_integration/common/apm_api_supertest.ts +++ b/x-pack/test/apm_api_integration/common/apm_api_supertest.ts @@ -15,15 +15,12 @@ import type { APIClientRequestParamsOf, } from '../../../plugins/apm/public/services/rest/createCallApmApi'; -export function createApmApiSupertest(st: supertest.SuperTest) { +export function createSupertestClient(st: supertest.SuperTest) { return async ( options: { endpoint: TEndpoint; } & APIClientRequestParamsOf & { params?: { query?: { _inspect?: boolean } } } - ): Promise<{ - status: number; - body: APIReturnType; - }> => { + ): Promise> => { const { endpoint } = options; const params = 'params' in options ? (options.params as Record) : {}; @@ -44,7 +41,7 @@ export function createApmApiSupertest(st: supertest.SuperTest) { }; } -export type ApmApiSupertest = ReturnType; +export type ApmApiSupertest = ReturnType; export class ApmApiError extends Error { res: request.Response; @@ -60,3 +57,8 @@ Body: ${JSON.stringify(res.body)}` this.res = res; } } + +export interface SupertestReturnType { + status: number; + body: APIReturnType; +} diff --git a/x-pack/test/apm_api_integration/common/authentication.ts b/x-pack/test/apm_api_integration/common/authentication.ts index 799e78e5646ba..e8c71c54c6fe0 100644 --- a/x-pack/test/apm_api_integration/common/authentication.ts +++ b/x-pack/test/apm_api_integration/common/authentication.ts @@ -18,13 +18,15 @@ export enum ApmUser { apmReadUserWithoutMlAccess = 'apm_read_user_without_ml_access', } +// TODO: Going forward we want to use the built-in roles `viewer` and `editor`. However ML privileges are not included in the built-in roles +// Until https://github.com/elastic/kibana/issues/71422 is closed we have to use the custom roles below const roles = { [ApmUser.noAccessUser]: {}, [ApmUser.apmReadUser]: { kibana: [ { base: [], - feature: { apm: ['read'], ml: ['read'], savedObjectsManagement: ['read'] }, + feature: { ml: ['read'] }, spaces: ['*'], }, ], @@ -51,7 +53,7 @@ const roles = { kibana: [ { base: [], - feature: { apm: ['all'], ml: ['all'], savedObjectsManagement: ['all'] }, + feature: { ml: ['all'] }, spaces: ['*'], }, ], @@ -81,16 +83,16 @@ const users = { roles: [], }, [ApmUser.apmReadUser]: { - roles: ['apm_user', ApmUser.apmReadUser], + roles: ['viewer', ApmUser.apmReadUser], }, [ApmUser.apmReadUserWithoutMlAccess]: { roles: [ApmUser.apmReadUserWithoutMlAccess], }, [ApmUser.apmWriteUser]: { - roles: ['apm_user', ApmUser.apmWriteUser], + roles: ['editor', ApmUser.apmWriteUser], }, [ApmUser.apmAnnotationsWriteUser]: { - roles: ['apm_user', ApmUser.apmWriteUser, ApmUser.apmAnnotationsWriteUser], + roles: ['editor', ApmUser.apmWriteUser, ApmUser.apmAnnotationsWriteUser], }, }; diff --git a/x-pack/test/apm_api_integration/common/config.ts b/x-pack/test/apm_api_integration/common/config.ts index c1ae7bb5f2b75..e8d777814402f 100644 --- a/x-pack/test/apm_api_integration/common/config.ts +++ b/x-pack/test/apm_api_integration/common/config.ts @@ -8,10 +8,12 @@ import { FtrConfigProviderContext } from '@kbn/test'; import supertest from 'supertest'; import { format, UrlObject } from 'url'; +import { SecurityServiceProvider } from 'test/common/services/security'; import { InheritedFtrProviderContext, InheritedServices } from './ftr_provider_context'; import { PromiseReturnType } from '../../../plugins/observability/typings/common'; import { createApmUser, APM_TEST_PASSWORD, ApmUser } from './authentication'; import { APMFtrConfigName } from '../configs'; +import { createSupertestClient } from './apm_api_supertest'; import { registry } from './registry'; interface Config { @@ -20,12 +22,29 @@ interface Config { kibanaConfig?: Record; } -const supertestAsApmUser = (kibanaServer: UrlObject, apmUser: ApmUser) => async ( - context: InheritedFtrProviderContext -) => { - const security = context.getService('security'); - await security.init(); +type SecurityService = PromiseReturnType; +function getLegacySupertestClient(kibanaServer: UrlObject, apmUser: ApmUser) { + return async (context: InheritedFtrProviderContext) => { + const security = context.getService('security'); + await security.init(); + + await createApmUser(security, apmUser); + + const url = format({ + ...kibanaServer, + auth: `${apmUser}:${APM_TEST_PASSWORD}`, + }); + + return supertest(url); + }; +} + +async function getApmApiClient( + kibanaServer: UrlObject, + security: SecurityService, + apmUser: ApmUser +) { await createApmUser(security, apmUser); const url = format({ @@ -33,8 +52,10 @@ const supertestAsApmUser = (kibanaServer: UrlObject, apmUser: ApmUser) => async auth: `${apmUser}:${APM_TEST_PASSWORD}`, }); - return supertest(url); -}; + return createSupertestClient(supertest(url)); +} + +export type CreateTestConfig = ReturnType; export function createTestConfig(config: Config) { const { license, name, kibanaConfig } = config; @@ -46,8 +67,7 @@ export function createTestConfig(config: Config) { const services = xPackAPITestsConfig.get('services') as InheritedServices; const servers = xPackAPITestsConfig.get('servers'); - - const supertestAsApmReadUser = supertestAsApmUser(servers.kibana, ApmUser.apmReadUser); + const kibanaServer = servers.kibana; registry.init(config.name); @@ -56,16 +76,38 @@ export function createTestConfig(config: Config) { servers, services: { ...services, - supertest: supertestAsApmReadUser, - supertestAsApmReadUser, - supertestAsNoAccessUser: supertestAsApmUser(servers.kibana, ApmUser.noAccessUser), - supertestAsApmWriteUser: supertestAsApmUser(servers.kibana, ApmUser.apmWriteUser), - supertestAsApmAnnotationsWriteUser: supertestAsApmUser( - servers.kibana, + + apmApiClient: async (context: InheritedFtrProviderContext) => { + const security = context.getService('security'); + await security.init(); + + return { + noAccessUser: await getApmApiClient(servers.kibana, security, ApmUser.noAccessUser), + readUser: await getApmApiClient(servers.kibana, security, ApmUser.apmReadUser), + writeUser: await getApmApiClient(servers.kibana, security, ApmUser.apmWriteUser), + annotationWriterUser: await getApmApiClient( + servers.kibana, + security, + ApmUser.apmAnnotationsWriteUser + ), + noMlAccessUser: await getApmApiClient( + servers.kibana, + security, + ApmUser.apmReadUserWithoutMlAccess + ), + }; + }, + + // legacy clients + legacySupertestAsNoAccessUser: getLegacySupertestClient(kibanaServer, ApmUser.noAccessUser), + legacySupertestAsApmReadUser: getLegacySupertestClient(kibanaServer, ApmUser.apmReadUser), + legacySupertestAsApmWriteUser: getLegacySupertestClient(kibanaServer, ApmUser.apmWriteUser), + legacySupertestAsApmAnnotationsWriteUser: getLegacySupertestClient( + kibanaServer, ApmUser.apmAnnotationsWriteUser ), - supertestAsApmReadUserWithoutMlAccess: supertestAsApmUser( - servers.kibana, + legacySupertestAsApmReadUserWithoutMlAccess: getLegacySupertestClient( + kibanaServer, ApmUser.apmReadUserWithoutMlAccess ), }, diff --git a/x-pack/test/apm_api_integration/common/registry.ts b/x-pack/test/apm_api_integration/common/registry.ts index 0f0af0bda6ab8..a37cd26f1fc3c 100644 --- a/x-pack/test/apm_api_integration/common/registry.ts +++ b/x-pack/test/apm_api_integration/common/registry.ts @@ -110,7 +110,7 @@ export const registry = { const esArchiver = context.getService('esArchiver'); const logger = context.getService('log'); - const supertest = context.getService('supertestAsApmWriteUser'); + const supertest = context.getService('legacySupertestAsApmWriteUser'); const logWithTimer = () => { const start = process.hrtime(); diff --git a/x-pack/test/apm_api_integration/configs/index.ts b/x-pack/test/apm_api_integration/configs/index.ts index 51bcb30a0c176..ad1f897debe32 100644 --- a/x-pack/test/apm_api_integration/configs/index.ts +++ b/x-pack/test/apm_api_integration/configs/index.ts @@ -6,7 +6,7 @@ */ import { mapValues } from 'lodash'; -import { createTestConfig } from '../common/config'; +import { createTestConfig, CreateTestConfig } from '../common/config'; const apmFtrConfigs = { basic: { @@ -34,9 +34,12 @@ const apmFtrConfigs = { export type APMFtrConfigName = keyof typeof apmFtrConfigs; -export const configs = mapValues(apmFtrConfigs, (value, key) => { - return createTestConfig({ - name: key as APMFtrConfigName, - ...value, - }); -}); +export const configs: Record = mapValues( + apmFtrConfigs, + (value, key) => { + return createTestConfig({ + name: key as APMFtrConfigName, + ...value, + }); + } +); diff --git a/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts b/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts index f12256a33ef05..c8bb844238020 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts @@ -6,13 +6,12 @@ */ import expect from '@kbn/expect'; -import { createApmApiSupertest } from '../../common/apm_api_supertest'; import archives from '../../common/fixtures/es_archiver/archives_metadata'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { - const apmApiSupertest = createApmApiSupertest(getService('supertest')); + const apmApiClient = getService('apmApiClient'); const archiveName = 'apm_8.0.0'; const { end } = archives[archiveName]; const start = new Date(Date.parse(end) - 600000).toISOString(); @@ -32,7 +31,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when(`without data loaded`, { config: 'basic', archives: [] }, () => { it('transaction_error_rate (without data)', async () => { const options = getOptions(); - const response = await apmApiSupertest({ + const response = await apmApiClient.readUser({ endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_rate', ...options, }); @@ -45,7 +44,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const options = getOptions(); options.params.query.transactionType = undefined; - const response = await apmApiSupertest({ + const response = await apmApiClient.readUser({ endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_count', ...options, }); @@ -57,7 +56,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('transaction_duration (without data)', async () => { const options = getOptions(); - const response = await apmApiSupertest({ + const response = await apmApiClient.readUser({ endpoint: 'GET /api/apm/alerts/chart_preview/transaction_duration', ...options, }); @@ -70,7 +69,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when(`with data loaded`, { config: 'basic', archives: [archiveName] }, () => { it('transaction_error_rate (with data)', async () => { const options = getOptions(); - const response = await apmApiSupertest({ + const response = await apmApiClient.readUser({ endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_rate', ...options, }); @@ -87,7 +86,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const options = getOptions(); options.params.query.transactionType = undefined; - const response = await apmApiSupertest({ + const response = await apmApiClient.readUser({ endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_count', ...options, }); @@ -102,7 +101,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('transaction_duration (with data)', async () => { const options = getOptions(); - const response = await apmApiSupertest({ + const response = await apmApiClient.readUser({ ...options, endpoint: 'GET /api/apm/alerts/chart_preview/transaction_duration', }); diff --git a/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts b/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts index 7cf1fe4c969cc..7f107f127594d 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts @@ -36,7 +36,7 @@ interface Alert { } export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertestAsApmWriteUser'); + const supertest = getService('legacySupertestAsApmWriteUser'); const es = getService('es'); const MAX_POLLS = 10; diff --git a/x-pack/test/apm_api_integration/tests/correlations/errors_failed_transactions.ts b/x-pack/test/apm_api_integration/tests/correlations/errors_failed_transactions.ts index 054ccbfb4996e..b08ced565ec30 100644 --- a/x-pack/test/apm_api_integration/tests/correlations/errors_failed_transactions.ts +++ b/x-pack/test/apm_api_integration/tests/correlations/errors_failed_transactions.ts @@ -13,7 +13,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); const archiveName = 'apm_8.0.0'; const range = archives_metadata[archiveName]; diff --git a/x-pack/test/apm_api_integration/tests/correlations/errors_overall.ts b/x-pack/test/apm_api_integration/tests/correlations/errors_overall.ts index a4e4077a17483..f4e95816a3996 100644 --- a/x-pack/test/apm_api_integration/tests/correlations/errors_overall.ts +++ b/x-pack/test/apm_api_integration/tests/correlations/errors_overall.ts @@ -6,36 +6,37 @@ */ import expect from '@kbn/expect'; -import { format } from 'url'; -import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; +import { SupertestReturnType } from '../../common/apm_api_supertest'; import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const apmApiClient = getService('apmApiClient'); const archiveName = 'apm_8.0.0'; const range = archives_metadata[archiveName]; - const url = format({ - pathname: `/api/apm/correlations/errors/overall_timeseries`, - query: { - start: range.start, - end: range.end, - environment: 'ENVIRONMENT_ALL', - kuery: '', + const urlConfig = { + endpoint: `GET /api/apm/correlations/errors/overall_timeseries` as const, + params: { + query: { + start: range.start, + end: range.end, + environment: 'ENVIRONMENT_ALL', + kuery: '', + }, }, - }); + }; registry.when( 'correlations errors overall without data', { config: 'trial', archives: [] }, () => { it('handles the empty state', async () => { - const response = await supertest.get(url); + const response = await apmApiClient.readUser(urlConfig); expect(response.status).to.be(200); - expect(response.body.response).to.be(undefined); + expect(response.body.overall).to.be(null); }); } ); @@ -44,14 +45,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { 'correlations errors overall with data and default args', { config: 'trial', archives: ['apm_8.0.0'] }, () => { - type ResponseBody = APIReturnType<'GET /api/apm/correlations/errors/overall_timeseries'>; - let response: { - status: number; - body: NonNullable; - }; + let response: SupertestReturnType<'GET /api/apm/correlations/errors/overall_timeseries'>; before(async () => { - response = await supertest.get(url); + response = await apmApiClient.readUser(urlConfig); }); it('returns successfully', () => { diff --git a/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.ts b/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.ts index 94c293cd1a19f..0205940d77724 100644 --- a/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.ts +++ b/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.ts @@ -13,7 +13,7 @@ import { parseBfetchResponse } from '../../common/utils/parse_b_fetch'; export default function ApiTest({ getService }: FtrProviderContext) { const retry = getService('retry'); - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); const getRequestBody = () => { const partialSearchRequest: PartialSearchRequest = { diff --git a/x-pack/test/apm_api_integration/tests/correlations/latency.ts b/x-pack/test/apm_api_integration/tests/correlations/latency.ts index 32ca71694626f..21cd855f4ed85 100644 --- a/x-pack/test/apm_api_integration/tests/correlations/latency.ts +++ b/x-pack/test/apm_api_integration/tests/correlations/latency.ts @@ -13,7 +13,7 @@ import { parseBfetchResponse } from '../../common/utils/parse_b_fetch'; export default function ApiTest({ getService }: FtrProviderContext) { const retry = getService('retry'); - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); const getRequestBody = () => { const partialSearchRequest: PartialSearchRequest = { diff --git a/x-pack/test/apm_api_integration/tests/correlations/latency_overall.ts b/x-pack/test/apm_api_integration/tests/correlations/latency_overall.ts index cfbe63e976655..722a9a2bc4fb7 100644 --- a/x-pack/test/apm_api_integration/tests/correlations/latency_overall.ts +++ b/x-pack/test/apm_api_integration/tests/correlations/latency_overall.ts @@ -13,7 +13,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); const archiveName = 'apm_8.0.0'; const range = archives_metadata[archiveName]; diff --git a/x-pack/test/apm_api_integration/tests/correlations/latency_slow_transactions.ts b/x-pack/test/apm_api_integration/tests/correlations/latency_slow_transactions.ts index dac9ed70bc483..09c092ed1a646 100644 --- a/x-pack/test/apm_api_integration/tests/correlations/latency_slow_transactions.ts +++ b/x-pack/test/apm_api_integration/tests/correlations/latency_slow_transactions.ts @@ -13,7 +13,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); const archiveName = 'apm_8.0.0'; const range = archives_metadata[archiveName]; diff --git a/x-pack/test/apm_api_integration/tests/csm/csm_services.ts b/x-pack/test/apm_api_integration/tests/csm/csm_services.ts index 57018b5012aa2..832ef93e3f721 100644 --- a/x-pack/test/apm_api_integration/tests/csm/csm_services.ts +++ b/x-pack/test/apm_api_integration/tests/csm/csm_services.ts @@ -10,7 +10,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function rumServicesApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); registry.when('CSM Services without data', { config: 'trial', archives: [] }, () => { it('returns empty list', async () => { diff --git a/x-pack/test/apm_api_integration/tests/csm/has_rum_data.ts b/x-pack/test/apm_api_integration/tests/csm/has_rum_data.ts index 15ddc04e2414d..3372e43396ed0 100644 --- a/x-pack/test/apm_api_integration/tests/csm/has_rum_data.ts +++ b/x-pack/test/apm_api_integration/tests/csm/has_rum_data.ts @@ -10,7 +10,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function rumHasDataApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); registry.when('has_rum_data without data', { config: 'trial', archives: [] }, () => { it('returns empty list', async () => { diff --git a/x-pack/test/apm_api_integration/tests/csm/js_errors.ts b/x-pack/test/apm_api_integration/tests/csm/js_errors.ts index 870c90273d5cc..6346c991373b5 100644 --- a/x-pack/test/apm_api_integration/tests/csm/js_errors.ts +++ b/x-pack/test/apm_api_integration/tests/csm/js_errors.ts @@ -10,7 +10,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function rumJsErrorsApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); registry.when('CSM JS errors with data', { config: 'trial', archives: [] }, () => { it('returns no js errors', async () => { diff --git a/x-pack/test/apm_api_integration/tests/csm/long_task_metrics.ts b/x-pack/test/apm_api_integration/tests/csm/long_task_metrics.ts index 86a99325fe9c9..0cb84d1935fa8 100644 --- a/x-pack/test/apm_api_integration/tests/csm/long_task_metrics.ts +++ b/x-pack/test/apm_api_integration/tests/csm/long_task_metrics.ts @@ -10,7 +10,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function rumServicesApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); registry.when('CSM long task metrics without data', { config: 'trial', archives: [] }, () => { it('returns empty list', async () => { diff --git a/x-pack/test/apm_api_integration/tests/csm/page_load_dist.ts b/x-pack/test/apm_api_integration/tests/csm/page_load_dist.ts index f26832c8fadbe..8d6a38f27a8c4 100644 --- a/x-pack/test/apm_api_integration/tests/csm/page_load_dist.ts +++ b/x-pack/test/apm_api_integration/tests/csm/page_load_dist.ts @@ -10,7 +10,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function rumServicesApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); registry.when('UX page load dist without data', { config: 'trial', archives: [] }, () => { it('returns empty list', async () => { diff --git a/x-pack/test/apm_api_integration/tests/csm/page_views.ts b/x-pack/test/apm_api_integration/tests/csm/page_views.ts index 6732f46011cb1..e5ffd37d3c682 100644 --- a/x-pack/test/apm_api_integration/tests/csm/page_views.ts +++ b/x-pack/test/apm_api_integration/tests/csm/page_views.ts @@ -10,7 +10,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function rumServicesApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); registry.when('CSM page views without data', { config: 'trial', archives: [] }, () => { it('returns empty list', async () => { diff --git a/x-pack/test/apm_api_integration/tests/csm/url_search.ts b/x-pack/test/apm_api_integration/tests/csm/url_search.ts index 4d0d120668519..3c63186879788 100644 --- a/x-pack/test/apm_api_integration/tests/csm/url_search.ts +++ b/x-pack/test/apm_api_integration/tests/csm/url_search.ts @@ -10,7 +10,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function rumServicesApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); registry.when('CSM url search api without data', { config: 'trial', archives: [] }, () => { it('returns empty list', async () => { diff --git a/x-pack/test/apm_api_integration/tests/csm/web_core_vitals.ts b/x-pack/test/apm_api_integration/tests/csm/web_core_vitals.ts index 19411f44dc771..2c89b13d1b725 100644 --- a/x-pack/test/apm_api_integration/tests/csm/web_core_vitals.ts +++ b/x-pack/test/apm_api_integration/tests/csm/web_core_vitals.ts @@ -10,7 +10,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function rumServicesApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); registry.when('CSM web core vitals without data', { config: 'trial', archives: [] }, () => { it('returns empty list', async () => { diff --git a/x-pack/test/apm_api_integration/tests/feature_controls.ts b/x-pack/test/apm_api_integration/tests/feature_controls.ts index 58193726e20f1..18fcf4fef5fec 100644 --- a/x-pack/test/apm_api_integration/tests/feature_controls.ts +++ b/x-pack/test/apm_api_integration/tests/feature_controls.ts @@ -10,7 +10,7 @@ import { FtrProviderContext } from '../common/ftr_provider_context'; import { registry } from '../common/registry'; export default function featureControlsTests({ getService }: FtrProviderContext) { - const supertest = getService('supertestAsApmWriteUser'); + const supertest = getService('legacySupertestAsApmWriteUser'); const supertestWithoutAuth = getService('supertestWithoutAuth'); const security = getService('security'); const spaces = getService('spaces'); diff --git a/x-pack/test/apm_api_integration/tests/inspect/inspect.ts b/x-pack/test/apm_api_integration/tests/inspect/inspect.ts index c2a4dfb77d0e6..95805f4ef4524 100644 --- a/x-pack/test/apm_api_integration/tests/inspect/inspect.ts +++ b/x-pack/test/apm_api_integration/tests/inspect/inspect.ts @@ -8,11 +8,11 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; -import { createApmApiSupertest } from '../../common/apm_api_supertest'; + import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; export default function customLinksTests({ getService }: FtrProviderContext) { - const supertestRead = createApmApiSupertest(getService('supertest')); + const apmApiClient = getService('apmApiClient'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; @@ -20,7 +20,7 @@ export default function customLinksTests({ getService }: FtrProviderContext) { registry.when('Inspect feature', { config: 'trial', archives: [archiveName] }, () => { describe('when omitting `_inspect` query param', () => { it('returns response without `_inspect`', async () => { - const { status, body } = await supertestRead({ + const { status, body } = await apmApiClient.readUser({ endpoint: 'GET /api/apm/environments', params: { query: { @@ -38,7 +38,7 @@ export default function customLinksTests({ getService }: FtrProviderContext) { describe('when passing `_inspect` as query param', () => { describe('elasticsearch calls made with end-user auth are returned', () => { it('for environments', async () => { - const { status, body } = await supertestRead({ + const { status, body } = await apmApiClient.readUser({ endpoint: 'GET /api/apm/environments', params: { query: { @@ -66,7 +66,7 @@ export default function customLinksTests({ getService }: FtrProviderContext) { describe('elasticsearch calls made with internal user are not return', () => { it('for custom links', async () => { - const { status, body } = await supertestRead({ + const { status, body } = await apmApiClient.readUser({ endpoint: 'GET /api/apm/settings/custom_links', params: { query: { @@ -82,7 +82,7 @@ export default function customLinksTests({ getService }: FtrProviderContext) { }); it('for agent configs', async () => { - const { status, body } = await supertestRead({ + const { status, body } = await apmApiClient.readUser({ endpoint: 'GET /api/apm/settings/agent-configuration', params: { query: { diff --git a/x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.ts b/x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.ts index 891334e1c1db2..a3e02984a16de 100644 --- a/x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.ts +++ b/x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.ts @@ -18,7 +18,7 @@ interface ChartResponse { } export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); registry.when( 'Metrics charts when data is loaded', diff --git a/x-pack/test/apm_api_integration/tests/observability_overview/has_data.ts b/x-pack/test/apm_api_integration/tests/observability_overview/has_data.ts index c6bdce217e229..1b0f8fdcf8736 100644 --- a/x-pack/test/apm_api_integration/tests/observability_overview/has_data.ts +++ b/x-pack/test/apm_api_integration/tests/observability_overview/has_data.ts @@ -6,20 +6,19 @@ */ import expect from '@kbn/expect'; -import { createApmApiSupertest } from '../../common/apm_api_supertest'; + import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const apmApiSupertest = createApmApiSupertest(supertest); + const apmApiClient = getService('apmApiClient'); registry.when( 'Observability overview when data is not loaded', { config: 'basic', archives: [] }, () => { it('returns false when there is no data', async () => { - const response = await apmApiSupertest({ + const response = await apmApiClient.readUser({ endpoint: 'GET /api/apm/observability_overview/has_data', }); expect(response.status).to.be(200); @@ -33,7 +32,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { { config: 'basic', archives: ['observability_overview'] }, () => { it('returns false when there is only onboarding data', async () => { - const response = await apmApiSupertest({ + const response = await apmApiClient.readUser({ endpoint: 'GET /api/apm/observability_overview/has_data', }); expect(response.status).to.be(200); @@ -47,7 +46,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { { config: 'basic', archives: ['apm_8.0.0'] }, () => { it('returns true when there is at least one document on transaction, error or metrics indices', async () => { - const response = await apmApiSupertest({ + const response = await apmApiClient.readUser({ endpoint: 'GET /api/apm/observability_overview/has_data', }); expect(response.status).to.be(200); diff --git a/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts b/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts index 8760b80f5c737..76a157d72cc6f 100644 --- a/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts +++ b/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts @@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; diff --git a/x-pack/test/apm_api_integration/tests/service_maps/service_maps.ts b/x-pack/test/apm_api_integration/tests/service_maps/service_maps.ts index 1520ecd644395..816e4e26ef869 100644 --- a/x-pack/test/apm_api_integration/tests/service_maps/service_maps.ts +++ b/x-pack/test/apm_api_integration/tests/service_maps/service_maps.ts @@ -15,8 +15,10 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function serviceMapsApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const supertestAsApmReadUserWithoutMlAccess = getService('supertestAsApmReadUserWithoutMlAccess'); + const supertest = getService('legacySupertestAsApmReadUser'); + const supertestAsApmReadUserWithoutMlAccess = getService( + 'legacySupertestAsApmReadUserWithoutMlAccess' + ); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; diff --git a/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts b/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts index 942378477f04c..4bd9785b31427 100644 --- a/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts @@ -9,7 +9,6 @@ import expect from '@kbn/expect'; import { last, omit, pick, sortBy } from 'lodash'; import { ValuesType } from 'utility-types'; import { Node, NodeType } from '../../../../../plugins/apm/common/connections'; -import { createApmApiSupertest } from '../../../common/apm_api_supertest'; import { roundNumber } from '../../../utils'; import { ENVIRONMENT_ALL, @@ -22,7 +21,7 @@ import { registry } from '../../../common/registry'; import { apmDependenciesMapping, createServiceDependencyDocs } from './es_utils'; export default function ApiTest({ getService }: FtrProviderContext) { - const apmApiSupertest = createApmApiSupertest(getService('supertest')); + const apmApiClient = getService('apmApiClient'); const es = getService('es'); const archiveName = 'apm_8.0.0'; @@ -37,7 +36,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { { config: 'basic', archives: [] }, () => { it('handles the empty state', async () => { - const response = await apmApiSupertest({ + const response = await apmApiClient.readUser({ endpoint: `GET /api/apm/services/{serviceName}/dependencies`, params: { path: { serviceName: 'opbeans-java' }, @@ -212,7 +211,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { refresh: 'wait_for', }); - response = await apmApiSupertest({ + response = await apmApiClient.readUser({ endpoint: `GET /api/apm/services/{serviceName}/dependencies`, params: { path: { serviceName: 'opbeans-java' }, @@ -314,7 +313,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }; before(async () => { - response = await apmApiSupertest({ + response = await apmApiClient.readUser({ endpoint: `GET /api/apm/services/{serviceName}/dependencies`, params: { path: { serviceName: 'opbeans-python' }, diff --git a/x-pack/test/apm_api_integration/tests/service_overview/instance_details.ts b/x-pack/test/apm_api_integration/tests/service_overview/instance_details.ts index 52525abe50373..40bfbbb699e65 100644 --- a/x-pack/test/apm_api_integration/tests/service_overview/instance_details.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/instance_details.ts @@ -6,18 +6,18 @@ */ import url from 'url'; import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; import archives from '../../common/fixtures/es_archiver/archives_metadata'; import { registry } from '../../common/registry'; import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; import { getServiceNodeIds } from './get_service_node_ids'; -import { createApmApiSupertest } from '../../common/apm_api_supertest'; +import { createSupertestClient } from '../../common/apm_api_supertest'; type ServiceOverviewInstanceDetails = APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}'>; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const apmApiSupertest = createApmApiSupertest(supertest); + const supertest = getService('legacySupertestAsApmReadUser'); + const apmApiSupertest = createSupertestClient(supertest); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; diff --git a/x-pack/test/apm_api_integration/tests/service_overview/instances_detailed_statistics.ts b/x-pack/test/apm_api_integration/tests/service_overview/instances_detailed_statistics.ts index cdea0da2671bb..ffadb7fcf7801 100644 --- a/x-pack/test/apm_api_integration/tests/service_overview/instances_detailed_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/instances_detailed_statistics.ts @@ -14,12 +14,12 @@ import { APIReturnType } from '../../../../plugins/apm/public/services/rest/crea import { FtrProviderContext } from '../../common/ftr_provider_context'; import archives from '../../common/fixtures/es_archiver/archives_metadata'; import { registry } from '../../common/registry'; -import { createApmApiSupertest } from '../../common/apm_api_supertest'; +import { createSupertestClient } from '../../common/apm_api_supertest'; import { getServiceNodeIds } from './get_service_node_ids'; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const apmApiSupertest = createApmApiSupertest(supertest); + const supertest = getService('legacySupertestAsApmReadUser'); + const apmApiSupertest = createSupertestClient(supertest); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; diff --git a/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.ts b/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.ts index 52ead7f2b7b81..355778757af3c 100644 --- a/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.ts @@ -13,11 +13,11 @@ import { isFiniteNumber } from '../../../../plugins/apm/common/utils/is_finite_n import { FtrProviderContext } from '../../common/ftr_provider_context'; import archives from '../../common/fixtures/es_archiver/archives_metadata'; import { registry } from '../../common/registry'; -import { createApmApiSupertest } from '../../common/apm_api_supertest'; + import { LatencyAggregationType } from '../../../../plugins/apm/common/latency_aggregation_types'; export default function ApiTest({ getService }: FtrProviderContext) { - const apmApiSupertest = createApmApiSupertest(getService('supertest')); + const apmApiClient = getService('apmApiClient'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; @@ -28,7 +28,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { () => { describe('when data is not loaded', () => { it('handles the empty state', async () => { - const response = await apmApiSupertest({ + const response = await apmApiClient.readUser({ endpoint: `GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics`, params: { path: { serviceName: 'opbeans-java' }, @@ -62,7 +62,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }; beforeEach(async () => { - response = await apmApiSupertest({ + response = await apmApiClient.readUser({ endpoint: `GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics`, params: { path: { serviceName: 'opbeans-java' }, @@ -133,7 +133,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }; beforeEach(async () => { - response = await apmApiSupertest({ + response = await apmApiClient.readUser({ endpoint: `GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics`, params: { path: { serviceName: 'opbeans-ruby' }, @@ -201,7 +201,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }; beforeEach(async () => { - response = await apmApiSupertest({ + response = await apmApiClient.readUser({ endpoint: `GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics`, params: { path: { serviceName: 'opbeans-java' }, diff --git a/x-pack/test/apm_api_integration/tests/services/agent.ts b/x-pack/test/apm_api_integration/tests/services/agent.ts index 5fd222c72a3b2..3e44dbe685cd8 100644 --- a/x-pack/test/apm_api_integration/tests/services/agent.ts +++ b/x-pack/test/apm_api_integration/tests/services/agent.ts @@ -11,7 +11,7 @@ import archives from '../../common/fixtures/es_archiver/archives_metadata'; import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); const archiveName = 'apm_8.0.0'; const range = archives[archiveName]; diff --git a/x-pack/test/apm_api_integration/tests/services/annotations.ts b/x-pack/test/apm_api_integration/tests/services/annotations.ts index 0a885301643c6..32ade1036e629 100644 --- a/x-pack/test/apm_api_integration/tests/services/annotations.ts +++ b/x-pack/test/apm_api_integration/tests/services/annotations.ts @@ -14,8 +14,8 @@ import { registry } from '../../common/registry'; const DEFAULT_INDEX_NAME = 'observability-annotations'; export default function annotationApiTests({ getService }: FtrProviderContext) { - const supertestRead = getService('supertestAsApmReadUser'); - const supertestWrite = getService('supertestAsApmAnnotationsWriteUser'); + const supertestRead = getService('legacySupertestAsApmReadUser'); + const supertestWrite = getService('legacySupertestAsApmAnnotationsWriteUser'); const es = getService('es'); function expectContainsObj(source: JsonObject, expected: JsonObject) { diff --git a/x-pack/test/apm_api_integration/tests/services/derived_annotations.ts b/x-pack/test/apm_api_integration/tests/services/derived_annotations.ts index 2ff4eb7e73306..f401d69b1b002 100644 --- a/x-pack/test/apm_api_integration/tests/services/derived_annotations.ts +++ b/x-pack/test/apm_api_integration/tests/services/derived_annotations.ts @@ -7,12 +7,12 @@ import expect from '@kbn/expect'; import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; -import { createApmApiSupertest } from '../../common/apm_api_supertest'; + import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function annotationApiTests({ getService }: FtrProviderContext) { - const supertestRead = createApmApiSupertest(getService('supertestAsApmReadUser')); + const apmApiClient = getService('apmApiClient'); const es = getService('es'); const dates = [ @@ -128,7 +128,7 @@ export default function annotationApiTests({ getService }: FtrProviderContext) { }); response = ( - await supertestRead({ + await apmApiClient.readUser({ endpoint: 'GET /api/apm/services/{serviceName}/annotation/search', params: { path: { diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups_detailed_statistics.ts b/x-pack/test/apm_api_integration/tests/services/error_groups_detailed_statistics.ts index d7eea2d24ddd3..24507c1e42708 100644 --- a/x-pack/test/apm_api_integration/tests/services/error_groups_detailed_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/services/error_groups_detailed_statistics.ts @@ -12,14 +12,14 @@ import archives_metadata from '../../common/fixtures/es_archiver/archives_metada import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; -import { createApmApiSupertest } from '../../common/apm_api_supertest'; +import { createSupertestClient } from '../../common/apm_api_supertest'; import { getErrorGroupIds } from './get_error_group_ids'; type ErrorGroupsDetailedStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/error_groups/detailed_statistics'>; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const apmApiSupertest = createApmApiSupertest(supertest); + const supertest = getService('legacySupertestAsApmReadUser'); + const apmApiSupertest = createSupertestClient(supertest); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups_main_statistics.ts b/x-pack/test/apm_api_integration/tests/services/error_groups_main_statistics.ts index 1dbd01cd9b4f7..c853bd60e43e0 100644 --- a/x-pack/test/apm_api_integration/tests/services/error_groups_main_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/services/error_groups_main_statistics.ts @@ -15,7 +15,7 @@ import { APIReturnType } from '../../../../plugins/apm/public/services/rest/crea type ErrorGroupsMainStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/error_groups/main_statistics'>; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; diff --git a/x-pack/test/apm_api_integration/tests/services/service_details.ts b/x-pack/test/apm_api_integration/tests/services/service_details.ts index 1f4b4a2c9909b..263aaa504946b 100644 --- a/x-pack/test/apm_api_integration/tests/services/service_details.ts +++ b/x-pack/test/apm_api_integration/tests/services/service_details.ts @@ -12,7 +12,7 @@ import archives from '../../common/fixtures/es_archiver/archives_metadata'; import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; diff --git a/x-pack/test/apm_api_integration/tests/services/service_icons.ts b/x-pack/test/apm_api_integration/tests/services/service_icons.ts index 5f16ad1d57f2b..619603efc4128 100644 --- a/x-pack/test/apm_api_integration/tests/services/service_icons.ts +++ b/x-pack/test/apm_api_integration/tests/services/service_icons.ts @@ -12,7 +12,7 @@ import archives from '../../common/fixtures/es_archiver/archives_metadata'; import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; diff --git a/x-pack/test/apm_api_integration/tests/services/services_detailed_statistics.ts b/x-pack/test/apm_api_integration/tests/services/services_detailed_statistics.ts index f19cb71018be0..043a3cdc2c9a3 100644 --- a/x-pack/test/apm_api_integration/tests/services/services_detailed_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/services/services_detailed_statistics.ts @@ -16,7 +16,7 @@ import { isFiniteNumber } from '../../../../plugins/apm/common/utils/is_finite_n type ServicesDetailedStatisticsReturn = APIReturnType<'GET /api/apm/services/detailed_statistics'>; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; diff --git a/x-pack/test/apm_api_integration/tests/services/throughput.ts b/x-pack/test/apm_api_integration/tests/services/throughput.ts index 9134b13e18db1..03815c9947e9a 100644 --- a/x-pack/test/apm_api_integration/tests/services/throughput.ts +++ b/x-pack/test/apm_api_integration/tests/services/throughput.ts @@ -13,19 +13,18 @@ import { APIReturnType } from '../../../../plugins/apm/public/services/rest/crea import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; -import { createApmApiSupertest } from '../../common/apm_api_supertest'; type ThroughputReturn = APIReturnType<'GET /api/apm/services/{serviceName}/throughput'>; export default function ApiTest({ getService }: FtrProviderContext) { - const apmApiSupertest = createApmApiSupertest(getService('supertest')); + const apmApiClient = getService('apmApiClient'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; registry.when('Throughput when data is not loaded', { config: 'basic', archives: [] }, () => { it('handles the empty state', async () => { - const response = await apmApiSupertest({ + const response = await apmApiClient.readUser({ endpoint: 'GET /api/apm/services/{serviceName}/throughput', params: { path: { @@ -54,7 +53,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { () => { describe('when querying without kql filter', () => { before(async () => { - const response = await apmApiSupertest({ + const response = await apmApiClient.readUser({ endpoint: 'GET /api/apm/services/{serviceName}/throughput', params: { path: { @@ -108,7 +107,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('with kql filter to force transaction-based UI', () => { before(async () => { - const response = await apmApiSupertest({ + const response = await apmApiClient.readUser({ endpoint: 'GET /api/apm/services/{serviceName}/throughput', params: { path: { @@ -144,7 +143,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { { config: 'basic', archives: [archiveName] }, () => { before(async () => { - const response = await apmApiSupertest({ + const response = await apmApiClient.readUser({ endpoint: 'GET /api/apm/services/{serviceName}/throughput', params: { path: { diff --git a/x-pack/test/apm_api_integration/tests/services/top_services.ts b/x-pack/test/apm_api_integration/tests/services/top_services.ts index 86d5db591a6ba..23b2ca7cfefe9 100644 --- a/x-pack/test/apm_api_integration/tests/services/top_services.ts +++ b/x-pack/test/apm_api_integration/tests/services/top_services.ts @@ -14,8 +14,10 @@ import archives_metadata from '../../common/fixtures/es_archiver/archives_metada import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const supertestAsApmReadUserWithoutMlAccess = getService('supertestAsApmReadUserWithoutMlAccess'); + const supertest = getService('legacySupertestAsApmReadUser'); + const supertestAsApmReadUserWithoutMlAccess = getService( + 'legacySupertestAsApmReadUserWithoutMlAccess' + ); const archiveName = 'apm_8.0.0'; diff --git a/x-pack/test/apm_api_integration/tests/services/transaction_types.ts b/x-pack/test/apm_api_integration/tests/services/transaction_types.ts index 568d75c3e9cc7..6f574b5c8e997 100644 --- a/x-pack/test/apm_api_integration/tests/services/transaction_types.ts +++ b/x-pack/test/apm_api_integration/tests/services/transaction_types.ts @@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; diff --git a/x-pack/test/apm_api_integration/tests/settings/agent_configuration.ts b/x-pack/test/apm_api_integration/tests/settings/agent_configuration.ts index 166c95b5f6de7..377c933144880 100644 --- a/x-pack/test/apm_api_integration/tests/settings/agent_configuration.ts +++ b/x-pack/test/apm_api_integration/tests/settings/agent_configuration.ts @@ -9,52 +9,51 @@ import expect from '@kbn/expect'; import { omit, orderBy } from 'lodash'; import { AgentConfigurationIntake } from '../../../../plugins/apm/common/agent_configuration/configuration_types'; import { AgentConfigSearchParams } from '../../../../plugins/apm/server/routes/settings/agent_configuration'; -import { createApmApiSupertest } from '../../common/apm_api_supertest'; + import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function agentConfigurationTests({ getService }: FtrProviderContext) { - const supertestRead = createApmApiSupertest(getService('supertestAsApmReadUser')); - const supertestWrite = createApmApiSupertest(getService('supertestAsApmWriteUser')); + const apmApiClient = getService('apmApiClient'); const log = getService('log'); const archiveName = 'apm_8.0.0'; function getServices() { - return supertestRead({ + return apmApiClient.readUser({ endpoint: 'GET /api/apm/settings/agent-configuration/services', }); } async function getEnvironments(serviceName: string) { - return supertestRead({ + return apmApiClient.readUser({ endpoint: 'GET /api/apm/settings/agent-configuration/environments', params: { query: { serviceName } }, }); } function getAgentName(serviceName: string) { - return supertestRead({ + return apmApiClient.readUser({ endpoint: 'GET /api/apm/settings/agent-configuration/agent_name', params: { query: { serviceName } }, }); } function searchConfigurations(configuration: AgentConfigSearchParams) { - return supertestRead({ + return apmApiClient.readUser({ endpoint: 'POST /api/apm/settings/agent-configuration/search', params: { body: configuration }, }); } function getAllConfigurations() { - return supertestRead({ endpoint: 'GET /api/apm/settings/agent-configuration' }); + return apmApiClient.readUser({ endpoint: 'GET /api/apm/settings/agent-configuration' }); } function createConfiguration(configuration: AgentConfigurationIntake, { user = 'write' } = {}) { log.debug('creating configuration', configuration.service); - const supertestClient = user === 'read' ? supertestRead : supertestWrite; + const supertestClient = user === 'read' ? apmApiClient.readUser : apmApiClient.writeUser; return supertestClient({ endpoint: 'PUT /api/apm/settings/agent-configuration', @@ -64,7 +63,7 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte function updateConfiguration(config: AgentConfigurationIntake, { user = 'write' } = {}) { log.debug('updating configuration', config.service); - const supertestClient = user === 'read' ? supertestRead : supertestWrite; + const supertestClient = user === 'read' ? apmApiClient.readUser : apmApiClient.writeUser; return supertestClient({ endpoint: 'PUT /api/apm/settings/agent-configuration', @@ -74,7 +73,7 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte function deleteConfiguration({ service }: AgentConfigurationIntake, { user = 'write' } = {}) { log.debug('deleting configuration', service); - const supertestClient = user === 'read' ? supertestRead : supertestWrite; + const supertestClient = user === 'read' ? apmApiClient.readUser : apmApiClient.writeUser; return supertestClient({ endpoint: 'DELETE /api/apm/settings/agent-configuration', diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/basic.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/basic.ts index 9a4968e50bfc2..40708adb754a8 100644 --- a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/basic.ts +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/basic.ts @@ -10,9 +10,9 @@ import { registry } from '../../../common/registry'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function apiTest({ getService }: FtrProviderContext) { - const noAccessUser = getService('supertestAsNoAccessUser'); - const readUser = getService('supertestAsApmReadUser'); - const writeUser = getService('supertestAsApmWriteUser'); + const noAccessUser = getService('legacySupertestAsNoAccessUser'); + const readUser = getService('legacySupertestAsApmReadUser'); + const writeUser = getService('legacySupertestAsApmWriteUser'); type SupertestAsUser = typeof noAccessUser | typeof readUser | typeof writeUser; diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/no_access_user.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/no_access_user.ts index 822053e3fc12a..135038755dc6e 100644 --- a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/no_access_user.ts +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/no_access_user.ts @@ -10,7 +10,7 @@ import { registry } from '../../../common/registry'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function apiTest({ getService }: FtrProviderContext) { - const noAccessUser = getService('supertestAsNoAccessUser'); + const noAccessUser = getService('legacySupertestAsNoAccessUser'); function getJobs() { return noAccessUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.ts index fb4069eae09d9..3beebb434b317 100644 --- a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.ts +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.ts @@ -10,7 +10,7 @@ import { registry } from '../../../common/registry'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function apiTest({ getService }: FtrProviderContext) { - const apmReadUser = getService('supertestAsApmReadUser'); + const apmReadUser = getService('legacySupertestAsApmReadUser'); function getJobs() { return apmReadUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.ts index 322c2a4a049cf..7c13533a14291 100644 --- a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.ts +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.ts @@ -7,20 +7,19 @@ import expect from '@kbn/expect'; import { countBy } from 'lodash'; -import { createApmApiSupertest } from '../../../common/apm_api_supertest'; import { registry } from '../../../common/registry'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function apiTest({ getService }: FtrProviderContext) { - const apmWriteUser = getService('supertestAsApmWriteUser'); - const apmApiWriteUser = createApmApiSupertest(getService('supertestAsApmWriteUser')); + const apmApiClient = getService('apmApiClient'); + const legacyWriteUserClient = getService('legacySupertestAsApmWriteUser'); function getJobs() { - return apmApiWriteUser({ endpoint: `GET /api/apm/settings/anomaly-detection/jobs` }); + return apmApiClient.writeUser({ endpoint: `GET /api/apm/settings/anomaly-detection/jobs` }); } function createJobs(environments: string[]) { - return apmApiWriteUser({ + return apmApiClient.writeUser({ endpoint: `POST /api/apm/settings/anomaly-detection/jobs`, params: { body: { environments }, @@ -29,7 +28,10 @@ export default function apiTest({ getService }: FtrProviderContext) { } function deleteJobs(jobIds: string[]) { - return apmWriteUser.post(`/api/ml/jobs/delete_jobs`).send({ jobIds }).set('kbn-xsrf', 'foo'); + return legacyWriteUserClient + .post(`/api/ml/jobs/delete_jobs`) + .send({ jobIds }) + .set('kbn-xsrf', 'foo'); } registry.when('ML jobs', { config: 'trial', archives: [] }, () => { diff --git a/x-pack/test/apm_api_integration/tests/settings/custom_link.ts b/x-pack/test/apm_api_integration/tests/settings/custom_link.ts index 7f1fb7df68390..03b2ad4aa3212 100644 --- a/x-pack/test/apm_api_integration/tests/settings/custom_link.ts +++ b/x-pack/test/apm_api_integration/tests/settings/custom_link.ts @@ -9,11 +9,10 @@ import expect from '@kbn/expect'; import { CustomLink } from '../../../../plugins/apm/common/custom_link/custom_link_types'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; -import { ApmApiError, createApmApiSupertest } from '../../common/apm_api_supertest'; +import { ApmApiError } from '../../common/apm_api_supertest'; export default function customLinksTests({ getService }: FtrProviderContext) { - const supertestRead = createApmApiSupertest(getService('supertest')); - const supertestWrite = createApmApiSupertest(getService('supertestAsApmWriteUser')); + const apmApiClient = getService('apmApiClient'); const log = getService('log'); const archiveName = 'apm_8.0.0'; @@ -50,6 +49,7 @@ export default function customLinksTests({ getService }: FtrProviderContext) { { key: 'transaction.type', value: 'qux' }, ], } as CustomLink; + await createCustomLink(customLink); }); @@ -125,7 +125,7 @@ export default function customLinksTests({ getService }: FtrProviderContext) { }); it('fetches a transaction sample', async () => { - const response = await supertestRead({ + const response = await apmApiClient.readUser({ endpoint: 'GET /api/apm/settings/custom_links/transaction', params: { query: { @@ -140,7 +140,7 @@ export default function customLinksTests({ getService }: FtrProviderContext) { ); function searchCustomLinks(filters?: any) { - return supertestRead({ + return apmApiClient.readUser({ endpoint: 'GET /api/apm/settings/custom_links', params: { query: filters, @@ -151,7 +151,7 @@ export default function customLinksTests({ getService }: FtrProviderContext) { async function createCustomLink(customLink: CustomLink) { log.debug('creating configuration', customLink); - return supertestWrite({ + return apmApiClient.writeUser({ endpoint: 'POST /api/apm/settings/custom_links', params: { body: customLink, @@ -162,7 +162,7 @@ export default function customLinksTests({ getService }: FtrProviderContext) { async function updateCustomLink(id: string, customLink: CustomLink) { log.debug('updating configuration', id, customLink); - return supertestWrite({ + return apmApiClient.writeUser({ endpoint: 'PUT /api/apm/settings/custom_links/{id}', params: { path: { id }, @@ -174,7 +174,7 @@ export default function customLinksTests({ getService }: FtrProviderContext) { async function deleteCustomLink(id: string) { log.debug('deleting configuration', id); - return supertestWrite({ + return apmApiClient.writeUser({ endpoint: 'DELETE /api/apm/settings/custom_links/{id}', params: { path: { id } }, }); diff --git a/x-pack/test/apm_api_integration/tests/traces/top_traces.ts b/x-pack/test/apm_api_integration/tests/traces/top_traces.ts index 29604bfc990df..705c3d9ff4a15 100644 --- a/x-pack/test/apm_api_integration/tests/traces/top_traces.ts +++ b/x-pack/test/apm_api_integration/tests/traces/top_traces.ts @@ -12,7 +12,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; diff --git a/x-pack/test/apm_api_integration/tests/transactions/breakdown.ts b/x-pack/test/apm_api_integration/tests/transactions/breakdown.ts index 92fdbc3588e39..de23b8fea5363 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/breakdown.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/breakdown.ts @@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; diff --git a/x-pack/test/apm_api_integration/tests/transactions/error_rate.ts b/x-pack/test/apm_api_integration/tests/transactions/error_rate.ts index bb6b465c9927c..517812c236c34 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/error_rate.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/error_rate.ts @@ -17,7 +17,7 @@ import { registry } from '../../common/registry'; type ErrorRate = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/charts/error_rate'>; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); const archiveName = 'apm_8.0.0'; diff --git a/x-pack/test/apm_api_integration/tests/transactions/latency.ts b/x-pack/test/apm_api_integration/tests/transactions/latency.ts index 7fa2c76dd54d8..5798da3019982 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/latency.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/latency.ts @@ -17,7 +17,7 @@ import { registry } from '../../common/registry'; type LatencyChartReturnType = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/charts/latency'>; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); const archiveName = 'apm_8.0.0'; diff --git a/x-pack/test/apm_api_integration/tests/transactions/trace_samples.ts b/x-pack/test/apm_api_integration/tests/transactions/trace_samples.ts index 73b1bbfd781d0..fca9222e69bd0 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/trace_samples.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/trace_samples.ts @@ -12,7 +12,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.ts index 3a97195ec587f..965c96bcf287b 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.ts @@ -18,7 +18,7 @@ import { removeEmptyCoordinates, roundNumber } from '../../utils'; type TransactionsGroupsDetailedStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/detailed_statistics'>; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_main_statistics.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_main_statistics.ts index d0672946ad019..406d6fa1333d9 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_main_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_main_statistics.ts @@ -16,7 +16,7 @@ import { registry } from '../../common/registry'; type TransactionsGroupsPrimaryStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/main_statistics'>; export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); + const supertest = getService('legacySupertestAsApmReadUser'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; From b7014c9af579f62e5e5c6cba20483756b2098b88 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 7 Sep 2021 01:26:13 -0400 Subject: [PATCH 21/43] [Security Solution] Timeline uses existing filter manager (#111143) (#111312) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Michael Olorunnisola --- .../use_investigate_in_timeline.tsx | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx index 51d19651a8efb..c750ca94e633b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import { EuiContextMenuItem } from '@elastic/eui'; @@ -13,11 +13,12 @@ import { useKibana } from '../../../../common/lib/kibana'; import { TimelineId } from '../../../../../common/types/timeline'; import { Ecs } from '../../../../../common/ecs'; import { TimelineNonEcsData } from '../../../../../common/search_strategy/timeline'; -import { timelineActions } from '../../../../timelines/store/timeline'; +import { timelineActions, timelineSelectors } from '../../../../timelines/store/timeline'; import { sendAlertToTimelineAction } from '../actions'; import { dispatchUpdateTimeline } from '../../../../timelines/components/open_timeline/helpers'; import { CreateTimelineProps } from '../types'; import { ACTION_INVESTIGATE_IN_TIMELINE } from '../translations'; +import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; import { useFetchEcsAlertsData } from '../../../containers/detection_engine/alerts/use_fetch_ecs_alerts_data'; interface UseInvestigateInTimelineActionProps { @@ -34,10 +35,20 @@ export const useInvestigateInTimeline = ({ onInvestigateInTimelineAlertClick, }: UseInvestigateInTimelineActionProps) => { const { - data: { search: searchStrategyClient }, + data: { search: searchStrategyClient, query }, } = useKibana().services; const dispatch = useDispatch(); + const filterManagerBackup = useMemo(() => query.filterManager, [query.filterManager]); + const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []); + const { filterManager: activeFilterManager } = useDeepEqualSelector((state) => + getManageTimeline(state, TimelineId.active ?? '') + ); + const filterManager = useMemo(() => activeFilterManager ?? filterManagerBackup, [ + activeFilterManager, + filterManagerBackup, + ]); + const updateTimelineIsLoading = useCallback( (payload) => dispatch(timelineActions.updateIsLoading(payload)), [dispatch] @@ -53,6 +64,7 @@ export const useInvestigateInTimeline = ({ notes: [], timeline: { ...timeline, + filterManager, // by setting as an empty array, it will default to all in the reducer because of the event type indexNames: [], show: true, @@ -61,7 +73,7 @@ export const useInvestigateInTimeline = ({ ruleNote, })(); }, - [dispatch, updateTimelineIsLoading] + [dispatch, filterManager, updateTimelineIsLoading] ); const showInvestigateInTimelineAction = alertIds != null; From c9e42c92ea7d98fa1ff4593dfdbe0199fb698fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20Zolt=C3=A1n=20Szab=C3=B3?= Date: Tue, 7 Sep 2021 10:50:39 +0200 Subject: [PATCH 22/43] [APM] Uses doc link service instead of ElasticDocsLink for linking dropped transaction spans (#110964) (#111315) --- ...kibana-plugin-core-public.doclinksstart.links.md | 1 + src/core/public/doc_links/doc_links_service.ts | 2 ++ src/core/public/public.api.md | 1 + .../transaction_flyout/DroppedSpansWarning.tsx | 13 +++++-------- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index 94809d3ddfa96..0a1f2d1bf8b4e 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -12,6 +12,7 @@ readonly links: { readonly apm: { readonly kibanaSettings: string; readonly supportedServiceMaps: string; + readonly droppedTransactionSpans: string; readonly upgrading: string; readonly metaData: string; }; diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index e51a3c42062f9..efce0263929e1 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -34,6 +34,7 @@ export class DocLinksService { apm: { kibanaSettings: `${KIBANA_DOCS}apm-settings-in-kibana.html`, supportedServiceMaps: `${KIBANA_DOCS}service-maps.html#service-maps-supported`, + droppedTransactionSpans: `${APM_DOCS}get-started/${DOC_LINK_VERSION}/transaction-spans.html#dropped-spans`, upgrading: `${APM_DOCS}server/${DOC_LINK_VERSION}/upgrading.html`, metaData: `${APM_DOCS}get-started/${DOC_LINK_VERSION}/metadata.html`, }, @@ -463,6 +464,7 @@ export interface DocLinksStart { readonly apm: { readonly kibanaSettings: string; readonly supportedServiceMaps: string; + readonly droppedTransactionSpans: string; readonly upgrading: string; readonly metaData: string; }; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index d074877337458..cdeb9ab7388ac 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -477,6 +477,7 @@ export interface DocLinksStart { readonly apm: { readonly kibanaSettings: string; readonly supportedServiceMaps: string; + readonly droppedTransactionSpans: string; readonly upgrading: string; readonly metaData: string; }; diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/transaction_flyout/DroppedSpansWarning.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/transaction_flyout/DroppedSpansWarning.tsx index 6fb1cdc45805e..2c6dbe99b6061 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/transaction_flyout/DroppedSpansWarning.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/transaction_flyout/DroppedSpansWarning.tsx @@ -5,17 +5,18 @@ * 2.0. */ -import { EuiCallOut, EuiHorizontalRule } from '@elastic/eui'; +import { EuiCallOut, EuiHorizontalRule, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { Transaction } from '../../../../../../../../typings/es_schemas/ui/transaction'; -import { ElasticDocsLink } from '../../../../../../shared/Links/ElasticDocsLink'; +import { useApmPluginContext } from '../../../../../../../context/apm_plugin/use_apm_plugin_context'; export function DroppedSpansWarning({ transactionDoc, }: { transactionDoc: Transaction; }) { + const { docLinks } = useApmPluginContext().core; const dropped = transactionDoc.transaction.span_count?.dropped; if (!dropped) { return null; @@ -32,18 +33,14 @@ export function DroppedSpansWarning({ values: { dropped }, } )}{' '} - + {i18n.translate( 'xpack.apm.transactionDetails.transFlyout.callout.learnMoreAboutDroppedSpansLinkText', { defaultMessage: 'Learn more about dropped spans.', } )} - + From cbfe68d9580c75c7fdb843ff9363f453aacf2042 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Tue, 7 Sep 2021 10:53:55 +0200 Subject: [PATCH 23/43] [Discover] Remove export* syntax (#110934) (#111316) --- x-pack/plugins/discover_enhanced/common/index.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/x-pack/plugins/discover_enhanced/common/index.ts b/x-pack/plugins/discover_enhanced/common/index.ts index 4ac13918c7ed3..99afdb5f30c6b 100644 --- a/x-pack/plugins/discover_enhanced/common/index.ts +++ b/x-pack/plugins/discover_enhanced/common/index.ts @@ -5,7 +5,4 @@ * 2.0. */ -// TODO: https://github.com/elastic/kibana/issues/110900 -/* eslint-disable @kbn/eslint/no_export_all */ - -export * from './config'; +export type { Config } from './config'; From ac9c7c1885067911e16db338b310edbd6907eb60 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 7 Sep 2021 05:57:11 -0400 Subject: [PATCH 24/43] [Graph] clear legacy docs (#111083) (#111321) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Dmitry Tomashevich <39378793+Dmitriynj@users.noreply.github.com> --- x-pack/plugins/graph/README.md | 10 +++++----- x-pack/plugins/graph/public/_main.scss | 1 - x-pack/plugins/graph/public/components/_sidebar.scss | 2 +- .../workspace_layout/workspace_top_nav_menu.tsx | 2 -- .../services/workspace/graph_client_workspace.js | 2 +- x-pack/plugins/graph/public/state_management/fields.ts | 2 +- .../graph/public/state_management/legacy.test.ts | 2 +- 7 files changed, 9 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/graph/README.md b/x-pack/plugins/graph/README.md index 99becabf70002..a0d7eb25ff987 100644 --- a/x-pack/plugins/graph/README.md +++ b/x-pack/plugins/graph/README.md @@ -17,18 +17,18 @@ Graph shows only up in the side bar if your server is running on a platinum or t ### Client `public/` -Currently most of the state handling is done by a central angular controller. This controller will be broken up into a redux/saga setup step by step. +Currently state handled by react/redux/saga and the core mutable `GraphWorkspace` instance, which managing the nodes and edges state. It should be rewritten in typescript and integrated into redux store. -* `angular/` contains all code using javascript and angular. Rewriting this code in typescript and react is currently ongoing. When the migration is finished, this folder will go away +* `apps/` contains all graph app routes * `components/` contains react components for various parts of the interface. Components can hold local UI state (e.g. current form data), everything else should be passed in from the caller. Styles should reside in a component-specific stylesheet -* `services/` contains functions that encapsule other parts of Kibana. Stateful dependencies are passed in from the outside. Components should not rely on services directly but have callbacks passed in. Once the migration to redux/saga is complete, only sagas will use services. +* `services/` contains the core workspace logic and functions that encapsule other parts of Kibana. Stateful dependencies are passed in from the outside. Components should not rely on services directly but have callbacks passed in. Once the migration to redux/saga is complete, only sagas will use services * `helpers/` contains side effect free helper functions that can be imported and used from components and services * `state_management/` contains reducers, action creators, selectors and sagas. It also exports the central store creator * Each file covers one functional area (e.g. handling of fields, handling of url templates...) * Generally there is no file separation between reducers, action creators, selectors and sagas of the same functional area * Sagas may contain cross-references between multiple functional areas (e.g. the loading saga sets fields and meta data). Because of this it is possible that circular imports occur. In this case the sagas are moved to a separate file `.sagas.ts`. -* `types/` contains type definitions for unmigrated functions in `angular/` and business objects -* `app.js` is the central entrypoint of the app. It initializes router, state management and root components. This will become `app.tsx` when the migration is complete +* `types/` contains type definitions for unmigrated `GraphWorkspace` methods +* `router.tsx` is the central entrypoint of the app ### Server `server/` diff --git a/x-pack/plugins/graph/public/_main.scss b/x-pack/plugins/graph/public/_main.scss index 22a849b0b2a60..6b32de32c06d0 100644 --- a/x-pack/plugins/graph/public/_main.scss +++ b/x-pack/plugins/graph/public/_main.scss @@ -21,7 +21,6 @@ */ .gphNoUserSelect { - padding-right: $euiSizeXS; user-select: none; -webkit-touch-callout: none; -webkit-tap-highlight-color: transparent; diff --git a/x-pack/plugins/graph/public/components/_sidebar.scss b/x-pack/plugins/graph/public/components/_sidebar.scss index 831032231fe8c..f835eeac10c0a 100644 --- a/x-pack/plugins/graph/public/components/_sidebar.scss +++ b/x-pack/plugins/graph/public/components/_sidebar.scss @@ -25,7 +25,7 @@ border-radius: $euiBorderRadius; margin-bottom: $euiSizeXS; - & > span { + .kuiIcon { padding-right: $euiSizeXS; } } diff --git a/x-pack/plugins/graph/public/components/workspace_layout/workspace_top_nav_menu.tsx b/x-pack/plugins/graph/public/components/workspace_layout/workspace_top_nav_menu.tsx index c5b10b9d92120..07e21287e5c16 100644 --- a/x-pack/plugins/graph/public/components/workspace_layout/workspace_top_nav_menu.tsx +++ b/x-pack/plugins/graph/public/components/workspace_layout/workspace_top_nav_menu.tsx @@ -39,8 +39,6 @@ export const WorkspaceTopNavMenu = (props: WorkspaceTopNavMenuProps) => { const store = useStore(); const location = useLocation(); const history = useHistory(); - - // register things for legacy angular UI const allSavingDisabled = props.graphSavePolicy === 'none'; // ===== Menubar configuration ========= diff --git a/x-pack/plugins/graph/public/services/workspace/graph_client_workspace.js b/x-pack/plugins/graph/public/services/workspace/graph_client_workspace.js index c849a25cb19bb..a1c85f8eaf80f 100644 --- a/x-pack/plugins/graph/public/services/workspace/graph_client_workspace.js +++ b/x-pack/plugins/graph/public/services/workspace/graph_client_workspace.js @@ -559,7 +559,7 @@ function GraphWorkspace(options) { } if (self.changeHandler) { // Hook to allow any client to respond to position changes - // e.g. angular adjusts and repaints node positions on screen. + // e.g. react adjusts and repaints node positions on screen. self.changeHandler(); } }); diff --git a/x-pack/plugins/graph/public/state_management/fields.ts b/x-pack/plugins/graph/public/state_management/fields.ts index 3a117fa6fe50a..34b1a74510e1a 100644 --- a/x-pack/plugins/graph/public/state_management/fields.ts +++ b/x-pack/plugins/graph/public/state_management/fields.ts @@ -65,7 +65,7 @@ export const hasFieldsSelector = createSelector( ); /** - * Saga making notifying angular when fields are selected to re-calculate the state of the save button. + * Saga making notifying react when fields are selected to re-calculate the state of the save button. * * Won't be necessary once the workspace is moved to redux */ diff --git a/x-pack/plugins/graph/public/state_management/legacy.test.ts b/x-pack/plugins/graph/public/state_management/legacy.test.ts index 5a05efdc478fc..fd5d31404f75b 100644 --- a/x-pack/plugins/graph/public/state_management/legacy.test.ts +++ b/x-pack/plugins/graph/public/state_management/legacy.test.ts @@ -80,7 +80,7 @@ describe('legacy sync sagas', () => { expect(env.mockedDeps.notifyReact).toHaveBeenCalled(); }); - it('notifies angular when fields are selected', () => { + it('notifies react when fields are selected', () => { env.store.dispatch(selectField('field1')); expect(env.mockedDeps.notifyReact).toHaveBeenCalled(); }); From 10153b7799fe24d922b74d76f5391497dfe8c90d Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 7 Sep 2021 05:58:53 -0400 Subject: [PATCH 25/43] [APM] Fix loading message missing or inconsistent in various list views (#110772) (#111322) * [APM] Fix loading message missing or inconsistent in various list views * fix types and i18n * fix comment * PR review comments * fix JVM loading message Co-authored-by: Miriam <31922082+MiriamAparicio@users.noreply.github.com> --- .../app/service_node_overview/index.tsx | 5 +- .../service_overview_errors_table/index.tsx | 4 +- ...ice_overview_instances_chart_and_table.tsx | 1 + .../index.tsx | 6 +- .../app/trace_overview/trace_list.tsx | 5 +- .../shared/dependencies_table/index.tsx | 55 +++++++++---------- .../components/shared/managed_table/index.tsx | 16 +++--- .../shared/overview_table_container/index.tsx | 14 ++--- .../shared/transactions_table/index.tsx | 5 +- 9 files changed, 58 insertions(+), 53 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx index 1158a671bfe0a..4bc33d440eeaf 100644 --- a/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx @@ -19,7 +19,7 @@ import { } from '../../../../common/utils/formatters'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { useApmParams } from '../../../hooks/use_apm_params'; -import { useFetcher } from '../../../hooks/use_fetcher'; +import { useFetcher, FETCH_STATUS } from '../../../hooks/use_fetcher'; import { useTimeRange } from '../../../hooks/use_time_range'; import { truncate, unit } from '../../../utils/style'; import { ServiceNodeMetricOverviewLink } from '../../shared/Links/apm/ServiceNodeMetricOverviewLink'; @@ -42,7 +42,7 @@ function ServiceNodeOverview() { const { serviceName } = useApmServiceContext(); - const { data } = useFetcher( + const { data, status } = useFetcher( (callApmApi) => { if (!start || !end) { return undefined; @@ -164,6 +164,7 @@ function ServiceNodeOverview() { return ( { setTableOptions({ pageIndex: newTableOptions.page?.index ?? 0, diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx index ee971bf82f86e..1c3fe76b997ff 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx @@ -52,6 +52,7 @@ interface Props { }) => void; detailedStatsData?: ServiceInstanceDetailedStatistics; isLoading: boolean; + isNotInitiated: boolean; } export function ServiceOverviewInstancesTable({ mainStatsItems = [], @@ -62,6 +63,7 @@ export function ServiceOverviewInstancesTable({ onChangeTableOptions, detailedStatsData: detailedStatsData, isLoading, + isNotInitiated, }: Props) { const { agentName } = useApmServiceContext(); @@ -151,13 +153,13 @@ export function ServiceOverviewInstancesTable({ ['items'][0]; @@ -127,14 +126,14 @@ const noItemsMessage = ( ); export function TraceList({ items = [], isLoading }: Props) { - const noItems = isLoading ? : noItemsMessage; return ( ); diff --git a/x-pack/plugins/apm/public/components/shared/dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/shared/dependencies_table/index.tsx index a3dabeeca5b4b..63d78ab4d4e40 100644 --- a/x-pack/plugins/apm/public/components/shared/dependencies_table/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/dependencies_table/index.tsx @@ -5,13 +5,7 @@ * 2.0. */ -import { - EuiBasicTableColumn, - EuiFlexGroup, - EuiFlexItem, - EuiInMemoryTable, - EuiTitle, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { ConnectionStatsItemWithComparisonData } from '../../../../common/connections'; @@ -24,8 +18,10 @@ import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { unit } from '../../../utils/style'; import { SparkPlot } from '../charts/spark_plot'; import { ImpactBar } from '../ImpactBar'; -import { TableFetchWrapper } from '../table_fetch_wrapper'; import { TruncateWithTooltip } from '../truncate_with_tooltip'; +import { ITableColumn, ManagedTable } from '../managed_table'; +import { EmptyMessage } from '../EmptyMessage'; +import { TableFetchWrapper } from '../table_fetch_wrapper'; import { OverviewTableContainer } from '../overview_table_container'; export type DependenciesItem = Omit< @@ -57,15 +53,7 @@ export function DependenciesTable(props: Props) { compact = true, } = props; - const pagination = compact - ? { - initialPageSize: 5, - pageSizeOptions: [5], - hidePerPageOptions: true, - } - : {}; - - const columns: Array> = [ + const columns: Array> = [ { field: 'name', name: nameColumnTitle, @@ -170,6 +158,18 @@ export function DependenciesTable(props: Props) { impactValue: item.currentStats.impact, })) ?? []; + const noItemsMessage = !compact ? ( + + ) : ( + i18n.translate('xpack.apm.dependenciesTable.notFoundLabel', { + defaultMessage: 'No dependencies found', + }) + ); + return ( @@ -186,22 +186,19 @@ export function DependenciesTable(props: Props) { - diff --git a/x-pack/plugins/apm/public/components/shared/managed_table/index.tsx b/x-pack/plugins/apm/public/components/shared/managed_table/index.tsx index 541ed63a080a2..f7009956bcf84 100644 --- a/x-pack/plugins/apm/public/components/shared/managed_table/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/managed_table/index.tsx @@ -127,16 +127,18 @@ function UnoptimizedManagedTable(props: Props) { }; }, [hidePerPageOptions, items, page, pageSize, pagination]); + const showNoItemsMessage = useMemo(() => { + return isLoading + ? i18n.translate('xpack.apm.managedTable.loadingDescription', { + defaultMessage: 'Loading
', + }) + : noItemsMessage; + }, [isLoading, noItemsMessage]); + return ( >} // EuiBasicTableColumn is stricter than ITableColumn sorting={sort} diff --git a/x-pack/plugins/apm/public/components/shared/overview_table_container/index.tsx b/x-pack/plugins/apm/public/components/shared/overview_table_container/index.tsx index 207fa8e1fea76..6caf6aca02733 100644 --- a/x-pack/plugins/apm/public/components/shared/overview_table_container/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/overview_table_container/index.tsx @@ -22,11 +22,11 @@ const tableHeight = 282; * * Only do this when we're at a non-mobile breakpoint. * - * Hide the empty message when we don't yet have any items and are still loading. + * Hide the empty message when we don't yet have any items and are still not initiated. */ const OverviewTableContainerDiv = euiStyled.div<{ fixedHeight?: boolean; - isEmptyAndLoading: boolean; + isEmptyAndNotInitiated: boolean; shouldUseMobileLayout: boolean; }>` ${({ fixedHeight, shouldUseMobileLayout }) => @@ -48,26 +48,26 @@ const OverviewTableContainerDiv = euiStyled.div<{ `} .euiTableRowCell { - visibility: ${({ isEmptyAndLoading }) => - isEmptyAndLoading ? 'hidden' : 'visible'}; + visibility: ${({ isEmptyAndNotInitiated }) => + isEmptyAndNotInitiated ? 'hidden' : 'visible'}; } `; export function OverviewTableContainer({ children, fixedHeight, - isEmptyAndLoading, + isEmptyAndNotInitiated, }: { children?: ReactNode; fixedHeight?: boolean; - isEmptyAndLoading: boolean; + isEmptyAndNotInitiated: boolean; }) { const { isMedium } = useBreakPoints(); return ( {children} diff --git a/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx b/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx index 593ce4c10609c..5d9f96500f101 100644 --- a/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx @@ -217,6 +217,7 @@ export function TransactionsTable({ }); const isLoading = status === FETCH_STATUS.LOADING; + const isNotInitiated = status === FETCH_STATUS.NOT_INITIATED; const pagination = { pageIndex, @@ -296,7 +297,9 @@ export function TransactionsTable({ Date: Tue, 7 Sep 2021 13:06:32 +0300 Subject: [PATCH 26/43] lens should register expression functions in setup contract (#110639) (#111324) * lens should register expression functions in setup contract Closes: #106510 * fix CI * build optimization * build optimizations - step 3 * fix CI * try to optimize bundle * Update x-pack/plugins/lens/common/expressions/time_scale/types.ts Co-authored-by: Marta Bondyra * Update types.ts Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Marta Bondyra Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Marta Bondyra --- .../counter_rate/counter_rate.test.ts | 78 ++++++----- .../counter_rate/counter_rate_fn.ts | 54 ++++++++ .../common/expressions/counter_rate/index.ts | 64 +-------- .../common/expressions/counter_rate/types.ts | 16 +++ .../common/expressions/datatable/datatable.ts | 108 ++------------- .../expressions/datatable/datatable_fn.ts | 93 +++++++++++++ .../common/expressions/datatable/index.ts | 2 + .../common/expressions/datatable/types.ts | 29 ++++ .../format_column/format_column.test.ts | 44 +++--- .../format_column/format_column_fn.ts | 84 ++++++++++++ .../common/expressions/format_column/index.ts | 117 +--------------- .../format_column/supported_formats.ts | 36 +++++ .../common/expressions/format_column/types.ts | 19 +++ .../common/expressions/merge_tables/index.ts | 2 +- .../expressions/metric_chart/metric_chart.ts | 12 +- .../common/expressions/pie_chart/index.ts | 11 +- .../common/expressions/pie_chart/pie_chart.ts | 3 +- .../expressions/rename_columns/index.ts | 2 +- .../rename_columns/rename_columns.test.ts | 12 +- .../rename_columns/rename_columns.ts | 74 +--------- .../rename_columns/rename_columns_fn.ts | 62 +++++++++ .../expressions/rename_columns/types.ts | 22 +++ .../common/expressions/time_scale/index.ts | 4 +- .../expressions/time_scale/time_scale.test.ts | 7 +- .../expressions/time_scale/time_scale.ts | 123 +---------------- .../expressions/time_scale/time_scale_fn.ts | 105 ++++++++++++++ .../common/expressions/time_scale/types.ts | 20 +++ .../lens/public/app_plugin/mounter.tsx | 10 +- .../app_plugin/shared/saved_modal_lazy.tsx | 16 ++- .../components/table_basic.tsx | 2 +- .../expression.test.tsx | 2 +- .../datatable_visualization/expression.tsx | 4 +- .../public/datatable_visualization/index.ts | 12 +- .../public/editor_frame_service/service.tsx | 12 +- .../embeddable/embeddable_component.tsx | 4 +- .../public/embeddable/embeddable_factory.ts | 2 +- x-pack/plugins/lens/public/expressions.ts | 64 +++++++++ .../heatmap_visualization/expression.tsx | 2 - .../public/heatmap_visualization/index.ts | 15 +- .../dimension_panel/time_scaling.tsx | 15 +- .../public/indexpattern_datasource/index.ts | 20 +-- .../indexpattern_datasource/indexpattern.tsx | 10 +- .../indexpattern_suggestions.ts | 4 +- .../operations/definitions/ranges/ranges.tsx | 2 +- .../lens/public/lens_attribute_service.ts | 10 +- .../metric_visualization/expression.test.tsx | 4 +- .../metric_visualization/expression.tsx | 4 +- .../lens/public/metric_visualization/index.ts | 8 +- .../metric_suggestions.ts | 2 +- .../public/pie_visualization/expression.tsx | 2 - .../lens/public/pie_visualization/index.ts | 6 +- x-pack/plugins/lens/public/plugin.ts | 129 +++++++++--------- x-pack/plugins/lens/public/search_provider.ts | 7 +- .../visualize_field_actions.ts | 2 +- x-pack/plugins/lens/public/utils.ts | 20 +-- x-pack/plugins/lens/public/vis_type_alias.ts | 4 +- .../xy_visualization/expression.test.tsx | 3 +- .../public/xy_visualization/expression.tsx | 12 -- .../lens/public/xy_visualization/index.ts | 25 +--- .../lens/public/xy_visualization/types.ts | 4 +- 60 files changed, 883 insertions(+), 758 deletions(-) create mode 100644 x-pack/plugins/lens/common/expressions/counter_rate/counter_rate_fn.ts create mode 100644 x-pack/plugins/lens/common/expressions/counter_rate/types.ts create mode 100644 x-pack/plugins/lens/common/expressions/datatable/datatable_fn.ts create mode 100644 x-pack/plugins/lens/common/expressions/datatable/types.ts create mode 100644 x-pack/plugins/lens/common/expressions/format_column/format_column_fn.ts create mode 100644 x-pack/plugins/lens/common/expressions/format_column/supported_formats.ts create mode 100644 x-pack/plugins/lens/common/expressions/format_column/types.ts create mode 100644 x-pack/plugins/lens/common/expressions/rename_columns/rename_columns_fn.ts create mode 100644 x-pack/plugins/lens/common/expressions/rename_columns/types.ts create mode 100644 x-pack/plugins/lens/common/expressions/time_scale/time_scale_fn.ts create mode 100644 x-pack/plugins/lens/public/expressions.ts diff --git a/x-pack/plugins/lens/common/expressions/counter_rate/counter_rate.test.ts b/x-pack/plugins/lens/common/expressions/counter_rate/counter_rate.test.ts index 3e1a5ad8e3964..5e41e1e4f95f8 100644 --- a/x-pack/plugins/lens/common/expressions/counter_rate/counter_rate.test.ts +++ b/x-pack/plugins/lens/common/expressions/counter_rate/counter_rate.test.ts @@ -12,10 +12,10 @@ import { functionWrapper } from 'src/plugins/expressions/common/expression_funct describe('lens_counter_rate', () => { const fn = functionWrapper(counterRate); - const runFn = (input: Datatable, args: CounterRateArgs) => fn(input, args) as Datatable; + const runFn = (input: Datatable, args: CounterRateArgs) => fn(input, args) as Promise; - it('calculates counter rate', () => { - const result = runFn( + it('calculates counter rate', async () => { + const result = await runFn( { type: 'datatable', columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], @@ -31,8 +31,8 @@ describe('lens_counter_rate', () => { expect(result.rows.map((row) => row.output)).toEqual([undefined, 0, 2, 3, 2]); }); - it('calculates counter rate with decreasing values in input', () => { - const result = runFn( + it('calculates counter rate with decreasing values in input', async () => { + const result = await runFn( { type: 'datatable', columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], @@ -48,8 +48,8 @@ describe('lens_counter_rate', () => { expect(result.rows.map((row) => row.output)).toEqual([undefined, 6, 5, 4]); }); - it('skips null or undefined values until there is real data', () => { - const result = runFn( + it('skips null or undefined values until there is real data', async () => { + const result = await runFn( { type: 'datatable', columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], @@ -85,8 +85,8 @@ describe('lens_counter_rate', () => { ]); }); - it('treats 0 as real data', () => { - const result = runFn( + it('treats 0 as real data', async () => { + const result = await runFn( { type: 'datatable', columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], @@ -123,8 +123,8 @@ describe('lens_counter_rate', () => { ]); }); - it('calculates counter rate for multiple series', () => { - const result = runFn( + it('calculates counter rate for multiple series', async () => { + const result = await runFn( { type: 'datatable', columns: [ @@ -157,8 +157,8 @@ describe('lens_counter_rate', () => { ]); }); - it('treats missing split column as separate series', () => { - const result = runFn( + it('treats missing split column as separate series', async () => { + const result = await runFn( { type: 'datatable', columns: [ @@ -190,8 +190,8 @@ describe('lens_counter_rate', () => { ]); }); - it('treats null like undefined and empty string for split columns', () => { - const result = runFn( + it('treats null like undefined and empty string for split columns', async () => { + const result = await runFn( { type: 'datatable', columns: [ @@ -225,8 +225,8 @@ describe('lens_counter_rate', () => { ]); }); - it('calculates counter rate for multiple series by multiple split columns', () => { - const result = runFn( + it('calculates counter rate for multiple series by multiple split columns', async () => { + const result = await runFn( { type: 'datatable', columns: [ @@ -259,8 +259,8 @@ describe('lens_counter_rate', () => { ]); }); - it('splits separate series by the string representation of the cell values', () => { - const result = runFn( + it('splits separate series by the string representation of the cell values', async () => { + const result = await runFn( { type: 'datatable', columns: [ @@ -280,8 +280,8 @@ describe('lens_counter_rate', () => { expect(result.rows.map((row) => row.output)).toEqual([undefined, 2 - 1, undefined, 11 - 10]); }); - it('casts values to number before calculating counter rate', () => { - const result = runFn( + it('casts values to number before calculating counter rate', async () => { + const result = await runFn( { type: 'datatable', columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], @@ -292,8 +292,8 @@ describe('lens_counter_rate', () => { expect(result.rows.map((row) => row.output)).toEqual([undefined, 7 - 5, 3, 2]); }); - it('casts values to number before calculating counter rate for NaN like values', () => { - const result = runFn( + it('casts values to number before calculating counter rate for NaN like values', async () => { + const result = await runFn( { type: 'datatable', columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], @@ -304,8 +304,8 @@ describe('lens_counter_rate', () => { expect(result.rows.map((row) => row.output)).toEqual([undefined, 7 - 5, NaN, 2, 5 - 2]); }); - it('copies over meta information from the source column', () => { - const result = runFn( + it('copies over meta information from the source column', async () => { + const result = await runFn( { type: 'datatable', columns: [ @@ -346,8 +346,8 @@ describe('lens_counter_rate', () => { }); }); - it('sets output name on output column if specified', () => { - const result = runFn( + it('sets output name on output column if specified', async () => { + const result = await runFn( { type: 'datatable', columns: [ @@ -370,7 +370,7 @@ describe('lens_counter_rate', () => { }); }); - it('returns source table if input column does not exist', () => { + it('returns source table if input column does not exist', async () => { const input: Datatable = { type: 'datatable', columns: [ @@ -384,12 +384,16 @@ describe('lens_counter_rate', () => { ], rows: [{ val: 5 }], }; - expect(runFn(input, { inputColumnId: 'nonexisting', outputColumnId: 'output' })).toBe(input); + expect(await runFn(input, { inputColumnId: 'nonexisting', outputColumnId: 'output' })).toBe( + input + ); }); - it('throws an error if output column exists already', () => { - expect(() => - runFn( + it('throws an error if output column exists already', async () => { + let error: Error | undefined; + + try { + await runFn( { type: 'datatable', columns: [ @@ -404,7 +408,13 @@ describe('lens_counter_rate', () => { rows: [{ val: 5 }], }, { inputColumnId: 'val', outputColumnId: 'val' } - ) - ).toThrow(); + ); + } catch (e) { + error = e; + } + + expect(error).toMatchInlineSnapshot( + `[Error: Specified outputColumnId val already exists. Please pick another column id.]` + ); }); }); diff --git a/x-pack/plugins/lens/common/expressions/counter_rate/counter_rate_fn.ts b/x-pack/plugins/lens/common/expressions/counter_rate/counter_rate_fn.ts new file mode 100644 index 0000000000000..6412b508b9649 --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/counter_rate/counter_rate_fn.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + buildResultColumns, + getBucketIdentifier, +} from '../../../../../../src/plugins/expressions/common'; +import type { CounterRateExpressionFunction } from './types'; + +export const counterRateFn: CounterRateExpressionFunction['fn'] = ( + input, + { by, inputColumnId, outputColumnId, outputColumnName } +) => { + const resultColumns = buildResultColumns(input, outputColumnId, inputColumnId, outputColumnName); + + if (!resultColumns) { + return input; + } + const previousValues: Partial> = {}; + + return { + ...input, + columns: resultColumns, + rows: input.rows.map((row) => { + const newRow = { ...row }; + + const bucketIdentifier = getBucketIdentifier(row, by); + const previousValue = previousValues[bucketIdentifier]; + const currentValue = newRow[inputColumnId]; + if (currentValue != null && previousValue != null) { + const currentValueAsNumber = Number(currentValue); + if (currentValueAsNumber >= previousValue) { + newRow[outputColumnId] = currentValueAsNumber - previousValue; + } else { + newRow[outputColumnId] = currentValueAsNumber; + } + } else { + newRow[outputColumnId] = undefined; + } + + if (currentValue != null) { + previousValues[bucketIdentifier] = Number(currentValue); + } else { + previousValues[bucketIdentifier] = undefined; + } + + return newRow; + }), + }; +}; diff --git a/x-pack/plugins/lens/common/expressions/counter_rate/index.ts b/x-pack/plugins/lens/common/expressions/counter_rate/index.ts index 41f5547dff969..f58b65814768d 100644 --- a/x-pack/plugins/lens/common/expressions/counter_rate/index.ts +++ b/x-pack/plugins/lens/common/expressions/counter_rate/index.ts @@ -6,14 +6,8 @@ */ import { i18n } from '@kbn/i18n'; -import { - getBucketIdentifier, - buildResultColumns, -} from '../../../../../../src/plugins/expressions/common'; -import type { - ExpressionFunctionDefinition, - Datatable, -} from '../../../../../../src/plugins/expressions/common'; + +import type { CounterRateExpressionFunction } from './types'; export interface CounterRateArgs { by?: string[]; @@ -22,13 +16,6 @@ export interface CounterRateArgs { outputColumnName?: string; } -export type ExpressionFunctionCounterRate = ExpressionFunctionDefinition< - 'lens_counter_rate', - Datatable, - CounterRateArgs, - Datatable ->; - /** * Calculates the counter rate of a specified column in the data table. * @@ -59,7 +46,7 @@ export type ExpressionFunctionCounterRate = ExpressionFunctionDefinition< * before comparison. If the values are objects, the return value of their `toString` method will be used for comparison. * Missing values (`null` and `undefined`) will be treated as empty strings. */ -export const counterRate: ExpressionFunctionCounterRate = { +export const counterRate: CounterRateExpressionFunction = { name: 'lens_counter_rate', type: 'datatable', @@ -101,46 +88,9 @@ export const counterRate: ExpressionFunctionCounterRate = { }, }, - fn(input, { by, inputColumnId, outputColumnId, outputColumnName }) { - const resultColumns = buildResultColumns( - input, - outputColumnId, - inputColumnId, - outputColumnName - ); - - if (!resultColumns) { - return input; - } - const previousValues: Partial> = {}; - return { - ...input, - columns: resultColumns, - rows: input.rows.map((row) => { - const newRow = { ...row }; - - const bucketIdentifier = getBucketIdentifier(row, by); - const previousValue = previousValues[bucketIdentifier]; - const currentValue = newRow[inputColumnId]; - if (currentValue != null && previousValue != null) { - const currentValueAsNumber = Number(currentValue); - if (currentValueAsNumber >= previousValue) { - newRow[outputColumnId] = currentValueAsNumber - previousValue; - } else { - newRow[outputColumnId] = currentValueAsNumber; - } - } else { - newRow[outputColumnId] = undefined; - } - - if (currentValue != null) { - previousValues[bucketIdentifier] = Number(currentValue); - } else { - previousValues[bucketIdentifier] = undefined; - } - - return newRow; - }), - }; + async fn(...args) { + /** Build optimization: prevent adding extra code into initial bundle **/ + const { counterRateFn } = await import('./counter_rate_fn'); + return counterRateFn(...args); }, }; diff --git a/x-pack/plugins/lens/common/expressions/counter_rate/types.ts b/x-pack/plugins/lens/common/expressions/counter_rate/types.ts new file mode 100644 index 0000000000000..f9e1cbbb43de3 --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/counter_rate/types.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Datatable, ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions'; +import { CounterRateArgs } from './index'; + +export type CounterRateExpressionFunction = ExpressionFunctionDefinition< + 'lens_counter_rate', + Datatable, + CounterRateArgs, + Datatable | Promise +>; diff --git a/x-pack/plugins/lens/common/expressions/datatable/datatable.ts b/x-pack/plugins/lens/common/expressions/datatable/datatable.ts index 32f6c1c089543..d9f1f9c1196ff 100644 --- a/x-pack/plugins/lens/common/expressions/datatable/datatable.ts +++ b/x-pack/plugins/lens/common/expressions/datatable/datatable.ts @@ -6,35 +6,16 @@ */ import { i18n } from '@kbn/i18n'; -import { cloneDeep } from 'lodash'; -import type { - ExecutionContext, - DatatableColumnMeta, - ExpressionFunctionDefinition, -} from '../../../../../../src/plugins/expressions/common'; -import type { FormatFactory, LensMultiTable } from '../../types'; +import type { ExecutionContext } from '../../../../../../src/plugins/expressions/common'; +import type { FormatFactory } from '../../types'; import type { ColumnConfigArg } from './datatable_column'; -import { getSortingCriteria } from './sorting'; -import { computeSummaryRowForColumn } from './summary'; -import { transposeTable } from './transpose_helpers'; +import type { DatatableExpressionFunction } from './types'; export interface SortingState { columnId: string | undefined; direction: 'asc' | 'desc' | 'none'; } -export interface DatatableProps { - data: LensMultiTable; - untransposedData?: LensMultiTable; - args: DatatableArgs; -} - -export interface DatatableRender { - type: 'render'; - as: 'lens_datatable_renderer'; - value: DatatableProps; -} - export interface DatatableArgs { title: string; description?: string; @@ -43,18 +24,9 @@ export interface DatatableArgs { sortingDirection: SortingState['direction']; } -function isRange(meta: { params?: { id?: string } } | undefined) { - return meta?.params?.id === 'range'; -} - export const getDatatable = ( getFormatFactory: (context: ExecutionContext) => FormatFactory | Promise -): ExpressionFunctionDefinition< - 'lens_datatable', - LensMultiTable, - DatatableArgs, - Promise -> => ({ +): DatatableExpressionFunction => ({ name: 'lens_datatable', type: 'render', inputTypes: ['lens_multitable'], @@ -86,73 +58,9 @@ export const getDatatable = ( help: '', }, }, - async fn(data, args, context) { - let untransposedData: LensMultiTable | undefined; - // do the sorting at this level to propagate it also at CSV download - const [firstTable] = Object.values(data.tables); - const [layerId] = Object.keys(context.inspectorAdapters.tables || {}); - const formatters: Record> = {}; - const formatFactory = await getFormatFactory(context); - - firstTable.columns.forEach((column) => { - formatters[column.id] = formatFactory(column.meta?.params); - }); - - const hasTransposedColumns = args.columns.some((c) => c.isTransposed); - if (hasTransposedColumns) { - // store original shape of data separately - untransposedData = cloneDeep(data); - // transposes table and args inplace - transposeTable(args, firstTable, formatters); - } - - const { sortingColumnId: sortBy, sortingDirection: sortDirection } = args; - - const columnsReverseLookup = firstTable.columns.reduce< - Record - >((memo, { id, name, meta }, i) => { - memo[id] = { name, index: i, meta }; - return memo; - }, {}); - - const columnsWithSummary = args.columns.filter((c) => c.summaryRow); - for (const column of columnsWithSummary) { - column.summaryRowValue = computeSummaryRowForColumn( - column, - firstTable, - formatters, - formatFactory({ id: 'number' }) - ); - } - - if (sortBy && columnsReverseLookup[sortBy] && sortDirection !== 'none') { - // Sort on raw values for these types, while use the formatted value for the rest - const sortingCriteria = getSortingCriteria( - isRange(columnsReverseLookup[sortBy]?.meta) - ? 'range' - : columnsReverseLookup[sortBy]?.meta?.type, - sortBy, - formatters[sortBy], - sortDirection - ); - // replace the table here - context.inspectorAdapters.tables[layerId].rows = (firstTable.rows || []) - .slice() - .sort(sortingCriteria); - // replace also the local copy - firstTable.rows = context.inspectorAdapters.tables[layerId].rows; - } else { - args.sortingColumnId = undefined; - args.sortingDirection = 'none'; - } - return { - type: 'render', - as: 'lens_datatable_renderer', - value: { - data, - untransposedData, - args, - }, - }; + async fn(...args) { + /** Build optimization: prevent adding extra code into initial bundle **/ + const { datatableFn } = await import('./datatable_fn'); + return datatableFn(getFormatFactory)(...args); }, }); diff --git a/x-pack/plugins/lens/common/expressions/datatable/datatable_fn.ts b/x-pack/plugins/lens/common/expressions/datatable/datatable_fn.ts new file mode 100644 index 0000000000000..cb03ec7f9dda6 --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/datatable/datatable_fn.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { cloneDeep } from 'lodash'; +import { FormatFactory, LensMultiTable } from '../../types'; +import { transposeTable } from './transpose_helpers'; +import { computeSummaryRowForColumn } from './summary'; +import { getSortingCriteria } from './sorting'; +import type { + DatatableColumnMeta, + ExecutionContext, +} from '../../../../../../src/plugins/expressions'; +import type { DatatableExpressionFunction } from './types'; + +function isRange(meta: { params?: { id?: string } } | undefined) { + return meta?.params?.id === 'range'; +} + +export const datatableFn = ( + getFormatFactory: (context: ExecutionContext) => FormatFactory | Promise +): DatatableExpressionFunction['fn'] => async (data, args, context) => { + let untransposedData: LensMultiTable | undefined; + // do the sorting at this level to propagate it also at CSV download + const [firstTable] = Object.values(data.tables); + const [layerId] = Object.keys(context.inspectorAdapters.tables || {}); + const formatters: Record> = {}; + const formatFactory = await getFormatFactory(context); + + firstTable.columns.forEach((column) => { + formatters[column.id] = formatFactory(column.meta?.params); + }); + + const hasTransposedColumns = args.columns.some((c) => c.isTransposed); + if (hasTransposedColumns) { + // store original shape of data separately + untransposedData = cloneDeep(data); + // transposes table and args inplace + transposeTable(args, firstTable, formatters); + } + + const { sortingColumnId: sortBy, sortingDirection: sortDirection } = args; + + const columnsReverseLookup = firstTable.columns.reduce< + Record + >((memo, { id, name, meta }, i) => { + memo[id] = { name, index: i, meta }; + return memo; + }, {}); + + const columnsWithSummary = args.columns.filter((c) => c.summaryRow); + for (const column of columnsWithSummary) { + column.summaryRowValue = computeSummaryRowForColumn( + column, + firstTable, + formatters, + formatFactory({ id: 'number' }) + ); + } + + if (sortBy && columnsReverseLookup[sortBy] && sortDirection !== 'none') { + // Sort on raw values for these types, while use the formatted value for the rest + const sortingCriteria = getSortingCriteria( + isRange(columnsReverseLookup[sortBy]?.meta) + ? 'range' + : columnsReverseLookup[sortBy]?.meta?.type, + sortBy, + formatters[sortBy], + sortDirection + ); + // replace the table here + context.inspectorAdapters.tables[layerId].rows = (firstTable.rows || []) + .slice() + .sort(sortingCriteria); + // replace also the local copy + firstTable.rows = context.inspectorAdapters.tables[layerId].rows; + } else { + args.sortingColumnId = undefined; + args.sortingDirection = 'none'; + } + return { + type: 'render', + as: 'lens_datatable_renderer', + value: { + data, + untransposedData, + args, + }, + }; +}; diff --git a/x-pack/plugins/lens/common/expressions/datatable/index.ts b/x-pack/plugins/lens/common/expressions/datatable/index.ts index 2602aae252ca9..cf9fb1d0b4791 100644 --- a/x-pack/plugins/lens/common/expressions/datatable/index.ts +++ b/x-pack/plugins/lens/common/expressions/datatable/index.ts @@ -10,3 +10,5 @@ export * from './datatable'; export * from './summary'; export * from './transpose_helpers'; export * from './utils'; + +export type { DatatableProps } from './types'; diff --git a/x-pack/plugins/lens/common/expressions/datatable/types.ts b/x-pack/plugins/lens/common/expressions/datatable/types.ts new file mode 100644 index 0000000000000..340f3eb0576b0 --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/datatable/types.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions'; +import type { LensMultiTable } from '../../types'; +import type { DatatableArgs } from './datatable'; + +export interface DatatableProps { + data: LensMultiTable; + untransposedData?: LensMultiTable; + args: DatatableArgs; +} + +export interface DatatableRender { + type: 'render'; + as: 'lens_datatable_renderer'; + value: DatatableProps; +} + +export type DatatableExpressionFunction = ExpressionFunctionDefinition< + 'lens_datatable', + LensMultiTable, + DatatableArgs, + Promise +>; diff --git a/x-pack/plugins/lens/common/expressions/format_column/format_column.test.ts b/x-pack/plugins/lens/common/expressions/format_column/format_column.test.ts index 4428225b349da..fbf3ff9c05b19 100644 --- a/x-pack/plugins/lens/common/expressions/format_column/format_column.test.ts +++ b/x-pack/plugins/lens/common/expressions/format_column/format_column.test.ts @@ -10,7 +10,9 @@ import { functionWrapper } from 'src/plugins/expressions/common/expression_funct import { FormatColumnArgs, formatColumn } from './index'; describe('format_column', () => { - const fn: (input: Datatable, args: FormatColumnArgs) => Datatable = functionWrapper(formatColumn); + const fn: (input: Datatable, args: FormatColumnArgs) => Promise = functionWrapper( + formatColumn + ); let datatable: Datatable; @@ -33,17 +35,17 @@ describe('format_column', () => { }; }); - it('overwrites format', () => { + it('overwrites format', async () => { datatable.columns[0].meta.params = { id: 'myformatter', params: {} }; - const result = fn(datatable, { columnId: 'test', format: 'otherformatter' }); + const result = await fn(datatable, { columnId: 'test', format: 'otherformatter' }); expect(result.columns[0].meta.params).toEqual({ id: 'otherformatter', }); }); - it('overwrites format with well known pattern', () => { + it('overwrites format with well known pattern', async () => { datatable.columns[0].meta.params = { id: 'myformatter', params: {} }; - const result = fn(datatable, { columnId: 'test', format: 'number' }); + const result = await fn(datatable, { columnId: 'test', format: 'number' }); expect(result.columns[0].meta.params).toEqual({ id: 'number', params: { @@ -52,9 +54,9 @@ describe('format_column', () => { }); }); - it('uses number of decimals if provided', () => { + it('uses number of decimals if provided', async () => { datatable.columns[0].meta.params = { id: 'myformatter', params: {} }; - const result = fn(datatable, { columnId: 'test', format: 'number', decimals: 5 }); + const result = await fn(datatable, { columnId: 'test', format: 'number', decimals: 5 }); expect(result.columns[0].meta.params).toEqual({ id: 'number', params: { @@ -63,9 +65,9 @@ describe('format_column', () => { }); }); - it('has special handling for 0 decimals', () => { + it('has special handling for 0 decimals', async () => { datatable.columns[0].meta.params = { id: 'myformatter', params: {} }; - const result = fn(datatable, { columnId: 'test', format: 'number', decimals: 0 }); + const result = await fn(datatable, { columnId: 'test', format: 'number', decimals: 0 }); expect(result.columns[0].meta.params).toEqual({ id: 'number', params: { @@ -75,8 +77,8 @@ describe('format_column', () => { }); describe('parent format', () => { - it('should ignore parent format if it is not specifying an id', () => { - const result = fn(datatable, { + it('should ignore parent format if it is not specifying an id', async () => { + const result = await fn(datatable, { columnId: 'test', format: '', parentFormat: JSON.stringify({ some: 'key' }), @@ -84,8 +86,8 @@ describe('format_column', () => { expect(result.columns[0].meta.params).toEqual(datatable.columns[0].meta.params); }); - it('set parent format with params', () => { - const result = fn(datatable, { + it('set parent format with params', async () => { + const result = await fn(datatable, { columnId: 'test', format: '', parentFormat: JSON.stringify({ id: 'wrapper', params: { wrapperParam: 123 } }), @@ -99,9 +101,9 @@ describe('format_column', () => { }); }); - it('retain inner formatter params', () => { + it('retain inner formatter params', async () => { datatable.columns[0].meta.params = { id: 'myformatter', params: { innerParam: 456 } }; - const result = fn(datatable, { + const result = await fn(datatable, { columnId: 'test', format: '', parentFormat: JSON.stringify({ id: 'wrapper', params: { wrapperParam: 123 } }), @@ -118,12 +120,12 @@ describe('format_column', () => { }); }); - it('overwrite existing wrapper param', () => { + it('overwrite existing wrapper param', async () => { datatable.columns[0].meta.params = { id: 'wrapper', params: { wrapperParam: 0, id: 'myformatter', params: { innerParam: 456 } }, }; - const result = fn(datatable, { + const result = await fn(datatable, { columnId: 'test', format: '', parentFormat: JSON.stringify({ id: 'wrapper', params: { wrapperParam: 123 } }), @@ -140,12 +142,12 @@ describe('format_column', () => { }); }); - it('overwrites format with well known pattern including decimals', () => { + it('overwrites format with well known pattern including decimals', async () => { datatable.columns[0].meta.params = { id: 'previousWrapper', params: { id: 'myformatter', params: { innerParam: 456 } }, }; - const result = fn(datatable, { + const result = await fn(datatable, { columnId: 'test', format: 'number', decimals: 5, @@ -164,10 +166,10 @@ describe('format_column', () => { }); }); - it('does not touch other column meta data', () => { + it('does not touch other column meta data', async () => { const extraColumn: DatatableColumn = { id: 'test2', name: 'test2', meta: { type: 'number' } }; datatable.columns.push(extraColumn); - const result = fn(datatable, { columnId: 'test', format: 'number' }); + const result = await fn(datatable, { columnId: 'test', format: 'number' }); expect(result.columns[1]).toEqual(extraColumn); }); }); diff --git a/x-pack/plugins/lens/common/expressions/format_column/format_column_fn.ts b/x-pack/plugins/lens/common/expressions/format_column/format_column_fn.ts new file mode 100644 index 0000000000000..37540ee0950af --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/format_column/format_column_fn.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { supportedFormats } from './supported_formats'; +import type { DatatableColumn } from '../../../../../../src/plugins/expressions'; +import type { FormatColumnArgs } from './index'; +import type { FormatColumnExpressionFunction } from './types'; + +function isNestedFormat(params: DatatableColumn['meta']['params']) { + // if there is a nested params object with an id, it's a nested format + return !!params?.params?.id; +} + +function withParams(col: DatatableColumn, params: Record) { + return { ...col, meta: { ...col.meta, params } }; +} + +export const formatColumnFn: FormatColumnExpressionFunction['fn'] = ( + input, + { format, columnId, decimals, parentFormat }: FormatColumnArgs +) => ({ + ...input, + columns: input.columns.map((col) => { + if (col.id === columnId) { + if (!parentFormat) { + if (supportedFormats[format]) { + return withParams(col, { + id: format, + params: { pattern: supportedFormats[format].decimalsToPattern(decimals) }, + }); + } else if (format) { + return withParams(col, { id: format }); + } else { + return col; + } + } + + const parsedParentFormat = JSON.parse(parentFormat); + const parentFormatId = parsedParentFormat.id; + const parentFormatParams = parsedParentFormat.params ?? {}; + + if (!parentFormatId) { + return col; + } + + if (format && supportedFormats[format]) { + return withParams(col, { + id: parentFormatId, + params: { + id: format, + params: { + pattern: supportedFormats[format].decimalsToPattern(decimals), + }, + ...parentFormatParams, + }, + }); + } + if (parentFormatParams) { + // if original format is already a nested one, we are just replacing the wrapper params + // otherwise wrapping it inside parentFormatId/parentFormatParams + const isNested = isNestedFormat(col.meta.params); + const innerParams = isNested + ? col.meta.params?.params + : { id: col.meta.params?.id, params: col.meta.params?.params }; + + const formatId = isNested ? col.meta.params?.id : parentFormatId; + + return withParams(col, { + ...col.meta.params, + id: formatId, + params: { + ...innerParams, + ...parentFormatParams, + }, + }); + } + } + return col; + }), +}); diff --git a/x-pack/plugins/lens/common/expressions/format_column/index.ts b/x-pack/plugins/lens/common/expressions/format_column/index.ts index c874eac1ede1f..0fc99ff8f7089 100644 --- a/x-pack/plugins/lens/common/expressions/format_column/index.ts +++ b/x-pack/plugins/lens/common/expressions/format_column/index.ts @@ -5,11 +5,7 @@ * 2.0. */ -import type { - ExpressionFunctionDefinition, - Datatable, - DatatableColumn, -} from '../../../../../../src/plugins/expressions/common'; +import type { FormatColumnExpressionFunction } from './types'; export interface FormatColumnArgs { format: string; @@ -18,42 +14,7 @@ export interface FormatColumnArgs { parentFormat?: string; } -export const supportedFormats: Record< - string, - { decimalsToPattern: (decimals?: number) => string } -> = { - number: { - decimalsToPattern: (decimals = 2) => { - if (decimals === 0) { - return `0,0`; - } - return `0,0.${'0'.repeat(decimals)}`; - }, - }, - percent: { - decimalsToPattern: (decimals = 2) => { - if (decimals === 0) { - return `0,0%`; - } - return `0,0.${'0'.repeat(decimals)}%`; - }, - }, - bytes: { - decimalsToPattern: (decimals = 2) => { - if (decimals === 0) { - return `0,0b`; - } - return `0,0.${'0'.repeat(decimals)}b`; - }, - }, -}; - -export const formatColumn: ExpressionFunctionDefinition< - 'lens_format_column', - Datatable, - FormatColumnArgs, - Datatable -> = { +export const formatColumn: FormatColumnExpressionFunction = { name: 'lens_format_column', type: 'datatable', help: '', @@ -78,75 +39,9 @@ export const formatColumn: ExpressionFunctionDefinition< }, }, inputTypes: ['datatable'], - fn(input, { format, columnId, decimals, parentFormat }: FormatColumnArgs) { - return { - ...input, - columns: input.columns.map((col) => { - if (col.id === columnId) { - if (!parentFormat) { - if (supportedFormats[format]) { - return withParams(col, { - id: format, - params: { pattern: supportedFormats[format].decimalsToPattern(decimals) }, - }); - } else if (format) { - return withParams(col, { id: format }); - } else { - return col; - } - } - - const parsedParentFormat = JSON.parse(parentFormat); - const parentFormatId = parsedParentFormat.id; - const parentFormatParams = parsedParentFormat.params ?? {}; - - if (!parentFormatId) { - return col; - } - - if (format && supportedFormats[format]) { - return withParams(col, { - id: parentFormatId, - params: { - id: format, - params: { - pattern: supportedFormats[format].decimalsToPattern(decimals), - }, - ...parentFormatParams, - }, - }); - } - if (parentFormatParams) { - // if original format is already a nested one, we are just replacing the wrapper params - // otherwise wrapping it inside parentFormatId/parentFormatParams - const isNested = isNestedFormat(col.meta.params); - const innerParams = isNested - ? col.meta.params?.params - : { id: col.meta.params?.id, params: col.meta.params?.params }; - - const formatId = isNested ? col.meta.params?.id : parentFormatId; - - return withParams(col, { - ...col.meta.params, - id: formatId, - params: { - ...innerParams, - ...parentFormatParams, - }, - }); - } - } - return col; - }), - }; + async fn(...args) { + /** Build optimization: prevent adding extra code into initial bundle **/ + const { formatColumnFn } = await import('./format_column_fn'); + return formatColumnFn(...args); }, }; - -function isNestedFormat(params: DatatableColumn['meta']['params']) { - // if there is a nested params object with an id, it's a nested format - return !!params?.params?.id; -} - -function withParams(col: DatatableColumn, params: Record) { - return { ...col, meta: { ...col.meta, params } }; -} diff --git a/x-pack/plugins/lens/common/expressions/format_column/supported_formats.ts b/x-pack/plugins/lens/common/expressions/format_column/supported_formats.ts new file mode 100644 index 0000000000000..d00d2f7dfc22f --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/format_column/supported_formats.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const supportedFormats: Record< + string, + { decimalsToPattern: (decimals?: number) => string } +> = { + number: { + decimalsToPattern: (decimals = 2) => { + if (decimals === 0) { + return `0,0`; + } + return `0,0.${'0'.repeat(decimals)}`; + }, + }, + percent: { + decimalsToPattern: (decimals = 2) => { + if (decimals === 0) { + return `0,0%`; + } + return `0,0.${'0'.repeat(decimals)}%`; + }, + }, + bytes: { + decimalsToPattern: (decimals = 2) => { + if (decimals === 0) { + return `0,0b`; + } + return `0,0.${'0'.repeat(decimals)}b`; + }, + }, +}; diff --git a/x-pack/plugins/lens/common/expressions/format_column/types.ts b/x-pack/plugins/lens/common/expressions/format_column/types.ts new file mode 100644 index 0000000000000..589422b253b93 --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/format_column/types.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + Datatable, + ExpressionFunctionDefinition, +} from '../../../../../../src/plugins/expressions'; +import type { FormatColumnArgs } from './index'; + +export type FormatColumnExpressionFunction = ExpressionFunctionDefinition< + 'lens_format_column', + Datatable, + FormatColumnArgs, + Datatable | Promise +>; diff --git a/x-pack/plugins/lens/common/expressions/merge_tables/index.ts b/x-pack/plugins/lens/common/expressions/merge_tables/index.ts index e190da19886df..7ede2236e8b07 100644 --- a/x-pack/plugins/lens/common/expressions/merge_tables/index.ts +++ b/x-pack/plugins/lens/common/expressions/merge_tables/index.ts @@ -15,7 +15,7 @@ import { toAbsoluteDates } from '../../../../../../src/plugins/data/common'; import type { ExpressionValueSearchContext } from '../../../../../../src/plugins/data/common'; import type { LensMultiTable } from '../../types'; -import { Adapters } from '../../../../../../src/plugins/inspector/common'; +import type { Adapters } from '../../../../../../src/plugins/inspector/common'; interface MergeTables { layerIds: string[]; diff --git a/x-pack/plugins/lens/common/expressions/metric_chart/metric_chart.ts b/x-pack/plugins/lens/common/expressions/metric_chart/metric_chart.ts index 6c05502bb2b03..0a867e4155c22 100644 --- a/x-pack/plugins/lens/common/expressions/metric_chart/metric_chart.ts +++ b/x-pack/plugins/lens/common/expressions/metric_chart/metric_chart.ts @@ -9,17 +9,17 @@ import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins import type { LensMultiTable } from '../../types'; import type { MetricConfig } from './types'; -export interface MetricChartProps { - data: LensMultiTable; - args: MetricConfig; -} - -export interface MetricRender { +interface MetricRender { type: 'render'; as: 'lens_metric_chart_renderer'; value: MetricChartProps; } +export interface MetricChartProps { + data: LensMultiTable; + args: MetricConfig; +} + export const metricChart: ExpressionFunctionDefinition< 'lens_metric_chart', LensMultiTable, diff --git a/x-pack/plugins/lens/common/expressions/pie_chart/index.ts b/x-pack/plugins/lens/common/expressions/pie_chart/index.ts index e82294f8aff25..1c1f6fdae4578 100644 --- a/x-pack/plugins/lens/common/expressions/pie_chart/index.ts +++ b/x-pack/plugins/lens/common/expressions/pie_chart/index.ts @@ -5,5 +5,12 @@ * 2.0. */ -export * from './types'; -export * from './pie_chart'; +export { pie } from './pie_chart'; + +export type { + SharedPieLayerState, + PieLayerState, + PieVisualizationState, + PieExpressionArgs, + PieExpressionProps, +} from './types'; diff --git a/x-pack/plugins/lens/common/expressions/pie_chart/pie_chart.ts b/x-pack/plugins/lens/common/expressions/pie_chart/pie_chart.ts index 7d228f04c25e7..ed0391a16af25 100644 --- a/x-pack/plugins/lens/common/expressions/pie_chart/pie_chart.ts +++ b/x-pack/plugins/lens/common/expressions/pie_chart/pie_chart.ts @@ -7,11 +7,12 @@ import { Position } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; + import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common'; import type { LensMultiTable } from '../../types'; import type { PieExpressionProps, PieExpressionArgs } from './types'; -export interface PieRender { +interface PieRender { type: 'render'; as: 'lens_pie_renderer'; value: PieExpressionProps; diff --git a/x-pack/plugins/lens/common/expressions/rename_columns/index.ts b/x-pack/plugins/lens/common/expressions/rename_columns/index.ts index 4cb8ff75f486d..86ab16e06ec01 100644 --- a/x-pack/plugins/lens/common/expressions/rename_columns/index.ts +++ b/x-pack/plugins/lens/common/expressions/rename_columns/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export * from './rename_columns'; +export { renameColumns } from './rename_columns'; diff --git a/x-pack/plugins/lens/common/expressions/rename_columns/rename_columns.test.ts b/x-pack/plugins/lens/common/expressions/rename_columns/rename_columns.test.ts index f3db64c1d2257..3bfed88625c89 100644 --- a/x-pack/plugins/lens/common/expressions/rename_columns/rename_columns.test.ts +++ b/x-pack/plugins/lens/common/expressions/rename_columns/rename_columns.test.ts @@ -10,7 +10,7 @@ import { Datatable } from '../../../../../../src/plugins/expressions/common'; import { createMockExecutionContext } from '../../../../../../src/plugins/expressions/common/mocks'; describe('rename_columns', () => { - it('should rename columns of a given datatable', () => { + it('should rename columns of a given datatable', async () => { const input: Datatable = { type: 'datatable', columns: [ @@ -36,7 +36,7 @@ describe('rename_columns', () => { }, }; - const result = renameColumns.fn( + const result = await renameColumns.fn( input, { idMap: JSON.stringify(idMap) }, createMockExecutionContext() @@ -83,7 +83,7 @@ describe('rename_columns', () => { `); }); - it('should keep columns which are not mapped', () => { + it('should keep columns which are not mapped', async () => { const input: Datatable = { type: 'datatable', columns: [ @@ -102,7 +102,7 @@ describe('rename_columns', () => { b: { id: 'c', label: 'Catamaran' }, }; - const result = renameColumns.fn( + const result = await renameColumns.fn( input, { idMap: JSON.stringify(idMap) }, createMockExecutionContext() @@ -149,7 +149,7 @@ describe('rename_columns', () => { `); }); - it('should rename date histograms', () => { + it('should rename date histograms', async () => { const input: Datatable = { type: 'datatable', columns: [ @@ -168,7 +168,7 @@ describe('rename_columns', () => { b: { id: 'c', label: 'Apple', operationType: 'date_histogram', sourceField: 'banana' }, }; - const result = renameColumns.fn( + const result = await renameColumns.fn( input, { idMap: JSON.stringify(idMap) }, createMockExecutionContext() diff --git a/x-pack/plugins/lens/common/expressions/rename_columns/rename_columns.ts b/x-pack/plugins/lens/common/expressions/rename_columns/rename_columns.ts index 517bd683d80ae..d425d5c80d18d 100644 --- a/x-pack/plugins/lens/common/expressions/rename_columns/rename_columns.ts +++ b/x-pack/plugins/lens/common/expressions/rename_columns/rename_columns.ts @@ -6,27 +6,9 @@ */ import { i18n } from '@kbn/i18n'; -import { - ExpressionFunctionDefinition, - Datatable, - DatatableColumn, -} from '../../../../../../src/plugins/expressions/common'; +import type { RenameColumnsExpressionFunction } from './types'; -interface RemapArgs { - idMap: string; -} - -type OriginalColumn = { id: string; label: string } & ( - | { operationType: 'date_histogram'; sourceField: string } - | { operationType: string; sourceField: never } -); - -export const renameColumns: ExpressionFunctionDefinition< - 'lens_rename_columns', - Datatable, - RemapArgs, - Datatable -> = { +export const renameColumns: RenameColumnsExpressionFunction = { name: 'lens_rename_columns', type: 'datatable', help: i18n.translate('xpack.lens.functions.renameColumns.help', { @@ -42,53 +24,9 @@ export const renameColumns: ExpressionFunctionDefinition< }, }, inputTypes: ['datatable'], - fn(data, { idMap: encodedIdMap }) { - const idMap = JSON.parse(encodedIdMap) as Record; - - return { - type: 'datatable', - rows: data.rows.map((row) => { - const mappedRow: Record = {}; - Object.entries(idMap).forEach(([fromId, toId]) => { - mappedRow[toId.id] = row[fromId]; - }); - - Object.entries(row).forEach(([id, value]) => { - if (id in idMap) { - mappedRow[idMap[id].id] = value; - } else { - mappedRow[id] = value; - } - }); - - return mappedRow; - }), - columns: data.columns.map((column) => { - const mappedItem = idMap[column.id]; - - if (!mappedItem) { - return column; - } - - return { - ...column, - id: mappedItem.id, - name: getColumnName(mappedItem, column), - }; - }), - }; + async fn(...args) { + /** Build optimization: prevent adding extra code into initial bundle **/ + const { renameColumnFn } = await import('./rename_columns_fn'); + return renameColumnFn(...args); }, }; - -function getColumnName(originalColumn: OriginalColumn, newColumn: DatatableColumn) { - if (originalColumn?.operationType === 'date_histogram') { - const fieldName = originalColumn.sourceField; - - // HACK: This is a hack, and introduces some fragility into - // column naming. Eventually, this should be calculated and - // built more systematically. - return newColumn.name.replace(fieldName, originalColumn.label); - } - - return originalColumn.label; -} diff --git a/x-pack/plugins/lens/common/expressions/rename_columns/rename_columns_fn.ts b/x-pack/plugins/lens/common/expressions/rename_columns/rename_columns_fn.ts new file mode 100644 index 0000000000000..ee0c7ed1eebec --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/rename_columns/rename_columns_fn.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { DatatableColumn } from '../../../../../../src/plugins/expressions/common'; +import type { OriginalColumn, RenameColumnsExpressionFunction } from './types'; + +function getColumnName(originalColumn: OriginalColumn, newColumn: DatatableColumn) { + if (originalColumn?.operationType === 'date_histogram') { + const fieldName = originalColumn.sourceField; + + // HACK: This is a hack, and introduces some fragility into + // column naming. Eventually, this should be calculated and + // built more systematically. + return newColumn.name.replace(fieldName, originalColumn.label); + } + + return originalColumn.label; +} + +export const renameColumnFn: RenameColumnsExpressionFunction['fn'] = ( + data, + { idMap: encodedIdMap } +) => { + const idMap = JSON.parse(encodedIdMap) as Record; + + return { + type: 'datatable', + rows: data.rows.map((row) => { + const mappedRow: Record = {}; + Object.entries(idMap).forEach(([fromId, toId]) => { + mappedRow[toId.id] = row[fromId]; + }); + + Object.entries(row).forEach(([id, value]) => { + if (id in idMap) { + mappedRow[idMap[id].id] = value; + } else { + mappedRow[id] = value; + } + }); + + return mappedRow; + }), + columns: data.columns.map((column) => { + const mappedItem = idMap[column.id]; + + if (!mappedItem) { + return column; + } + + return { + ...column, + id: mappedItem.id, + name: getColumnName(mappedItem, column), + }; + }), + }; +}; diff --git a/x-pack/plugins/lens/common/expressions/rename_columns/types.ts b/x-pack/plugins/lens/common/expressions/rename_columns/types.ts new file mode 100644 index 0000000000000..685ccfb89e4ca --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/rename_columns/types.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Datatable, ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions'; + +export type OriginalColumn = { id: string; label: string } & ( + | { operationType: 'date_histogram'; sourceField: string } + | { operationType: string; sourceField: never } +); + +export type RenameColumnsExpressionFunction = ExpressionFunctionDefinition< + 'lens_rename_columns', + Datatable, + { + idMap: string; + }, + Datatable | Promise +>; diff --git a/x-pack/plugins/lens/common/expressions/time_scale/index.ts b/x-pack/plugins/lens/common/expressions/time_scale/index.ts index 92fec01a9ecbc..b5bd7dbcbb074 100644 --- a/x-pack/plugins/lens/common/expressions/time_scale/index.ts +++ b/x-pack/plugins/lens/common/expressions/time_scale/index.ts @@ -5,5 +5,5 @@ * 2.0. */ -export * from './time_scale'; -export * from './types'; +export { getTimeScale } from './time_scale'; +export type { TimeScaleUnit, TimeScaleArgs } from './types'; diff --git a/x-pack/plugins/lens/common/expressions/time_scale/time_scale.test.ts b/x-pack/plugins/lens/common/expressions/time_scale/time_scale.test.ts index 9f299d9e3d74f..d51f2594b4267 100644 --- a/x-pack/plugins/lens/common/expressions/time_scale/time_scale.test.ts +++ b/x-pack/plugins/lens/common/expressions/time_scale/time_scale.test.ts @@ -6,8 +6,8 @@ */ import moment from 'moment'; -import { Datatable } from 'src/plugins/expressions/public'; -import { TimeRange } from 'src/plugins/data/public'; +import type { Datatable } from 'src/plugins/expressions/public'; +import type { TimeRange } from 'src/plugins/data/public'; import { functionWrapper } from 'src/plugins/expressions/common/expression_functions/specs/tests/utils'; // mock the specific inner variable: @@ -22,7 +22,8 @@ jest.mock('../../../../../../src/plugins/data/common/query/timefilter/get_time', }; }); -import { getTimeScale, TimeScaleArgs } from './time_scale'; +import { getTimeScale } from './time_scale'; +import type { TimeScaleArgs } from './types'; describe('time_scale', () => { let timeScaleWrapped: (input: Datatable, args: TimeScaleArgs) => Promise; diff --git a/x-pack/plugins/lens/common/expressions/time_scale/time_scale.ts b/x-pack/plugins/lens/common/expressions/time_scale/time_scale.ts index 711b770fb140f..21bef6de980ac 100644 --- a/x-pack/plugins/lens/common/expressions/time_scale/time_scale.ts +++ b/x-pack/plugins/lens/common/expressions/time_scale/time_scale.ts @@ -5,46 +5,12 @@ * 2.0. */ -import moment from 'moment-timezone'; -import { i18n } from '@kbn/i18n'; -import type { - ExpressionFunctionDefinition, - Datatable, -} from '../../../../../../src/plugins/expressions/common'; -import { - getDateHistogramMetaDataByDatatableColumn, - parseInterval, - calculateBounds, -} from '../../../../../../src/plugins/data/common'; -import { - buildResultColumns, - ExecutionContext, -} from '../../../../../../src/plugins/expressions/common'; -import type { TimeScaleUnit } from './types'; - -export interface TimeScaleArgs { - dateColumnId: string; - inputColumnId: string; - outputColumnId: string; - targetUnit: TimeScaleUnit; - outputColumnName?: string; -} - -const unitInMs: Record = { - s: 1000, - m: 1000 * 60, - h: 1000 * 60 * 60, - d: 1000 * 60 * 60 * 24, -}; +import type { ExecutionContext } from '../../../../../../src/plugins/expressions/common'; +import type { TimeScaleExpressionFunction } from './types'; export const getTimeScale = ( getTimezone: (context: ExecutionContext) => string | Promise -): ExpressionFunctionDefinition< - 'lens_time_scale', - Datatable, - TimeScaleArgs, - Promise -> => ({ +): TimeScaleExpressionFunction => ({ name: 'lens_time_scale', type: 'datatable', help: '', @@ -76,84 +42,9 @@ export const getTimeScale = ( }, }, inputTypes: ['datatable'], - async fn( - input, - { dateColumnId, inputColumnId, outputColumnId, outputColumnName, targetUnit }: TimeScaleArgs, - context - ) { - const dateColumnDefinition = input.columns.find((column) => column.id === dateColumnId); - - if (!dateColumnDefinition) { - throw new Error( - i18n.translate('xpack.lens.functions.timeScale.dateColumnMissingMessage', { - defaultMessage: 'Specified dateColumnId {columnId} does not exist.', - values: { - columnId: dateColumnId, - }, - }) - ); - } - - const resultColumns = buildResultColumns( - input, - outputColumnId, - inputColumnId, - outputColumnName, - { allowColumnOverwrite: true } - ); - - if (!resultColumns) { - return input; - } - - const targetUnitInMs = unitInMs[targetUnit]; - const timeInfo = getDateHistogramMetaDataByDatatableColumn(dateColumnDefinition, { - timeZone: await getTimezone(context), - }); - const intervalDuration = timeInfo?.interval && parseInterval(timeInfo.interval); - - if (!timeInfo || !intervalDuration) { - throw new Error( - i18n.translate('xpack.lens.functions.timeScale.timeInfoMissingMessage', { - defaultMessage: 'Could not fetch date histogram information', - }) - ); - } - // the datemath plugin always parses dates by using the current default moment time zone. - // to use the configured time zone, we are switching just for the bounds calculation. - const defaultTimezone = moment().zoneName(); - moment.tz.setDefault(timeInfo.timeZone); - - const timeBounds = timeInfo.timeRange && calculateBounds(timeInfo.timeRange); - - const result = { - ...input, - columns: resultColumns, - rows: input.rows.map((row) => { - const newRow = { ...row }; - - let startOfBucket = moment(row[dateColumnId]); - let endOfBucket = startOfBucket.clone().add(intervalDuration); - if (timeBounds && timeBounds.min) { - startOfBucket = moment.max(startOfBucket, timeBounds.min); - } - if (timeBounds && timeBounds.max) { - endOfBucket = moment.min(endOfBucket, timeBounds.max); - } - const bucketSize = endOfBucket.diff(startOfBucket); - const factor = bucketSize / targetUnitInMs; - - const currentValue = newRow[inputColumnId]; - if (currentValue != null) { - newRow[outputColumnId] = Number(currentValue) / factor; - } - - return newRow; - }), - }; - // reset default moment timezone - moment.tz.setDefault(defaultTimezone); - - return result; + async fn(...args) { + /** Build optimization: prevent adding extra code into initial bundle **/ + const { timeScaleFn } = await import('./time_scale_fn'); + return timeScaleFn(getTimezone)(...args); }, }); diff --git a/x-pack/plugins/lens/common/expressions/time_scale/time_scale_fn.ts b/x-pack/plugins/lens/common/expressions/time_scale/time_scale_fn.ts new file mode 100644 index 0000000000000..e6113afebca22 --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/time_scale/time_scale_fn.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment-timezone'; +import { i18n } from '@kbn/i18n'; +import { + buildResultColumns, + ExecutionContext, +} from '../../../../../../src/plugins/expressions/common'; +import { + calculateBounds, + getDateHistogramMetaDataByDatatableColumn, + parseInterval, +} from '../../../../../../src/plugins/data/common'; +import type { TimeScaleExpressionFunction, TimeScaleUnit, TimeScaleArgs } from './types'; + +const unitInMs: Record = { + s: 1000, + m: 1000 * 60, + h: 1000 * 60 * 60, + d: 1000 * 60 * 60 * 24, +}; + +export const timeScaleFn = ( + getTimezone: (context: ExecutionContext) => string | Promise +): TimeScaleExpressionFunction['fn'] => async ( + input, + { dateColumnId, inputColumnId, outputColumnId, outputColumnName, targetUnit }: TimeScaleArgs, + context +) => { + const dateColumnDefinition = input.columns.find((column) => column.id === dateColumnId); + + if (!dateColumnDefinition) { + throw new Error( + i18n.translate('xpack.lens.functions.timeScale.dateColumnMissingMessage', { + defaultMessage: 'Specified dateColumnId {columnId} does not exist.', + values: { + columnId: dateColumnId, + }, + }) + ); + } + + const resultColumns = buildResultColumns(input, outputColumnId, inputColumnId, outputColumnName, { + allowColumnOverwrite: true, + }); + + if (!resultColumns) { + return input; + } + + const targetUnitInMs = unitInMs[targetUnit]; + const timeInfo = getDateHistogramMetaDataByDatatableColumn(dateColumnDefinition, { + timeZone: await getTimezone(context), + }); + const intervalDuration = timeInfo?.interval && parseInterval(timeInfo.interval); + + if (!timeInfo || !intervalDuration) { + throw new Error( + i18n.translate('xpack.lens.functions.timeScale.timeInfoMissingMessage', { + defaultMessage: 'Could not fetch date histogram information', + }) + ); + } + // the datemath plugin always parses dates by using the current default moment time zone. + // to use the configured time zone, we are switching just for the bounds calculation. + const defaultTimezone = moment().zoneName(); + moment.tz.setDefault(timeInfo.timeZone); + + const timeBounds = timeInfo.timeRange && calculateBounds(timeInfo.timeRange); + + const result = { + ...input, + columns: resultColumns, + rows: input.rows.map((row) => { + const newRow = { ...row }; + + let startOfBucket = moment(row[dateColumnId]); + let endOfBucket = startOfBucket.clone().add(intervalDuration); + if (timeBounds && timeBounds.min) { + startOfBucket = moment.max(startOfBucket, timeBounds.min); + } + if (timeBounds && timeBounds.max) { + endOfBucket = moment.min(endOfBucket, timeBounds.max); + } + const bucketSize = endOfBucket.diff(startOfBucket); + const factor = bucketSize / targetUnitInMs; + + const currentValue = newRow[inputColumnId]; + if (currentValue != null) { + newRow[outputColumnId] = Number(currentValue) / factor; + } + + return newRow; + }), + }; + // reset default moment timezone + moment.tz.setDefault(defaultTimezone); + + return result; +}; diff --git a/x-pack/plugins/lens/common/expressions/time_scale/types.ts b/x-pack/plugins/lens/common/expressions/time_scale/types.ts index 4ee00ce53e68b..58c5e5723a7c1 100644 --- a/x-pack/plugins/lens/common/expressions/time_scale/types.ts +++ b/x-pack/plugins/lens/common/expressions/time_scale/types.ts @@ -5,4 +5,24 @@ * 2.0. */ +import type { + Datatable, + ExpressionFunctionDefinition, +} from '../../../../../../src/plugins/expressions'; + export type TimeScaleUnit = 's' | 'm' | 'h' | 'd'; + +export interface TimeScaleArgs { + dateColumnId: string; + inputColumnId: string; + outputColumnId: string; + targetUnit: TimeScaleUnit; + outputColumnName?: string; +} + +export type TimeScaleExpressionFunction = ExpressionFunctionDefinition< + 'lens_time_scale', + Datatable, + TimeScaleArgs, + Promise +>; diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index 5bd0fa9594bdf..de091fd305e85 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -45,7 +45,7 @@ import { getPreloadedState } from '../state_management/lens_slice'; export async function getLensServices( coreStart: CoreStart, startDependencies: LensPluginStartDependencies, - attributeService: () => Promise + attributeService: LensAttributeService ): Promise { const { data, @@ -71,7 +71,7 @@ export async function getLensServices( stateTransfer, usageCollection, savedObjectsTagging, - attributeService: await attributeService(), + attributeService, http: coreStart.http, chrome: coreStart.chrome, overlays: coreStart.overlays, @@ -97,8 +97,8 @@ export async function mountApp( params: AppMountParameters, mountProps: { createEditorFrame: EditorFrameStart['createInstance']; - attributeService: () => Promise; - getPresentationUtilContext: () => Promise; + attributeService: LensAttributeService; + getPresentationUtilContext: () => FC; } ) { const { createEditorFrame, attributeService, getPresentationUtilContext } = mountProps; @@ -252,7 +252,7 @@ export async function mountApp( params.element.classList.add('lnsAppWrapper'); - const PresentationUtilContext = await getPresentationUtilContext(); + const PresentationUtilContext = getPresentationUtilContext(); render( diff --git a/x-pack/plugins/lens/public/app_plugin/shared/saved_modal_lazy.tsx b/x-pack/plugins/lens/public/app_plugin/shared/saved_modal_lazy.tsx index f1a537fe65928..e1df6ca60dd6e 100644 --- a/x-pack/plugins/lens/public/app_plugin/shared/saved_modal_lazy.tsx +++ b/x-pack/plugins/lens/public/app_plugin/shared/saved_modal_lazy.tsx @@ -8,11 +8,11 @@ import React, { Suspense, useEffect, useState } from 'react'; import { EuiLoadingSpinner, EuiOverlayMask } from '@elastic/eui'; -import { CoreStart } from 'kibana/public'; +import type { CoreStart } from 'kibana/public'; import type { SaveModalContainerProps } from '../save_modal_container'; -import type { LensAttributeService } from '../../lens_attribute_service'; import type { LensPluginStartDependencies } from '../../plugin'; import type { LensAppServices } from '../types'; + const SaveModal = React.lazy(() => import('../save_modal_container')); function LoadingSpinnerWithOverlay() { @@ -33,16 +33,20 @@ const LensSavedModalLazy = (props: SaveModalContainerProps) => { export function getSaveModalComponent( coreStart: CoreStart, - startDependencies: LensPluginStartDependencies, - attributeService: () => Promise + startDependencies: LensPluginStartDependencies ) { return (props: Omit) => { const [lensServices, setLensServices] = useState(); useEffect(() => { async function loadLensService() { - const { getLensServices } = await import('../../async_services'); - const lensServicesT = await getLensServices(coreStart, startDependencies, attributeService); + const { getLensServices, getLensAttributeService } = await import('../../async_services'); + + const lensServicesT = await getLensServices( + coreStart, + startDependencies, + getLensAttributeService(coreStart, startDependencies) + ); setLensServices(lensServicesT); } diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx index ac1324385dbd1..6f00dc37fcd52 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx @@ -20,7 +20,7 @@ import { } from '@elastic/eui'; import type { LensFilterEvent, LensTableRowContextMenuEvent } from '../../types'; import type { FormatFactory } from '../../../common'; -import { LensGridDirection } from '../../../common/expressions'; +import type { LensGridDirection } from '../../../common/expressions'; import { VisualizationContainer } from '../../visualization_container'; import { EmptyPlaceholder, findMinMaxByColumnId } from '../../shared_components'; import { LensIconChartDatatable } from '../../assets/chart_datatable'; diff --git a/x-pack/plugins/lens/public/datatable_visualization/expression.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/expression.test.tsx index b2a25cba329df..74c33a2b02a5b 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/expression.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/expression.test.tsx @@ -9,7 +9,7 @@ import type { DatatableProps } from '../../common/expressions'; import type { LensMultiTable } from '../../common'; import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks'; import type { FormatFactory } from '../../common'; -import { getDatatable } from './expression'; +import { getDatatable } from '../../common/expressions'; function sampleArgs() { const indexPatternId = 'indexPatternId'; diff --git a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx index 4e541bce9a8c2..03691d56ee56a 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx @@ -18,9 +18,7 @@ import { DatatableComponent } from './components/table_basic'; import type { ILensInterpreterRenderHandlers } from '../types'; import type { FormatFactory } from '../../common'; -import { DatatableProps } from '../../common/expressions'; - -export { datatableColumn, getDatatable } from '../../common/expressions'; +import type { DatatableProps } from '../../common/expressions'; export const getDatatableRenderer = (dependencies: { formatFactory: FormatFactory; diff --git a/x-pack/plugins/lens/public/datatable_visualization/index.ts b/x-pack/plugins/lens/public/datatable_visualization/index.ts index 3349f229a6048..51f1f54cc03ce 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/index.ts +++ b/x-pack/plugins/lens/public/datatable_visualization/index.ts @@ -23,23 +23,14 @@ export interface DatatableVisualizationPluginSetupPlugins { } export class DatatableVisualization { - constructor() {} - setup( core: CoreSetup, { expressions, formatFactory, editorFrame, charts }: DatatableVisualizationPluginSetupPlugins ) { editorFrame.registerVisualization(async () => { - const { - getDatatable, - datatableColumn, - getDatatableRenderer, - getDatatableVisualization, - } = await import('../async_services'); + const { getDatatableRenderer, getDatatableVisualization } = await import('../async_services'); const palettes = await charts.palettes.getPalettes(); - expressions.registerFunction(() => datatableColumn); - expressions.registerFunction(() => getDatatable(() => formatFactory)); expressions.registerRenderer(() => getDatatableRenderer({ formatFactory, @@ -50,6 +41,7 @@ export class DatatableVisualization { uiSettings: core.uiSettings, }) ); + return getDatatableVisualization({ paletteService: palettes }); }); } diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.tsx index e1b1c637fa24b..d97cfd3cbca23 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { CoreSetup, CoreStart } from 'kibana/public'; +import { CoreStart } from 'kibana/public'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; import { ExpressionsSetup, ExpressionsStart } from '../../../../../src/plugins/expressions/public'; import { EmbeddableSetup, EmbeddableStart } from '../../../../../src/plugins/embeddable/public'; @@ -22,7 +22,6 @@ import { EditorFrameStart, } from '../types'; import { Document } from '../persistence/saved_object_store'; -import { mergeTables } from '../../common/expressions'; import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; import { DashboardStart } from '../../../../../src/plugins/dashboard/public'; @@ -59,8 +58,6 @@ async function collectAsyncDefinitions( } export class EditorFrameService { - constructor() {} - private readonly datasources: Array Promise)> = []; private readonly visualizations: Array Promise)> = []; @@ -81,12 +78,7 @@ export class EditorFrameService { return await persistedStateToExpression(resolvedDatasources, resolvedVisualizations, doc); }; - public setup( - core: CoreSetup, - plugins: EditorFrameSetupPlugins - ): EditorFrameSetup { - plugins.expressions.registerFunction(() => mergeTables); - + public setup(): EditorFrameSetup { return { registerDatasource: (datasource) => { this.datasources.push(datasource as Datasource); diff --git a/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx b/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx index e8095f6c741a4..7f65e50bf4429 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx @@ -6,8 +6,8 @@ */ import React, { FC, useEffect } from 'react'; -import { CoreStart } from 'kibana/public'; -import { UiActionsStart } from 'src/plugins/ui_actions/public'; +import type { CoreStart } from 'kibana/public'; +import type { UiActionsStart } from 'src/plugins/ui_actions/public'; import type { Start as InspectorStartContract } from 'src/plugins/inspector/public'; import { EuiLoadingChart } from '@elastic/eui'; import { diff --git a/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts b/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts index 5620f053cebf2..954905c51a4b7 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts +++ b/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts @@ -21,7 +21,7 @@ import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; import { Start as InspectorStart } from '../../../../../src/plugins/inspector/public'; import { Document } from '../persistence/saved_object_store'; import { LensAttributeService } from '../lens_attribute_service'; -import { DOC_TYPE } from '../../common'; +import { DOC_TYPE } from '../../common/constants'; import { ErrorMessage } from '../editor_frame_service/types'; import { extract, inject } from '../../common/embeddable_factory'; diff --git a/x-pack/plugins/lens/public/expressions.ts b/x-pack/plugins/lens/public/expressions.ts new file mode 100644 index 0000000000000..27f3179a2d0c8 --- /dev/null +++ b/x-pack/plugins/lens/public/expressions.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ExpressionsSetup } from 'src/plugins/expressions/public'; + +import { + axisExtentConfig, + yAxisConfig, + axisTitlesVisibilityConfig, +} from '../common/expressions/xy_chart/axis_config'; +import { gridlinesConfig } from '../common/expressions/xy_chart/grid_lines_config'; +import { labelsOrientationConfig } from '../common/expressions/xy_chart/labels_orientation_config'; +import { layerConfig } from '../common/expressions/xy_chart/layer_config'; +import { legendConfig } from '../common/expressions/xy_chart/legend_config'; +import { tickLabelsConfig } from '../common/expressions/xy_chart/tick_labels_config'; +import { xyChart } from '../common/expressions/xy_chart/xy_chart'; + +import { getDatatable } from '../common/expressions/datatable/datatable'; +import { datatableColumn } from '../common/expressions/datatable/datatable_column'; + +import { heatmap } from '../common/expressions/heatmap_chart/heatmap_chart'; +import { heatmapGridConfig } from '../common/expressions/heatmap_chart/heatmap_grid'; +import { heatmapLegendConfig } from '../common/expressions/heatmap_chart/heatmap_legend'; + +import { mergeTables } from '../common/expressions/merge_tables'; +import { renameColumns } from '../common/expressions/rename_columns/rename_columns'; +import { pie } from '../common/expressions/pie_chart/pie_chart'; +import { formatColumn } from '../common/expressions/format_column'; +import { counterRate } from '../common/expressions/counter_rate'; +import { getTimeScale } from '../common/expressions/time_scale/time_scale'; +import { metricChart } from '../common/expressions/metric_chart/metric_chart'; + +export const setupExpressions = ( + expressions: ExpressionsSetup, + formatFactory: Parameters[0], + getTimeZone: Parameters[0] +) => + [ + pie, + xyChart, + mergeTables, + counterRate, + metricChart, + yAxisConfig, + layerConfig, + formatColumn, + legendConfig, + renameColumns, + gridlinesConfig, + datatableColumn, + tickLabelsConfig, + axisTitlesVisibilityConfig, + heatmap, + heatmapLegendConfig, + heatmapGridConfig, + axisExtentConfig, + labelsOrientationConfig, + getDatatable(formatFactory), + getTimeScale(getTimeZone), + ].forEach((expressionFn) => expressions.registerFunction(expressionFn)); diff --git a/x-pack/plugins/lens/public/heatmap_visualization/expression.tsx b/x-pack/plugins/lens/public/heatmap_visualization/expression.tsx index 98ce4b399ae8d..84c8f987f524d 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/heatmap_visualization/expression.tsx @@ -17,8 +17,6 @@ import type { ChartsPluginSetup, PaletteRegistry } from '../../../../../src/plug import { HeatmapChartReportable } from './chart_component'; import type { HeatmapExpressionProps } from './types'; -export { heatmapGridConfig, heatmapLegendConfig, heatmap } from '../../common/expressions'; - export const getHeatmapRenderer = (dependencies: { formatFactory: FormatFactory; chartsThemeService: ChartsPluginSetup['theme']; diff --git a/x-pack/plugins/lens/public/heatmap_visualization/index.ts b/x-pack/plugins/lens/public/heatmap_visualization/index.ts index 5fb4524939f11..3ac3f769e4b5f 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/index.ts +++ b/x-pack/plugins/lens/public/heatmap_visualization/index.ts @@ -20,28 +20,15 @@ export interface HeatmapVisualizationPluginSetupPlugins { } export class HeatmapVisualization { - constructor() {} - setup( core: CoreSetup, { expressions, formatFactory, editorFrame, charts }: HeatmapVisualizationPluginSetupPlugins ) { editorFrame.registerVisualization(async () => { const timeZone = getTimeZone(core.uiSettings); - - const { - getHeatmapVisualization, - heatmap, - heatmapLegendConfig, - heatmapGridConfig, - getHeatmapRenderer, - } = await import('../async_services'); + const { getHeatmapVisualization, getHeatmapRenderer } = await import('../async_services'); const palettes = await charts.palettes.getPalettes(); - expressions.registerFunction(() => heatmap); - expressions.registerFunction(() => heatmapLegendConfig); - expressions.registerFunction(() => heatmapGridConfig); - expressions.registerRenderer( getHeatmapRenderer({ formatFactory, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_scaling.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_scaling.tsx index 7c611230683d3..8a670e7562573 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_scaling.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_scaling.tsx @@ -5,9 +5,16 @@ * 2.0. */ -import { EuiToolTip } from '@elastic/eui'; -import { EuiIcon } from '@elastic/eui'; -import { EuiFormRow, EuiSelect, EuiFlexItem, EuiFlexGroup, EuiButtonIcon } from '@elastic/eui'; +import { + EuiToolTip, + EuiIcon, + EuiFormRow, + EuiSelect, + EuiFlexItem, + EuiFlexGroup, + EuiButtonIcon, +} from '@elastic/eui'; + import { i18n } from '@kbn/i18n'; import React from 'react'; import { @@ -17,7 +24,7 @@ import { } from '../operations'; import type { TimeScaleUnit } from '../../../common/expressions'; import { unitSuffixesLong } from '../../../common/suffix_formatter'; -import { IndexPatternLayer } from '../types'; +import type { IndexPatternLayer } from '../types'; export function setTimeScaling( columnId: string, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts index 9ff80f51bea97..5f4afc9df6179 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts @@ -20,7 +20,6 @@ import type { FieldFormatsStart, FieldFormatsSetup, } from '../../../../../src/plugins/field_formats/public'; -import { getTimeZone } from '../utils'; export interface IndexPatternDatasourceSetupPlugins { expressions: ExpressionsSetup; @@ -38,8 +37,6 @@ export interface IndexPatternDatasourceStartPlugins { } export class IndexPatternDatasource { - constructor() {} - setup( core: CoreSetup, { @@ -50,15 +47,9 @@ export class IndexPatternDatasource { }: IndexPatternDatasourceSetupPlugins ) { editorFrame.registerDatasource(async () => { - const { - getIndexPatternDatasource, - renameColumns, - formatColumn, - counterRate, - getTimeScale, - getSuffixFormatter, - suffixFormatterId, - } = await import('../async_services'); + const { getIndexPatternDatasource, getSuffixFormatter, suffixFormatterId } = await import( + '../async_services' + ); if (!fieldFormatsSetup.has(suffixFormatterId)) { const startServices = createStartServicesGetter(core.getStartServices); @@ -69,11 +60,6 @@ export class IndexPatternDatasource { fieldFormatsSetup.register([suffixFormatter]); } - expressions.registerFunction(getTimeScale(() => getTimeZone(core.uiSettings))); - expressions.registerFunction(counterRate); - expressions.registerFunction(renameColumns); - expressions.registerFunction(formatColumn); - const [ coreStart, { indexPatternFieldEditor, uiActions, data, fieldFormats }, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index 8f66bcf7fe49c..6a45e3c987f3d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -70,19 +70,13 @@ export function columnToOperation(column: IndexPatternColumn, uniqueLabel?: stri }; } -export { - CounterRateArgs, - ExpressionFunctionCounterRate, - counterRate, -} from '../../common/expressions'; -export { FormatColumnArgs, supportedFormats, formatColumn } from '../../common/expressions'; +export type { FormatColumnArgs, TimeScaleArgs, CounterRateArgs } from '../../common/expressions'; + export { getSuffixFormatter, unitSuffixesLong, suffixFormatterId, } from '../../common/suffix_formatter'; -export { getTimeScale, TimeScaleArgs } from '../../common/expressions'; -export { renameColumns } from '../../common/expressions'; export function getIndexPatternDatasource({ core, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts index cff036db4813b..b0793bf912bb2 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts @@ -8,7 +8,7 @@ import { flatten, minBy, pick, mapValues, partition } from 'lodash'; import { i18n } from '@kbn/i18n'; import { generateId } from '../id_generator'; -import { DatasourceSuggestion, TableChangeType } from '../types'; +import type { DatasourceSuggestion, TableChangeType } from '../types'; import { columnToOperation } from './indexpattern'; import { insertNewColumn, @@ -23,7 +23,7 @@ import { getReferencedColumnIds, } from './operations'; import { hasField } from './utils'; -import { +import type { IndexPattern, IndexPatternPrivateState, IndexPatternLayer, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx index ceb02ab724ac5..29e7de18ca4ad 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx @@ -21,7 +21,7 @@ import { RangeEditor } from './range_editor'; import { OperationDefinition } from '../index'; import { FieldBasedIndexPatternColumn } from '../column_types'; import { updateColumnParam } from '../../layer_helpers'; -import { supportedFormats } from '../../../../../common/expressions'; +import { supportedFormats } from '../../../../../common/expressions/format_column/supported_formats'; import { MODES, AUTO_BARS, DEFAULT_INTERVAL, MIN_HISTOGRAM_BARS, SLICES } from './constants'; import { IndexPattern, IndexPatternField } from '../../../types'; import { getInvalidFieldMessage, isValidNumber } from '../helpers'; diff --git a/x-pack/plugins/lens/public/lens_attribute_service.ts b/x-pack/plugins/lens/public/lens_attribute_service.ts index 09c98b3dcba72..fb4ef4fee72ef 100644 --- a/x-pack/plugins/lens/public/lens_attribute_service.ts +++ b/x-pack/plugins/lens/public/lens_attribute_service.ts @@ -5,17 +5,17 @@ * 2.0. */ -import { CoreStart } from '../../../../src/core/public'; -import { LensPluginStartDependencies } from './plugin'; -import { AttributeService } from '../../../../src/plugins/embeddable/public'; -import { +import type { CoreStart } from '../../../../src/core/public'; +import type { LensPluginStartDependencies } from './plugin'; +import type { AttributeService } from '../../../../src/plugins/embeddable/public'; +import type { ResolvedLensSavedObjectAttributes, LensByValueInput, LensByReferenceInput, } from './embeddable/embeddable'; import { SavedObjectIndexStore } from './persistence'; import { checkForDuplicateTitle, OnSaveProps } from '../../../../src/plugins/saved_objects/public'; -import { DOC_TYPE } from '../common'; +import { DOC_TYPE } from '../common/constants'; export type LensAttributeService = AttributeService< ResolvedLensSavedObjectAttributes, diff --git a/x-pack/plugins/lens/public/metric_visualization/expression.test.tsx b/x-pack/plugins/lens/public/metric_visualization/expression.test.tsx index 36ae3904f073c..a3ac5b5837772 100644 --- a/x-pack/plugins/lens/public/metric_visualization/expression.test.tsx +++ b/x-pack/plugins/lens/public/metric_visualization/expression.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { MetricChart, metricChart } from './expression'; -import { MetricConfig } from '../../common/expressions'; +import { MetricChart } from './expression'; +import { MetricConfig, metricChart } from '../../common/expressions'; import React from 'react'; import { shallow } from 'enzyme'; import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks'; diff --git a/x-pack/plugins/lens/public/metric_visualization/expression.tsx b/x-pack/plugins/lens/public/metric_visualization/expression.tsx index 41b487e790a08..8838e6df777c9 100644 --- a/x-pack/plugins/lens/public/metric_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/metric_visualization/expression.tsx @@ -19,9 +19,7 @@ import { EmptyPlaceholder } from '../shared_components'; import { LensIconChartMetric } from '../assets/chart_metric'; import type { FormatFactory } from '../../common'; import type { MetricChartProps } from '../../common/expressions'; - -export { metricChart } from '../../common/expressions'; -export type { MetricState, MetricConfig } from '../../common/expressions'; +export type { MetricChartProps, MetricState, MetricConfig } from '../../common/expressions'; export const getMetricChartRenderer = ( formatFactory: FormatFactory diff --git a/x-pack/plugins/lens/public/metric_visualization/index.ts b/x-pack/plugins/lens/public/metric_visualization/index.ts index 29138979ab858..20c25b285bd5b 100644 --- a/x-pack/plugins/lens/public/metric_visualization/index.ts +++ b/x-pack/plugins/lens/public/metric_visualization/index.ts @@ -17,18 +17,12 @@ export interface MetricVisualizationPluginSetupPlugins { } export class MetricVisualization { - constructor() {} - setup( _core: CoreSetup | null, { expressions, formatFactory, editorFrame }: MetricVisualizationPluginSetupPlugins ) { editorFrame.registerVisualization(async () => { - const { metricVisualization, metricChart, getMetricChartRenderer } = await import( - '../async_services' - ); - - expressions.registerFunction(() => metricChart); + const { metricVisualization, getMetricChartRenderer } = await import('../async_services'); expressions.registerRenderer(() => getMetricChartRenderer(formatFactory)); return metricVisualization; diff --git a/x-pack/plugins/lens/public/metric_visualization/metric_suggestions.ts b/x-pack/plugins/lens/public/metric_visualization/metric_suggestions.ts index de79f5f0a4cbc..3d6b2683b4ad2 100644 --- a/x-pack/plugins/lens/public/metric_visualization/metric_suggestions.ts +++ b/x-pack/plugins/lens/public/metric_visualization/metric_suggestions.ts @@ -6,7 +6,7 @@ */ import { SuggestionRequest, VisualizationSuggestion, TableSuggestion } from '../types'; -import { MetricState } from '../../common/expressions'; +import type { MetricState } from '../../common/expressions'; import { layerTypes } from '../../common'; import { LensIconChartMetric } from '../assets/chart_metric'; diff --git a/x-pack/plugins/lens/public/pie_visualization/expression.tsx b/x-pack/plugins/lens/public/pie_visualization/expression.tsx index c947d50d5b910..d26289450bd0f 100644 --- a/x-pack/plugins/lens/public/pie_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/expression.tsx @@ -19,8 +19,6 @@ import type { FormatFactory } from '../../common'; import type { PieExpressionProps } from '../../common/expressions'; import type { ChartsPluginSetup, PaletteRegistry } from '../../../../../src/plugins/charts/public'; -export { pie } from '../../common/expressions'; - export const getPieRenderer = (dependencies: { formatFactory: FormatFactory; chartsThemeService: ChartsPluginSetup['theme']; diff --git a/x-pack/plugins/lens/public/pie_visualization/index.ts b/x-pack/plugins/lens/public/pie_visualization/index.ts index 6d34de85f1801..b4670b3b9c9dd 100644 --- a/x-pack/plugins/lens/public/pie_visualization/index.ts +++ b/x-pack/plugins/lens/public/pie_visualization/index.ts @@ -24,18 +24,14 @@ export interface PieVisualizationPluginStartPlugins { } export class PieVisualization { - constructor() {} - setup( core: CoreSetup, { expressions, formatFactory, editorFrame, charts }: PieVisualizationPluginSetupPlugins ) { editorFrame.registerVisualization(async () => { - const { getPieVisualization, pie, getPieRenderer } = await import('../async_services'); + const { getPieVisualization, getPieRenderer } = await import('../async_services'); const palettes = await charts.palettes.getPalettes(); - expressions.registerFunction(() => pie); - expressions.registerRenderer( getPieRenderer({ formatFactory, diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 26278f446c558..5326927d2c6c5 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -5,28 +5,34 @@ * 2.0. */ -import { AppMountParameters, CoreSetup, CoreStart } from 'kibana/public'; +import type { AppMountParameters, CoreSetup, CoreStart } from 'kibana/public'; import type { Start as InspectorStartContract } from 'src/plugins/inspector/public'; import type { FieldFormatsSetup, FieldFormatsStart } from 'src/plugins/field_formats/public'; -import { UsageCollectionSetup, UsageCollectionStart } from 'src/plugins/usage_collection/public'; -import { SpacesPluginStart } from '../../spaces/public'; -import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public'; -import { EmbeddableSetup, EmbeddableStart } from '../../../../src/plugins/embeddable/public'; -import { DashboardStart } from '../../../../src/plugins/dashboard/public'; -import { +import type { + UsageCollectionSetup, + UsageCollectionStart, +} from 'src/plugins/usage_collection/public'; +import type { + DataPublicPluginSetup, + DataPublicPluginStart, +} from '../../../../src/plugins/data/public'; +import type { EmbeddableSetup, EmbeddableStart } from '../../../../src/plugins/embeddable/public'; +import type { DashboardStart } from '../../../../src/plugins/dashboard/public'; +import type { SpacesPluginStart } from '../../spaces/public'; +import type { ExpressionsServiceSetup, ExpressionsSetup, ExpressionsStart, } from '../../../../src/plugins/expressions/public'; -import { +import type { VisualizationsSetup, VisualizationsStart, } from '../../../../src/plugins/visualizations/public'; -import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public'; -import { UrlForwardingSetup } from '../../../../src/plugins/url_forwarding/public'; -import { GlobalSearchPluginSetup } from '../../global_search/public'; -import { ChartsPluginSetup, ChartsPluginStart } from '../../../../src/plugins/charts/public'; -import { PresentationUtilPluginStart } from '../../../../src/plugins/presentation_util/public'; +import type { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public'; +import type { UrlForwardingSetup } from '../../../../src/plugins/url_forwarding/public'; +import type { GlobalSearchPluginSetup } from '../../global_search/public'; +import type { ChartsPluginSetup, ChartsPluginStart } from '../../../../src/plugins/charts/public'; +import type { PresentationUtilPluginStart } from '../../../../src/plugins/presentation_util/public'; import { EmbeddableStateTransfer } from '../../../../src/plugins/embeddable/public'; import type { EditorFrameService as EditorFrameServiceType } from './editor_frame_service'; import { IndexPatternFieldEditorStart } from '../../../../src/plugins/index_pattern_field_editor/public'; @@ -51,29 +57,33 @@ import type { PieVisualizationPluginSetupPlugins, } from './pie_visualization'; import type { HeatmapVisualization as HeatmapVisualizationType } from './heatmap_visualization'; -import { AppNavLinkStatus } from '../../../../src/core/public'; import type { SavedObjectTaggingPluginStart } from '../../saved_objects_tagging/public'; +import { AppNavLinkStatus } from '../../../../src/core/public'; + import { UiActionsStart, ACTION_VISUALIZE_FIELD, VISUALIZE_FIELD_TRIGGER, } from '../../../../src/plugins/ui_actions/public'; -import { APP_ID, FormatFactory, getEditPath, NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../common'; -import type { EditorFrameStart, VisualizationType } from './types'; +import { APP_ID, getEditPath, NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../common/constants'; +import type { FormatFactory } from '../common/types'; +import type { VisualizationType } from './types'; import { getLensAliasConfig } from './vis_type_alias'; import { visualizeFieldAction } from './trigger_actions/visualize_field_actions'; -import { getSearchProvider } from './search_provider'; -import { LensAttributeService } from './lens_attribute_service'; -import { LensEmbeddableInput } from './embeddable'; +import type { LensEmbeddableInput } from './embeddable'; import { EmbeddableFactory, LensEmbeddableStartServices } from './embeddable/embeddable_factory'; import { EmbeddableComponentProps, getEmbeddableComponent, } from './embeddable/embeddable_component'; import { getSaveModalComponent } from './app_plugin/shared/saved_modal_lazy'; -import { SaveModalContainerProps } from './app_plugin/save_modal_container'; +import type { SaveModalContainerProps } from './app_plugin/save_modal_container'; + +import { createStartServicesGetter } from '../../../../src/plugins/kibana_utils/public'; +import { setupExpressions } from './expressions'; +import { getSearchProvider } from './search_provider'; export interface LensPluginSetupDependencies { urlForwarding: UrlForwardingSetup; @@ -154,8 +164,6 @@ export interface LensPublicStart { export class LensPlugin { private datatableVisualization: DatatableVisualizationType | undefined; private editorFrameService: EditorFrameServiceType | undefined; - private createEditorFrame: EditorFrameStart['createInstance'] | null = null; - private attributeService: (() => Promise) | null = null; private indexpatternDatasource: IndexPatternDatasourceType | undefined; private xyVisualization: XyVisualizationType | undefined; private metricVisualization: MetricVisualizationType | undefined; @@ -178,37 +186,32 @@ export class LensPlugin { usageCollection, }: LensPluginSetupDependencies ) { - this.attributeService = async () => { - const { getLensAttributeService } = await import('./async_services'); - const [coreStart, startDependencies] = await core.getStartServices(); - return getLensAttributeService(coreStart, startDependencies); - }; + const startServices = createStartServicesGetter(core.getStartServices); const getStartServices = async (): Promise => { - const [coreStart, deps] = await core.getStartServices(); + const { getLensAttributeService } = await import('./async_services'); + const { core: coreStart, plugins } = startServices(); - this.initParts( + await this.initParts( core, data, - embeddable, charts, expressions, - usageCollection, fieldFormats, - deps.fieldFormats.deserialize + plugins.fieldFormats.deserialize ); return { - attributeService: await this.attributeService!(), + attributeService: getLensAttributeService(coreStart, plugins), capabilities: coreStart.application.capabilities, coreHttp: coreStart.http, - timefilter: deps.data.query.timefilter.timefilter, - expressionRenderer: deps.expressions.ReactExpressionRenderer, + timefilter: plugins.data.query.timefilter.timefilter, + expressionRenderer: plugins.expressions.ReactExpressionRenderer, documentToExpression: this.editorFrameService!.documentToExpression, - indexPatternService: deps.data.indexPatterns, - uiActions: deps.uiActions, + indexPatternService: plugins.data.indexPatterns, + uiActions: plugins.uiActions, usageCollection, - inspector: deps.inspector, + inspector: plugins.inspector, }; }; @@ -218,17 +221,22 @@ export class LensPlugin { visualizations.registerAlias(getLensAliasConfig()); - const getPresentationUtilContext = async () => { - const [, deps] = await core.getStartServices(); - const { ContextProvider } = deps.presentationUtil; - return ContextProvider; - }; + setupExpressions( + expressions, + () => startServices().plugins.fieldFormats.deserialize, + async () => { + const { getTimeZone } = await import('./utils'); + return getTimeZone(core.uiSettings); + } + ); + + const getPresentationUtilContext = () => + startServices().plugins.presentationUtil.ContextProvider; const ensureDefaultIndexPattern = async () => { - const [, deps] = await core.getStartServices(); // make sure a default index pattern exists // if not, the page will be redirected to management and visualize won't be rendered - await deps.data.indexPatterns.ensureDefaultIndexPattern(); + await startServices().plugins.data.indexPatterns.ensureDefaultIndexPattern(); }; core.application.register({ @@ -236,25 +244,27 @@ export class LensPlugin { title: NOT_INTERNATIONALIZED_PRODUCT_NAME, navLinkStatus: AppNavLinkStatus.hidden, mount: async (params: AppMountParameters) => { - const [, deps] = await core.getStartServices(); + const { core: coreStart, plugins: deps } = startServices(); await this.initParts( core, data, - embeddable, charts, expressions, - usageCollection, fieldFormats, deps.fieldFormats.deserialize ); - const { mountApp, stopReportManager } = await import('./async_services'); + const { mountApp, stopReportManager, getLensAttributeService } = await import( + './async_services' + ); + const frameStart = this.editorFrameService!.start(coreStart, deps); + this.stopReportManager = stopReportManager; await ensureDefaultIndexPattern(); return mountApp(core, params, { - createEditorFrame: this.createEditorFrame!, - attributeService: this.attributeService!, + createEditorFrame: frameStart.createInstance, + attributeService: getLensAttributeService(coreStart, deps), getPresentationUtilContext, }); }, @@ -280,10 +290,8 @@ export class LensPlugin { private async initParts( core: CoreSetup, data: DataPublicPluginSetup, - embeddable: EmbeddableSetup | undefined, charts: ChartsPluginSetup, expressions: ExpressionsServiceSetup, - usageCollection: UsageCollectionSetup | undefined, fieldFormats: FieldFormatsSetup, formatFactory: FormatFactory ) { @@ -303,13 +311,9 @@ export class LensPlugin { this.metricVisualization = new MetricVisualization(); this.pieVisualization = new PieVisualization(); this.heatmapVisualization = new HeatmapVisualization(); - const editorFrameSetupInterface = this.editorFrameService.setup(core, { - data, - embeddable, - charts, - expressions, - usageCollection, - }); + + const editorFrameSetupInterface = this.editorFrameService.setup(); + const dependencies: IndexPatternDatasourceSetupPlugins & XyVisualizationPluginSetupPlugins & DatatableVisualizationPluginSetupPlugins & @@ -328,9 +332,6 @@ export class LensPlugin { this.metricVisualization.setup(core, dependencies); this.pieVisualization.setup(core, dependencies); this.heatmapVisualization.setup(core, dependencies); - const [coreStart, startDependencies] = await core.getStartServices(); - const frameStart = this.editorFrameService.start(coreStart, startDependencies); - this.createEditorFrame = frameStart.createInstance; } start(core: CoreStart, startDependencies: LensPluginStartDependencies): LensPublicStart { @@ -345,7 +346,7 @@ export class LensPlugin { return { EmbeddableComponent: getEmbeddableComponent(core, startDependencies), - SaveModalComponent: getSaveModalComponent(core, startDependencies, this.attributeService!), + SaveModalComponent: getSaveModalComponent(core, startDependencies), navigateToPrefilledEditor: ( input, { openInNewTab = false, originatingApp = '', originatingPath } = {} diff --git a/x-pack/plugins/lens/public/search_provider.ts b/x-pack/plugins/lens/public/search_provider.ts index 4bc18f2653a0b..a0d41da2d9740 100644 --- a/x-pack/plugins/lens/public/search_provider.ts +++ b/x-pack/plugins/lens/public/search_provider.ts @@ -5,12 +5,13 @@ * 2.0. */ -import { ApplicationStart } from 'kibana/public'; +import type { ApplicationStart } from 'kibana/public'; import { from, of } from 'rxjs'; import { i18n } from '@kbn/i18n'; import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public'; -import { GlobalSearchResultProvider } from '../../global_search/public'; -import { getFullPath } from '../common'; +import { getFullPath } from '../common/constants'; + +import type { GlobalSearchResultProvider } from '../../global_search/public'; /** * Global search provider adding a Lens entry. diff --git a/x-pack/plugins/lens/public/trigger_actions/visualize_field_actions.ts b/x-pack/plugins/lens/public/trigger_actions/visualize_field_actions.ts index 9e7507b01bc59..4e105ed9db499 100644 --- a/x-pack/plugins/lens/public/trigger_actions/visualize_field_actions.ts +++ b/x-pack/plugins/lens/public/trigger_actions/visualize_field_actions.ts @@ -11,7 +11,7 @@ import { ACTION_VISUALIZE_LENS_FIELD, VisualizeFieldContext, } from '../../../../../src/plugins/ui_actions/public'; -import { ApplicationStart } from '../../../../../src/core/public'; +import type { ApplicationStart } from '../../../../../src/core/public'; export const visualizeFieldAction = (application: ApplicationStart) => createAction({ diff --git a/x-pack/plugins/lens/public/utils.ts b/x-pack/plugins/lens/public/utils.ts index b7dd3ed3733cf..993be9a06a2d9 100644 --- a/x-pack/plugins/lens/public/utils.ts +++ b/x-pack/plugins/lens/public/utils.ts @@ -4,16 +4,20 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { uniq } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { IndexPattern, IndexPatternsContract, TimefilterContract } from 'src/plugins/data/public'; -import { IUiSettingsClient } from 'kibana/public'; import moment from 'moment-timezone'; -import { SavedObjectReference } from 'kibana/public'; -import { uniq } from 'lodash'; -import { Document } from './persistence/saved_object_store'; -import { Datasource, DatasourceMap } from './types'; -import { DatasourceStates } from './state_management'; + +import type { + IndexPattern, + IndexPatternsContract, + TimefilterContract, +} from 'src/plugins/data/public'; +import type { IUiSettingsClient } from 'kibana/public'; +import type { SavedObjectReference } from 'kibana/public'; +import type { Document } from './persistence/saved_object_store'; +import type { Datasource, DatasourceMap } from './types'; +import type { DatasourceStates } from './state_management'; export function getVisualizeGeoFieldMessage(fieldType: string) { return i18n.translate('xpack.lens.visualizeGeoFieldMessage', { diff --git a/x-pack/plugins/lens/public/vis_type_alias.ts b/x-pack/plugins/lens/public/vis_type_alias.ts index 5b48ef8b31923..96332e07069b0 100644 --- a/x-pack/plugins/lens/public/vis_type_alias.ts +++ b/x-pack/plugins/lens/public/vis_type_alias.ts @@ -6,8 +6,8 @@ */ import { i18n } from '@kbn/i18n'; -import { VisTypeAlias } from 'src/plugins/visualizations/public'; -import { getBasePath, getEditPath } from '../common'; +import type { VisTypeAlias } from 'src/plugins/visualizations/public'; +import { getBasePath, getEditPath } from '../common/constants'; export const getLensAliasConfig = (): VisTypeAlias => ({ aliasPath: getBasePath(), diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx index 3994aadd9a989..671db4653a88a 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx @@ -22,9 +22,10 @@ import { LayoutDirection, } from '@elastic/charts'; import { PaletteOutput } from 'src/plugins/charts/public'; -import { calculateMinInterval, XYChart, XYChartRenderProps, xyChart } from './expression'; +import { calculateMinInterval, XYChart, XYChartRenderProps } from './expression'; import type { LensMultiTable } from '../../common'; import { layerTypes } from '../../common'; +import { xyChart } from '../../common/expressions'; import { layerConfig, legendConfig, diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index 75d14d9b48ee3..b7f1a9dabf3c7 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -74,18 +74,6 @@ type SeriesSpec = InferPropType & InferPropType & InferPropType; -export { - legendConfig, - yAxisConfig, - tickLabelsConfig, - gridlinesConfig, - axisTitlesVisibilityConfig, - axisExtentConfig, - layerConfig, - xyChart, - labelsOrientationConfig, -} from '../../common/expressions'; - export type XYChartRenderProps = XYChartProps & { chartsThemeService: ChartsPluginSetup['theme']; chartsActiveCursorService: ChartsPluginStart['activeCursor']; diff --git a/x-pack/plugins/lens/public/xy_visualization/index.ts b/x-pack/plugins/lens/public/xy_visualization/index.ts index 6823ffedc9d90..f9d48ffaaae37 100644 --- a/x-pack/plugins/lens/public/xy_visualization/index.ts +++ b/x-pack/plugins/lens/public/xy_visualization/index.ts @@ -21,37 +21,14 @@ export interface XyVisualizationPluginSetupPlugins { } export class XyVisualization { - constructor() {} - setup( core: CoreSetup, { expressions, formatFactory, editorFrame }: XyVisualizationPluginSetupPlugins ) { editorFrame.registerVisualization(async () => { - const { - legendConfig, - yAxisConfig, - tickLabelsConfig, - gridlinesConfig, - axisTitlesVisibilityConfig, - axisExtentConfig, - labelsOrientationConfig, - layerConfig, - xyChart, - getXyChartRenderer, - getXyVisualization, - } = await import('../async_services'); + const { getXyChartRenderer, getXyVisualization } = await import('../async_services'); const [, { charts, fieldFormats }] = await core.getStartServices(); const palettes = await charts.palettes.getPalettes(); - expressions.registerFunction(() => legendConfig); - expressions.registerFunction(() => yAxisConfig); - expressions.registerFunction(() => tickLabelsConfig); - expressions.registerFunction(() => axisExtentConfig); - expressions.registerFunction(() => labelsOrientationConfig); - expressions.registerFunction(() => gridlinesConfig); - expressions.registerFunction(() => axisTitlesVisibilityConfig); - expressions.registerFunction(() => layerConfig); - expressions.registerFunction(() => xyChart); expressions.registerRenderer( getXyChartRenderer({ diff --git a/x-pack/plugins/lens/public/xy_visualization/types.ts b/x-pack/plugins/lens/public/xy_visualization/types.ts index 9d32c2f71c530..4729cfb96f324 100644 --- a/x-pack/plugins/lens/public/xy_visualization/types.ts +++ b/x-pack/plugins/lens/public/xy_visualization/types.ts @@ -17,8 +17,8 @@ import { LensIconChartBarHorizontalStacked } from '../assets/chart_bar_horizonta import { LensIconChartBarHorizontalPercentage } from '../assets/chart_bar_horizontal_percentage'; import { LensIconChartLine } from '../assets/chart_line'; -import { VisualizationType } from '../types'; -import { +import type { VisualizationType } from '../types'; +import type { SeriesType, ValueLabelConfig, LegendConfig, From cd622af39124a9d8bc9d9ea8565da0c63fec8dfa Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 7 Sep 2021 07:16:40 -0400 Subject: [PATCH 27/43] [Discover] Fix infinite scrolling of classic table (#110944) (#111330) Co-authored-by: Matthias Wilhelm --- .../doc_table/doc_table_infinite.tsx | 8 +--- .../lib/should_load_next_doc_patch.test.ts | 41 +++++++++++++++++++ .../lib/should_load_next_doc_patch.ts | 26 ++++++++++++ 3 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 src/plugins/discover/public/application/apps/main/components/doc_table/lib/should_load_next_doc_patch.test.ts create mode 100644 src/plugins/discover/public/application/apps/main/components/doc_table/lib/should_load_next_doc_patch.ts diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_infinite.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_infinite.tsx index 778c6c1abe274..5f0825d9cbd15 100644 --- a/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_infinite.tsx +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_infinite.tsx @@ -13,6 +13,7 @@ import { debounce } from 'lodash'; import { EuiButtonEmpty } from '@elastic/eui'; import { DocTableProps, DocTableRenderProps, DocTableWrapper } from './doc_table_wrapper'; import { SkipBottomButton } from '../skip_bottom_button'; +import { shouldLoadNextDocPatch } from './lib/should_load_next_doc_patch'; const FOOTER_PADDING = { padding: 0 }; @@ -35,12 +36,7 @@ const DocTableInfiniteContent = (props: DocTableRenderProps) => { const scheduleCheck = debounce(() => { const isMobileView = document.getElementsByClassName('dscSidebar__mobile').length > 0; const usedScrollDiv = isMobileView ? scrollMobileElem : scrollDiv; - - const scrollusedHeight = usedScrollDiv.scrollHeight; - const scrollTop = Math.abs(usedScrollDiv.scrollTop); - const clientHeight = usedScrollDiv.clientHeight; - - if (scrollTop + clientHeight === scrollusedHeight) { + if (shouldLoadNextDocPatch(usedScrollDiv)) { setLimit((prevLimit) => prevLimit + 50); } }, 50); diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/lib/should_load_next_doc_patch.test.ts b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/should_load_next_doc_patch.test.ts new file mode 100644 index 0000000000000..ea6dd4f9b3e31 --- /dev/null +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/should_load_next_doc_patch.test.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { shouldLoadNextDocPatch } from './should_load_next_doc_patch'; + +describe('shouldLoadNextDocPatch', () => { + test('next patch should not be loaded', () => { + const scrollingDomEl = { + scrollHeight: 500, + scrollTop: 100, + clientHeight: 100, + } as HTMLElement; + + expect(shouldLoadNextDocPatch(scrollingDomEl)).toBeFalsy(); + }); + + test('next patch should be loaded', () => { + const scrollingDomEl = { + scrollHeight: 500, + scrollTop: 350, + clientHeight: 100, + } as HTMLElement; + + expect(shouldLoadNextDocPatch(scrollingDomEl)).toBeTruthy(); + }); + + test("next patch should be loaded even there's a decimal scroll height", () => { + const scrollingDomEl = { + scrollHeight: 500, + scrollTop: 350.34234234, + clientHeight: 100, + } as HTMLElement; + + expect(shouldLoadNextDocPatch(scrollingDomEl)).toBeTruthy(); + }); +}); diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/lib/should_load_next_doc_patch.ts b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/should_load_next_doc_patch.ts new file mode 100644 index 0000000000000..5834b6808fac1 --- /dev/null +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/should_load_next_doc_patch.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// use a buffer to start rendering more documents before the user completely scrolles down +const verticalScrollBuffer = 100; + +/** + * Helper function to determine if the next patch of 50 documents should be loaded + */ +export function shouldLoadNextDocPatch(domEl: HTMLElement) { + // the height of the scrolling div, including content not visible on the screen due to overflow. + const scrollHeight = domEl.scrollHeight; + // the number of pixels that the div is is scrolled vertically + const scrollTop = domEl.scrollTop; + // the inner height of the scrolling div, excluding content that's visible on the screen + const clientHeight = domEl.clientHeight; + + const consumedHeight = scrollTop + clientHeight; + const remainingHeight = scrollHeight - consumedHeight; + return remainingHeight < verticalScrollBuffer; +} From e7aef4203652d6a896a512ce07f11536390a7b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20Zolt=C3=A1n=20Szab=C3=B3?= Date: Tue, 7 Sep 2021 13:28:42 +0200 Subject: [PATCH 28/43] [APM] Uses doc link service instead of ElasticDocsLink for linking custom links (#111149) (#111334) --- ...bana-plugin-core-public.doclinksstart.links.md | 1 + .../kibana-plugin-core-public.doclinksstart.md | 1 + src/core/public/doc_links/doc_links_service.ts | 2 ++ src/core/public/public.api.md | 1 + .../Documentation.tsx | 15 +++++---------- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index 0a1f2d1bf8b4e..c556d58421737 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -12,6 +12,7 @@ readonly links: { readonly apm: { readonly kibanaSettings: string; readonly supportedServiceMaps: string; + readonly customLinks: string; readonly droppedTransactionSpans: string; readonly upgrading: string; readonly metaData: string; diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md index 4b6e1ad2631b4..908615b3220dd 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md @@ -17,6 +17,7 @@ export interface DocLinksStart | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string | | +| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly apm: {
readonly kibanaSettings: string;
readonly supportedServiceMaps: string;
readonly customLinks: string;
};
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
readonly ecs: {
readonly guide: string;
};
} | | | [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly apm: {
readonly kibanaSettings: string;
readonly supportedServiceMaps: string;
readonly upgrading: string;
};
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
readonly ecs: {
readonly guide: string;
};
} | | | [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly apm: {
readonly kibanaSettings: string;
readonly supportedServiceMaps: string;
readonly metaData: string;
};
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
readonly ecs: {
readonly guide: string;
};
} | | diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index efce0263929e1..e4d440b2d8c83 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -34,6 +34,7 @@ export class DocLinksService { apm: { kibanaSettings: `${KIBANA_DOCS}apm-settings-in-kibana.html`, supportedServiceMaps: `${KIBANA_DOCS}service-maps.html#service-maps-supported`, + customLinks: `${KIBANA_DOCS}custom-links.html`, droppedTransactionSpans: `${APM_DOCS}get-started/${DOC_LINK_VERSION}/transaction-spans.html#dropped-spans`, upgrading: `${APM_DOCS}server/${DOC_LINK_VERSION}/upgrading.html`, metaData: `${APM_DOCS}get-started/${DOC_LINK_VERSION}/metadata.html`, @@ -464,6 +465,7 @@ export interface DocLinksStart { readonly apm: { readonly kibanaSettings: string; readonly supportedServiceMaps: string; + readonly customLinks: string; readonly droppedTransactionSpans: string; readonly upgrading: string; readonly metaData: string; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index cdeb9ab7388ac..78032145a5c73 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -477,6 +477,7 @@ export interface DocLinksStart { readonly apm: { readonly kibanaSettings: string; readonly supportedServiceMaps: string; + readonly customLinks: string; readonly droppedTransactionSpans: string; readonly upgrading: string; readonly metaData: string; diff --git a/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/Documentation.tsx b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/Documentation.tsx index 58bcd756582a4..95f7cc871bcb3 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/Documentation.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/Documentation.tsx @@ -6,19 +6,14 @@ */ import React from 'react'; -import { ElasticDocsLink } from '../../../../../shared/Links/ElasticDocsLink'; +import { EuiLink } from '@elastic/eui'; +import { useApmPluginContext } from '../../../../../../context/apm_plugin/use_apm_plugin_context'; interface Props { label: string; } + export function Documentation({ label }: Props) { - return ( - - {label} - - ); + const { docLinks } = useApmPluginContext().core; + return {label}; } From d0e0917150035b59895aafb7e333511b8370bf12 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 7 Sep 2021 07:32:49 -0400 Subject: [PATCH 29/43] Fix inconsistent total count on TopN events panel (#111256) (#111332) We can't display `response.totalCount` because it is the total number of events the query returns. It doesn't take into account the aggregation. It does include events with missing `stackedByField` and events that are not included in the 10 top. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Pablo Machado --- .../public/common/containers/matrix_histogram/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts index 233313e47ad66..1be71423899e2 100644 --- a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts +++ b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts @@ -141,7 +141,7 @@ export const useMatrixHistogram = ({ data: response.matrixHistogramData, inspect: getInspectResponse(response, prevResponse.inspect), refetch: refetch.current, - totalCount: response.totalCount, + totalCount: histogramBuckets.reduce((acc, bucket) => bucket.doc_count + acc, 0), buckets: histogramBuckets, })); searchSubscription$.current.unsubscribe(); From 8eeb3e06223aa0acbb0bd59dbd09c10ec19848e4 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 7 Sep 2021 08:00:56 -0400 Subject: [PATCH 30/43] [Fleet] Stop loading js-yaml in main plugin bundle (#111169) (#111342) Co-authored-by: Josh Dover <1813008+joshdover@users.noreply.github.com> --- packages/kbn-optimizer/limits.yml | 2 +- .../services/full_agent_policy_to_yaml.ts | 6 +- .../services/validate_package_policy.test.ts | 69 +++++++++++++------ .../services/validate_package_policy.ts | 26 +++++-- .../components/agent_policy_yaml_flyout.tsx | 3 +- .../create_package_policy_page/index.tsx | 4 +- .../services/has_invalid_but_required_var.ts | 5 +- .../edit_package_policy_page/index.tsx | 8 ++- .../standalone_instructions.tsx | 3 +- .../server/routes/agent_policy/handlers.ts | 3 +- .../fleet/server/services/package_policy.ts | 3 +- 11 files changed, 91 insertions(+), 41 deletions(-) diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 222cfc013271e..e099251b555d5 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -34,7 +34,7 @@ pageLoadAssetSize: indexPatternManagement: 28222 indexPatternEditor: 40000 infra: 184320 - fleet: 465774 + fleet: 250000 ingestPipelines: 58003 inputControlVis: 172675 inspector: 148711 diff --git a/x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts b/x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts index a2c1dcd83dd20..f4193d619e168 100644 --- a/x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts +++ b/x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { safeDump } from 'js-yaml'; +import type { safeDump } from 'js-yaml'; import type { FullAgentPolicy } from '../types'; @@ -25,8 +25,8 @@ const POLICY_KEYS_ORDER = [ 'input', ]; -export const fullAgentPolicyToYaml = (policy: FullAgentPolicy): string => { - return safeDump(policy, { +export const fullAgentPolicyToYaml = (policy: FullAgentPolicy, toYaml: typeof safeDump): string => { + return toYaml(policy, { skipInvalid: true, sortKeys: (keyA: string, keyB: string) => { const indexA = POLICY_KEYS_ORDER.indexOf(keyA); diff --git a/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts b/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts index 3e4f156da3379..30bd9f071feb8 100644 --- a/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts +++ b/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { safeLoad } from 'js-yaml'; + import { installationStatuses } from '../constants'; import type { PackageInfo, NewPackagePolicy, RegistryPolicyTemplate } from '../types'; @@ -371,13 +373,13 @@ describe('Fleet - validatePackagePolicy()', () => { }; it('returns no errors for valid package policy', () => { - expect(validatePackagePolicy(validPackagePolicy, mockPackage)).toEqual( + expect(validatePackagePolicy(validPackagePolicy, mockPackage, safeLoad)).toEqual( noErrorsValidationResults ); }); it('returns errors for invalid package policy', () => { - expect(validatePackagePolicy(invalidPackagePolicy, mockPackage)).toEqual({ + expect(validatePackagePolicy(invalidPackagePolicy, mockPackage, safeLoad)).toEqual({ name: ['Name is required'], description: null, namespace: null, @@ -423,7 +425,11 @@ describe('Fleet - validatePackagePolicy()', () => { enabled: false, })); expect( - validatePackagePolicy({ ...validPackagePolicy, inputs: disabledInputs }, mockPackage) + validatePackagePolicy( + { ...validPackagePolicy, inputs: disabledInputs }, + mockPackage, + safeLoad + ) ).toEqual(noErrorsValidationResults); }); @@ -439,7 +445,8 @@ describe('Fleet - validatePackagePolicy()', () => { expect( validatePackagePolicy( { ...invalidPackagePolicy, inputs: inputsWithDisabledStreams }, - mockPackage + mockPackage, + safeLoad ) ).toEqual({ name: ['Name is required'], @@ -485,10 +492,14 @@ describe('Fleet - validatePackagePolicy()', () => { it('returns no errors for packages with no package policies', () => { expect( - validatePackagePolicy(validPackagePolicy, { - ...mockPackage, - policy_templates: undefined, - }) + validatePackagePolicy( + validPackagePolicy, + { + ...mockPackage, + policy_templates: undefined, + }, + safeLoad + ) ).toEqual({ name: null, description: null, @@ -496,10 +507,14 @@ describe('Fleet - validatePackagePolicy()', () => { inputs: null, }); expect( - validatePackagePolicy(validPackagePolicy, { - ...mockPackage, - policy_templates: [], - }) + validatePackagePolicy( + validPackagePolicy, + { + ...mockPackage, + policy_templates: [], + }, + safeLoad + ) ).toEqual({ name: null, description: null, @@ -510,10 +525,14 @@ describe('Fleet - validatePackagePolicy()', () => { it('returns no errors for packages with no inputs', () => { expect( - validatePackagePolicy(validPackagePolicy, { - ...mockPackage, - policy_templates: [{} as RegistryPolicyTemplate], - }) + validatePackagePolicy( + validPackagePolicy, + { + ...mockPackage, + policy_templates: [{} as RegistryPolicyTemplate], + }, + safeLoad + ) ).toEqual({ name: null, description: null, @@ -521,10 +540,14 @@ describe('Fleet - validatePackagePolicy()', () => { inputs: null, }); expect( - validatePackagePolicy(validPackagePolicy, { - ...mockPackage, - policy_templates: [({ inputs: [] } as unknown) as RegistryPolicyTemplate], - }) + validatePackagePolicy( + validPackagePolicy, + { + ...mockPackage, + policy_templates: [({ inputs: [] } as unknown) as RegistryPolicyTemplate], + }, + safeLoad + ) ).toEqual({ name: null, description: null, @@ -539,7 +562,8 @@ describe('Fleet - validatePackagePolicy()', () => { expect( validatePackagePolicy( INVALID_AWS_POLICY as NewPackagePolicy, - (AWS_PACKAGE as unknown) as PackageInfo + (AWS_PACKAGE as unknown) as PackageInfo, + safeLoad ) ).toMatchSnapshot(); }); @@ -549,7 +573,8 @@ describe('Fleet - validatePackagePolicy()', () => { validationHasErrors( validatePackagePolicy( VALID_AWS_POLICY as NewPackagePolicy, - (AWS_PACKAGE as unknown) as PackageInfo + (AWS_PACKAGE as unknown) as PackageInfo, + safeLoad ) ) ).toBe(false); diff --git a/x-pack/plugins/fleet/common/services/validate_package_policy.ts b/x-pack/plugins/fleet/common/services/validate_package_policy.ts index 67df65b2f12bf..12bef2dfd7c27 100644 --- a/x-pack/plugins/fleet/common/services/validate_package_policy.ts +++ b/x-pack/plugins/fleet/common/services/validate_package_policy.ts @@ -7,7 +7,6 @@ import { getFlattenedObject } from '@kbn/std'; import { i18n } from '@kbn/i18n'; -import { safeLoad } from 'js-yaml'; import { keyBy } from 'lodash'; import type { @@ -47,7 +46,8 @@ export type PackagePolicyValidationResults = { */ export const validatePackagePolicy = ( packagePolicy: NewPackagePolicy, - packageInfo: PackageInfo + packageInfo: PackageInfo, + safeLoadYaml: (yaml: string) => any ): PackagePolicyValidationResults => { const hasIntegrations = doesPackageHaveIntegrations(packageInfo); const validationResults: PackagePolicyValidationResults = { @@ -75,7 +75,12 @@ export const validatePackagePolicy = ( const packageVars = Object.entries(packagePolicy.vars || {}); if (packageVars.length) { validationResults.vars = packageVars.reduce((results, [name, varEntry]) => { - results[name] = validatePackagePolicyConfig(varEntry, packageVarsByName[name], name); + results[name] = validatePackagePolicyConfig( + varEntry, + packageVarsByName[name], + name, + safeLoadYaml + ); return results; }, {} as ValidationEntry); } @@ -139,7 +144,8 @@ export const validatePackagePolicy = ( ? validatePackagePolicyConfig( configEntry, inputVarDefsByPolicyTemplateAndType[inputKey][name], - name + name, + safeLoadYaml ) : null; return results; @@ -162,7 +168,12 @@ export const validatePackagePolicy = ( (results, [name, configEntry]) => { results[name] = streamVarDefs && streamVarDefs[name] && input.enabled && stream.enabled - ? validatePackagePolicyConfig(configEntry, streamVarDefs[name], name) + ? validatePackagePolicyConfig( + configEntry, + streamVarDefs[name], + name, + safeLoadYaml + ) : null; return results; }, @@ -191,7 +202,8 @@ export const validatePackagePolicy = ( export const validatePackagePolicyConfig = ( configEntry: PackagePolicyConfigRecordEntry, varDef: RegistryVarsEntry, - varName: string + varName: string, + safeLoadYaml: (yaml: string) => any ): string[] | null => { const errors = []; const { value } = configEntry; @@ -223,7 +235,7 @@ export const validatePackagePolicyConfig = ( if (varDef.type === 'yaml') { try { - parsedValue = safeLoad(value); + parsedValue = safeLoadYaml(value); } catch (e) { errors.push( i18n.translate('xpack.fleet.packagePolicyValidation.invalidYamlFormatErrorMessage', { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_yaml_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_yaml_flyout.tsx index e434347c2c367..8cc07556eee7f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_yaml_flyout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_yaml_flyout.tsx @@ -8,6 +8,7 @@ import React, { memo } from 'react'; import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n/react'; +import { safeDump } from 'js-yaml'; import { EuiCodeBlock, EuiFlexGroup, @@ -54,7 +55,7 @@ export const AgentPolicyYamlFlyout = memo<{ policyId: string; onClose: () => voi ) : ( - {fullAgentPolicyToYaml(yamlData!.item)} + {fullAgentPolicyToYaml(yamlData!.item, safeDump)} ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx index 47ecfd6bd121c..1f7fa0ceb354b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx @@ -23,6 +23,7 @@ import { } from '@elastic/eui'; import type { EuiStepProps } from '@elastic/eui/src/components/steps/step'; import type { ApplicationStart } from 'kibana/public'; +import { safeLoad } from 'js-yaml'; import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_react/public'; import type { @@ -191,7 +192,8 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { if (packageInfo) { const newValidationResult = validatePackagePolicy( newPackagePolicy || packagePolicy, - packageInfo + packageInfo, + safeLoad ); setValidationResults(newValidationResult); // eslint-disable-next-line no-console diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.ts index bf75b05f41b8d..e41b98c05e41c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { safeLoad } from 'js-yaml'; + import type { PackagePolicyConfigRecord, RegistryVarsEntry } from '../../../../types'; import { validatePackagePolicyConfig } from './'; @@ -25,7 +27,8 @@ export const hasInvalidButRequiredVar = ( validatePackagePolicyConfig( packagePolicyVars[registryVar.name], registryVar, - registryVar.name + registryVar.name, + safeLoad )?.length) ) ) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx index ea027f95eb9e8..a36bc988da89f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx @@ -9,6 +9,7 @@ import React, { useState, useEffect, useCallback, useMemo, memo } from 'react'; import { useRouteMatch } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { safeLoad } from 'js-yaml'; import { EuiButtonEmpty, EuiButton, @@ -201,7 +202,9 @@ export const EditPackagePolicyForm = memo<{ if (packageData?.response) { setPackageInfo(packageData.response); - setValidationResults(validatePackagePolicy(newPackagePolicy, packageData.response)); + setValidationResults( + validatePackagePolicy(newPackagePolicy, packageData.response, safeLoad) + ); setFormState('VALID'); } } @@ -239,7 +242,8 @@ export const EditPackagePolicyForm = memo<{ if (packageInfo) { const newValidationResult = validatePackagePolicy( newPackagePolicy || packagePolicy, - packageInfo + packageInfo, + safeLoad ); setValidationResults(newValidationResult); // eslint-disable-next-line no-console diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/standalone_instructions.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/standalone_instructions.tsx index 59898b9190c00..d7b9ae2aef08a 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/standalone_instructions.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/standalone_instructions.tsx @@ -21,6 +21,7 @@ import { import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { safeDump } from 'js-yaml'; import { useStartServices, useLink, sendGetOneAgentPolicyFull } from '../../hooks'; import { fullAgentPolicyToYaml, agentPolicyRouteService } from '../../services'; @@ -71,7 +72,7 @@ export const StandaloneInstructions = React.memo(({ agentPolicy, agentPol fetchFullPolicy(); }, [selectedPolicyId, notifications.toasts]); - const yaml = useMemo(() => fullAgentPolicyToYaml(fullAgentPolicy), [fullAgentPolicy]); + const yaml = useMemo(() => fullAgentPolicyToYaml(fullAgentPolicy, safeDump), [fullAgentPolicy]); const steps = [ DownloadStep(), !agentPolicy diff --git a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts index 57401a525b5d7..a7cf606e92c0b 100644 --- a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts @@ -8,6 +8,7 @@ import type { TypeOf } from '@kbn/config-schema'; import type { RequestHandler, ResponseHeaders } from 'src/core/server'; import bluebird from 'bluebird'; +import { safeDump } from 'js-yaml'; import { fullAgentPolicyToYaml } from '../../../common/services'; import { appContextService, agentPolicyService, packagePolicyService } from '../../services'; @@ -269,7 +270,7 @@ export const downloadFullAgentPolicy: RequestHandler< standalone: request.query.standalone === true, }); if (fullAgentPolicy) { - const body = fullAgentPolicyToYaml(fullAgentPolicy); + const body = fullAgentPolicyToYaml(fullAgentPolicy, safeDump); const headers: ResponseHeaders = { 'content-type': 'text/x-yaml', 'content-disposition': `attachment; filename="elastic-agent.yml"`, diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 8ff3c20b7aa15..598dd16b2928e 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -16,6 +16,7 @@ import type { SavedObjectsClientContract, } from 'src/core/server'; import uuid from 'uuid'; +import { safeLoad } from 'js-yaml'; import type { AuthenticatedUser } from '../../../security/server'; import { @@ -988,7 +989,7 @@ export function overridePackageInputs( inputs, }; - const validationResults = validatePackagePolicy(resultingPackagePolicy, packageInfo); + const validationResults = validatePackagePolicy(resultingPackagePolicy, packageInfo, safeLoad); if (validationHasErrors(validationResults)) { const responseFormattedValidationErrors = Object.entries(getFlattenedObject(validationResults)) From f9a35838b8301e9a63a9a256f30856375730bad8 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 7 Sep 2021 08:54:18 -0400 Subject: [PATCH 31/43] [RAC] Fix missing case title and tags after adding visualization (#111236) (#111353) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patryk KopyciƄski Co-authored-by: Christos Nasikas Co-authored-by: Patryk KopyciƄski Co-authored-by: Christos Nasikas --- .../public/components/create/description.tsx | 13 +++++- .../components/markdown_editor/context.tsx | 2 + .../components/markdown_editor/editor.tsx | 40 +++++++------------ .../components/markdown_editor/eui_form.tsx | 25 +++++++++--- .../markdown_editor/plugins/lens/plugin.tsx | 14 +++++-- .../plugins/lens/use_lens_draft_comment.ts | 2 + 6 files changed, 60 insertions(+), 36 deletions(-) diff --git a/x-pack/plugins/cases/public/components/create/description.tsx b/x-pack/plugins/cases/public/components/create/description.tsx index f589587356c4b..d831470ea1d54 100644 --- a/x-pack/plugins/cases/public/components/create/description.tsx +++ b/x-pack/plugins/cases/public/components/create/description.tsx @@ -7,7 +7,7 @@ import React, { memo, useEffect, useRef } from 'react'; import { MarkdownEditorForm } from '../markdown_editor'; -import { UseField, useFormContext } from '../../common/shared_imports'; +import { UseField, useFormContext, useFormData } from '../../common/shared_imports'; import { useLensDraftComment } from '../markdown_editor/plugins/lens/use_lens_draft_comment'; interface Props { @@ -24,12 +24,21 @@ const DescriptionComponent: React.FC = ({ isLoading }) => { clearDraftComment, } = useLensDraftComment(); const { setFieldValue } = useFormContext(); + const [{ title, tags }] = useFormData({ watch: ['title', 'tags'] }); const editorRef = useRef>(); useEffect(() => { if (draftComment?.commentId === fieldName && editorRef.current) { setFieldValue(fieldName, draftComment.comment); + if (draftComment.caseTitle) { + setFieldValue('title', draftComment.caseTitle); + } + + if (draftComment.caseTags && draftComment.caseTags.length > 0) { + setFieldValue('tags', draftComment.caseTags); + } + if (hasIncomingLensState) { openLensModal({ editorRef: editorRef.current }); } else { @@ -48,6 +57,8 @@ const DescriptionComponent: React.FC = ({ isLoading }) => { dataTestSubj: 'caseDescription', idAria: 'caseDescription', isDisabled: isLoading, + caseTitle: title, + caseTags: tags, }} /> ); diff --git a/x-pack/plugins/cases/public/components/markdown_editor/context.tsx b/x-pack/plugins/cases/public/components/markdown_editor/context.tsx index d7f5b0612cb73..44b65d6277b3f 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/context.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/context.tsx @@ -10,4 +10,6 @@ import React from 'react'; export const CommentEditorContext = React.createContext<{ editorId: string; value: string; + caseTitle?: string; + caseTags?: string[]; } | null>(null); diff --git a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx index b61fae25aa399..f2351a2b2d793 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx @@ -9,7 +9,6 @@ import React, { memo, forwardRef, useCallback, - useMemo, useRef, useState, useImperativeHandle, @@ -24,7 +23,6 @@ import { } from '@elastic/eui'; import { ContextShape } from '@elastic/eui/src/components/markdown_editor/markdown_context'; import { usePlugins } from './use_plugins'; -import { CommentEditorContext } from './context'; import { useLensButtonToggle } from './plugins/lens/use_lens_button_toggle'; interface MarkdownEditorProps { @@ -65,14 +63,6 @@ const MarkdownEditorComponent = forwardRef ({ - editorId, - value, - }), - [editorId, value] - ); - // @ts-expect-error useImperativeHandle(ref, () => { if (!editorRef.current) { @@ -88,22 +78,20 @@ const MarkdownEditorComponent = forwardRef - - + ); } ); diff --git a/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx b/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx index 2719f38f98fc2..94881e30e10f3 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx @@ -5,19 +5,22 @@ * 2.0. */ -import React, { forwardRef } from 'react'; +import React, { forwardRef, useMemo } from 'react'; import styled from 'styled-components'; import { EuiMarkdownEditorProps, EuiFormRow, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; import { FieldHook, getFieldValidityAndErrorMessage } from '../../common/shared_imports'; import { MarkdownEditor, MarkdownEditorRef } from './editor'; +import { CommentEditorContext } from './context'; type MarkdownEditorFormProps = EuiMarkdownEditorProps & { id: string; - field: FieldHook; + field: FieldHook; dataTestSubj: string; idAria: string; isDisabled?: boolean; bottomRightContent?: React.ReactNode; + caseTitle?: string; + caseTags?: string[]; }; const BottomContentWrapper = styled(EuiFlexGroup)` @@ -28,11 +31,21 @@ const BottomContentWrapper = styled(EuiFlexGroup)` export const MarkdownEditorForm = React.memo( forwardRef( - ({ id, field, dataTestSubj, idAria, bottomRightContent }, ref) => { + ({ id, field, dataTestSubj, idAria, bottomRightContent, caseTitle, caseTags }, ref) => { const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + const commentEditorContextValue = useMemo( + () => ({ + editorId: id, + value: field.value, + caseTitle, + caseTags, + }), + [id, field.value, caseTitle, caseTags] + ); + return ( - <> + @@ -57,7 +70,7 @@ export const MarkdownEditorForm = React.memo( {bottomRightContent} )} - + ); } ) diff --git a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx index 732a99968e883..f840b1ea5bb50 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx @@ -135,6 +135,8 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ commentId: commentEditorContext?.editorId, comment: commentEditorContext?.value, position: node?.position, + caseTitle: commentEditorContext?.caseTitle, + caseTags: commentEditorContext?.caseTags, }); lens?.navigateToPrefilledEditor(undefined, { @@ -145,10 +147,12 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ storage, commentEditorContext?.editorId, commentEditorContext?.value, + commentEditorContext?.caseTitle, + commentEditorContext?.caseTags, node?.position, + lens, currentAppId, originatingPath, - lens, ]); const handleEditInLensClick = useCallback( @@ -157,6 +161,8 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ commentId: commentEditorContext?.editorId, comment: commentEditorContext?.value, position: node?.position, + caseTitle: commentEditorContext?.caseTitle, + caseTags: commentEditorContext?.caseTags, }); lens?.navigateToPrefilledEditor( @@ -177,11 +183,13 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ storage, commentEditorContext?.editorId, commentEditorContext?.value, + commentEditorContext?.caseTitle, + commentEditorContext?.caseTags, node?.position, + node?.attributes, + lens, currentAppId, originatingPath, - lens, - node?.attributes, ] ); diff --git a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/use_lens_draft_comment.ts b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/use_lens_draft_comment.ts index 2a77037b300a3..a2dccc0e44d74 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/use_lens_draft_comment.ts +++ b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/use_lens_draft_comment.ts @@ -16,6 +16,8 @@ interface DraftComment { commentId: string; comment: string; position: EuiMarkdownAstNodePosition; + caseTitle?: string; + caseTags?: string[]; } export const useLensDraftComment = () => { From 346b258cd82f80fdc3d8f186cc2eedef82ab0696 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ester=20Mart=C3=AD=20Vilaseca?= Date: Tue, 7 Sep 2021 15:19:05 +0200 Subject: [PATCH 32/43] [Montiroing] Add monitoring toolbar with date picker (#110612) (#111355) Co-authored-by: Phillip Burch --- .../application/global_state_context.tsx | 7 ++- .../monitoring/public/application/index.tsx | 63 ++++++++++--------- .../application/pages/page_template.tsx | 8 ++- .../application/pages/use_monitoring_time.tsx | 50 +++++++++++++++ .../public/components/shared/toolbar.tsx | 56 +++++++++++++++++ 5 files changed, 149 insertions(+), 35 deletions(-) create mode 100644 x-pack/plugins/monitoring/public/application/pages/use_monitoring_time.tsx create mode 100644 x-pack/plugins/monitoring/public/components/shared/toolbar.tsx diff --git a/x-pack/plugins/monitoring/public/application/global_state_context.tsx b/x-pack/plugins/monitoring/public/application/global_state_context.tsx index 042d55418c5ab..dc33316dbd9d9 100644 --- a/x-pack/plugins/monitoring/public/application/global_state_context.tsx +++ b/x-pack/plugins/monitoring/public/application/global_state_context.tsx @@ -11,7 +11,6 @@ import { MonitoringStartPluginDependencies } from '../types'; interface GlobalStateProviderProps { query: MonitoringStartPluginDependencies['data']['query']; toasts: MonitoringStartPluginDependencies['core']['notifications']['toasts']; - children: React.ReactNode; } interface State { @@ -21,7 +20,11 @@ interface State { export const GlobalStateContext = createContext({} as State); -export const GlobalStateProvider = ({ query, toasts, children }: GlobalStateProviderProps) => { +export const GlobalStateProvider: React.FC = ({ + query, + toasts, + children, +}) => { // TODO: remove fakeAngularRootScope and fakeAngularLocation when angular is removed const fakeAngularRootScope: Partial = { $on: ( diff --git a/x-pack/plugins/monitoring/public/application/index.tsx b/x-pack/plugins/monitoring/public/application/index.tsx index ce38b00a359c8..e15ad995ca161 100644 --- a/x-pack/plugins/monitoring/public/application/index.tsx +++ b/x-pack/plugins/monitoring/public/application/index.tsx @@ -17,6 +17,7 @@ import { GlobalStateProvider } from './global_state_context'; import { ExternalConfigContext, ExternalConfig } from './external_config_context'; import { createPreserveQueryHistory } from './preserve_query_history'; import { RouteInit } from './route_init'; +import { MonitoringTimeContainer } from './pages/use_monitoring_time'; export const renderApp = ( core: CoreStart, @@ -45,36 +46,38 @@ const MonitoringApp: React.FC<{ - - - - - - - - - - + + + + + + + + + + + + diff --git a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx index 531de505bf43d..f40c2d3ec5e50 100644 --- a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx @@ -8,6 +8,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiTab, EuiTabs, EuiTitle } from '@elastic/eui'; import React from 'react'; import { useTitle } from '../hooks/use_title'; +import { MonitoringToolbar } from '../../components/shared/toolbar'; export interface TabMenuItem { id: string; @@ -20,11 +21,10 @@ export interface TabMenuItem { interface PageTemplateProps { title: string; pageTitle?: string; - children: React.ReactNode; tabs?: TabMenuItem[]; } -export const PageTemplate = ({ title, pageTitle, tabs, children }: PageTemplateProps) => { +export const PageTemplate: React.FC = ({ title, pageTitle, tabs, children }) => { useTitle('', title); return ( @@ -52,7 +52,9 @@ export const PageTemplate = ({ title, pageTitle, tabs, children }: PageTemplateP
- {/* HERE GOES THE TIMEPICKER */} + + + {tabs && ( diff --git a/x-pack/plugins/monitoring/public/application/pages/use_monitoring_time.tsx b/x-pack/plugins/monitoring/public/application/pages/use_monitoring_time.tsx new file mode 100644 index 0000000000000..f54d40ed29a06 --- /dev/null +++ b/x-pack/plugins/monitoring/public/application/pages/use_monitoring_time.tsx @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { useCallback, useState } from 'react'; +import createContainer from 'constate'; + +interface TimeOptions { + from: string; + to: string; + interval: string; +} + +export const DEFAULT_TIMERANGE: TimeOptions = { + from: 'now-1h', + to: 'now', + interval: '>=10s', +}; + +export const useMonitoringTime = () => { + const defaultTimeRange = { + from: 'now-1h', + to: 'now', + interval: DEFAULT_TIMERANGE.interval, + }; + const [refreshInterval, setRefreshInterval] = useState(5000); + const [isPaused, setIsPaused] = useState(false); + const [currentTimerange, setTimeRange] = useState(defaultTimeRange); + + const handleTimeChange = useCallback( + (start: string, end: string) => { + setTimeRange({ ...currentTimerange, from: start, to: end }); + }, + [currentTimerange, setTimeRange] + ); + + return { + currentTimerange, + setTimeRange, + handleTimeChange, + setRefreshInterval, + refreshInterval, + setIsPaused, + isPaused, + }; +}; + +export const MonitoringTimeContainer = createContainer(useMonitoringTime); diff --git a/x-pack/plugins/monitoring/public/components/shared/toolbar.tsx b/x-pack/plugins/monitoring/public/components/shared/toolbar.tsx new file mode 100644 index 0000000000000..6e45d4d831ec9 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/shared/toolbar.tsx @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiSuperDatePicker, OnRefreshChangeProps } from '@elastic/eui'; +import React, { useContext, useCallback } from 'react'; +import { MonitoringTimeContainer } from '../../application/pages/use_monitoring_time'; + +export const MonitoringToolbar = () => { + const { + currentTimerange, + handleTimeChange, + setRefreshInterval, + refreshInterval, + setIsPaused, + isPaused, + } = useContext(MonitoringTimeContainer.Context); + + const onTimeChange = useCallback( + (selectedTime: { start: string; end: string; isInvalid: boolean }) => { + if (selectedTime.isInvalid) { + return; + } + handleTimeChange(selectedTime.start, selectedTime.end); + }, + [handleTimeChange] + ); + + const onRefreshChange = useCallback( + ({ refreshInterval: ri, isPaused: isP }: OnRefreshChangeProps) => { + setRefreshInterval(ri); + setIsPaused(isP); + }, + [setRefreshInterval, setIsPaused] + ); + + return ( + + Setup Button + + {}} + isPaused={isPaused} + refreshInterval={refreshInterval} + onRefreshChange={onRefreshChange} + /> + + + ); +}; From 9a23ec20bb3309f9d3631d67afd92010a991fbb6 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 7 Sep 2021 09:34:15 -0400 Subject: [PATCH 33/43] [RAC] [Observability] Disable RAC feature flags for now (#111296) (#111358) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Felix StĂŒrmer --- x-pack/plugins/observability/server/index.ts | 4 +- x-pack/plugins/rule_registry/server/config.ts | 2 +- x-pack/scripts/functional_tests.js | 1 + x-pack/test/functional/config.js | 1 - .../apps/observability/alerts/index.ts | 0 .../observability/feature_controls/index.ts | 0 .../observability_security.ts | 0 .../apps/observability/index.ts | 0 .../ftr_provider_context.d.ts | 13 +++++ .../with_rac_write.config.ts | 51 +++++++++++++++++++ 10 files changed, 68 insertions(+), 4 deletions(-) rename x-pack/test/{functional => observability_functional}/apps/observability/alerts/index.ts (100%) rename x-pack/test/{functional => observability_functional}/apps/observability/feature_controls/index.ts (100%) rename x-pack/test/{functional => observability_functional}/apps/observability/feature_controls/observability_security.ts (100%) rename x-pack/test/{functional => observability_functional}/apps/observability/index.ts (100%) create mode 100644 x-pack/test/observability_functional/ftr_provider_context.d.ts create mode 100644 x-pack/test/observability_functional/with_rac_write.config.ts diff --git a/x-pack/plugins/observability/server/index.ts b/x-pack/plugins/observability/server/index.ts index eb08e9f3c258d..9a62602859c54 100644 --- a/x-pack/plugins/observability/server/index.ts +++ b/x-pack/plugins/observability/server/index.ts @@ -29,8 +29,8 @@ export const config = { index: schema.string({ defaultValue: 'observability-annotations' }), }), unsafe: schema.object({ - alertingExperience: schema.object({ enabled: schema.boolean({ defaultValue: true }) }), - cases: schema.object({ enabled: schema.boolean({ defaultValue: true }) }), + alertingExperience: schema.object({ enabled: schema.boolean({ defaultValue: false }) }), + cases: schema.object({ enabled: schema.boolean({ defaultValue: false }) }), }), }), }; diff --git a/x-pack/plugins/rule_registry/server/config.ts b/x-pack/plugins/rule_registry/server/config.ts index 8f98ceb2dd8db..481c5fe3cce8b 100644 --- a/x-pack/plugins/rule_registry/server/config.ts +++ b/x-pack/plugins/rule_registry/server/config.ts @@ -11,7 +11,7 @@ export const config = { schema: schema.object({ enabled: schema.boolean({ defaultValue: true }), write: schema.object({ - enabled: schema.boolean({ defaultValue: true }), + enabled: schema.boolean({ defaultValue: false }), }), unsafe: schema.object({ legacyMultiTenancy: schema.object({ diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index 34c0038b2ceb2..43933f6260415 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -60,6 +60,7 @@ require('@kbn/test').runTestsCli([ require.resolve('../test/security_api_integration/anonymous_es_anonymous.config.ts'), require.resolve('../test/observability_api_integration/basic/config.ts'), require.resolve('../test/observability_api_integration/trial/config.ts'), + require.resolve('../test/observability_functional/with_rac_write.config.ts'), require.resolve('../test/encrypted_saved_objects_api_integration/config.ts'), require.resolve('../test/spaces_api_integration/spaces_only/config.ts'), require.resolve('../test/spaces_api_integration/security_and_spaces/config_trial.ts'), diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index e29b9a2629cd5..9698ab5ee5137 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -59,7 +59,6 @@ export default async function ({ readConfigFile }) { resolve(__dirname, './apps/reporting_management'), resolve(__dirname, './apps/management'), resolve(__dirname, './apps/reporting'), - resolve(__dirname, './apps/observability'), //This upgrade assistant file needs to be run after the rest of the jobs because of //it being destructive. diff --git a/x-pack/test/functional/apps/observability/alerts/index.ts b/x-pack/test/observability_functional/apps/observability/alerts/index.ts similarity index 100% rename from x-pack/test/functional/apps/observability/alerts/index.ts rename to x-pack/test/observability_functional/apps/observability/alerts/index.ts diff --git a/x-pack/test/functional/apps/observability/feature_controls/index.ts b/x-pack/test/observability_functional/apps/observability/feature_controls/index.ts similarity index 100% rename from x-pack/test/functional/apps/observability/feature_controls/index.ts rename to x-pack/test/observability_functional/apps/observability/feature_controls/index.ts diff --git a/x-pack/test/functional/apps/observability/feature_controls/observability_security.ts b/x-pack/test/observability_functional/apps/observability/feature_controls/observability_security.ts similarity index 100% rename from x-pack/test/functional/apps/observability/feature_controls/observability_security.ts rename to x-pack/test/observability_functional/apps/observability/feature_controls/observability_security.ts diff --git a/x-pack/test/functional/apps/observability/index.ts b/x-pack/test/observability_functional/apps/observability/index.ts similarity index 100% rename from x-pack/test/functional/apps/observability/index.ts rename to x-pack/test/observability_functional/apps/observability/index.ts diff --git a/x-pack/test/observability_functional/ftr_provider_context.d.ts b/x-pack/test/observability_functional/ftr_provider_context.d.ts new file mode 100644 index 0000000000000..66d4e37b795ca --- /dev/null +++ b/x-pack/test/observability_functional/ftr_provider_context.d.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { GenericFtrProviderContext } from '@kbn/test'; + +import { pageObjects } from '../functional/page_objects'; +import { services } from '../functional/services'; + +export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/observability_functional/with_rac_write.config.ts b/x-pack/test/observability_functional/with_rac_write.config.ts new file mode 100644 index 0000000000000..89a0da7857333 --- /dev/null +++ b/x-pack/test/observability_functional/with_rac_write.config.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { readFileSync } from 'fs'; +import { resolve } from 'path'; +import { CA_CERT_PATH } from '@kbn/dev-utils'; +import { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const xpackFunctionalConfig = await readConfigFile(require.resolve('../functional/config.js')); + + const servers = { + ...xpackFunctionalConfig.get('servers'), + elasticsearch: { + ...xpackFunctionalConfig.get('servers.elasticsearch'), + protocol: 'https', + certificateAuthorities: [readFileSync(CA_CERT_PATH)], + }, + }; + + return { + // default to the xpack functional config + ...xpackFunctionalConfig.getAll(), + servers, + esTestCluster: { + ...xpackFunctionalConfig.get('esTestCluster'), + ssl: true, + }, + kbnTestServer: { + ...xpackFunctionalConfig.get('kbnTestServer'), + serverArgs: [ + ...xpackFunctionalConfig.get('kbnTestServer.serverArgs'), + `--elasticsearch.hosts=https://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`, + `--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`, + // TO DO: Remove feature flags once we're good to go + '--xpack.observability.unsafe.alertingExperience.enabled=true', + '--xpack.observability.unsafe.cases.enabled=true', + '--xpack.ruleRegistry.write.enabled=true', + ], + }, + testFiles: [resolve(__dirname, './apps/observability')], + junit: { + ...xpackFunctionalConfig.get('junit'), + reportName: 'Chrome X-Pack Observability UI Functional Tests', + }, + }; +} From d3c3e4e4ab1ff460850e2c32b7ed6048490f244f Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 7 Sep 2021 09:35:56 -0400 Subject: [PATCH 34/43] [RAC] Remove delete phase from default RAC ILM policy (#111139) (#111360) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Felix StĂŒrmer --- .../lifecycle_policies/default_lifecycle_policy.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/rule_registry/common/assets/lifecycle_policies/default_lifecycle_policy.ts b/x-pack/plugins/rule_registry/common/assets/lifecycle_policies/default_lifecycle_policy.ts index f207087f7aa19..24653dbf07d2d 100644 --- a/x-pack/plugins/rule_registry/common/assets/lifecycle_policies/default_lifecycle_policy.ts +++ b/x-pack/plugins/rule_registry/common/assets/lifecycle_policies/default_lifecycle_policy.ts @@ -11,16 +11,11 @@ export const defaultLifecyclePolicy = { hot: { actions: { rollover: { - max_age: '90d', - max_size: '50gb', + max_age: '30d', + max_primary_shard_size: '50gb', }, }, }, - delete: { - actions: { - delete: {}, - }, - }, }, }, }; From d6787612b50c16bdbc439c9edb107970fc9a5287 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 7 Sep 2021 09:39:57 -0400 Subject: [PATCH 35/43] [RAC] Add loading and empty states to the alerts table - Take II (#110504) (#111110) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Alejandro FernĂĄndez GĂłmez --- .../components/alerts_table/index.tsx | 7 +- ...on_product_no_results_magnifying_glass.svg | 1 + .../components/t_grid/integrated/index.tsx | 106 +++++++----------- .../public/components/t_grid/shared/index.tsx | 90 +++++++++++++++ .../components/t_grid/standalone/index.tsx | 48 +++----- .../public/components/t_grid/styles.tsx | 7 ++ .../timelines/public/methods/index.tsx | 11 +- .../applications/timelines_test/index.tsx | 69 ++++++------ 8 files changed, 195 insertions(+), 144 deletions(-) create mode 100644 x-pack/plugins/timelines/public/assets/illustration_product_no_results_magnifying_glass.svg create mode 100644 x-pack/plugins/timelines/public/components/t_grid/shared/index.tsx diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index e179c02987462..3c277d1d4019b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import { EuiLoadingContent, EuiPanel } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { connect, ConnectedProps, useDispatch } from 'react-redux'; @@ -369,11 +368,7 @@ export const AlertsTableComponent: React.FC = ({ }, [dispatch, defaultTimelineModel, filterManager, tGridEnabled, timelineId]); if (loading || indexPatternsLoading || isEmpty(selectedPatterns)) { - return ( - - - - ); + return null; } return ( diff --git a/x-pack/plugins/timelines/public/assets/illustration_product_no_results_magnifying_glass.svg b/x-pack/plugins/timelines/public/assets/illustration_product_no_results_magnifying_glass.svg new file mode 100644 index 0000000000000..b9a0df1630b20 --- /dev/null +++ b/x-pack/plugins/timelines/public/assets/illustration_product_no_results_magnifying_glass.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx index c3c83f6be72c8..cdfca4e09eb10 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx @@ -8,19 +8,12 @@ import type { AlertConsumers as AlertConsumersTyped } from '@kbn/rule-data-utils'; // @ts-expect-error import { AlertConsumers as AlertConsumersNonTyped } from '@kbn/rule-data-utils/target_node/alerts_as_data_rbac'; -import { - EuiEmptyPrompt, - EuiFlexGroup, - EuiFlexItem, - EuiPanel, - EuiLoadingContent, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; import React, { useEffect, useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; import { useDispatch } from 'react-redux'; -import { FormattedMessage } from '@kbn/i18n/react'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { Direction, EntityType } from '../../../../common/search_strategy'; import type { DocValueFields } from '../../../../common/search_strategy'; @@ -53,6 +46,7 @@ import { SELECTOR_TIMELINE_GLOBAL_CONTAINER, UpdatedFlexGroup, UpdatedFlexItem } import { Sort } from '../body/sort'; import { InspectButton, InspectButtonContainer } from '../../inspect'; import { SummaryViewSelector, ViewSelection } from '../event_rendered_view/selector'; +import { TGridLoading, TGridEmpty } from '../shared'; const AlertConsumers: typeof AlertConsumersTyped = AlertConsumersNonTyped; @@ -269,6 +263,8 @@ const TGridIntegratedComponent: React.FC = ({ [deletedEventIds.length, totalCount] ); + const hasAlerts = totalCountMinusDeleted > 0; + const nonDeletedEvents = useMemo(() => events.filter((e) => !deletedEventIds.includes(e._id)), [ deletedEventIds, events, @@ -300,7 +296,7 @@ const TGridIntegratedComponent: React.FC = ({ data-test-subj="events-viewer-panel" $isFullScreen={globalFullScreen} > - {isFirstUpdate.current && } + {isFirstUpdate.current && } {graphOverlay} @@ -325,61 +321,43 @@ const TGridIntegratedComponent: React.FC = ({ {!graphEventId && graphOverlay == null && ( - - - {totalCountMinusDeleted === 0 && loading === false && ( - - - - } - titleSize="s" - body={ -

- -

- } - /> - )} - {totalCountMinusDeleted > 0 && ( - - )} -
-
+ <> + {!hasAlerts && !loading && } + {hasAlerts && ( + + + + + + )} + )} )} diff --git a/x-pack/plugins/timelines/public/components/t_grid/shared/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/shared/index.tsx new file mode 100644 index 0000000000000..563e8224058c0 --- /dev/null +++ b/x-pack/plugins/timelines/public/components/t_grid/shared/index.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { + EuiPanel, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, + EuiImage, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import type { CoreStart } from '../../../../../../../src/core/public'; + +const heights = { + tall: 490, + short: 250, +}; + +export const TGridLoading: React.FC<{ height?: keyof typeof heights }> = ({ height = 'tall' }) => { + return ( + + + + + + + + ); +}; + +const panelStyle = { + maxWidth: 500, +}; + +export const TGridEmpty: React.FC<{ height?: keyof typeof heights }> = ({ height = 'tall' }) => { + const { http } = useKibana().services; + + return ( + + + + + + + + +

+ +

+
+

+ +

+
+
+ + + +
+
+
+
+
+ ); +}; diff --git a/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx index ee9b7be48df63..74dd8c01295be 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx @@ -4,8 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiLoadingContent } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFlexItem } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; import React, { useEffect, useMemo, useState, useRef } from 'react'; import styled from 'styled-components'; @@ -39,10 +38,16 @@ import type { State } from '../../../store/t_grid'; import { useTimelineEvents } from '../../../container'; import { StatefulBody } from '../body'; import { LastUpdatedAt } from '../..'; -import { SELECTOR_TIMELINE_GLOBAL_CONTAINER, UpdatedFlexItem, UpdatedFlexGroup } from '../styles'; +import { + SELECTOR_TIMELINE_GLOBAL_CONTAINER, + UpdatedFlexItem, + UpdatedFlexGroup, + FullWidthFlexGroup, +} from '../styles'; import { InspectButton, InspectButtonContainer } from '../../inspect'; import { useFetchIndex } from '../../../container/source'; import { AddToCaseAction } from '../../actions/timeline/cases/add_to_case_action'; +import { TGridLoading, TGridEmpty } from '../shared'; export const EVENTS_VIEWER_HEADER_HEIGHT = 90; // px const STANDALONE_ID = 'standalone-t-grid'; @@ -68,12 +73,6 @@ const EventsContainerLoading = styled.div.attrs(({ className = '' }) => ({ flex-direction: column; `; -const FullWidthFlexGroup = styled(EuiFlexGroup)<{ $visible: boolean }>` - overflow: hidden; - margin: 0; - display: ${({ $visible }) => ($visible ? 'flex' : 'none')}; -`; - const ScrollableFlexItem = styled(EuiFlexItem)` overflow: auto; `; @@ -255,6 +254,8 @@ const TGridStandaloneComponent: React.FC = ({ () => (totalCount > 0 ? totalCount - deletedEventIds.length : 0), [deletedEventIds.length, totalCount] ); + const hasAlerts = totalCountMinusDeleted > 0; + const activeCaseFlowId = useSelector((state: State) => tGridSelectors.activeCaseFlowId(state)); const selectedEvent = useMemo(() => { const matchedEvent = events.find((event) => event.ecs._id === activeCaseFlowId); @@ -338,14 +339,14 @@ const TGridStandaloneComponent: React.FC = ({ return ( - {isFirstUpdate.current && } + {isFirstUpdate.current && } {canQueryTimeline ? ( <> - + @@ -354,28 +355,9 @@ const TGridStandaloneComponent: React.FC = ({ - {totalCountMinusDeleted === 0 && loading === false && ( - - - - } - titleSize="s" - body={ -

- -

- } - /> - )} - {totalCountMinusDeleted > 0 && ( + {!hasAlerts && !loading && } + + {hasAlerts && ( ( }) )<{ $isVisible: boolean }>``; +export const FullWidthFlexGroup = styled(EuiFlexGroup)<{ $visible?: boolean }>` + overflow: hidden; + margin: 0; + min-height: 490px; + display: ${({ $visible = true }) => ($visible ? 'flex' : 'none')}; +`; + export const UpdatedFlexGroup = styled(EuiFlexGroup)` position: absolute; z-index: ${({ theme }) => theme.eui.euiZLevel1}; diff --git a/x-pack/plugins/timelines/public/methods/index.tsx b/x-pack/plugins/timelines/public/methods/index.tsx index 91802c4eb10e1..06bb1ae443216 100644 --- a/x-pack/plugins/timelines/public/methods/index.tsx +++ b/x-pack/plugins/timelines/public/methods/index.tsx @@ -6,7 +6,7 @@ */ import React, { lazy, Suspense } from 'react'; -import { EuiLoadingContent, EuiLoadingSpinner, EuiPanel } from '@elastic/eui'; +import { EuiLoadingSpinner } from '@elastic/eui'; import { I18nProvider } from '@kbn/i18n/react'; import type { Store } from 'redux'; import { Provider } from 'react-redux'; @@ -17,6 +17,7 @@ import type { LastUpdatedAtProps, LoadingPanelProps, FieldBrowserProps } from '. import type { AddToCaseActionProps } from '../components/actions/timeline/cases/add_to_case_action'; import { initialTGridState } from '../store/t_grid/reducer'; import { createStore } from '../store/t_grid'; +import { TGridLoading } from '../components/t_grid/shared'; const initializeStore = ({ store, @@ -51,13 +52,7 @@ export const getTGridLazy = ( ) => { initializeStore({ store, storage, setStore }); return ( - - -
- } - > + }> ); diff --git a/x-pack/test/plugin_functional/plugins/timelines_test/public/applications/timelines_test/index.tsx b/x-pack/test/plugin_functional/plugins/timelines_test/public/applications/timelines_test/index.tsx index adc10ae0a4161..a37c00144504d 100644 --- a/x-pack/test/plugin_functional/plugins/timelines_test/public/applications/timelines_test/index.tsx +++ b/x-pack/test/plugin_functional/plugins/timelines_test/public/applications/timelines_test/index.tsx @@ -11,6 +11,7 @@ import ReactDOM from 'react-dom'; import { AppMountParameters, CoreStart } from 'kibana/public'; import { I18nProvider } from '@kbn/i18n/react'; import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public'; +import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; import { TimelinesUIStart } from '../../../../../../../plugins/timelines/public'; import { DataPublicPluginStart } from '../../../../../../../../src/plugins/data/public'; @@ -60,39 +61,41 @@ const AppRoot = React.memo( - {(timelinesPluginSetup && - timelinesPluginSetup.getTGrid && - timelinesPluginSetup.getTGrid<'standalone'>({ - appId: 'securitySolution', - type: 'standalone', - casePermissions: { - read: true, - crud: true, - }, - columns: [], - indexNames: [], - deletedEventIds: [], - end: '', - footerText: 'Events', - filters: [], - hasAlertsCrudPermissions, - itemsPerPageOptions: [1, 2, 3], - loadingText: 'Loading events', - renderCellValue: () =>
test
, - sort: [], - leadingControlColumns: [], - trailingControlColumns: [], - query: { - query: '', - language: 'kuery', - }, - setRefetch, - start: '', - rowRenderers: [], - filterStatus: 'open', - unit: (n: number) => `${n}`, - })) ?? - null} + + {(timelinesPluginSetup && + timelinesPluginSetup.getTGrid && + timelinesPluginSetup.getTGrid<'standalone'>({ + appId: 'securitySolution', + type: 'standalone', + casePermissions: { + read: true, + crud: true, + }, + columns: [], + indexNames: [], + deletedEventIds: [], + end: '', + footerText: 'Events', + filters: [], + hasAlertsCrudPermissions, + itemsPerPageOptions: [1, 2, 3], + loadingText: 'Loading events', + renderCellValue: () =>
test
, + sort: [], + leadingControlColumns: [], + trailingControlColumns: [], + query: { + query: '', + language: 'kuery', + }, + setRefetch, + start: '', + rowRenderers: [], + filterStatus: 'open', + unit: (n: number) => `${n}`, + })) ?? + null} +
From bd9380b68cbef804b0ffa17297a4fc5875dbc6c9 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 7 Sep 2021 09:48:21 -0400 Subject: [PATCH 36/43] Fix ML alert not allowed in Uptime app. (#111180) (#111205) Co-authored-by: Justin Kambic --- x-pack/plugins/uptime/server/kibana.index.ts | 30 ++++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/uptime/server/kibana.index.ts b/x-pack/plugins/uptime/server/kibana.index.ts index c303c78273331..3b1001daf0515 100644 --- a/x-pack/plugins/uptime/server/kibana.index.ts +++ b/x-pack/plugins/uptime/server/kibana.index.ts @@ -46,7 +46,11 @@ export const initServerWithKibana = ( management: { insightsAndAlerting: ['triggersActions'], }, - alerting: ['xpack.uptime.alerts.tls', 'xpack.uptime.alerts.monitorStatus'], + alerting: [ + 'xpack.uptime.alerts.tls', + 'xpack.uptime.alerts.monitorStatus', + 'xpack.uptime.alerts.durationAnomaly', + ], privileges: { all: { app: ['uptime', 'kibana'], @@ -58,10 +62,18 @@ export const initServerWithKibana = ( }, alerting: { rule: { - all: ['xpack.uptime.alerts.tls', 'xpack.uptime.alerts.monitorStatus'], + all: [ + 'xpack.uptime.alerts.tls', + 'xpack.uptime.alerts.monitorStatus', + 'xpack.uptime.alerts.durationAnomaly', + ], }, alert: { - all: ['xpack.uptime.alerts.tls', 'xpack.uptime.alerts.monitorStatus'], + all: [ + 'xpack.uptime.alerts.tls', + 'xpack.uptime.alerts.monitorStatus', + 'xpack.uptime.alerts.durationAnomaly', + ], }, }, management: { @@ -79,10 +91,18 @@ export const initServerWithKibana = ( }, alerting: { rule: { - read: ['xpack.uptime.alerts.tls', 'xpack.uptime.alerts.monitorStatus'], + read: [ + 'xpack.uptime.alerts.tls', + 'xpack.uptime.alerts.monitorStatus', + 'xpack.uptime.alerts.durationAnomaly', + ], }, alert: { - read: ['xpack.uptime.alerts.tls', 'xpack.uptime.alerts.monitorStatus'], + read: [ + 'xpack.uptime.alerts.tls', + 'xpack.uptime.alerts.monitorStatus', + 'xpack.uptime.alerts.durationAnomaly', + ], }, }, management: { From e78c4ab5f14c3d27d890a4ddc518cd3ab9e9f578 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 7 Sep 2021 09:56:56 -0400 Subject: [PATCH 37/43] Add dev_docs for Fleet data model (#109476) (#111388) Co-authored-by: Josh Dover <1813008+joshdover@users.noreply.github.com> --- x-pack/plugins/fleet/dev_docs/data_model.md | 212 ++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 x-pack/plugins/fleet/dev_docs/data_model.md diff --git a/x-pack/plugins/fleet/dev_docs/data_model.md b/x-pack/plugins/fleet/dev_docs/data_model.md new file mode 100644 index 0000000000000..ec9fa031d09d3 --- /dev/null +++ b/x-pack/plugins/fleet/dev_docs/data_model.md @@ -0,0 +1,212 @@ +# Fleet Data Model + +The Fleet plugin has 3 sources of data that it reads and writes to, these large categories are: +- **Package Registry**: read-only data source for retrieving packages published by Elastic +- **`.fleet-*` Indices**: read & write data source for interacting with Elastic Agent policies, actions, and enrollment tokens +- **Saved Objects**: read & write data source for storing installed packages, configured policies, outputs, and other settings + +## Package Registry + +The package registry hosts all of the packages available for installation by Fleet. The Fleet plugin in Kibana interacts +with the registry exclusively through read-only JSON APIs for listing, searching, and download packages. Read more about +the available APIs in the [package-registry repository](https://github.com/elastic/package-registry). + +By default, the Fleet plugin will use Elastic's nightly 'snapshot' registry on the `master` branch, the 'staging' +registry on Kibana nightly snapshot builds, and the 'prod' registry for release builds. The registry that will be used +can be configured by setting the `xpack.fleet.registryUrl` in the `kibana.yml` file. + +The code that integrates with this registry API is contained in the +[`x-pack/plugins/fleet/server/services/epm/registry`](../server/services/epm/registry) directory. + +## `.fleet-*` Indices + +For any data that needs to be accessible by Fleet Service instances to push updates to, we write and read data +directly to a handful of `.fleet-` Elasticsearch indices. Fleet Server instances are configured with an API key that +has access only to these indices. + +In prior alpha versions of Fleet, this data was also stored in Saved Objects because Elastic Agent instances were +communicating directly with Kibana for policy updates. Once Fleet Server was introduced, that data was migrated to these +Elasticsearch indices to be readable by Fleet Server. + +### `.fleet-agents` index + +Each document in this index tracks an individual Elastic Agent's enrollment in the Fleet, which policy it is current +assigned to, its check in status, which packages are currently installed, and other metadata about the Agent. + +All of the code that interacts with this index is currently located in +[`x-pack/plugins/fleet/server/services/agents/crud.ts`](../server/services/agents/crud.ts) and the schema of these +documents is maintained by the `FleetServerAgent` TypeScript interface. + +Prior to Fleet Server, this data was stored in the `fleet-agents` Saved Object type which is now obsolete. + +### `.fleet-actions` index + +Each document in this index represents an action that was initiated by a user and needs to be processed by Fleet Server +and sent to any agents that it applies to. Actions can apply to one or more agents. There are different types of actions +that can be created such as policy changes, unenrollments, upgrades, etc. See the `AgentActionType` type for a complete +list. + +The total schema for actions is represented by the `FleetServerAgentAction` type. + +### `.fleet-actions-results` + +### `.fleet-servers` + +### `.fleet-artifacts` + +### `.fleet-entrollment-api-keys` + +### `.fleet-policies` + +### `.fleet-policies-leader` + +## Saved Object types + +The Fleet plugin leverages several Saved Object types to track metadata on install packages, agent policies, and more. +This document is intended to outline what each type is for, the primary places it's accessed from in the codebase, and +any caveats regarding the history of that saved object type. + +At this point in time, all types are currently: +- `hidden: false` +- `namespaceType: agnostic` +- `management.importableAndExportable: false` + +### `ingest_manager_settings` + +- Constant in code: `GLOBAL_SETTINGS_SAVED_OBJECT_TYPE` +- Introduced in ? +- Migrations: 7.10.0, 7.13.0 +- [Code Link](../server/saved_objects/index.ts#57) + +Tracks the Fleet server host addresses and whether or not the cluster has been shown the "add data" and +"fleet migration" notices in the UI. + +Can be accessed via the APIs exposed in the [server's settings service](../server/services/settings.ts). + + +### `ingest-agent-policies` + +- Constant in code: `AGENT_POLICY_SAVED_OBJECT_TYPE` +- Introduced in ? +- [Code Link](../server/saved_objects/index.ts#136) +- Migrations: 7.10.0, 7.12.0 +- References to other objects: + - `package_policies` - array of IDs that point to the specific integration instances for this agent policy (`ingest-package-policies`) + +The overall policy for a group of agents. Each policy consists of specific integration configurations for a group of +enrolled agents. + +### `ingest-package-policies` + +- Constant in code: `PACKAGE_POLICY_SAVED_OBJECT_TYPE` +- Introduced in ? +- [Code Link](../server/saved_objects/index.ts#212) +- Migrations: 7.10.0, 7.11.0, 7.12.0, 7.13.0, 7.14.0, 7.15.0 +- References to other objects: + - `policy_id` - ID that points to an agent policy (`ingest-agent-policies`) + - `output_id` - ID that points to an output (`ingest-outputs`) + +Contains the configuration for a specific instance of a package integration as configured for an agent policy. + +### `ingest-outputs` + +- Constant in code: `OUTPUT_SAVED_OBJECT_TYPE` +- Introduced in ? +- [Code Link](../server/saved_objects/index.ts#190) +- Migrations: 7.13.0 + +Contains configuration for ingest outputs that can be shared across multiple `ingest-package-policies`. Currently the UI +only exposes a single Elasticsearch output that will be used for all package policies, but in the future this may be +used for other types of outputs like separate monitoring clusters, Logstash, etc. + +### `epm-packages` + +- Constant in code: `PACKAGES_SAVED_OBJECT_TYPE` +- Introduced in ? +- [Code Link](../server/saved_objects/index.ts#279) +- Migrations: 7.14.0, 7.14.1 +- References to other objects: + - `installed_es` - array of assets installed into Elasticsearch + - `installed_es.id` - ID in Elasticsearch of an asset (eg. `logs-system.application-1.1.2`) + - `installed_es.type` - type of Elasticsearch asset (eg. `ingest_pipeline`) + - `installed_kibana` - array of assets that were installed into Kibana + - `installed_kibana.id` - Saved Object ID (eg. `system-01c54730-fee6-11e9-8405-516218e3d268`) + - `installed_kibana.type` - Saved Object type name (eg. `dashboard`) + - One caveat with this array is that the IDs are currently space-specific so if a package's assets were installed in + one space, they may not be visible in other spaces. We also do not keep track of which space these assets were + installed into. + - `package_assets` - array of original file contents of the package as it was installed + - `package_assets.id` - Saved Object ID for a `epm-package-assets` type + - `package_assets.type` - Saved Object type for the asset. As of now, only `epm-packages-assets` are supported. + +Contains metadata on an installed integration package including references to all assets installed in Kibana and +Elasticsearch. This allows for easy cleanup when a package is removed or upgraded. + +### `epm-packages-assets` + +- Constant in code: `ASSETS_SAVED_OBJECT_TYPE` +- Introduced in ? +- [Code Link](../server/saved_objects/index.ts#328) +- Migrations: +- References to other objects: + +Contains the raw file contents of a package, where each document represents one file from the original package. Storing +these as Saved Objects allows Fleet to install package contents when the package registry is down or unavailable. Also +allows for installing packages that were uploaded manually and are not from a package registry. The `asset_path` field +represents the relative file path of the file from the package contents +(eg. `system-1.1.2/data_stream/application/agent/stream/httpjson.yml.hbs`). + +### `fleet-preconfiguration-deletion-record` + +- Constant in code: `PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE` +- Introduced in ? +- [Code Link](../server/saved_objects/index.ts#328) +- Migrations: +- References to other objects: + - `id` - references the policy ID from the preconfiguration API + +Used as "tombstone record" to indicate that a package that was installed by default through preconfiguration was +explicitly deleted by user. Used to avoid recreating a preconfiguration policy that a user explicitly does not want. + +### `fleet-agents` + +**DEPRECATED in favor of `.fleet-agents` index.** + +- Constant in code: `AGENT_SAVED_OBJECT_TYPE` +- Introduced in ? +- [Code Link](../server/saved_objects/index.ts#76) +- Migrations: 7.10.0, 7.12.0 +- References to other objects: + - `policy_id` - ID that points to the policy (`ingest-agent-policies`) this agent is assigned to. + - `access_api_key_id` + - `default_api_key_id` + +Tracks an individual Elastic Agent's enrollment in the Fleet, which policy it is current assigned to, its check in +status, which packages are currently installed, and other metadata about the Agent. + +### `fleet-agent-actions` + +**DEPRECATED in favor of `.fleet-agent-actions` index.** + +- Constant in code: `AGENT_ACTION_SAVED_OBJECT_TYPE` +- Introduced in ? +- [Code Link](../server/saved_objects/index.ts#113) +- Migrations: 7.10.0 +- References to other objects: + - `agent_id` - ID that points to the agent for this action (`fleet-agents`) + - `policy_id`- ID that points to the policy for this action (`ingest-agent-policies`) + + +### `fleet-enrollment-api-keys` + +**DEPRECATED in favor of `.fleet-enrollment-api-keys` index.** + +- Constant in code: `ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE` +- Introduced in ? +- [Code Link](../server/saved_objects/index.ts#166) +- Migrations: 7.10.0 +- References to other objects: + - `api_key_id` + - `policy_id` - ID that points to an agent policy (`ingest-agent-policies`) + +Contains an enrollment key that can be used to enroll a new agent in a specific agent policy. \ No newline at end of file From 7ccf7fe25712196170c7dde94000f5ccb4b9a0f8 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 7 Sep 2021 10:47:58 -0400 Subject: [PATCH 38/43] [APM] Deprecate `apm_oss.indexPattern` config option (#111133) (#111364) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: SĂžren Louv-Jansen --- src/plugins/apm_oss/server/index.ts | 5 +- .../apm/public/services/rest/index_pattern.ts | 7 --- x-pack/plugins/apm/server/index.test.ts | 2 - x-pack/plugins/apm/server/index.ts | 5 -- .../create_static_index_pattern.test.ts | 50 ++++++++------- .../create_static_index_pattern.ts | 61 +++++++++++-------- .../get_apm_index_pattern_title.test.ts | 35 +++++++++++ .../get_apm_index_pattern_title.ts | 14 +++-- .../get_dynamic_index_pattern.ts | 9 ++- x-pack/plugins/apm/server/plugin.ts | 42 ++++++------- .../apm/server/routes/index_pattern.ts | 14 +---- .../apm/server/tutorial/envs/on_prem.ts | 35 +++++------ x-pack/plugins/apm/server/tutorial/index.ts | 25 ++++---- 13 files changed, 160 insertions(+), 144 deletions(-) create mode 100644 x-pack/plugins/apm/server/lib/index_pattern/get_apm_index_pattern_title.test.ts diff --git a/src/plugins/apm_oss/server/index.ts b/src/plugins/apm_oss/server/index.ts index 7b16c42f4c9b9..bf6baf1876074 100644 --- a/src/plugins/apm_oss/server/index.ts +++ b/src/plugins/apm_oss/server/index.ts @@ -10,7 +10,10 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { ConfigDeprecationProvider, PluginInitializerContext } from '../../../core/server'; import { APMOSSPlugin } from './plugin'; -const deprecations: ConfigDeprecationProvider = ({ unused }) => [unused('fleetMode')]; +const deprecations: ConfigDeprecationProvider = ({ unused }) => [ + unused('fleetMode'), + unused('indexPattern'), +]; export const config = { schema: schema.object({ diff --git a/x-pack/plugins/apm/public/services/rest/index_pattern.ts b/x-pack/plugins/apm/public/services/rest/index_pattern.ts index bc321d700a7a6..0bb5839759f64 100644 --- a/x-pack/plugins/apm/public/services/rest/index_pattern.ts +++ b/x-pack/plugins/apm/public/services/rest/index_pattern.ts @@ -13,10 +13,3 @@ export const createStaticIndexPattern = async () => { signal: null, }); }; - -export const getApmIndexPatternTitle = async () => { - return await callApmApi({ - endpoint: 'GET /api/apm/index_pattern/title', - signal: null, - }); -}; diff --git a/x-pack/plugins/apm/server/index.test.ts b/x-pack/plugins/apm/server/index.test.ts index 56c9825db5a5c..be93557fea6fc 100644 --- a/x-pack/plugins/apm/server/index.test.ts +++ b/x-pack/plugins/apm/server/index.test.ts @@ -18,7 +18,6 @@ describe('mergeConfigs', () => { spanIndices: 'apm-*-span-*', errorIndices: 'apm-*-error-*', metricsIndices: 'apm-*-metric-*', - indexPattern: 'apm-*', } as APMOSSConfig; const apmConfig = { @@ -30,7 +29,6 @@ describe('mergeConfigs', () => { expect(mergeConfigs(apmOssConfig, apmConfig)).toEqual({ 'apm_oss.errorIndices': 'logs-apm*,apm-*-error-*', - 'apm_oss.indexPattern': 'traces-apm*,logs-apm*,metrics-apm*,apm-*', 'apm_oss.metricsIndices': 'metrics-apm*,apm-*-metric-*', 'apm_oss.spanIndices': 'traces-apm*,apm-*-span-*', 'apm_oss.transactionIndices': 'traces-apm*,apm-*-transaction-*', diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts index 5b97173601950..db62cc7adae2b 100644 --- a/x-pack/plugins/apm/server/index.ts +++ b/x-pack/plugins/apm/server/index.ts @@ -72,7 +72,6 @@ export function mergeConfigs( 'apm_oss.metricsIndices': apmOssConfig.metricsIndices, 'apm_oss.sourcemapIndices': apmOssConfig.sourcemapIndices, 'apm_oss.onboardingIndices': apmOssConfig.onboardingIndices, - 'apm_oss.indexPattern': apmOssConfig.indexPattern, /* eslint-enable @typescript-eslint/naming-convention */ 'xpack.apm.serviceMapEnabled': apmConfig.serviceMapEnabled, 'xpack.apm.serviceMapFingerprintBucketSize': @@ -117,10 +116,6 @@ export function mergeConfigs( 'apm_oss.metricsIndices' ] = `metrics-apm*,${mergedConfig['apm_oss.metricsIndices']}`; - mergedConfig[ - 'apm_oss.indexPattern' - ] = `traces-apm*,logs-apm*,metrics-apm*,${mergedConfig['apm_oss.indexPattern']}`; - return mergedConfig; } diff --git a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.test.ts b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.test.ts index ef869a0ed6cfa..35f5721eee05c 100644 --- a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.test.ts +++ b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.test.ts @@ -5,28 +5,36 @@ * 2.0. */ +/* eslint-disable @typescript-eslint/naming-convention */ import { createStaticIndexPattern } from './create_static_index_pattern'; import { Setup } from '../helpers/setup_request'; import * as HistoricalAgentData from '../services/get_services/has_historical_agent_data'; import { InternalSavedObjectsClient } from '../helpers/get_internal_saved_objects_client'; import { APMConfig } from '../..'; -function getMockSavedObjectsClient() { +function getMockSavedObjectsClient(existingIndexPatternTitle: string) { return ({ get: jest.fn(() => ({ attributes: { - title: 'apm-*', + title: existingIndexPatternTitle, }, })), create: jest.fn(), } as unknown) as InternalSavedObjectsClient; } +const setup = ({ + indices: { + 'apm_oss.transactionIndices': 'apm-*-transaction-*', + 'apm_oss.spanIndices': 'apm-*-span-*', + 'apm_oss.errorIndices': 'apm-*-error-*', + 'apm_oss.metricsIndices': 'apm-*-metrics-*', + }, +} as unknown) as Setup; + describe('createStaticIndexPattern', () => { it(`should not create index pattern if 'xpack.apm.autocreateApmIndexPattern=false'`, async () => { - const setup = {} as Setup; - - const savedObjectsClient = getMockSavedObjectsClient(); + const savedObjectsClient = getMockSavedObjectsClient('apm-*'); await createStaticIndexPattern({ setup, config: { 'xpack.apm.autocreateApmIndexPattern': false } as APMConfig, @@ -37,14 +45,12 @@ describe('createStaticIndexPattern', () => { }); it(`should not create index pattern if no APM data is found`, async () => { - const setup = {} as Setup; - // does not have APM data jest .spyOn(HistoricalAgentData, 'hasHistoricalAgentData') .mockResolvedValue(false); - const savedObjectsClient = getMockSavedObjectsClient(); + const savedObjectsClient = getMockSavedObjectsClient('apm-*'); await createStaticIndexPattern({ setup, @@ -56,14 +62,12 @@ describe('createStaticIndexPattern', () => { }); it(`should create index pattern`, async () => { - const setup = {} as Setup; - // does have APM data jest .spyOn(HistoricalAgentData, 'hasHistoricalAgentData') .mockResolvedValue(true); - const savedObjectsClient = getMockSavedObjectsClient(); + const savedObjectsClient = getMockSavedObjectsClient('apm-*'); await createStaticIndexPattern({ setup, @@ -75,23 +79,20 @@ describe('createStaticIndexPattern', () => { expect(savedObjectsClient.create).toHaveBeenCalled(); }); - it(`should upgrade an index pattern if 'apm_oss.indexPattern' does not match title`, async () => { - const setup = {} as Setup; - + it(`should overwrite the index pattern if the new index pattern title does not match the old index pattern title`, async () => { // does have APM data jest .spyOn(HistoricalAgentData, 'hasHistoricalAgentData') .mockResolvedValue(true); - const savedObjectsClient = getMockSavedObjectsClient(); - const apmIndexPatternTitle = 'traces-apm*,logs-apm*,metrics-apm*,apm-*'; + const savedObjectsClient = getMockSavedObjectsClient('apm-*'); + const expectedIndexPatternTitle = + 'apm-*-transaction-*,apm-*-span-*,apm-*-error-*,apm-*-metrics-*'; await createStaticIndexPattern({ setup, config: { 'xpack.apm.autocreateApmIndexPattern': true, - // eslint-disable-next-line @typescript-eslint/naming-convention - 'apm_oss.indexPattern': apmIndexPatternTitle, } as APMConfig, savedObjectsClient, spaceId: 'default', @@ -101,29 +102,26 @@ describe('createStaticIndexPattern', () => { expect(savedObjectsClient.create).toHaveBeenCalled(); // @ts-ignore expect(savedObjectsClient.create.mock.calls[0][1].title).toBe( - apmIndexPatternTitle + expectedIndexPatternTitle ); // @ts-ignore expect(savedObjectsClient.create.mock.calls[0][2].overwrite).toBe(true); }); - it(`should not upgrade an index pattern if 'apm_oss.indexPattern' already match existing title`, async () => { - const setup = {} as Setup; - + it(`should not overwrite an index pattern if the new index pattern title matches the old index pattern title`, async () => { // does have APM data jest .spyOn(HistoricalAgentData, 'hasHistoricalAgentData') .mockResolvedValue(true); - const savedObjectsClient = getMockSavedObjectsClient(); - const apmIndexPatternTitle = 'apm-*'; + const savedObjectsClient = getMockSavedObjectsClient( + 'apm-*-transaction-*,apm-*-span-*,apm-*-error-*,apm-*-metrics-*' + ); await createStaticIndexPattern({ setup, config: { 'xpack.apm.autocreateApmIndexPattern': true, - // eslint-disable-next-line @typescript-eslint/naming-convention - 'apm_oss.indexPattern': apmIndexPatternTitle, } as APMConfig, savedObjectsClient, spaceId: 'default', diff --git a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts index 5dbee59b4ce86..414414c6bfe65 100644 --- a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts +++ b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts @@ -45,29 +45,12 @@ export async function createStaticIndexPattern({ return false; } - const apmIndexPatternTitle = getApmIndexPatternTitle(config); - - if (!overwrite) { - try { - const { - attributes: { title: existingApmIndexPatternTitle }, - }: { - attributes: ApmIndexPatternAttributes; - } = await savedObjectsClient.get( - 'index-pattern', - APM_STATIC_INDEX_PATTERN_ID - ); - // if the existing index pattern does not matches the new one, force an update - if (existingApmIndexPatternTitle !== apmIndexPatternTitle) { - overwrite = true; - } - } catch (e) { - // if the index pattern (saved object) is not found, then we can continue with creation - if (!SavedObjectsErrorHelpers.isNotFoundError(e)) { - throw e; - } - } - } + const apmIndexPatternTitle = getApmIndexPatternTitle(setup.indices); + const forceOverwrite = await getForceOverwrite({ + apmIndexPatternTitle, + overwrite, + savedObjectsClient, + }); try { await withApmSpan('create_index_pattern_saved_object', () => @@ -79,7 +62,7 @@ export async function createStaticIndexPattern({ }, { id: APM_STATIC_INDEX_PATTERN_ID, - overwrite, + overwrite: forceOverwrite ? true : overwrite, namespace: spaceId, } ) @@ -95,3 +78,33 @@ export async function createStaticIndexPattern({ } }); } + +// force an overwrite of the index pattern if the index pattern has been changed +async function getForceOverwrite({ + savedObjectsClient, + overwrite, + apmIndexPatternTitle, +}: { + savedObjectsClient: InternalSavedObjectsClient; + overwrite: boolean; + apmIndexPatternTitle: string; +}) { + if (!overwrite) { + try { + const existingIndexPattern = await savedObjectsClient.get( + 'index-pattern', + APM_STATIC_INDEX_PATTERN_ID + ); + + // if the existing index pattern does not matches the new one, force an update + return existingIndexPattern.attributes.title !== apmIndexPatternTitle; + } catch (e) { + // ignore exception if the index pattern (saved object) is not found + if (SavedObjectsErrorHelpers.isNotFoundError(e)) { + return false; + } + + throw e; + } + } +} diff --git a/x-pack/plugins/apm/server/lib/index_pattern/get_apm_index_pattern_title.test.ts b/x-pack/plugins/apm/server/lib/index_pattern/get_apm_index_pattern_title.test.ts new file mode 100644 index 0000000000000..8103630157584 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/index_pattern/get_apm_index_pattern_title.test.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* eslint-disable @typescript-eslint/naming-convention */ + +import { ApmIndicesConfig } from '../settings/apm_indices/get_apm_indices'; +import { getApmIndexPatternTitle } from './get_apm_index_pattern_title'; + +describe('getApmIndexPatternTitle', () => { + it('returns an index pattern title by combining existing indicies', () => { + const title = getApmIndexPatternTitle({ + 'apm_oss.transactionIndices': 'apm-*-transaction-*', + 'apm_oss.spanIndices': 'apm-*-span-*', + 'apm_oss.errorIndices': 'apm-*-error-*', + 'apm_oss.metricsIndices': 'apm-*-metrics-*', + } as ApmIndicesConfig); + expect(title).toBe( + 'apm-*-transaction-*,apm-*-span-*,apm-*-error-*,apm-*-metrics-*' + ); + }); + + it('removes duplicates', () => { + const title = getApmIndexPatternTitle({ + 'apm_oss.transactionIndices': 'apm-*', + 'apm_oss.spanIndices': 'apm-*', + 'apm_oss.errorIndices': 'apm-*', + 'apm_oss.metricsIndices': 'apm-*', + } as ApmIndicesConfig); + expect(title).toBe('apm-*'); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/index_pattern/get_apm_index_pattern_title.ts b/x-pack/plugins/apm/server/lib/index_pattern/get_apm_index_pattern_title.ts index faec64c798c7d..e65f200130e9a 100644 --- a/x-pack/plugins/apm/server/lib/index_pattern/get_apm_index_pattern_title.ts +++ b/x-pack/plugins/apm/server/lib/index_pattern/get_apm_index_pattern_title.ts @@ -5,10 +5,14 @@ * 2.0. */ -import { APMRouteHandlerResources } from '../../routes/typings'; +import { uniq } from 'lodash'; +import { ApmIndicesConfig } from '../settings/apm_indices/get_apm_indices'; -export function getApmIndexPatternTitle( - config: APMRouteHandlerResources['config'] -) { - return config['apm_oss.indexPattern']; +export function getApmIndexPatternTitle(apmIndicesConfig: ApmIndicesConfig) { + return uniq([ + apmIndicesConfig['apm_oss.transactionIndices'], + apmIndicesConfig['apm_oss.spanIndices'], + apmIndicesConfig['apm_oss.errorIndices'], + apmIndicesConfig['apm_oss.metricsIndices'], + ]).join(','); } diff --git a/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts b/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts index 8bbc22fbf289d..19bd801a1f0e1 100644 --- a/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts +++ b/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts @@ -11,6 +11,8 @@ import { } from '../../../../../../src/plugins/data/server'; import { APMRouteHandlerResources } from '../../routes/typings'; import { withApmSpan } from '../../utils/with_apm_span'; +import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; +import { getApmIndexPatternTitle } from './get_apm_index_pattern_title'; export interface IndexPatternTitleAndFields { title: string; @@ -18,14 +20,17 @@ export interface IndexPatternTitleAndFields { fields: FieldDescriptor[]; } -// TODO: this is currently cached globally. In the future we might want to cache this per user export const getDynamicIndexPattern = ({ config, context, logger, }: Pick) => { return withApmSpan('get_dynamic_index_pattern', async () => { - const indexPatternTitle = config['apm_oss.indexPattern']; + const apmIndicies = await getApmIndices({ + savedObjectsClient: context.core.savedObjects.client, + config, + }); + const indexPatternTitle = getApmIndexPatternTitle(apmIndicies); const indexPatternsFetcher = new IndexPatternsFetcher( context.core.elasticsearch.client.asCurrentUser diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index fadeae338cbdb..14c8bd9087b89 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -88,13 +88,12 @@ export class APMPlugin plugins.apmOss.config, this.initContext.config.get() ); - this.currentConfig = currentConfig; if ( plugins.taskManager && plugins.usageCollection && - this.currentConfig['xpack.apm.telemetryCollectionEnabled'] + currentConfig['xpack.apm.telemetryCollectionEnabled'] ) { createApmTelemetry({ core, @@ -156,21 +155,22 @@ export class APMPlugin }; }) as APMRouteHandlerResources['plugins']; - plugins.home?.tutorials.registerTutorial( - tutorialProvider({ - isEnabled: this.currentConfig['xpack.apm.ui.enabled'], - indexPatternTitle: this.currentConfig['apm_oss.indexPattern'], - cloud: plugins.cloud, - isFleetPluginEnabled: !isEmpty(resourcePlugins.fleet), - indices: { - errorIndices: this.currentConfig['apm_oss.errorIndices'], - metricsIndices: this.currentConfig['apm_oss.metricsIndices'], - onboardingIndices: this.currentConfig['apm_oss.onboardingIndices'], - sourcemapIndices: this.currentConfig['apm_oss.sourcemapIndices'], - transactionIndices: this.currentConfig['apm_oss.transactionIndices'], - }, - }) - ); + const boundGetApmIndices = async () => + getApmIndices({ + savedObjectsClient: await getInternalSavedObjectsClient(core), + config: await mergedConfig$.pipe(take(1)).toPromise(), + }); + + boundGetApmIndices().then((indices) => { + plugins.home?.tutorials.registerTutorial( + tutorialProvider({ + apmConfig: currentConfig, + apmIndices: indices, + cloud: plugins.cloud, + isFleetPluginEnabled: !isEmpty(resourcePlugins.fleet), + }) + ); + }); const telemetryUsageCounter = resourcePlugins.usageCollection?.setup.createUsageCounter( APM_SERVER_FEATURE_ID @@ -189,12 +189,6 @@ export class APMPlugin telemetryUsageCounter, }); - const boundGetApmIndices = async () => - getApmIndices({ - savedObjectsClient: await getInternalSavedObjectsClient(core), - config: await mergedConfig$.pipe(take(1)).toPromise(), - }); - if (plugins.alerting) { registerApmAlerts({ ruleDataClient, @@ -208,7 +202,7 @@ export class APMPlugin registerFleetPolicyCallbacks({ plugins: resourcePlugins, ruleDataClient, - config: this.currentConfig, + config: currentConfig, logger: this.logger, }); diff --git a/x-pack/plugins/apm/server/routes/index_pattern.ts b/x-pack/plugins/apm/server/routes/index_pattern.ts index 190baf3bbc270..c957e828bf12a 100644 --- a/x-pack/plugins/apm/server/routes/index_pattern.ts +++ b/x-pack/plugins/apm/server/routes/index_pattern.ts @@ -8,7 +8,6 @@ import { createStaticIndexPattern } from '../lib/index_pattern/create_static_index_pattern'; import { createApmServerRouteRepository } from './create_apm_server_route_repository'; import { setupRequest } from '../lib/helpers/setup_request'; -import { getApmIndexPatternTitle } from '../lib/index_pattern/get_apm_index_pattern_title'; import { getDynamicIndexPattern } from '../lib/index_pattern/get_dynamic_index_pattern'; import { createApmServerRoute } from './create_apm_server_route'; @@ -56,17 +55,6 @@ const dynamicIndexPatternRoute = createApmServerRoute({ }, }); -const indexPatternTitleRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/index_pattern/title', - options: { tags: ['access:apm'] }, - handler: async ({ config }) => { - return { - indexPatternTitle: getApmIndexPatternTitle(config), - }; - }, -}); - export const indexPatternRouteRepository = createApmServerRouteRepository() .add(staticIndexPatternRoute) - .add(dynamicIndexPatternRoute) - .add(indexPatternTitleRoute); + .add(dynamicIndexPatternRoute); diff --git a/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts b/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts index 38c8dbfcbe8ba..fb9fbae33ac82 100644 --- a/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts +++ b/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; +import { APMConfig } from '../..'; import { INSTRUCTION_VARIANT, InstructionsSchema, @@ -33,18 +34,16 @@ import { } from '../../../common/tutorial/instructions/apm_server_instructions'; export function onPremInstructions({ - errorIndices, - transactionIndices, - metricsIndices, - sourcemapIndices, - onboardingIndices, + apmConfig, isFleetPluginEnabled, }: { - errorIndices: string; - transactionIndices: string; - metricsIndices: string; - sourcemapIndices: string; - onboardingIndices: string; + apmConfig: Pick< + APMConfig, + | 'apm_oss.errorIndices' + | 'apm_oss.transactionIndices' + | 'apm_oss.metricsIndices' + | 'apm_oss.onboardingIndices' + >; isFleetPluginEnabled: boolean; }): InstructionsSchema { const EDIT_CONFIG = createEditConfig(); @@ -145,7 +144,7 @@ export function onPremInstructions({ } ), esHitsCheck: { - index: onboardingIndices, + index: apmConfig['apm_oss.onboardingIndices'], query: { bool: { filter: [ @@ -238,22 +237,16 @@ export function onPremInstructions({ ), esHitsCheck: { index: [ - errorIndices, - transactionIndices, - metricsIndices, - sourcemapIndices, + apmConfig['apm_oss.errorIndices'], + apmConfig['apm_oss.transactionIndices'], + apmConfig['apm_oss.metricsIndices'], ], query: { bool: { filter: [ { terms: { - 'processor.event': [ - 'error', - 'transaction', - 'metric', - 'sourcemap', - ], + 'processor.event': ['error', 'transaction', 'metric'], }, }, { range: { 'observer.version_major': { gte: 7 } } }, diff --git a/x-pack/plugins/apm/server/tutorial/index.ts b/x-pack/plugins/apm/server/tutorial/index.ts index edf056a6d1be4..78dd1110e7c62 100644 --- a/x-pack/plugins/apm/server/tutorial/index.ts +++ b/x-pack/plugins/apm/server/tutorial/index.ts @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; +import { APMConfig } from '..'; import { ArtifactsSchema, TutorialsCategory, @@ -13,6 +14,8 @@ import { } from '../../../../../src/plugins/home/server'; import { CloudSetup } from '../../../cloud/server'; import { APM_STATIC_INDEX_PATTERN_ID } from '../../common/index_pattern_constants'; +import { getApmIndexPatternTitle } from '../lib/index_pattern/get_apm_index_pattern_title'; +import { ApmIndicesConfig } from '../lib/settings/apm_indices/get_apm_indices'; import { createElasticCloudInstructions } from './envs/elastic_cloud'; import { onPremInstructions } from './envs/on_prem'; import apmIndexPattern from './index_pattern.json'; @@ -24,24 +27,18 @@ const apmIntro = i18n.translate('xpack.apm.tutorial.introduction', { const moduleName = 'apm'; export const tutorialProvider = ({ - isEnabled, - indexPatternTitle, - indices, + apmConfig, + apmIndices, cloud, isFleetPluginEnabled, }: { - isEnabled: boolean; - indexPatternTitle: string; + apmConfig: APMConfig; + apmIndices: ApmIndicesConfig; cloud?: CloudSetup; - indices: { - errorIndices: string; - transactionIndices: string; - metricsIndices: string; - sourcemapIndices: string; - onboardingIndices: string; - }; isFleetPluginEnabled: boolean; }) => () => { + const indexPatternTitle = getApmIndexPatternTitle(apmIndices); + const savedObjects = [ { ...apmIndexPattern, @@ -68,7 +65,7 @@ export const tutorialProvider = ({ ], }; - if (isEnabled) { + if (apmConfig['xpack.apm.ui.enabled']) { // @ts-expect-error artifacts.application is readonly artifacts.application = { path: '/app/apm', @@ -106,7 +103,7 @@ It allows you to monitor the performance of thousands of applications in real ti euiIconType: 'apmApp', artifacts, customStatusCheckName: 'apm_fleet_server_status_check', - onPrem: onPremInstructions({ ...indices, isFleetPluginEnabled }), + onPrem: onPremInstructions({ apmConfig, isFleetPluginEnabled }), elasticCloud: createElasticCloudInstructions(cloud), previewImagePath: '/plugins/apm/assets/apm.png', savedObjects, From 17a5743dc1f1788a9381be598e3c31c489a9b42c Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 7 Sep 2021 10:55:36 -0400 Subject: [PATCH 39/43] [Fleet] Add icons to Integrations global search results (#111131) (#111373) Co-authored-by: Josh Dover <1813008+joshdover@users.noreply.github.com> --- .../fleet/public/search_provider.test.ts | 40 ++++++++++++++++++- .../plugins/fleet/public/search_provider.ts | 35 +++++++++++----- .../public/components/search_bar.tsx | 4 +- 3 files changed, 67 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/fleet/public/search_provider.test.ts b/x-pack/plugins/fleet/public/search_provider.test.ts index 8eee18710d477..ef6bda44d512b 100644 --- a/x-pack/plugins/fleet/public/search_provider.test.ts +++ b/x-pack/plugins/fleet/public/search_provider.test.ts @@ -10,7 +10,7 @@ import { NEVER } from 'rxjs'; import { coreMock } from 'src/core/public/mocks'; -import { createPackageSearchProvider } from './search_provider'; +import { createPackageSearchProvider, toSearchResult } from './search_provider'; import type { GetPackagesResponse } from './types'; jest.mock('./hooks/use_request/epm', () => { @@ -286,4 +286,42 @@ describe('Package search provider', () => { }); }); }); + + describe('toSearchResult', () => { + let startMock: ReturnType; + + beforeEach(() => { + startMock = coreMock.createStart(); + }); + + it('uses svg icon if available', () => { + const pkg = { + ...testResponse[0], + icons: [{ type: 'image/svg+xml', src: '/img_nginx.svg', path: '' }], + }; + const { icon } = toSearchResult(pkg, startMock.application, startMock.http.basePath); + expect(icon).toMatchInlineSnapshot(`"/api/fleet/epm/packages/test/test/img_nginx.svg"`); + }); + + it('prepends base path to svg URL', () => { + startMock = coreMock.createStart({ basePath: '/foo' }); + const pkg = { + ...testResponse[0], + icons: [{ type: 'image/svg+xml', src: '/img_nginx.svg', path: '' }], + }; + const { icon } = toSearchResult(pkg, startMock.application, startMock.http.basePath); + expect(icon).toMatchInlineSnapshot(`"/foo/api/fleet/epm/packages/test/test/img_nginx.svg"`); + }); + + // ICON_TYPES is empty in EUI: https://github.com/elastic/eui/issues/5138 + it.skip('uses eui icon type as fallback', () => { + const pkg = { + ...testResponse[0], + name: 'elasticsearch', + icons: [{ type: 'image/jpg', src: '/img_nginx.svg', path: '' }], + }; + const { icon } = toSearchResult(pkg, startMock.application, startMock.http.basePath); + expect(icon).toMatchInlineSnapshot(`"logoElasticsearch"`); + }); + }); }); diff --git a/x-pack/plugins/fleet/public/search_provider.ts b/x-pack/plugins/fleet/public/search_provider.ts index 5f53c0a8e44ba..403abf89715c8 100644 --- a/x-pack/plugins/fleet/public/search_provider.ts +++ b/x-pack/plugins/fleet/public/search_provider.ts @@ -4,21 +4,23 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { CoreSetup, CoreStart, ApplicationStart } from 'src/core/public'; +import type { CoreSetup, CoreStart, ApplicationStart, IBasePath } from 'src/core/public'; import type { Observable } from 'rxjs'; import { from, of, combineLatest } from 'rxjs'; import { map, shareReplay, takeUntil } from 'rxjs/operators'; +import { ICON_TYPES } from '@elastic/eui'; + import type { GlobalSearchResultProvider, GlobalSearchProviderResult, } from '../../global_search/public'; -import { INTEGRATIONS_PLUGIN_ID } from '../common'; +import { epmRouteService, INTEGRATIONS_PLUGIN_ID } from '../common'; import { sendGetPackages } from './hooks'; -import type { GetPackagesResponse } from './types'; +import type { GetPackagesResponse, PackageListItem } from './types'; import { pagePathGetters } from './constants'; const packageType = 'integration'; @@ -34,16 +36,31 @@ const createPackages$ = () => shareReplay(1) ); -const toSearchResult = ( - pkg: GetPackagesResponse['response'][number], - application: ApplicationStart -) => { +const getEuiIconType = (pkg: PackageListItem, basePath: IBasePath): string | undefined => { + const pkgIcon = pkg.icons?.find((icon) => icon.type === 'image/svg+xml'); + if (!pkgIcon) { + // If no valid SVG is available, attempt to fallback to built-in EUI icons + return ICON_TYPES.find((key) => key.toLowerCase() === `logo${pkg.name}`); + } + + return basePath.prepend( + epmRouteService.getFilePath(`/package/${pkg.name}/${pkg.version}${pkgIcon.src}`) + ); +}; + +/** Exported for testing only @internal */ +export const toSearchResult = ( + pkg: PackageListItem, + application: ApplicationStart, + basePath: IBasePath +): GlobalSearchProviderResult => { const pkgkey = `${pkg.name}-${pkg.version}`; return { id: pkgkey, type: packageType, title: pkg.title, score: 80, + icon: getEuiIconType(pkg, basePath), url: { // prettier-ignore path: `${application.getUrlForApp(INTEGRATIONS_PLUGIN_ID)}${pagePathGetters.integration_details_overview({ pkgkey })[1]}`, @@ -95,13 +112,13 @@ export const createPackageSearchProvider = (core: CoreSetup): GlobalSearchResult return packagesResponse .flatMap( includeAllPackages - ? (pkg) => toSearchResult(pkg, coreStart.application) + ? (pkg) => toSearchResult(pkg, coreStart.application, coreStart.http.basePath) : (pkg) => { if (!term || !pkg.title.toLowerCase().includes(term)) { return []; } - return toSearchResult(pkg, coreStart.application); + return toSearchResult(pkg, coreStart.application, coreStart.http.basePath); } ) .slice(0, maxResults); diff --git a/x-pack/plugins/global_search_bar/public/components/search_bar.tsx b/x-pack/plugins/global_search_bar/public/components/search_bar.tsx index 5234b4e7b0ad5..c459b2c045681 100644 --- a/x-pack/plugins/global_search_bar/public/components/search_bar.tsx +++ b/x-pack/plugins/global_search_bar/public/components/search_bar.tsx @@ -135,8 +135,8 @@ const resultToOption = ( ): EuiSelectableTemplateSitewideOption => { const { id, title, url, icon, type, meta = {} } = result; const { tagIds = [], categoryLabel = '' } = meta as { tagIds: string[]; categoryLabel: string }; - // only displaying icons for applications - const useIcon = type === 'application'; + // only displaying icons for applications and integrations + const useIcon = type === 'application' || type === 'integration'; const option: EuiSelectableTemplateSitewideOption = { key: id, label: title, From d4dbce0e1e2deb59093cab2baf8eef1d3661f8a3 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Tue, 7 Sep 2021 07:57:21 -0700 Subject: [PATCH 40/43] Skip flaky UA Backup step polling test. --- .../overview/backup_step/backup_step.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/backup_step/backup_step.test.tsx b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/backup_step/backup_step.test.tsx index c99aa7031aa3d..39a94ec458be4 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/backup_step/backup_step.test.tsx +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/backup_step/backup_step.test.tsx @@ -123,7 +123,8 @@ describe('Overview - Backup Step', () => { }); }); - test('polls for new status', async () => { + // FLAKY: https://github.com/elastic/kibana/issues/111255 + test.skip('polls for new status', async () => { // The behavior we're testing involves state changes over time, so we need finer control over // timing. jest.useFakeTimers(); From 43d55d95dd66c9c8a56d5344d17bf5caba775fae Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 7 Sep 2021 12:15:29 -0400 Subject: [PATCH 41/43] add hasHeaderBanner$ API to chrome (#111248) (#111391) * add hasHeaderBanner$ API to chrome * update generated doc Co-authored-by: Pierre Gayvallet --- ...-core-public.chromestart.hasheaderbanner_.md | 17 +++++++++++++++++ .../kibana-plugin-core-public.chromestart.md | 1 + .../kibana-plugin-core-public.doclinksstart.md | 4 +--- src/core/public/chrome/chrome_service.mock.ts | 2 ++ src/core/public/chrome/chrome_service.test.ts | 13 +++++++++++++ src/core/public/chrome/chrome_service.tsx | 7 +++++++ src/core/public/chrome/types.ts | 5 +++++ src/core/public/public.api.md | 1 + 8 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 docs/development/core/public/kibana-plugin-core-public.chromestart.hasheaderbanner_.md diff --git a/docs/development/core/public/kibana-plugin-core-public.chromestart.hasheaderbanner_.md b/docs/development/core/public/kibana-plugin-core-public.chromestart.hasheaderbanner_.md new file mode 100644 index 0000000000000..6ce0671eb5230 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.chromestart.hasheaderbanner_.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ChromeStart](./kibana-plugin-core-public.chromestart.md) > [hasHeaderBanner$](./kibana-plugin-core-public.chromestart.hasheaderbanner_.md) + +## ChromeStart.hasHeaderBanner$() method + +Get an observable of the current header banner presence state. + +Signature: + +```typescript +hasHeaderBanner$(): Observable; +``` +Returns: + +`Observable` + diff --git a/docs/development/core/public/kibana-plugin-core-public.chromestart.md b/docs/development/core/public/kibana-plugin-core-public.chromestart.md index 7285b4a00a0ec..ffc77dd653c0f 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromestart.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromestart.md @@ -57,6 +57,7 @@ core.chrome.setHelpExtension(elem => { | [getHelpExtension$()](./kibana-plugin-core-public.chromestart.gethelpextension_.md) | Get an observable of the current custom help conttent | | [getIsNavDrawerLocked$()](./kibana-plugin-core-public.chromestart.getisnavdrawerlocked_.md) | Get an observable of the current locked state of the nav drawer. | | [getIsVisible$()](./kibana-plugin-core-public.chromestart.getisvisible_.md) | Get an observable of the current visibility state of the chrome. | +| [hasHeaderBanner$()](./kibana-plugin-core-public.chromestart.hasheaderbanner_.md) | Get an observable of the current header banner presence state. | | [setBadge(badge)](./kibana-plugin-core-public.chromestart.setbadge.md) | Override the current badge | | [setBreadcrumbs(newBreadcrumbs)](./kibana-plugin-core-public.chromestart.setbreadcrumbs.md) | Override the current set of breadcrumbs | | [setBreadcrumbsAppendExtension(breadcrumbsAppendExtension)](./kibana-plugin-core-public.chromestart.setbreadcrumbsappendextension.md) | Mount an element next to the last breadcrumb | diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md index 908615b3220dd..7d862f50bba18 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md @@ -17,7 +17,5 @@ export interface DocLinksStart | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string | | -| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly apm: {
readonly kibanaSettings: string;
readonly supportedServiceMaps: string;
readonly customLinks: string;
};
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
readonly ecs: {
readonly guide: string;
};
} | | -| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly apm: {
readonly kibanaSettings: string;
readonly supportedServiceMaps: string;
readonly upgrading: string;
};
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
readonly ecs: {
readonly guide: string;
};
} | | -| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly apm: {
readonly kibanaSettings: string;
readonly supportedServiceMaps: string;
readonly metaData: string;
};
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
readonly ecs: {
readonly guide: string;
};
} | | +| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly apm: {
readonly kibanaSettings: string;
readonly supportedServiceMaps: string;
readonly customLinks: string;
readonly droppedTransactionSpans: string;
readonly upgrading: string;
readonly metaData: string;
};
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
readonly ecs: {
readonly guide: string;
};
} | | diff --git a/src/core/public/chrome/chrome_service.mock.ts b/src/core/public/chrome/chrome_service.mock.ts index 5e29218250fb9..893b68ed429b8 100644 --- a/src/core/public/chrome/chrome_service.mock.ts +++ b/src/core/public/chrome/chrome_service.mock.ts @@ -55,6 +55,7 @@ const createStartContractMock = () => { getCustomNavLink$: jest.fn(), setCustomNavLink: jest.fn(), setHeaderBanner: jest.fn(), + hasHeaderBanner$: jest.fn(), getBodyClasses$: jest.fn(), }; startContract.navLinks.getAll.mockReturnValue([]); @@ -66,6 +67,7 @@ const createStartContractMock = () => { startContract.getHelpExtension$.mockReturnValue(new BehaviorSubject(undefined)); startContract.getIsNavDrawerLocked$.mockReturnValue(new BehaviorSubject(false)); startContract.getBodyClasses$.mockReturnValue(new BehaviorSubject([])); + startContract.hasHeaderBanner$.mockReturnValue(new BehaviorSubject(false)); return startContract; }; diff --git a/src/core/public/chrome/chrome_service.test.ts b/src/core/public/chrome/chrome_service.test.ts index 8df8d76a13c46..b3815c02674e1 100644 --- a/src/core/public/chrome/chrome_service.test.ts +++ b/src/core/public/chrome/chrome_service.test.ts @@ -390,6 +390,19 @@ describe('start', () => { }); }); + describe('header banner', () => { + it('updates/emits the state of the header banner', async () => { + const { chrome, service } = await start(); + const promise = chrome.hasHeaderBanner$().pipe(toArray()).toPromise(); + + chrome.setHeaderBanner({ content: () => () => undefined }); + chrome.setHeaderBanner(undefined); + service.stop(); + + await expect(promise).resolves.toEqual([false, true, false]); + }); + }); + describe('erase chrome fields', () => { it('while switching an app', async () => { const startDeps = defaultStartDeps([new FakeApp('alpha')]); diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index 5740e1739280a..8c8b264b094cc 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -273,6 +273,13 @@ export class ChromeService { headerBanner$.next(headerBanner); }, + hasHeaderBanner$: () => { + return headerBanner$.pipe( + takeUntil(this.stop$), + map((banner) => Boolean(banner)) + ); + }, + getBodyClasses$: () => bodyClasses$.pipe(takeUntil(this.stop$)), }; } diff --git a/src/core/public/chrome/types.ts b/src/core/public/chrome/types.ts index 813f385fc94d2..98987678d64cd 100644 --- a/src/core/public/chrome/types.ts +++ b/src/core/public/chrome/types.ts @@ -168,6 +168,11 @@ export interface ChromeStart { * @remarks Using `undefined` when invoking this API will remove the banner. */ setHeaderBanner(headerBanner?: ChromeUserBanner): void; + + /** + * Get an observable of the current header banner presence state. + */ + hasHeaderBanner$(): Observable; } /** @internal */ diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 78032145a5c73..0038c193c510e 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -356,6 +356,7 @@ export interface ChromeStart { getHelpExtension$(): Observable; getIsNavDrawerLocked$(): Observable; getIsVisible$(): Observable; + hasHeaderBanner$(): Observable; navControls: ChromeNavControls; navLinks: ChromeNavLinks; recentlyAccessed: ChromeRecentlyAccessed; From 32abdc7a3db7c94092f105ad2ab83600173e1e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ester=20Mart=C3=AD=20Vilaseca?= Date: Tue, 7 Sep 2021 18:30:57 +0200 Subject: [PATCH 42/43] [Stack Monitoring] Add setup mode to react app (#110670) (#111395) * Show setup mode button and setup bottom bar * Adapt setup mode in react components to work without angular * Add setup mode data update to react app * Add missing functions from setup mode * Revert setup mode changes from react components * remove some empty lines * Add setup button to monitoring toolbar * Fix types Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../application/global_state_context.tsx | 4 +- .../pages/cluster/overview_page.tsx | 26 ++- .../application/pages/page_template.tsx | 31 +-- .../application/setup_mode/setup_mode.tsx | 200 ++++++++++++++++ .../setup_mode/setup_mode_renderer.d.ts | 8 + .../setup_mode/setup_mode_renderer.js | 217 ++++++++++++++++++ .../public/components/shared/toolbar.tsx | 54 +++-- .../monitoring/public/external_config.ts | 16 ++ .../monitoring/public/lib/setup_mode.tsx | 4 + x-pack/plugins/monitoring/public/plugin.ts | 2 + 10 files changed, 514 insertions(+), 48 deletions(-) create mode 100644 x-pack/plugins/monitoring/public/application/setup_mode/setup_mode.tsx create mode 100644 x-pack/plugins/monitoring/public/application/setup_mode/setup_mode_renderer.d.ts create mode 100644 x-pack/plugins/monitoring/public/application/setup_mode/setup_mode_renderer.js create mode 100644 x-pack/plugins/monitoring/public/external_config.ts diff --git a/x-pack/plugins/monitoring/public/application/global_state_context.tsx b/x-pack/plugins/monitoring/public/application/global_state_context.tsx index dc33316dbd9d9..57bb638651d05 100644 --- a/x-pack/plugins/monitoring/public/application/global_state_context.tsx +++ b/x-pack/plugins/monitoring/public/application/global_state_context.tsx @@ -13,9 +13,11 @@ interface GlobalStateProviderProps { toasts: MonitoringStartPluginDependencies['core']['notifications']['toasts']; } -interface State { +export interface State { cluster_uuid?: string; ccs?: any; + inSetupMode?: boolean; + save?: () => void; } export const GlobalStateContext = createContext({} as State); diff --git a/x-pack/plugins/monitoring/public/application/pages/cluster/overview_page.tsx b/x-pack/plugins/monitoring/public/application/pages/cluster/overview_page.tsx index ddc097caea575..f329323bafda8 100644 --- a/x-pack/plugins/monitoring/public/application/pages/cluster/overview_page.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/cluster/overview_page.tsx @@ -15,8 +15,15 @@ import { TabMenuItem } from '../page_template'; import { PageLoading } from '../../../components'; import { Overview } from '../../../components/cluster/overview'; import { ExternalConfigContext } from '../../external_config_context'; +import { SetupModeRenderer } from '../../setup_mode/setup_mode_renderer'; +import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context'; const CODE_PATHS = [CODE_PATH_ALL]; +interface SetupModeProps { + setupMode: any; + flyoutComponent: any; + bottomBarComponent: any; +} export const ClusterOverview: React.FC<{}> = () => { // TODO: check how many requests with useClusters @@ -49,11 +56,20 @@ export const ClusterOverview: React.FC<{}> = () => { return ( {loaded ? ( - ( + + {flyoutComponent} + + {/* */} + {bottomBarComponent} + + )} /> ) : ( diff --git a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx index f40c2d3ec5e50..29aafa09814fb 100644 --- a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiTab, EuiTabs, EuiTitle } from '@elastic/eui'; +import { EuiTab, EuiTabs } from '@elastic/eui'; import React from 'react'; import { useTitle } from '../hooks/use_title'; import { MonitoringToolbar } from '../../components/shared/toolbar'; @@ -29,34 +29,7 @@ export const PageTemplate: React.FC = ({ title, pageTitle, ta return (
- - - - -
{/* HERE GOES THE SETUP BUTTON */}
-
- - {pageTitle && ( -
- -

{pageTitle}

-
-
- )} -
-
-
- - - - -
- + {tabs && ( {tabs.map((item, idx) => { diff --git a/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode.tsx b/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode.tsx new file mode 100644 index 0000000000000..70932e5177337 --- /dev/null +++ b/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode.tsx @@ -0,0 +1,200 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from 'react-dom'; +import { get, includes } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { HttpStart } from 'kibana/public'; +import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; +import { Legacy } from '../../legacy_shims'; +import { SetupModeEnterButton } from '../../components/setup_mode/enter_button'; +import { SetupModeFeature } from '../../../common/enums'; +import { ISetupModeContext } from '../../components/setup_mode/setup_mode_context'; +import { State as GlobalState } from '../../application/global_state_context'; + +function isOnPage(hash: string) { + return includes(window.location.hash, hash); +} + +let globalState: GlobalState; +let httpService: HttpStart; + +interface ISetupModeState { + enabled: boolean; + data: any; + callback?: (() => void) | null; + hideBottomBar: boolean; +} +const setupModeState: ISetupModeState = { + enabled: false, + data: null, + callback: null, + hideBottomBar: false, +}; + +export const getSetupModeState = () => setupModeState; + +export const setNewlyDiscoveredClusterUuid = (clusterUuid: string) => { + globalState.cluster_uuid = clusterUuid; + globalState.save?.(); +}; + +export const fetchCollectionData = async (uuid?: string, fetchWithoutClusterUuid = false) => { + const clusterUuid = globalState.cluster_uuid; + const ccs = globalState.ccs; + + let url = '../api/monitoring/v1/setup/collection'; + if (uuid) { + url += `/node/${uuid}`; + } else if (!fetchWithoutClusterUuid && clusterUuid) { + url += `/cluster/${clusterUuid}`; + } else { + url += '/cluster'; + } + + try { + const response = await httpService.post(url, { + body: JSON.stringify({ + ccs, + }), + }); + return response; + } catch (err) { + // TODO: handle errors + throw new Error(err); + } +}; + +const notifySetupModeDataChange = () => setupModeState.callback && setupModeState.callback(); + +export const updateSetupModeData = async (uuid?: string, fetchWithoutClusterUuid = false) => { + const data = await fetchCollectionData(uuid, fetchWithoutClusterUuid); + setupModeState.data = data; + const hasPermissions = get(data, '_meta.hasPermissions', false); + if (!hasPermissions) { + let text: string = ''; + if (!hasPermissions) { + text = i18n.translate('xpack.monitoring.setupMode.notAvailablePermissions', { + defaultMessage: 'You do not have the necessary permissions to do this.', + }); + } + + Legacy.shims.toastNotifications.addDanger({ + title: i18n.translate('xpack.monitoring.setupMode.notAvailableTitle', { + defaultMessage: 'Setup mode is not available', + }), + text, + }); + return toggleSetupMode(false); + } + notifySetupModeDataChange(); + + const clusterUuid = globalState.cluster_uuid; + if (!clusterUuid) { + const liveClusterUuid: string = get(data, '_meta.liveClusterUuid'); + const migratedEsNodes = Object.values(get(data, 'elasticsearch.byUuid', {})).filter( + (node: any) => node.isPartiallyMigrated || node.isFullyMigrated + ); + if (liveClusterUuid && migratedEsNodes.length > 0) { + setNewlyDiscoveredClusterUuid(liveClusterUuid); + } + } +}; + +export const hideBottomBar = () => { + setupModeState.hideBottomBar = true; + notifySetupModeDataChange(); +}; +export const showBottomBar = () => { + setupModeState.hideBottomBar = false; + notifySetupModeDataChange(); +}; + +export const disableElasticsearchInternalCollection = async () => { + const clusterUuid = globalState.cluster_uuid; + const url = `../api/monitoring/v1/setup/collection/${clusterUuid}/disable_internal_collection`; + try { + const response = await httpService.post(url); + return response; + } catch (err) { + // TODO: handle errors + throw new Error(err); + } +}; + +export const toggleSetupMode = (inSetupMode: boolean) => { + setupModeState.enabled = inSetupMode; + globalState.inSetupMode = inSetupMode; + globalState.save?.(); + setSetupModeMenuItem(); + notifySetupModeDataChange(); + + if (inSetupMode) { + // Intentionally do not await this so we don't block UI operations + updateSetupModeData(); + } +}; + +export const setSetupModeMenuItem = () => { + if (isOnPage('no-data')) { + return; + } + + const enabled = !globalState.inSetupMode; + const I18nContext = Legacy.shims.I18nContext; + + render( + + + + + , + document.getElementById('setupModeNav') + ); +}; + +export const initSetupModeState = async ( + state: GlobalState, + http: HttpStart, + callback?: () => void +) => { + globalState = state; + httpService = http; + if (callback) { + setupModeState.callback = callback; + } + + if (globalState.inSetupMode) { + toggleSetupMode(true); + } +}; + +export const isInSetupMode = (context?: ISetupModeContext) => { + if (context?.setupModeSupported === false) { + return false; + } + if (setupModeState.enabled) { + return true; + } + + return globalState.inSetupMode; +}; + +export const isSetupModeFeatureEnabled = (feature: SetupModeFeature) => { + if (!setupModeState.enabled) { + return false; + } + + if (feature === SetupModeFeature.MetricbeatMigration) { + if (Legacy.shims.isCloud) { + return false; + } + } + + return true; +}; diff --git a/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode_renderer.d.ts b/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode_renderer.d.ts new file mode 100644 index 0000000000000..27462f07c07be --- /dev/null +++ b/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode_renderer.d.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const SetupModeRenderer: FunctionComponent; diff --git a/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode_renderer.js b/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode_renderer.js new file mode 100644 index 0000000000000..337dacd4ecae9 --- /dev/null +++ b/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode_renderer.js @@ -0,0 +1,217 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { Fragment } from 'react'; +import { + getSetupModeState, + initSetupModeState, + updateSetupModeData, + disableElasticsearchInternalCollection, + toggleSetupMode, + setSetupModeMenuItem, +} from './setup_mode'; +import { Flyout } from '../../components/metricbeat_migration/flyout'; +import { + EuiBottomBar, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiTextColor, + EuiIcon, + EuiSpacer, +} from '@elastic/eui'; +import { findNewUuid } from '../../components/renderers/lib/find_new_uuid'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { GlobalStateContext } from '../../application/global_state_context'; +import { withKibana } from '../../../../../../src/plugins/kibana_react/public'; + +class WrappedSetupModeRenderer extends React.Component { + globalState; + state = { + renderState: false, + isFlyoutOpen: false, + instance: null, + newProduct: null, + isSettingUpNew: false, + }; + + UNSAFE_componentWillMount() { + this.globalState = this.context; + const { kibana } = this.props; + initSetupModeState(this.globalState, kibana.services.http, (_oldData) => { + const newState = { renderState: true }; + + const { productName } = this.props; + if (!productName) { + this.setState(newState); + return; + } + + const setupModeState = getSetupModeState(); + if (!setupModeState.enabled || !setupModeState.data) { + this.setState(newState); + return; + } + + const data = setupModeState.data[productName]; + const oldData = _oldData ? _oldData[productName] : null; + if (data && oldData) { + const newUuid = findNewUuid(Object.keys(oldData.byUuid), Object.keys(data.byUuid)); + if (newUuid) { + newState.newProduct = data.byUuid[newUuid]; + } + } + + this.setState(newState); + }); + setSetupModeMenuItem(); + } + + reset() { + this.setState({ + renderState: false, + isFlyoutOpen: false, + instance: null, + newProduct: null, + isSettingUpNew: false, + }); + } + + getFlyout(data, meta) { + const { productName } = this.props; + const { isFlyoutOpen, instance, isSettingUpNew, newProduct } = this.state; + if (!data || !isFlyoutOpen) { + return null; + } + + let product = null; + if (newProduct) { + product = newProduct; + } + // For new instance discovery flow, we pass in empty instance object + else if (instance && Object.keys(instance).length) { + product = data.byUuid[instance.uuid]; + } + + if (!product) { + const uuids = Object.values(data.byUuid); + if (uuids.length && !isSettingUpNew) { + product = uuids[0]; + } else { + product = { + isNetNewUser: true, + }; + } + } + + return ( + this.reset()} + productName={productName} + product={product} + meta={meta} + instance={instance} + updateProduct={updateSetupModeData} + isSettingUpNew={isSettingUpNew} + /> + ); + } + + getBottomBar(setupModeState) { + if (!setupModeState.enabled || setupModeState.hideBottomBar) { + return null; + } + + return ( + + + + + + + + + , + }} + /> + + + + + + + + toggleSetupMode(false)} + > + {i18n.translate('xpack.monitoring.setupMode.exit', { + defaultMessage: `Exit setup mode`, + })} + + + + + + + + ); + } + + async shortcutToFinishMigration() { + await disableElasticsearchInternalCollection(); + await updateSetupModeData(); + } + + render() { + const { render, productName } = this.props; + const setupModeState = getSetupModeState(); + + let data = { byUuid: {} }; + if (setupModeState.data) { + if (productName && setupModeState.data[productName]) { + data = setupModeState.data[productName]; + } else if (setupModeState.data) { + data = setupModeState.data; + } + } + + const meta = setupModeState.data ? setupModeState.data._meta : null; + + return render({ + setupMode: { + data, + meta, + enabled: setupModeState.enabled, + productName, + updateSetupModeData, + shortcutToFinishMigration: () => this.shortcutToFinishMigration(), + openFlyout: (instance, isSettingUpNew) => + this.setState({ isFlyoutOpen: true, instance, isSettingUpNew }), + closeFlyout: () => this.setState({ isFlyoutOpen: false }), + }, + flyoutComponent: this.getFlyout(data, meta), + bottomBarComponent: this.getBottomBar(setupModeState), + }); + } +} + +WrappedSetupModeRenderer.contextType = GlobalStateContext; +export const SetupModeRenderer = withKibana(WrappedSetupModeRenderer); diff --git a/x-pack/plugins/monitoring/public/components/shared/toolbar.tsx b/x-pack/plugins/monitoring/public/components/shared/toolbar.tsx index 6e45d4d831ec9..e5962b7f80876 100644 --- a/x-pack/plugins/monitoring/public/components/shared/toolbar.tsx +++ b/x-pack/plugins/monitoring/public/components/shared/toolbar.tsx @@ -5,11 +5,21 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiSuperDatePicker, OnRefreshChangeProps } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiSuperDatePicker, + EuiTitle, + OnRefreshChangeProps, +} from '@elastic/eui'; import React, { useContext, useCallback } from 'react'; import { MonitoringTimeContainer } from '../../application/pages/use_monitoring_time'; -export const MonitoringToolbar = () => { +interface MonitoringToolbarProps { + pageTitle?: string; +} + +export const MonitoringToolbar: React.FC = ({ pageTitle }) => { const { currentTimerange, handleTimeChange, @@ -38,18 +48,36 @@ export const MonitoringToolbar = () => { ); return ( - - Setup Button + + + + +
{/* HERE GOES THE SETUP BUTTON */}
+
+ + {pageTitle && ( +
+ +

{pageTitle}

+
+
+ )} +
+
+
+ - {}} - isPaused={isPaused} - refreshInterval={refreshInterval} - onRefreshChange={onRefreshChange} - /> +
+ {}} + isPaused={isPaused} + refreshInterval={refreshInterval} + onRefreshChange={onRefreshChange} + /> +
); diff --git a/x-pack/plugins/monitoring/public/external_config.ts b/x-pack/plugins/monitoring/public/external_config.ts new file mode 100644 index 0000000000000..29ce410a5a9dc --- /dev/null +++ b/x-pack/plugins/monitoring/public/external_config.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +let config: { [key: string]: unknown } = {}; + +export const setConfig = (externalConfig: { [key: string]: unknown }) => { + config = externalConfig; +}; + +export const isReactMigrationEnabled = () => { + return config.renderReactApp; +}; diff --git a/x-pack/plugins/monitoring/public/lib/setup_mode.tsx b/x-pack/plugins/monitoring/public/lib/setup_mode.tsx index 28fd7494b1d10..f622f2944a31a 100644 --- a/x-pack/plugins/monitoring/public/lib/setup_mode.tsx +++ b/x-pack/plugins/monitoring/public/lib/setup_mode.tsx @@ -15,6 +15,8 @@ import { ajaxErrorHandlersProvider } from './ajax_error_handler'; import { SetupModeEnterButton } from '../components/setup_mode/enter_button'; import { SetupModeFeature } from '../../common/enums'; import { ISetupModeContext } from '../components/setup_mode/setup_mode_context'; +import * as setupModeReact from '../application/setup_mode/setup_mode'; +import { isReactMigrationEnabled } from '../external_config'; function isOnPage(hash: string) { return includes(window.location.hash, hash); @@ -209,6 +211,7 @@ export const initSetupModeState = async ($scope: any, $injector: any, callback?: }; export const isInSetupMode = (context?: ISetupModeContext) => { + if (isReactMigrationEnabled()) return setupModeReact.isInSetupMode(context); if (context?.setupModeSupported === false) { return false; } @@ -222,6 +225,7 @@ export const isInSetupMode = (context?: ISetupModeContext) => { }; export const isSetupModeFeatureEnabled = (feature: SetupModeFeature) => { + if (isReactMigrationEnabled()) return setupModeReact.isSetupModeFeatureEnabled(feature); if (!setupModeState.enabled) { return false; } diff --git a/x-pack/plugins/monitoring/public/plugin.ts b/x-pack/plugins/monitoring/public/plugin.ts index 6884dba760fcd..6f625194287ba 100644 --- a/x-pack/plugins/monitoring/public/plugin.ts +++ b/x-pack/plugins/monitoring/public/plugin.ts @@ -36,6 +36,7 @@ import { createThreadPoolRejectionsAlertType } from './alerts/thread_pool_reject import { createMemoryUsageAlertType } from './alerts/memory_usage_alert'; import { createCCRReadExceptionsAlertType } from './alerts/ccr_read_exceptions_alert'; import { createLargeShardSizeAlertType } from './alerts/large_shard_size_alert'; +import { setConfig } from './external_config'; interface MonitoringSetupPluginDependencies { home?: HomePublicPluginSetup; @@ -125,6 +126,7 @@ export class MonitoringPlugin }); const config = Object.fromEntries(externalConfig); + setConfig(config); if (config.renderReactApp) { const { renderApp } = await import('./application'); return renderApp(coreStart, pluginsStart, params, config); From 403ac3d5390d3bef012e620748ca0f8e3eb33f29 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 7 Sep 2021 12:57:18 -0400 Subject: [PATCH 43/43] [Security Solution] styling for reason popover (#111338) (#111400) * add panelClassName * remove unused data-test-subj Co-authored-by: Angela Chuang <6295984+angorayc@users.noreply.github.com> --- .../renderers/reason_column_renderer.test.tsx | 17 +++++++++++++++++ .../body/renderers/reason_column_renderer.tsx | 1 + 2 files changed, 18 insertions(+) diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/reason_column_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/reason_column_renderer.test.tsx index addb991af58d7..ee8a275279607 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/reason_column_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/reason_column_renderer.test.tsx @@ -144,5 +144,22 @@ describe('reasonColumnRenderer', () => { expect(wrapper.queryByTestId('test-row-render')).toBeInTheDocument(); }); + + it('the popover always contains a class that hides it when an overlay (e.g. the inspect modal) is displayed', () => { + const renderedColumn = reasonColumnRenderer.renderColumn({ + ...defaultProps, + ecsData: validEcs, + rowRenderers, + browserFields, + }); + + const wrapper = render({renderedColumn}); + + fireEvent.click(wrapper.getByTestId('reason-cell-button')); + + expect(wrapper.getByRole('dialog')).toHaveClass( + 'euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--right withHoverActions__popover' + ); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/reason_column_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/reason_column_renderer.tsx index 00f5fd5717aeb..52483b4853cbc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/reason_column_renderer.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/reason_column_renderer.tsx @@ -122,6 +122,7 @@ const ReasonCell: React.FC<{ isOpen={isOpen} anchorPosition="rightCenter" closePopover={handleClosePopOver} + panelClassName="withHoverActions__popover" button={button} >