fix(core): implement editor timeout and report error from boundary (#5105) (#5151)

fix(core): implement editor timeout and report error from boundary (#5105)

ci: add sentry env when frontend assets build (#5131)

fix(core): expose catched editor load error (#5133)

fix(infra): use blocksuite api to check compatibility (#5137)

fix(infra): compatibility logic follow blocksuite (#5143)

fix(core): rerender error boundary when route change and improve sentry report (#5147)
This commit is contained in:
Joooye_34
2023-12-01 07:25:08 +00:00
parent 99f98fb9d3
commit eb7d293aaa
42 changed files with 791 additions and 342 deletions

View File

@@ -7,14 +7,12 @@ import type { CSSProperties, ReactElement } from 'react';
import {
memo,
Suspense,
useCallback,
useEffect,
useLayoutEffect,
useRef,
useState,
} from 'react';
import type { FallbackProps } from 'react-error-boundary';
import { ErrorBoundary } from 'react-error-boundary';
import { type Map as YMap } from 'yjs';
import { Skeleton } from '../../ui/skeleton';
import {
@@ -77,6 +75,67 @@ const useBlockElementById = (
return blockElement;
};
/**
* TODO: Define error to unexpected state together in the future.
*/
export class NoPageRootError extends Error {
constructor(public page: Page) {
super('Page root not found when render editor!');
// Log info to let sentry collect more message
const hasExpectSpace = Array.from(page.doc.spaces.values()).some(
doc => page.spaceDoc.guid === doc.guid
);
const blocks = page.spaceDoc.getMap('blocks') as YMap<YMap<any>>;
const havePageBlock = Array.from(blocks.values()).some(
block => block.get('sys:flavour') === 'affine:page'
);
console.info(
'NoPageRootError current data: %s',
JSON.stringify({
expectPageId: page.id,
expectGuid: page.spaceDoc.guid,
hasExpectSpace,
blockSize: blocks.size,
havePageBlock,
})
);
}
}
/**
* TODO: Defined async cache to support suspense, instead of reflect symbol to provider persistent error cache.
*/
const PAGE_LOAD_KEY = Symbol('PAGE_LOAD');
const PAGE_ROOT_KEY = Symbol('PAGE_ROOT');
function usePageRoot(page: Page) {
let load$ = Reflect.get(page, PAGE_LOAD_KEY);
if (!load$) {
load$ = page.load();
Reflect.set(page, PAGE_LOAD_KEY, load$);
}
use(load$);
if (!page.root) {
let root$: Promise<void> | undefined = Reflect.get(page, PAGE_ROOT_KEY);
if (!root$) {
root$ = new Promise((resolve, reject) => {
const disposable = page.slots.rootAdded.once(() => {
resolve();
});
window.setTimeout(() => {
disposable.dispose();
reject(new NoPageRootError(page));
}, 20 * 1000);
});
Reflect.set(page, PAGE_ROOT_KEY, root$);
}
use(root$);
}
return page.root;
}
const BlockSuiteEditorImpl = ({
mode,
page,
@@ -86,9 +145,8 @@ const BlockSuiteEditorImpl = ({
onModeChange,
style,
}: EditorProps): ReactElement => {
if (!page.loaded) {
use(page.waitForLoaded());
}
usePageRoot(page);
assertExists(page, 'page should not be null');
const editorRef = useRef<EditorContainer | null>(null);
if (editorRef.current === null) {
@@ -176,27 +234,7 @@ const BlockSuiteEditorImpl = ({
);
};
const BlockSuiteErrorFallback = (
props: FallbackProps & ErrorBoundaryProps
): ReactElement => {
return (
<div>
<h1>Sorry.. there was an error</h1>
<div>{props.error.message}</div>
<button
data-testid="error-fallback-reset-button"
onClick={() => {
props.onReset?.();
props.resetErrorBoundary();
}}
>
Try again
</button>
</div>
);
};
export const BlockSuiteFallback = memo(function BlockSuiteFallback() {
export const EditorLoading = memo(function EditorLoading() {
return (
<div className={blockSuiteEditorStyle}>
<Skeleton
@@ -210,21 +248,12 @@ export const BlockSuiteFallback = memo(function BlockSuiteFallback() {
});
export const BlockSuiteEditor = memo(function BlockSuiteEditor(
props: EditorProps & ErrorBoundaryProps
props: EditorProps
): ReactElement {
return (
<ErrorBoundary
fallbackRender={useCallback(
(fallbackProps: FallbackProps) => (
<BlockSuiteErrorFallback {...fallbackProps} onReset={props.onReset} />
),
[props.onReset]
)}
>
<Suspense fallback={<BlockSuiteFallback />}>
<BlockSuiteEditorImpl key={props.page.id} {...props} />
</Suspense>
</ErrorBoundary>
<Suspense fallback={<EditorLoading />}>
<BlockSuiteEditorImpl key={props.page.id} {...props} />
</Suspense>
);
});

View File

@@ -1,4 +1,4 @@
import { BlockSuiteFallback } from '../block-suite-editor';
import { EditorLoading } from '../block-suite-editor';
import {
pageDetailSkeletonStyle,
pageDetailSkeletonTitleStyle,
@@ -8,7 +8,7 @@ export const PageDetailSkeleton = () => {
return (
<div className={pageDetailSkeletonStyle}>
<div className={pageDetailSkeletonTitleStyle} />
<BlockSuiteFallback />
<EditorLoading />
</div>
);
};