Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,7 @@ import {
type OurLogsResponseItem,
} from 'sentry/views/explore/logs/types';
import {TraceItemDataset} from 'sentry/views/explore/types';
import {getRepresentativeTraceEvent} from 'sentry/views/performance/newTraceDetails/traceApi/utils';
import {
isEAPError,
isTraceError,
} from 'sentry/views/performance/newTraceDetails/traceGuards';
import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree';
import {useIsEAPTraceEnabled} from 'sentry/views/performance/newTraceDetails/useIsEAPTraceEnabled';

type Params = {
logs: OurLogsResponseItem[] | undefined;
Expand All @@ -34,28 +28,20 @@ export function useTraceRootEvent({
logs,
traceId,
}: Params): TraceRootEventQueryResults {
const rep = getRepresentativeTraceEvent(tree, logs);
const rep = tree.findRepresentativeTraceNode({logs});
const organization = useOrganization();

// TODO: This is a bit of a mess, we won't need all of this once we switch to EAP only
const treeIsLoading = tree.type === 'loading';
const hasOnlyLogs = !!(tree.type === 'empty' && logs && logs.length > 0);
const enabledBase =
!treeIsLoading && (tree.type === 'trace' || hasOnlyLogs) && !!rep?.event && !!traceId;

const isRepEventError =
rep.event && OurLogKnownFieldKey.PROJECT_ID in rep.event
? false
: isTraceError(rep.event) || isEAPError(rep.event);
const enabledBase = !treeIsLoading && !!rep?.event;

const isEAPTraceEnabled = useIsEAPTraceEnabled();
const isEAPQueryEnabled =
!isRepEventError && // Errors are not supported in EAP yet
(isEAPTraceEnabled || (!treeIsLoading && hasOnlyLogs));
const isRepLog = rep?.dataset === TraceItemDataset.LOGS;
const isEAPQueryEnabled = !!(isRepLog || rep?.event?.isEAPEvent);

const projectSlug = rep?.event?.projectSlug;
const legacyRootEvent = useApiQuery<EventTransaction>(
[
`/organizations/${organization.slug}/events/${rep?.event?.project_slug}:${rep?.event?.event_id}/`,
`/organizations/${organization.slug}/events/${projectSlug}:${rep?.event?.id}/`,
{
query: {
referrer: 'trace-details-summary',
Expand All @@ -65,32 +51,27 @@ export function useTraceRootEvent({
{
// 10 minutes
staleTime: 1000 * 60 * 10,
enabled: enabledBase && !isEAPQueryEnabled,
enabled: enabledBase && !isEAPQueryEnabled && !!projectSlug && !!rep?.event?.id,
}
);

const projectId = rep.event
const projectId = rep?.event
? OurLogKnownFieldKey.PROJECT_ID in rep.event
? rep.event[OurLogKnownFieldKey.PROJECT_ID]
: rep.event.project_id
: rep.event.projectId
: '';
const eventId = rep.event
? OurLogKnownFieldKey.ID in rep.event
const eventId = rep?.event
? OurLogKnownFieldKey.PROJECT_ID in rep.event
? rep.event[OurLogKnownFieldKey.ID]
: rep.event.event_id
: rep.event.id
: '';

const itemTypes = {
log: TraceItemDataset.LOGS,
span: TraceItemDataset.SPANS,
uptime_check: TraceItemDataset.UPTIME_RESULTS,
};
const dataset = rep?.dataset ?? TraceItemDataset.SPANS;

const rootEvent = useTraceItemDetails({
traceItemId: String(eventId),
projectId: String(projectId),
traceId,
traceItemType: itemTypes[rep.type],
traceItemType: dataset,
referrer: 'api.explore.log-item-details',
enabled: enabledBase && isEAPQueryEnabled,
});
Expand Down
105 changes: 1 addition & 104 deletions static/app/views/performance/newTraceDetails/traceApi/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import type {TraceItemDetailsResponse} from 'sentry/views/explore/hooks/useTraceItemDetails';
import type {OurLogsResponseItem} from 'sentry/views/explore/logs/types';
import type {TraceRootEventQueryResults} from 'sentry/views/performance/newTraceDetails/traceApi/useTraceRootEvent';
import {
isEAPTraceNode,
isRootEvent,
isTraceNode,
isTraceSplitResult,
isUptimeCheckNode,
} from 'sentry/views/performance/newTraceDetails/traceGuards';
import {isTraceSplitResult} from 'sentry/views/performance/newTraceDetails/traceGuards';
import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree';

export function isEmptyTrace(trace: TraceTree.Trace): boolean {
Expand All @@ -18,87 +11,6 @@ export function isEmptyTrace(trace: TraceTree.Trace): boolean {
return trace.length === 0;
}

const CANDIDATE_TRACE_TITLE_OPS = ['pageload', 'navigation', 'ui.load'];

export type RepresentativeTraceEvent = {
event: TraceTree.TraceEvent | OurLogsResponseItem | null;
type: 'span' | 'log' | 'uptime_check';
};

export const getRepresentativeTraceEvent = (
tree: TraceTree,
logs: OurLogsResponseItem[] | undefined
): RepresentativeTraceEvent => {
const hasLogs = logs && logs.length > 0;
if (tree.type === 'empty' && hasLogs) {
return {
event: logs[0]!,
type: 'log',
};
}

const traceNode = tree.root.children[0];

if (!traceNode) {
return {
event: null,
type: 'span',
};
}

if (!isTraceNode(traceNode) && !isEAPTraceNode(traceNode)) {
throw new TypeError('Not trace node');
}

const traceChild = traceNode.children[0];

if (traceChild && isUptimeCheckNode(traceChild)) {
return {type: 'uptime_check', event: traceChild.value};
}

let preferredRootEvent: TraceTree.TraceEvent | null = null;
let firstRootEvent: TraceTree.TraceEvent | null = null;
let candidateEvent: TraceTree.TraceEvent | null = null;
let firstEvent: TraceTree.TraceEvent | null = null;

const isEAP = isEAPTraceNode(traceNode);
const events = isEAP
? traceNode.value
: [...traceNode.value.transactions, ...traceNode.value.orphan_errors];
for (const event of events) {
if (isRootEvent(event)) {
if (!firstRootEvent) {
firstRootEvent = event;
}

if (hasPreferredOp(event)) {
preferredRootEvent = event;
break;
}
// Otherwise we keep looking for a root eap transaction. If we don't find one, we use other roots, like standalone spans.
continue;
} else if (
// If we haven't found a root transaction, but we found a candidate transaction
// with an op that we care about, we can use it for the title. We keep looking for
// a root.
!candidateEvent &&
hasPreferredOp(event)
) {
candidateEvent = event;
continue;
} else if (!firstEvent) {
// If we haven't found a root or candidate transaction, we can use the first transaction
// in the trace for the title.
firstEvent = event;
}
}

return {
event: preferredRootEvent ?? firstRootEvent ?? candidateEvent ?? firstEvent,
type: 'span',
};
};

export const isTraceItemDetailsResponse = (
data: TraceRootEventQueryResults['data']
): data is TraceItemDetailsResponse => {
Expand All @@ -110,18 +22,3 @@ export const isValidEventUUID = (id: string): boolean => {
/^[0-9a-f]{8}[0-9a-f]{4}[1-5][0-9a-f]{3}[89ab][0-9a-f]{3}[0-9a-f]{12}$/i;
return uuidRegex.test(id);
};

/**
* Prefer "special" root events over generic root events when generating a title
* for the waterfall view. Picking these improves contextual navigation for linked
* traces, resulting in more meaningful waterfall titles.
*/
function hasPreferredOp(event: TraceTree.TraceEvent): boolean {
const op =
'op' in event
? event.op
: 'transaction.op' in event
? event['transaction.op']
: undefined;
return !!op && CANDIDATE_TRACE_TITLE_OPS.includes(op);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import {
import {SectionDivider} from 'sentry/views/issueDetails/streamline/foldSection';
import type {TraceRootEventQueryResults} from 'sentry/views/performance/newTraceDetails/traceApi/useTraceRootEvent';
import {isTraceItemDetailsResponse} from 'sentry/views/performance/newTraceDetails/traceApi/utils';
import {isEAPTraceNode} from 'sentry/views/performance/newTraceDetails/traceGuards';
import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree';
import {
TRACE_VIEW_MOBILE_VITALS,
Expand Down Expand Up @@ -52,9 +51,8 @@ export function TraceContextVitals({rootEventResults, tree, containerWidth}: Pro
? TRACE_VIEW_WEB_VITALS
: TRACE_VIEW_MOBILE_VITALS;

const isEAPTrace = isEAPTraceNode(traceNode);
const collectedVitals =
isEAPTrace && tree.vital_types.has('mobile')
traceNode.isEAPEvent && tree.vital_types.has('mobile')
? getMobileVitalsFromRootEventResults(rootEventResults.data)
: Array.from(tree.vitals.values()).flat();

Expand Down
9 changes: 0 additions & 9 deletions static/app/views/performance/newTraceDetails/traceGuards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import type {ParentAutogroupNode} from './traceModels/traceTreeNode/parentAutogr
import type {SiblingAutogroupNode} from './traceModels/traceTreeNode/siblingAutogroupNode';
import type {SpanNode} from './traceModels/traceTreeNode/spanNode';
import type {TransactionNode} from './traceModels/traceTreeNode/transactionNode';
import type {UptimeCheckNode} from './traceModels/traceTreeNode/uptimeCheckNode';

export function isMissingInstrumentationNode(
node: BaseNode
Expand All @@ -34,10 +33,6 @@ export function isEAPSpanNode(node: BaseNode): node is EapSpanNode {
return isEAPSpan(node.value);
}

export function isUptimeCheckNode(node: BaseNode): node is UptimeCheckNode {
return isUptimeCheck(node.value);
}

export function isTransactionNode(node: BaseNode): node is TransactionNode {
return !!(node.value && 'transaction.op' in node.value);
}
Expand Down Expand Up @@ -91,10 +86,6 @@ export function isTraceNode(
return !!(node.value && 'orphan_errors' in node.value && 'transactions' in node.value);
}

export function isEAPTraceNode(node: BaseNode): node is BaseNode<TraceTree.EAPTrace> {
return !!node.value && Array.isArray(node.value) && !isTraceNode(node);
}

export function shouldAddMissingInstrumentationSpan(sdk: string | undefined): boolean {
if (!sdk) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {useModuleURLBuilder} from 'sentry/views/insights/common/utils/useModuleU
import {useDomainViewFilters} from 'sentry/views/insights/pages/useFilters';
import type {TraceMetaQueryResults} from 'sentry/views/performance/newTraceDetails/traceApi/useTraceMeta';
import type {TraceRootEventQueryResults} from 'sentry/views/performance/newTraceDetails/traceApi/useTraceRootEvent';
import {getRepresentativeTraceEvent} from 'sentry/views/performance/newTraceDetails/traceApi/utils';
import Highlights from 'sentry/views/performance/newTraceDetails/traceHeader/highlights';
import {PlaceHolder} from 'sentry/views/performance/newTraceDetails/traceHeader/placeholder';
import Projects from 'sentry/views/performance/newTraceDetails/traceHeader/projects';
Expand Down Expand Up @@ -64,12 +63,12 @@ export function TraceMetaDataHeader(props: TraceMetadataHeaderProps) {
return <PlaceHolder organization={props.organization} traceSlug={props.traceSlug} />;
}

const rep = getRepresentativeTraceEvent(props.tree, props.logs);
const rep = props.tree.findRepresentativeTraceNode({logs: props.logs});
const project = projects.find(p => {
const id =
rep.event && OurLogKnownFieldKey.PROJECT_ID in rep.event
rep?.event && OurLogKnownFieldKey.PROJECT_ID in rep.event
? rep.event[OurLogKnownFieldKey.PROJECT_ID]
: rep.event?.project_id;
: rep?.event?.projectId;
return p.id === String(id);
});

Expand All @@ -92,11 +91,7 @@ export function TraceMetaDataHeader(props: TraceMetadataHeaderProps) {
</ButtonBar>
</TraceHeaderComponents.HeaderRow>
<TraceHeaderComponents.HeaderRow>
<Title
representativeEvent={rep}
rootEventResults={props.rootEventResults}
tree={props.tree}
/>
<Title representativeEvent={rep} rootEventResults={props.rootEventResults} />
<Meta
organization={props.organization}
tree={props.tree}
Expand Down
35 changes: 19 additions & 16 deletions static/app/views/performance/newTraceDetails/traceHeader/meta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import type {Organization} from 'sentry/types/organization';
import getDuration from 'sentry/utils/duration/getDuration';
import type {OurLogsResponseItem} from 'sentry/views/explore/logs/types';
import {
OurLogKnownFieldKey,
type OurLogsResponseItem,
} from 'sentry/views/explore/logs/types';
import type {TraceMetaQueryResults} from 'sentry/views/performance/newTraceDetails/traceApi/useTraceMeta';
import type {RepresentativeTraceEvent} from 'sentry/views/performance/newTraceDetails/traceApi/utils';
import {TraceDrawerComponents} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/styles';
import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree';
import type {BaseNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode';
import {useTraceQueryParams} from 'sentry/views/performance/newTraceDetails/useTraceQueryParams';

type MetaDataProps = {
Expand Down Expand Up @@ -48,23 +51,17 @@ interface MetaProps {
logs: OurLogsResponseItem[] | undefined;
meta: TraceMetaQueryResults['data'];
organization: Organization;
representativeEvent: RepresentativeTraceEvent;
representativeEvent: TraceTree.RepresentativeTraceEvent | null;
tree: TraceTree;
}

function getRootDuration(event: TraceTree.TraceEvent | null) {
if (!event) {
function getRootDuration(node: BaseNode | null) {
if (!node) {
return '\u2014';
}

const startTimestamp = 'start_timestamp' in event ? event.start_timestamp : undefined;
// TODO Abdullah Khan: Clean this up once getRepresentativeTraceEvent is moved to the TraceTree class
const endTimestamp =
'timestamp' in event
? event.timestamp
: 'event_timestamp' in event && typeof event.event_timestamp === 'number'
? event.event_timestamp
: undefined;
const startTimestamp = node.startTimestamp;
const endTimestamp = node.endTimestamp;

if (!startTimestamp || !endTimestamp) {
return '\u2014';
Expand Down Expand Up @@ -92,6 +89,8 @@ export function Meta(props: MetaProps) {
const hasSpans = spansCount > 0;
const hasLogs = (props.logs?.length ?? 0) > 0;

const repEvent = props.representativeEvent?.event;

return (
<MetaWrapper>
<MetaSection
Expand Down Expand Up @@ -124,9 +123,13 @@ export function Meta(props: MetaProps) {
<MetaSection
headingText={t('Root Duration')}
rightAlignBody
bodyText={getRootDuration(
props.representativeEvent.event as TraceTree.TraceEvent
)}
bodyText={
repEvent
? OurLogKnownFieldKey.PROJECT_ID in repEvent
? '\u2014' // Logs don't have a duration
: getRootDuration(repEvent)
: '\u2014'
}
/>
) : hasLogs ? (
<MetaSection
Expand Down
Loading
Loading