Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
7e8dae2
refactor(replays): Update replay header layout and styling
jerryzhou196 Nov 26, 2025
ba2e5d7
refactor(replays): Enhance ReplayDetails component with new UI header
jerryzhou196 Nov 26, 2025
5357092
refactor(platformList): Simplify styling and remove unused components
jerryzhou196 Nov 28, 2025
378a7cd
undo avatar list change
jerryzhou196 Nov 28, 2025
99fca14
refactor(replays): Simplify styled components and adjust layout in re…
jerryzhou196 Nov 28, 2025
53ec39d
added platformList PR (https://github.com/getsentry/sentry/pull/104135)
jerryzhou196 Nov 28, 2025
a564b50
removed unnecessary margin-left
jerryzhou196 Nov 28, 2025
b8c0d84
Merge branch 'new-replay-ui-2-fix-badges' into new-replay-ui-1-2-3
jerryzhou196 Nov 28, 2025
d8d2d3d
Merge branch 'new-replay-ui-1-reduce-top-header' into new-replay-ui-1…
jerryzhou196 Nov 28, 2025
57bfcdd
style(replays): Adjust height of KeyMetrics component for improved la…
jerryzhou196 Nov 28, 2025
c1ef79e
Merge branch new-replay-ui-3-shrink-bottom-header into new-replay-ui-…
jerryzhou196 Nov 28, 2025
781acf6
refactor(replays): Adjust padding in StyledFlex component for improve…
jerryzhou196 Nov 28, 2025
63d07b8
Merge branch 'new-replay-ui-1-reduce-top-header' into new-replay-ui-1…
jerryzhou196 Nov 28, 2025
8e9f79d
removed z-index
jerryzhou196 Nov 28, 2025
89dcbef
Merge branch 'new-replay-ui-1-reduce-top-header' into new-replay-ui-1…
jerryzhou196 Nov 28, 2025
c860b82
added query forwarding
jerryzhou196 Nov 28, 2025
60bd093
style(replays): Replace Flex with StyledFlex in ReplayDetailsUserBadg…
jerryzhou196 Nov 28, 2025
c2d586f
Merge branch 'new-replay-ui-3-shrink-bottom-header' into new-replay-u…
jerryzhou196 Nov 28, 2025
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
1 change: 0 additions & 1 deletion static/app/components/core/avatar/avatarList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@ const AvatarListWrapper = styled('div')`

const AvatarStyle = (p: {theme: Theme}) => css`
border: 2px solid ${p.theme.background};
margin-left: -8px;
cursor: default;
&:hover {
Expand Down
48 changes: 8 additions & 40 deletions static/app/components/platformList.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type {Theme} from '@emotion/react';
import {css} from '@emotion/react';
import styled from '@emotion/styled';
import {PlatformIcon} from 'platformicons';
Expand Down Expand Up @@ -62,41 +61,19 @@ function getOverlapWidth(size: number) {
return Math.round(size / 4);
}

const commonStyles = ({theme}: {theme: Theme}) => css`
cursor: default;
border-radius: ${theme.borderRadius};
box-shadow: 0 0 0 1px ${theme.background};
:hover {
z-index: 1;
}
`;

const PlatformIcons = styled('div')`
display: flex;
`;

const InnerWrapper = styled('div')`
display: flex;
position: relative;
`;

const StyledPlatformIcon = styled(PlatformIcon)`
${p => commonStyles(p)};
`;

const Counter = styled('div')`
${p => commonStyles(p)};
display: flex;
align-items: center;
justify-content: center;
text-align: center;
font-weight: ${p => p.theme.fontWeight.bold};
font-size: ${p => p.theme.fontSize.xs};
background-color: ${p => p.theme.gray200};
color: ${p => p.theme.subText};
padding: 0 1px;
position: absolute;
right: -1px;
cursor: default;
border-radius: ${p => p.theme.borderRadius};
box-shadow: 0 0 0 1px ${p => p.theme.background};
:hover {
z-index: 1;
}
height: ${p => p.size}px;
min-width: ${p => p.size}px;
`;

const Wrapper = styled('div')<WrapperProps>`
Expand All @@ -111,13 +88,4 @@ const Wrapper = styled('div')<WrapperProps>`
}
`}
}

${InnerWrapper} {
padding-right: ${p => p.size / 2 + 1}px;
}

${Counter} {
height: ${p => p.size}px;
min-width: ${p => p.size}px;
}
`;
8 changes: 4 additions & 4 deletions static/app/components/replays/header/replayMetaData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ const KeyMetrics = styled('dl')`
grid-template-rows: max-content 1fr;
grid-template-columns: repeat(4, max-content);
grid-auto-flow: column;
height: 42px;
gap: 0 ${space(3)};
align-items: center;
align-self: end;
color: ${p => p.theme.subText};
margin: 0;

Expand All @@ -126,12 +126,12 @@ const KeyMetrics = styled('dl')`
`;

const KeyMetricLabel = styled('dt')`
font-size: ${p => p.theme.fontSize.md};
font-size: ${p => p.theme.fontSize.sm};
`;

const KeyMetricData = styled('dd')`
font-size: ${p => p.theme.fontSize.xl};
font-weight: ${p => p.theme.fontWeight.normal};
font-size: ${p => p.theme.fontSize.md};
font-weight: ${p => p.theme.fontWeight.bold};
display: flex;
align-items: center;
gap: ${space(1)};
Expand Down
2 changes: 1 addition & 1 deletion static/app/components/replays/header/replayViewers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default function ReplayViewers({projectId, replayId}: Props) {
});

return isPending || isError ? (
<Placeholder width="55px" height="27px" />
<Placeholder width="25px" height="25px" />
) : (
<AvatarList avatarSize={25} users={data?.data.viewed_by} />
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ interface CellProps {
replay: ListRecord;
rowIndex: number;
showDropdownFilters: boolean;
className?: string;
query?: Query;
}

Expand Down Expand Up @@ -511,7 +512,7 @@ export const ReplaySessionColumn: ReplayTableColumn = {
interactive: true,
sortKey: 'started_at',
width: 'minmax(150px, 1fr)',
Component: ({replay, query}) => {
Component: ({replay, query, className}) => {
const routes = useRoutes();
const referrer = getRouteStringFromRoutes(routes);

Expand All @@ -538,6 +539,7 @@ export const ReplaySessionColumn: ReplayTableColumn = {

return (
<CellLink
className={className}
to={{
pathname: makeReplaysPathname({path: `/${replay.id}/`, organization}),
query,
Expand Down
8 changes: 5 additions & 3 deletions static/app/views/nav/secondary/secondary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -271,16 +271,18 @@ const Wrapper = styled('div')`
grid-template-rows: auto 1fr auto;
`;

export const NAV_HEIGHT = '44px';
export const NAV_BORDER_BOTTOM = `1px solid`;

const Header = styled('div')`
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
font-size: ${p => p.theme.fontSize.md};
font-weight: ${p => p.theme.fontWeight.bold};
padding: 0 ${space(1)} 0 ${space(2)};
height: 44px;
border-bottom: 1px solid ${p => p.theme.innerBorder};

height: ${NAV_HEIGHT};
border-bottom: ${NAV_BORDER_BOTTOM} ${p => p.theme.innerBorder};
button {
color: inherit;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function ReplayDetailsHeaderActions({readerResult}: Props) {
renderArchived={() => null}
renderError={() => null}
renderThrottled={() => null}
renderLoading={() => <Placeholder height="33px" width="203px" />}
renderLoading={() => <Placeholder height="32px" width="352px" />}
renderMissing={() => null}
renderProcessingError={({replayRecord, projectSlug}) => (
<ButtonActionsWrapper>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {Flex} from 'sentry/components/core/layout';
import Placeholder from 'sentry/components/placeholder';
import ReplayMetaData from 'sentry/components/replays/header/replayMetaData';
import ReplayLoadingState from 'sentry/components/replays/player/replayLoadingState';
Expand All @@ -14,7 +15,11 @@ export default function ReplayDetailsMetadata({readerResult}: Props) {
renderArchived={() => null}
renderError={() => null}
renderThrottled={() => null}
renderLoading={() => <Placeholder height="47px" width="203px" />}
renderLoading={() => (
<Flex justify="end">
<Placeholder height="42px" width="276px" />
</Flex>
)}
renderMissing={() => null}
renderProcessingError={() => null}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ export default function ReplayDetailsPageBreadcrumbs({readerResult}: Props) {

const StyledBreadcrumbs = styled(Breadcrumbs)`
padding: 0;
z-index: 1;
height: 34px;
`;

const ShortId = styled('div')`
Expand Down
149 changes: 30 additions & 119 deletions static/app/views/replays/detail/header/replayDetailsUserBadge.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,25 @@
import styled from '@emotion/styled';

import {Flex} from '@sentry/scraps/layout';
import {Text} from '@sentry/scraps/text';
import {Tooltip} from '@sentry/scraps/tooltip';

import {Button} from 'sentry/components/core/button';
import {Link} from 'sentry/components/core/link';
import UserBadge from 'sentry/components/idBadge/userBadge';
import * as Layout from 'sentry/components/layouts/thirds';
import {Flex} from 'sentry/components/core/layout';
import Placeholder from 'sentry/components/placeholder';
import ReplayLoadingState from 'sentry/components/replays/player/replayLoadingState';
import {
getLiveDurationMs,
getReplayExpiresAtMs,
LIVE_TOOLTIP_MESSAGE,
LiveIndicator,
} from 'sentry/components/replays/replayLiveIndicator';
import TimeSince from 'sentry/components/timeSince';
import {IconCalendar, IconRefresh} from 'sentry/icons';
import {getReplayExpiresAtMs} from 'sentry/components/replays/replayLiveIndicator';
import {ReplaySessionColumn} from 'sentry/components/replays/table/replayTableColumns';
import {IconRefresh} from 'sentry/icons';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import {trackAnalytics} from 'sentry/utils/analytics';
import {useQueryClient} from 'sentry/utils/queryClient';
import type useLoadReplayReader from 'sentry/utils/replays/hooks/useLoadReplayReader';
import usePollReplayRecord from 'sentry/utils/replays/hooks/usePollReplayRecord';
import {useReplayProjectSlug} from 'sentry/utils/replays/hooks/useReplayProjectSlug';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';
import {useReplaySummaryContext} from 'sentry/views/replays/detail/ai/replaySummaryContext';
import {makeReplaysPathname} from 'sentry/views/replays/pathnames';

interface Props {
readerResult: ReturnType<typeof useLoadReplayReader>;
}

export default function ReplayDetailsUserBadge({readerResult}: Props) {
const organization = useOrganization();
const replayRecord = readerResult.replayRecord;
Expand All @@ -41,28 +28,7 @@ export default function ReplayDetailsUserBadge({readerResult}: Props) {
const replayId = readerResult.replayId;

const queryClient = useQueryClient();

// Generate search query based on available user data
const getUserSearchQuery = () => {
if (!replayRecord?.user) {
return null;
}

const user = replayRecord.user;
// Prefer email over id for search query
if (user.email) {
return `user.email:"${user.email}"`;
}
if (user.id) {
return `user.id:"${user.id}"`;
}
return null;
};

const searchQuery = getUserSearchQuery();
const userDisplayName = replayRecord?.user.display_name || t('Anonymous User');
const projectSlug = useReplayProjectSlug({replayRecord});

const {startSummaryRequest} = useReplaySummaryContext();

const handleRefresh = async () => {
Expand Down Expand Up @@ -99,77 +65,27 @@ export default function ReplayDetailsUserBadge({readerResult}: Props) {

const showRefreshButton = polledCountSegments > prevSegments;

const showLiveIndicator =
!isReplayExpired && replayRecord && getLiveDurationMs(replayRecord.finished_at) > 0;
const location = useLocation();

const badge = replayRecord ? (
<UserBadge
avatarSize={24}
displayName={
<DisplayHeader>
<Layout.Title>
{searchQuery ? (
<Link
to={{
pathname: makeReplaysPathname({
path: '/',
organization,
}),
query: {
query: searchQuery,
},
}}
>
{userDisplayName}
</Link>
) : (
userDisplayName
)}
</Layout.Title>
{replayRecord.started_at ? (
<TimeContainer>
<IconCalendar color="gray300" size="xs" />
<TimeSince
date={replayRecord.started_at}
isTooltipHoverable
unitStyle="regular"
/>
{showLiveIndicator ? (
<Tooltip
title={LIVE_TOOLTIP_MESSAGE}
underlineColor="success"
showUnderline
>
<Flex align="center">
<Text bold variant="success" data-test-id="live-badge">
{t('LIVE')}
</Text>
<LiveIndicator />
</Flex>
</Tooltip>
) : null}
<Button
title={t('Replay is outdated. Refresh for latest activity.')}
data-test-id="refresh-button"
size="xs"
onClick={handleRefresh}
style={{visibility: showRefreshButton ? 'visible' : 'hidden'}}
>
<IconRefresh />
</Button>
</TimeContainer>
) : null}
</DisplayHeader>
}
user={{
name: replayRecord.user.display_name || '',
email: replayRecord.user.email || '',
username: replayRecord.user.username || '',
ip_address: replayRecord.user.ip || '',
id: replayRecord.user.id || '',
}}
hideEmail
/>
<ColumnWrapper gap="md">
<StyledReplaySessionColumn
replay={replayRecord}
rowIndex={0}
columnIndex={0}
showDropdownFilters={false}
query={location.query}
/>
<Button
title={t('Replay is outdated. Refresh for latest activity.')}
data-test-id="refresh-button"
size="xs"
onClick={handleRefresh}
style={{visibility: showRefreshButton ? 'visible' : 'hidden'}}
>
<IconRefresh />
</Button>
</ColumnWrapper>
) : null;

return (
Expand All @@ -179,7 +95,7 @@ export default function ReplayDetailsUserBadge({readerResult}: Props) {
renderError={() => null}
renderThrottled={() => null}
renderLoading={() =>
replayRecord ? badge : <Placeholder width="30%" height="68px" />
replayRecord ? badge : <Placeholder width="251px" height="42px" />
}
renderMissing={() => null}
renderProcessingError={() => badge}
Expand All @@ -189,16 +105,11 @@ export default function ReplayDetailsUserBadge({readerResult}: Props) {
);
}

const TimeContainer = styled('div')`
display: flex;
gap: ${space(1)};
align-items: center;
color: ${p => p.theme.subText};
font-size: ${p => p.theme.fontSize.md};
line-height: 1.4;
// column components expect to be stored in a relative container
const ColumnWrapper = styled(Flex)`
position: relative;
`;

const DisplayHeader = styled('div')`
display: flex;
flex-direction: column;
const StyledReplaySessionColumn = styled(ReplaySessionColumn.Component)`
flex: 0;
`;
Loading
Loading