Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelogs/fragments/10728.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fix:
- Trace details flyout loading ([#10728](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/10728))
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,41 @@ describe('TraceDetails', () => {
consoleSpy.mockRestore();
});

it('handles default dataset', async () => {
const mockCreateTraceAppState = jest.requireMock('./state/trace_app_state').createTraceAppState;

mockCreateTraceAppState.mockImplementationOnce(({ stateDefaults }: any) => ({
stateContainer: {
get: () => ({ ...stateDefaults, traceId: 'test-trace-id' }),
state$: {
subscribe: jest.fn(() => ({ unsubscribe: jest.fn() })),
},
},
stopStateSync: jest.fn(),
}));

const defaultDataset = {
id: 'default-dataset-id',
title: 'otel-v1-apm-span-*',
type: 'INDEX_PATTERN',
timeFieldName: 'endTime',
};

const history = createMemoryHistory();
render(
<Router history={history}>
<TraceDetails defaultDataset={defaultDataset} />
</Router>
);

expect(mockPplService.fetchTraceSpans).toHaveBeenCalledWith({
traceId: 'test-trace-id',
dataset: defaultDataset,
filters: [],
limit: 100,
});
});

it('sets page title correctly', async () => {
const history = createMemoryHistory();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export interface TraceDetailsProps {
setMenuMountPoint?: (mount: MountPoint | undefined) => void;
isEmbedded?: boolean;
isFlyout?: boolean;
defaultDataset?: Dataset;
}
// Displaying only 10 logs in the tab
export const LOGS_DATA = 10;
Expand All @@ -74,6 +75,7 @@ export const TraceDetails: React.FC<TraceDetailsProps> = ({
setMenuMountPoint,
isEmbedded = false,
isFlyout = false,
defaultDataset,
}) => {
const {
services: { chrome, data, osdUrlStateStorage, savedObjects, uiSettings },
Expand All @@ -84,7 +86,7 @@ export const TraceDetails: React.FC<TraceDetailsProps> = ({
return createTraceAppState({
stateDefaults: {
traceId: '',
dataset: {
dataset: defaultDataset ?? {
id: 'default-dataset-id',
title: 'otel-v1-apm-span-*',
type: 'INDEX_PATTERN',
Expand All @@ -94,6 +96,7 @@ export const TraceDetails: React.FC<TraceDetailsProps> = ({
},
osdUrlStateStorage: osdUrlStateStorage!,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [osdUrlStateStorage]);

// Get current state values and subscribe to changes
Expand All @@ -114,7 +117,6 @@ export const TraceDetails: React.FC<TraceDetailsProps> = ({
const [transformedHits, setTransformedHits] = useState<TraceHit[]>([]);
const [spanFilters, setSpanFilters] = useState<SpanFilter[]>([]);
const [pplQueryData, setPplQueryData] = useState<PPLResponse | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [isBackgroundLoading, setIsBackgroundLoading] = useState<boolean>(false);
const [unfilteredHits, setUnfilteredHits] = useState<TraceHit[]>([]);
const mainPanelRef = useRef<HTMLDivElement | null>(null);
Expand All @@ -128,6 +130,7 @@ export const TraceDetails: React.FC<TraceDetailsProps> = ({
isValid: boolean;
missingFields: string[];
} | null>(null);
const [prevTraceId, setPrevTraceId] = useState<string | undefined>(undefined);

// Create PPL service instance
const pplService = useMemo(() => (data ? new TracePPLService(data) : undefined), [data]);
Expand Down Expand Up @@ -180,13 +183,15 @@ export const TraceDetails: React.FC<TraceDetailsProps> = ({
}
}, [dataset, correlationService, data, traceId]);

const isLoading = prevTraceId !== traceId;

useEffect(() => {
const fetchData = async (filters: SpanFilter[] = []) => {
if (!pplService || !traceId || !dataset) return;

// Only show full loading spinner on initial load
if (transformedHits.length === 0) {
setIsLoading(true);
if (isLoading) {
setTransformedHits([]);
setUnfilteredHits([]);
} else {
// Use background loading for filter updates
setIsBackgroundLoading(true);
Expand All @@ -207,8 +212,8 @@ export const TraceDetails: React.FC<TraceDetailsProps> = ({
// eslint-disable-next-line no-console
console.error('Failed to fetch trace data:', err);
} finally {
setIsLoading(false);
setIsBackgroundLoading(false);
setPrevTraceId(traceId);
}
};

Expand All @@ -222,7 +227,8 @@ export const TraceDetails: React.FC<TraceDetailsProps> = ({
if (traceId && dataset && pplService) {
fetchData(spanFilters);
}
}, [traceId, dataset, pplService, spanFilters, transformedHits.length]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [traceId, dataset, pplService, spanFilters]);

useEffect(() => {
if (!pplQueryData) return;
Expand Down Expand Up @@ -277,7 +283,7 @@ export const TraceDetails: React.FC<TraceDetailsProps> = ({

// Find root span for breadcrumb (always shows root span info)
const rootSpan = useMemo((): TraceHit | undefined => {
if (transformedHits.length === 0) return undefined;
if (isLoading || transformedHits.length === 0) return undefined;

// Find span without parent first
const spanWithoutParent = transformedHits.find((span) => !span.parentSpanId);
Expand All @@ -290,11 +296,11 @@ export const TraceDetails: React.FC<TraceDetailsProps> = ({
const currentTime = new Date(current.startTime || 0).getTime();
return currentTime < earliestTime ? current : earliest;
}, undefined);
}, [transformedHits]);
}, [transformedHits, isLoading]);

// Find selected span, with fallback to root span logic
const selectedSpan = useMemo((): TraceHit | undefined => {
if (transformedHits.length === 0) return undefined;
if (isLoading || transformedHits.length === 0) return undefined;

// If we have a specific spanId, try to find it first
if (spanId) {
Expand All @@ -304,7 +310,7 @@ export const TraceDetails: React.FC<TraceDetailsProps> = ({

// Fallback to root span if no specific span selected or found
return rootSpan;
}, [spanId, transformedHits, rootSpan]);
}, [spanId, transformedHits, rootSpan, isLoading]);

// Update URL state when fallback span selection occurs
useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,22 @@ import { EuiFlyout } from '@elastic/eui';
import React from 'react';
import { useTraceFlyoutContext } from './trace_flyout_context';
import { TraceDetails } from '../trace_details/trace_view';
import { getTraceDetailsUrlParams } from '../../../../components/data_table/table_cell/trace_utils/trace_utils';

export const TraceFlyout: React.FC = () => {
const { closeTraceFlyout, flyoutData, isFlyoutOpen } = useTraceFlyoutContext();

if (!flyoutData || !isFlyoutOpen) return null;

const { dataset } = getTraceDetailsUrlParams(
flyoutData.spanId,
flyoutData.traceId,
flyoutData.dataset
);

return (
<EuiFlyout data-test-subj="traceFlyout" onClose={closeTraceFlyout} ownFocus={false}>
<TraceDetails isFlyout={true} />
<TraceDetails isFlyout={true} defaultDataset={dataset} />
</EuiFlyout>
);
};
Loading