mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 18:26:05 +08:00
fix(infra): use blocksuite api to check compatibility (#5137)
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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)));
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user