History migration: Add error handling for all history requests (#12872)

* Add error handling for all history requests

* Remove comment

GitOrigin-RevId: 528dc98a0fc4ab523f8536274996c4166be45064
This commit is contained in:
Tim Down 2023-05-02 15:04:58 +01:00 committed by Copybot
parent 70bae34bd8
commit 1fb921de99
9 changed files with 86 additions and 49 deletions

View file

@ -4,7 +4,7 @@ import { Alert } from 'react-bootstrap'
// Using this workaround due to inconsistent and improper error responses from the server // Using this workaround due to inconsistent and improper error responses from the server
type ModalErrorProps = { type ModalErrorProps = {
error: { error: {
response: Response response?: Response
data?: { data?: {
message?: string message?: string
} }
@ -14,7 +14,7 @@ type ModalErrorProps = {
function ModalError({ error }: ModalErrorProps) { function ModalError({ error }: ModalErrorProps) {
const { t } = useTranslation() const { t } = useTranslation()
if (error.response.status === 400 && error?.data?.message) { if (error.response?.status === 400 && error.data?.message) {
return <Alert bsStyle="danger">{error.data.message}</Alert> return <Alert bsStyle="danger">{error.data.message}</Alert>
} }

View file

@ -7,12 +7,13 @@ import { useHistoryContext } from '../../context/history-context'
import { diffDoc } from '../../services/api' import { diffDoc } from '../../services/api'
import { highlightsFromDiffResponse } from '../../utils/highlights-from-diff-response' import { highlightsFromDiffResponse } from '../../utils/highlights-from-diff-response'
import useAsync from '../../../../shared/hooks/use-async' import useAsync from '../../../../shared/hooks/use-async'
import ErrorMessage from '../error-message'
function DiffView() { function DiffView() {
const [diff, setDiff] = useState<Nullable<Diff>>(null) const [diff, setDiff] = useState<Nullable<Diff>>(null)
const { selection, projectId } = useHistoryContext() const { selection, projectId } = useHistoryContext()
const { isLoading, runAsync } = useAsync<DocDiffResponse>() const { isLoading, runAsync, error } = useAsync<DocDiffResponse>()
const { updateRange, selectedFile } = selection const { updateRange, selectedFile } = selection
@ -23,9 +24,8 @@ function DiffView() {
const { fromV, toV } = updateRange const { fromV, toV } = updateRange
// TODO: Error handling runAsync(diffDoc(projectId, fromV, toV, selectedFile.pathname))
runAsync(diffDoc(projectId, fromV, toV, selectedFile.pathname)).then( .then(data => {
data => {
let diff: Diff | undefined let diff: Diff | undefined
if (!data?.diff) { if (!data?.diff) {
@ -42,18 +42,24 @@ function DiffView() {
} }
setDiff(diff) setDiff(diff)
} })
) .catch(console.error)
}, [projectId, runAsync, updateRange, selectedFile]) }, [projectId, runAsync, updateRange, selectedFile])
return ( return (
<div className="doc-panel"> <div className="doc-panel">
<div className="history-header toolbar-container"> {error ? (
<Toolbar diff={diff} selection={selection} /> <ErrorMessage />
</div> ) : (
<div className="doc-container"> <>
<Main diff={diff} isLoading={isLoading} /> <div className="history-header toolbar-container">
</div> <Toolbar diff={diff} selection={selection} />
</div>
<div className="doc-container">
<Main diff={diff} isLoading={isLoading} />
</div>
</>
)}
</div> </div>
) )
} }

View file

@ -0,0 +1,13 @@
import { useTranslation } from 'react-i18next'
export default function ErrorMessage() {
const { t } = useTranslation()
return (
<div className="history-error">
<div className="text-danger error">
{t('generic_something_went_wrong')}
</div>
</div>
)
}

View file

@ -7,13 +7,13 @@ import {
import HistoryFileTreeFolderList from './file-tree/history-file-tree-folder-list' import HistoryFileTreeFolderList from './file-tree/history-file-tree-folder-list'
export default function HistoryFileTree() { export default function HistoryFileTree() {
const { files } = useHistoryContext().selection const { selection, error } = useHistoryContext()
const fileTree = _.reduce(files, reducePathsToTree, []) const fileTree = _.reduce(selection.files, reducePathsToTree, [])
const mappedFileTree = fileTreeDiffToFileTreeData(fileTree) const mappedFileTree = fileTreeDiffToFileTreeData(fileTree)
return ( return error ? null : (
<HistoryFileTreeFolderList <HistoryFileTreeFolderList
folders={mappedFileTree.folders} folders={mappedFileTree.folders}
docs={mappedFileTree.docs ?? []} docs={mappedFileTree.docs ?? []}

View file

@ -4,27 +4,33 @@ import { HistoryProvider, useHistoryContext } from '../context/history-context'
import { createPortal } from 'react-dom' import { createPortal } from 'react-dom'
import HistoryFileTree from './history-file-tree' import HistoryFileTree from './history-file-tree'
import LoadingSpinner from '../../../shared/components/loading-spinner' import LoadingSpinner from '../../../shared/components/loading-spinner'
import ErrorMessage from './error-message'
const fileTreeContainer = document.getElementById('history-file-tree') const fileTreeContainer = document.getElementById('history-file-tree')
function Main() { function Main() {
const { loadingState } = useHistoryContext() const { loadingState, error } = useHistoryContext()
let content = null
if (loadingState === 'loadingInitial') {
content = <LoadingSpinner />
} else if (error) {
content = <ErrorMessage />
} else {
content = (
<>
<DiffView />
<ChangeList />
</>
)
}
return ( return (
<> <>
{fileTreeContainer {fileTreeContainer
? createPortal(<HistoryFileTree />, fileTreeContainer) ? createPortal(<HistoryFileTree />, fileTreeContainer)
: null} : null}
<div className="history-react"> <div className="history-react">{content}</div>
{loadingState === 'loadingInitial' ? (
<LoadingSpinner />
) : (
<>
<DiffView />
<ChangeList />
</>
)}
</div>
</> </>
) )
} }

View file

@ -88,7 +88,7 @@ function useHistory() {
const [labels, setLabels] = useState<HistoryContextValue['labels']>(null) const [labels, setLabels] = useState<HistoryContextValue['labels']>(null)
const [loadingState, setLoadingState] = const [loadingState, setLoadingState] =
useState<HistoryContextValue['loadingState']>('loadingInitial') useState<HistoryContextValue['loadingState']>('loadingInitial')
const [error, setError] = useState(null) const [error, setError] = useState<HistoryContextValue['error']>(null)
const fetchNextBatchOfUpdates = useCallback(() => { const fetchNextBatchOfUpdates = useCallback(() => {
const loadUpdates = (updatesData: Update[]) => { const loadUpdates = (updatesData: Update[]) => {
@ -197,28 +197,32 @@ function useHistory() {
} }
const { fromV, toV } = updateRange const { fromV, toV } = updateRange
diffFiles(projectId, fromV, toV).then(({ diff: files }) => { diffFiles(projectId, fromV, toV)
const selectedFile = autoSelectFile( .then(({ diff: files }) => {
files, const selectedFile = autoSelectFile(
updateRange.toV, files,
comparing, updateRange.toV,
updates comparing,
) updates
const newFiles = files.map(file => { )
if (isFileRenamed(file) && file.newPathname) { const newFiles = files.map(file => {
return renamePathnameKey(file) if (isFileRenamed(file) && file.newPathname) {
} return renamePathnameKey(file)
}
return file return file
})
setSelection({
updateRange,
comparing,
files: newFiles,
selectedFile,
})
}) })
setSelection({ .catch(error => {
updateRange, setError(error)
comparing,
files: newFiles,
selectedFile,
}) })
}) }, [updateRange, projectId, updates, comparing, setError])
}, [updateRange, projectId, updates, comparing])
useEffect(() => { useEffect(() => {
// Set update range if there isn't one and updates have loaded // Set update range if there isn't one and updates have loaded
@ -239,6 +243,7 @@ function useHistory() {
const value = useMemo<HistoryContextValue>( const value = useMemo<HistoryContextValue>(
() => ({ () => ({
error, error,
setError,
loadingState, loadingState,
setLoadingState, setLoadingState,
updatesInfo, updatesInfo,
@ -255,6 +260,7 @@ function useHistory() {
}), }),
[ [
error, error,
setError,
loadingState, loadingState,
setLoadingState, setLoadingState,
updatesInfo, updatesInfo,

View file

@ -9,7 +9,7 @@ import type { HistoryContextValue } from '../types/history-context-value'
export function useRestoreDeletedFile() { export function useRestoreDeletedFile() {
const { runAsync } = useAsync() const { runAsync } = useAsync()
const { setLoadingState, projectId } = useHistoryContext() const { setLoadingState, projectId, setError } = useHistoryContext()
const ide = useIdeContext() const ide = useIdeContext()
const { setView } = useLayoutContext() const { setView } = useLayoutContext()
@ -35,6 +35,7 @@ export function useRestoreDeletedFile() {
setView('editor') setView('editor')
}) })
.catch(error => setError(error))
.finally(() => { .finally(() => {
setLoadingState('ready') setLoadingState('ready')
}) })

View file

@ -27,6 +27,7 @@ export type HistoryContextValue = {
React.SetStateAction<HistoryContextValue['loadingState']> React.SetStateAction<HistoryContextValue['loadingState']>
> >
error: Nullable<unknown> error: Nullable<unknown>
setError: React.Dispatch<React.SetStateAction<HistoryContextValue['error']>>
labels: Nullable<LoadedLabel[]> labels: Nullable<LoadedLabel[]>
setLabels: React.Dispatch<React.SetStateAction<HistoryContextValue['labels']>> setLabels: React.Dispatch<React.SetStateAction<HistoryContextValue['labels']>>
projectId: string projectId: string

View file

@ -485,3 +485,7 @@ history-root {
} }
} }
} }
.history-error {
padding: 16px;
}