Skip to content

Commit

Permalink
Fix type errors in devtools package (#938)
Browse files Browse the repository at this point in the history
* Add EventsForDocReplay to handle document replay events

* Resolve other type errors in devtools package

* Update devtools-publish workflow trigger condition

* Rename ReplayDocEventType to DocEventScope

---------

Co-authored-by: Youngteac Hong <[email protected]>
  • Loading branch information
chacha912 and hackerwins authored Jan 14, 2025
1 parent a217445 commit 0754863
Show file tree
Hide file tree
Showing 11 changed files with 152 additions and 109 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/devtools-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
branches:
- 'main'
paths:
- packages/devtools/**
- packages/devtools/package.json
jobs:
build-and-deploy:
runs-on: ubuntu-latest
Expand Down
10 changes: 7 additions & 3 deletions packages/devtools/src/devtools/components/Detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,13 @@ function TreeGraph({ tree }: { tree: Devtools.TreeNodeInfo }) {
[],
);

return flattenTreeWithDepth(tree).map((node) => (
<TreeNode key={node.id} node={node} />
));
return (
<>
{flattenTreeWithDepth(tree).map((node) => (
<TreeNode key={node.id} node={node} />
))}
</>
);
}

export function TreeDetail({
Expand Down
4 changes: 2 additions & 2 deletions packages/devtools/src/devtools/components/Tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import useResizeObserver from 'use-resize-observer';

import { useSelectedNode } from '../contexts/SelectedNode';
import { useSelectedPresence } from '../contexts/SelectedPresence';
import type { Devtools } from 'yorkie-js-sdk';
import type { Devtools, Json } from 'yorkie-js-sdk';

import {
ArrayIcon,
Expand All @@ -47,7 +47,7 @@ export type UserNode = Devtools.Client & {
export type PresenceJsonNode = {
id: string;
key: string;
value: Devtools.Json;
value: Json;
isLastChild: boolean;
type: 'JSON';
};
Expand Down
76 changes: 37 additions & 39 deletions packages/devtools/src/devtools/contexts/YorkieSource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,14 @@ import {
useState,
} from 'react';

import {
DocEventType,
type SDKToPanelMessage,
type TransactionEvent,
} from 'yorkie-js-sdk';
import { DocEventType, Devtools, type SDKToPanelMessage } from 'yorkie-js-sdk';
import { connectPort, sendToSDK } from '../../port';
import { Code, YorkieError } from '@yorkie-js-sdk/src/util/error';

const DocKeyContext = createContext<string>(null);
const YorkieDocContext = createContext(null);
const TransactionEventsContext = createContext<{
events: Array<TransactionEvent>;
const DocEventsForReplayContext = createContext<{
events: Array<Devtools.DocEventsForReplay>;
hidePresenceEvents: boolean;
setHidePresenceEvents: Dispatch<SetStateAction<boolean>>;
}>(null);
Expand All @@ -47,17 +43,16 @@ type Props = {
export function YorkieSourceProvider({ children }: Props) {
const [currentDocKey, setCurrentDocKey] = useState<string>('');
const [doc, setDoc] = useState(null);
const [transactionEvents, setTransactionEvents] = useState<
Array<TransactionEvent>
const [docEventsForReplay, setDocEventsForReplay] = useState<
Array<Devtools.DocEventsForReplay>
>([]);

// filter out presence events
const [hideTransactionPresenceEvents, setHideTransactionPresenceEvents] =
useState(false);
const [hidePresenceEvents, setHidePresenceEvents] = useState(false);

const resetDocument = () => {
setCurrentDocKey('');
setTransactionEvents([]);
setDocEventsForReplay([]);
setDoc(null);
};

Expand All @@ -77,11 +72,11 @@ export function YorkieSourceProvider({ children }: Props) {
case 'doc::sync::full':
// TODO(chacha912): Notify the user that they need to use the latest version of Yorkie-JS-SDK.
if (message.events === undefined) break;
setTransactionEvents(message.events);
setDocEventsForReplay(message.events);
break;
case 'doc::sync::partial':
if (message.event === undefined) break;
setTransactionEvents((events) => [...events, message.event]);
setDocEventsForReplay((events) => [...events, message.event]);
break;
}
}, []);
Expand All @@ -108,17 +103,17 @@ export function YorkieSourceProvider({ children }: Props) {

return (
<DocKeyContext.Provider value={currentDocKey}>
<TransactionEventsContext.Provider
<DocEventsForReplayContext.Provider
value={{
events: transactionEvents,
hidePresenceEvents: hideTransactionPresenceEvents,
setHidePresenceEvents: setHideTransactionPresenceEvents,
events: docEventsForReplay,
hidePresenceEvents,
setHidePresenceEvents,
}}
>
<YorkieDocContext.Provider value={[doc, setDoc]}>
{children}
</YorkieDocContext.Provider>
</TransactionEventsContext.Provider>
</DocEventsForReplayContext.Provider>
</DocKeyContext.Provider>
);
}
Expand All @@ -145,51 +140,54 @@ export function useYorkieDoc() {
return value;
}

export enum TransactionEventType {
Document = 'document',
/**
* `DocEventScope` represents the scope of the document event.
*/
export enum DocEventScope {
Root = 'root',
Presence = 'presence',
Document = 'document',
}

export const getTransactionEventType = (
event: TransactionEvent,
): TransactionEventType => {
for (const docEvent of event) {
export const getDocEventsScope = (
events: Devtools.DocEventsForReplay,
): DocEventScope => {
for (const e of events) {
if (
docEvent.type === DocEventType.StatusChanged ||
docEvent.type === DocEventType.Snapshot ||
docEvent.type === DocEventType.LocalChange ||
docEvent.type === DocEventType.RemoteChange
e.type === DocEventType.Snapshot ||
e.type === DocEventType.LocalChange ||
e.type === DocEventType.RemoteChange
) {
return TransactionEventType.Document;
return DocEventScope.Root;
} else if (e.type === DocEventType.StatusChanged) {
return DocEventScope.Document;
}
}

return TransactionEventType.Presence;
return DocEventScope.Presence;
};

export function useTransactionEvents() {
export function useDocEventsForReplay() {
const { events, hidePresenceEvents, setHidePresenceEvents } = useContext(
TransactionEventsContext,
DocEventsForReplayContext,
);

if (events === undefined) {
throw new YorkieError(
Code.ErrContextNotProvided,
'useTransactionEvents should be used within YorkieSourceProvider',
'useDocEventsForReplay should be used within YorkieSourceProvider',
);
}

// create an enhanced events with metadata
const enhancedEvents = useMemo(() => {
return events.map((event) => {
const transactionEventType = getTransactionEventType(event);
const scope = getDocEventsScope(event);

return {
event,
transactionEventType,
isFiltered:
hidePresenceEvents &&
transactionEventType === TransactionEventType.Presence,
scope,
isFiltered: hidePresenceEvents && scope === DocEventScope.Presence,
};
});
}, [hidePresenceEvents, events]);
Expand Down
6 changes: 3 additions & 3 deletions packages/devtools/src/devtools/panel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { SelectedPresenceProvider } from '../contexts/SelectedPresence';
import {
YorkieSourceProvider,
useCurrentDocKey,
useTransactionEvents,
useDocEventsForReplay,
useYorkieDoc,
} from '../contexts/YorkieSource';
import { Document } from '../tabs/Document';
Expand All @@ -34,7 +34,7 @@ import { Separator } from '../components/ResizableSeparator';
const Panel = () => {
const currentDocKey = useCurrentDocKey();
const { originalEvents, presenceFilteredEvents, hidePresenceEvents } =
useTransactionEvents();
useDocEventsForReplay();
const [, setDoc] = useYorkieDoc();
const [selectedEventIndexInfo, setSelectedEventIndexInfo] = useState({
index: null,
Expand Down Expand Up @@ -91,7 +91,7 @@ const Panel = () => {
filteredEventIndex++;
}

doc.applyTransactionEvent(originalEvents[eventIndex].event);
doc.applyDocEventsForReplay(originalEvents[eventIndex].event);
eventIndex++;
}

Expand Down
18 changes: 6 additions & 12 deletions packages/devtools/src/devtools/tabs/History.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,15 @@
*/

import { useEffect, useState, useRef } from 'react';
import { DocEventType, Change, type TransactionEvent } from 'yorkie-js-sdk';
import { DocEventType, Change, Devtools } from 'yorkie-js-sdk';
import Slider from 'rc-slider';
import { JSONView } from '../components/JsonView';
import { CursorIcon, DocumentIcon } from '../icons';
import {
TransactionEventType,
useTransactionEvents,
} from '../contexts/YorkieSource';
import { DocEventScope, useDocEventsForReplay } from '../contexts/YorkieSource';

const SLIDER_MARK_WIDTH = 24;

const getEventInfo = (event: TransactionEvent) => {
const getEventInfo = (event: Devtools.DocEventsForReplay) => {
const info = [];
for (const docEvent of event) {
if (
Expand Down Expand Up @@ -75,7 +72,7 @@ export function History({
presenceFilteredEvents,
hidePresenceEvents,
setHidePresenceEvents,
} = useTransactionEvents();
} = useDocEventsForReplay();

const events = hidePresenceEvents ? presenceFilteredEvents : originalEvents;

Expand Down Expand Up @@ -109,13 +106,10 @@ export function History({
const marks = {};
for (const [index, event] of events.entries()) {
const source = event.event[0].source;
const transactionEventType = event.transactionEventType;

marks[index] = (
<span
className={`mark-history mark-${source} mark-${transactionEventType}`}
>
{transactionEventType === TransactionEventType.Presence ? (
<span className={`mark-history mark-${source} mark-${event.scope}`}>
{event.scope === DocEventScope.Presence ? (
<CursorIcon />
) : (
<DocumentIcon />
Expand Down
36 changes: 10 additions & 26 deletions packages/sdk/src/devtools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,29 @@
* limitations under the License.
*/

import {
Document,
Indexable,
TransactionEvent,
} from '@yorkie-js-sdk/src/yorkie';
import { DocEventType } from '@yorkie-js-sdk/src/document/document';
import { Document, Indexable } from '@yorkie-js-sdk/src/yorkie';
import { logger } from '@yorkie-js-sdk/src/util/logger';
import type * as DevTools from './protocol';
import { EventSourceDevPanel, EventSourceSDK } from './protocol';
import { DocEventsForReplay, isDocEventsForReplay } from './types';

type DevtoolsStatus = 'connected' | 'disconnected' | 'synced';
let devtoolsStatus: DevtoolsStatus = 'disconnected';
const unsubsByDocKey = new Map<string, Array<() => void>>();

/**
* `transactionEventsByDocKey` stores all events in the document for replaying
* `docEventsForReplayByDocKey` stores all events in the document for replaying
* (time-traveling feature) in Devtools. Later, external storage such as
* IndexedDB will be used.
*/
const transactionEventsByDocKey = new Map<string, Array<TransactionEvent>>();
const docEventsForReplayByDocKey = new Map<string, Array<DocEventsForReplay>>();
declare global {
interface Window {
transactionEventsByDocKey: Map<string, Array<TransactionEvent>>;
docEventsForReplayByDocKey: Map<string, Array<DocEventsForReplay>>;
}
}
if (typeof window !== 'undefined') {
window.transactionEventsByDocKey = transactionEventsByDocKey;
window.docEventsForReplayByDocKey = docEventsForReplayByDocKey;
}

/**
Expand Down Expand Up @@ -79,25 +75,13 @@ export function setupDevtools<T, P extends Indexable>(
return;
}

transactionEventsByDocKey.set(doc.getKey(), []);
docEventsForReplayByDocKey.set(doc.getKey(), []);
const unsub = doc.subscribe('all', (event) => {
if (
event.some(
(docEvent) =>
docEvent.type !== DocEventType.StatusChanged &&
docEvent.type !== DocEventType.Snapshot &&
docEvent.type !== DocEventType.LocalChange &&
docEvent.type !== DocEventType.RemoteChange &&
docEvent.type !== DocEventType.Initialized &&
docEvent.type !== DocEventType.Watched &&
docEvent.type !== DocEventType.Unwatched &&
docEvent.type !== DocEventType.PresenceChanged,
)
) {
if (!isDocEventsForReplay(event)) {
return;
}

transactionEventsByDocKey.get(doc.getKey())!.push(event);
docEventsForReplayByDocKey.get(doc.getKey())!.push(event);
if (devtoolsStatus === 'synced') {
sendToPanel({
msg: 'doc::sync::partial',
Expand Down Expand Up @@ -148,7 +132,7 @@ export function setupDevtools<T, P extends Indexable>(
sendToPanel({
msg: 'doc::sync::full',
docKey: doc.getKey(),
events: transactionEventsByDocKey.get(doc.getKey())!,
events: docEventsForReplayByDocKey.get(doc.getKey())!,
});
logger.info(`[YD] Devtools subscribed. Doc: ${doc.getKey()}`);
break;
Expand Down
6 changes: 3 additions & 3 deletions packages/sdk/src/devtools/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import type { TransactionEvent } from '@yorkie-js-sdk/src/document/document';
import { DocEventsForReplay } from './types';

/**
* `EventSourceDevPanel` is the name of the source representing messages
Expand Down Expand Up @@ -76,15 +76,15 @@ export type SDKToPanelMessage =
| {
msg: 'doc::sync::full';
docKey: string;
events: Array<TransactionEvent>;
events: Array<DocEventsForReplay>;
}
/**
* Sent whenever the document is changed.
*/
| {
msg: 'doc::sync::partial';
docKey: string;
event: TransactionEvent;
event: DocEventsForReplay;
};

export type FullPanelToSDKMessage = PanelToSDKMessage & {
Expand Down
Loading

0 comments on commit 0754863

Please sign in to comment.