-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
feat(replays): add ability to travel to previous and next replay #102583
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 77 commits
e336a81
76b424d
81f8b76
0481f7e
b92a135
dda816f
411821b
ba35080
24c97a4
d2ecfbf
8f60afe
7a46ed8
0b6ba8d
8383216
d70cc2d
6277a38
f5f3700
40be3b2
d2f35ac
00a8a90
3db7bba
ab71b4c
1ab7a54
8d8da21
c5ae428
40b2262
a2646bb
3c2e433
525f5d8
afab360
1682740
c0c6c01
cc6b7d0
f5347d6
89e9577
e4d9d75
6561e90
0d4f502
4bfb035
c09d6e6
21ed4b8
cb30e23
3a20fb7
47e4fe3
cb5cbfd
a664264
dd12572
30011c5
353f170
ee88392
108f0d5
5bf9e79
a770c0c
649918f
aac8f09
5218eb6
fb7722f
ab5c5d2
72614f1
25cbaaa
f9aabfa
1f8ba92
b714401
dab5a78
4c03a95
01ab6c3
7deb031
18a2e93
92e9e4a
4860a30
3c093c9
a567e2c
989b379
164a2b2
0fc2f4a
6efdbb3
6f21136
47d5c55
0e36592
5c130e6
302d359
d42da0d
d1aa45a
e03e05d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import type {ReactNode} from 'react'; | ||
| import {createContext, useContext} from 'react'; | ||
|
|
||
| import type {ReplayListRecord} from 'sentry/views/replays/types'; | ||
|
|
||
| interface Props { | ||
| children: ReactNode; | ||
| replays: ReplayListRecord[] | undefined; | ||
| } | ||
|
|
||
| const Context = createContext<ReplayListRecord[] | undefined>(undefined); | ||
|
|
||
| export function ReplayPlaylistProvider({children, replays}: Props) { | ||
| return <Context value={replays}>{children}</Context>; | ||
| } | ||
|
|
||
| export function useReplayPlaylist() { | ||
| return useContext(Context); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,21 @@ | ||
| import {useState} from 'react'; | ||
| import {useMemo, useRef, useState} from 'react'; | ||
| import styled from '@emotion/styled'; | ||
|
|
||
| import {Breadcrumbs} from 'sentry/components/breadcrumbs'; | ||
| import {Button} from 'sentry/components/core/button'; | ||
| import {Button, ButtonBar} from 'sentry/components/core/button'; | ||
| import {LinkButton} from 'sentry/components/core/button/linkButton'; | ||
| import {Flex} from 'sentry/components/core/layout'; | ||
| import {Tooltip} from 'sentry/components/core/tooltip'; | ||
| import ProjectBadge from 'sentry/components/idBadge/projectBadge'; | ||
| import Placeholder from 'sentry/components/placeholder'; | ||
| import {useReplayContext} from 'sentry/components/replays/replayContext'; | ||
| import {IconCopy} from 'sentry/icons'; | ||
| import {IconCopy, IconNext, IconPrevious} from 'sentry/icons'; | ||
| import {t} from 'sentry/locale'; | ||
| import {defined} from 'sentry/utils'; | ||
| import EventView from 'sentry/utils/discover/eventView'; | ||
| import {getShortEventId} from 'sentry/utils/events'; | ||
| import type useLoadReplayReader from 'sentry/utils/replays/hooks/useLoadReplayReader'; | ||
| import {useReplayPlaylist} from 'sentry/utils/replays/playback/providers/replayPlaylistProvider'; | ||
| import useCopyToClipboard from 'sentry/utils/useCopyToClipboard'; | ||
| import {useLocation} from 'sentry/utils/useLocation'; | ||
| import useOrganization from 'sentry/utils/useOrganization'; | ||
|
|
@@ -33,6 +35,26 @@ export default function ReplayDetailsPageBreadcrumbs({readerResult}: Props) { | |
| const [isHovered, setIsHovered] = useState(false); | ||
jerryzhou196 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const {currentTime} = useReplayContext(); | ||
|
|
||
| const replays = useReplayPlaylist(); | ||
|
|
||
| const initialLocation = useRef(location); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Replay Navigation Loses Query ContextThe
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We talked about this offline and agreed this is fine - I'm re-thinking this because I think this will be a maintenance nightmare down the road. It might be better to be explicit about the query params we want to preserve. Let's not change anything on this PR for now, but let's talk about it some more after this gets merged.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sounds good |
||
|
|
||
| const currentReplayIndex = useMemo( | ||
| () => replays?.findIndex(r => r.id === replayRecord?.id) ?? -1, | ||
| [replays, replayRecord] | ||
| ); | ||
|
|
||
| const nextReplay = useMemo( | ||
| () => | ||
| currentReplayIndex >= 0 && currentReplayIndex < (replays?.length ?? 0) - 1 | ||
| ? replays?.[currentReplayIndex + 1] | ||
| : undefined, | ||
| [replays, currentReplayIndex] | ||
| ); | ||
| const previousReplay = useMemo( | ||
| () => (currentReplayIndex > 0 ? replays?.[currentReplayIndex - 1] : undefined), | ||
| [replays, currentReplayIndex] | ||
| ); | ||
| // Create URL with current timestamp for copying | ||
| const replayUrlWithTimestamp = replayRecord | ||
| ? (() => { | ||
|
|
@@ -74,22 +96,53 @@ export default function ReplayDetailsPageBreadcrumbs({readerResult}: Props) { | |
|
|
||
| const replayCrumb = { | ||
| label: replayRecord ? ( | ||
| <Flex | ||
| align="center" | ||
| gap="xs" | ||
| onMouseEnter={() => setIsHovered(true)} | ||
| onMouseLeave={() => setIsHovered(false)} | ||
| > | ||
| <div | ||
| onClick={() => | ||
| copy(replayUrlWithTimestamp, { | ||
| successMessage: t('Copied replay link to clipboard'), | ||
| }) | ||
| } | ||
| <Flex> | ||
| <Flex | ||
| align="center" | ||
| gap="xs" | ||
| onMouseEnter={() => setIsHovered(true)} | ||
| onMouseLeave={() => setIsHovered(false)} | ||
| > | ||
| {getShortEventId(replayRecord?.id)} | ||
| </div> | ||
| {isHovered && ( | ||
| {organization.features.includes('replay-playlist-view') && ( | ||
| <Flex> | ||
| <ButtonBar merged gap="0"> | ||
| <LinkButton | ||
| size="xs" | ||
| icon={<IconPrevious />} | ||
| disabled={!previousReplay} | ||
| to={{ | ||
| pathname: previousReplay | ||
| ? makeReplaysPathname({ | ||
| path: `/${previousReplay.id}/`, | ||
| organization, | ||
| }) | ||
| : undefined, | ||
| query: initialLocation.current.query, | ||
| }} | ||
| /> | ||
| <LinkButton | ||
| size="xs" | ||
| icon={<IconNext />} | ||
| disabled={!nextReplay} | ||
| to={{ | ||
| pathname: nextReplay | ||
| ? makeReplaysPathname({path: `/${nextReplay.id}/`, organization}) | ||
| : undefined, | ||
| query: initialLocation.current.query, | ||
| }} | ||
| /> | ||
| </ButtonBar> | ||
| </Flex> | ||
| )} | ||
| <StyledDiv | ||
jerryzhou196 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| onClick={() => | ||
| copy(replayUrlWithTimestamp, { | ||
| successMessage: t('Copied replay link to clipboard'), | ||
| }) | ||
| } | ||
| > | ||
| {getShortEventId(replayRecord?.id)} | ||
| </StyledDiv> | ||
| <Tooltip title={t('Copy link to replay at current timestamp')}> | ||
| <Button | ||
| aria-label={t('Copy link to replay at current timestamp')} | ||
|
|
@@ -100,10 +153,11 @@ export default function ReplayDetailsPageBreadcrumbs({readerResult}: Props) { | |
| } | ||
| size="zero" | ||
| borderless | ||
| style={isHovered ? {} : {visibility: 'hidden'}} | ||
| icon={<IconCopy size="xs" color="subText" />} | ||
| /> | ||
| </Tooltip> | ||
| )} | ||
| </Flex> | ||
| </Flex> | ||
| ) : ( | ||
| <Placeholder width="100%" height="16px" /> | ||
|
|
@@ -122,3 +176,7 @@ export default function ReplayDetailsPageBreadcrumbs({readerResult}: Props) { | |
| const StyledBreadcrumbs = styled(Breadcrumbs)` | ||
| padding: 0; | ||
| `; | ||
|
|
||
| const StyledDiv = styled('div')` | ||
| margin-left: 10px; | ||
| `; | ||
Uh oh!
There was an error while loading. Please reload this page.