[APM] Fix loading state in trace samples (#142831)

* Fix loading state in trace samples

* Add space before the loading to avoid jumping

* refactor results from fetch
This commit is contained in:
Miriam 2022-10-17 11:15:06 +01:00 committed by GitHub
parent 4b88273767
commit 9782417578
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 177 additions and 144 deletions

View file

@ -5,14 +5,14 @@
* 2.0.
*/
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React, { useEffect, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { useHistory } from 'react-router-dom';
import {
TraceSearchQuery,
TraceSearchType,
} from '../../../../common/trace_explorer';
import { useApmParams } from '../../../hooks/use_apm_params';
import { useFetcher, FETCH_STATUS } from '../../../hooks/use_fetcher';
import { useFetcher } from '../../../hooks/use_fetcher';
import { useTimeRange } from '../../../hooks/use_time_range';
import { ApmDatePicker } from '../../shared/date_picker/apm_date_picker';
import { fromQuery, toQuery, push } from '../../shared/links/url_helpers';
@ -20,6 +20,10 @@ import { useWaterfallFetcher } from '../transaction_details/use_waterfall_fetche
import { WaterfallWithSummary } from '../transaction_details/waterfall_with_summary';
import { TraceSearchBox } from './trace_search_box';
const INITIAL_DATA = {
traceSamples: [],
};
export function TraceExplorer() {
const [query, setQuery] = useState<TraceSearchQuery>({
query: '',
@ -54,7 +58,11 @@ export function TraceExplorer() {
rangeTo,
});
const { data: traceSamplesData, status: traceSamplesStatus } = useFetcher(
const {
data = INITIAL_DATA,
status,
error,
} = useFetcher(
(callApmApi) => {
return callApmApi('GET /internal/apm/traces/find', {
params: {
@ -72,7 +80,7 @@ export function TraceExplorer() {
);
useEffect(() => {
const nextSample = traceSamplesData?.samples[0];
const nextSample = data.traceSamples[0];
const nextWaterfallItemId = '';
history.replace({
...history.location,
@ -83,18 +91,23 @@ export function TraceExplorer() {
waterfallItemId: nextWaterfallItemId,
}),
});
}, [traceSamplesData, history]);
}, [data, history]);
const { waterfall, status: waterfallStatus } = useWaterfallFetcher({
const waterfallFetchResult = useWaterfallFetcher({
traceId,
transactionId,
start,
end,
});
const isLoading =
traceSamplesStatus === FETCH_STATUS.LOADING ||
waterfallStatus === FETCH_STATUS.LOADING;
const traceSamplesFetchResult = useMemo(
() => ({
data,
status,
error,
}),
[data, status, error]
);
return (
<EuiFlexGroup direction="column" gutterSize="s">
@ -127,8 +140,9 @@ export function TraceExplorer() {
</EuiFlexItem>
<EuiFlexItem>
<WaterfallWithSummary
waterfallFetchResult={waterfallFetchResult}
traceSamplesFetchResult={traceSamplesFetchResult}
environment={environment}
isLoading={isLoading}
onSampleClick={(sample) => {
push(history, {
query: {
@ -145,11 +159,12 @@ export function TraceExplorer() {
},
});
}}
traceSamples={traceSamplesData?.samples ?? []}
waterfall={waterfall}
detailTab={detailTab}
waterfallItemId={waterfallItemId}
serviceName={waterfall.entryWaterfallTransaction?.doc.service.name}
serviceName={
waterfallFetchResult.waterfall.entryWaterfallTransaction?.doc
.service.name
}
/>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -87,8 +87,11 @@ describe('transaction_details/distribution', () => {
<TransactionDistribution
onChartSelection={jest.fn()}
onClearSelection={jest.fn()}
traceSamples={[]}
traceSamplesStatus={useFetcherModule.FETCH_STATUS.LOADING}
traceSamplesFetchResult={{
data: { traceSamples: [] },
status: useFetcherModule.FETCH_STATUS.LOADING,
error: undefined,
}}
/>,
{ wrapper: Wrapper }
@ -112,8 +115,11 @@ describe('transaction_details/distribution', () => {
<TransactionDistribution
onChartSelection={jest.fn()}
onClearSelection={jest.fn()}
traceSamples={[]}
traceSamplesStatus={useFetcherModule.FETCH_STATUS.SUCCESS}
traceSamplesFetchResult={{
data: { traceSamples: [] },
status: useFetcherModule.FETCH_STATUS.LOADING,
error: undefined,
}}
/>
</Wrapper>
);

View file

@ -12,9 +12,7 @@ import { useHistory } from 'react-router-dom';
import { ProcessorEvent } from '@kbn/observability-plugin/common';
import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params';
import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
import type { TabContentProps } from '../types';
import { useWaterfallFetcher } from '../use_waterfall_fetcher';
import { WaterfallWithSummary } from '../waterfall_with_summary';
@ -26,21 +24,20 @@ import { HeightRetainer } from '../../../shared/height_retainer';
import { fromQuery, toQuery } from '../../../shared/links/url_helpers';
import { TransactionTab } from '../waterfall_with_summary/transaction_tabs';
import { useTransactionDistributionChartData } from './use_transaction_distribution_chart_data';
import { TraceSamplesFetchResult } from '../../../../hooks/use_transaction_trace_samples_fetcher';
interface TransactionDistributionProps {
onChartSelection: (event: XYBrushEvent) => void;
onClearSelection: () => void;
selection?: [number, number];
traceSamples: TabContentProps['traceSamples'];
traceSamplesStatus: FETCH_STATUS;
traceSamplesFetchResult: TraceSamplesFetchResult;
}
export function TransactionDistribution({
onChartSelection,
onClearSelection,
selection,
traceSamples,
traceSamplesStatus,
traceSamplesFetchResult,
}: TransactionDistributionProps) {
const { urlParams } = useLegacyUrlParams();
const { traceId, transactionId } = urlParams;
@ -52,7 +49,7 @@ export function TransactionDistribution({
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
const history = useHistory();
const { waterfall, status: waterfallStatus } = useWaterfallFetcher({
const waterfallFetchResult = useWaterfallFetcher({
traceId,
transactionId,
start,
@ -65,12 +62,10 @@ export function TransactionDistribution({
} = useApmParams('/services/{serviceName}/transactions/view');
const { serviceName } = useApmServiceContext();
const isLoading =
waterfallStatus === FETCH_STATUS.LOADING ||
traceSamplesStatus === FETCH_STATUS.LOADING;
const markerCurrentEvent =
waterfall.entryWaterfallTransaction?.doc.transaction.duration.us;
waterfallFetchResult.waterfall.entryWaterfallTransaction?.doc.transaction
.duration.us;
const {
chartData,
@ -121,9 +116,8 @@ export function TransactionDistribution({
serviceName={serviceName}
waterfallItemId={waterfallItemId}
detailTab={detailTab as TransactionTab | undefined}
waterfall={waterfall}
isLoading={isLoading}
traceSamples={traceSamples}
waterfallFetchResult={waterfallFetchResult}
traceSamplesFetchResult={traceSamplesFetchResult}
/>
</div>
</HeightRetainer>

View file

@ -18,8 +18,7 @@ import { useLicenseContext } from '../../../context/license/use_license_context'
import { LicensePrompt } from '../../shared/license_prompt';
import { FailedTransactionsCorrelations } from '../correlations/failed_transactions_correlations';
import type { TabContentProps } from './types';
import { TabContentProps } from './transaction_details_tabs';
function FailedTransactionsCorrelationsTab({ onFilter }: TabContentProps) {
const license = useLicenseContext();

View file

@ -18,8 +18,7 @@ import { useLicenseContext } from '../../../context/license/use_license_context'
import { LicensePrompt } from '../../shared/license_prompt';
import { LatencyCorrelations } from '../correlations/latency_correlations';
import type { TabContentProps } from './types';
import { TabContentProps } from './transaction_details_tabs';
function LatencyCorrelationsTab({ onFilter }: TabContentProps) {
const license = useLicenseContext();

View file

@ -10,15 +10,14 @@ import React from 'react';
import { i18n } from '@kbn/i18n';
import { TransactionDistribution } from './distribution';
import type { TabContentProps } from './types';
import { TabContentProps } from './transaction_details_tabs';
function TraceSamplesTab({
selectSampleFromChartSelection,
clearChartSelection,
sampleRangeFrom,
sampleRangeTo,
traceSamples,
traceSamplesStatus,
traceSamplesFetchResult,
}: TabContentProps) {
return (
<TransactionDistribution
@ -29,8 +28,7 @@ function TraceSamplesTab({
? [sampleRangeFrom, sampleRangeTo]
: undefined
}
traceSamplesStatus={traceSamplesStatus}
traceSamples={traceSamples}
traceSamplesFetchResult={traceSamplesFetchResult}
/>
);
}

View file

@ -11,9 +11,13 @@ import { omit } from 'lodash';
import { useHistory } from 'react-router-dom';
import { EuiPanel, EuiSpacer, EuiTabs, EuiTab } from '@elastic/eui';
import { XYBrushEvent } from '@elastic/charts';
import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_params';
import { useApmParams } from '../../../hooks/use_apm_params';
import { useTransactionTraceSamplesFetcher } from '../../../hooks/use_transaction_trace_samples_fetcher';
import {
TraceSamplesFetchResult,
useTransactionTraceSamplesFetcher,
} from '../../../hooks/use_transaction_trace_samples_fetcher';
import { maybe } from '../../../../common/utils/maybe';
import { fromQuery, toQuery } from '../../shared/links/url_helpers';
@ -24,6 +28,15 @@ import { traceSamplesTab } from './trace_samples_tab';
import { useSampleChartSelection } from '../../../hooks/use_sample_chart_selection';
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
export interface TabContentProps {
clearChartSelection: () => void;
onFilter: () => void;
sampleRangeFrom?: number;
sampleRangeTo?: number;
selectSampleFromChartSelection: (selection: XYBrushEvent) => void;
traceSamplesFetchResult: TraceSamplesFetchResult;
}
const tabs = [
traceSamplesTab,
latencyCorrelationsTab,
@ -41,15 +54,14 @@ export function TransactionDetailsTabs() {
tabs.find((tab) => tab.key === currentTab) ?? traceSamplesTab;
const { environment, kuery, transactionName } = query;
const { traceSamplesData, traceSamplesStatus } =
useTransactionTraceSamplesFetcher({
transactionName,
kuery,
environment,
});
const traceSamplesFetchResult = useTransactionTraceSamplesFetcher({
transactionName,
kuery,
environment,
});
const { sampleRangeFrom, sampleRangeTo, transactionId, traceId } = urlParams;
const { traceSamples } = traceSamplesData;
const { clearChartSelection, selectSampleFromChartSelection } =
useSampleChartSelection();
@ -65,14 +77,19 @@ export function TransactionDetailsTabs() {
}, [traceSamplesTabKey]);
useEffect(() => {
const selectedSample = traceSamples.find(
const selectedSample = traceSamplesFetchResult.data?.traceSamples.find(
(sample) =>
sample.transactionId === transactionId && sample.traceId === traceId
);
if (traceSamplesStatus === FETCH_STATUS.SUCCESS && !selectedSample) {
if (
traceSamplesFetchResult.status === FETCH_STATUS.SUCCESS &&
!selectedSample
) {
// selected sample was not found. select a new one:
const preferredSample = maybe(traceSamples[0]);
const preferredSample = maybe(
traceSamplesFetchResult.data?.traceSamples[0]
);
history.replace({
...history.location,
@ -85,7 +102,7 @@ export function TransactionDetailsTabs() {
}),
});
}
}, [history, traceSamples, transactionId, traceId, traceSamplesStatus]);
}, [history, transactionId, traceId, traceSamplesFetchResult]);
return (
<>
@ -112,8 +129,7 @@ export function TransactionDetailsTabs() {
sampleRangeFrom,
sampleRangeTo,
selectSampleFromChartSelection,
traceSamples,
traceSamplesStatus,
traceSamplesFetchResult,
}}
/>
</EuiPanel>

View file

@ -1,21 +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 { XYBrushEvent } from '@elastic/charts';
import type { TraceSample } from '../../../hooks/use_transaction_trace_samples_fetcher';
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
export interface TabContentProps {
clearChartSelection: () => void;
onFilter: () => void;
sampleRangeFrom?: number;
sampleRangeTo?: number;
selectSampleFromChartSelection: (selection: XYBrushEvent) => void;
traceSamples: TraceSample[];
traceSamplesStatus: FETCH_STATUS;
}

View file

@ -16,6 +16,7 @@ const INITIAL_DATA: APIReturnType<'GET /internal/apm/traces/{traceId}'> = {
exceedsMax: false,
linkedChildrenOfSpanCountBySpanId: {},
};
export type WaterfallFetchResult = ReturnType<typeof useWaterfallFetcher>;
export function useWaterfallFetcher({
traceId,

View file

@ -12,22 +12,22 @@ import {
EuiPagination,
EuiSpacer,
EuiTitle,
EuiLoadingContent,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useEffect, useState } from 'react';
import { LoadingStatePrompt } from '../../../shared/loading_state_prompt';
import { TransactionSummary } from '../../../shared/summary/transaction_summary';
import { TransactionActionMenu } from '../../../shared/transaction_action_menu/transaction_action_menu';
import type { TraceSample } from '../../../../hooks/use_transaction_trace_samples_fetcher';
import { MaybeViewTraceLink } from './maybe_view_trace_link';
import { TransactionTab, TransactionTabs } from './transaction_tabs';
import { IWaterfall } from './waterfall_container/waterfall/waterfall_helpers/waterfall_helpers';
import { Environment } from '../../../../../common/environment_rt';
import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
import { TraceSamplesFetchResult } from '../../../../hooks/use_transaction_trace_samples_fetcher';
import { WaterfallFetchResult } from '../use_waterfall_fetcher';
interface Props {
waterfall: IWaterfall;
isLoading: boolean;
traceSamples: TraceSample[];
waterfallFetchResult: WaterfallFetchResult;
traceSamplesFetchResult: TraceSamplesFetchResult;
environment: Environment;
onSampleClick: (sample: { transactionId: string; traceId: string }) => void;
onTabClick: (tab: string) => void;
@ -37,9 +37,8 @@ interface Props {
}
export function WaterfallWithSummary({
waterfall,
isLoading,
traceSamples,
waterfallFetchResult,
traceSamplesFetchResult,
environment,
onSampleClick,
onTabClick,
@ -51,17 +50,27 @@ export function WaterfallWithSummary({
useEffect(() => {
setSampleActivePage(0);
}, [traceSamples]);
}, [traceSamplesFetchResult.data.traceSamples]);
const goToSample = (index: number) => {
setSampleActivePage(index);
const sample = traceSamples[index];
const sample = traceSamplesFetchResult.data.traceSamples[index];
onSampleClick(sample);
};
const { entryWaterfallTransaction } = waterfall;
const { entryWaterfallTransaction } = waterfallFetchResult.waterfall;
const isLoading =
waterfallFetchResult.status === FETCH_STATUS.LOADING ||
traceSamplesFetchResult.status === FETCH_STATUS.LOADING;
const isSucceded =
waterfallFetchResult.status === FETCH_STATUS.SUCCESS &&
traceSamplesFetchResult.status === FETCH_STATUS.SUCCESS;
if ((!entryWaterfallTransaction || traceSamples.length === 0) && !isLoading) {
if (
!entryWaterfallTransaction &&
traceSamplesFetchResult.data.traceSamples.length === 0 &&
isSucceded
) {
return (
<EuiEmptyPrompt
title={
@ -91,9 +100,9 @@ export function WaterfallWithSummary({
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem>
{traceSamples.length > 0 && (
{traceSamplesFetchResult.data.traceSamples.length > 0 && (
<EuiPagination
pageCount={traceSamples.length}
pageCount={traceSamplesFetchResult.data.traceSamples.length}
activePage={sampleActivePage}
onPageClick={goToSample}
compressed
@ -112,7 +121,7 @@ export function WaterfallWithSummary({
<MaybeViewTraceLink
isLoading={isLoading}
transaction={entryTransaction}
waterfall={waterfall}
waterfall={waterfallFetchResult.waterfall}
environment={environment}
/>
</EuiFlexItem>
@ -123,25 +132,34 @@ export function WaterfallWithSummary({
<EuiSpacer size="s" />
{isLoading || !entryTransaction ? (
<LoadingStatePrompt />
) : (
<>
<TransactionSummary
errorCount={waterfall.apiResponse.errorDocs.length}
totalDuration={waterfall.rootTransaction?.transaction.duration.us}
transaction={entryTransaction}
/>
<EuiSpacer size="s" />
<TransactionTabs
transaction={entryTransaction}
detailTab={detailTab}
serviceName={serviceName}
waterfallItemId={waterfallItemId}
onTabClick={onTabClick}
waterfall={waterfall}
/>
<EuiLoadingContent lines={1} data-test-sub="loading-content" />
</>
) : (
<TransactionSummary
errorCount={
waterfallFetchResult.waterfall.apiResponse.errorDocs.length
}
totalDuration={
waterfallFetchResult.waterfall.rootTransaction?.transaction.duration
.us
}
transaction={entryTransaction}
/>
)}
<EuiSpacer size="s" />
<TransactionTabs
transaction={entryTransaction}
detailTab={detailTab}
serviceName={serviceName}
waterfallItemId={waterfallItemId}
onTabClick={onTabClick}
waterfall={waterfallFetchResult.waterfall}
isLoading={isLoading}
/>
</>
);
}

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { EuiSpacer, EuiTab, EuiTabs } from '@elastic/eui';
import { EuiSpacer, EuiTab, EuiTabs, EuiLoadingContent } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { LogStream } from '@kbn/infra-plugin/public';
import React from 'react';
@ -15,7 +15,8 @@ import { WaterfallContainer } from './waterfall_container';
import { IWaterfall } from './waterfall_container/waterfall/waterfall_helpers/waterfall_helpers';
interface Props {
transaction: Transaction;
transaction?: Transaction;
isLoading: boolean;
waterfall: IWaterfall;
detailTab?: TransactionTab;
serviceName?: string;
@ -26,6 +27,7 @@ interface Props {
export function TransactionTabs({
transaction,
waterfall,
isLoading,
detailTab,
waterfallItemId,
serviceName,
@ -36,7 +38,7 @@ export function TransactionTabs({
const TabContent = currentTab.component;
return (
<React.Fragment>
<>
<EuiTabs>
{tabs.map(({ key, label }) => {
return (
@ -54,14 +56,17 @@ export function TransactionTabs({
</EuiTabs>
<EuiSpacer />
<TabContent
waterfallItemId={waterfallItemId}
serviceName={serviceName}
waterfall={waterfall}
transaction={transaction}
/>
</React.Fragment>
{isLoading || !transaction ? (
<EuiLoadingContent lines={3} data-test-sub="loading-content" />
) : (
<TabContent
waterfallItemId={waterfallItemId}
serviceName={serviceName}
waterfall={waterfall}
transaction={transaction}
/>
)}
</>
);
}

View file

@ -5,21 +5,21 @@
* 2.0.
*/
import { useMemo } from 'react';
import { useFetcher } from './use_fetcher';
import { useLegacyUrlParams } from '../context/url_params_context/use_url_params';
import { useApmServiceContext } from '../context/apm_service/use_apm_service_context';
import { useApmParams } from './use_apm_params';
import { useTimeRange } from './use_time_range';
export interface TraceSample {
traceId: string;
transactionId: string;
}
const INITIAL_DATA = {
traceSamples: [] as TraceSample[],
traceSamples: [],
};
export type TraceSamplesFetchResult = ReturnType<
typeof useTransactionTraceSamplesFetcher
>;
export function useTransactionTraceSamplesFetcher({
transactionName,
kuery,
@ -87,9 +87,12 @@ export function useTransactionTraceSamplesFetcher({
]
);
return {
traceSamplesData: data,
traceSamplesStatus: status,
traceSamplesError: error,
};
return useMemo(
() => ({
data,
status,
error,
}),
[data, status, error]
);
}

View file

@ -169,14 +169,14 @@ const findTracesRoute = createApmServerRoute({
handler: async (
resources
): Promise<{
samples: Array<{ traceId: string; transactionId: string }>;
traceSamples: Array<{ traceId: string; transactionId: string }>;
}> => {
const { start, end, environment, query, type } = resources.params.query;
const setup = await setupRequest(resources);
return {
samples: await getTraceSamplesByQuery({
traceSamples: await getTraceSamplesByQuery({
setup,
start,
end,

View file

@ -58,13 +58,13 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
}
function fetchTraces(samples: Array<{ traceId: string; transactionId: string }>) {
if (!samples.length) {
function fetchTraces(traceSamples: Array<{ traceId: string; transactionId: string }>) {
if (!traceSamples.length) {
return [];
}
return Promise.all(
samples.map(async ({ traceId }) => {
traceSamples.map(async ({ traceId }) => {
const response = await apmApiClient.readUser({
endpoint: `GET /internal/apm/traces/{traceId}`,
params: {
@ -90,7 +90,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(response.status).to.be(200);
expect(response.body).to.eql({
samples: [],
traceSamples: [],
});
});
});
@ -168,28 +168,28 @@ export default function ApiTest({ getService }: FtrProviderContext) {
describe('and the query is empty', () => {
it('returns all trace samples', async () => {
const {
body: { samples },
body: { traceSamples },
} = await fetchTraceSamples({
query: '',
type: TraceSearchType.kql,
environment: 'ENVIRONMENT_ALL',
});
expect(samples.length).to.eql(5);
expect(traceSamples.length).to.eql(5);
});
});
describe('and query is set', () => {
it('returns the relevant traces', async () => {
const {
body: { samples },
body: { traceSamples },
} = await fetchTraceSamples({
query: 'span.destination.service.resource:elasticsearch',
type: TraceSearchType.kql,
environment: 'ENVIRONMENT_ALL',
});
expect(samples.length).to.eql(1);
expect(traceSamples.length).to.eql(1);
});
});
});
@ -214,7 +214,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
describe('and the query is set', () => {
it('returns the correct trace samples for transaction sequences', async () => {
const {
body: { samples },
body: { traceSamples },
} = await fetchTraceSamples({
query: `sequence by trace.id
[ transaction where service.name == "java" ]
@ -223,7 +223,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
environment: 'ENVIRONMENT_ALL',
});
const traces = await fetchTraces(samples);
const traces = await fetchTraces(traceSamples);
expect(traces.length).to.eql(2);
@ -242,7 +242,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
it('returns the correct trace samples for join sequences', async () => {
const {
body: { samples },
body: { traceSamples },
} = await fetchTraceSamples({
query: `sequence by trace.id
[ span where service.name == "java" ] by span.id
@ -251,7 +251,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
environment: 'ENVIRONMENT_ALL',
});
const traces = await fetchTraces(samples);
const traces = await fetchTraces(traceSamples);
expect(traces.length).to.eql(1);
@ -266,7 +266,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
it('returns the correct trace samples for exit spans', async () => {
const {
body: { samples },
body: { traceSamples },
} = await fetchTraceSamples({
query: `sequence by trace.id
[ transaction where service.name == "python" ]
@ -275,7 +275,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
environment: 'ENVIRONMENT_ALL',
});
const traces = await fetchTraces(samples);
const traces = await fetchTraces(traceSamples);
expect(traces.length).to.eql(1);