-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
feat(replay): Visually compare & diff react hydration errors on the Replay Details page #61477
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
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
bc59c1a
wip - visually compare react hydration errors with replay
ryan953 3653f92
add an html diff snippet too
ryan953 3e6109f
rename breadcrumb and add feature flag
ryan953 7aa9b61
remove console.log
ryan953 78b5b81
add missing mock
ryan953 d2c04f5
Merge branch 'master' into react-hydration-visual-compare
scttcper 6995528
rename to `replay.hydrate-error`
scttcper a9f3070
whoops
scttcper 0e623fe
add tabs to the modal, shrink the button
scttcper c98f319
switch to monaco for diffing
scttcper 43de60d
move button into its own component, add lazy loading
scttcper File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 69 additions & 0 deletions
69
static/app/components/replays/breadcrumbs/openReplayComparisonButton.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import {Fragment, lazy, Suspense} from 'react'; | ||
| import {css} from '@emotion/react'; | ||
|
|
||
| import {openModal} from 'sentry/actionCreators/modal'; | ||
| import {Button} from 'sentry/components/button'; | ||
| import LoadingIndicator from 'sentry/components/loadingIndicator'; | ||
| import {t} from 'sentry/locale'; | ||
| import ReplayReader from 'sentry/utils/replays/replayReader'; | ||
| import useOrganization from 'sentry/utils/useOrganization'; | ||
|
|
||
| const LazyComparisonModal = lazy( | ||
| () => import('sentry/components/replays/breadcrumbs/replayComparisonModal') | ||
| ); | ||
|
|
||
| interface Props { | ||
| leftTimestamp: number; | ||
| replay: null | ReplayReader; | ||
| rightTimestamp: number; | ||
| } | ||
|
|
||
| export function OpenReplayComparisonButton({ | ||
| leftTimestamp, | ||
| replay, | ||
| rightTimestamp, | ||
| }: Props) { | ||
| const organization = useOrganization(); | ||
|
|
||
| return ( | ||
| <Button | ||
| role="button" | ||
| size="xs" | ||
| onClick={() => { | ||
| openModal( | ||
| deps => ( | ||
| <Suspense | ||
| fallback={ | ||
| <Fragment> | ||
| <deps.Header closeButton> | ||
| <deps.Header>{t('Hydration Error')}</deps.Header> | ||
| </deps.Header> | ||
| <deps.Body> | ||
| <LoadingIndicator /> | ||
| </deps.Body> | ||
| </Fragment> | ||
| } | ||
| > | ||
| <LazyComparisonModal | ||
| replay={replay} | ||
| organization={organization} | ||
| leftTimestamp={leftTimestamp} | ||
| rightTimestamp={rightTimestamp} | ||
| {...deps} | ||
| /> | ||
| </Suspense> | ||
| ), | ||
| {modalCss} | ||
| ); | ||
| }} | ||
| > | ||
| {t('Open Hydration Diff')} | ||
| </Button> | ||
| ); | ||
| } | ||
|
|
||
| const modalCss = css` | ||
| width: 95vw; | ||
| min-height: 80vh; | ||
| max-height: 95vh; | ||
| `; |
145 changes: 145 additions & 0 deletions
145
static/app/components/replays/breadcrumbs/replayComparisonModal.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| import {useEffect, useState} from 'react'; | ||
| import styled from '@emotion/styled'; | ||
| import {DiffEditor} from '@monaco-editor/react'; | ||
| import beautify from 'js-beautify'; | ||
|
|
||
| import {ModalRenderProps} from 'sentry/actionCreators/modal'; | ||
| import {Flex} from 'sentry/components/profiling/flex'; | ||
| import { | ||
| Provider as ReplayContextProvider, | ||
| useReplayContext, | ||
| } from 'sentry/components/replays/replayContext'; | ||
| import ReplayPlayer from 'sentry/components/replays/replayPlayer'; | ||
| import {TabList} from 'sentry/components/tabs'; | ||
| import {t} from 'sentry/locale'; | ||
| import ConfigStore from 'sentry/stores/configStore'; | ||
| import {useLegacyStore} from 'sentry/stores/useLegacyStore'; | ||
| import {space} from 'sentry/styles/space'; | ||
| import {Organization} from 'sentry/types'; | ||
| import ReplayReader from 'sentry/utils/replays/replayReader'; | ||
| import {OrganizationContext} from 'sentry/views/organizationContext'; | ||
|
|
||
| interface Props extends ModalRenderProps { | ||
| leftTimestamp: number; | ||
| organization: Organization; | ||
| replay: null | ReplayReader; | ||
| rightTimestamp: number; | ||
| } | ||
|
|
||
| export default function ReplayComparisonModal({ | ||
| Body, | ||
| Header, | ||
| leftTimestamp, | ||
| organization, | ||
| replay, | ||
| rightTimestamp, | ||
| }: Props) { | ||
| const fetching = false; | ||
|
|
||
| const config = useLegacyStore(ConfigStore); | ||
| const isDark = config.theme === 'dark'; | ||
|
|
||
| const [activeTab, setActiveTab] = useState<'visual' | 'html'>('html'); | ||
|
|
||
| const [leftBody, setLeftBody] = useState(null); | ||
| const [rightBody, setRightBody] = useState(null); | ||
|
|
||
| return ( | ||
| <OrganizationContext.Provider value={organization}> | ||
| <Header closeButton> | ||
| <h4>{t('Hydration Error')}</h4> | ||
| </Header> | ||
| <Body> | ||
| <Flex gap={space(2)} column> | ||
| <TabList | ||
| hideBorder | ||
| selectedKey={activeTab} | ||
| onSelectionChange={tab => setActiveTab(tab as 'visual' | 'html')} | ||
| > | ||
| <TabList.Item key="html">Html Diff</TabList.Item> | ||
| <TabList.Item key="visual">Visual Diff</TabList.Item> | ||
| </TabList> | ||
| <Flex | ||
| gap={space(2)} | ||
| style={{ | ||
| // Using css to hide since the splitdiff uses the html from the iframes | ||
| // TODO: This causes a bit of a flash when switching tabs | ||
| display: activeTab === 'visual' ? undefined : 'none', | ||
| }} | ||
| > | ||
| <ReplayContextProvider | ||
| isFetching={fetching} | ||
| replay={replay} | ||
| initialTimeOffsetMs={{offsetMs: leftTimestamp - 1}} | ||
| > | ||
| <ComparisonSideWrapper id="leftSide"> | ||
| <ReplaySide | ||
| selector="#leftSide iframe" | ||
| expectedTime={leftTimestamp - 1} | ||
| onLoad={setLeftBody} | ||
| /> | ||
| </ComparisonSideWrapper> | ||
| </ReplayContextProvider> | ||
| <ReplayContextProvider | ||
| isFetching={fetching} | ||
| replay={replay} | ||
| initialTimeOffsetMs={{offsetMs: rightTimestamp + 1}} | ||
| > | ||
| <ComparisonSideWrapper id="rightSide"> | ||
| <ReplaySide | ||
| selector="#rightSide iframe" | ||
| expectedTime={rightTimestamp + 1} | ||
| onLoad={setRightBody} | ||
| /> | ||
| </ComparisonSideWrapper> | ||
| </ReplayContextProvider> | ||
| </Flex> | ||
| {activeTab === 'html' && leftBody && rightBody ? ( | ||
| <div> | ||
| <DiffEditor | ||
| height="60vh" | ||
| theme={isDark ? 'vs-dark' : 'light'} | ||
| language="html" | ||
| original={leftBody} | ||
| modified={rightBody} | ||
| options={{ | ||
| // Options - https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IDiffEditorConstructionOptions.html | ||
| scrollBeyondLastLine: false, | ||
| readOnly: true, | ||
| }} | ||
| /> | ||
| </div> | ||
| ) : null} | ||
| </Flex> | ||
| </Body> | ||
| </OrganizationContext.Provider> | ||
| ); | ||
| } | ||
|
|
||
| function ReplaySide({expectedTime, selector, onLoad}) { | ||
| const {currentTime} = useReplayContext(); | ||
|
|
||
| useEffect(() => { | ||
| if (currentTime === expectedTime) { | ||
| setTimeout(() => { | ||
| const iframe = document.querySelector(selector) as HTMLIFrameElement; | ||
| const body = iframe.contentWindow?.document.body; | ||
| if (body) { | ||
| onLoad( | ||
| beautify.html(body.innerHTML, { | ||
| indent_size: 2, | ||
| wrap_line_length: 80, | ||
| }) | ||
| ); | ||
| } | ||
| }, 0); | ||
| } | ||
| }, [currentTime, expectedTime, selector, onLoad]); | ||
| return <ReplayPlayer isPreview />; | ||
| } | ||
|
|
||
| const ComparisonSideWrapper = styled('div')` | ||
| display: contents; | ||
| flex-grow: 1; | ||
| max-width: 50%; | ||
| `; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fix that nesting issue i see