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

This commit is contained in:
Joooye_34
2023-11-30 16:48:13 +08:00
committed by GitHub
parent a226eb8d5f
commit 47d5f9e1c2
10 changed files with 91 additions and 57 deletions

View File

@@ -3,6 +3,7 @@ import type { Page, PageMeta, Workspace } from '@blocksuite/store';
import type { createStore, WritableAtom } from 'jotai/vanilla'; import type { createStore, WritableAtom } from 'jotai/vanilla';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { checkWorkspaceCompatibility, MigrationPoint } from '..';
import { migratePages } from '../migration/blocksuite'; import { migratePages } from '../migration/blocksuite';
export async function initEmptyPage(page: Page, title?: string) { export async function initEmptyPage(page: Page, title?: string) {
@@ -244,46 +245,48 @@ export async function buildShowcaseWorkspace(
{} as Record<string, string> {} as Record<string, string>
); );
}); });
await Promise.all(
data.map(async ([id, promise, newId]) => {
const { default: template } = await promise;
let json = JSON.stringify(template);
Object.entries(idMap).forEach(([oldId, newId]) => {
json = json.replaceAll(oldId, newId);
});
json = JSON.parse(json);
await workspace
.importPageSnapshot(structuredClone(json), newId)
.catch(error => {
console.error('error importing page', id, error);
});
const page = workspace.getPage(newId);
assertExists(page);
await page.load();
workspace.schema.upgradePage(
0,
{
'affine:note': 1,
'affine:bookmark': 1,
'affine:database': 2,
'affine:divider': 1,
'affine:image': 1,
'affine:list': 1,
'affine:code': 1,
'affine:page': 2,
'affine:paragraph': 1,
'affine:surface': 3,
},
page.spaceDoc
);
// The showcase building will create multiple pages once, and may skip the version writing. // Import page one by one to prevent workspace meta race condition problem.
// https://github.com/toeverything/blocksuite/blob/master/packages/store/src/workspace/page.ts#L662 for (const [id, promise, newId] of data) {
if (!workspace.meta.blockVersions) { const { default: template } = await promise;
await migratePages(workspace.doc, workspace.schema); let json = JSON.stringify(template);
} Object.entries(idMap).forEach(([oldId, newId]) => {
}) json = json.replaceAll(oldId, newId);
); });
json = JSON.parse(json);
await workspace
.importPageSnapshot(structuredClone(json), newId)
.catch(error => {
console.error('error importing page', id, error);
});
const page = workspace.getPage(newId);
assertExists(page);
await page.load();
workspace.schema.upgradePage(
0,
{
'affine:note': 1,
'affine:bookmark': 1,
'affine:database': 2,
'affine:divider': 1,
'affine:image': 1,
'affine:list': 1,
'affine:code': 1,
'affine:page': 2,
'affine:paragraph': 1,
'affine:surface': 3,
},
page.spaceDoc
);
}
// The showcase building will create multiple pages once, and may skip the version writing.
// https://github.com/toeverything/blocksuite/blob/master/packages/store/src/workspace/page.ts#L662
const compatibilityResult = checkWorkspaceCompatibility(workspace);
if (compatibilityResult === MigrationPoint.BlockVersion) {
await migratePages(workspace.doc, workspace.schema);
}
Object.entries(pageMetas).forEach(([oldId, meta]) => { Object.entries(pageMetas).forEach(([oldId, meta]) => {
const newId = idMap[oldId]; const newId = idMap[oldId];
workspace.setPageMeta(newId, meta); workspace.setPageMeta(newId, meta);

View File

@@ -20,13 +20,18 @@ export async function migratePages(
const meta = rootDoc.getMap('meta') as YMap<unknown>; const meta = rootDoc.getMap('meta') as YMap<unknown>;
const versions = meta.get('blockVersions') as YMap<number>; const versions = meta.get('blockVersions') as YMap<number>;
const oldVersions = versions?.toJSON() ?? {}; const oldVersions = versions?.toJSON() ?? {};
spaces.forEach((space: YDoc) => { spaces.forEach((space: YDoc) => {
try { schema.upgradePage(0, oldVersions, space);
schema.upgradePage(0, oldVersions, space);
} catch (e) {
console.error(`page ${space.guid} upgrade failed`, e);
}
}); });
schema.upgradeWorkspace(rootDoc);
// Hard code to upgrade page version to 2.
// Let e2e to ensure the data version is correct.
const pageVersion = meta.get('pageVersion');
if (typeof pageVersion !== 'number' || pageVersion < 2) {
meta.set('pageVersion', 2);
}
const newVersions = getLatestVersions(schema); const newVersions = getLatestVersions(schema);
meta.set('blockVersions', new YMap(Object.entries(newVersions))); meta.set('blockVersions', new YMap(Object.entries(newVersions)));

View File

@@ -43,3 +43,25 @@ export function guidCompatibilityFix(rootDoc: YDoc) {
}); });
return changed; return changed;
} }
/**
* Hard code to fix workspace version to be compatible with legacy data.
* Let e2e to ensure the data version is correct.
*/
export function fixWorkspaceVersion(rootDoc: YDoc) {
const meta = rootDoc.getMap('meta') as YMap<unknown>;
/**
* It doesn't matter to upgrade workspace version from 1 or undefined to 2.
* Blocksuite just set the value, do nothing else.
*/
const workspaceVersion = meta.get('workspaceVersion');
if (typeof workspaceVersion !== 'number' || workspaceVersion < 2) {
meta.set('workspaceVersion', 2);
const pageVersion = meta.get('pageVersion');
if (typeof pageVersion !== 'number') {
meta.set('pageVersion', 1);
}
}
}

View File

@@ -58,19 +58,16 @@ export function checkWorkspaceCompatibility(
return MigrationPoint.SubDoc; return MigrationPoint.SubDoc;
} }
// Sometimes, blocksuite will not write blockVersions to meta. const hasVersion = workspace.meta.hasVersion;
// Just fix it when user open the workspace. if (!hasVersion) {
const blockVersions = workspace.meta.blockVersions;
if (!blockVersions) {
return MigrationPoint.BlockVersion; return MigrationPoint.BlockVersion;
} }
// From v2, we depend on blocksuite to check and migrate data. try {
for (const [flavour, version] of Object.entries(blockVersions)) { workspace.meta.validateVersion(workspace);
const schema = workspace.schema.flavourSchemaMap.get(flavour); } catch (e) {
if (schema?.version !== version) { console.info('validateVersion error', e);
return MigrationPoint.BlockVersion; return MigrationPoint.BlockVersion;
}
} }
return null; return null;

View File

@@ -85,12 +85,14 @@ export class NoPageRootError extends Error {
const spaceVectors = Array.from(page.doc.spaces.entries()).map( const spaceVectors = Array.from(page.doc.spaces.entries()).map(
([pageId, doc]) => `${pageId} > ${doc.guid}` ([pageId, doc]) => `${pageId} > ${doc.guid}`
); );
const blocks = page.doc.getMap('blocks');
console.info( console.info(
'NoPageRootError current data: %s', 'NoPageRootError current data: %s',
JSON.stringify({ JSON.stringify({
expectPageId: page.id, expectPageId: page.id,
expectGuid: page.spaceDoc.guid, expectGuid: page.spaceDoc.guid,
spaceVectors, spaceVectors,
blockSize: blocks.size,
}) })
); );
} }

View File

@@ -75,6 +75,7 @@ export async function setup(store: ReturnType<typeof createStore>) {
), ),
}), }),
new Sentry.Replay(), new Sentry.Replay(),
new Sentry.BrowserTracing(),
], ],
}); });
Sentry.setTags({ Sentry.setTags({

View File

@@ -19,6 +19,7 @@ export const upgradeTips = style({
fontStyle: 'normal', fontStyle: 'normal',
fontWeight: '400', fontWeight: '400',
lineHeight: '20px', lineHeight: '20px',
textAlign: 'center',
}); });
const rotate = keyframes({ const rotate = keyframes({

View File

@@ -44,7 +44,7 @@ interface WorkspaceUpgradeProps {
export const WorkspaceUpgrade = function WorkspaceUpgrade( export const WorkspaceUpgrade = function WorkspaceUpgrade(
props: WorkspaceUpgradeProps props: WorkspaceUpgradeProps
) { ) {
const [upgradeState, , upgradeWorkspace, newWorkspaceId] = const [upgradeState, error, upgradeWorkspace, newWorkspaceId] =
useUpgradeWorkspace(props.migration); useUpgradeWorkspace(props.migration);
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
@@ -75,7 +75,7 @@ export const WorkspaceUpgrade = function WorkspaceUpgrade(
<div className={styles.upgradeBox}> <div className={styles.upgradeBox}>
<AffineShapeIcon width={180} height={180} /> <AffineShapeIcon width={180} height={180} />
<p className={styles.upgradeTips}> <p className={styles.upgradeTips}>
{t[UPGRADE_TIPS_KEYS[upgradeState]]()} {error ? error.message : t[UPGRADE_TIPS_KEYS[upgradeState]]()}
</p> </p>
<Button <Button
data-testid="upgrade-workspace-button" data-testid="upgrade-workspace-button"

View File

@@ -164,7 +164,8 @@ export const DetailPage = (): ReactElement => {
}); });
} }
return <DetailPageImpl />; // Add a key to force rerender when page changed, to avoid some lifecycle issues.
return <DetailPageImpl key={currentPageId} />;
}; };
export const loader: LoaderFunction = async () => { export const loader: LoaderFunction = async () => {

View File

@@ -8,6 +8,7 @@ import {
import type { MigrationPoint } from '@toeverything/infra/blocksuite'; import type { MigrationPoint } from '@toeverything/infra/blocksuite';
import { import {
checkWorkspaceCompatibility, checkWorkspaceCompatibility,
fixWorkspaceVersion,
guidCompatibilityFix, guidCompatibilityFix,
} from '@toeverything/infra/blocksuite'; } from '@toeverything/infra/blocksuite';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
@@ -54,6 +55,7 @@ export const loader: LoaderFunction = async args => {
workspaceLoaderLogger.info('workspace loaded'); workspaceLoaderLogger.info('workspace loaded');
guidCompatibilityFix(workspace.doc); guidCompatibilityFix(workspace.doc);
fixWorkspaceVersion(workspace.doc);
return checkWorkspaceCompatibility(workspace); return checkWorkspaceCompatibility(workspace);
}; };