From 6d7998e70ce3919c9f33cd0357b63661488e585c Mon Sep 17 00:00:00 2001 From: Marshall Main <55718608+marshallmain@users.noreply.github.com> Date: Thu, 26 Aug 2021 18:29:21 -0700 Subject: [PATCH 01/12] [Security Solution] Fix another reference to ALERT_STATUS (#110376) --- .../detections/components/alerts_table/default_config.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx index 6608a0dd1458c..7e755ad654685 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx @@ -235,7 +235,7 @@ export const buildAlertStatusesFilterRuleRegistry = (statuses: Status[]): Filter bool: { should: statuses.map((status) => ({ term: { - [ALERT_STATUS]: status, + [ALERT_WORKFLOW_STATUS]: status, }, })), }, From 49e3edf032466ea1b70eaca85406321bc6db7417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Thu, 26 Aug 2021 23:56:14 -0400 Subject: [PATCH 02/12] [APM] Latency threshold alerts are not being triggered (#110315) --- ...egister_transaction_duration_alert_type.ts | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts index f3893a9da24c2..fedc185d84f9f 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts @@ -5,9 +5,8 @@ * 2.0. */ -import { schema } from '@kbn/config-schema'; -import { take } from 'rxjs/operators'; import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types'; +import { schema } from '@kbn/config-schema'; import type { ALERT_EVALUATION_THRESHOLD as ALERT_EVALUATION_THRESHOLD_TYPED, ALERT_EVALUATION_VALUE as ALERT_EVALUATION_VALUE_TYPED, @@ -19,33 +18,36 @@ import { ALERT_REASON as ALERT_REASON_NON_TYPED, // @ts-expect-error } from '@kbn/rule-data-utils/target_node/technical_field_names'; -import { SearchAggregatedTransactionSetting } from '../../../common/aggregated_transactions'; +import { take } from 'rxjs/operators'; import { asDuration } from '../../../../observability/common/utils/formatters'; import { createLifecycleRuleTypeFactory } from '../../../../rule_registry/server'; -import { - getEnvironmentLabel, - getEnvironmentEsField, -} from '../../../common/environment_filter_values'; +import { SearchAggregatedTransactionSetting } from '../../../common/aggregated_transactions'; import { AlertType, - APM_SERVER_FEATURE_ID, ALERT_TYPES_CONFIG, + APM_SERVER_FEATURE_ID, formatTransactionDurationReason, } from '../../../common/alert_types'; import { PROCESSOR_EVENT, SERVICE_NAME, - TRANSACTION_DURATION, TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; +import { + getEnvironmentEsField, + getEnvironmentLabel, +} from '../../../common/environment_filter_values'; import { ProcessorEvent } from '../../../common/processor_event'; -import { getDurationFormatter } from '../../../common/utils/formatters'; import { environmentQuery } from '../../../common/utils/environment_query'; +import { getDurationFormatter } from '../../../common/utils/formatters'; +import { + getDocumentTypeFilterForAggregatedTransactions, + getTransactionDurationFieldForAggregatedTransactions, +} from '../helpers/aggregated_transactions'; import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; import { apmActionVariables } from './action_variables'; import { alertingEsClient } from './alerting_es_client'; import { RegisterRuleDependencies } from './register_apm_alerts'; -import { getDocumentTypeFilterForAggregatedTransactions } from '../helpers/aggregated_transactions'; const ALERT_EVALUATION_THRESHOLD: typeof ALERT_EVALUATION_THRESHOLD_TYPED = ALERT_EVALUATION_THRESHOLD_NON_TYPED; const ALERT_EVALUATION_VALUE: typeof ALERT_EVALUATION_VALUE_TYPED = ALERT_EVALUATION_VALUE_NON_TYPED; @@ -118,6 +120,10 @@ export function registerTransactionDurationAlertType({ ? indices['apm_oss.metricsIndices'] : indices['apm_oss.transactionIndices']; + const field = getTransactionDurationFieldForAggregatedTransactions( + searchAggregatedTransactions + ); + const searchParams = { index, body: { @@ -148,10 +154,10 @@ export function registerTransactionDurationAlertType({ aggs: { latency: alertParams.aggregationType === 'avg' - ? { avg: { field: TRANSACTION_DURATION } } + ? { avg: { field } } : { percentiles: { - field: TRANSACTION_DURATION, + field, percents: [ alertParams.aggregationType === '95th' ? 95 : 99, ], From 5208cfdbc71127ae665d7c66068673a7b00efd8c Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov Date: Fri, 27 Aug 2021 09:04:55 +0300 Subject: [PATCH 03/12] [Canvas] Fixes Expression errors are not rendered as markdown. --- src/plugins/expression_error/kibana.json | 2 +- .../expression_error/public/components/error/error.tsx | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/plugins/expression_error/kibana.json b/src/plugins/expression_error/kibana.json index aa3201694619c..bf4eb51524ec6 100755 --- a/src/plugins/expression_error/kibana.json +++ b/src/plugins/expression_error/kibana.json @@ -11,5 +11,5 @@ "ui": true, "requiredPlugins": ["expressions", "presentationUtil"], "optionalPlugins": [], - "requiredBundles": [] + "requiredBundles": ["kibanaReact"] } diff --git a/src/plugins/expression_error/public/components/error/error.tsx b/src/plugins/expression_error/public/components/error/error.tsx index 637309448da23..808424f9f4f8b 100644 --- a/src/plugins/expression_error/public/components/error/error.tsx +++ b/src/plugins/expression_error/public/components/error/error.tsx @@ -11,6 +11,7 @@ import { EuiCallOut } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { get } from 'lodash'; import { ShowDebugging } from './show_debugging'; +import { Markdown } from '../../../../kibana_react/public'; export interface Props { payload: { @@ -40,8 +41,11 @@ export const Error: FC = ({ payload }) => { title={strings.getTitle()} >

{message ? strings.getDescription() : ''}

- {message &&

{message}

} - + {message && ( +

+ +

+ )} ); From dfa6aa8bdf197414d98cda444fcf5a15c5478901 Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov Date: Fri, 27 Aug 2021 09:06:31 +0300 Subject: [PATCH 04/12] [Canvas] Fixes Expression Failed Exit Button not Clickable . (#110191) * Fixed not clickable cross at error popup. --- .../public/components/error/error.tsx | 14 +++++++++----- .../public/components/error_component.tsx | 3 ++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/plugins/expression_error/public/components/error/error.tsx b/src/plugins/expression_error/public/components/error/error.tsx index 808424f9f4f8b..2b42aa0f7eccd 100644 --- a/src/plugins/expression_error/public/components/error/error.tsx +++ b/src/plugins/expression_error/public/components/error/error.tsx @@ -7,9 +7,8 @@ */ import React, { FC } from 'react'; -import { EuiCallOut } from '@elastic/eui'; +import { EuiButtonIcon, EuiCallOut } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { get } from 'lodash'; import { ShowDebugging } from './show_debugging'; import { Markdown } from '../../../../kibana_react/public'; @@ -17,6 +16,7 @@ export interface Props { payload: { error: Error; }; + onClose?: () => void; } const strings = { @@ -30,14 +30,18 @@ const strings = { }), }; -export const Error: FC = ({ payload }) => { - const message = get(payload, 'error.message'); +export const Error: FC = ({ payload, onClose }) => { + const message = payload.error?.message; + + const CloseIconButton = () => ( + + ); return (

{message ? strings.getDescription() : ''}

diff --git a/src/plugins/expression_error/public/components/error_component.tsx b/src/plugins/expression_error/public/components/error_component.tsx index 58161d8a068a2..2a019c9ce6945 100644 --- a/src/plugins/expression_error/public/components/error_component.tsx +++ b/src/plugins/expression_error/public/components/error_component.tsx @@ -28,6 +28,7 @@ function ErrorComponent({ onLoaded, parentNode, error }: ErrorComponentProps) { const [isPopoverOpen, setPopoverOpen] = useState(false); const handlePopoverClick = () => setPopoverOpen(!isPopoverOpen); + const closePopover = () => setPopoverOpen(false); const updateErrorView = useCallback(() => { @@ -56,7 +57,7 @@ function ErrorComponent({ onLoaded, parentNode, error }: ErrorComponentProps) { } isOpen={isPopoverOpen} > - + ); From dde701faaaef3ef6ff3160aa0d16d0696710c409 Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov Date: Fri, 27 Aug 2021 09:26:55 +0300 Subject: [PATCH 05/12] [Canvas] ItemGrid refactor. (#110044) --- x-pack/plugins/canvas/public/components/item_grid/index.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/canvas/public/components/item_grid/index.ts b/x-pack/plugins/canvas/public/components/item_grid/index.ts index 5e17416c30321..5ad51718bc109 100644 --- a/x-pack/plugins/canvas/public/components/item_grid/index.ts +++ b/x-pack/plugins/canvas/public/components/item_grid/index.ts @@ -4,8 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { pure } from 'recompose'; +import { memo } from 'react'; import { ItemGrid as Component, Props as ComponentProps } from './item_grid'; -export const ItemGrid = pure>(Component); +export const ItemGrid = memo>(Component); From 2e4e0fca4c4c4a1e168b9a62a3b151a3735436dc Mon Sep 17 00:00:00 2001 From: Catherine Liu Date: Thu, 26 Aug 2021 23:46:11 -0700 Subject: [PATCH 06/12] Clears resolved arg on embeddable destroy (#109945) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/canvas/public/lib/create_handlers.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x-pack/plugins/canvas/public/lib/create_handlers.ts b/x-pack/plugins/canvas/public/lib/create_handlers.ts index aba29ccd542be..dfc4387bcbf92 100644 --- a/x-pack/plugins/canvas/public/lib/create_handlers.ts +++ b/x-pack/plugins/canvas/public/lib/create_handlers.ts @@ -14,6 +14,7 @@ import { import { setFilter } from '../state/actions/elements'; import { updateEmbeddableExpression, fetchEmbeddableRenderable } from '../state/actions/embeddable'; import { RendererHandlers, CanvasElement } from '../../types'; +import { clearValue } from '../state/actions/resolved_args'; // This class creates stub handlers to ensure every element and renderer fulfills the contract. // TODO: consider warning if these methods are invoked but not implemented by the renderer...? @@ -123,6 +124,8 @@ export const createDispatchedHandlerFactory = ( }, onEmbeddableDestroyed() { + const argumentPath = [element.id, 'expressionRenderable']; + dispatch(clearValue({ path: argumentPath })); dispatch(fetchEmbeddableRenderable(element.id)); }, From 73f8a92a333bd20d7784af9b373849374b5818b6 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Fri, 27 Aug 2021 00:30:59 -0700 Subject: [PATCH 07/12] [bazel] Move keepalive to common (#110351) Signed-off-by: Tyler Smalley Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/dev/ci_setup/.bazelrc-ci | 3 --- src/dev/ci_setup/.bazelrc-ci.common | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dev/ci_setup/.bazelrc-ci b/src/dev/ci_setup/.bazelrc-ci index bb8710d69ed54..ef6fab3a30590 100644 --- a/src/dev/ci_setup/.bazelrc-ci +++ b/src/dev/ci_setup/.bazelrc-ci @@ -12,8 +12,5 @@ build --bes_backend=grpcs://cloud.buildbuddy.io build --remote_cache=grpcs://cloud.buildbuddy.io build --remote_timeout=3600 -## Avoid to keep connections to build event backend connections alive across builds -build --keep_backend_build_event_connections_alive=false - ## Metadata settings build --build_metadata=ROLE=CI diff --git a/src/dev/ci_setup/.bazelrc-ci.common b/src/dev/ci_setup/.bazelrc-ci.common index 9d00ee5639741..56a5ee8d30cd6 100644 --- a/src/dev/ci_setup/.bazelrc-ci.common +++ b/src/dev/ci_setup/.bazelrc-ci.common @@ -6,3 +6,6 @@ build --noshow_progress # More details on failures build --verbose_failures=true + +## Avoid to keep connections to build event backend connections alive across builds +build --keep_backend_build_event_connections_alive=false From d43e9f586bee4bbf040c4c895a9a0783faf5020e Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov Date: Fri, 27 Aug 2021 13:58:10 +0300 Subject: [PATCH 08/12] [Canvas] Fixes Storybook for DatasourceComponent is crashing. (#110180) * Added mock for `es_service`. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../plugins/canvas/storybook/__mocks__/es_service.ts | 10 ++++++++++ x-pack/plugins/canvas/storybook/main.ts | 4 ++++ 2 files changed, 14 insertions(+) create mode 100644 x-pack/plugins/canvas/storybook/__mocks__/es_service.ts diff --git a/x-pack/plugins/canvas/storybook/__mocks__/es_service.ts b/x-pack/plugins/canvas/storybook/__mocks__/es_service.ts new file mode 100644 index 0000000000000..686fd7b4fa947 --- /dev/null +++ b/x-pack/plugins/canvas/storybook/__mocks__/es_service.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getDefaultIndex = () => { + return Promise.resolve('Demonstration index'); +}; diff --git a/x-pack/plugins/canvas/storybook/main.ts b/x-pack/plugins/canvas/storybook/main.ts index 69c05322cf3f0..a043efd7c87f5 100644 --- a/x-pack/plugins/canvas/storybook/main.ts +++ b/x-pack/plugins/canvas/storybook/main.ts @@ -56,6 +56,10 @@ const canvasWebpack = { resolve: { alias: { 'src/plugins': resolve(KIBANA_ROOT, 'src/plugins'), + '../../lib/es_service': resolve( + KIBANA_ROOT, + 'x-pack/plugins/canvas/storybook/__mocks__/es_service.ts' + ), }, }, }; From 54a45bba6532eacc4b59befd2cb0d203ce9298ed Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Fri, 27 Aug 2021 13:08:50 +0200 Subject: [PATCH 09/12] [ML] APM Latency Correlations: Fix empty state (#109813) - Correctly renders the empty chart state when no data is available. - Hides the "Click drag to select" and trace samples message when the chart shows an empty state to avoid redundant info. - Adds jest unit tests that would fail with the previously visible loading indicators. - Fix a bug with cancelling search strategies. --- .../search_strategies/correlations/types.ts | 9 ++ .../app/correlations/empty_state_prompt.tsx | 3 +- .../failed_transactions_correlations.tsx | 49 +++--- .../latency_correlations.test.tsx | 131 +++++++++++++++ .../app/correlations/latency_correlations.tsx | 30 ++-- .../distribution/index.test.ts | 22 --- .../distribution/index.test.tsx | 151 ++++++++++++++++++ .../distribution/index.tsx | 58 ++++--- .../transaction_details/trace_samples_tab.tsx | 28 ++-- .../shared/charts/chart_container.tsx | 4 +- .../transaction_distribution_chart/index.tsx | 45 ++++-- ...ailed_transactions_correlations_fetcher.ts | 93 +++++------ .../use_transaction_distribution_fetcher.ts | 136 ++++++++-------- ...ransaction_latency_correlations_fetcher.ts | 143 +++++++++-------- .../correlations/search_strategy.ts | 19 ++- 15 files changed, 621 insertions(+), 300 deletions(-) create mode 100644 x-pack/plugins/apm/public/components/app/correlations/latency_correlations.test.tsx delete mode 100644 x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.test.ts create mode 100644 x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.test.tsx diff --git a/x-pack/plugins/apm/common/search_strategies/correlations/types.ts b/x-pack/plugins/apm/common/search_strategies/correlations/types.ts index 703106628f561..886c5fd6161d8 100644 --- a/x-pack/plugins/apm/common/search_strategies/correlations/types.ts +++ b/x-pack/plugins/apm/common/search_strategies/correlations/types.ts @@ -52,3 +52,12 @@ export interface AsyncSearchProviderProgress { loadedFieldValuePairs: number; loadedHistograms: number; } + +export interface SearchServiceRawResponse { + ccsWarning: boolean; + log: string[]; + overallHistogram?: HistogramItem[]; + percentileThresholdValue?: number; + took: number; + values: SearchServiceValue[]; +} diff --git a/x-pack/plugins/apm/public/components/app/correlations/empty_state_prompt.tsx b/x-pack/plugins/apm/public/components/app/correlations/empty_state_prompt.tsx index 57e57a526baff..9b161fc1b9fa9 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/empty_state_prompt.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/empty_state_prompt.tsx @@ -33,8 +33,7 @@ export function CorrelationsEmptyStatePrompt() { id="xpack.apm.correlations.noCorrelationsTextLine1" defaultMessage="Correlations will only be identified if they have significant impact." /> -

-

+
(enableInspectEsQueries); - const searchServicePrams: SearchServiceParams = { - environment, - kuery, - serviceName, - transactionName, - transactionType, - start, - end, - }; - const result = useFailedTransactionsCorrelationsFetcher(); const { @@ -93,26 +82,30 @@ export function FailedTransactionsCorrelations({ } = result; const startFetchHandler = useCallback(() => { - startFetch(searchServicePrams); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [environment, serviceName, kuery, start, end]); + startFetch({ + environment, + kuery, + serviceName, + transactionName, + transactionType, + start, + end, + }); + }, [ + startFetch, + environment, + serviceName, + transactionName, + transactionType, + kuery, + start, + end, + ]); - // start fetching on load - // we want this effect to execute exactly once after the component mounts useEffect(() => { - if (isRunning) { - cancelFetch(); - } - startFetchHandler(); - - return () => { - // cancel any running async partial request when unmounting the component - // we want this effect to execute exactly once after the component mounts - cancelFetch(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [startFetchHandler]); + return cancelFetch; + }, [cancelFetch, startFetchHandler]); const [ selectedSignificantTerm, diff --git a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.test.tsx b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.test.tsx new file mode 100644 index 0000000000000..b0da5b6d60d74 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.test.tsx @@ -0,0 +1,131 @@ +/* + * 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 { render, screen, waitFor } from '@testing-library/react'; +import { createMemoryHistory } from 'history'; +import React, { ReactNode } from 'react'; +import { of } from 'rxjs'; + +import { __IntlProvider as IntlProvider } from '@kbn/i18n/react'; + +import { CoreStart } from 'kibana/public'; +import { merge } from 'lodash'; +import { dataPluginMock } from 'src/plugins/data/public/mocks'; +import type { IKibanaSearchResponse } from 'src/plugins/data/public'; +import { EuiThemeProvider } from 'src/plugins/kibana_react/common'; +import { createKibanaReactContext } from 'src/plugins/kibana_react/public'; +import type { SearchServiceRawResponse } from '../../../../common/search_strategies/correlations/types'; +import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; +import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; +import { + mockApmPluginContextValue, + MockApmPluginContextWrapper, +} from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { fromQuery } from '../../shared/Links/url_helpers'; + +import { LatencyCorrelations } from './latency_correlations'; + +function Wrapper({ + children, + dataSearchResponse, +}: { + children?: ReactNode; + dataSearchResponse: IKibanaSearchResponse; +}) { + const mockDataSearch = jest.fn(() => of(dataSearchResponse)); + + const dataPluginMockStart = dataPluginMock.createStartContract(); + const KibanaReactContext = createKibanaReactContext({ + data: { + ...dataPluginMockStart, + search: { + ...dataPluginMockStart.search, + search: mockDataSearch, + }, + }, + usageCollection: { reportUiCounter: () => {} }, + } as Partial); + + const httpGet = jest.fn(); + + const history = createMemoryHistory(); + jest.spyOn(history, 'push'); + jest.spyOn(history, 'replace'); + + history.replace({ + pathname: '/services/the-service-name/transactions/view', + search: fromQuery({ transactionName: 'the-transaction-name' }), + }); + + const mockPluginContext = (merge({}, mockApmPluginContextValue, { + core: { http: { get: httpGet } }, + }) as unknown) as ApmPluginContextValue; + + return ( + + + + + + {children} + + + + + + ); +} + +describe('correlations', () => { + describe('LatencyCorrelations', () => { + it('shows loading indicator when the service is running and returned no results yet', async () => { + render( + + + + ); + + await waitFor(() => { + expect(screen.getByTestId('apmCorrelationsChart')).toBeInTheDocument(); + expect(screen.getByTestId('loading')).toBeInTheDocument(); + }); + }); + + it("doesn't show loading indicator when the service isn't running", async () => { + render( + + + + ); + + await waitFor(() => { + expect(screen.getByTestId('apmCorrelationsChart')).toBeInTheDocument(); + expect(screen.queryByTestId('loading')).toBeNull(); // it doesn't exist + }); + }); + }); +}); diff --git a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx index 74702e621a0ba..ad8a56a3ac6f9 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx @@ -61,7 +61,7 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { const { query: { kuery, environment, rangeFrom, rangeTo }, - } = useApmParams('/services/:serviceName'); + } = useApmParams('/services/:serviceName/transactions/view'); const { urlParams } = useUrlParams(); @@ -95,25 +95,21 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { end, percentileThreshold: DEFAULT_PERCENTILE_THRESHOLD, }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [environment, serviceName, kuery, start, end]); + }, [ + startFetch, + environment, + serviceName, + transactionName, + transactionType, + kuery, + start, + end, + ]); - // start fetching on load - // we want this effect to execute exactly once after the component mounts useEffect(() => { - if (isRunning) { - cancelFetch(); - } - startFetchHandler(); - - return () => { - // cancel any running async partial request when unmounting the component - // we want this effect to execute exactly once after the component mounts - cancelFetch(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [startFetchHandler]); + return cancelFetch; + }, [cancelFetch, startFetchHandler]); useEffect(() => { if (isErrorMessage(error)) { diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.test.ts b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.test.ts deleted file mode 100644 index f541c16e655ab..0000000000000 --- a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { getFormattedSelection } from './index'; - -describe('transaction_details/distribution', () => { - describe('getFormattedSelection', () => { - it('displays only one unit if from and to share the same unit', () => { - expect(getFormattedSelection([10000, 100000])).toEqual('10 - 100 ms'); - }); - - it('displays two units when from and to have different units', () => { - expect(getFormattedSelection([100000, 1000000000])).toEqual( - '100 ms - 17 min' - ); - }); - }); -}); diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.test.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.test.tsx new file mode 100644 index 0000000000000..5a9977b373c33 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.test.tsx @@ -0,0 +1,151 @@ +/* + * 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 { render, screen, waitFor } from '@testing-library/react'; +import { createMemoryHistory } from 'history'; +import React, { ReactNode } from 'react'; +import { of } from 'rxjs'; + +import { CoreStart } from 'kibana/public'; +import { merge } from 'lodash'; +import { dataPluginMock } from 'src/plugins/data/public/mocks'; +import type { IKibanaSearchResponse } from 'src/plugins/data/public'; +import { EuiThemeProvider } from 'src/plugins/kibana_react/common'; +import { createKibanaReactContext } from 'src/plugins/kibana_react/public'; +import type { SearchServiceRawResponse } from '../../../../../common/search_strategies/correlations/types'; +import { MockUrlParamsContextProvider } from '../../../../context/url_params_context/mock_url_params_context_provider'; +import { ApmPluginContextValue } from '../../../../context/apm_plugin/apm_plugin_context'; +import { + mockApmPluginContextValue, + MockApmPluginContextWrapper, +} from '../../../../context/apm_plugin/mock_apm_plugin_context'; +import { fromQuery } from '../../../shared/Links/url_helpers'; + +import { getFormattedSelection, TransactionDistribution } from './index'; + +function Wrapper({ + children, + dataSearchResponse, +}: { + children?: ReactNode; + dataSearchResponse: IKibanaSearchResponse; +}) { + const mockDataSearch = jest.fn(() => of(dataSearchResponse)); + + const dataPluginMockStart = dataPluginMock.createStartContract(); + const KibanaReactContext = createKibanaReactContext({ + data: { + ...dataPluginMockStart, + search: { + ...dataPluginMockStart.search, + search: mockDataSearch, + }, + }, + usageCollection: { reportUiCounter: () => {} }, + } as Partial); + + const httpGet = jest.fn(); + + const history = createMemoryHistory(); + jest.spyOn(history, 'push'); + jest.spyOn(history, 'replace'); + + history.replace({ + pathname: '/services/the-service-name/transactions/view', + search: fromQuery({ transactionName: 'the-transaction-name' }), + }); + + const mockPluginContext = (merge({}, mockApmPluginContextValue, { + core: { http: { get: httpGet } }, + }) as unknown) as ApmPluginContextValue; + + return ( + + + + + {children} + + + + + ); +} + +describe('transaction_details/distribution', () => { + describe('getFormattedSelection', () => { + it('displays only one unit if from and to share the same unit', () => { + expect(getFormattedSelection([10000, 100000])).toEqual('10 - 100 ms'); + }); + + it('displays two units when from and to have different units', () => { + expect(getFormattedSelection([100000, 1000000000])).toEqual( + '100 ms - 17 min' + ); + }); + }); + + describe('TransactionDistribution', () => { + it('shows loading indicator when the service is running and returned no results yet', async () => { + const onHasData = jest.fn(); + render( + + + + ); + + await waitFor(() => { + expect(screen.getByTestId('apmCorrelationsChart')).toBeInTheDocument(); + expect(screen.getByTestId('loading')).toBeInTheDocument(); + expect(onHasData).toHaveBeenLastCalledWith(false); + }); + }); + + it("doesn't show loading indicator when the service isn't running", async () => { + const onHasData = jest.fn(); + render( + + + + ); + + await waitFor(() => { + expect(screen.getByTestId('apmCorrelationsChart')).toBeInTheDocument(); + expect(screen.queryByTestId('loading')).toBeNull(); // it doesn't exist + expect(onHasData).toHaveBeenLastCalledWith(false); + }); + }); + }); +}); diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx index 86ebc04944cd5..2da61bc0fc555 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { BrushEndListener, XYBrushArea } from '@elastic/charts'; import { EuiBadge, @@ -21,7 +21,10 @@ import { getDurationFormatter } from '../../../../../common/utils/formatters'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { useTransactionDistributionFetcher } from '../../../../hooks/use_transaction_distribution_fetcher'; -import { TransactionDistributionChart } from '../../../shared/charts/transaction_distribution_chart'; +import { + OnHasData, + TransactionDistributionChart, +} from '../../../shared/charts/transaction_distribution_chart'; import { useUiTracker } from '../../../../../../observability/public'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { useApmParams } from '../../../../hooks/use_apm_params'; @@ -47,10 +50,11 @@ export function getFormattedSelection(selection: Selection): string { }`; } -interface Props { +interface TransactionDistributionProps { markerCurrentTransaction?: number; onChartSelection: BrushEndListener; onClearSelection: () => void; + onHasData: OnHasData; selection?: Selection; } @@ -58,8 +62,9 @@ export function TransactionDistribution({ markerCurrentTransaction, onChartSelection, onClearSelection, + onHasData, selection, -}: Props) { +}: TransactionDistributionProps) { const { core: { notifications }, } = useApmPluginContext(); @@ -68,7 +73,7 @@ export function TransactionDistribution({ const { query: { kuery, environment, rangeFrom, rangeTo }, - } = useApmParams('/services/:serviceName'); + } = useApmParams('/services/:serviceName/transactions/view'); const { start, end } = useTimeRange({ rangeFrom, rangeTo }); @@ -76,6 +81,16 @@ export function TransactionDistribution({ const { transactionName } = urlParams; + const [showSelection, setShowSelection] = useState(false); + + const onTransactionDistributionHasData: OnHasData = useCallback( + (hasData) => { + setShowSelection(hasData); + onHasData(hasData); + }, + [onHasData] + ); + const emptySelectionText = i18n.translate( 'xpack.apm.transactionDetails.emptySelectionText', { @@ -93,17 +108,12 @@ export function TransactionDistribution({ const { error, percentileThresholdValue, - isRunning, startFetch, cancelFetch, transactionDistribution, } = useTransactionDistributionFetcher(); - useEffect(() => { - if (isRunning) { - cancelFetch(); - } - + const startFetchHandler = useCallback(() => { startFetch({ environment, kuery, @@ -114,14 +124,21 @@ export function TransactionDistribution({ end, percentileThreshold: DEFAULT_PERCENTILE_THRESHOLD, }); + }, [ + startFetch, + environment, + serviceName, + transactionName, + transactionType, + kuery, + start, + end, + ]); - return () => { - // cancel any running async partial request when unmounting the component - // we want this effect to execute exactly once after the component mounts - cancelFetch(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [environment, serviceName, kuery, start, end]); + useEffect(() => { + startFetchHandler(); + return cancelFetch; + }, [cancelFetch, startFetchHandler]); useEffect(() => { if (isErrorMessage(error)) { @@ -166,7 +183,7 @@ export function TransactionDistribution({ - {!selection && ( + {showSelection && !selection && ( )} - {selection && ( + {showSelection && selection && ( diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/trace_samples_tab.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/trace_samples_tab.tsx index 0421fcd055d6c..ea02cfea5a941 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/trace_samples_tab.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/trace_samples_tab.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useState } from 'react'; import { EuiSpacer } from '@elastic/eui'; @@ -34,11 +34,17 @@ function TraceSamplesTab({ status: waterfallStatus, } = useWaterfallFetcher(); + const [ + transactionDistributionHasData, + setTransactionDistributionHasData, + ] = useState(false); + return ( <> - + {transactionDistributionHasData && ( + <> + - + + + )} ); } diff --git a/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx b/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx index 4098fc5e696db..695e62b3b7d78 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { FETCH_STATUS } from '../../../hooks/use_fetcher'; -interface Props { +export interface ChartContainerProps { hasData: boolean; status: FETCH_STATUS; height: number; @@ -24,7 +24,7 @@ export function ChartContainer({ status, hasData, id, -}: Props) { +}: ChartContainerProps) { if (!hasData && status === FETCH_STATUS.LOADING) { return ; } diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.tsx index c511a708058d3..a58a2887b1576 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { AnnotationDomainType, AreaSeries, @@ -35,7 +35,14 @@ import { HistogramItem } from '../../../../../common/search_strategies/correlati import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { useTheme } from '../../../../hooks/use_theme'; -import { ChartContainer } from '../chart_container'; +import { ChartContainer, ChartContainerProps } from '../chart_container'; + +export type TransactionDistributionChartLoadingState = Pick< + ChartContainerProps, + 'hasData' | 'status' +>; + +export type OnHasData = (hasData: boolean) => void; interface TransactionDistributionChartProps { field?: string; @@ -46,6 +53,7 @@ interface TransactionDistributionChartProps { markerPercentile: number; overallHistogram?: HistogramItem[]; onChartSelection?: BrushEndListener; + onHasData?: OnHasData; selection?: [number, number]; } @@ -103,6 +111,7 @@ export function TransactionDistributionChart({ markerPercentile, overallHistogram, onChartSelection, + onHasData, selection, }: TransactionDistributionChartProps) { const chartTheme = useChartTheme(); @@ -154,6 +163,24 @@ export function TransactionDistributionChart({ ] : undefined; + const chartLoadingState: TransactionDistributionChartLoadingState = useMemo( + () => ({ + hasData: + Array.isArray(patchedOverallHistogram) && + patchedOverallHistogram.length > 0, + status: Array.isArray(patchedOverallHistogram) + ? FETCH_STATUS.SUCCESS + : FETCH_STATUS.LOADING, + }), + [patchedOverallHistogram] + ); + + useEffect(() => { + if (onHasData) { + onHasData(chartLoadingState.hasData); + } + }, [chartLoadingState, onHasData]); + return (

0 - } - status={ - Array.isArray(patchedOverallHistogram) - ? FETCH_STATUS.SUCCESS - : FETCH_STATUS.LOADING - } + hasData={chartLoadingState.hasData} + status={chartLoadingState.status} > { })); } - const startFetch = (params: SearchServiceParams) => { - setFetchState((prevState) => ({ - ...prevState, - error: undefined, - isComplete: false, - })); - searchSubscription$.current?.unsubscribe(); - abortCtrl.current.abort(); - abortCtrl.current = new AbortController(); + const startFetch = useCallback( + (params: SearchServiceParams) => { + setFetchState((prevState) => ({ + ...prevState, + error: undefined, + isComplete: false, + })); + searchSubscription$.current?.unsubscribe(); + abortCtrl.current.abort(); + abortCtrl.current = new AbortController(); - const req = { params }; + const req = { params }; - // Submit the search request using the `data.search` service. - searchSubscription$.current = data.search - .search>(req, { - strategy: FAILED_TRANSACTIONS_CORRELATION_SEARCH_STRATEGY, - abortSignal: abortCtrl.current.signal, - }) - .subscribe({ - next: (res: IKibanaSearchResponse) => { - setResponse(res); - if (isCompleteResponse(res)) { - searchSubscription$.current?.unsubscribe(); + // Submit the search request using the `data.search` service. + searchSubscription$.current = data.search + .search>(req, { + strategy: FAILED_TRANSACTIONS_CORRELATION_SEARCH_STRATEGY, + abortSignal: abortCtrl.current.signal, + }) + .subscribe({ + next: (res: IKibanaSearchResponse) => { + setResponse(res); + if (isCompleteResponse(res)) { + searchSubscription$.current?.unsubscribe(); + setFetchState((prevState) => ({ + ...prevState, + isRunnning: false, + isComplete: true, + })); + } else if (isErrorResponse(res)) { + searchSubscription$.current?.unsubscribe(); + setFetchState((prevState) => ({ + ...prevState, + error: (res as unknown) as Error, + isRunning: false, + })); + } + }, + error: (error: Error) => { setFetchState((prevState) => ({ ...prevState, - isRunnning: false, - isComplete: true, + error, + isRunning: false, })); - } else if (isErrorResponse(res)) { - searchSubscription$.current?.unsubscribe(); - setFetchState((prevState) => ({ - ...prevState, - error: (res as unknown) as Error, - setIsRunning: false, - })); - } - }, - error: (error: Error) => { - setFetchState((prevState) => ({ - ...prevState, - error, - setIsRunning: false, - })); - }, - }); - }; + }, + }); + }, + [data.search, setFetchState] + ); - const cancelFetch = () => { + const cancelFetch = useCallback(() => { searchSubscription$.current?.unsubscribe(); searchSubscription$.current = undefined; abortCtrl.current.abort(); setFetchState((prevState) => ({ ...prevState, - setIsRunning: false, + isRunning: false, })); - }; + }, [setFetchState]); return { ...fetchState, diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts index 870dc8030d70b..2ff1b83ef1782 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { useRef, useState } from 'react'; +import { useCallback, useRef, useState } from 'react'; import type { Subscription } from 'rxjs'; import { IKibanaSearchRequest, @@ -14,31 +14,21 @@ import { isErrorResponse, } from '../../../../../src/plugins/data/public'; import type { - HistogramItem, SearchServiceParams, - SearchServiceValue, + SearchServiceRawResponse, } from '../../common/search_strategies/correlations/types'; import { useKibana } from '../../../../../src/plugins/kibana_react/public'; import { ApmPluginStartDeps } from '../plugin'; -interface RawResponse { - percentileThresholdValue?: number; - took: number; - values: SearchServiceValue[]; - overallHistogram: HistogramItem[]; - log: string[]; - ccsWarning: boolean; -} - interface TransactionDistributionFetcherState { error?: Error; isComplete: boolean; isRunning: boolean; loaded: number; - ccsWarning: RawResponse['ccsWarning']; - log: RawResponse['log']; - transactionDistribution?: RawResponse['overallHistogram']; - percentileThresholdValue?: RawResponse['percentileThresholdValue']; + ccsWarning: SearchServiceRawResponse['ccsWarning']; + log: SearchServiceRawResponse['log']; + transactionDistribution?: SearchServiceRawResponse['overallHistogram']; + percentileThresholdValue?: SearchServiceRawResponse['percentileThresholdValue']; timeTook?: number; total: number; } @@ -63,7 +53,9 @@ export function useTransactionDistributionFetcher() { const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(); - function setResponse(response: IKibanaSearchResponse) { + function setResponse( + response: IKibanaSearchResponse + ) { setFetchState((prevState) => ({ ...prevState, isRunning: response.isRunning || false, @@ -83,71 +75,81 @@ export function useTransactionDistributionFetcher() { response.rawResponse?.percentileThresholdValue, } : {}), + // if loading is done but didn't return any data for the overall histogram, + // set it to an empty array so the consuming chart component knows loading is done. + ...(!response.isRunning && + response.rawResponse?.overallHistogram === undefined + ? { transactionDistribution: [] } + : {}), })); } - const startFetch = ( - params: Omit - ) => { - setFetchState((prevState) => ({ - ...prevState, - error: undefined, - isComplete: false, - })); - searchSubscription$.current?.unsubscribe(); - abortCtrl.current.abort(); - abortCtrl.current = new AbortController(); + const startFetch = useCallback( + (params: Omit) => { + setFetchState((prevState) => ({ + ...prevState, + error: undefined, + isComplete: false, + })); + searchSubscription$.current?.unsubscribe(); + abortCtrl.current.abort(); + abortCtrl.current = new AbortController(); - const searchServiceParams: SearchServiceParams = { - ...params, - analyzeCorrelations: false, - }; - const req = { params: searchServiceParams }; + const searchServiceParams: SearchServiceParams = { + ...params, + analyzeCorrelations: false, + }; + const req = { params: searchServiceParams }; - // Submit the search request using the `data.search` service. - searchSubscription$.current = data.search - .search>(req, { - strategy: 'apmCorrelationsSearchStrategy', - abortSignal: abortCtrl.current.signal, - }) - .subscribe({ - next: (res: IKibanaSearchResponse) => { - setResponse(res); - if (isCompleteResponse(res)) { - searchSubscription$.current?.unsubscribe(); - setFetchState((prevState) => ({ - ...prevState, - isRunnning: false, - isComplete: true, - })); - } else if (isErrorResponse(res)) { - searchSubscription$.current?.unsubscribe(); + // Submit the search request using the `data.search` service. + searchSubscription$.current = data.search + .search< + IKibanaSearchRequest, + IKibanaSearchResponse + >(req, { + strategy: 'apmCorrelationsSearchStrategy', + abortSignal: abortCtrl.current.signal, + }) + .subscribe({ + next: (res: IKibanaSearchResponse) => { + setResponse(res); + if (isCompleteResponse(res)) { + searchSubscription$.current?.unsubscribe(); + setFetchState((prevState) => ({ + ...prevState, + isRunnning: false, + isComplete: true, + })); + } else if (isErrorResponse(res)) { + searchSubscription$.current?.unsubscribe(); + setFetchState((prevState) => ({ + ...prevState, + error: (res as unknown) as Error, + isRunning: false, + })); + } + }, + error: (error: Error) => { setFetchState((prevState) => ({ ...prevState, - error: (res as unknown) as Error, - setIsRunning: false, + error, + isRunning: false, })); - } - }, - error: (error: Error) => { - setFetchState((prevState) => ({ - ...prevState, - error, - setIsRunning: false, - })); - }, - }); - }; + }, + }); + }, + [data.search, setFetchState] + ); - const cancelFetch = () => { + const cancelFetch = useCallback(() => { searchSubscription$.current?.unsubscribe(); searchSubscription$.current = undefined; abortCtrl.current.abort(); setFetchState((prevState) => ({ ...prevState, - setIsRunning: false, + isRunning: false, })); - }; + }, [setFetchState]); return { ...fetchState, diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_latency_correlations_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_latency_correlations_fetcher.ts index 49f2a279f4931..0b035c6af2354 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_latency_correlations_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_latency_correlations_fetcher.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { useRef, useState } from 'react'; +import { useCallback, useRef, useState } from 'react'; import type { Subscription } from 'rxjs'; import { IKibanaSearchRequest, @@ -14,32 +14,22 @@ import { isErrorResponse, } from '../../../../../src/plugins/data/public'; import type { - HistogramItem, SearchServiceParams, - SearchServiceValue, + SearchServiceRawResponse, } from '../../common/search_strategies/correlations/types'; import { useKibana } from '../../../../../src/plugins/kibana_react/public'; import { ApmPluginStartDeps } from '../plugin'; -interface RawResponse { - percentileThresholdValue?: number; - took: number; - values: SearchServiceValue[]; - overallHistogram: HistogramItem[]; - log: string[]; - ccsWarning: boolean; -} - interface TransactionLatencyCorrelationsFetcherState { error?: Error; isComplete: boolean; isRunning: boolean; loaded: number; - ccsWarning: RawResponse['ccsWarning']; - histograms: RawResponse['values']; - log: RawResponse['log']; - overallHistogram?: RawResponse['overallHistogram']; - percentileThresholdValue?: RawResponse['percentileThresholdValue']; + ccsWarning: SearchServiceRawResponse['ccsWarning']; + histograms: SearchServiceRawResponse['values']; + log: SearchServiceRawResponse['log']; + overallHistogram?: SearchServiceRawResponse['overallHistogram']; + percentileThresholdValue?: SearchServiceRawResponse['percentileThresholdValue']; timeTook?: number; total: number; } @@ -65,7 +55,9 @@ export const useTransactionLatencyCorrelationsFetcher = () => { const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(); - function setResponse(response: IKibanaSearchResponse) { + function setResponse( + response: IKibanaSearchResponse + ) { setFetchState((prevState) => ({ ...prevState, isRunning: response.isRunning || false, @@ -85,71 +77,86 @@ export const useTransactionLatencyCorrelationsFetcher = () => { response.rawResponse?.percentileThresholdValue, } : {}), + // if loading is done but didn't return any data for the overall histogram, + // set it to an empty array so the consuming chart component knows loading is done. + ...(!response.isRunning && + response.rawResponse?.overallHistogram === undefined + ? { overallHistogram: [] } + : {}), })); } - const startFetch = ( - params: Omit - ) => { - setFetchState((prevState) => ({ - ...prevState, - error: undefined, - isComplete: false, - })); - searchSubscription$.current?.unsubscribe(); - abortCtrl.current.abort(); - abortCtrl.current = new AbortController(); + const startFetch = useCallback( + (params: Omit) => { + setFetchState((prevState) => ({ + ...prevState, + error: undefined, + isComplete: false, + })); + searchSubscription$.current?.unsubscribe(); + abortCtrl.current.abort(); + abortCtrl.current = new AbortController(); - const searchServiceParams: SearchServiceParams = { - ...params, - analyzeCorrelations: true, - }; - const req = { params: searchServiceParams }; + const searchServiceParams: SearchServiceParams = { + ...params, + analyzeCorrelations: true, + }; + const req = { params: searchServiceParams }; - // Submit the search request using the `data.search` service. - searchSubscription$.current = data.search - .search>(req, { - strategy: 'apmCorrelationsSearchStrategy', - abortSignal: abortCtrl.current.signal, - }) - .subscribe({ - next: (res: IKibanaSearchResponse) => { - setResponse(res); - if (isCompleteResponse(res)) { - searchSubscription$.current?.unsubscribe(); + // Submit the search request using the `data.search` service. + searchSubscription$.current = data.search + .search< + IKibanaSearchRequest, + IKibanaSearchResponse + >(req, { + strategy: 'apmCorrelationsSearchStrategy', + abortSignal: abortCtrl.current.signal, + }) + .subscribe({ + next: (res: IKibanaSearchResponse) => { + setResponse(res); + if (isCompleteResponse(res)) { + searchSubscription$.current?.unsubscribe(); + setFetchState((prevState) => ({ + ...prevState, + isRunnning: false, + isComplete: true, + })); + } else if (isErrorResponse(res)) { + searchSubscription$.current?.unsubscribe(); + setFetchState((prevState) => ({ + ...prevState, + error: (res as unknown) as Error, + isRunning: false, + })); + } + }, + error: (error: Error) => { setFetchState((prevState) => ({ ...prevState, - isRunnning: false, - isComplete: true, + error, + isRunning: false, })); - } else if (isErrorResponse(res)) { - searchSubscription$.current?.unsubscribe(); - setFetchState((prevState) => ({ - ...prevState, - error: (res as unknown) as Error, - setIsRunning: false, - })); - } - }, - error: (error: Error) => { - setFetchState((prevState) => ({ - ...prevState, - error, - setIsRunning: false, - })); - }, - }); - }; + }, + }); + }, + [data.search, setFetchState] + ); - const cancelFetch = () => { + const cancelFetch = useCallback(() => { searchSubscription$.current?.unsubscribe(); searchSubscription$.current = undefined; abortCtrl.current.abort(); setFetchState((prevState) => ({ ...prevState, - setIsRunning: false, + // If we didn't receive data for the overall histogram yet + // set it to an empty array to indicate loading stopped. + ...(prevState.overallHistogram === undefined + ? { overallHistogram: [] } + : {}), + isRunning: false, })); - }; + }, [setFetchState]); return { ...fetchState, diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.ts index 3601f19ad7051..7f67147a75580 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.ts @@ -16,6 +16,7 @@ import { import type { SearchServiceParams, + SearchServiceRawResponse, SearchServiceValue, } from '../../../../common/search_strategies/correlations/types'; @@ -100,20 +101,22 @@ export const apmCorrelationsSearchStrategyProvider = ( const took = Date.now() - started; + const rawResponse: SearchServiceRawResponse = { + ccsWarning, + log, + took, + values, + percentileThresholdValue, + overallHistogram, + }; + return of({ id, loaded, total, isRunning, isPartial: isRunning, - rawResponse: { - ccsWarning, - log, - took, - values, - percentileThresholdValue, - overallHistogram, - }, + rawResponse, }); }, cancel: async (id, options, deps) => { From 74f3b76592e843f76e4877e4263d3b5ae8be6b1c Mon Sep 17 00:00:00 2001 From: Kyle Pollich Date: Fri, 27 Aug 2021 08:08:53 -0400 Subject: [PATCH 10/12] [Fleet] Fix upgrade link in Fleet policy table (#110228) * Fix upgrade link in Fleet policy table * Ensure upgrade page displays as upgrade even without from prop --- .../components/package_policies/package_policies_table.tsx | 2 +- .../agent_policy/edit_package_policy_page/index.tsx | 6 ++---- .../agent_policy/upgrade_package_policy_page/index.tsx | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx index c84df5790ecb5..eb5f8e6c6a9b8 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx @@ -237,7 +237,7 @@ export const PackagePoliciesTable: React.FunctionComponent = ({ upgradePackagePolicyHref={`${getHref('upgrade_package_policy', { policyId: agentPolicy.id, packagePolicyId: packagePolicy.id, - })}`} + })}?from=fleet-policy-list`} /> ); }, 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 8a1ffe21a90e0..67cff3e2d0304 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 @@ -71,8 +71,9 @@ export const EditPackagePolicyPage = memo(() => { export const EditPackagePolicyForm = memo<{ packagePolicyId: string; + isUpgrade?: boolean; from?: EditPackagePolicyFrom; -}>(({ packagePolicyId, from = 'edit' }) => { +}>(({ packagePolicyId, isUpgrade = false, from = 'edit' }) => { const { application, notifications } = useStartServices(); const { agents: { enabled: isFleetEnabled }, @@ -99,9 +100,6 @@ export const EditPackagePolicyForm = memo<{ >(); const [dryRunData, setDryRunData] = useState(); - const isUpgrade = - from === 'upgrade-from-fleet-policy-list' || from === 'upgrade-from-integrations-policy-list'; - const policyId = agentPolicy?.id ?? ''; // Retrieve agent policy, package, and package policy info diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/upgrade_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/upgrade_package_policy_page/index.tsx index e9442f84d228e..18cf7847cd29b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/upgrade_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/upgrade_package_policy_page/index.tsx @@ -30,5 +30,5 @@ export const UpgradePackagePolicyPage = memo(() => { from = 'upgrade-from-integrations-policy-list'; } - return ; + return ; }); From c92978b9e58c1a7900460a9ffbe788e96218ace0 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Fri, 27 Aug 2021 15:55:59 +0100 Subject: [PATCH 11/12] chore(NA): moving @kbn/securitysolution-list-constants to babel transpiler (#110269) * chore(NA): moving @kbn/securitysolution-list-constants to babel transpiler * chore(NA): add web targets --- .../.babelrc | 4 +++ .../.babelrc.browser | 4 +++ .../BUILD.bazel | 29 +++++++++++++------ .../package.json | 5 ++-- .../tsconfig.json | 3 +- 5 files changed, 33 insertions(+), 12 deletions(-) create mode 100644 packages/kbn-securitysolution-list-constants/.babelrc create mode 100644 packages/kbn-securitysolution-list-constants/.babelrc.browser diff --git a/packages/kbn-securitysolution-list-constants/.babelrc b/packages/kbn-securitysolution-list-constants/.babelrc new file mode 100644 index 0000000000000..40a198521b903 --- /dev/null +++ b/packages/kbn-securitysolution-list-constants/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["@kbn/babel-preset/node_preset"], + "ignore": ["**/*.test.ts", "**/*.test.tsx"] +} diff --git a/packages/kbn-securitysolution-list-constants/.babelrc.browser b/packages/kbn-securitysolution-list-constants/.babelrc.browser new file mode 100644 index 0000000000000..71bbfbcd6eb2f --- /dev/null +++ b/packages/kbn-securitysolution-list-constants/.babelrc.browser @@ -0,0 +1,4 @@ +{ + "presets": ["@kbn/babel-preset/webpack_preset"], + "ignore": ["**/*.test.ts", "**/*.test.tsx"] +} diff --git a/packages/kbn-securitysolution-list-constants/BUILD.bazel b/packages/kbn-securitysolution-list-constants/BUILD.bazel index e56c96e0b5da9..9b3de9520f6a1 100644 --- a/packages/kbn-securitysolution-list-constants/BUILD.bazel +++ b/packages/kbn-securitysolution-list-constants/BUILD.bazel @@ -1,5 +1,6 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") +load("//src/dev/bazel:index.bzl", "jsts_transpiler") PKG_BASE_NAME = "kbn-securitysolution-list-constants" @@ -27,16 +28,25 @@ NPM_MODULE_EXTRA_FILES = [ "README.md", ] -SRC_DEPS = [ - "@npm//tslib", -] +RUNTIME_DEPS = [] TYPES_DEPS = [ "@npm//@types/jest", "@npm//@types/node", ] -DEPS = SRC_DEPS + TYPES_DEPS +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +jsts_transpiler( + name = "target_web", + srcs = SRCS, + build_pkg_name = package_name(), + config_file = ".babelrc.browser" +) ts_config( name = "tsconfig", @@ -48,24 +58,25 @@ ts_config( ) ts_project( - name = "tsc", + name = "tsc_types", srcs = SRCS, + deps = TYPES_DEPS, args = ["--pretty"], declaration = True, declaration_map = True, - out_dir = "target", + emit_declaration_only = True, + out_dir = "target_types", root_dir = "src", source_map = True, tsconfig = ":tsconfig", - deps = DEPS, ) js_library( name = PKG_BASE_NAME, - package_name = PKG_REQUIRE_NAME, srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":target_web", ":tsc_types"], + package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], - deps = DEPS + [":tsc"], ) pkg_npm( diff --git a/packages/kbn-securitysolution-list-constants/package.json b/packages/kbn-securitysolution-list-constants/package.json index b9d65734aff56..37f15fd06b0bc 100644 --- a/packages/kbn-securitysolution-list-constants/package.json +++ b/packages/kbn-securitysolution-list-constants/package.json @@ -3,7 +3,8 @@ "version": "1.0.0", "description": "security solution list constants to use across plugins such lists, security_solution, cases, etc...", "license": "SSPL-1.0 OR Elastic License 2.0", - "main": "./target/index.js", - "types": "./target/index.d.ts", + "browser": "./target_web/index.js", + "main": "./target_node/index.js", + "types": "./target_types/index.d.ts", "private": true } diff --git a/packages/kbn-securitysolution-list-constants/tsconfig.json b/packages/kbn-securitysolution-list-constants/tsconfig.json index 769b5df990e6b..8697cbd61580a 100644 --- a/packages/kbn-securitysolution-list-constants/tsconfig.json +++ b/packages/kbn-securitysolution-list-constants/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "declaration": true, "declarationMap": true, - "outDir": "target", + "emitDeclarationOnly": true, + "outDir": "target_types", "rootDir": "src", "sourceMap": true, "sourceRoot": "../../../../packages/kbn-securitysolution-list-constants/src", From 127402415c04208aa3bf144be6652af5d0594e39 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Fri, 27 Aug 2021 15:56:18 +0100 Subject: [PATCH 12/12] chore(NA): moving @kbn/securitysolution-list-api to babel transpiler (#110265) * chore(NA): moving @kbn/securitysolution-list-api to babel transpiler * chore(NA): add web targets --- .../kbn-securitysolution-list-api/.babelrc | 4 +++ .../.babelrc.browser | 4 +++ .../kbn-securitysolution-list-api/BUILD.bazel | 35 ++++++++++++++----- .../package.json | 5 +-- .../tsconfig.json | 3 +- 5 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 packages/kbn-securitysolution-list-api/.babelrc create mode 100644 packages/kbn-securitysolution-list-api/.babelrc.browser diff --git a/packages/kbn-securitysolution-list-api/.babelrc b/packages/kbn-securitysolution-list-api/.babelrc new file mode 100644 index 0000000000000..40a198521b903 --- /dev/null +++ b/packages/kbn-securitysolution-list-api/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["@kbn/babel-preset/node_preset"], + "ignore": ["**/*.test.ts", "**/*.test.tsx"] +} diff --git a/packages/kbn-securitysolution-list-api/.babelrc.browser b/packages/kbn-securitysolution-list-api/.babelrc.browser new file mode 100644 index 0000000000000..71bbfbcd6eb2f --- /dev/null +++ b/packages/kbn-securitysolution-list-api/.babelrc.browser @@ -0,0 +1,4 @@ +{ + "presets": ["@kbn/babel-preset/webpack_preset"], + "ignore": ["**/*.test.ts", "**/*.test.tsx"] +} diff --git a/packages/kbn-securitysolution-list-api/BUILD.bazel b/packages/kbn-securitysolution-list-api/BUILD.bazel index 726d75d8c86b8..6858a9389119f 100644 --- a/packages/kbn-securitysolution-list-api/BUILD.bazel +++ b/packages/kbn-securitysolution-list-api/BUILD.bazel @@ -1,5 +1,6 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") +load("//src/dev/bazel:index.bzl", "jsts_transpiler") PKG_BASE_NAME = "kbn-securitysolution-list-api" @@ -27,21 +28,36 @@ NPM_MODULE_EXTRA_FILES = [ "README.md", ] -SRC_DEPS = [ +RUNTIME_DEPS = [ + "//packages/kbn-securitysolution-io-ts-list-types", "//packages/kbn-securitysolution-io-ts-utils", "//packages/kbn-securitysolution-list-constants", - "//packages/kbn-securitysolution-io-ts-list-types", "@npm//fp-ts", "@npm//io-ts", - "@npm//tslib", ] TYPES_DEPS = [ + "//packages/kbn-securitysolution-io-ts-list-types", + "//packages/kbn-securitysolution-io-ts-utils", + "//packages/kbn-securitysolution-list-constants", + "@npm//fp-ts", + "@npm//io-ts", "@npm//@types/jest", "@npm//@types/node", ] -DEPS = SRC_DEPS + TYPES_DEPS +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +jsts_transpiler( + name = "target_web", + srcs = SRCS, + build_pkg_name = package_name(), + config_file = ".babelrc.browser" +) ts_config( name = "tsconfig", @@ -53,24 +69,25 @@ ts_config( ) ts_project( - name = "tsc", + name = "tsc_types", srcs = SRCS, + deps = TYPES_DEPS, args = ["--pretty"], declaration = True, declaration_map = True, - out_dir = "target", + emit_declaration_only = True, + out_dir = "target_types", root_dir = "src", source_map = True, tsconfig = ":tsconfig", - deps = DEPS, ) js_library( name = PKG_BASE_NAME, - package_name = PKG_REQUIRE_NAME, srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":target_web", ":tsc_types"], + package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], - deps = DEPS + [":tsc"], ) pkg_npm( diff --git a/packages/kbn-securitysolution-list-api/package.json b/packages/kbn-securitysolution-list-api/package.json index 11108c61bfde1..8454f13d841b4 100644 --- a/packages/kbn-securitysolution-list-api/package.json +++ b/packages/kbn-securitysolution-list-api/package.json @@ -3,7 +3,8 @@ "version": "1.0.0", "description": "security solution list REST API", "license": "SSPL-1.0 OR Elastic License 2.0", - "main": "./target/index.js", - "types": "./target/index.d.ts", + "browser": "./target_web/index.js", + "main": "./target_node/index.js", + "types": "./target_types/index.d.ts", "private": true } diff --git a/packages/kbn-securitysolution-list-api/tsconfig.json b/packages/kbn-securitysolution-list-api/tsconfig.json index c2ac6d3d92286..d51cd3ac71d8d 100644 --- a/packages/kbn-securitysolution-list-api/tsconfig.json +++ b/packages/kbn-securitysolution-list-api/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "declaration": true, "declarationMap": true, - "outDir": "target", + "emitDeclarationOnly": true, + "outDir": "target_types", "rootDir": "src", "sourceMap": true, "sourceRoot": "../../../../packages/kbn-securitysolution-list-api/src",