Skip to content
Draft
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
@@ -1,7 +1,7 @@
import type React from 'react';
import {useRef} from 'react';

import {Container, Flex} from '@sentry/scraps/layout';
import {Container, Flex, Stack} from '@sentry/scraps/layout';
import {SplitPanel} from '@sentry/scraps/splitPanel';

import {Placeholder} from 'sentry/components/placeholder';
Expand Down Expand Up @@ -83,6 +83,92 @@ export function ConversationLeftPanel({children}: {children: React.ReactNode}) {
);
}

export function SpanDetailCard({
children,
embedded,
ref,
}: {
children: React.ReactNode;
embedded?: boolean;
ref?: React.Ref<HTMLDivElement>;
}) {
return (
<Stack
ref={ref}
background="primary"
border={embedded ? undefined : 'primary'}
radius={embedded ? undefined : 'md'}
padding="xl"
gap="lg"
flex="1"
minWidth="0"
minHeight="0"
height={embedded ? '100%' : {xs: 'auto', sm: '100%'}}
overflowY={embedded ? 'auto' : {xs: 'visible', sm: 'auto'}}
overflowX={embedded ? 'hidden' : {xs: 'visible', sm: 'hidden'}}
>
{children}
</Stack>
);
}

export function ConversationTimelineLayout({
left,
right,
leftPadding = 'md',
}: {
left: React.ReactNode;
leftPadding?: React.ComponentProps<typeof Container>['padding'];
right?: React.ReactNode;
}) {
return (
<Flex flex="1" minWidth="0" minHeight="0" overflow="hidden">
<ConversationLeftPanel>
<Container
containerType="inline-size"
flex="1"
minHeight="0"
width="100%"
background="secondary"
>
<Flex
direction={{xs: 'column', sm: 'row'}}
height="100%"
width="100%"
gap="md"
minHeight="0"
overflowY="auto"
overflowX="hidden"
>
<Container
flex={{xs: '0 0 auto', sm: '1'}}
minWidth="0"
minHeight={{xs: 'auto', sm: '0'}}
padding={leftPadding}
background="primary"
border="primary"
radius="md"
overflowX="hidden"
overflowY={{xs: 'hidden', sm: 'auto'}}
>
{left}
</Container>
{right ? (
<Flex
width={{xs: '100%', sm: '430px'}}
flex="0 0 auto"
minHeight={{xs: 'auto', sm: '0'}}
>
{right}
</Flex>
) : null}
</Flex>
</Container>
</ConversationLeftPanel>
</Flex>
);
}

export function ConversationDetailPanel({
selectedNode,
nodeTraceMap,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {capitalize} from 'sentry/utils/string/capitalize';
import {useLocation} from 'sentry/utils/useLocation';
import {useOrganization} from 'sentry/utils/useOrganization';
import {useProjects} from 'sentry/utils/useProjects';
import {SpanDetailCard} from 'sentry/views/explore/conversations/components/conversationLayout';
import {useTraceItemDetails} from 'sentry/views/explore/hooks/useTraceItemDetails';
import {TraceItemDataset} from 'sentry/views/explore/types';
import {getNodeTimeBounds} from 'sentry/views/insights/pages/agents/components/aiSpanList';
Expand Down Expand Up @@ -116,20 +117,7 @@ export function ConversationSpanDetail({
);

return (
<Stack
ref={scrollContainerRef}
background="primary"
border={embedded ? undefined : 'primary'}
radius={embedded ? undefined : 'md'}
padding="xl"
gap="lg"
flex="1"
minWidth="0"
minHeight="0"
height={embedded ? '100%' : {xs: 'auto', sm: '100%'}}
overflowY={embedded ? 'auto' : {xs: 'visible', sm: 'auto'}}
overflowX={embedded ? 'hidden' : {xs: 'visible', sm: 'hidden'}}
>
<SpanDetailCard ref={scrollContainerRef} embedded={embedded}>
<Flex align="center" gap="lg" flexShrink={0}>
<Flex flex="1" minWidth="0" align="center" gap="md">
<Container
Expand Down Expand Up @@ -222,7 +210,7 @@ export function ConversationSpanDetail({
</TabPanels>
</Container>
</TabStateProvider>
</Stack>
</SpanDetailCard>
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ export function ConversationSummaryNew({
<Placeholder width="16px" height="16px" />
<Placeholder width="120px" height="14px" />
</Flex>
<Flex align="center" gap="xs">
<Placeholder width="12px" height="12px" />
<Placeholder width="40px" height="14px" />
</Flex>
<Flex align="center" gap="sm">
<Placeholder width="72px" height="20px" />
<Placeholder width="72px" height="20px" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,14 @@ import {useCallback, useEffect, useRef} from 'react';
import * as Sentry from '@sentry/react';
import {parseAsBoolean, parseAsStringLiteral, useQueryStates} from 'nuqs';

import {Container, Flex} from '@sentry/scraps/layout';

import {EmptyMessage} from 'sentry/components/emptyMessage';
import {t} from 'sentry/locale';
import {
ConversationLeftPanel,
ConversationViewSkeleton,
} from 'sentry/views/explore/conversations/components/conversationLayout';
import {ConversationTimelineLayout} from 'sentry/views/explore/conversations/components/conversationLayout';
import {
CONVERSATION_SPAN_DETAIL_TABS,
ConversationSpanDetail,
} from 'sentry/views/explore/conversations/components/conversationSpanDetail';
import {ConversationViewSkeletonNew} from 'sentry/views/explore/conversations/components/conversationViewSkeleton';
import {
MessagesPanelNew,
MessagesPanelSkeleton,
Expand Down Expand Up @@ -100,9 +96,9 @@ export function ConversationViewContentNew({
const isTranscript = !isTimeline;

// The transcript renders its own chat-shaped skeleton inside the layout below;
// the timeline tab keeps the legacy span-detail skeleton.
// the timeline tab renders the redesigned span-timeline skeleton.
if (isLoading && !isTranscript) {
return <ConversationViewSkeleton />;
return <ConversationViewSkeletonNew />;
}

if (error) {
Expand All @@ -115,76 +111,43 @@ export function ConversationViewContentNew({

return (
<TraceStateProvider initialPreferences={DEFAULT_TRACE_VIEW_PREFERENCES}>
<Flex flex="1" minWidth="0" minHeight="0" overflow="hidden">
<ConversationLeftPanel>
<Container
containerType="inline-size"
flex="1"
minHeight="0"
width="100%"
background="secondary"
>
<Flex
direction={{xs: 'column', sm: 'row'}}
height="100%"
width="100%"
gap="md"
minHeight="0"
overflowY="auto"
overflowX="hidden"
>
<Container
flex={{xs: '0 0 auto', sm: '1'}}
minWidth="0"
minHeight={{xs: 'auto', sm: '0'}}
padding={isTranscript ? '0' : 'md'}
background="primary"
border="primary"
radius="md"
overflowX="hidden"
overflowY={{xs: 'hidden', sm: 'auto'}}
>
{isTranscript ? (
isLoading ? (
<MessagesPanelSkeleton />
) : (
<MessagesPanelNew
nodes={nodes}
selectedNodeId={selectedNode?.id ?? null}
onSelectNode={handleSelectAndOpenDetail}
nodeTraceMap={nodeTraceMap}
/>
)
) : (
<AiSpanTimeline
nodes={nodes}
selectedNodeKey={selectedNode?.id ?? ''}
onSelectNode={handleSelectAndOpenDetail}
nodeTraceMap={nodeTraceMap}
compressGaps
/>
)}
</Container>
{detailState.detailOpen && selectedNode ? (
<Flex
width={{xs: '100%', sm: '430px'}}
flex="0 0 auto"
minHeight={{xs: 'auto', sm: '0'}}
>
<ConversationSpanDetail
scrollResetKey={activeTab}
node={selectedNode}
traceId={nodeTraceMap?.get(selectedNode.id) ?? ''}
activeTab={detailState.detailTab}
onTabChange={detailTab => setDetailState({detailTab})}
onClose={() => setDetailState({detailOpen: false, detailTab: null})}
/>
</Flex>
) : null}
</Flex>
</Container>
</ConversationLeftPanel>
</Flex>
<ConversationTimelineLayout
leftPadding={isTranscript ? '0' : 'md'}
left={
isTranscript ? (
isLoading ? (
<MessagesPanelSkeleton />
) : (
<MessagesPanelNew
nodes={nodes}
selectedNodeId={selectedNode?.id ?? null}
onSelectNode={handleSelectAndOpenDetail}
nodeTraceMap={nodeTraceMap}
/>
)
) : (
<AiSpanTimeline
nodes={nodes}
selectedNodeKey={selectedNode?.id ?? ''}
onSelectNode={handleSelectAndOpenDetail}
nodeTraceMap={nodeTraceMap}
compressGaps
/>
)
}
right={
detailState.detailOpen && selectedNode ? (
<ConversationSpanDetail
scrollResetKey={activeTab}
node={selectedNode}
traceId={nodeTraceMap?.get(selectedNode.id) ?? ''}
activeTab={detailState.detailTab}
onTabChange={detailTab => setDetailState({detailTab})}
onClose={() => setDetailState({detailOpen: false, detailTab: null})}
/>
) : null
}
/>
</TraceStateProvider>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import {Container, Flex, Grid, Stack} from '@sentry/scraps/layout';

import {Placeholder} from 'sentry/components/placeholder';
import {
ConversationTimelineLayout,
SpanDetailCard,
} from 'sentry/views/explore/conversations/components/conversationLayout';

const TIMELINE_SKELETON_ROWS: Array<{
title: string;
indent?: boolean;
secondary?: string;
}> = [
{title: '130px', secondary: '90px'},
{title: '90px', secondary: '60px', indent: true},
{title: '110px', indent: true},
{title: '80px', secondary: '100px', indent: true},
{title: '85px', secondary: '55px', indent: true},
{title: '120px', indent: true},
{title: '100px', secondary: '70px'},
{title: '95px', secondary: '85px', indent: true},
];

export function ConversationViewSkeletonNew() {
return (
<ConversationTimelineLayout
left={<TimelineSkeleton />}
right={<SpanDetailSkeleton />}
/>
);
}

function TimelineSkeleton() {
return (
<Stack gap="xs">
{TIMELINE_SKELETON_ROWS.map((row, index) => (
<Container
key={`${row.title}-${index}`}
padding="xs"
paddingLeft={row.indent ? 'xl' : 'xs'}
>
<Stack gap="xs">
<Flex align="center" gap="md">
<Placeholder height="16px" width="16px" />
<Placeholder height="14px" width={row.title} />
{row.secondary ? <Placeholder height="14px" width={row.secondary} /> : null}
<Flex flex="1" justify="end" gap="md">
<Placeholder height="14px" width="48px" />
<Placeholder height="14px" width="40px" />
</Flex>
</Flex>
<Placeholder height="4px" width="100%" />
</Stack>
</Container>
))}
</Stack>
);
}

function SpanDetailSkeleton() {
return (
<SpanDetailCard>
<Flex align="center" gap="lg" flexShrink={0}>
<Placeholder height="16px" width="16px" />
<Placeholder height="16px" width="180px" />
</Flex>
<Stack gap="md" flexShrink={0}>
<Placeholder height="16px" width="60px" />
<Grid columns="max-content minmax(0, 1fr)" gap="md lg" align="center">
<Placeholder height="14px" width="80px" />
<Placeholder height="14px" width="200px" />
<Placeholder height="14px" width="60px" />
<Placeholder height="14px" width="160px" />
</Grid>
</Stack>
<Flex gap="lg" flexShrink={0} borderBottom="primary" paddingBottom="sm">
<Placeholder height="16px" width="40px" />
<Placeholder height="16px" width="48px" />
<Placeholder height="16px" width="72px" />
</Flex>
<Placeholder height="240px" width="100%" />
</SpanDetailCard>
);
}
Loading