Skip to content

Commit

Permalink
feat(new-trace) Adding trace level ops breakdown (#84354)
Browse files Browse the repository at this point in the history
<img width="775" alt="Screenshot 2025-01-30 at 7 50 13 PM"
src="https://github.com/user-attachments/assets/84209045-87b1-4432-af98-a09ad1f79b3c"
/>

--------------

<img width="694" alt="Screenshot 2025-01-30 at 7 50 21 PM"
src="https://github.com/user-attachments/assets/f3e0a2d7-93ca-4e1d-91aa-5eb751afc058"
/>

---------

Co-authored-by: Abdullah Khan <[email protected]>
  • Loading branch information
Abdkhan14 and Abdullah Khan authored Feb 3, 2025
1 parent 03ffa20 commit 48f5106
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 2 deletions.
22 changes: 22 additions & 0 deletions static/app/views/performance/newTraceDetails/trace.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,17 @@ function mockMetricsResponse() {
});
}

function mockEventsResponse() {
MockApiClient.addMockResponse({
url: '/organizations/org-slug/events/',
method: 'GET',
body: {
data: [],
queries: [],
},
});
}

function getVirtualizedContainer(): HTMLElement {
const virtualizedContainer = screen.queryByTestId('trace-virtualized-list');
if (!virtualizedContainer) {
Expand Down Expand Up @@ -277,6 +288,7 @@ async function keyboardNavigationTestSetup() {
mockTraceRootEvent('0');
mockTraceEventDetails();
mockMetricsResponse();
mockEventsResponse();

const value = render(<TraceView />, {router});
const virtualizedContainer = getVirtualizedContainer();
Expand Down Expand Up @@ -334,6 +346,7 @@ async function pageloadTestSetup() {
mockTraceRootEvent('0');
mockTraceEventDetails();
mockMetricsResponse();
mockEventsResponse();

const value = render(<TraceView />, {router});
const virtualizedContainer = getVirtualizedContainer();
Expand Down Expand Up @@ -392,6 +405,7 @@ async function nestedTransactionsTestSetup() {
mockTraceRootEvent('0');
mockTraceEventDetails();
mockMetricsResponse();
mockEventsResponse();

const value = render(<TraceView />, {router});
const virtualizedContainer = getVirtualizedContainer();
Expand Down Expand Up @@ -448,6 +462,7 @@ async function searchTestSetup() {
mockTraceRootEvent('0');
mockTraceEventDetails();
mockMetricsResponse();
mockEventsResponse();

const value = render(<TraceView />, {router});
const virtualizedContainer = getVirtualizedContainer();
Expand Down Expand Up @@ -508,6 +523,7 @@ async function simpleTestSetup() {
mockTraceRootEvent('0');
mockTraceEventDetails();
mockMetricsResponse();
mockEventsResponse();

const value = render(<TraceView />, {router});
const virtualizedContainer = getVirtualizedContainer();
Expand Down Expand Up @@ -611,6 +627,7 @@ async function completeTestSetup() {
mockTraceRootEvent('0');
mockTraceEventDetails();
mockMetricsResponse();
mockEventsResponse();

MockApiClient.addMockResponse({
url: '/organizations/org-slug/events/project_slug:error0/',
Expand Down Expand Up @@ -864,6 +881,7 @@ describe('trace view', () => {
mockTraceResponse();
mockTraceMetaResponse();
mockTraceTagsResponse();
mockEventsResponse();

render(<TraceView />, {router});
expect(await screen.findByText(/assembling the trace/i)).toBeInTheDocument();
Expand All @@ -874,6 +892,7 @@ describe('trace view', () => {
mockTraceResponse({statusCode: 404});
mockTraceMetaResponse({statusCode: 404});
mockTraceTagsResponse({statusCode: 404});
mockEventsResponse();

render(<TraceView />, {router});
expect(await screen.findByText(/we failed to load your trace/i)).toBeInTheDocument();
Expand All @@ -890,6 +909,7 @@ describe('trace view', () => {
});
mockTraceMetaResponse({statusCode: 404});
mockTraceTagsResponse({statusCode: 404});
mockEventsResponse();

render(<TraceView />, {router});
expect(await screen.findByText(/we failed to load your trace/i)).toBeInTheDocument();
Expand All @@ -905,6 +925,7 @@ describe('trace view', () => {
});
mockTraceMetaResponse();
mockTraceTagsResponse();
mockEventsResponse();

render(<TraceView />, {router});
expect(
Expand Down Expand Up @@ -1608,6 +1629,7 @@ describe('trace view', () => {
mockTraceRootEvent('0');
mockTraceEventDetails();
mockMetricsResponse();
mockEventsResponse();

mockTraceResponse({
body: {
Expand Down
7 changes: 7 additions & 0 deletions static/app/views/performance/newTraceDetails/trace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import {
isTraceNode,
isTransactionNode,
} from './traceGuards';
import {TraceLevelOpsBreakdown} from './traceLevelOpsBreakdown';
import type {TraceReducerState} from './traceState';

function computeNextIndexFromAction(
Expand Down Expand Up @@ -401,6 +402,9 @@ export function Trace({
className="TraceScrollbarContainer"
ref={manager.registerHorizontalScrollBarContainerRef}
>
{trace_id ? (
<TraceLevelOpsBreakdown traceSlug={trace_id} isTraceLoading={isLoading} />
) : null}
<div className="TraceScrollbarScroller" />
</div>
<div className="TraceDivider" ref={manager.registerDividerRef} />
Expand Down Expand Up @@ -840,6 +844,9 @@ const TraceStylingWrapper = styled('div')`
overflow-x: auto;
overscroll-behavior: none;
will-change: transform;
z-index: 10;
display: flex;
align-items: center;
.TraceScrollbarScroller {
height: 1px;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import styled from '@emotion/styled';

import {addErrorMessage} from 'sentry/actionCreators/indicator';
import {pickBarColor} from 'sentry/components/performance/waterfall/utils';
import Placeholder from 'sentry/components/placeholder';
import {IconCircleFill} from 'sentry/icons/iconCircleFill';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import type {NewQuery} from 'sentry/types/organization';
import {useDiscoverQuery} from 'sentry/utils/discover/discoverQuery';
import {DiscoverDatasets} from 'sentry/utils/discover/types';
import type {Color} from 'sentry/utils/theme';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';

import {useTraceEventView} from './useTraceEventView';
import {type TraceViewQueryParams, useTraceQueryParams} from './useTraceQueryParams';

function useTraceLevelOpsQuery(
traceSlug: string,
params: TraceViewQueryParams,
partialSavedQuery: Partial<NewQuery>
) {
const location = useLocation();
const organization = useOrganization();
const eventView = useTraceEventView(traceSlug, params, {
...partialSavedQuery,
dataset: DiscoverDatasets.SPANS_EAP,
});

return useDiscoverQuery({
eventView,
orgSlug: organization.slug,
location,
});
}

function LoadingPlaceHolder() {
return (
<Container>
<StyledPlaceholder height={'16px'} width={'100px'} />
<StyledPlaceholder height={'16px'} width={'100px'} />
<StyledPlaceholder height={'16px'} width={'100px'} />
</Container>
);
}

type Props = {
isTraceLoading: boolean;
traceSlug: string;
};

export function TraceLevelOpsBreakdown({traceSlug, isTraceLoading}: Props) {
const urlParams = useTraceQueryParams();
const {
data: opsCountsResult,
isPending: isOpsCountsLoading,
isError: isOpsCountsError,
} = useTraceLevelOpsQuery(traceSlug ?? '', urlParams, {
fields: ['span.op', 'count()'],
orderby: '-count',
});
const {
data: totalCountResult,
isPending: isTotalCountLoading,
isError: isTotalCountError,
} = useTraceLevelOpsQuery(traceSlug ?? '', urlParams, {
fields: ['count()'],
});

if (isOpsCountsLoading || isTotalCountLoading || isTraceLoading) {
return <LoadingPlaceHolder />;
}

if (isOpsCountsError || isTotalCountError) {
addErrorMessage(t('Failed to load trace level ops breakdown'));
return null;
}

const totalCount = totalCountResult?.data[0]?.['count()'] ?? 0;

if (typeof totalCount !== 'number' || totalCount <= 0) {
return null;
}

return (
<Container>
{opsCountsResult?.data.slice(0, 4).map(currOp => {
const operationName = currOp['span.op'];
const count = currOp['count()'];

if (typeof operationName !== 'string' || typeof count !== 'number') {
return null;
}

const percentage = count / totalCount;
const color = pickBarColor(operationName);
const pctLabel = isFinite(percentage) ? Math.round(percentage * 100) : '∞';

return (
<HighlightsOpRow key={operationName}>
<IconCircleFill size="xs" color={color as Color} />
{operationName}
<span>{pctLabel}%</span>
</HighlightsOpRow>
);
})}
</Container>
);
}

const HighlightsOpRow = styled('div')`
display: flex;
align-items: center;
font-size: ${p => p.theme.fontSizeSmall};
gap: 5px;
`;

const Container = styled('div')`
display: flex;
align-items: center;
padding-left: ${space(1)};
gap: ${space(2)};
`;

const StyledPlaceholder = styled(Placeholder)`
border-radius: ${p => p.theme.borderRadius};
`;
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import {useMemo} from 'react';

import {ALL_ACCESS_PROJECTS} from 'sentry/constants/pageFilters';
import type {NewQuery} from 'sentry/types/organization';
import EventView from 'sentry/utils/discover/eventView';

import type {TraceViewQueryParams} from './useTraceQueryParams';

export function useTraceEventView(
traceSlug: string,
params: TraceViewQueryParams
params: TraceViewQueryParams,
partialSavedQuery?: Partial<NewQuery>
): EventView {
return useMemo(() => {
let startTimeStamp = params.start;
Expand All @@ -34,6 +36,7 @@ export function useTraceEventView(
start: startTimeStamp,
end: endTimeStamp,
range: !(startTimeStamp || endTimeStamp) ? params.statsPeriod : undefined,
...partialSavedQuery,
});
}, [params, traceSlug]);
}, [params, traceSlug, partialSavedQuery]);
}

0 comments on commit 48f5106

Please sign in to comment.