From df3da17143a782a14379e37fd4fed4ab87b77c6a Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Thu, 16 Jan 2025 00:31:12 +0000 Subject: [PATCH 01/56] include allowedTypes for root field --- .../data-visualization/DefaultVisualizers.ts | 32 +- .../SharedTreeVisualizer.ts | 2 +- .../src/test/DefaultVisualizers.spec.ts | 374 ++++++++++++++++++ .../devtools-test-app/src/FluidObject.ts | 49 ++- 4 files changed, 430 insertions(+), 27 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index 77400415cf12..e3b2e9de13b5 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -22,16 +22,21 @@ import { SharedMatrix } from "@fluidframework/matrix/internal"; import { SharedString } from "@fluidframework/sequence/internal"; import type { ISharedObject } from "@fluidframework/shared-object-base/internal"; import type { ITreeInternal } from "@fluidframework/tree/internal"; -import { SharedTree } from "@fluidframework/tree/internal"; +import { SchemaFactory, SharedTree, Tree } from "@fluidframework/tree/internal"; import { EditType } from "../CommonInterfaces.js"; import type { VisualizeChildData, VisualizeSharedObject } from "./DataVisualization.js"; import { + concatenateTypes, determineNodeKind, toVisualTree, visualizeSharedTreeNodeBySchema, } from "./SharedTreeVisualizer.js"; +import { + VisualSharedTreeNodeKind, + type VisualSharedTreeNode, +} from "./VisualSharedTreeTypes.js"; import { type FluidObjectNode, type FluidObjectTreeNode, @@ -260,8 +265,6 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( // Root node of the SharedTree's content. const treeView = sharedTree.exportVerbose(); - // TODO: this visualizer doesn't consider the root as a field, and thus does not display the allowed types or handle when it is empty. - // Tracked by https://dev.azure.com/fluidframework/internal/_workitems/edit/26472. if (treeView === undefined) { throw new Error("Support for visualizing empty trees is not implemented"); } @@ -269,17 +272,26 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( // Schema of the tree node. const treeSchema = sharedTree.exportSimpleSchema(); - // Traverses the SharedTree and generates a visual representation of the tree and its schema. - const visualTreeRepresentation = await visualizeSharedTreeNodeBySchema( - treeView, - treeSchema, - visualizeChildData, - ); + const sf = new SchemaFactory(undefined); + const schemaName = Tree.is(treeView, [sf.boolean, sf.null, sf.number, sf.handle, sf.string]) + ? Tree.schema(treeView).identifier + : treeView.type; + + // Create a root field visualization that shows the allowed types at the root + const visualTreeRepresentation: VisualSharedTreeNode = { + schema: { + schemaName, + allowedTypes: concatenateTypes(treeSchema.allowedTypes), + }, + fields: { + root: await visualizeSharedTreeNodeBySchema(treeView, treeSchema, visualizeChildData), + }, + kind: VisualSharedTreeNodeKind.InternalNode, + }; // Maps the `visualTreeRepresentation` in the format compatible to {@link visualizeChildData} function. const visualTree = toVisualTree(visualTreeRepresentation); - // TODO: Validate the type casting. const visualTreeResult: FluidObjectNode = { ...visualTree, fluidObjectId: sharedTree.id, diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index ba0723df9d7b..45961ffe9443 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -150,7 +150,7 @@ export function toVisualTree(tree: VisualSharedTreeNode): VisualChildNode { /** * Concatenrate allowed types for `ObjectNodeStoredSchema` and `MapNodeStoredSchema`. */ -function concatenateTypes(fieldTypes: ReadonlySet): string { +export function concatenateTypes(fieldTypes: ReadonlySet): string { return [...fieldTypes].join(" | "); } diff --git a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts index bd922daf590e..0e9575438591 100644 --- a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts +++ b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts @@ -1730,6 +1730,380 @@ describe("DefaultVisualizers unit tests", () => { expect(result).to.deep.equal(expected); }); + it.only("SharedTree: Renders multiple allowed types in SharedTree's root field", async () => { + const factory = SharedTree.getFactory(); + const builder = new SchemaFactory("shared-tree-test"); + + const sharedTree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "test", + ); + + const view = sharedTree.viewWith( + new TreeViewConfiguration({ schema: [builder.string, builder.number] }), + ); + view.initialize(23); + + const result = await visualizeSharedTree( + sharedTree as unknown as ISharedObject, + visualizeChildData, + ); + + const expected = { + children: { + root: { + value: 23, + nodeKind: "ValueNode", + tooltipContents: { + schema: { + nodeKind: "TreeNode", + children: { + name: { + nodeKind: "ValueNode", + value: "com.fluidframework.leaf.number", + }, + }, + }, + }, + }, + }, + nodeKind: "FluidTreeNode", + tooltipContents: { + schema: { + nodeKind: "TreeNode", + children: { + name: { + nodeKind: "ValueNode", + value: "root", + }, + allowedTypes: { + value: "com.fluidframework.leaf.string | com.fluidframework.leaf.number", + nodeKind: "ValueNode", + }, + }, + }, + }, + fluidObjectId: "test", + typeMetadata: "SharedTree", + }; + + expect(result).to.deep.equal(expected); + }); + + it.only("SharedTree: Renders multiple allowed types in SharedTree's root field", async () => { + const factory = SharedTree.getFactory(); + const builder = new SchemaFactory("shared-tree-test"); + + const sharedTree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "test", + ); + + class LeafSchema extends builder.object("leaf-item", { + leafField: [builder.boolean, builder.handle, builder.string], + }) {} + + class ChildSchema extends builder.object("child-item", { + childField: [builder.string, builder.boolean], + childData: builder.optional(LeafSchema), + }) {} + + class RootNodeTwoItemTwo extends builder.object("root-node-two-item-two", { + childrenOne: builder.array(ChildSchema), + childrenTwo: builder.number, + }) {} + + class RootNodeTwoItem extends builder.object("root-node-item", { + childrenOne: builder.number, + childrenTwo: RootNodeTwoItemTwo, + }) {} + + class RootNodeOne extends builder.object("root-node-one", { + leafField: [builder.boolean, builder.handle, builder.string], + }) {} + + class RootNodeTwo extends builder.object("root-node-two", { + childField: RootNodeTwoItem, + }) {} + + const view = sharedTree.viewWith( + new TreeViewConfiguration({ + schema: [RootNodeOne, RootNodeTwo, builder.string, builder.number], + }), + ); + + view.initialize({ + childField: { + childrenOne: 42, + childrenTwo: { + childrenOne: [ + { + childField: false, + childData: { + leafField: "leaf data", + }, + }, + { + childField: true, + }, + ], + childrenTwo: 123, + }, + }, + }); + + const result = await visualizeSharedTree( + sharedTree as unknown as ISharedObject, + visualizeChildData, + ); + + const expected = { + children: { + root: { + children: { + childField: { + children: { + childrenOne: { + value: 42, + nodeKind: "ValueNode", + tooltipContents: { + schema: { + nodeKind: "TreeNode", + children: { + name: { + nodeKind: "ValueNode", + value: "com.fluidframework.leaf.number", + }, + }, + }, + }, + }, + childrenTwo: { + children: { + childrenOne: { + children: { + "0": { + children: { + childField: { + value: false, + nodeKind: "ValueNode", + tooltipContents: { + schema: { + nodeKind: "TreeNode", + children: { + name: { + nodeKind: "ValueNode", + value: "com.fluidframework.leaf.boolean", + }, + }, + }, + }, + }, + childData: { + children: { + leafField: { + value: "leaf data", + nodeKind: "ValueNode", + tooltipContents: { + schema: { + nodeKind: "TreeNode", + children: { + name: { + nodeKind: "ValueNode", + value: "com.fluidframework.leaf.string", + }, + }, + }, + }, + }, + }, + nodeKind: "TreeNode", + tooltipContents: { + schema: { + nodeKind: "TreeNode", + children: { + name: { + nodeKind: "ValueNode", + value: "shared-tree-test.leaf-item", + }, + allowedTypes: { + value: + "{ leafField : com.fluidframework.leaf.boolean | com.fluidframework.leaf.handle | com.fluidframework.leaf.string }", + nodeKind: "ValueNode", + }, + }, + }, + }, + }, + }, + nodeKind: "TreeNode", + tooltipContents: { + schema: { + nodeKind: "TreeNode", + children: { + name: { + nodeKind: "ValueNode", + value: "shared-tree-test.child-item", + }, + allowedTypes: { + value: + "{ childField : com.fluidframework.leaf.string | com.fluidframework.leaf.boolean, childData : shared-tree-test.leaf-item }", + nodeKind: "ValueNode", + }, + }, + }, + }, + }, + "1": { + children: { + childField: { + value: true, + nodeKind: "ValueNode", + tooltipContents: { + schema: { + nodeKind: "TreeNode", + children: { + name: { + nodeKind: "ValueNode", + value: "com.fluidframework.leaf.boolean", + }, + }, + }, + }, + }, + }, + nodeKind: "TreeNode", + tooltipContents: { + schema: { + nodeKind: "TreeNode", + children: { + name: { + nodeKind: "ValueNode", + value: "shared-tree-test.child-item", + }, + allowedTypes: { + value: + "{ childField : com.fluidframework.leaf.string | com.fluidframework.leaf.boolean, childData : shared-tree-test.leaf-item }", + nodeKind: "ValueNode", + }, + }, + }, + }, + }, + }, + nodeKind: "TreeNode", + tooltipContents: { + schema: { + nodeKind: "TreeNode", + children: { + name: { + nodeKind: "ValueNode", + value: 'shared-tree-test.Array<["shared-tree-test.child-item"]>', + }, + allowedTypes: { + value: "shared-tree-test.child-item", + nodeKind: "ValueNode", + }, + }, + }, + }, + }, + childrenTwo: { + value: 123, + nodeKind: "ValueNode", + tooltipContents: { + schema: { + nodeKind: "TreeNode", + children: { + name: { + nodeKind: "ValueNode", + value: "com.fluidframework.leaf.number", + }, + }, + }, + }, + }, + }, + nodeKind: "TreeNode", + tooltipContents: { + schema: { + nodeKind: "TreeNode", + children: { + name: { + nodeKind: "ValueNode", + value: "shared-tree-test.root-node-two-item-two", + }, + allowedTypes: { + value: + '{ childrenOne : shared-tree-test.Array<["shared-tree-test.child-item"]>, childrenTwo : com.fluidframework.leaf.number }', + nodeKind: "ValueNode", + }, + }, + }, + }, + }, + }, + nodeKind: "TreeNode", + tooltipContents: { + schema: { + nodeKind: "TreeNode", + children: { + name: { + nodeKind: "ValueNode", + value: "shared-tree-test.root-node-item", + }, + allowedTypes: { + value: + "{ childrenOne : com.fluidframework.leaf.number, childrenTwo : shared-tree-test.root-node-two-item-two }", + nodeKind: "ValueNode", + }, + }, + }, + }, + }, + }, + nodeKind: "TreeNode", + tooltipContents: { + schema: { + nodeKind: "TreeNode", + children: { + name: { + nodeKind: "ValueNode", + value: "shared-tree-test.root-node-two", + }, + allowedTypes: { + value: "{ childField : shared-tree-test.root-node-item }", + nodeKind: "ValueNode", + }, + }, + }, + }, + }, + }, + nodeKind: "FluidTreeNode", + tooltipContents: { + schema: { + nodeKind: "TreeNode", + children: { + name: { + nodeKind: "ValueNode", + value: "shared-tree-test.root-node-two", + }, + allowedTypes: { + value: + "shared-tree-test.root-node-one | shared-tree-test.root-node-two | com.fluidframework.leaf.string | com.fluidframework.leaf.number", + nodeKind: "ValueNode", + }, + }, + }, + }, + fluidObjectId: "test", + typeMetadata: "SharedTree", + }; + + expect(result).to.deep.equal(expected); + }); + it("Unknown SharedObject", async () => { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const unknownObject = { diff --git a/packages/tools/devtools/devtools-test-app/src/FluidObject.ts b/packages/tools/devtools/devtools-test-app/src/FluidObject.ts index d498309e0a12..fdc16fa42a9c 100644 --- a/packages/tools/devtools/devtools-test-app/src/FluidObject.ts +++ b/packages/tools/devtools/devtools-test-app/src/FluidObject.ts @@ -189,29 +189,46 @@ export class AppData extends DataObject { childData: builder.optional(LeafSchema), }) {} - class RootNodeSchema extends builder.object("root-item", { + class RootNodeTwoItemTwo extends builder.object("root-node-two-item-two", { childrenOne: builder.array(ChildSchema), childrenTwo: builder.number, }) {} - const config = new TreeViewConfiguration({ schema: RootNodeSchema }); + class RootNodeTwoItem extends builder.object("root-node-item", { + childrenOne: builder.number, + childrenTwo: RootNodeTwoItemTwo, + }) {} + + class RootNodeOne extends builder.object("root-node-one", { + leafField: [builder.boolean, builder.handle, builder.string], + }) {} + + class RootNodeTwo extends builder.object("root-node-two", { + childField: RootNodeTwoItem, + }) {} + + const config = new TreeViewConfiguration({ + schema: [RootNodeOne, RootNodeTwo, builder.string, builder.number], + }); const view = sharedTree.viewWith(config); view.initialize({ - childrenOne: [ - { - childField: "Hello world!", - childData: { - leafField: "Hello world again!", - }, + childField: { + childrenOne: 42, + childrenTwo: { + childrenOne: [ + { + childField: false, + childData: { + leafField: "leaf data", + }, + }, + { + childField: true, + }, + ], + childrenTwo: 123, }, - { - childField: true, - childData: { - leafField: false, - }, - }, - ], - childrenTwo: 32, + }, }); } } From fd50e40a68c784edc4746b42dbb629bf55d3f8f4 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Thu, 16 Jan 2025 01:08:33 +0000 Subject: [PATCH 02/56] add condition --- .../data-visualization/DefaultVisualizers.ts | 18 ++++++++++++------ .../data-visualization/SharedTreeVisualizer.ts | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index e3b2e9de13b5..595a9cd7aa89 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -32,6 +32,7 @@ import { determineNodeKind, toVisualTree, visualizeSharedTreeNodeBySchema, + visualizeVerboseNodeFields, } from "./SharedTreeVisualizer.js"; import { VisualSharedTreeNodeKind, @@ -273,9 +274,8 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( const treeSchema = sharedTree.exportSimpleSchema(); const sf = new SchemaFactory(undefined); - const schemaName = Tree.is(treeView, [sf.boolean, sf.null, sf.number, sf.handle, sf.string]) - ? Tree.schema(treeView).identifier - : treeView.type; + const isLeaf = Tree.is(treeView, [sf.boolean, sf.null, sf.number, sf.handle, sf.string]); + const schemaName = isLeaf ? Tree.schema(treeView).identifier : treeView.type; // Create a root field visualization that shows the allowed types at the root const visualTreeRepresentation: VisualSharedTreeNode = { @@ -283,9 +283,15 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( schemaName, allowedTypes: concatenateTypes(treeSchema.allowedTypes), }, - fields: { - root: await visualizeSharedTreeNodeBySchema(treeView, treeSchema, visualizeChildData), - }, + fields: isLeaf + ? { + root: await visualizeSharedTreeNodeBySchema( + treeView, + treeSchema, + visualizeChildData, + ), + } + : await visualizeVerboseNodeFields(treeView, treeSchema, visualizeChildData), kind: VisualSharedTreeNodeKind.InternalNode, }; diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 45961ffe9443..6c5f81ebca4e 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -171,7 +171,7 @@ function getObjectAllowedTypes(schema: SimpleObjectNodeSchema): string { /** * Returns the schema & fields of the node. */ -async function visualizeVerboseNodeFields( +export async function visualizeVerboseNodeFields( tree: VerboseTreeNode, treeSchema: SimpleTreeSchema, visualizeChildData: VisualizeChildData, From cf42f7a82f769a1c3852efcebae5e50fe572d07f Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Thu, 16 Jan 2025 01:09:57 +0000 Subject: [PATCH 03/56] Revert "add condition" This reverts commit fd50e40a68c784edc4746b42dbb629bf55d3f8f4. --- .../data-visualization/DefaultVisualizers.ts | 18 ++++++------------ .../data-visualization/SharedTreeVisualizer.ts | 2 +- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index 595a9cd7aa89..e3b2e9de13b5 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -32,7 +32,6 @@ import { determineNodeKind, toVisualTree, visualizeSharedTreeNodeBySchema, - visualizeVerboseNodeFields, } from "./SharedTreeVisualizer.js"; import { VisualSharedTreeNodeKind, @@ -274,8 +273,9 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( const treeSchema = sharedTree.exportSimpleSchema(); const sf = new SchemaFactory(undefined); - const isLeaf = Tree.is(treeView, [sf.boolean, sf.null, sf.number, sf.handle, sf.string]); - const schemaName = isLeaf ? Tree.schema(treeView).identifier : treeView.type; + const schemaName = Tree.is(treeView, [sf.boolean, sf.null, sf.number, sf.handle, sf.string]) + ? Tree.schema(treeView).identifier + : treeView.type; // Create a root field visualization that shows the allowed types at the root const visualTreeRepresentation: VisualSharedTreeNode = { @@ -283,15 +283,9 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( schemaName, allowedTypes: concatenateTypes(treeSchema.allowedTypes), }, - fields: isLeaf - ? { - root: await visualizeSharedTreeNodeBySchema( - treeView, - treeSchema, - visualizeChildData, - ), - } - : await visualizeVerboseNodeFields(treeView, treeSchema, visualizeChildData), + fields: { + root: await visualizeSharedTreeNodeBySchema(treeView, treeSchema, visualizeChildData), + }, kind: VisualSharedTreeNodeKind.InternalNode, }; diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 6c5f81ebca4e..45961ffe9443 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -171,7 +171,7 @@ function getObjectAllowedTypes(schema: SimpleObjectNodeSchema): string { /** * Returns the schema & fields of the node. */ -export async function visualizeVerboseNodeFields( +async function visualizeVerboseNodeFields( tree: VerboseTreeNode, treeSchema: SimpleTreeSchema, visualizeChildData: VisualizeChildData, From 9a6793aa663869a2f58d6adec27b20c0ea264f79 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Thu, 16 Jan 2025 19:11:20 +0000 Subject: [PATCH 04/56] change variable name & remove test --- .../data-visualization/DefaultVisualizers.ts | 10 +- .../src/test/DefaultVisualizers.spec.ts | 314 ------------------ 2 files changed, 8 insertions(+), 316 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index e3b2e9de13b5..5673f7cc9531 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -272,8 +272,14 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( // Schema of the tree node. const treeSchema = sharedTree.exportSimpleSchema(); - const sf = new SchemaFactory(undefined); - const schemaName = Tree.is(treeView, [sf.boolean, sf.null, sf.number, sf.handle, sf.string]) + const schemaFactory = new SchemaFactory(undefined); + const schemaName = Tree.is(treeView, [ + schemaFactory.boolean, + schemaFactory.null, + schemaFactory.number, + schemaFactory.handle, + schemaFactory.string, + ]) ? Tree.schema(treeView).identifier : treeView.type; diff --git a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts index 0e9575438591..d532f1a8ddeb 100644 --- a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts +++ b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts @@ -1790,320 +1790,6 @@ describe("DefaultVisualizers unit tests", () => { expect(result).to.deep.equal(expected); }); - it.only("SharedTree: Renders multiple allowed types in SharedTree's root field", async () => { - const factory = SharedTree.getFactory(); - const builder = new SchemaFactory("shared-tree-test"); - - const sharedTree = factory.create( - new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), - "test", - ); - - class LeafSchema extends builder.object("leaf-item", { - leafField: [builder.boolean, builder.handle, builder.string], - }) {} - - class ChildSchema extends builder.object("child-item", { - childField: [builder.string, builder.boolean], - childData: builder.optional(LeafSchema), - }) {} - - class RootNodeTwoItemTwo extends builder.object("root-node-two-item-two", { - childrenOne: builder.array(ChildSchema), - childrenTwo: builder.number, - }) {} - - class RootNodeTwoItem extends builder.object("root-node-item", { - childrenOne: builder.number, - childrenTwo: RootNodeTwoItemTwo, - }) {} - - class RootNodeOne extends builder.object("root-node-one", { - leafField: [builder.boolean, builder.handle, builder.string], - }) {} - - class RootNodeTwo extends builder.object("root-node-two", { - childField: RootNodeTwoItem, - }) {} - - const view = sharedTree.viewWith( - new TreeViewConfiguration({ - schema: [RootNodeOne, RootNodeTwo, builder.string, builder.number], - }), - ); - - view.initialize({ - childField: { - childrenOne: 42, - childrenTwo: { - childrenOne: [ - { - childField: false, - childData: { - leafField: "leaf data", - }, - }, - { - childField: true, - }, - ], - childrenTwo: 123, - }, - }, - }); - - const result = await visualizeSharedTree( - sharedTree as unknown as ISharedObject, - visualizeChildData, - ); - - const expected = { - children: { - root: { - children: { - childField: { - children: { - childrenOne: { - value: 42, - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.number", - }, - }, - }, - }, - }, - childrenTwo: { - children: { - childrenOne: { - children: { - "0": { - children: { - childField: { - value: false, - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.boolean", - }, - }, - }, - }, - }, - childData: { - children: { - leafField: { - value: "leaf data", - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.string", - }, - }, - }, - }, - }, - }, - nodeKind: "TreeNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "shared-tree-test.leaf-item", - }, - allowedTypes: { - value: - "{ leafField : com.fluidframework.leaf.boolean | com.fluidframework.leaf.handle | com.fluidframework.leaf.string }", - nodeKind: "ValueNode", - }, - }, - }, - }, - }, - }, - nodeKind: "TreeNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "shared-tree-test.child-item", - }, - allowedTypes: { - value: - "{ childField : com.fluidframework.leaf.string | com.fluidframework.leaf.boolean, childData : shared-tree-test.leaf-item }", - nodeKind: "ValueNode", - }, - }, - }, - }, - }, - "1": { - children: { - childField: { - value: true, - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.boolean", - }, - }, - }, - }, - }, - }, - nodeKind: "TreeNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "shared-tree-test.child-item", - }, - allowedTypes: { - value: - "{ childField : com.fluidframework.leaf.string | com.fluidframework.leaf.boolean, childData : shared-tree-test.leaf-item }", - nodeKind: "ValueNode", - }, - }, - }, - }, - }, - }, - nodeKind: "TreeNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: 'shared-tree-test.Array<["shared-tree-test.child-item"]>', - }, - allowedTypes: { - value: "shared-tree-test.child-item", - nodeKind: "ValueNode", - }, - }, - }, - }, - }, - childrenTwo: { - value: 123, - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.number", - }, - }, - }, - }, - }, - }, - nodeKind: "TreeNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "shared-tree-test.root-node-two-item-two", - }, - allowedTypes: { - value: - '{ childrenOne : shared-tree-test.Array<["shared-tree-test.child-item"]>, childrenTwo : com.fluidframework.leaf.number }', - nodeKind: "ValueNode", - }, - }, - }, - }, - }, - }, - nodeKind: "TreeNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "shared-tree-test.root-node-item", - }, - allowedTypes: { - value: - "{ childrenOne : com.fluidframework.leaf.number, childrenTwo : shared-tree-test.root-node-two-item-two }", - nodeKind: "ValueNode", - }, - }, - }, - }, - }, - }, - nodeKind: "TreeNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "shared-tree-test.root-node-two", - }, - allowedTypes: { - value: "{ childField : shared-tree-test.root-node-item }", - nodeKind: "ValueNode", - }, - }, - }, - }, - }, - }, - nodeKind: "FluidTreeNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "shared-tree-test.root-node-two", - }, - allowedTypes: { - value: - "shared-tree-test.root-node-one | shared-tree-test.root-node-two | com.fluidframework.leaf.string | com.fluidframework.leaf.number", - nodeKind: "ValueNode", - }, - }, - }, - }, - fluidObjectId: "test", - typeMetadata: "SharedTree", - }; - - expect(result).to.deep.equal(expected); - }); - it("Unknown SharedObject", async () => { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const unknownObject = { From d4c49019a9ae7101077bc9f364cbbc97e72506be Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Fri, 17 Jan 2025 19:38:51 +0000 Subject: [PATCH 05/56] Co-authored-by: Joshua Smithrud --- .../data-visualization/DefaultVisualizers.ts | 4 +- .../SharedTreeVisualizer.ts | 70 ++++++++++++------- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index 5673f7cc9531..7237836572cf 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -31,7 +31,7 @@ import { concatenateTypes, determineNodeKind, toVisualTree, - visualizeSharedTreeNodeBySchema, + visualizeSharedTreeBySchema, } from "./SharedTreeVisualizer.js"; import { VisualSharedTreeNodeKind, @@ -290,7 +290,7 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( allowedTypes: concatenateTypes(treeSchema.allowedTypes), }, fields: { - root: await visualizeSharedTreeNodeBySchema(treeView, treeSchema, visualizeChildData), + root: await visualizeSharedTreeBySchema(treeView, treeSchema, visualizeChildData), }, kind: VisualSharedTreeNodeKind.InternalNode, }; diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 45961ffe9443..95ceba7bcb65 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -172,16 +172,14 @@ function getObjectAllowedTypes(schema: SimpleObjectNodeSchema): string { * Returns the schema & fields of the node. */ async function visualizeVerboseNodeFields( - tree: VerboseTreeNode, + treeFields: VerboseTree[] | Record, treeSchema: SimpleTreeSchema, visualizeChildData: VisualizeChildData, ): Promise> { - const treeFields = tree.fields; - const fields: Record = {}; for (const [fieldKey, childField] of Object.entries(treeFields)) { - fields[fieldKey] = await visualizeSharedTreeNodeBySchema( + fields[fieldKey] = await visualizeSharedTreeBySchema( childField, treeSchema, visualizeChildData, @@ -205,7 +203,7 @@ async function visualizeObjectNode( schemaName: tree.type, allowedTypes: getObjectAllowedTypes(nodeSchema), }, - fields: await visualizeVerboseNodeFields(tree, treeSchema, visualizeChildData), + fields: await visualizeVerboseNodeFields(tree.fields, treeSchema, visualizeChildData), kind: VisualSharedTreeNodeKind.InternalNode, }; } @@ -224,36 +222,25 @@ async function visualizeMapNode( schemaName: tree.type, allowedTypes: `Record`, }, - fields: await visualizeVerboseNodeFields(tree, treeSchema, visualizeChildData), + fields: await visualizeVerboseNodeFields(tree.fields, treeSchema, visualizeChildData), kind: VisualSharedTreeNodeKind.InternalNode, }; } /** - * Main recursive helper function to create the visual representation of the SharedTree. - * Processes tree nodes based on their schema type (e.g., ObjectNodeStoredSchema, MapNodeStoredSchema, LeafNodeStoredSchema), producing the visual representation for each type. + * Helper function to create the visual representation of non-leaf SharedTree nodes. + * Processes internal tree nodes based on their schema type (e.g., ObjectNodeStoredSchema, MapNodeStoredSchema, ArrayNodeStoredSchema), + * producing the visual representation for each type. * * @see {@link https://fluidframework.com/docs/data-structures/tree/} for more information on the SharedTree schema. * * @remarks */ -export async function visualizeSharedTreeNodeBySchema( - tree: VerboseTree, +async function visualizeSharedTreeNodeBySchema( + tree: VerboseTreeNode, treeSchema: SimpleTreeSchema, visualizeChildData: VisualizeChildData, ): Promise { - const sf = new SchemaFactory(undefined); - if (Tree.is(tree, [sf.boolean, sf.null, sf.number, sf.handle, sf.string])) { - const nodeSchema = Tree.schema(tree); - return { - schema: { - schemaName: nodeSchema.identifier, - }, - value: await visualizeChildData(tree), - kind: VisualSharedTreeNodeKind.LeafNode, - }; - } - const schema = treeSchema.definitions.get(tree.type); if (schema === undefined) { throw new TypeError("Unrecognized schema type."); @@ -281,7 +268,7 @@ export async function visualizeSharedTreeNodeBySchema( } for (let i = 0; i < children.length; i++) { - fields[i] = await visualizeSharedTreeNodeBySchema( + fields[i] = await visualizeSharedTreeBySchema( children[i], treeSchema, visualizeChildData, @@ -293,7 +280,7 @@ export async function visualizeSharedTreeNodeBySchema( schemaName: tree.type, allowedTypes: concatenateTypes(schema.allowedTypes), }, - fields: await visualizeVerboseNodeFields(tree, treeSchema, visualizeChildData), + fields: await visualizeVerboseNodeFields(tree.fields, treeSchema, visualizeChildData), kind: VisualSharedTreeNodeKind.InternalNode, }; } @@ -302,3 +289,38 @@ export async function visualizeSharedTreeNodeBySchema( } } } + +/** + * Creates a visual representation of a SharedTree based on its schema. + * @param tree - The {@link VerboseTree} to visualize + * @param treeSchema - The schema that defines the structure and types of the tree + * @param visualizeChildData - Callback function to visualize child node data + * @returns A visual representation of the tree that includes schema information and node values + * + * @remarks + * This function handles both leaf nodes (primitive values, handles) and internal nodes (objects, maps, arrays). + * For leaf nodes, it creates a visual representation with the node's schema and value. + * For internal nodes, it recursively processes the node's fields using {@link visualizeSharedTreeNodeBySchema}. + */ +export async function visualizeSharedTreeBySchema( + tree: VerboseTree, + treeSchema: SimpleTreeSchema, + visualizeChildData: VisualizeChildData, +): Promise { + const schemaFactory = new SchemaFactory(undefined); + return Tree.is(tree, [ + schemaFactory.boolean, + schemaFactory.null, + schemaFactory.number, + schemaFactory.handle, + schemaFactory.string, + ]) + ? { + schema: { + schemaName: Tree.schema(tree).identifier, + }, + value: await visualizeChildData(tree), + kind: VisualSharedTreeNodeKind.LeafNode, + } + : visualizeSharedTreeNodeBySchema(tree, treeSchema, visualizeChildData); +} From 4990a601ce07572d8393e3cff921fe798902ee60 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Fri, 17 Jan 2025 19:41:42 +0000 Subject: [PATCH 06/56] change name --- .../src/data-visualization/SharedTreeVisualizer.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 95ceba7bcb65..4354a81cafac 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -91,7 +91,7 @@ function createToolTipContents(schema: SharedTreeSchemaNode): VisualTreeNode { } /** - * Converts the visual representation from {@link visualizeSharedTreeNodeBySchema} to a visual tree compatible with the devtools-view. + * Converts the visual representation from {@link visualizeInternalNodeBySchema} to a visual tree compatible with the devtools-view. * @param tree - the visual representation of the SharedTree. * @returns - the visual representation of type {@link VisualChildNode} */ @@ -236,7 +236,7 @@ async function visualizeMapNode( * * @remarks */ -async function visualizeSharedTreeNodeBySchema( +async function visualizeInternalNodeBySchema( tree: VerboseTreeNode, treeSchema: SimpleTreeSchema, visualizeChildData: VisualizeChildData, @@ -300,7 +300,7 @@ async function visualizeSharedTreeNodeBySchema( * @remarks * This function handles both leaf nodes (primitive values, handles) and internal nodes (objects, maps, arrays). * For leaf nodes, it creates a visual representation with the node's schema and value. - * For internal nodes, it recursively processes the node's fields using {@link visualizeSharedTreeNodeBySchema}. + * For internal nodes, it recursively processes the node's fields using {@link visualizeInternalNodeBySchema}. */ export async function visualizeSharedTreeBySchema( tree: VerboseTree, @@ -322,5 +322,5 @@ export async function visualizeSharedTreeBySchema( value: await visualizeChildData(tree), kind: VisualSharedTreeNodeKind.LeafNode, } - : visualizeSharedTreeNodeBySchema(tree, treeSchema, visualizeChildData); + : visualizeInternalNodeBySchema(tree, treeSchema, visualizeChildData); } From 363ced9e95552ab321e1048499e713e52eab159a Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Fri, 17 Jan 2025 20:00:56 +0000 Subject: [PATCH 07/56] handle undefined --- .../src/data-visualization/DefaultVisualizers.ts | 8 +++++++- .../tools/devtools/devtools-test-app/src/FluidObject.ts | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index 7237836572cf..d8a659023d5b 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -265,8 +265,14 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( // Root node of the SharedTree's content. const treeView = sharedTree.exportVerbose(); + if (treeView === undefined) { - throw new Error("Support for visualizing empty trees is not implemented"); + return { + fluidObjectId: sharedTree.id, + typeMetadata: "SharedTree", + nodeKind: VisualNodeKind.FluidTreeNode, + children: {}, + }; } // Schema of the tree node. diff --git a/packages/tools/devtools/devtools-test-app/src/FluidObject.ts b/packages/tools/devtools/devtools-test-app/src/FluidObject.ts index fdc16fa42a9c..6c41592b6a39 100644 --- a/packages/tools/devtools/devtools-test-app/src/FluidObject.ts +++ b/packages/tools/devtools/devtools-test-app/src/FluidObject.ts @@ -204,7 +204,7 @@ export class AppData extends DataObject { }) {} class RootNodeTwo extends builder.object("root-node-two", { - childField: RootNodeTwoItem, + childField: builder.optional(RootNodeTwoItem), }) {} const config = new TreeViewConfiguration({ From dbf8f9bf39eb41cd22d4748f0a6da0c05036a6cc Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Fri, 17 Jan 2025 23:19:05 +0000 Subject: [PATCH 08/56] refactor / undefined --- .../data-visualization/DefaultVisualizers.ts | 35 +++++-------------- .../SharedTreeVisualizer.ts | 12 ++++++- 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index d8a659023d5b..eeab5ec91adf 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -22,21 +22,17 @@ import { SharedMatrix } from "@fluidframework/matrix/internal"; import { SharedString } from "@fluidframework/sequence/internal"; import type { ISharedObject } from "@fluidframework/shared-object-base/internal"; import type { ITreeInternal } from "@fluidframework/tree/internal"; -import { SchemaFactory, SharedTree, Tree } from "@fluidframework/tree/internal"; +import { SharedTree } from "@fluidframework/tree/internal"; import { EditType } from "../CommonInterfaces.js"; import type { VisualizeChildData, VisualizeSharedObject } from "./DataVisualization.js"; import { - concatenateTypes, determineNodeKind, toVisualTree, visualizeSharedTreeBySchema, } from "./SharedTreeVisualizer.js"; -import { - VisualSharedTreeNodeKind, - type VisualSharedTreeNode, -} from "./VisualSharedTreeTypes.js"; +import type { VisualSharedTreeNode } from "./VisualSharedTreeTypes.js"; import { type FluidObjectNode, type FluidObjectTreeNode, @@ -278,28 +274,13 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( // Schema of the tree node. const treeSchema = sharedTree.exportSimpleSchema(); - const schemaFactory = new SchemaFactory(undefined); - const schemaName = Tree.is(treeView, [ - schemaFactory.boolean, - schemaFactory.null, - schemaFactory.number, - schemaFactory.handle, - schemaFactory.string, - ]) - ? Tree.schema(treeView).identifier - : treeView.type; - // Create a root field visualization that shows the allowed types at the root - const visualTreeRepresentation: VisualSharedTreeNode = { - schema: { - schemaName, - allowedTypes: concatenateTypes(treeSchema.allowedTypes), - }, - fields: { - root: await visualizeSharedTreeBySchema(treeView, treeSchema, visualizeChildData), - }, - kind: VisualSharedTreeNodeKind.InternalNode, - }; + const visualTreeRepresentation: VisualSharedTreeNode = await visualizeSharedTreeBySchema( + treeView, + treeSchema, + visualizeChildData, + true, + ); // Maps the `visualTreeRepresentation` in the format compatible to {@link visualizeChildData} function. const visualTree = toVisualTree(visualTreeRepresentation); diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 4354a81cafac..08fbd2df7526 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -198,10 +198,14 @@ async function visualizeObjectNode( treeSchema: SimpleTreeSchema, visualizeChildData: VisualizeChildData, ): Promise { + const isRootField = treeSchema.allowedTypes.has(tree.type); + return { schema: { schemaName: tree.type, - allowedTypes: getObjectAllowedTypes(nodeSchema), + allowedTypes: isRootField + ? concatenateTypes(treeSchema.allowedTypes) + : getObjectAllowedTypes(nodeSchema), }, fields: await visualizeVerboseNodeFields(tree.fields, treeSchema, visualizeChildData), kind: VisualSharedTreeNodeKind.InternalNode, @@ -306,8 +310,10 @@ export async function visualizeSharedTreeBySchema( tree: VerboseTree, treeSchema: SimpleTreeSchema, visualizeChildData: VisualizeChildData, + isRootField?: boolean, ): Promise { const schemaFactory = new SchemaFactory(undefined); + return Tree.is(tree, [ schemaFactory.boolean, schemaFactory.null, @@ -318,6 +324,10 @@ export async function visualizeSharedTreeBySchema( ? { schema: { schemaName: Tree.schema(tree).identifier, + allowedTypes: + isRootField === true + ? concatenateTypes(treeSchema.allowedTypes) + : Tree.schema(tree).identifier, }, value: await visualizeChildData(tree), kind: VisualSharedTreeNodeKind.LeafNode, From d80f2e28cfa7b1bfd2b8f2ce75c36193e88417ef Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Fri, 24 Jan 2025 23:58:35 +0000 Subject: [PATCH 09/56] add allowedtypes --- .../data-visualization/DefaultVisualizers.ts | 8 +- .../SharedTreeVisualizer.ts | 117 ++++++++++++------ .../src/test/DefaultVisualizers.spec.ts | 61 ++++++++- .../devtools-test-app/src/FluidObject.ts | 50 ++++---- 4 files changed, 169 insertions(+), 67 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index eeab5ec91adf..bbac1338fcc2 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -28,6 +28,7 @@ import { EditType } from "../CommonInterfaces.js"; import type { VisualizeChildData, VisualizeSharedObject } from "./DataVisualization.js"; import { + concatenateTypes, determineNodeKind, toVisualTree, visualizeSharedTreeBySchema, @@ -272,14 +273,15 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( } // Schema of the tree node. - const treeSchema = sharedTree.exportSimpleSchema(); + const treeDefinitions = sharedTree.exportSimpleSchema().definitions; + const allowedTypes = concatenateTypes(sharedTree.exportSimpleSchema().allowedTypes); // Create a root field visualization that shows the allowed types at the root const visualTreeRepresentation: VisualSharedTreeNode = await visualizeSharedTreeBySchema( treeView, - treeSchema, + treeDefinitions, + allowedTypes, visualizeChildData, - true, ); // Maps the `visualTreeRepresentation` in the format compatible to {@link visualizeChildData} function. diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 08fbd2df7526..f28f56c9481b 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -5,8 +5,8 @@ import type { SimpleMapNodeSchema, + SimpleNodeSchema, SimpleObjectNodeSchema, - SimpleTreeSchema, VerboseTree, VerboseTreeNode, } from "@fluidframework/tree/internal"; @@ -157,23 +157,24 @@ export function concatenateTypes(fieldTypes: ReadonlySet): string { /** * Returns the allowed fields & types for the object fields (e.g., `foo : string | number, bar: boolean`) */ -function getObjectAllowedTypes(schema: SimpleObjectNodeSchema): string { - const result: string[] = []; +// function getObjectAllowedTypes(schema: SimpleObjectNodeSchema): string { +// const result: string[] = []; - for (const [fieldKey, treeFieldSimpleSchema] of Object.entries(schema.fields)) { - const fieldTypes = treeFieldSimpleSchema.allowedTypes; - result.push(`${fieldKey} : ${concatenateTypes(fieldTypes)}`); - } +// for (const [fieldKey, treeFieldSimpleSchema] of Object.entries(schema.fields)) { +// const fieldTypes = treeFieldSimpleSchema.allowedTypes; +// result.push(`${fieldKey} : ${concatenateTypes(fieldTypes)}`); +// } - return `{ ${result.join(", ")} }`; -} +// return `{ ${result.join(", ")} }`; +// } /** * Returns the schema & fields of the node. */ async function visualizeVerboseNodeFields( treeFields: VerboseTree[] | Record, - treeSchema: SimpleTreeSchema, + treeDefinitions: ReadonlyMap, + allowedTypes: Record, visualizeChildData: VisualizeChildData, ): Promise> { const fields: Record = {}; @@ -181,7 +182,8 @@ async function visualizeVerboseNodeFields( for (const [fieldKey, childField] of Object.entries(treeFields)) { fields[fieldKey] = await visualizeSharedTreeBySchema( childField, - treeSchema, + treeDefinitions, + allowedTypes[fieldKey], visualizeChildData, ); } @@ -189,25 +191,43 @@ async function visualizeVerboseNodeFields( return fields; } +function getObjectAllowedTypes(schema: SimpleObjectNodeSchema): Record { + const result: Record = {}; + + for (const [fieldKey, treeFieldSimpleSchema] of Object.entries(schema.fields)) { + const fieldTypes = treeFieldSimpleSchema.allowedTypes; + result[fieldKey] = concatenateTypes(fieldTypes); + } + + return result; +} + /** * Returns the schema & fields of the node with type {@link ObjectNodeStoredSchema}. */ async function visualizeObjectNode( tree: VerboseTreeNode, - nodeSchema: SimpleObjectNodeSchema, - treeSchema: SimpleTreeSchema, + treeDefinitions: ReadonlyMap, + allowedTypes: string, visualizeChildData: VisualizeChildData, ): Promise { - const isRootField = treeSchema.allowedTypes.has(tree.type); + const objectAllowedTypes = getObjectAllowedTypes( + treeDefinitions.get(tree.type) as SimpleObjectNodeSchema, + ); + + console.log("objectAllowedTypes", objectAllowedTypes); return { schema: { schemaName: tree.type, - allowedTypes: isRootField - ? concatenateTypes(treeSchema.allowedTypes) - : getObjectAllowedTypes(nodeSchema), + allowedTypes, }, - fields: await visualizeVerboseNodeFields(tree.fields, treeSchema, visualizeChildData), + fields: await visualizeVerboseNodeFields( + tree.fields, + treeDefinitions, + objectAllowedTypes, + visualizeChildData, + ), kind: VisualSharedTreeNodeKind.InternalNode, }; } @@ -218,15 +238,26 @@ async function visualizeObjectNode( async function visualizeMapNode( tree: VerboseTreeNode, nodeSchema: SimpleMapNodeSchema, - treeSchema: SimpleTreeSchema, + treeDefinitions: ReadonlyMap, + allowedTypes: string, visualizeChildData: VisualizeChildData, ): Promise { + const mapAllowedTypes: Record = {}; + + for (const key of Object.keys(tree.fields)) { + mapAllowedTypes[key] = concatenateTypes(nodeSchema.allowedTypes); + } return { schema: { schemaName: tree.type, - allowedTypes: `Record`, + allowedTypes, }, - fields: await visualizeVerboseNodeFields(tree.fields, treeSchema, visualizeChildData), + fields: await visualizeVerboseNodeFields( + tree.fields, + treeDefinitions, + mapAllowedTypes, + visualizeChildData, + ), kind: VisualSharedTreeNodeKind.InternalNode, }; } @@ -242,10 +273,12 @@ async function visualizeMapNode( */ async function visualizeInternalNodeBySchema( tree: VerboseTreeNode, - treeSchema: SimpleTreeSchema, + treeDefinitions: ReadonlyMap, + allowedTypes: string, visualizeChildData: VisualizeChildData, ): Promise { - const schema = treeSchema.definitions.get(tree.type); + const schema = treeDefinitions.get(tree.type); + if (schema === undefined) { throw new TypeError("Unrecognized schema type."); } @@ -254,14 +287,20 @@ async function visualizeInternalNodeBySchema( case NodeKind.Object: { const objectVisualized = visualizeObjectNode( tree, - schema, - treeSchema, + treeDefinitions, + allowedTypes, visualizeChildData, ); return objectVisualized; } case NodeKind.Map: { - const mapVisualized = visualizeMapNode(tree, schema, treeSchema, visualizeChildData); + const mapVisualized = visualizeMapNode( + tree, + schema, + treeDefinitions, + allowedTypes, + visualizeChildData, + ); return mapVisualized; } case NodeKind.Array: { @@ -271,10 +310,14 @@ async function visualizeInternalNodeBySchema( throw new TypeError("Invalid array"); } + const arrayAllowedTypes: Record = {}; for (let i = 0; i < children.length; i++) { + arrayAllowedTypes[i] = concatenateTypes(schema.allowedTypes); + fields[i] = await visualizeSharedTreeBySchema( children[i], - treeSchema, + treeDefinitions, + arrayAllowedTypes[i], visualizeChildData, ); } @@ -282,9 +325,14 @@ async function visualizeInternalNodeBySchema( return { schema: { schemaName: tree.type, - allowedTypes: concatenateTypes(schema.allowedTypes), + allowedTypes, }, - fields: await visualizeVerboseNodeFields(tree.fields, treeSchema, visualizeChildData), + fields: await visualizeVerboseNodeFields( + tree.fields, + treeDefinitions, + arrayAllowedTypes, + visualizeChildData, + ), kind: VisualSharedTreeNodeKind.InternalNode, }; } @@ -308,9 +356,9 @@ async function visualizeInternalNodeBySchema( */ export async function visualizeSharedTreeBySchema( tree: VerboseTree, - treeSchema: SimpleTreeSchema, + treeDefinitions: ReadonlyMap, + allowedTypes: string, visualizeChildData: VisualizeChildData, - isRootField?: boolean, ): Promise { const schemaFactory = new SchemaFactory(undefined); @@ -324,13 +372,10 @@ export async function visualizeSharedTreeBySchema( ? { schema: { schemaName: Tree.schema(tree).identifier, - allowedTypes: - isRootField === true - ? concatenateTypes(treeSchema.allowedTypes) - : Tree.schema(tree).identifier, + allowedTypes, }, value: await visualizeChildData(tree), kind: VisualSharedTreeNodeKind.LeafNode, } - : visualizeInternalNodeBySchema(tree, treeSchema, visualizeChildData); + : visualizeInternalNodeBySchema(tree, treeDefinitions, allowedTypes, visualizeChildData); } diff --git a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts index d532f1a8ddeb..42a96bed6b1f 100644 --- a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts +++ b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts @@ -1730,7 +1730,7 @@ describe("DefaultVisualizers unit tests", () => { expect(result).to.deep.equal(expected); }); - it.only("SharedTree: Renders multiple allowed types in SharedTree's root field", async () => { + it("SharedTree: Renders multiple allowed types in SharedTree's root field", async () => { const factory = SharedTree.getFactory(); const builder = new SchemaFactory("shared-tree-test"); @@ -1790,6 +1790,65 @@ describe("DefaultVisualizers unit tests", () => { expect(result).to.deep.equal(expected); }); + it.only("Test", async () => { + const factory = SharedTree.getFactory(); + const builder = new SchemaFactory("shared-tree-test"); + + const sharedTree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "test", + ); + + class LeafSchema extends builder.object("item-two-second-first", { + twoFirstSecondFirst: [builder.boolean, builder.handle, builder.string], + }) {} + + class TwoFirst extends builder.object("root-node-item-two-first", { + twoFirstFirst: [builder.string, builder.boolean], + twoFirstSecond: builder.optional(LeafSchema), + }) {} + + class RootNodeItemTwo extends builder.object("root-node-item-two", { + twoFirst: [builder.array(TwoFirst), builder.boolean], + twoSecond: builder.number, + }) {} + + class RootNodeItem extends builder.object("root-node-item", { + one: [builder.number, builder.string], + two: RootNodeItemTwo, + }) {} + + class RootNode extends builder.object("root-node", { + rootNode: builder.optional([RootNodeItem, builder.string]), + }) {} + + const config = new TreeViewConfiguration({ + schema: [RootNode, builder.string, builder.number], + }); + const view = sharedTree.viewWith(config); + view.initialize({ + rootNode: { + one: 42, + two: { + twoFirst: [ + { + twoFirstFirst: false, + twoFirstSecond: { + twoFirstSecondFirst: "leaf value", + }, + }, + { + twoFirstFirst: true, + }, + ], + twoSecond: 123, + }, + }, + }); + + await visualizeSharedTree(sharedTree as unknown as ISharedObject, visualizeChildData); + }); + it("Unknown SharedObject", async () => { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const unknownObject = { diff --git a/packages/tools/devtools/devtools-test-app/src/FluidObject.ts b/packages/tools/devtools/devtools-test-app/src/FluidObject.ts index 6c41592b6a39..6b00e1f02946 100644 --- a/packages/tools/devtools/devtools-test-app/src/FluidObject.ts +++ b/packages/tools/devtools/devtools-test-app/src/FluidObject.ts @@ -180,53 +180,49 @@ export class AppData extends DataObject { // TODO: Maybe include example handle - class LeafSchema extends builder.object("leaf-item", { - leafField: [builder.boolean, builder.handle, builder.string], + class LeafSchema extends builder.object("item-two-second-first", { + twoFirstSecondFirst: [builder.boolean, builder.handle, builder.string], }) {} - class ChildSchema extends builder.object("child-item", { - childField: [builder.string, builder.boolean], - childData: builder.optional(LeafSchema), + class TwoFirst extends builder.object("root-node-item-two-first", { + twoFirstFirst: [builder.string, builder.boolean], + twoFirstSecond: builder.optional(LeafSchema), }) {} - class RootNodeTwoItemTwo extends builder.object("root-node-two-item-two", { - childrenOne: builder.array(ChildSchema), - childrenTwo: builder.number, + class RootNodeItemTwo extends builder.object("root-node-item-two", { + twoFirst: [builder.array(TwoFirst), builder.boolean], + twoSecond: builder.number, }) {} - class RootNodeTwoItem extends builder.object("root-node-item", { - childrenOne: builder.number, - childrenTwo: RootNodeTwoItemTwo, + class RootNodeItem extends builder.object("root-node-item", { + one: [builder.number, builder.string], + two: RootNodeItemTwo, }) {} - class RootNodeOne extends builder.object("root-node-one", { - leafField: [builder.boolean, builder.handle, builder.string], - }) {} - - class RootNodeTwo extends builder.object("root-node-two", { - childField: builder.optional(RootNodeTwoItem), + class RootNode extends builder.object("root-node", { + rootNode: builder.optional([RootNodeItem, builder.string]), }) {} const config = new TreeViewConfiguration({ - schema: [RootNodeOne, RootNodeTwo, builder.string, builder.number], + schema: [RootNode, builder.string, builder.number], }); const view = sharedTree.viewWith(config); view.initialize({ - childField: { - childrenOne: 42, - childrenTwo: { - childrenOne: [ + rootNode: { + one: 42, + two: { + twoFirst: [ { - childField: false, - childData: { - leafField: "leaf data", + twoFirstFirst: false, + twoFirstSecond: { + twoFirstSecondFirst: "leaf value", }, }, { - childField: true, + twoFirstFirst: true, }, ], - childrenTwo: 123, + twoSecond: 123, }, }, }); From 3159b8088e8e153e87a3b9eda44c310e24cc24ba Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Sat, 25 Jan 2025 00:24:44 +0000 Subject: [PATCH 10/56] fix schema name / remove console --- .../SharedTreeVisualizer.ts | 2 - .../src/test/DefaultVisualizers.spec.ts | 59 ------------------- .../devtools-test-app/src/FluidObject.ts | 54 +++++++++-------- 3 files changed, 31 insertions(+), 84 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index f28f56c9481b..b111998442e8 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -215,8 +215,6 @@ async function visualizeObjectNode( treeDefinitions.get(tree.type) as SimpleObjectNodeSchema, ); - console.log("objectAllowedTypes", objectAllowedTypes); - return { schema: { schemaName: tree.type, diff --git a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts index 42a96bed6b1f..0181a8f355d8 100644 --- a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts +++ b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts @@ -1790,65 +1790,6 @@ describe("DefaultVisualizers unit tests", () => { expect(result).to.deep.equal(expected); }); - it.only("Test", async () => { - const factory = SharedTree.getFactory(); - const builder = new SchemaFactory("shared-tree-test"); - - const sharedTree = factory.create( - new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), - "test", - ); - - class LeafSchema extends builder.object("item-two-second-first", { - twoFirstSecondFirst: [builder.boolean, builder.handle, builder.string], - }) {} - - class TwoFirst extends builder.object("root-node-item-two-first", { - twoFirstFirst: [builder.string, builder.boolean], - twoFirstSecond: builder.optional(LeafSchema), - }) {} - - class RootNodeItemTwo extends builder.object("root-node-item-two", { - twoFirst: [builder.array(TwoFirst), builder.boolean], - twoSecond: builder.number, - }) {} - - class RootNodeItem extends builder.object("root-node-item", { - one: [builder.number, builder.string], - two: RootNodeItemTwo, - }) {} - - class RootNode extends builder.object("root-node", { - rootNode: builder.optional([RootNodeItem, builder.string]), - }) {} - - const config = new TreeViewConfiguration({ - schema: [RootNode, builder.string, builder.number], - }); - const view = sharedTree.viewWith(config); - view.initialize({ - rootNode: { - one: 42, - two: { - twoFirst: [ - { - twoFirstFirst: false, - twoFirstSecond: { - twoFirstSecondFirst: "leaf value", - }, - }, - { - twoFirstFirst: true, - }, - ], - twoSecond: 123, - }, - }, - }); - - await visualizeSharedTree(sharedTree as unknown as ISharedObject, visualizeChildData); - }); - it("Unknown SharedObject", async () => { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const unknownObject = { diff --git a/packages/tools/devtools/devtools-test-app/src/FluidObject.ts b/packages/tools/devtools/devtools-test-app/src/FluidObject.ts index 6b00e1f02946..e07f8895c468 100644 --- a/packages/tools/devtools/devtools-test-app/src/FluidObject.ts +++ b/packages/tools/devtools/devtools-test-app/src/FluidObject.ts @@ -179,50 +179,58 @@ export class AppData extends DataObject { const builder = new SchemaFactory("DefaultVisualizer_SharedTree_Test"); // TODO: Maybe include example handle + class LeafNodeSchema extends builder.object("leaf-node", { + value: [builder.boolean, builder.handle, builder.string], + }) {} + + class ArrayNodeSchema extends builder.object("array-node", { + value: [builder.string, builder.boolean], + childLeaf: builder.optional(LeafNodeSchema), + }) {} - class LeafSchema extends builder.object("item-two-second-first", { - twoFirstSecondFirst: [builder.boolean, builder.handle, builder.string], + class BranchNodeSchema extends builder.object("branch-node", { + arrayOrHandleNode: [builder.array(ArrayNodeSchema), builder.handle], + numericLeafNode: builder.number, }) {} - class TwoFirst extends builder.object("root-node-item-two-first", { - twoFirstFirst: [builder.string, builder.boolean], - twoFirstSecond: builder.optional(LeafSchema), + class MainBranchA extends builder.object("main-object-a", { + numericLeafNode: builder.number, + objectNode: BranchNodeSchema, }) {} - class RootNodeItemTwo extends builder.object("root-node-item-two", { - twoFirst: [builder.array(TwoFirst), builder.boolean], - twoSecond: builder.number, + class MainBranchB extends builder.object("main-object-b", { + numericLeafNode: builder.number, + booleanValue: builder.boolean, }) {} - class RootNodeItem extends builder.object("root-node-item", { - one: [builder.number, builder.string], - two: RootNodeItemTwo, + class RootFieldA extends builder.object("root-field-a", { + value: [builder.boolean, builder.handle, builder.string], }) {} - class RootNode extends builder.object("root-node", { - rootNode: builder.optional([RootNodeItem, builder.string]), + class RootFieldB extends builder.object("root-field-b", { + mainObjectNode: builder.optional([MainBranchA, MainBranchB]), }) {} const config = new TreeViewConfiguration({ - schema: [RootNode, builder.string, builder.number], + schema: [RootFieldA, RootFieldB, builder.string, builder.number], }); const view = sharedTree.viewWith(config); view.initialize({ - rootNode: { - one: 42, - two: { - twoFirst: [ + mainObjectNode: { + numericLeafNode: 42, + objectNode: { + arrayOrHandleNode: [ { - twoFirstFirst: false, - twoFirstSecond: { - twoFirstSecondFirst: "leaf value", + value: false, + childLeaf: { + value: "hello world!", }, }, { - twoFirstFirst: true, + value: true, }, ], - twoSecond: 123, + numericLeafNode: 123, }, }, }); From b40de038f08c6d0add93bbe41812304ec4cf98ad Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Sat, 25 Jan 2025 00:52:22 +0000 Subject: [PATCH 11/56] fix doc --- .../src/data-visualization/DefaultVisualizers.ts | 5 +++++ .../src/data-visualization/SharedTreeVisualizer.ts | 14 ++------------ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index bbac1338fcc2..0d773aeb2fdb 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -274,6 +274,11 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( // Schema of the tree node. const treeDefinitions = sharedTree.exportSimpleSchema().definitions; + /** + * {@link visualizeSharedTreeBySchema} passes `allowedTypes` into co-recursive functions while constructing the visual representation. + * Since the {@link SimpleTreeSchema.allowedTypes} of each children node is only accessible at the parent field level, + * each node's allowed types are computed at the parent field level. + */ const allowedTypes = concatenateTypes(sharedTree.exportSimpleSchema().allowedTypes); // Create a root field visualization that shows the allowed types at the root diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index b111998442e8..5bad4c86594f 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -157,16 +157,6 @@ export function concatenateTypes(fieldTypes: ReadonlySet): string { /** * Returns the allowed fields & types for the object fields (e.g., `foo : string | number, bar: boolean`) */ -// function getObjectAllowedTypes(schema: SimpleObjectNodeSchema): string { -// const result: string[] = []; - -// for (const [fieldKey, treeFieldSimpleSchema] of Object.entries(schema.fields)) { -// const fieldTypes = treeFieldSimpleSchema.allowedTypes; -// result.push(`${fieldKey} : ${concatenateTypes(fieldTypes)}`); -// } - -// return `{ ${result.join(", ")} }`; -// } /** * Returns the schema & fields of the node. @@ -191,7 +181,7 @@ async function visualizeVerboseNodeFields( return fields; } -function getObjectAllowedTypes(schema: SimpleObjectNodeSchema): Record { +function storeObjectAllowedTypes(schema: SimpleObjectNodeSchema): Record { const result: Record = {}; for (const [fieldKey, treeFieldSimpleSchema] of Object.entries(schema.fields)) { @@ -211,7 +201,7 @@ async function visualizeObjectNode( allowedTypes: string, visualizeChildData: VisualizeChildData, ): Promise { - const objectAllowedTypes = getObjectAllowedTypes( + const objectAllowedTypes = storeObjectAllowedTypes( treeDefinitions.get(tree.type) as SimpleObjectNodeSchema, ); From 13ee23decdff5b9fb2038a6921f5a4afb95b6cab Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Sat, 25 Jan 2025 02:13:26 +0000 Subject: [PATCH 12/56] fix test --- .../src/test/DefaultVisualizers.spec.ts | 282 +++++++++++------- 1 file changed, 177 insertions(+), 105 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts index 0181a8f355d8..9dba289a131f 100644 --- a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts +++ b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts @@ -394,7 +394,9 @@ describe("DefaultVisualizers unit tests", () => { "test", ); - const view = sharedTree.viewWith(new TreeViewConfiguration({ schema: builder.number })); + const view = sharedTree.viewWith( + new TreeViewConfiguration({ schema: [builder.number, builder.string] }), + ); view.initialize(0); const result = await visualizeSharedTree( @@ -413,6 +415,10 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.number", }, + allowedTypes: { + value: "com.fluidframework.leaf.number | com.fluidframework.leaf.string", + nodeKind: "ValueNode", + }, }, }, }, @@ -463,6 +469,10 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.number", }, + allowedTypes: { + value: "com.fluidframework.leaf.number | com.fluidframework.leaf.string", + nodeKind: "ValueNode", + }, }, }, }, @@ -478,6 +488,10 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.number", }, + allowedTypes: { + value: "com.fluidframework.leaf.number | com.fluidframework.leaf.string", + nodeKind: "ValueNode", + }, }, }, }, @@ -493,6 +507,10 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.number", }, + allowedTypes: { + value: "com.fluidframework.leaf.number | com.fluidframework.leaf.string", + nodeKind: "ValueNode", + }, }, }, }, @@ -508,6 +526,10 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.number", }, + allowedTypes: { + value: "com.fluidframework.leaf.number | com.fluidframework.leaf.string", + nodeKind: "ValueNode", + }, }, }, }, @@ -523,6 +545,10 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.string", }, + allowedTypes: { + value: "com.fluidframework.leaf.number | com.fluidframework.leaf.string", + nodeKind: "ValueNode", + }, }, }, }, @@ -538,6 +564,10 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.string", }, + allowedTypes: { + value: "com.fluidframework.leaf.number | com.fluidframework.leaf.string", + nodeKind: "ValueNode", + }, }, }, }, @@ -554,7 +584,8 @@ describe("DefaultVisualizers unit tests", () => { 'shared-tree-test.Array<["com.fluidframework.leaf.number","com.fluidframework.leaf.string"]>', }, allowedTypes: { - value: "com.fluidframework.leaf.number | com.fluidframework.leaf.string", + value: + 'shared-tree-test.Array<["com.fluidframework.leaf.number","com.fluidframework.leaf.string"]>', nodeKind: "ValueNode", }, }, @@ -572,8 +603,7 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.root-item", }, allowedTypes: { - value: - '{ foo : shared-tree-test.Array<["com.fluidframework.leaf.number","com.fluidframework.leaf.string"]> }', + value: "shared-tree-test.root-item", nodeKind: "ValueNode", }, }, @@ -630,6 +660,11 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.number", }, + allowedTypes: { + value: + "com.fluidframework.leaf.string | com.fluidframework.leaf.number | com.fluidframework.leaf.handle", + nodeKind: "ValueNode", + }, }, }, }, @@ -645,6 +680,11 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.number", }, + allowedTypes: { + value: + "com.fluidframework.leaf.string | com.fluidframework.leaf.number | com.fluidframework.leaf.handle", + nodeKind: "ValueNode", + }, }, }, }, @@ -660,6 +700,11 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.number", }, + allowedTypes: { + value: + "com.fluidframework.leaf.string | com.fluidframework.leaf.number | com.fluidframework.leaf.handle", + nodeKind: "ValueNode", + }, }, }, }, @@ -677,7 +722,7 @@ describe("DefaultVisualizers unit tests", () => { }, allowedTypes: { value: - "Record", + 'shared-tree-test.Map<["com.fluidframework.leaf.handle","com.fluidframework.leaf.number","com.fluidframework.leaf.string"]>', nodeKind: "ValueNode", }, }, @@ -695,8 +740,7 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.root-item", }, allowedTypes: { - value: - '{ foo : shared-tree-test.Map<["com.fluidframework.leaf.handle","com.fluidframework.leaf.number","com.fluidframework.leaf.string"]> }', + value: "shared-tree-test.root-item", nodeKind: "ValueNode", }, }, @@ -705,7 +749,6 @@ describe("DefaultVisualizers unit tests", () => { fluidObjectId: "test", typeMetadata: "SharedTree", }; - expect(result).to.deep.equal(expected); }); @@ -720,7 +763,7 @@ describe("DefaultVisualizers unit tests", () => { class RootNodeSchema extends builder.object("root-item", { foo: builder.object("bar-item", { - apple: builder.boolean, + apple: [builder.boolean, builder.string], banana: builder.string, }), }) {} @@ -755,6 +798,11 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.boolean", }, + allowedTypes: { + value: + "com.fluidframework.leaf.boolean | com.fluidframework.leaf.string", + nodeKind: "ValueNode", + }, }, }, }, @@ -770,6 +818,10 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.string", }, + allowedTypes: { + value: "com.fluidframework.leaf.string", + nodeKind: "ValueNode", + }, }, }, }, @@ -785,8 +837,7 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.bar-item", }, allowedTypes: { - value: - "{ apple : com.fluidframework.leaf.boolean, banana : com.fluidframework.leaf.string }", + value: "shared-tree-test.bar-item", nodeKind: "ValueNode", }, }, @@ -804,7 +855,7 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.root-item", }, allowedTypes: { - value: "{ foo : shared-tree-test.bar-item }", + value: "shared-tree-test.root-item", nodeKind: "ValueNode", }, }, @@ -849,12 +900,15 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.handle", }, + allowedTypes: { + value: "com.fluidframework.leaf.handle", + nodeKind: "ValueNode", + }, }, }, }, typeMetadata: "SharedTree", }; - expect(result).to.deep.equal(expected); }); @@ -906,6 +960,10 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.handle", }, + allowedTypes: { + value: "com.fluidframework.leaf.handle", + nodeKind: "ValueNode", + }, }, }, }, @@ -921,7 +979,7 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.bar-item", }, allowedTypes: { - value: "{ apple : com.fluidframework.leaf.handle }", + value: "shared-tree-test.bar-item", nodeKind: "ValueNode", }, }, @@ -939,7 +997,7 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.root-item", }, allowedTypes: { - value: "{ foo : shared-tree-test.bar-item }", + value: "shared-tree-test.root-item", nodeKind: "ValueNode", }, }, @@ -948,7 +1006,6 @@ describe("DefaultVisualizers unit tests", () => { fluidObjectId: "test", typeMetadata: "SharedTree", }; - expect(result).to.deep.equal(expected); }); @@ -1074,6 +1131,11 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.number", }, + allowedTypes: { + value: + "com.fluidframework.leaf.number | com.fluidframework.leaf.string", + nodeKind: "ValueNode", + }, }, }, }, @@ -1093,6 +1155,10 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.string", }, + allowedTypes: { + value: "com.fluidframework.leaf.string", + nodeKind: "ValueNode", + }, }, }, }, @@ -1108,7 +1174,7 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.broccoli-object-schema", }, allowedTypes: { - value: "{ alpaca : com.fluidframework.leaf.string }", + value: "shared-tree-test.broccoli-object-schema", nodeKind: "ValueNode", }, }, @@ -1127,7 +1193,8 @@ describe("DefaultVisualizers unit tests", () => { 'shared-tree-test.Array<["shared-tree-test.broccoli-object-schema"]>', }, allowedTypes: { - value: "shared-tree-test.broccoli-object-schema", + value: + 'shared-tree-test.Array<["shared-tree-test.broccoli-object-schema"]>', nodeKind: "ValueNode", }, }, @@ -1145,8 +1212,7 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.apple-object-schema", }, allowedTypes: { - value: - '{ avocado : com.fluidframework.leaf.number | com.fluidframework.leaf.string, broccoli : shared-tree-test.Array<["shared-tree-test.broccoli-object-schema"]> }', + value: "shared-tree-test.apple-object-schema", nodeKind: "ValueNode", }, }, @@ -1165,7 +1231,8 @@ describe("DefaultVisualizers unit tests", () => { 'shared-tree-test.Array<["shared-tree-test.apple-object-schema"]>', }, allowedTypes: { - value: "shared-tree-test.apple-object-schema", + value: + 'shared-tree-test.Array<["shared-tree-test.apple-object-schema"]>', nodeKind: "ValueNode", }, }, @@ -1185,6 +1252,11 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.boolean", }, + allowedTypes: { + value: + "com.fluidframework.leaf.boolean | com.fluidframework.leaf.string | com.fluidframework.leaf.number", + nodeKind: "ValueNode", + }, }, }, }, @@ -1200,8 +1272,7 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.banana-object", }, allowedTypes: { - value: - "{ miniBanana : com.fluidframework.leaf.boolean | com.fluidframework.leaf.string | com.fluidframework.leaf.number }", + value: "shared-tree-test.banana-object", nodeKind: "ValueNode", }, }, @@ -1219,6 +1290,10 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.number", }, + allowedTypes: { + value: "com.fluidframework.leaf.number", + nodeKind: "ValueNode", + }, }, }, }, @@ -1234,8 +1309,7 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.foo-item", }, allowedTypes: { - value: - '{ apple : shared-tree-test.Array<["shared-tree-test.apple-object-schema"]>, banana : shared-tree-test.banana-object, cherry : com.fluidframework.leaf.number }', + value: "shared-tree-test.foo-item", nodeKind: "ValueNode", }, }, @@ -1259,6 +1333,11 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.string", }, + allowedTypes: { + value: + "com.fluidframework.leaf.number | com.fluidframework.leaf.string", + nodeKind: "ValueNode", + }, }, }, }, @@ -1278,6 +1357,10 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.string", }, + allowedTypes: { + value: "com.fluidframework.leaf.string", + nodeKind: "ValueNode", + }, }, }, }, @@ -1293,7 +1376,7 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.broccoli-object-schema", }, allowedTypes: { - value: "{ alpaca : com.fluidframework.leaf.string }", + value: "shared-tree-test.broccoli-object-schema", nodeKind: "ValueNode", }, }, @@ -1312,7 +1395,8 @@ describe("DefaultVisualizers unit tests", () => { 'shared-tree-test.Array<["shared-tree-test.broccoli-object-schema"]>', }, allowedTypes: { - value: "shared-tree-test.broccoli-object-schema", + value: + 'shared-tree-test.Array<["shared-tree-test.broccoli-object-schema"]>', nodeKind: "ValueNode", }, }, @@ -1330,8 +1414,7 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.apple-object-schema", }, allowedTypes: { - value: - '{ avocado : com.fluidframework.leaf.number | com.fluidframework.leaf.string, broccoli : shared-tree-test.Array<["shared-tree-test.broccoli-object-schema"]> }', + value: "shared-tree-test.apple-object-schema", nodeKind: "ValueNode", }, }, @@ -1350,7 +1433,8 @@ describe("DefaultVisualizers unit tests", () => { 'shared-tree-test.Array<["shared-tree-test.apple-object-schema"]>', }, allowedTypes: { - value: "shared-tree-test.apple-object-schema", + value: + 'shared-tree-test.Array<["shared-tree-test.apple-object-schema"]>', nodeKind: "ValueNode", }, }, @@ -1370,6 +1454,11 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.boolean", }, + allowedTypes: { + value: + "com.fluidframework.leaf.boolean | com.fluidframework.leaf.string | com.fluidframework.leaf.number", + nodeKind: "ValueNode", + }, }, }, }, @@ -1385,8 +1474,7 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.banana-object", }, allowedTypes: { - value: - "{ miniBanana : com.fluidframework.leaf.boolean | com.fluidframework.leaf.string | com.fluidframework.leaf.number }", + value: "shared-tree-test.banana-object", nodeKind: "ValueNode", }, }, @@ -1404,8 +1492,7 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.foo-item", }, allowedTypes: { - value: - '{ apple : shared-tree-test.Array<["shared-tree-test.apple-object-schema"]>, banana : shared-tree-test.banana-object, cherry : com.fluidframework.leaf.number }', + value: "shared-tree-test.foo-item", nodeKind: "ValueNode", }, }, @@ -1423,7 +1510,7 @@ describe("DefaultVisualizers unit tests", () => { value: 'shared-tree-test.Array<["shared-tree-test.foo-item"]>', }, allowedTypes: { - value: "shared-tree-test.foo-item", + value: 'shared-tree-test.Array<["shared-tree-test.foo-item"]>', nodeKind: "ValueNode", }, }, @@ -1443,6 +1530,10 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.boolean", }, + allowedTypes: { + value: "com.fluidframework.leaf.boolean", + nodeKind: "ValueNode", + }, }, }, }, @@ -1458,6 +1549,10 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.string", }, + allowedTypes: { + value: "com.fluidframework.leaf.string", + nodeKind: "ValueNode", + }, }, }, }, @@ -1475,6 +1570,11 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.boolean", }, + allowedTypes: { + value: + "com.fluidframework.leaf.boolean | com.fluidframework.leaf.string | com.fluidframework.leaf.handle", + nodeKind: "ValueNode", + }, }, }, }, @@ -1490,8 +1590,7 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.chai-latte-object", }, allowedTypes: { - value: - "{ appleCider : com.fluidframework.leaf.boolean | com.fluidframework.leaf.string | com.fluidframework.leaf.handle }", + value: "shared-tree-test.chai-latte-object", nodeKind: "ValueNode", }, }, @@ -1513,6 +1612,10 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.boolean", }, + allowedTypes: { + value: "com.fluidframework.leaf.boolean", + nodeKind: "ValueNode", + }, }, }, }, @@ -1528,7 +1631,7 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.dalgona-object", }, allowedTypes: { - value: "{ avengers : com.fluidframework.leaf.boolean }", + value: "shared-tree-test.dalgona-object", nodeKind: "ValueNode", }, }, @@ -1546,7 +1649,7 @@ describe("DefaultVisualizers unit tests", () => { value: 'shared-tree-test.Array<["shared-tree-test.dalgona-object"]>', }, allowedTypes: { - value: "shared-tree-test.dalgona-object", + value: 'shared-tree-test.Array<["shared-tree-test.dalgona-object"]>', nodeKind: "ValueNode", }, }, @@ -1566,6 +1669,11 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.number", }, + allowedTypes: { + value: + "com.fluidframework.leaf.number | com.fluidframework.leaf.string", + nodeKind: "ValueNode", + }, }, }, }, @@ -1581,6 +1689,11 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.string", }, + allowedTypes: { + value: + "com.fluidframework.leaf.number | com.fluidframework.leaf.string", + nodeKind: "ValueNode", + }, }, }, }, @@ -1597,7 +1710,8 @@ describe("DefaultVisualizers unit tests", () => { 'shared-tree-test.Array<["com.fluidframework.leaf.number","com.fluidframework.leaf.string"]>', }, allowedTypes: { - value: "com.fluidframework.leaf.number | com.fluidframework.leaf.string", + value: + 'shared-tree-test.Array<["com.fluidframework.leaf.number","com.fluidframework.leaf.string"]>', nodeKind: "ValueNode", }, }, @@ -1615,8 +1729,7 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.bar-item", }, allowedTypes: { - value: - '{ americano : com.fluidframework.leaf.boolean, bubbleTea : com.fluidframework.leaf.string, chaiLatte : shared-tree-test.chai-latte-object, dalgona : shared-tree-test.Array<["shared-tree-test.dalgona-object"]>, espresso : shared-tree-test.Array<["com.fluidframework.leaf.number","com.fluidframework.leaf.string"]> }', + value: "shared-tree-test.bar-item", nodeKind: "ValueNode", }, }, @@ -1634,6 +1747,11 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.number", }, + allowedTypes: { + value: + "com.fluidframework.leaf.number | com.fluidframework.leaf.string | com.fluidframework.leaf.boolean", + nodeKind: "ValueNode", + }, }, }, }, @@ -1651,6 +1769,11 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.number", }, + allowedTypes: { + value: + "com.fluidframework.leaf.string | com.fluidframework.leaf.number | com.fluidframework.leaf.handle | shared-tree-test.map-object", + nodeKind: "ValueNode", + }, }, }, }, @@ -1666,6 +1789,11 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.number", }, + allowedTypes: { + value: + "com.fluidframework.leaf.string | com.fluidframework.leaf.number | com.fluidframework.leaf.handle | shared-tree-test.map-object", + nodeKind: "ValueNode", + }, }, }, }, @@ -1681,6 +1809,11 @@ describe("DefaultVisualizers unit tests", () => { nodeKind: "ValueNode", value: "com.fluidframework.leaf.number", }, + allowedTypes: { + value: + "com.fluidframework.leaf.string | com.fluidframework.leaf.number | com.fluidframework.leaf.handle | shared-tree-test.map-object", + nodeKind: "ValueNode", + }, }, }, }, @@ -1698,7 +1831,7 @@ describe("DefaultVisualizers unit tests", () => { }, allowedTypes: { value: - "Record", + 'shared-tree-test.Map<["com.fluidframework.leaf.handle","com.fluidframework.leaf.number","com.fluidframework.leaf.string","shared-tree-test.map-object"]>', nodeKind: "ValueNode", }, }, @@ -1716,68 +1849,7 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.root-item", }, allowedTypes: { - value: - '{ foo : shared-tree-test.Array<["shared-tree-test.foo-item"]>, bar : shared-tree-test.bar-item, baz : com.fluidframework.leaf.number | com.fluidframework.leaf.string | com.fluidframework.leaf.boolean, foobar : shared-tree-test.Map<["com.fluidframework.leaf.handle","com.fluidframework.leaf.number","com.fluidframework.leaf.string","shared-tree-test.map-object"]> }', - nodeKind: "ValueNode", - }, - }, - }, - }, - fluidObjectId: "test", - typeMetadata: "SharedTree", - }; - - expect(result).to.deep.equal(expected); - }); - - it("SharedTree: Renders multiple allowed types in SharedTree's root field", async () => { - const factory = SharedTree.getFactory(); - const builder = new SchemaFactory("shared-tree-test"); - - const sharedTree = factory.create( - new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), - "test", - ); - - const view = sharedTree.viewWith( - new TreeViewConfiguration({ schema: [builder.string, builder.number] }), - ); - view.initialize(23); - - const result = await visualizeSharedTree( - sharedTree as unknown as ISharedObject, - visualizeChildData, - ); - - const expected = { - children: { - root: { - value: 23, - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.number", - }, - }, - }, - }, - }, - }, - nodeKind: "FluidTreeNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "root", - }, - allowedTypes: { - value: "com.fluidframework.leaf.string | com.fluidframework.leaf.number", + value: "shared-tree-test.root-item", nodeKind: "ValueNode", }, }, From 0746731f79dc0e852efc7eaab161be216d767839 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Mon, 27 Jan 2025 20:15:35 +0000 Subject: [PATCH 13/56] make example schema more concise --- .../src/data-visualization/DefaultVisualizers.ts | 1 + .../tools/devtools/devtools-test-app/src/FluidObject.ts | 8 ++------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index 0d773aeb2fdb..b52ecf87bde6 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -274,6 +274,7 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( // Schema of the tree node. const treeDefinitions = sharedTree.exportSimpleSchema().definitions; + /** * {@link visualizeSharedTreeBySchema} passes `allowedTypes` into co-recursive functions while constructing the visual representation. * Since the {@link SimpleTreeSchema.allowedTypes} of each children node is only accessible at the parent field level, diff --git a/packages/tools/devtools/devtools-test-app/src/FluidObject.ts b/packages/tools/devtools/devtools-test-app/src/FluidObject.ts index e07f8895c468..092a029b5a12 100644 --- a/packages/tools/devtools/devtools-test-app/src/FluidObject.ts +++ b/packages/tools/devtools/devtools-test-app/src/FluidObject.ts @@ -203,16 +203,12 @@ export class AppData extends DataObject { booleanValue: builder.boolean, }) {} - class RootFieldA extends builder.object("root-field-a", { - value: [builder.boolean, builder.handle, builder.string], - }) {} - - class RootFieldB extends builder.object("root-field-b", { + class RootField extends builder.object("root-field-b", { mainObjectNode: builder.optional([MainBranchA, MainBranchB]), }) {} const config = new TreeViewConfiguration({ - schema: [RootFieldA, RootFieldB, builder.string, builder.number], + schema: [RootField, builder.number], }); const view = sharedTree.viewWith(config); view.initialize({ From f02d1a3f5b010ffb57e18ebe56d10e3281e419eb Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Tue, 28 Jan 2025 04:55:14 +0000 Subject: [PATCH 14/56] revertible: adds kind --- .../data-visualization/DefaultVisualizers.ts | 4 ++- .../SharedTreeVisualizer.ts | 21 +++++++++--- .../VisualSharedTreeTypes.ts | 5 +++ .../src/test/DefaultVisualizers.spec.ts | 34 +++++++++++++++++++ .../devtools-test-app/src/FluidObject.ts | 4 +-- 5 files changed, 61 insertions(+), 7 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index b52ecf87bde6..94c6d3e22d3a 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -30,6 +30,7 @@ import type { VisualizeChildData, VisualizeSharedObject } from "./DataVisualizat import { concatenateTypes, determineNodeKind, + extractKind, toVisualTree, visualizeSharedTreeBySchema, } from "./SharedTreeVisualizer.js"; @@ -280,7 +281,8 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( * Since the {@link SimpleTreeSchema.allowedTypes} of each children node is only accessible at the parent field level, * each node's allowed types are computed at the parent field level. */ - const allowedTypes = concatenateTypes(sharedTree.exportSimpleSchema().allowedTypes); + const allowedTypes = `${extractKind(sharedTree.exportSimpleSchema())} ${concatenateTypes(sharedTree.exportSimpleSchema().allowedTypes)}`; + // const fieldKind = extractKind(sharedTree.exportSimpleSchema()); // Create a root field visualization that shows the allowed types at the root const visualTreeRepresentation: VisualSharedTreeNode = await visualizeSharedTreeBySchema( diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 5bad4c86594f..92fc142d8667 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -3,10 +3,13 @@ * Licensed under the MIT License. */ +import { FieldKind } from "@fluidframework/tree"; import type { + SimpleFieldSchema, SimpleMapNodeSchema, SimpleNodeSchema, SimpleObjectNodeSchema, + SimpleTreeSchema, VerboseTree, VerboseTreeNode, } from "@fluidframework/tree/internal"; @@ -148,15 +151,25 @@ export function toVisualTree(tree: VisualSharedTreeNode): VisualChildNode { } /** - * Concatenrate allowed types for `ObjectNodeStoredSchema` and `MapNodeStoredSchema`. + * Concatenate allowed types for `ObjectNodeStoredSchema` and `MapNodeStoredSchema`. */ export function concatenateTypes(fieldTypes: ReadonlySet): string { return [...fieldTypes].join(" | "); } /** - * Returns the allowed fields & types for the object fields (e.g., `foo : string | number, bar: boolean`) + * Extract {@link FieldKind} from the schema. */ +export function extractKind(schema: SimpleTreeSchema | SimpleFieldSchema): string { + const allowedTypesPrefix = + schema.kind === FieldKind.Optional + ? "optional | " + : schema.kind === FieldKind.Required + ? "required | " + : "identifier | "; + + return `${allowedTypesPrefix}${[...schema.allowedTypes].join(" | ")}`; +} /** * Returns the schema & fields of the node. @@ -185,8 +198,8 @@ function storeObjectAllowedTypes(schema: SimpleObjectNodeSchema): Record = {}; for (const [fieldKey, treeFieldSimpleSchema] of Object.entries(schema.fields)) { - const fieldTypes = treeFieldSimpleSchema.allowedTypes; - result[fieldKey] = concatenateTypes(fieldTypes); + result[fieldKey] = + `${extractKind(treeFieldSimpleSchema)} ${concatenateTypes(treeFieldSimpleSchema.allowedTypes)}`; } return result; diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts b/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts index 8cfd3cd7106e..046dd1e47d28 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts @@ -29,6 +29,11 @@ export interface SharedTreeSchemaNode { */ schemaName: string; + /** + * TODO + */ + fieldKind?: string; + /** * Types allowed (e.g., string, number, boolean, handle & etc.) inside the node. * - InternalNode: `Record`. diff --git a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts index 9dba289a131f..e9f80b1cdf2c 100644 --- a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts +++ b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts @@ -1862,6 +1862,40 @@ describe("DefaultVisualizers unit tests", () => { expect(result).to.deep.equal(expected); }); + // it.only("Example", async () => { + // const factory = SharedTree.getFactory(); + // const builder = new SchemaFactory("shared-tree-test"); + + // const sharedTree = factory.create( + // new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + // "test", + // ); + + // class RootNodeSchema extends builder.object("root-item", { + // foo: builder.object("bar-item", { + // banana: builder.string, + // }), + // }) {} + + // const view = sharedTree.viewWith( + // new TreeViewConfiguration({ schema: builder.optional([RootNodeSchema]) }), + // ); + // view.initialize( + // new RootNodeSchema({ + // foo: { + // banana: "Taro Bubble Tea", + // }, + // }), + // ); + + // const result = await visualizeSharedTree( + // sharedTree as unknown as ISharedObject, + // visualizeChildData, + // ); + + // console.log(result); + // }); + it("Unknown SharedObject", async () => { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const unknownObject = { diff --git a/packages/tools/devtools/devtools-test-app/src/FluidObject.ts b/packages/tools/devtools/devtools-test-app/src/FluidObject.ts index 092a029b5a12..427f8bcc3d6e 100644 --- a/packages/tools/devtools/devtools-test-app/src/FluidObject.ts +++ b/packages/tools/devtools/devtools-test-app/src/FluidObject.ts @@ -194,12 +194,12 @@ export class AppData extends DataObject { }) {} class MainBranchA extends builder.object("main-object-a", { - numericLeafNode: builder.number, + numericLeafNode: builder.optional(builder.number), objectNode: BranchNodeSchema, }) {} class MainBranchB extends builder.object("main-object-b", { - numericLeafNode: builder.number, + numericLeafNode: builder.optional(builder.number), booleanValue: builder.boolean, }) {} From a77bf59f3f206f2cffc91264b92911c1189865d5 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Tue, 28 Jan 2025 23:37:40 +0000 Subject: [PATCH 15/56] add requirement --- packages/tools/devtools/devtools-test-app/src/FluidObject.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/tools/devtools/devtools-test-app/src/FluidObject.ts b/packages/tools/devtools/devtools-test-app/src/FluidObject.ts index 427f8bcc3d6e..b734e4bd59d3 100644 --- a/packages/tools/devtools/devtools-test-app/src/FluidObject.ts +++ b/packages/tools/devtools/devtools-test-app/src/FluidObject.ts @@ -189,8 +189,8 @@ export class AppData extends DataObject { }) {} class BranchNodeSchema extends builder.object("branch-node", { - arrayOrHandleNode: [builder.array(ArrayNodeSchema), builder.handle], - numericLeafNode: builder.number, + arrayOrHandleNode: builder.optional([builder.array(ArrayNodeSchema), builder.handle]), + numericLeafNode: builder.optional(builder.number), }) {} class MainBranchA extends builder.object("main-object-a", { From 9132cbb8f1f6300f0f6b4cc4945833b83eaaa925 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Wed, 29 Jan 2025 00:14:25 +0000 Subject: [PATCH 16/56] change name & schema --- .../devtools-test-app/src/FluidObject.ts | 76 ++++++++++--------- 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/packages/tools/devtools/devtools-test-app/src/FluidObject.ts b/packages/tools/devtools/devtools-test-app/src/FluidObject.ts index b734e4bd59d3..b2de9adce2fe 100644 --- a/packages/tools/devtools/devtools-test-app/src/FluidObject.ts +++ b/packages/tools/devtools/devtools-test-app/src/FluidObject.ts @@ -175,58 +175,64 @@ export class AppData extends DataObject { } private populateSharedTree(sharedTree: ITree): void { - // Set up SharedTree for visualization - const builder = new SchemaFactory("DefaultVisualizer_SharedTree_Test"); - - // TODO: Maybe include example handle - class LeafNodeSchema extends builder.object("leaf-node", { - value: [builder.boolean, builder.handle, builder.string], - }) {} - - class ArrayNodeSchema extends builder.object("array-node", { - value: [builder.string, builder.boolean], - childLeaf: builder.optional(LeafNodeSchema), + const builder = new SchemaFactory("TodoList_Schema"); + + class TodoItem extends builder.object("todo-item", { + title: builder.string, + completed: builder.boolean, + dueDate: builder.optional(builder.string), + assignee: builder.optional(builder.string), + colloborators: builder.optional(builder.array(builder.string)), }) {} - class BranchNodeSchema extends builder.object("branch-node", { - arrayOrHandleNode: builder.optional([builder.array(ArrayNodeSchema), builder.handle]), - numericLeafNode: builder.optional(builder.number), + class TodoObject extends builder.object("todo-list", { + items: builder.array(TodoItem), }) {} - class MainBranchA extends builder.object("main-object-a", { - numericLeafNode: builder.optional(builder.number), - objectNode: BranchNodeSchema, + class TodoCategory extends builder.object("todo-category", { + work: TodoObject, + personal: TodoObject, }) {} - class MainBranchB extends builder.object("main-object-b", { - numericLeafNode: builder.optional(builder.number), - booleanValue: builder.boolean, - }) {} - - class RootField extends builder.object("root-field-b", { - mainObjectNode: builder.optional([MainBranchA, MainBranchB]), + class TodoWorkspace extends builder.object("todo-workspace", { + lists: TodoCategory, }) {} const config = new TreeViewConfiguration({ - schema: [RootField, builder.number], + schema: [TodoWorkspace], }); + const view = sharedTree.viewWith(config); view.initialize({ - mainObjectNode: { - numericLeafNode: 42, - objectNode: { - arrayOrHandleNode: [ + lists: { + work: { + items: [ + { + title: "Finish design doc.", + completed: false, + dueDate: "2048-01-01", + assignee: "Kevin", + colloborators: ["Rick"], + }, + { + title: "Review pull requests", + completed: true, + assignee: "Bob", + }, + ], + }, + personal: { + items: [ { - value: false, - childLeaf: { - value: "hello world!", - }, + title: "Buy groceries", + completed: false, }, { - value: true, + title: "Schedule dentist appointment", + completed: false, + dueDate: "2024-05-04", }, ], - numericLeafNode: 123, }, }, }); From 4e8613957637143f4e35301a4f315708567c63e7 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Wed, 29 Jan 2025 00:27:33 +0000 Subject: [PATCH 17/56] change name & schema --- .../data-visualization/DefaultVisualizers.ts | 7 ++- .../SharedTreeVisualizer.ts | 60 ++++++++++++++----- .../VisualSharedTreeTypes.ts | 10 ++-- 3 files changed, 55 insertions(+), 22 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index 94c6d3e22d3a..c14d5ff9950f 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -30,7 +30,7 @@ import type { VisualizeChildData, VisualizeSharedObject } from "./DataVisualizat import { concatenateTypes, determineNodeKind, - extractKind, + getRequirement, toVisualTree, visualizeSharedTreeBySchema, } from "./SharedTreeVisualizer.js"; @@ -281,14 +281,15 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( * Since the {@link SimpleTreeSchema.allowedTypes} of each children node is only accessible at the parent field level, * each node's allowed types are computed at the parent field level. */ - const allowedTypes = `${extractKind(sharedTree.exportSimpleSchema())} ${concatenateTypes(sharedTree.exportSimpleSchema().allowedTypes)}`; - // const fieldKind = extractKind(sharedTree.exportSimpleSchema()); + const allowedTypes = concatenateTypes(sharedTree.exportSimpleSchema().allowedTypes); + const requirement = getRequirement(sharedTree.exportSimpleSchema()); // Create a root field visualization that shows the allowed types at the root const visualTreeRepresentation: VisualSharedTreeNode = await visualizeSharedTreeBySchema( treeView, treeDefinitions, allowedTypes, + requirement, visualizeChildData, ); diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 92fc142d8667..25655f5e346e 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -87,6 +87,12 @@ function createToolTipContents(schema: SharedTreeSchemaNode): VisualTreeNode { if (schema.allowedTypes !== undefined) { children.allowedTypes = createAllowedTypesVisualTree(schema.allowedTypes); } + if (schema.requirement !== undefined) { + children.requirement = { + nodeKind: VisualNodeKind.ValueNode, + value: schema.requirement, + }; + } return { nodeKind: VisualNodeKind.TreeNode, children, @@ -160,15 +166,15 @@ export function concatenateTypes(fieldTypes: ReadonlySet): string { /** * Extract {@link FieldKind} from the schema. */ -export function extractKind(schema: SimpleTreeSchema | SimpleFieldSchema): string { +export function getRequirement(schema: SimpleTreeSchema | SimpleFieldSchema): string { const allowedTypesPrefix = schema.kind === FieldKind.Optional - ? "optional | " + ? "optional" : schema.kind === FieldKind.Required - ? "required | " - : "identifier | "; + ? "required" + : "identifier"; - return `${allowedTypesPrefix}${[...schema.allowedTypes].join(" | ")}`; + return allowedTypesPrefix; } /** @@ -178,6 +184,7 @@ async function visualizeVerboseNodeFields( treeFields: VerboseTree[] | Record, treeDefinitions: ReadonlyMap, allowedTypes: Record, + requirements: Record, visualizeChildData: VisualizeChildData, ): Promise> { const fields: Record = {}; @@ -187,6 +194,7 @@ async function visualizeVerboseNodeFields( childField, treeDefinitions, allowedTypes[fieldKey], + requirements[fieldKey], visualizeChildData, ); } @@ -194,15 +202,19 @@ async function visualizeVerboseNodeFields( return fields; } -function storeObjectAllowedTypes(schema: SimpleObjectNodeSchema): Record { - const result: Record = {}; +function storeObjectAllowedTypes(schema: SimpleObjectNodeSchema): { + allowedTypes: Record; + requirements: Record; +} { + const allowedTypes: Record = {}; + const requirements: Record = {}; for (const [fieldKey, treeFieldSimpleSchema] of Object.entries(schema.fields)) { - result[fieldKey] = - `${extractKind(treeFieldSimpleSchema)} ${concatenateTypes(treeFieldSimpleSchema.allowedTypes)}`; + allowedTypes[fieldKey] = concatenateTypes(treeFieldSimpleSchema.allowedTypes); + requirements[fieldKey] = getRequirement(treeFieldSimpleSchema); } - return result; + return { allowedTypes, requirements }; } /** @@ -212,21 +224,23 @@ async function visualizeObjectNode( tree: VerboseTreeNode, treeDefinitions: ReadonlyMap, allowedTypes: string, + requirement: string, visualizeChildData: VisualizeChildData, ): Promise { - const objectAllowedTypes = storeObjectAllowedTypes( - treeDefinitions.get(tree.type) as SimpleObjectNodeSchema, - ); + const { allowedTypes: objectAllowedTypes, requirements: objectRequirements } = + storeObjectAllowedTypes(treeDefinitions.get(tree.type) as SimpleObjectNodeSchema); return { schema: { schemaName: tree.type, allowedTypes, + requirement, }, fields: await visualizeVerboseNodeFields( tree.fields, treeDefinitions, objectAllowedTypes, + objectRequirements, visualizeChildData, ), kind: VisualSharedTreeNodeKind.InternalNode, @@ -244,9 +258,11 @@ async function visualizeMapNode( visualizeChildData: VisualizeChildData, ): Promise { const mapAllowedTypes: Record = {}; + const mapRequirements: Record = {}; for (const key of Object.keys(tree.fields)) { mapAllowedTypes[key] = concatenateTypes(nodeSchema.allowedTypes); + mapRequirements[key] = "required"; } return { schema: { @@ -257,6 +273,7 @@ async function visualizeMapNode( tree.fields, treeDefinitions, mapAllowedTypes, + mapRequirements, visualizeChildData, ), kind: VisualSharedTreeNodeKind.InternalNode, @@ -276,6 +293,7 @@ async function visualizeInternalNodeBySchema( tree: VerboseTreeNode, treeDefinitions: ReadonlyMap, allowedTypes: string, + requirement: string, visualizeChildData: VisualizeChildData, ): Promise { const schema = treeDefinitions.get(tree.type); @@ -290,6 +308,7 @@ async function visualizeInternalNodeBySchema( tree, treeDefinitions, allowedTypes, + requirement, visualizeChildData, ); return objectVisualized; @@ -312,13 +331,16 @@ async function visualizeInternalNodeBySchema( } const arrayAllowedTypes: Record = {}; + const arrayRequirements: Record = {}; for (let i = 0; i < children.length; i++) { arrayAllowedTypes[i] = concatenateTypes(schema.allowedTypes); + arrayRequirements[i] = "required"; fields[i] = await visualizeSharedTreeBySchema( children[i], treeDefinitions, arrayAllowedTypes[i], + arrayRequirements[i], visualizeChildData, ); } @@ -327,11 +349,13 @@ async function visualizeInternalNodeBySchema( schema: { schemaName: tree.type, allowedTypes, + requirement, }, fields: await visualizeVerboseNodeFields( tree.fields, treeDefinitions, arrayAllowedTypes, + arrayRequirements, visualizeChildData, ), kind: VisualSharedTreeNodeKind.InternalNode, @@ -359,6 +383,7 @@ export async function visualizeSharedTreeBySchema( tree: VerboseTree, treeDefinitions: ReadonlyMap, allowedTypes: string, + requirement: string, visualizeChildData: VisualizeChildData, ): Promise { const schemaFactory = new SchemaFactory(undefined); @@ -374,9 +399,16 @@ export async function visualizeSharedTreeBySchema( schema: { schemaName: Tree.schema(tree).identifier, allowedTypes, + requirement, }, value: await visualizeChildData(tree), kind: VisualSharedTreeNodeKind.LeafNode, } - : visualizeInternalNodeBySchema(tree, treeDefinitions, allowedTypes, visualizeChildData); + : visualizeInternalNodeBySchema( + tree, + treeDefinitions, + allowedTypes, + requirement, + visualizeChildData, + ); } diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts b/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts index 046dd1e47d28..12a39b2aa32f 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts @@ -29,16 +29,16 @@ export interface SharedTreeSchemaNode { */ schemaName: string; - /** - * TODO - */ - fieldKind?: string; - /** * Types allowed (e.g., string, number, boolean, handle & etc.) inside the node. * - InternalNode: `Record`. */ allowedTypes?: string | Record; + + /** + * If the field or node is required (e.g., required, optional) or not. + */ + requirement?: string; } /** From 08db65a4d1388250b06d857e658ae309dc419da5 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Wed, 29 Jan 2025 00:51:55 +0000 Subject: [PATCH 18/56] fix varaible name / add undefined --- .../data-visualization/DefaultVisualizers.ts | 4 +- .../SharedTreeVisualizer.ts | 48 ++++++++----------- .../VisualSharedTreeTypes.ts | 2 +- .../src/test/DefaultVisualizers.spec.ts | 34 ------------- 4 files changed, 23 insertions(+), 65 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index c14d5ff9950f..f4ce4dd12ea8 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -282,14 +282,14 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( * each node's allowed types are computed at the parent field level. */ const allowedTypes = concatenateTypes(sharedTree.exportSimpleSchema().allowedTypes); - const requirement = getRequirement(sharedTree.exportSimpleSchema()); + const isRequired = getRequirement(sharedTree.exportSimpleSchema()); // Create a root field visualization that shows the allowed types at the root const visualTreeRepresentation: VisualSharedTreeNode = await visualizeSharedTreeBySchema( treeView, treeDefinitions, allowedTypes, - requirement, + isRequired, visualizeChildData, ); diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 25655f5e346e..2e69a111ccdc 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -87,10 +87,10 @@ function createToolTipContents(schema: SharedTreeSchemaNode): VisualTreeNode { if (schema.allowedTypes !== undefined) { children.allowedTypes = createAllowedTypesVisualTree(schema.allowedTypes); } - if (schema.requirement !== undefined) { - children.requirement = { + if (schema.isRequired !== undefined) { + children.isRequired = { nodeKind: VisualNodeKind.ValueNode, - value: schema.requirement, + value: schema.isRequired, }; } return { @@ -167,14 +167,7 @@ export function concatenateTypes(fieldTypes: ReadonlySet): string { * Extract {@link FieldKind} from the schema. */ export function getRequirement(schema: SimpleTreeSchema | SimpleFieldSchema): string { - const allowedTypesPrefix = - schema.kind === FieldKind.Optional - ? "optional" - : schema.kind === FieldKind.Required - ? "required" - : "identifier"; - - return allowedTypesPrefix; + return schema.kind === FieldKind.Required ? "true" : "false"; } /** @@ -184,7 +177,7 @@ async function visualizeVerboseNodeFields( treeFields: VerboseTree[] | Record, treeDefinitions: ReadonlyMap, allowedTypes: Record, - requirements: Record, + requirements: Record | undefined, visualizeChildData: VisualizeChildData, ): Promise> { const fields: Record = {}; @@ -194,7 +187,7 @@ async function visualizeVerboseNodeFields( childField, treeDefinitions, allowedTypes[fieldKey], - requirements[fieldKey], + requirements === undefined ? requirements : requirements[fieldKey], visualizeChildData, ); } @@ -224,7 +217,7 @@ async function visualizeObjectNode( tree: VerboseTreeNode, treeDefinitions: ReadonlyMap, allowedTypes: string, - requirement: string, + isRequired: string | undefined, visualizeChildData: VisualizeChildData, ): Promise { const { allowedTypes: objectAllowedTypes, requirements: objectRequirements } = @@ -234,7 +227,7 @@ async function visualizeObjectNode( schema: { schemaName: tree.type, allowedTypes, - requirement, + isRequired, }, fields: await visualizeVerboseNodeFields( tree.fields, @@ -255,25 +248,25 @@ async function visualizeMapNode( nodeSchema: SimpleMapNodeSchema, treeDefinitions: ReadonlyMap, allowedTypes: string, + isRequired: string | undefined, visualizeChildData: VisualizeChildData, ): Promise { const mapAllowedTypes: Record = {}; - const mapRequirements: Record = {}; for (const key of Object.keys(tree.fields)) { mapAllowedTypes[key] = concatenateTypes(nodeSchema.allowedTypes); - mapRequirements[key] = "required"; } return { schema: { schemaName: tree.type, allowedTypes, + isRequired, }, fields: await visualizeVerboseNodeFields( tree.fields, treeDefinitions, mapAllowedTypes, - mapRequirements, + undefined, visualizeChildData, ), kind: VisualSharedTreeNodeKind.InternalNode, @@ -293,7 +286,7 @@ async function visualizeInternalNodeBySchema( tree: VerboseTreeNode, treeDefinitions: ReadonlyMap, allowedTypes: string, - requirement: string, + isRequired: string | undefined, visualizeChildData: VisualizeChildData, ): Promise { const schema = treeDefinitions.get(tree.type); @@ -308,7 +301,7 @@ async function visualizeInternalNodeBySchema( tree, treeDefinitions, allowedTypes, - requirement, + isRequired, visualizeChildData, ); return objectVisualized; @@ -319,6 +312,7 @@ async function visualizeInternalNodeBySchema( schema, treeDefinitions, allowedTypes, + isRequired, visualizeChildData, ); return mapVisualized; @@ -331,16 +325,14 @@ async function visualizeInternalNodeBySchema( } const arrayAllowedTypes: Record = {}; - const arrayRequirements: Record = {}; for (let i = 0; i < children.length; i++) { arrayAllowedTypes[i] = concatenateTypes(schema.allowedTypes); - arrayRequirements[i] = "required"; fields[i] = await visualizeSharedTreeBySchema( children[i], treeDefinitions, arrayAllowedTypes[i], - arrayRequirements[i], + undefined, visualizeChildData, ); } @@ -349,13 +341,13 @@ async function visualizeInternalNodeBySchema( schema: { schemaName: tree.type, allowedTypes, - requirement, + isRequired, }, fields: await visualizeVerboseNodeFields( tree.fields, treeDefinitions, arrayAllowedTypes, - arrayRequirements, + undefined, visualizeChildData, ), kind: VisualSharedTreeNodeKind.InternalNode, @@ -383,7 +375,7 @@ export async function visualizeSharedTreeBySchema( tree: VerboseTree, treeDefinitions: ReadonlyMap, allowedTypes: string, - requirement: string, + isRequired: string | undefined, visualizeChildData: VisualizeChildData, ): Promise { const schemaFactory = new SchemaFactory(undefined); @@ -399,7 +391,7 @@ export async function visualizeSharedTreeBySchema( schema: { schemaName: Tree.schema(tree).identifier, allowedTypes, - requirement, + isRequired, }, value: await visualizeChildData(tree), kind: VisualSharedTreeNodeKind.LeafNode, @@ -408,7 +400,7 @@ export async function visualizeSharedTreeBySchema( tree, treeDefinitions, allowedTypes, - requirement, + isRequired, visualizeChildData, ); } diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts b/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts index 12a39b2aa32f..cd4c15720f64 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts @@ -38,7 +38,7 @@ export interface SharedTreeSchemaNode { /** * If the field or node is required (e.g., required, optional) or not. */ - requirement?: string; + isRequired?: string; } /** diff --git a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts index e9f80b1cdf2c..9dba289a131f 100644 --- a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts +++ b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts @@ -1862,40 +1862,6 @@ describe("DefaultVisualizers unit tests", () => { expect(result).to.deep.equal(expected); }); - // it.only("Example", async () => { - // const factory = SharedTree.getFactory(); - // const builder = new SchemaFactory("shared-tree-test"); - - // const sharedTree = factory.create( - // new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), - // "test", - // ); - - // class RootNodeSchema extends builder.object("root-item", { - // foo: builder.object("bar-item", { - // banana: builder.string, - // }), - // }) {} - - // const view = sharedTree.viewWith( - // new TreeViewConfiguration({ schema: builder.optional([RootNodeSchema]) }), - // ); - // view.initialize( - // new RootNodeSchema({ - // foo: { - // banana: "Taro Bubble Tea", - // }, - // }), - // ); - - // const result = await visualizeSharedTree( - // sharedTree as unknown as ISharedObject, - // visualizeChildData, - // ); - - // console.log(result); - // }); - it("Unknown SharedObject", async () => { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const unknownObject = { From e8c06bf77af1c2118d88507fd58bd362b583d53d Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Wed, 29 Jan 2025 01:17:28 +0000 Subject: [PATCH 19/56] change test --- .../data-visualization/DefaultVisualizers.ts | 4 +- .../SharedTreeVisualizer.ts | 15 +- .../src/test/DefaultVisualizers.spec.ts | 154 +++++++++++++++++- 3 files changed, 165 insertions(+), 8 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index f4ce4dd12ea8..e02868f4f85d 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -30,7 +30,7 @@ import type { VisualizeChildData, VisualizeSharedObject } from "./DataVisualizat import { concatenateTypes, determineNodeKind, - getRequirement, + getIsRequired, toVisualTree, visualizeSharedTreeBySchema, } from "./SharedTreeVisualizer.js"; @@ -282,7 +282,7 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( * each node's allowed types are computed at the parent field level. */ const allowedTypes = concatenateTypes(sharedTree.exportSimpleSchema().allowedTypes); - const isRequired = getRequirement(sharedTree.exportSimpleSchema()); + const isRequired = getIsRequired(sharedTree.exportSimpleSchema()); // Create a root field visualization that shows the allowed types at the root const visualTreeRepresentation: VisualSharedTreeNode = await visualizeSharedTreeBySchema( diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 2e69a111ccdc..db9ba9c0a378 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -164,9 +164,9 @@ export function concatenateTypes(fieldTypes: ReadonlySet): string { } /** - * Extract {@link FieldKind} from the schema. + * Returns if the node under the field is required or optional from the {@link FieldKind} of schema. */ -export function getRequirement(schema: SimpleTreeSchema | SimpleFieldSchema): string { +export function getIsRequired(schema: SimpleTreeSchema | SimpleFieldSchema): string { return schema.kind === FieldKind.Required ? "true" : "false"; } @@ -195,6 +195,9 @@ async function visualizeVerboseNodeFields( return fields; } +/** + * Extracts and stores allowed types & kind from {@link SimpleFieldSchema}. + */ function storeObjectAllowedTypes(schema: SimpleObjectNodeSchema): { allowedTypes: Record; requirements: Record; @@ -204,7 +207,7 @@ function storeObjectAllowedTypes(schema: SimpleObjectNodeSchema): { for (const [fieldKey, treeFieldSimpleSchema] of Object.entries(schema.fields)) { allowedTypes[fieldKey] = concatenateTypes(treeFieldSimpleSchema.allowedTypes); - requirements[fieldKey] = getRequirement(treeFieldSimpleSchema); + requirements[fieldKey] = getIsRequired(treeFieldSimpleSchema); } return { allowedTypes, requirements }; @@ -362,7 +365,11 @@ async function visualizeInternalNodeBySchema( /** * Creates a visual representation of a SharedTree based on its schema. * @param tree - The {@link VerboseTree} to visualize - * @param treeSchema - The schema that defines the structure and types of the tree + * @param treeDefinitions - The schema that defines the structure and types of the tree + * @param allowedTypes - String representing the allowed types for this node + * @param isRequired - Whether this field is required in its parent object schema. + * Only meaningful for direct children of object nodes. + * Undefined for array/map elements since they are always required within their parent. * @param visualizeChildData - Callback function to visualize child node data * @returns A visual representation of the tree that includes schema information and node values * diff --git a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts index 9dba289a131f..3fc0d49dc8b3 100644 --- a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts +++ b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts @@ -419,6 +419,10 @@ describe("DefaultVisualizers unit tests", () => { value: "com.fluidframework.leaf.number | com.fluidframework.leaf.string", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -588,6 +592,10 @@ describe("DefaultVisualizers unit tests", () => { 'shared-tree-test.Array<["com.fluidframework.leaf.number","com.fluidframework.leaf.string"]>', nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -606,6 +614,10 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.root-item", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -725,6 +737,10 @@ describe("DefaultVisualizers unit tests", () => { 'shared-tree-test.Map<["com.fluidframework.leaf.handle","com.fluidframework.leaf.number","com.fluidframework.leaf.string"]>', nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -743,6 +759,10 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.root-item", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -803,6 +823,10 @@ describe("DefaultVisualizers unit tests", () => { "com.fluidframework.leaf.boolean | com.fluidframework.leaf.string", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -822,6 +846,10 @@ describe("DefaultVisualizers unit tests", () => { value: "com.fluidframework.leaf.string", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -840,6 +868,10 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.bar-item", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -858,13 +890,16 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.root-item", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, fluidObjectId: "test", typeMetadata: "SharedTree", }; - expect(result).to.deep.equal(expected); }); @@ -904,6 +939,10 @@ describe("DefaultVisualizers unit tests", () => { value: "com.fluidframework.leaf.handle", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -964,6 +1003,10 @@ describe("DefaultVisualizers unit tests", () => { value: "com.fluidframework.leaf.handle", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -982,6 +1025,10 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.bar-item", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1000,6 +1047,10 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.root-item", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1136,6 +1187,10 @@ describe("DefaultVisualizers unit tests", () => { "com.fluidframework.leaf.number | com.fluidframework.leaf.string", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1159,6 +1214,10 @@ describe("DefaultVisualizers unit tests", () => { value: "com.fluidframework.leaf.string", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1197,6 +1256,10 @@ describe("DefaultVisualizers unit tests", () => { 'shared-tree-test.Array<["shared-tree-test.broccoli-object-schema"]>', nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1235,6 +1298,10 @@ describe("DefaultVisualizers unit tests", () => { 'shared-tree-test.Array<["shared-tree-test.apple-object-schema"]>', nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1257,6 +1324,10 @@ describe("DefaultVisualizers unit tests", () => { "com.fluidframework.leaf.boolean | com.fluidframework.leaf.string | com.fluidframework.leaf.number", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1275,6 +1346,10 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.banana-object", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1294,6 +1369,10 @@ describe("DefaultVisualizers unit tests", () => { value: "com.fluidframework.leaf.number", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "false", + }, }, }, }, @@ -1338,6 +1417,10 @@ describe("DefaultVisualizers unit tests", () => { "com.fluidframework.leaf.number | com.fluidframework.leaf.string", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1361,6 +1444,10 @@ describe("DefaultVisualizers unit tests", () => { value: "com.fluidframework.leaf.string", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1399,6 +1486,10 @@ describe("DefaultVisualizers unit tests", () => { 'shared-tree-test.Array<["shared-tree-test.broccoli-object-schema"]>', nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1437,6 +1528,10 @@ describe("DefaultVisualizers unit tests", () => { 'shared-tree-test.Array<["shared-tree-test.apple-object-schema"]>', nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1459,6 +1554,10 @@ describe("DefaultVisualizers unit tests", () => { "com.fluidframework.leaf.boolean | com.fluidframework.leaf.string | com.fluidframework.leaf.number", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1477,6 +1576,10 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.banana-object", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1513,6 +1616,10 @@ describe("DefaultVisualizers unit tests", () => { value: 'shared-tree-test.Array<["shared-tree-test.foo-item"]>', nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1534,6 +1641,10 @@ describe("DefaultVisualizers unit tests", () => { value: "com.fluidframework.leaf.boolean", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1553,6 +1664,10 @@ describe("DefaultVisualizers unit tests", () => { value: "com.fluidframework.leaf.string", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1575,6 +1690,10 @@ describe("DefaultVisualizers unit tests", () => { "com.fluidframework.leaf.boolean | com.fluidframework.leaf.string | com.fluidframework.leaf.handle", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1593,6 +1712,10 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.chai-latte-object", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1616,6 +1739,10 @@ describe("DefaultVisualizers unit tests", () => { value: "com.fluidframework.leaf.boolean", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1652,6 +1779,10 @@ describe("DefaultVisualizers unit tests", () => { value: 'shared-tree-test.Array<["shared-tree-test.dalgona-object"]>', nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1714,6 +1845,10 @@ describe("DefaultVisualizers unit tests", () => { 'shared-tree-test.Array<["com.fluidframework.leaf.number","com.fluidframework.leaf.string"]>', nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1732,6 +1867,10 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.bar-item", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1752,6 +1891,10 @@ describe("DefaultVisualizers unit tests", () => { "com.fluidframework.leaf.number | com.fluidframework.leaf.string | com.fluidframework.leaf.boolean", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1834,6 +1977,10 @@ describe("DefaultVisualizers unit tests", () => { 'shared-tree-test.Map<["com.fluidframework.leaf.handle","com.fluidframework.leaf.number","com.fluidframework.leaf.string","shared-tree-test.map-object"]>', nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, @@ -1852,13 +1999,16 @@ describe("DefaultVisualizers unit tests", () => { value: "shared-tree-test.root-item", nodeKind: "ValueNode", }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, }, }, }, fluidObjectId: "test", typeMetadata: "SharedTree", }; - expect(result).to.deep.equal(expected); }); From 96a015bf707e891285d53471de1866b8f38a11db Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Wed, 29 Jan 2025 01:21:08 +0000 Subject: [PATCH 20/56] improve doc --- .../src/data-visualization/SharedTreeVisualizer.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index db9ba9c0a378..20dc45fdf16e 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -164,7 +164,14 @@ export function concatenateTypes(fieldTypes: ReadonlySet): string { } /** - * Returns if the node under the field is required or optional from the {@link FieldKind} of schema. + * Determines if a field or node is required based on its schema kind. + * @param schema - The schema to check, either a SimpleTreeSchema or SimpleFieldSchema + * @returns "true" if the field is required (FieldKind.Required), "false" otherwise + * + * @remarks + * This function is used when visualizing schema information to indicate whether + * fields in object nodes are required or optional. The return value is a string + * rather than a boolean to match the expected format for schema visualization. */ export function getIsRequired(schema: SimpleTreeSchema | SimpleFieldSchema): string { return schema.kind === FieldKind.Required ? "true" : "false"; From 18fbeac4eabb74944f17be6d9c6b4254365616ab Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Thu, 30 Jan 2025 03:10:32 +0000 Subject: [PATCH 21/56] change --- .../data-visualization/DefaultVisualizers.ts | 6 +-- .../SharedTreeVisualizer.ts | 40 ++++++------------- 2 files changed, 15 insertions(+), 31 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index e02868f4f85d..2ec7d9674c84 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -22,7 +22,7 @@ import { SharedMatrix } from "@fluidframework/matrix/internal"; import { SharedString } from "@fluidframework/sequence/internal"; import type { ISharedObject } from "@fluidframework/shared-object-base/internal"; import type { ITreeInternal } from "@fluidframework/tree/internal"; -import { SharedTree } from "@fluidframework/tree/internal"; +import { FieldKind, SharedTree } from "@fluidframework/tree/internal"; import { EditType } from "../CommonInterfaces.js"; @@ -30,7 +30,6 @@ import type { VisualizeChildData, VisualizeSharedObject } from "./DataVisualizat import { concatenateTypes, determineNodeKind, - getIsRequired, toVisualTree, visualizeSharedTreeBySchema, } from "./SharedTreeVisualizer.js"; @@ -282,7 +281,8 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( * each node's allowed types are computed at the parent field level. */ const allowedTypes = concatenateTypes(sharedTree.exportSimpleSchema().allowedTypes); - const isRequired = getIsRequired(sharedTree.exportSimpleSchema()); + const isRequired = + sharedTree.exportSimpleSchema().kind === FieldKind.Required ? true : false; // Create a root field visualization that shows the allowed types at the root const visualTreeRepresentation: VisualSharedTreeNode = await visualizeSharedTreeBySchema( diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 20dc45fdf16e..4bc15f668836 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -5,11 +5,9 @@ import { FieldKind } from "@fluidframework/tree"; import type { - SimpleFieldSchema, SimpleMapNodeSchema, SimpleNodeSchema, SimpleObjectNodeSchema, - SimpleTreeSchema, VerboseTree, VerboseTreeNode, } from "@fluidframework/tree/internal"; @@ -163,20 +161,6 @@ export function concatenateTypes(fieldTypes: ReadonlySet): string { return [...fieldTypes].join(" | "); } -/** - * Determines if a field or node is required based on its schema kind. - * @param schema - The schema to check, either a SimpleTreeSchema or SimpleFieldSchema - * @returns "true" if the field is required (FieldKind.Required), "false" otherwise - * - * @remarks - * This function is used when visualizing schema information to indicate whether - * fields in object nodes are required or optional. The return value is a string - * rather than a boolean to match the expected format for schema visualization. - */ -export function getIsRequired(schema: SimpleTreeSchema | SimpleFieldSchema): string { - return schema.kind === FieldKind.Required ? "true" : "false"; -} - /** * Returns the schema & fields of the node. */ @@ -184,7 +168,7 @@ async function visualizeVerboseNodeFields( treeFields: VerboseTree[] | Record, treeDefinitions: ReadonlyMap, allowedTypes: Record, - requirements: Record | undefined, + requirements: Record | undefined, visualizeChildData: VisualizeChildData, ): Promise> { const fields: Record = {}; @@ -207,14 +191,14 @@ async function visualizeVerboseNodeFields( */ function storeObjectAllowedTypes(schema: SimpleObjectNodeSchema): { allowedTypes: Record; - requirements: Record; + requirements: Record; } { const allowedTypes: Record = {}; - const requirements: Record = {}; + const requirements: Record = {}; for (const [fieldKey, treeFieldSimpleSchema] of Object.entries(schema.fields)) { allowedTypes[fieldKey] = concatenateTypes(treeFieldSimpleSchema.allowedTypes); - requirements[fieldKey] = getIsRequired(treeFieldSimpleSchema); + requirements[fieldKey] = treeFieldSimpleSchema.kind === FieldKind.Required ? true : false; } return { allowedTypes, requirements }; @@ -227,7 +211,7 @@ async function visualizeObjectNode( tree: VerboseTreeNode, treeDefinitions: ReadonlyMap, allowedTypes: string, - isRequired: string | undefined, + isRequired: boolean | undefined, visualizeChildData: VisualizeChildData, ): Promise { const { allowedTypes: objectAllowedTypes, requirements: objectRequirements } = @@ -237,7 +221,7 @@ async function visualizeObjectNode( schema: { schemaName: tree.type, allowedTypes, - isRequired, + isRequired: isRequired?.toString(), }, fields: await visualizeVerboseNodeFields( tree.fields, @@ -258,7 +242,7 @@ async function visualizeMapNode( nodeSchema: SimpleMapNodeSchema, treeDefinitions: ReadonlyMap, allowedTypes: string, - isRequired: string | undefined, + isRequired: boolean | undefined, visualizeChildData: VisualizeChildData, ): Promise { const mapAllowedTypes: Record = {}; @@ -270,7 +254,7 @@ async function visualizeMapNode( schema: { schemaName: tree.type, allowedTypes, - isRequired, + isRequired: isRequired?.toString(), }, fields: await visualizeVerboseNodeFields( tree.fields, @@ -296,7 +280,7 @@ async function visualizeInternalNodeBySchema( tree: VerboseTreeNode, treeDefinitions: ReadonlyMap, allowedTypes: string, - isRequired: string | undefined, + isRequired: boolean | undefined, visualizeChildData: VisualizeChildData, ): Promise { const schema = treeDefinitions.get(tree.type); @@ -351,7 +335,7 @@ async function visualizeInternalNodeBySchema( schema: { schemaName: tree.type, allowedTypes, - isRequired, + isRequired: isRequired?.toString(), }, fields: await visualizeVerboseNodeFields( tree.fields, @@ -389,7 +373,7 @@ export async function visualizeSharedTreeBySchema( tree: VerboseTree, treeDefinitions: ReadonlyMap, allowedTypes: string, - isRequired: string | undefined, + isRequired: boolean | undefined, visualizeChildData: VisualizeChildData, ): Promise { const schemaFactory = new SchemaFactory(undefined); @@ -405,7 +389,7 @@ export async function visualizeSharedTreeBySchema( schema: { schemaName: Tree.schema(tree).identifier, allowedTypes, - isRequired, + isRequired: isRequired?.toString(), }, value: await visualizeChildData(tree), kind: VisualSharedTreeNodeKind.LeafNode, From 3bc38fbc32af3640ffc9febbe269d83657945abf Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Thu, 30 Jan 2025 03:23:20 +0000 Subject: [PATCH 22/56] change param type --- .../data-visualization/DefaultVisualizers.ts | 3 +- .../SharedTreeVisualizer.ts | 34 +++++++++---------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index 2ec7d9674c84..f1917403c63b 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -28,7 +28,6 @@ import { EditType } from "../CommonInterfaces.js"; import type { VisualizeChildData, VisualizeSharedObject } from "./DataVisualization.js"; import { - concatenateTypes, determineNodeKind, toVisualTree, visualizeSharedTreeBySchema, @@ -280,7 +279,7 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( * Since the {@link SimpleTreeSchema.allowedTypes} of each children node is only accessible at the parent field level, * each node's allowed types are computed at the parent field level. */ - const allowedTypes = concatenateTypes(sharedTree.exportSimpleSchema().allowedTypes); + const allowedTypes = sharedTree.exportSimpleSchema().allowedTypes; const isRequired = sharedTree.exportSimpleSchema().kind === FieldKind.Required ? true : false; diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 4bc15f668836..44af5196cc93 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -157,7 +157,7 @@ export function toVisualTree(tree: VisualSharedTreeNode): VisualChildNode { /** * Concatenate allowed types for `ObjectNodeStoredSchema` and `MapNodeStoredSchema`. */ -export function concatenateTypes(fieldTypes: ReadonlySet): string { +function concatenateTypes(fieldTypes: ReadonlySet): string { return [...fieldTypes].join(" | "); } @@ -167,7 +167,7 @@ export function concatenateTypes(fieldTypes: ReadonlySet): string { async function visualizeVerboseNodeFields( treeFields: VerboseTree[] | Record, treeDefinitions: ReadonlyMap, - allowedTypes: Record, + allowedTypes: Record>, requirements: Record | undefined, visualizeChildData: VisualizeChildData, ): Promise> { @@ -190,14 +190,14 @@ async function visualizeVerboseNodeFields( * Extracts and stores allowed types & kind from {@link SimpleFieldSchema}. */ function storeObjectAllowedTypes(schema: SimpleObjectNodeSchema): { - allowedTypes: Record; + allowedTypes: Record>; requirements: Record; } { - const allowedTypes: Record = {}; + const allowedTypes: Record> = {}; const requirements: Record = {}; for (const [fieldKey, treeFieldSimpleSchema] of Object.entries(schema.fields)) { - allowedTypes[fieldKey] = concatenateTypes(treeFieldSimpleSchema.allowedTypes); + allowedTypes[fieldKey] = treeFieldSimpleSchema.allowedTypes; requirements[fieldKey] = treeFieldSimpleSchema.kind === FieldKind.Required ? true : false; } @@ -210,7 +210,7 @@ function storeObjectAllowedTypes(schema: SimpleObjectNodeSchema): { async function visualizeObjectNode( tree: VerboseTreeNode, treeDefinitions: ReadonlyMap, - allowedTypes: string, + allowedTypes: ReadonlySet, isRequired: boolean | undefined, visualizeChildData: VisualizeChildData, ): Promise { @@ -220,7 +220,7 @@ async function visualizeObjectNode( return { schema: { schemaName: tree.type, - allowedTypes, + allowedTypes: concatenateTypes(allowedTypes), isRequired: isRequired?.toString(), }, fields: await visualizeVerboseNodeFields( @@ -241,19 +241,19 @@ async function visualizeMapNode( tree: VerboseTreeNode, nodeSchema: SimpleMapNodeSchema, treeDefinitions: ReadonlyMap, - allowedTypes: string, + allowedTypes: ReadonlySet, isRequired: boolean | undefined, visualizeChildData: VisualizeChildData, ): Promise { - const mapAllowedTypes: Record = {}; + const mapAllowedTypes: Record> = {}; for (const key of Object.keys(tree.fields)) { - mapAllowedTypes[key] = concatenateTypes(nodeSchema.allowedTypes); + mapAllowedTypes[key] = nodeSchema.allowedTypes; } return { schema: { schemaName: tree.type, - allowedTypes, + allowedTypes: concatenateTypes(allowedTypes), isRequired: isRequired?.toString(), }, fields: await visualizeVerboseNodeFields( @@ -279,7 +279,7 @@ async function visualizeMapNode( async function visualizeInternalNodeBySchema( tree: VerboseTreeNode, treeDefinitions: ReadonlyMap, - allowedTypes: string, + allowedTypes: ReadonlySet, isRequired: boolean | undefined, visualizeChildData: VisualizeChildData, ): Promise { @@ -318,9 +318,9 @@ async function visualizeInternalNodeBySchema( throw new TypeError("Invalid array"); } - const arrayAllowedTypes: Record = {}; + const arrayAllowedTypes: Record> = {}; for (let i = 0; i < children.length; i++) { - arrayAllowedTypes[i] = concatenateTypes(schema.allowedTypes); + arrayAllowedTypes[i] = schema.allowedTypes; fields[i] = await visualizeSharedTreeBySchema( children[i], @@ -334,7 +334,7 @@ async function visualizeInternalNodeBySchema( return { schema: { schemaName: tree.type, - allowedTypes, + allowedTypes: concatenateTypes(schema.allowedTypes), isRequired: isRequired?.toString(), }, fields: await visualizeVerboseNodeFields( @@ -372,7 +372,7 @@ async function visualizeInternalNodeBySchema( export async function visualizeSharedTreeBySchema( tree: VerboseTree, treeDefinitions: ReadonlyMap, - allowedTypes: string, + allowedTypes: ReadonlySet, isRequired: boolean | undefined, visualizeChildData: VisualizeChildData, ): Promise { @@ -388,7 +388,7 @@ export async function visualizeSharedTreeBySchema( ? { schema: { schemaName: Tree.schema(tree).identifier, - allowedTypes, + allowedTypes: concatenateTypes(allowedTypes), isRequired: isRequired?.toString(), }, value: await visualizeChildData(tree), From 542d26efad5da297469c329df8b6a98b382b012e Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Thu, 30 Jan 2025 03:39:14 +0000 Subject: [PATCH 23/56] changed to map --- .../tools/devtools/devtools-test-app/src/FluidObject.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/tools/devtools/devtools-test-app/src/FluidObject.ts b/packages/tools/devtools/devtools-test-app/src/FluidObject.ts index b2de9adce2fe..f574b407ef75 100644 --- a/packages/tools/devtools/devtools-test-app/src/FluidObject.ts +++ b/packages/tools/devtools/devtools-test-app/src/FluidObject.ts @@ -182,17 +182,14 @@ export class AppData extends DataObject { completed: builder.boolean, dueDate: builder.optional(builder.string), assignee: builder.optional(builder.string), - colloborators: builder.optional(builder.array(builder.string)), + collaborators: builder.optional(builder.array(builder.string)), }) {} class TodoObject extends builder.object("todo-list", { items: builder.array(TodoItem), }) {} - class TodoCategory extends builder.object("todo-category", { - work: TodoObject, - personal: TodoObject, - }) {} + const TodoCategory = builder.map("todo-category", [TodoObject]); class TodoWorkspace extends builder.object("todo-workspace", { lists: TodoCategory, @@ -212,7 +209,7 @@ export class AppData extends DataObject { completed: false, dueDate: "2048-01-01", assignee: "Kevin", - colloborators: ["Rick"], + collaborators: ["Rick"], }, { title: "Review pull requests", From 279031dee7a424d8030b9b2f2d83ac5570a20adf Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Thu, 30 Jan 2025 03:40:46 +0000 Subject: [PATCH 24/56] fix doc --- .../src/data-visualization/SharedTreeVisualizer.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 44af5196cc93..9b7d227022af 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -356,8 +356,10 @@ async function visualizeInternalNodeBySchema( /** * Creates a visual representation of a SharedTree based on its schema. * @param tree - The {@link VerboseTree} to visualize - * @param treeDefinitions - The schema that defines the structure and types of the tree - * @param allowedTypes - String representing the allowed types for this node + * @param treeDefinitions - Map containing all schema definitions for the entire tree structure. Each definition + * describes the shape and constraints of a particular node type. + * @param allowedTypes - Set of type names that are valid for this specific node position in the tree. This is a + * subset of the types defined in treeDefinitions. * @param isRequired - Whether this field is required in its parent object schema. * Only meaningful for direct children of object nodes. * Undefined for array/map elements since they are always required within their parent. From 9219ae1f2604b156fc9236ccd40d6bba0e590d1b Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Thu, 30 Jan 2025 03:49:27 +0000 Subject: [PATCH 25/56] REVERTIBLE: add undefined case --- .../data-visualization/DefaultVisualizers.ts | 39 +++++++++++---- .../SharedTreeVisualizer.ts | 2 +- .../src/test/DefaultVisualizers.spec.ts | 50 +++++++++++++++++++ 3 files changed, 80 insertions(+), 11 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index f1917403c63b..34c2a3a468c7 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -28,6 +28,7 @@ import { EditType } from "../CommonInterfaces.js"; import type { VisualizeChildData, VisualizeSharedObject } from "./DataVisualization.js"; import { + concatenateTypes, determineNodeKind, toVisualTree, visualizeSharedTreeBySchema, @@ -261,16 +262,6 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( // Root node of the SharedTree's content. const treeView = sharedTree.exportVerbose(); - - if (treeView === undefined) { - return { - fluidObjectId: sharedTree.id, - typeMetadata: "SharedTree", - nodeKind: VisualNodeKind.FluidTreeNode, - children: {}, - }; - } - // Schema of the tree node. const treeDefinitions = sharedTree.exportSimpleSchema().definitions; @@ -283,6 +274,34 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( const isRequired = sharedTree.exportSimpleSchema().kind === FieldKind.Required ? true : false; + if (treeView === undefined) { + return { + fluidObjectId: sharedTree.id, + typeMetadata: "SharedTree", + nodeKind: VisualNodeKind.FluidTreeNode, + tooltipContents: { + schema: { + nodeKind: VisualNodeKind.TreeNode, + children: { + name: { + nodeKind: VisualNodeKind.ValueNode, + value: "undefined tree", // TODO: Change name + }, + allowedTypes: { + nodeKind: VisualNodeKind.ValueNode, + value: concatenateTypes(allowedTypes), + }, + isRequired: { + nodeKind: VisualNodeKind.ValueNode, + value: isRequired?.toString(), + }, + }, + }, + }, + children: {}, + }; + } + // Create a root field visualization that shows the allowed types at the root const visualTreeRepresentation: VisualSharedTreeNode = await visualizeSharedTreeBySchema( treeView, diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 9b7d227022af..0fdab2ea48e4 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -157,7 +157,7 @@ export function toVisualTree(tree: VisualSharedTreeNode): VisualChildNode { /** * Concatenate allowed types for `ObjectNodeStoredSchema` and `MapNodeStoredSchema`. */ -function concatenateTypes(fieldTypes: ReadonlySet): string { +export function concatenateTypes(fieldTypes: ReadonlySet): string { return [...fieldTypes].join(" | "); } diff --git a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts index 3fc0d49dc8b3..2c752ca9ee74 100644 --- a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts +++ b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts @@ -2012,6 +2012,56 @@ describe("DefaultVisualizers unit tests", () => { expect(result).to.deep.equal(expected); }); + it("SharedTree: Empty Root", async () => { + const factory = SharedTree.getFactory(); + const builder = new SchemaFactory("shared-tree-test"); + + const sharedTree = factory.create( + new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), + "test", + ); + + const view = sharedTree.viewWith( + new TreeViewConfiguration({ + schema: builder.optional([builder.number, builder.string]), + }), + ); + view.initialize(undefined); + + const result = await visualizeSharedTree( + sharedTree as unknown as ISharedObject, + visualizeChildData, + ); + + const expected = { + fluidObjectId: sharedTree.id, + typeMetadata: "SharedTree", + nodeKind: VisualNodeKind.FluidTreeNode, + tooltipContents: { + schema: { + nodeKind: VisualNodeKind.TreeNode, + children: { + name: { + nodeKind: VisualNodeKind.ValueNode, + value: "undefined tree", + }, + allowedTypes: { + nodeKind: VisualNodeKind.ValueNode, + value: "com.fluidframework.leaf.number | com.fluidframework.leaf.string", + }, + isRequired: { + nodeKind: VisualNodeKind.ValueNode, + value: "false", + }, + }, + }, + }, + children: {}, + }; + + expect(result).to.deep.equal(expected); + }); + it("Unknown SharedObject", async () => { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const unknownObject = { From 2d305c6014882217c94772c6354dc9ca1bc9438c Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Fri, 31 Jan 2025 19:12:45 +0000 Subject: [PATCH 26/56] omit schema for undefined --- .../data-visualization/DefaultVisualizers.ts | 4 ---- .../SharedTreeVisualizer.ts | 4 +--- .../src/test/DefaultVisualizers.spec.ts | 24 +++++++------------ 3 files changed, 10 insertions(+), 22 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index 34c2a3a468c7..87fe57291c95 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -283,10 +283,6 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( schema: { nodeKind: VisualNodeKind.TreeNode, children: { - name: { - nodeKind: VisualNodeKind.ValueNode, - value: "undefined tree", // TODO: Change name - }, allowedTypes: { nodeKind: VisualNodeKind.ValueNode, value: concatenateTypes(allowedTypes), diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 0fdab2ea48e4..953b182f9b16 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -241,7 +241,6 @@ async function visualizeMapNode( tree: VerboseTreeNode, nodeSchema: SimpleMapNodeSchema, treeDefinitions: ReadonlyMap, - allowedTypes: ReadonlySet, isRequired: boolean | undefined, visualizeChildData: VisualizeChildData, ): Promise { @@ -253,7 +252,7 @@ async function visualizeMapNode( return { schema: { schemaName: tree.type, - allowedTypes: concatenateTypes(allowedTypes), + allowedTypes: concatenateTypes(nodeSchema.allowedTypes), isRequired: isRequired?.toString(), }, fields: await visualizeVerboseNodeFields( @@ -305,7 +304,6 @@ async function visualizeInternalNodeBySchema( tree, schema, treeDefinitions, - allowedTypes, isRequired, visualizeChildData, ); diff --git a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts index 2c752ca9ee74..8a847f032cf1 100644 --- a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts +++ b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts @@ -588,8 +588,7 @@ describe("DefaultVisualizers unit tests", () => { 'shared-tree-test.Array<["com.fluidframework.leaf.number","com.fluidframework.leaf.string"]>', }, allowedTypes: { - value: - 'shared-tree-test.Array<["com.fluidframework.leaf.number","com.fluidframework.leaf.string"]>', + value: "com.fluidframework.leaf.number | com.fluidframework.leaf.string", nodeKind: "ValueNode", }, isRequired: { @@ -1252,8 +1251,7 @@ describe("DefaultVisualizers unit tests", () => { 'shared-tree-test.Array<["shared-tree-test.broccoli-object-schema"]>', }, allowedTypes: { - value: - 'shared-tree-test.Array<["shared-tree-test.broccoli-object-schema"]>', + value: "shared-tree-test.broccoli-object-schema", nodeKind: "ValueNode", }, isRequired: { @@ -1294,8 +1292,7 @@ describe("DefaultVisualizers unit tests", () => { 'shared-tree-test.Array<["shared-tree-test.apple-object-schema"]>', }, allowedTypes: { - value: - 'shared-tree-test.Array<["shared-tree-test.apple-object-schema"]>', + value: "shared-tree-test.apple-object-schema", nodeKind: "ValueNode", }, isRequired: { @@ -1482,8 +1479,7 @@ describe("DefaultVisualizers unit tests", () => { 'shared-tree-test.Array<["shared-tree-test.broccoli-object-schema"]>', }, allowedTypes: { - value: - 'shared-tree-test.Array<["shared-tree-test.broccoli-object-schema"]>', + value: "shared-tree-test.broccoli-object-schema", nodeKind: "ValueNode", }, isRequired: { @@ -1524,8 +1520,7 @@ describe("DefaultVisualizers unit tests", () => { 'shared-tree-test.Array<["shared-tree-test.apple-object-schema"]>', }, allowedTypes: { - value: - 'shared-tree-test.Array<["shared-tree-test.apple-object-schema"]>', + value: "shared-tree-test.apple-object-schema", nodeKind: "ValueNode", }, isRequired: { @@ -1613,7 +1608,7 @@ describe("DefaultVisualizers unit tests", () => { value: 'shared-tree-test.Array<["shared-tree-test.foo-item"]>', }, allowedTypes: { - value: 'shared-tree-test.Array<["shared-tree-test.foo-item"]>', + value: "shared-tree-test.foo-item", nodeKind: "ValueNode", }, isRequired: { @@ -1776,7 +1771,7 @@ describe("DefaultVisualizers unit tests", () => { value: 'shared-tree-test.Array<["shared-tree-test.dalgona-object"]>', }, allowedTypes: { - value: 'shared-tree-test.Array<["shared-tree-test.dalgona-object"]>', + value: "shared-tree-test.dalgona-object", nodeKind: "ValueNode", }, isRequired: { @@ -1841,8 +1836,7 @@ describe("DefaultVisualizers unit tests", () => { 'shared-tree-test.Array<["com.fluidframework.leaf.number","com.fluidframework.leaf.string"]>', }, allowedTypes: { - value: - 'shared-tree-test.Array<["com.fluidframework.leaf.number","com.fluidframework.leaf.string"]>', + value: "com.fluidframework.leaf.number | com.fluidframework.leaf.string", nodeKind: "ValueNode", }, isRequired: { @@ -1974,7 +1968,7 @@ describe("DefaultVisualizers unit tests", () => { }, allowedTypes: { value: - 'shared-tree-test.Map<["com.fluidframework.leaf.handle","com.fluidframework.leaf.number","com.fluidframework.leaf.string","shared-tree-test.map-object"]>', + "com.fluidframework.leaf.string | com.fluidframework.leaf.number | com.fluidframework.leaf.handle | shared-tree-test.map-object", nodeKind: "ValueNode", }, isRequired: { From a284f3a2ad3ae0e38dafefba9c7a35bdf9c5aec8 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Fri, 31 Jan 2025 23:54:30 +0000 Subject: [PATCH 27/56] change fix --- .../SharedTreeVisualizer.ts | 34 +++++++++++-------- .../VisualSharedTreeTypes.ts | 2 +- .../src/test/DefaultVisualizers.spec.ts | 6 +--- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 953b182f9b16..a0cd511fbba7 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -191,17 +191,23 @@ async function visualizeVerboseNodeFields( */ function storeObjectAllowedTypes(schema: SimpleObjectNodeSchema): { allowedTypes: Record>; - requirements: Record; + elementIsRequired: Record; } { const allowedTypes: Record> = {}; - const requirements: Record = {}; + + /** + * Maps each field or node identifier to a boolean indicating + * whether it is required (true) or optional (false). + */ + const elementIsRequired: Record = {}; for (const [fieldKey, treeFieldSimpleSchema] of Object.entries(schema.fields)) { allowedTypes[fieldKey] = treeFieldSimpleSchema.allowedTypes; - requirements[fieldKey] = treeFieldSimpleSchema.kind === FieldKind.Required ? true : false; + elementIsRequired[fieldKey] = + treeFieldSimpleSchema.kind === FieldKind.Required ? true : false; } - return { allowedTypes, requirements }; + return { allowedTypes, elementIsRequired }; } /** @@ -214,7 +220,7 @@ async function visualizeObjectNode( isRequired: boolean | undefined, visualizeChildData: VisualizeChildData, ): Promise { - const { allowedTypes: objectAllowedTypes, requirements: objectRequirements } = + const { allowedTypes: objectNodeAllowedTypes, elementIsRequired: objectNodeIsRequired } = storeObjectAllowedTypes(treeDefinitions.get(tree.type) as SimpleObjectNodeSchema); return { @@ -226,8 +232,8 @@ async function visualizeObjectNode( fields: await visualizeVerboseNodeFields( tree.fields, treeDefinitions, - objectAllowedTypes, - objectRequirements, + objectNodeAllowedTypes, + objectNodeIsRequired, visualizeChildData, ), kind: VisualSharedTreeNodeKind.InternalNode, @@ -244,10 +250,10 @@ async function visualizeMapNode( isRequired: boolean | undefined, visualizeChildData: VisualizeChildData, ): Promise { - const mapAllowedTypes: Record> = {}; + const mapNodeAllowedTypes: Record> = {}; for (const key of Object.keys(tree.fields)) { - mapAllowedTypes[key] = nodeSchema.allowedTypes; + mapNodeAllowedTypes[key] = nodeSchema.allowedTypes; } return { schema: { @@ -258,7 +264,7 @@ async function visualizeMapNode( fields: await visualizeVerboseNodeFields( tree.fields, treeDefinitions, - mapAllowedTypes, + mapNodeAllowedTypes, undefined, visualizeChildData, ), @@ -316,14 +322,14 @@ async function visualizeInternalNodeBySchema( throw new TypeError("Invalid array"); } - const arrayAllowedTypes: Record> = {}; + const arrayNodeAllowedTypes: Record> = {}; for (let i = 0; i < children.length; i++) { - arrayAllowedTypes[i] = schema.allowedTypes; + arrayNodeAllowedTypes[i] = schema.allowedTypes; fields[i] = await visualizeSharedTreeBySchema( children[i], treeDefinitions, - arrayAllowedTypes[i], + arrayNodeAllowedTypes[i], undefined, visualizeChildData, ); @@ -338,7 +344,7 @@ async function visualizeInternalNodeBySchema( fields: await visualizeVerboseNodeFields( tree.fields, treeDefinitions, - arrayAllowedTypes, + arrayNodeAllowedTypes, undefined, visualizeChildData, ), diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts b/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts index cd4c15720f64..0be92e059c93 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts @@ -36,7 +36,7 @@ export interface SharedTreeSchemaNode { allowedTypes?: string | Record; /** - * If the field or node is required (e.g., required, optional) or not. + * If the field or node is required {@link FieldKind.Required} or not {@link FieldKind.Optional} */ isRequired?: string; } diff --git a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts index 8a847f032cf1..675db071f25d 100644 --- a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts +++ b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts @@ -733,7 +733,7 @@ describe("DefaultVisualizers unit tests", () => { }, allowedTypes: { value: - 'shared-tree-test.Map<["com.fluidframework.leaf.handle","com.fluidframework.leaf.number","com.fluidframework.leaf.string"]>', + "com.fluidframework.leaf.string | com.fluidframework.leaf.number | com.fluidframework.leaf.handle", nodeKind: "ValueNode", }, isRequired: { @@ -2035,10 +2035,6 @@ describe("DefaultVisualizers unit tests", () => { schema: { nodeKind: VisualNodeKind.TreeNode, children: { - name: { - nodeKind: VisualNodeKind.ValueNode, - value: "undefined tree", - }, allowedTypes: { nodeKind: VisualNodeKind.ValueNode, value: "com.fluidframework.leaf.number | com.fluidframework.leaf.string", From f44bd59653634db031234fd73aea22d60d1eb169 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Tue, 4 Feb 2025 10:15:30 -0800 Subject: [PATCH 28/56] Update packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts Co-authored-by: Joshua Smithrud <54606601+Josmithr@users.noreply.github.com> --- .../devtools-core/src/data-visualization/DefaultVisualizers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index 87fe57291c95..cd0d3284a8bb 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -262,7 +262,7 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( // Root node of the SharedTree's content. const treeView = sharedTree.exportVerbose(); - // Schema of the tree node. + // All schema definitions for the SharedTree. const treeDefinitions = sharedTree.exportSimpleSchema().definitions; /** From 7d389dc757ef3fd21a3c481b30176e604010028c Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Tue, 4 Feb 2025 10:15:44 -0800 Subject: [PATCH 29/56] Update packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts Co-authored-by: Joshua Smithrud <54606601+Josmithr@users.noreply.github.com> --- .../src/data-visualization/SharedTreeVisualizer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index a0cd511fbba7..be6341918f3d 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -165,7 +165,7 @@ export function concatenateTypes(fieldTypes: ReadonlySet): string { * Returns the schema & fields of the node. */ async function visualizeVerboseNodeFields( - treeFields: VerboseTree[] | Record, + treeFields: readonly VerboseTree[] | Record, treeDefinitions: ReadonlyMap, allowedTypes: Record>, requirements: Record | undefined, From 89d0365e4a6324da7d47ef4bd12fd598cf38c9c5 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Tue, 4 Feb 2025 10:22:53 -0800 Subject: [PATCH 30/56] Update packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts Co-authored-by: Joshua Smithrud <54606601+Josmithr@users.noreply.github.com> --- .../src/data-visualization/SharedTreeVisualizer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index be6341918f3d..05222f6a0486 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -273,7 +273,7 @@ async function visualizeMapNode( } /** - * Helper function to create the visual representation of non-leaf SharedTree nodes. + * Creates the visual representation of non-leaf SharedTree nodes. * Processes internal tree nodes based on their schema type (e.g., ObjectNodeStoredSchema, MapNodeStoredSchema, ArrayNodeStoredSchema), * producing the visual representation for each type. * From 035c070eb0da14b6c48052fcca2b5f680f0a7bcf Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Tue, 4 Feb 2025 19:43:33 +0000 Subject: [PATCH 31/56] add doc & couple the parameters & add readonly --- .../data-visualization/DefaultVisualizers.ts | 3 +- .../SharedTreeVisualizer.ts | 51 +++++++++++++------ .../VisualSharedTreeTypes.ts | 11 ++-- 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index cd0d3284a8bb..f5b84d662b7b 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -302,8 +302,7 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( const visualTreeRepresentation: VisualSharedTreeNode = await visualizeSharedTreeBySchema( treeView, treeDefinitions, - allowedTypes, - isRequired, + { allowedTypes, isRequired }, visualizeChildData, ); diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 05222f6a0486..99b3b4ecff05 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -162,7 +162,32 @@ export function concatenateTypes(fieldTypes: ReadonlySet): string { } /** - * Returns the schema & fields of the node. + * Properties that describe schema constraints for a field in the tree + */ +interface FieldSchemaProperties { + /** + * Set of type names that are valid for this specific node position in the tree. + * This is a subset of the types defined in treeDefinitions. + */ + allowedTypes: ReadonlySet; + + /** + * Whether this field is required in its parent object schema. + * Only meaningful for direct children of object nodes. + * Undefined for array/map elements since they are always required within their parent. + */ + isRequired?: boolean; +} + +/** + * Processes and visualizes the fields of a verbose tree node. + * + * @param treeFields - The fields of the tree node to visualize. Can be either an array of VerboseTree (for array nodes) or a Record of field names to VerboseTree (for object/map nodes). + * @param treeDefinitions - Map containing all schema definitions for the entire tree structure. Each definition describes the shape and constraints of a particular node type. + * @param allowedTypes - Record mapping field names to their allowed type sets. Specifies which types are valid for each field position in the tree. + * @param requirements - Optional record mapping field names to boolean values indicating whether each field is required (true) or optional (false). Only meaningful for object node fields. + * + * @returns A record mapping field names/indices to their visual tree representations. */ async function visualizeVerboseNodeFields( treeFields: readonly VerboseTree[] | Record, @@ -177,8 +202,10 @@ async function visualizeVerboseNodeFields( fields[fieldKey] = await visualizeSharedTreeBySchema( childField, treeDefinitions, - allowedTypes[fieldKey], - requirements === undefined ? requirements : requirements[fieldKey], + { + allowedTypes: allowedTypes[fieldKey], + isRequired: requirements === undefined ? undefined : requirements[fieldKey], + }, visualizeChildData, ); } @@ -216,8 +243,7 @@ function storeObjectAllowedTypes(schema: SimpleObjectNodeSchema): { async function visualizeObjectNode( tree: VerboseTreeNode, treeDefinitions: ReadonlyMap, - allowedTypes: ReadonlySet, - isRequired: boolean | undefined, + { allowedTypes, isRequired }: FieldSchemaProperties, visualizeChildData: VisualizeChildData, ): Promise { const { allowedTypes: objectNodeAllowedTypes, elementIsRequired: objectNodeIsRequired } = @@ -284,8 +310,7 @@ async function visualizeMapNode( async function visualizeInternalNodeBySchema( tree: VerboseTreeNode, treeDefinitions: ReadonlyMap, - allowedTypes: ReadonlySet, - isRequired: boolean | undefined, + { allowedTypes, isRequired }: FieldSchemaProperties, visualizeChildData: VisualizeChildData, ): Promise { const schema = treeDefinitions.get(tree.type); @@ -299,8 +324,7 @@ async function visualizeInternalNodeBySchema( const objectVisualized = visualizeObjectNode( tree, treeDefinitions, - allowedTypes, - isRequired, + { allowedTypes, isRequired }, visualizeChildData, ); return objectVisualized; @@ -329,8 +353,7 @@ async function visualizeInternalNodeBySchema( fields[i] = await visualizeSharedTreeBySchema( children[i], treeDefinitions, - arrayNodeAllowedTypes[i], - undefined, + { allowedTypes: arrayNodeAllowedTypes[i] }, visualizeChildData, ); } @@ -378,8 +401,7 @@ async function visualizeInternalNodeBySchema( export async function visualizeSharedTreeBySchema( tree: VerboseTree, treeDefinitions: ReadonlyMap, - allowedTypes: ReadonlySet, - isRequired: boolean | undefined, + { allowedTypes, isRequired }: FieldSchemaProperties, visualizeChildData: VisualizeChildData, ): Promise { const schemaFactory = new SchemaFactory(undefined); @@ -403,8 +425,7 @@ export async function visualizeSharedTreeBySchema( : visualizeInternalNodeBySchema( tree, treeDefinitions, - allowedTypes, - isRequired, + { allowedTypes, isRequired }, visualizeChildData, ); } diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts b/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts index 0be92e059c93..0568d214b83f 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts @@ -27,18 +27,21 @@ export interface SharedTreeSchemaNode { /** * Name of the SharedTree schema. */ - schemaName: string; + readonly schemaName: string; /** * Types allowed (e.g., string, number, boolean, handle & etc.) inside the node. * - InternalNode: `Record`. */ - allowedTypes?: string | Record; + readonly allowedTypes?: string | Record; /** - * If the field or node is required {@link FieldKind.Required} or not {@link FieldKind.Optional} + * If the field or node is required or optional. + * - When {@link FieldKind.Required}: The field must be present + * - When {@link FieldKind.Optional}: The field may be omitted + * - When undefined: Treated the same as {@link FieldKind.Optional} */ - isRequired?: string; + readonly isRequired?: string; } /** From fdfa737eb6c24a6d6a0b96314e0a2bd8ff83b9e4 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Tue, 4 Feb 2025 19:57:38 +0000 Subject: [PATCH 32/56] change --- .../src/data-visualization/SharedTreeVisualizer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 99b3b4ecff05..1c1cdc506e0d 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -347,11 +347,11 @@ async function visualizeInternalNodeBySchema( } const arrayNodeAllowedTypes: Record> = {}; - for (let i = 0; i < children.length; i++) { + for (const [i, child] of children.entries()) { arrayNodeAllowedTypes[i] = schema.allowedTypes; fields[i] = await visualizeSharedTreeBySchema( - children[i], + child, treeDefinitions, { allowedTypes: arrayNodeAllowedTypes[i] }, visualizeChildData, From aeee9a2919d8c53d09aa01e0d7e1f1b6356838d7 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Tue, 4 Feb 2025 20:48:18 +0000 Subject: [PATCH 33/56] change type --- .../src/data-visualization/SharedTreeVisualizer.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 1c1cdc506e0d..83e99165587a 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -169,7 +169,7 @@ interface FieldSchemaProperties { * Set of type names that are valid for this specific node position in the tree. * This is a subset of the types defined in treeDefinitions. */ - allowedTypes: ReadonlySet; + allowedTypes?: ReadonlySet; /** * Whether this field is required in its parent object schema. @@ -252,7 +252,7 @@ async function visualizeObjectNode( return { schema: { schemaName: tree.type, - allowedTypes: concatenateTypes(allowedTypes), + allowedTypes: concatenateTypes(allowedTypes ?? new Set()), isRequired: isRequired?.toString(), }, fields: await visualizeVerboseNodeFields( @@ -416,7 +416,7 @@ export async function visualizeSharedTreeBySchema( ? { schema: { schemaName: Tree.schema(tree).identifier, - allowedTypes: concatenateTypes(allowedTypes), + allowedTypes: concatenateTypes(allowedTypes ?? new Set()), isRequired: isRequired?.toString(), }, value: await visualizeChildData(tree), From dfc6f0079363edc6ea43c58c3fec411d019ebae1 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Tue, 4 Feb 2025 20:57:12 +0000 Subject: [PATCH 34/56] change param --- .../SharedTreeVisualizer.ts | 73 ++++++++----------- 1 file changed, 29 insertions(+), 44 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 83e99165587a..3c94e2c0b956 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -184,16 +184,15 @@ interface FieldSchemaProperties { * * @param treeFields - The fields of the tree node to visualize. Can be either an array of VerboseTree (for array nodes) or a Record of field names to VerboseTree (for object/map nodes). * @param treeDefinitions - Map containing all schema definitions for the entire tree structure. Each definition describes the shape and constraints of a particular node type. - * @param allowedTypes - Record mapping field names to their allowed type sets. Specifies which types are valid for each field position in the tree. - * @param requirements - Optional record mapping field names to boolean values indicating whether each field is required (true) or optional (false). Only meaningful for object node fields. + * @param fieldSchemaProperties - Record mapping field names to their FieldSchemaProperties. Specifies which types are valid for each field position in the tree. + * @param visualizeChildData - Callback function to visualize child node data * * @returns A record mapping field names/indices to their visual tree representations. */ async function visualizeVerboseNodeFields( treeFields: readonly VerboseTree[] | Record, treeDefinitions: ReadonlyMap, - allowedTypes: Record>, - requirements: Record | undefined, + fieldSchemaProperties: Record, visualizeChildData: VisualizeChildData, ): Promise> { const fields: Record = {}; @@ -202,10 +201,7 @@ async function visualizeVerboseNodeFields( fields[fieldKey] = await visualizeSharedTreeBySchema( childField, treeDefinitions, - { - allowedTypes: allowedTypes[fieldKey], - isRequired: requirements === undefined ? undefined : requirements[fieldKey], - }, + fieldSchemaProperties[fieldKey] ?? {}, visualizeChildData, ); } @@ -216,25 +212,19 @@ async function visualizeVerboseNodeFields( /** * Extracts and stores allowed types & kind from {@link SimpleFieldSchema}. */ -function storeObjectAllowedTypes(schema: SimpleObjectNodeSchema): { - allowedTypes: Record>; - elementIsRequired: Record; -} { - const allowedTypes: Record> = {}; - - /** - * Maps each field or node identifier to a boolean indicating - * whether it is required (true) or optional (false). - */ - const elementIsRequired: Record = {}; +function storeObjectAllowedTypes( + schema: SimpleObjectNodeSchema, +): Record { + const fieldSchemaProperties: Record = {}; for (const [fieldKey, treeFieldSimpleSchema] of Object.entries(schema.fields)) { - allowedTypes[fieldKey] = treeFieldSimpleSchema.allowedTypes; - elementIsRequired[fieldKey] = - treeFieldSimpleSchema.kind === FieldKind.Required ? true : false; + fieldSchemaProperties[fieldKey] = { + allowedTypes: treeFieldSimpleSchema.allowedTypes, + isRequired: treeFieldSimpleSchema.kind === FieldKind.Required, + }; } - return { allowedTypes, elementIsRequired }; + return fieldSchemaProperties; } /** @@ -246,8 +236,9 @@ async function visualizeObjectNode( { allowedTypes, isRequired }: FieldSchemaProperties, visualizeChildData: VisualizeChildData, ): Promise { - const { allowedTypes: objectNodeAllowedTypes, elementIsRequired: objectNodeIsRequired } = - storeObjectAllowedTypes(treeDefinitions.get(tree.type) as SimpleObjectNodeSchema); + const fieldSchemaProperties = storeObjectAllowedTypes( + treeDefinitions.get(tree.type) as SimpleObjectNodeSchema, + ); return { schema: { @@ -258,8 +249,7 @@ async function visualizeObjectNode( fields: await visualizeVerboseNodeFields( tree.fields, treeDefinitions, - objectNodeAllowedTypes, - objectNodeIsRequired, + fieldSchemaProperties, visualizeChildData, ), kind: VisualSharedTreeNodeKind.InternalNode, @@ -276,11 +266,14 @@ async function visualizeMapNode( isRequired: boolean | undefined, visualizeChildData: VisualizeChildData, ): Promise { - const mapNodeAllowedTypes: Record> = {}; + const fieldSchemaProperties: Record = {}; for (const key of Object.keys(tree.fields)) { - mapNodeAllowedTypes[key] = nodeSchema.allowedTypes; + fieldSchemaProperties[key] = { + allowedTypes: nodeSchema.allowedTypes, + }; } + return { schema: { schemaName: tree.type, @@ -290,8 +283,7 @@ async function visualizeMapNode( fields: await visualizeVerboseNodeFields( tree.fields, treeDefinitions, - mapNodeAllowedTypes, - undefined, + fieldSchemaProperties, visualizeChildData, ), kind: VisualSharedTreeNodeKind.InternalNode, @@ -340,22 +332,16 @@ async function visualizeInternalNodeBySchema( return mapVisualized; } case NodeKind.Array: { - const fields: Record = {}; + const fieldSchemaProperties: Record = {}; const children = tree.fields; if (!Array.isArray(children)) { throw new TypeError("Invalid array"); } - const arrayNodeAllowedTypes: Record> = {}; - for (const [i, child] of children.entries()) { - arrayNodeAllowedTypes[i] = schema.allowedTypes; - - fields[i] = await visualizeSharedTreeBySchema( - child, - treeDefinitions, - { allowedTypes: arrayNodeAllowedTypes[i] }, - visualizeChildData, - ); + for (const [i, _] of children.entries()) { + fieldSchemaProperties[i] = { + allowedTypes: schema.allowedTypes, + }; } return { @@ -367,8 +353,7 @@ async function visualizeInternalNodeBySchema( fields: await visualizeVerboseNodeFields( tree.fields, treeDefinitions, - arrayNodeAllowedTypes, - undefined, + fieldSchemaProperties, visualizeChildData, ), kind: VisualSharedTreeNodeKind.InternalNode, From 0f70aae1cf4c3e1d613ea7feb6c8c322e11d30bd Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Tue, 4 Feb 2025 21:20:53 +0000 Subject: [PATCH 35/56] Revert "change param" This reverts commit dfc6f0079363edc6ea43c58c3fec411d019ebae1. --- .../SharedTreeVisualizer.ts | 73 +++++++++++-------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 3c94e2c0b956..83e99165587a 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -184,15 +184,16 @@ interface FieldSchemaProperties { * * @param treeFields - The fields of the tree node to visualize. Can be either an array of VerboseTree (for array nodes) or a Record of field names to VerboseTree (for object/map nodes). * @param treeDefinitions - Map containing all schema definitions for the entire tree structure. Each definition describes the shape and constraints of a particular node type. - * @param fieldSchemaProperties - Record mapping field names to their FieldSchemaProperties. Specifies which types are valid for each field position in the tree. - * @param visualizeChildData - Callback function to visualize child node data + * @param allowedTypes - Record mapping field names to their allowed type sets. Specifies which types are valid for each field position in the tree. + * @param requirements - Optional record mapping field names to boolean values indicating whether each field is required (true) or optional (false). Only meaningful for object node fields. * * @returns A record mapping field names/indices to their visual tree representations. */ async function visualizeVerboseNodeFields( treeFields: readonly VerboseTree[] | Record, treeDefinitions: ReadonlyMap, - fieldSchemaProperties: Record, + allowedTypes: Record>, + requirements: Record | undefined, visualizeChildData: VisualizeChildData, ): Promise> { const fields: Record = {}; @@ -201,7 +202,10 @@ async function visualizeVerboseNodeFields( fields[fieldKey] = await visualizeSharedTreeBySchema( childField, treeDefinitions, - fieldSchemaProperties[fieldKey] ?? {}, + { + allowedTypes: allowedTypes[fieldKey], + isRequired: requirements === undefined ? undefined : requirements[fieldKey], + }, visualizeChildData, ); } @@ -212,19 +216,25 @@ async function visualizeVerboseNodeFields( /** * Extracts and stores allowed types & kind from {@link SimpleFieldSchema}. */ -function storeObjectAllowedTypes( - schema: SimpleObjectNodeSchema, -): Record { - const fieldSchemaProperties: Record = {}; +function storeObjectAllowedTypes(schema: SimpleObjectNodeSchema): { + allowedTypes: Record>; + elementIsRequired: Record; +} { + const allowedTypes: Record> = {}; + + /** + * Maps each field or node identifier to a boolean indicating + * whether it is required (true) or optional (false). + */ + const elementIsRequired: Record = {}; for (const [fieldKey, treeFieldSimpleSchema] of Object.entries(schema.fields)) { - fieldSchemaProperties[fieldKey] = { - allowedTypes: treeFieldSimpleSchema.allowedTypes, - isRequired: treeFieldSimpleSchema.kind === FieldKind.Required, - }; + allowedTypes[fieldKey] = treeFieldSimpleSchema.allowedTypes; + elementIsRequired[fieldKey] = + treeFieldSimpleSchema.kind === FieldKind.Required ? true : false; } - return fieldSchemaProperties; + return { allowedTypes, elementIsRequired }; } /** @@ -236,9 +246,8 @@ async function visualizeObjectNode( { allowedTypes, isRequired }: FieldSchemaProperties, visualizeChildData: VisualizeChildData, ): Promise { - const fieldSchemaProperties = storeObjectAllowedTypes( - treeDefinitions.get(tree.type) as SimpleObjectNodeSchema, - ); + const { allowedTypes: objectNodeAllowedTypes, elementIsRequired: objectNodeIsRequired } = + storeObjectAllowedTypes(treeDefinitions.get(tree.type) as SimpleObjectNodeSchema); return { schema: { @@ -249,7 +258,8 @@ async function visualizeObjectNode( fields: await visualizeVerboseNodeFields( tree.fields, treeDefinitions, - fieldSchemaProperties, + objectNodeAllowedTypes, + objectNodeIsRequired, visualizeChildData, ), kind: VisualSharedTreeNodeKind.InternalNode, @@ -266,14 +276,11 @@ async function visualizeMapNode( isRequired: boolean | undefined, visualizeChildData: VisualizeChildData, ): Promise { - const fieldSchemaProperties: Record = {}; + const mapNodeAllowedTypes: Record> = {}; for (const key of Object.keys(tree.fields)) { - fieldSchemaProperties[key] = { - allowedTypes: nodeSchema.allowedTypes, - }; + mapNodeAllowedTypes[key] = nodeSchema.allowedTypes; } - return { schema: { schemaName: tree.type, @@ -283,7 +290,8 @@ async function visualizeMapNode( fields: await visualizeVerboseNodeFields( tree.fields, treeDefinitions, - fieldSchemaProperties, + mapNodeAllowedTypes, + undefined, visualizeChildData, ), kind: VisualSharedTreeNodeKind.InternalNode, @@ -332,16 +340,22 @@ async function visualizeInternalNodeBySchema( return mapVisualized; } case NodeKind.Array: { - const fieldSchemaProperties: Record = {}; + const fields: Record = {}; const children = tree.fields; if (!Array.isArray(children)) { throw new TypeError("Invalid array"); } - for (const [i, _] of children.entries()) { - fieldSchemaProperties[i] = { - allowedTypes: schema.allowedTypes, - }; + const arrayNodeAllowedTypes: Record> = {}; + for (const [i, child] of children.entries()) { + arrayNodeAllowedTypes[i] = schema.allowedTypes; + + fields[i] = await visualizeSharedTreeBySchema( + child, + treeDefinitions, + { allowedTypes: arrayNodeAllowedTypes[i] }, + visualizeChildData, + ); } return { @@ -353,7 +367,8 @@ async function visualizeInternalNodeBySchema( fields: await visualizeVerboseNodeFields( tree.fields, treeDefinitions, - fieldSchemaProperties, + arrayNodeAllowedTypes, + undefined, visualizeChildData, ), kind: VisualSharedTreeNodeKind.InternalNode, From 3fa33c2831d6127bb3572bc8cd62f41b00995cff Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Fri, 7 Feb 2025 14:01:24 -0800 Subject: [PATCH 36/56] Update packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts Co-authored-by: Alex Villarreal <716334+alexvy86@users.noreply.github.com> --- .../src/data-visualization/VisualSharedTreeTypes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts b/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts index 0568d214b83f..7e5d8a5b283b 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts @@ -36,7 +36,7 @@ export interface SharedTreeSchemaNode { readonly allowedTypes?: string | Record; /** - * If the field or node is required or optional. + * If the field is required or optional. * - When {@link FieldKind.Required}: The field must be present * - When {@link FieldKind.Optional}: The field may be omitted * - When undefined: Treated the same as {@link FieldKind.Optional} From 809f3fefb26b4e5df4de77a2020dfba87fd15034 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Fri, 7 Feb 2025 14:01:37 -0800 Subject: [PATCH 37/56] Update packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts Co-authored-by: Alex Villarreal <716334+alexvy86@users.noreply.github.com> --- .../devtools-core/src/data-visualization/DefaultVisualizers.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index 07a058a1f18d..c26b21fd676e 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -271,8 +271,7 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( * each node's allowed types are computed at the parent field level. */ const allowedTypes = sharedTree.exportSimpleSchema().allowedTypes; - const isRequired = - sharedTree.exportSimpleSchema().kind === FieldKind.Required ? true : false; + const isRequired = sharedTree.exportSimpleSchema().kind === FieldKind.Required; if (treeView === undefined) { return { From 472aa646983a5bea0bcc1cc1320f03fbec2d2b8d Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Fri, 7 Feb 2025 14:01:48 -0800 Subject: [PATCH 38/56] Update packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts Co-authored-by: Alex Villarreal <716334+alexvy86@users.noreply.github.com> --- .../src/data-visualization/SharedTreeVisualizer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 83e99165587a..d29049e53ca9 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -184,7 +184,7 @@ interface FieldSchemaProperties { * * @param treeFields - The fields of the tree node to visualize. Can be either an array of VerboseTree (for array nodes) or a Record of field names to VerboseTree (for object/map nodes). * @param treeDefinitions - Map containing all schema definitions for the entire tree structure. Each definition describes the shape and constraints of a particular node type. - * @param allowedTypes - Record mapping field names to their allowed type sets. Specifies which types are valid for each field position in the tree. + * @param allowedTypes - Record mapping field names to their allowed type sets. Specifies which types are valid for each field in the tree. * @param requirements - Optional record mapping field names to boolean values indicating whether each field is required (true) or optional (false). Only meaningful for object node fields. * * @returns A record mapping field names/indices to their visual tree representations. From 8a650d126a722ef249ecea588f6870e1378d64b1 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Fri, 7 Feb 2025 22:34:05 +0000 Subject: [PATCH 39/56] fix nit --- .../src/data-visualization/DefaultVisualizers.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index 07a058a1f18d..c360c892f3a1 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -263,16 +263,16 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( // Root node of the SharedTree's content. const treeView = sharedTree.exportVerbose(); // All schema definitions for the SharedTree. - const treeDefinitions = sharedTree.exportSimpleSchema().definitions; + const treeSimpleSchema = sharedTree.exportSimpleSchema(); + const treeDefinitions = treeSimpleSchema.definitions; /** * {@link visualizeSharedTreeBySchema} passes `allowedTypes` into co-recursive functions while constructing the visual representation. * Since the {@link SimpleTreeSchema.allowedTypes} of each children node is only accessible at the parent field level, * each node's allowed types are computed at the parent field level. */ - const allowedTypes = sharedTree.exportSimpleSchema().allowedTypes; - const isRequired = - sharedTree.exportSimpleSchema().kind === FieldKind.Required ? true : false; + const allowedTypes = treeSimpleSchema.allowedTypes; + const isRequired = treeSimpleSchema.kind === FieldKind.Required; if (treeView === undefined) { return { @@ -289,7 +289,7 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( }, isRequired: { nodeKind: VisualNodeKind.ValueNode, - value: isRequired?.toString(), + value: isRequired.toString(), }, }, }, @@ -309,6 +309,7 @@ export const visualizeSharedTree: VisualizeSharedObject = async ( // Maps the `visualTreeRepresentation` in the format compatible to {@link visualizeChildData} function. const visualTree = toVisualTree(visualTreeRepresentation); + // TODO: Validate the type casting. const visualTreeResult: FluidObjectNode = { ...visualTree, fluidObjectId: sharedTree.id, From 8fb3ca9c21869f9dabe977cde83ef154d29ea659 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Fri, 7 Feb 2025 23:07:07 +0000 Subject: [PATCH 40/56] change tree api --- .../devtools-core/src/data-visualization/DefaultVisualizers.ts | 3 +-- .../src/data-visualization/SharedTreeVisualizer.ts | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts index b6d624cec1d9..fe93cfff457d 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/DefaultVisualizers.ts @@ -20,9 +20,8 @@ import { import { SharedMatrix } from "@fluidframework/matrix/internal"; import { SharedString } from "@fluidframework/sequence/internal"; import type { ISharedObject } from "@fluidframework/shared-object-base/internal"; -import { FieldKind, SharedTree } from "@fluidframework/tree/internal"; import type { ITreeInternal, IChannelView } from "@fluidframework/tree/internal"; -import { SharedTree } from "@fluidframework/tree/internal"; +import { FieldKind, SharedTree } from "@fluidframework/tree/internal"; import { EditType } from "../CommonInterfaces.js"; diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 2fe10c5bf80f..e5e39da32890 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -404,8 +404,6 @@ export async function visualizeSharedTreeBySchema( { allowedTypes, isRequired }: FieldSchemaProperties, visualizeChildData: VisualizeChildData, ): Promise { - const schemaFactory = new SchemaFactory(undefined); - return Tree.is(tree, SchemaFactory.leaves) ? { schema: { From 74130663ec1f7442461e9414ea515442171a8ebf Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Sat, 8 Feb 2025 00:25:41 +0000 Subject: [PATCH 41/56] change name --- .../src/data-visualization/SharedTreeVisualizer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index e5e39da32890..7eeb6b468c23 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -216,7 +216,7 @@ async function visualizeVerboseNodeFields( /** * Extracts and stores allowed types & kind from {@link SimpleFieldSchema}. */ -function storeObjectAllowedTypes(schema: SimpleObjectNodeSchema): { +function getFieldTooltipProperties(schema: SimpleObjectNodeSchema): { allowedTypes: Record>; elementIsRequired: Record; } { @@ -247,7 +247,7 @@ async function visualizeObjectNode( visualizeChildData: VisualizeChildData, ): Promise { const { allowedTypes: objectNodeAllowedTypes, elementIsRequired: objectNodeIsRequired } = - storeObjectAllowedTypes(treeDefinitions.get(tree.type) as SimpleObjectNodeSchema); + getFieldTooltipProperties(treeDefinitions.get(tree.type) as SimpleObjectNodeSchema); return { schema: { From 3ff216d7759bc6a6b2cef41b2cdb08e3f56bcd8f Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Mon, 10 Feb 2025 22:58:20 +0000 Subject: [PATCH 42/56] nit changes --- .../SharedTreeVisualizer.ts | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 7eeb6b468c23..c24899166993 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -98,7 +98,7 @@ function createToolTipContents(schema: SharedTreeSchemaNode): VisualTreeNode { } /** - * Converts the visual representation from {@link visualizeInternalNodeBySchema} to a visual tree compatible with the devtools-view. + * Converts the visual representation from {@link visualizeNodeBySchema} to a visual tree compatible with the devtools-view. * @param tree - the visual representation of the SharedTree. * @returns - the visual representation of type {@link VisualChildNode} */ @@ -169,14 +169,14 @@ interface FieldSchemaProperties { * Set of type names that are valid for this specific node position in the tree. * This is a subset of the types defined in treeDefinitions. */ - allowedTypes?: ReadonlySet; + allowedTypes: ReadonlySet | undefined; /** * Whether this field is required in its parent object schema. * Only meaningful for direct children of object nodes. * Undefined for array/map elements since they are always required within their parent. */ - isRequired?: boolean; + isRequired: boolean | undefined; } /** @@ -218,7 +218,7 @@ async function visualizeVerboseNodeFields( */ function getFieldTooltipProperties(schema: SimpleObjectNodeSchema): { allowedTypes: Record>; - elementIsRequired: Record; + isRequired: Record; } { const allowedTypes: Record> = {}; @@ -226,15 +226,14 @@ function getFieldTooltipProperties(schema: SimpleObjectNodeSchema): { * Maps each field or node identifier to a boolean indicating * whether it is required (true) or optional (false). */ - const elementIsRequired: Record = {}; + const isRequired: Record = {}; for (const [fieldKey, treeFieldSimpleSchema] of Object.entries(schema.fields)) { allowedTypes[fieldKey] = treeFieldSimpleSchema.allowedTypes; - elementIsRequired[fieldKey] = - treeFieldSimpleSchema.kind === FieldKind.Required ? true : false; + isRequired[fieldKey] = treeFieldSimpleSchema.kind === FieldKind.Required ? true : false; } - return { allowedTypes, elementIsRequired }; + return { allowedTypes, isRequired }; } /** @@ -246,7 +245,7 @@ async function visualizeObjectNode( { allowedTypes, isRequired }: FieldSchemaProperties, visualizeChildData: VisualizeChildData, ): Promise { - const { allowedTypes: objectNodeAllowedTypes, elementIsRequired: objectNodeIsRequired } = + const { allowedTypes: objectNodeAllowedTypes, isRequired: objectNodeIsRequired } = getFieldTooltipProperties(treeDefinitions.get(tree.type) as SimpleObjectNodeSchema); return { @@ -307,7 +306,7 @@ async function visualizeMapNode( * * @remarks */ -async function visualizeInternalNodeBySchema( +async function visualizeNodeBySchema( tree: VerboseTreeNode, treeDefinitions: ReadonlyMap, { allowedTypes, isRequired }: FieldSchemaProperties, @@ -353,7 +352,7 @@ async function visualizeInternalNodeBySchema( fields[i] = await visualizeSharedTreeBySchema( child, treeDefinitions, - { allowedTypes: arrayNodeAllowedTypes[i] }, + { allowedTypes: arrayNodeAllowedTypes[i], isRequired: undefined }, visualizeChildData, ); } @@ -396,7 +395,7 @@ async function visualizeInternalNodeBySchema( * @remarks * This function handles both leaf nodes (primitive values, handles) and internal nodes (objects, maps, arrays). * For leaf nodes, it creates a visual representation with the node's schema and value. - * For internal nodes, it recursively processes the node's fields using {@link visualizeInternalNodeBySchema}. + * For internal nodes, it recursively processes the node's fields using {@link visualizeNodeBySchema}. */ export async function visualizeSharedTreeBySchema( tree: VerboseTree, @@ -414,7 +413,7 @@ export async function visualizeSharedTreeBySchema( value: await visualizeChildData(tree), kind: VisualSharedTreeNodeKind.LeafNode, } - : visualizeInternalNodeBySchema( + : visualizeNodeBySchema( tree, treeDefinitions, { allowedTypes, isRequired }, From 60ecfd35720da30e98fbae8366069e6c1584ab9e Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Mon, 10 Feb 2025 23:35:01 +0000 Subject: [PATCH 43/56] remove optional --- .../src/data-visualization/VisualSharedTreeTypes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts b/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts index 7e5d8a5b283b..bfc13db93548 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/VisualSharedTreeTypes.ts @@ -33,7 +33,7 @@ export interface SharedTreeSchemaNode { * Types allowed (e.g., string, number, boolean, handle & etc.) inside the node. * - InternalNode: `Record`. */ - readonly allowedTypes?: string | Record; + readonly allowedTypes: string | Record; /** * If the field is required or optional. From 67c0065c7f008859f318d962cf038902c01f7f1f Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Tue, 11 Feb 2025 00:27:42 +0000 Subject: [PATCH 44/56] fieldschema --- .../SharedTreeVisualizer.ts | 63 ++++++++++--------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index c24899166993..23bca93a9cb8 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -192,8 +192,7 @@ interface FieldSchemaProperties { async function visualizeVerboseNodeFields( treeFields: readonly VerboseTree[] | Record, treeDefinitions: ReadonlyMap, - allowedTypes: Record>, - requirements: Record | undefined, + fieldSchemaProperties: Record, visualizeChildData: VisualizeChildData, ): Promise> { const fields: Record = {}; @@ -203,8 +202,11 @@ async function visualizeVerboseNodeFields( childField, treeDefinitions, { - allowedTypes: allowedTypes[fieldKey], - isRequired: requirements === undefined ? undefined : requirements[fieldKey], + allowedTypes: fieldSchemaProperties[fieldKey]?.allowedTypes, + isRequired: + fieldSchemaProperties[fieldKey]?.isRequired === undefined + ? undefined + : fieldSchemaProperties[fieldKey]?.isRequired, }, visualizeChildData, ); @@ -216,24 +218,19 @@ async function visualizeVerboseNodeFields( /** * Extracts and stores allowed types & kind from {@link SimpleFieldSchema}. */ -function getFieldTooltipProperties(schema: SimpleObjectNodeSchema): { - allowedTypes: Record>; - isRequired: Record; -} { - const allowedTypes: Record> = {}; - - /** - * Maps each field or node identifier to a boolean indicating - * whether it is required (true) or optional (false). - */ - const isRequired: Record = {}; +function getFieldTooltipProperties( + schema: SimpleObjectNodeSchema, +): Record { + const result: Record = {}; for (const [fieldKey, treeFieldSimpleSchema] of Object.entries(schema.fields)) { - allowedTypes[fieldKey] = treeFieldSimpleSchema.allowedTypes; - isRequired[fieldKey] = treeFieldSimpleSchema.kind === FieldKind.Required ? true : false; + result[fieldKey] = { + allowedTypes: treeFieldSimpleSchema.allowedTypes, + isRequired: treeFieldSimpleSchema.kind === FieldKind.Required ? true : false, + }; } - return { allowedTypes, isRequired }; + return result; } /** @@ -245,8 +242,9 @@ async function visualizeObjectNode( { allowedTypes, isRequired }: FieldSchemaProperties, visualizeChildData: VisualizeChildData, ): Promise { - const { allowedTypes: objectNodeAllowedTypes, isRequired: objectNodeIsRequired } = - getFieldTooltipProperties(treeDefinitions.get(tree.type) as SimpleObjectNodeSchema); + const objectNodeSchemaProperties = getFieldTooltipProperties( + treeDefinitions.get(tree.type) as SimpleObjectNodeSchema, + ); return { schema: { @@ -257,8 +255,7 @@ async function visualizeObjectNode( fields: await visualizeVerboseNodeFields( tree.fields, treeDefinitions, - objectNodeAllowedTypes, - objectNodeIsRequired, + objectNodeSchemaProperties, visualizeChildData, ), kind: VisualSharedTreeNodeKind.InternalNode, @@ -275,10 +272,13 @@ async function visualizeMapNode( isRequired: boolean | undefined, visualizeChildData: VisualizeChildData, ): Promise { - const mapNodeAllowedTypes: Record> = {}; + const mapNodeSchemaProperties: Record = {}; for (const key of Object.keys(tree.fields)) { - mapNodeAllowedTypes[key] = nodeSchema.allowedTypes; + mapNodeSchemaProperties[key] = { + allowedTypes: nodeSchema.allowedTypes, + isRequired: undefined, + }; } return { schema: { @@ -289,8 +289,7 @@ async function visualizeMapNode( fields: await visualizeVerboseNodeFields( tree.fields, treeDefinitions, - mapNodeAllowedTypes, - undefined, + mapNodeSchemaProperties, visualizeChildData, ), kind: VisualSharedTreeNodeKind.InternalNode, @@ -345,14 +344,17 @@ async function visualizeNodeBySchema( throw new TypeError("Invalid array"); } - const arrayNodeAllowedTypes: Record> = {}; + const arrayNodeSchemaProperties: Record = {}; for (const [i, child] of children.entries()) { - arrayNodeAllowedTypes[i] = schema.allowedTypes; + arrayNodeSchemaProperties[i] = { + allowedTypes: schema.allowedTypes, + isRequired: undefined, + }; fields[i] = await visualizeSharedTreeBySchema( child, treeDefinitions, - { allowedTypes: arrayNodeAllowedTypes[i], isRequired: undefined }, + { allowedTypes: arrayNodeSchemaProperties[i]?.allowedTypes, isRequired: undefined }, visualizeChildData, ); } @@ -366,8 +368,7 @@ async function visualizeNodeBySchema( fields: await visualizeVerboseNodeFields( tree.fields, treeDefinitions, - arrayNodeAllowedTypes, - undefined, + arrayNodeSchemaProperties, visualizeChildData, ), kind: VisualSharedTreeNodeKind.InternalNode, From 735f1d8cfe59f275100f47ec35671d1549cda8f6 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Tue, 11 Feb 2025 13:48:03 -0800 Subject: [PATCH 45/56] Update packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts Co-authored-by: Joshua Smithrud <54606601+Josmithr@users.noreply.github.com> --- .../src/data-visualization/SharedTreeVisualizer.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 23bca93a9cb8..e30dfc4f54fd 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -172,9 +172,10 @@ interface FieldSchemaProperties { allowedTypes: ReadonlySet | undefined; /** - * Whether this field is required in its parent object schema. - * Only meaningful for direct children of object nodes. - * Undefined for array/map elements since they are always required within their parent. + * Whether the field is required (true) or optional (false). + * + * `undefined` indicates that the field is implicitly required. + * In this case, no requirement information will be displayed by the devtools. */ isRequired: boolean | undefined; } From c366807f37263017f3b864e49775ff7a8e17a4e4 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Tue, 11 Feb 2025 13:48:16 -0800 Subject: [PATCH 46/56] Update packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts Co-authored-by: Joshua Smithrud <54606601+Josmithr@users.noreply.github.com> --- .../src/data-visualization/SharedTreeVisualizer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index e30dfc4f54fd..54f73b09fe86 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -166,7 +166,7 @@ export function concatenateTypes(fieldTypes: ReadonlySet): string { */ interface FieldSchemaProperties { /** - * Set of type names that are valid for this specific node position in the tree. + * Set of node schema (represented by name) that are valid under this field. * This is a subset of the types defined in treeDefinitions. */ allowedTypes: ReadonlySet | undefined; From 701f22bb81791655cb6b1d0ccb3ba97a6ea6adf3 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Tue, 11 Feb 2025 13:48:24 -0800 Subject: [PATCH 47/56] Update packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts Co-authored-by: Alex Villarreal <716334+alexvy86@users.noreply.github.com> --- .../devtools-core/src/data-visualization/SharedTreeVisualizer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 54f73b09fe86..033f57e8f9ca 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -185,7 +185,6 @@ interface FieldSchemaProperties { * * @param treeFields - The fields of the tree node to visualize. Can be either an array of VerboseTree (for array nodes) or a Record of field names to VerboseTree (for object/map nodes). * @param treeDefinitions - Map containing all schema definitions for the entire tree structure. Each definition describes the shape and constraints of a particular node type. - * @param allowedTypes - Record mapping field names to their allowed type sets. Specifies which types are valid for each field in the tree. * @param requirements - Optional record mapping field names to boolean values indicating whether each field is required (true) or optional (false). Only meaningful for object node fields. * * @returns A record mapping field names/indices to their visual tree representations. From 4b3f178cb2b1712c1e6703a1879f95cce737484f Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Tue, 11 Feb 2025 13:48:44 -0800 Subject: [PATCH 48/56] Update packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts Co-authored-by: Alex Villarreal <716334+alexvy86@users.noreply.github.com> --- .../src/data-visualization/SharedTreeVisualizer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 033f57e8f9ca..29b8af0c9ad6 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -216,7 +216,7 @@ async function visualizeVerboseNodeFields( } /** - * Extracts and stores allowed types & kind from {@link SimpleFieldSchema}. + * Extracts and stores allowed types & kind for each field ({@link SimpleFieldSchema}) of a node schema ({@link SimpleObjectNodeSchema}). */ function getFieldTooltipProperties( schema: SimpleObjectNodeSchema, From cfabb66a77815c7f570704d8b855bc620a02bfbf Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Tue, 11 Feb 2025 13:48:54 -0800 Subject: [PATCH 49/56] Update packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts Co-authored-by: Joshua Smithrud <54606601+Josmithr@users.noreply.github.com> --- .../src/data-visualization/SharedTreeVisualizer.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 29b8af0c9ad6..16b42071cb3f 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -298,6 +298,8 @@ async function visualizeMapNode( /** * Creates the visual representation of non-leaf SharedTree nodes. + * + * @remarks * Processes internal tree nodes based on their schema type (e.g., ObjectNodeStoredSchema, MapNodeStoredSchema, ArrayNodeStoredSchema), * producing the visual representation for each type. * From df84d2594c602fddb737e6852dcdfb155ea3bb08 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Tue, 11 Feb 2025 22:28:48 +0000 Subject: [PATCH 50/56] change comment --- .../SharedTreeVisualizer.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 16b42071cb3f..2483b5b5dcc0 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -165,10 +165,6 @@ export function concatenateTypes(fieldTypes: ReadonlySet): string { * Properties that describe schema constraints for a field in the tree */ interface FieldSchemaProperties { - /** - * Set of node schema (represented by name) that are valid under this field. - * This is a subset of the types defined in treeDefinitions. - */ allowedTypes: ReadonlySet | undefined; /** @@ -185,7 +181,8 @@ interface FieldSchemaProperties { * * @param treeFields - The fields of the tree node to visualize. Can be either an array of VerboseTree (for array nodes) or a Record of field names to VerboseTree (for object/map nodes). * @param treeDefinitions - Map containing all schema definitions for the entire tree structure. Each definition describes the shape and constraints of a particular node type. - * @param requirements - Optional record mapping field names to boolean values indicating whether each field is required (true) or optional (false). Only meaningful for object node fields. + * @param fieldSchemaProperties - Record mapping field names to their schema properties, including allowed types and whether they are required. + * @param visualizeChildData - Callback function to visualize child node data. * * @returns A record mapping field names/indices to their visual tree representations. */ @@ -348,15 +345,16 @@ async function visualizeNodeBySchema( const arrayNodeSchemaProperties: Record = {}; for (const [i, child] of children.entries()) { - arrayNodeSchemaProperties[i] = { + const fieldSchema = { allowedTypes: schema.allowedTypes, isRequired: undefined, }; + arrayNodeSchemaProperties[i] = fieldSchema; fields[i] = await visualizeSharedTreeBySchema( child, treeDefinitions, - { allowedTypes: arrayNodeSchemaProperties[i]?.allowedTypes, isRequired: undefined }, + { allowedTypes: fieldSchema.allowedTypes, isRequired: undefined }, visualizeChildData, ); } @@ -387,9 +385,10 @@ async function visualizeNodeBySchema( * @param tree - The {@link VerboseTree} to visualize * @param treeDefinitions - Map containing all schema definitions for the entire tree structure. Each definition * describes the shape and constraints of a particular node type. - * @param allowedTypes - Set of type names that are valid for this specific node position in the tree. This is a - * subset of the types defined in treeDefinitions. - * @param isRequired - Whether this field is required in its parent object schema. + * @param fieldSchemaProperties - Properties describing schema constraints for this field: + * - `allowedTypes`: Set of type names that are valid for this specific node position in the tree. + * This is a subset of the types defined in treeDefinitions. + * - `isRequired`: Whether this field is required in its parent object schema. * Only meaningful for direct children of object nodes. * Undefined for array/map elements since they are always required within their parent. * @param visualizeChildData - Callback function to visualize child node data From a451b28548db06235107baf2276106a3770feec3 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Tue, 11 Feb 2025 22:32:50 +0000 Subject: [PATCH 51/56] change param type --- .../src/data-visualization/SharedTreeVisualizer.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 2483b5b5dcc0..6af2b15ee7ff 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -235,13 +235,12 @@ function getFieldTooltipProperties( */ async function visualizeObjectNode( tree: VerboseTreeNode, + schema: SimpleObjectNodeSchema, treeDefinitions: ReadonlyMap, { allowedTypes, isRequired }: FieldSchemaProperties, visualizeChildData: VisualizeChildData, ): Promise { - const objectNodeSchemaProperties = getFieldTooltipProperties( - treeDefinitions.get(tree.type) as SimpleObjectNodeSchema, - ); + const objectNodeSchemaProperties = getFieldTooltipProperties(schema); return { schema: { @@ -320,6 +319,7 @@ async function visualizeNodeBySchema( case NodeKind.Object: { const objectVisualized = visualizeObjectNode( tree, + schema, treeDefinitions, { allowedTypes, isRequired }, visualizeChildData, From 60e7f8085ecfd0da73897d7be828292755291a1d Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Wed, 12 Feb 2025 23:52:56 +0000 Subject: [PATCH 52/56] REVERTIBLE: change array / map --- .../SharedTreeVisualizer.ts | 125 ++++++++++-------- 1 file changed, 68 insertions(+), 57 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 6af2b15ee7ff..4eb4883a089a 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -5,6 +5,7 @@ import { FieldKind } from "@fluidframework/tree"; import type { + SimpleArrayNodeSchema, SimpleMapNodeSchema, SimpleNodeSchema, SimpleObjectNodeSchema, @@ -176,6 +177,24 @@ interface FieldSchemaProperties { isRequired: boolean | undefined; } +/** + * Extracts and stores allowed types & kind for each field ({@link SimpleFieldSchema}) of a node schema ({@link SimpleObjectNodeSchema}). + */ +function getFieldTooltipProperties( + schema: SimpleObjectNodeSchema, +): Record { + const result: Record = {}; + + for (const [fieldKey, treeFieldSimpleSchema] of Object.entries(schema.fields)) { + result[fieldKey] = { + allowedTypes: treeFieldSimpleSchema.allowedTypes, + isRequired: treeFieldSimpleSchema.kind === FieldKind.Required ? true : false, + }; + } + + return result; +} + /** * Processes and visualizes the fields of a verbose tree node. * @@ -212,24 +231,6 @@ async function visualizeVerboseNodeFields( return fields; } -/** - * Extracts and stores allowed types & kind for each field ({@link SimpleFieldSchema}) of a node schema ({@link SimpleObjectNodeSchema}). - */ -function getFieldTooltipProperties( - schema: SimpleObjectNodeSchema, -): Record { - const result: Record = {}; - - for (const [fieldKey, treeFieldSimpleSchema] of Object.entries(schema.fields)) { - result[fieldKey] = { - allowedTypes: treeFieldSimpleSchema.allowedTypes, - isRequired: treeFieldSimpleSchema.kind === FieldKind.Required ? true : false, - }; - } - - return result; -} - /** * Returns the schema & fields of the node with type {@link ObjectNodeStoredSchema}. */ @@ -265,7 +266,7 @@ async function visualizeMapNode( tree: VerboseTreeNode, nodeSchema: SimpleMapNodeSchema, treeDefinitions: ReadonlyMap, - isRequired: boolean | undefined, + { allowedTypes, isRequired }: FieldSchemaProperties, visualizeChildData: VisualizeChildData, ): Promise { const mapNodeSchemaProperties: Record = {}; @@ -279,7 +280,7 @@ async function visualizeMapNode( return { schema: { schemaName: tree.type, - allowedTypes: concatenateTypes(nodeSchema.allowedTypes), + allowedTypes: concatenateTypes(allowedTypes ?? new Set()), isRequired: isRequired?.toString(), }, fields: await visualizeVerboseNodeFields( @@ -292,6 +293,45 @@ async function visualizeMapNode( }; } +/** + * Returns the schema & fields of the node with type {@link ArrayNodeStoredSchema}. + */ +async function visualizeArrayNode( + tree: VerboseTreeNode, + schema: SimpleArrayNodeSchema, + treeDefinitions: ReadonlyMap, + { allowedTypes, isRequired }: FieldSchemaProperties, + visualizeChildData: VisualizeChildData, +): Promise { + const children = tree.fields; + if (!Array.isArray(children)) { + throw new TypeError("Invalid array"); + } + + const arrayNodeSchemaProperties: Record = {}; + for (const [i] of children.entries()) { + arrayNodeSchemaProperties[i] = { + allowedTypes: schema.allowedTypes, + isRequired: undefined, + }; + } + + return { + schema: { + schemaName: tree.type, + allowedTypes: concatenateTypes(allowedTypes ?? new Set()), + isRequired: isRequired?.toString(), + }, + fields: await visualizeVerboseNodeFields( + tree.fields, + treeDefinitions, + arrayNodeSchemaProperties, + visualizeChildData, + ), + kind: VisualSharedTreeNodeKind.InternalNode, + }; +} + /** * Creates the visual representation of non-leaf SharedTree nodes. * @@ -331,48 +371,19 @@ async function visualizeNodeBySchema( tree, schema, treeDefinitions, - isRequired, + { allowedTypes, isRequired }, visualizeChildData, ); return mapVisualized; } case NodeKind.Array: { - const fields: Record = {}; - const children = tree.fields; - if (!Array.isArray(children)) { - throw new TypeError("Invalid array"); - } - - const arrayNodeSchemaProperties: Record = {}; - for (const [i, child] of children.entries()) { - const fieldSchema = { - allowedTypes: schema.allowedTypes, - isRequired: undefined, - }; - arrayNodeSchemaProperties[i] = fieldSchema; - - fields[i] = await visualizeSharedTreeBySchema( - child, - treeDefinitions, - { allowedTypes: fieldSchema.allowedTypes, isRequired: undefined }, - visualizeChildData, - ); - } - - return { - schema: { - schemaName: tree.type, - allowedTypes: concatenateTypes(schema.allowedTypes), - isRequired: isRequired?.toString(), - }, - fields: await visualizeVerboseNodeFields( - tree.fields, - treeDefinitions, - arrayNodeSchemaProperties, - visualizeChildData, - ), - kind: VisualSharedTreeNodeKind.InternalNode, - }; + return visualizeArrayNode( + tree, + schema, + treeDefinitions, + { allowedTypes, isRequired }, + visualizeChildData, + ); } default: { throw new TypeError("Unrecognized schema type."); From 2856505925b3f7b071a8920b63e7f4ed520da4c5 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Thu, 13 Feb 2025 00:02:42 +0000 Subject: [PATCH 53/56] change name --- .../src/data-visualization/SharedTreeVisualizer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 4eb4883a089a..4af8775f55ad 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -264,7 +264,7 @@ async function visualizeObjectNode( */ async function visualizeMapNode( tree: VerboseTreeNode, - nodeSchema: SimpleMapNodeSchema, + schema: SimpleMapNodeSchema, treeDefinitions: ReadonlyMap, { allowedTypes, isRequired }: FieldSchemaProperties, visualizeChildData: VisualizeChildData, @@ -273,7 +273,7 @@ async function visualizeMapNode( for (const key of Object.keys(tree.fields)) { mapNodeSchemaProperties[key] = { - allowedTypes: nodeSchema.allowedTypes, + allowedTypes: schema.allowedTypes, isRequired: undefined, }; } From af9078e7102bb413a78ee09abe4fa6f15286907c Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Thu, 13 Feb 2025 00:35:44 +0000 Subject: [PATCH 54/56] change example schema --- .../devtools-test-app/src/FluidObject.ts | 73 +++++++++++-------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/packages/tools/devtools/devtools-test-app/src/FluidObject.ts b/packages/tools/devtools/devtools-test-app/src/FluidObject.ts index f574b407ef75..60b68f00d2b7 100644 --- a/packages/tools/devtools/devtools-test-app/src/FluidObject.ts +++ b/packages/tools/devtools/devtools-test-app/src/FluidObject.ts @@ -177,22 +177,32 @@ export class AppData extends DataObject { private populateSharedTree(sharedTree: ITree): void { const builder = new SchemaFactory("TodoList_Schema"); - class TodoItem extends builder.object("todo-item", { + class WorkItem extends builder.object("work-item", { title: builder.string, completed: builder.boolean, - dueDate: builder.optional(builder.string), - assignee: builder.optional(builder.string), + dueDate: builder.string, + assignee: builder.string, collaborators: builder.optional(builder.array(builder.string)), }) {} - class TodoObject extends builder.object("todo-list", { - items: builder.array(TodoItem), + class PersonalItem extends builder.object("personal-item", { + title: builder.string, + completed: builder.boolean, + dueDate: builder.string, + location: builder.optional(builder.string), + with: builder.optional(builder.array(builder.string)), + }) {} + + class Work extends builder.object("work-category", { + work: [builder.map([WorkItem]), builder.array(WorkItem)], }) {} - const TodoCategory = builder.map("todo-category", [TodoObject]); + class Personal extends builder.object("personal-category", { + personal: [builder.map([PersonalItem]), builder.array(PersonalItem)], + }) {} class TodoWorkspace extends builder.object("todo-workspace", { - lists: TodoCategory, + categories: [Work, Personal], }) {} const config = new TreeViewConfiguration({ @@ -201,36 +211,41 @@ export class AppData extends DataObject { const view = sharedTree.viewWith(config); view.initialize({ - lists: { - work: { - items: [ + categories: { + work: [ + { + title: "Submit a PR", + completed: false, + dueDate: "2026-01-01", + assignee: "Alice", + collaborators: ["Bob, Charlie"], + }, + { + title: "Review a PR", + completed: true, + dueDate: "2025-01-01", + assignee: "David", + }, + ], + personal: new Map([ + [ + "Health", { - title: "Finish design doc.", - completed: false, - dueDate: "2048-01-01", - assignee: "Kevin", - collaborators: ["Rick"], - }, - { - title: "Review pull requests", + title: "Go to the gym", completed: true, - assignee: "Bob", + dueDate: "2025-01-01", + with: ["Wayne", "Tyler"], }, ], - }, - personal: { - items: [ - { - title: "Buy groceries", - completed: false, - }, + [ + "Education", { - title: "Schedule dentist appointment", + title: "Finish reading the book", completed: false, - dueDate: "2024-05-04", + dueDate: "2026-01-01", }, ], - }, + ]), }, }); } From d9ccdc40792cc140dc119371ae1e4ed1ff59bdf3 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Thu, 13 Feb 2025 01:04:25 +0000 Subject: [PATCH 55/56] fix example code --- .../devtools-test-app/src/FluidObject.ts | 81 +++++++++---------- 1 file changed, 39 insertions(+), 42 deletions(-) diff --git a/packages/tools/devtools/devtools-test-app/src/FluidObject.ts b/packages/tools/devtools/devtools-test-app/src/FluidObject.ts index 60b68f00d2b7..f72b8e89a448 100644 --- a/packages/tools/devtools/devtools-test-app/src/FluidObject.ts +++ b/packages/tools/devtools/devtools-test-app/src/FluidObject.ts @@ -193,16 +193,11 @@ export class AppData extends DataObject { with: builder.optional(builder.array(builder.string)), }) {} - class Work extends builder.object("work-category", { - work: [builder.map([WorkItem]), builder.array(WorkItem)], - }) {} - - class Personal extends builder.object("personal-category", { - personal: [builder.map([PersonalItem]), builder.array(PersonalItem)], - }) {} - class TodoWorkspace extends builder.object("todo-workspace", { - categories: [Work, Personal], + categories: builder.object("todo-categories", { + work: [builder.map([WorkItem]), builder.array(WorkItem)], + personal: [builder.map([PersonalItem]), builder.array(PersonalItem)], + }), }) {} const config = new TreeViewConfiguration({ @@ -210,43 +205,45 @@ export class AppData extends DataObject { }); const view = sharedTree.viewWith(config); - view.initialize({ - categories: { - work: [ - { - title: "Submit a PR", - completed: false, - dueDate: "2026-01-01", - assignee: "Alice", - collaborators: ["Bob, Charlie"], - }, - { - title: "Review a PR", - completed: true, - dueDate: "2025-01-01", - assignee: "David", - }, - ], - personal: new Map([ - [ - "Health", + view.initialize( + new TodoWorkspace({ + categories: { + work: [ { - title: "Go to the gym", - completed: true, - dueDate: "2025-01-01", - with: ["Wayne", "Tyler"], - }, - ], - [ - "Education", - { - title: "Finish reading the book", + title: "Submit a PR", completed: false, dueDate: "2026-01-01", + assignee: "Alice", + collaborators: ["Bob", "Charlie"], + }, + { + title: "Review a PR", + completed: true, + dueDate: "2025-01-01", + assignee: "David", }, ], - ]), - }, - }); + personal: new Map([ + [ + "Health", + { + title: "Go to the gym", + completed: true, + dueDate: "2025-01-01", + with: ["Wayne", "Tyler"], + }, + ], + [ + "Education", + { + title: "Finish reading the book", + completed: false, + dueDate: "2026-01-01", + }, + ], + ]), + }, + }), + ); } } From 4f0c5ed6c066559176f0ef05ab87bac2d9eb00e0 Mon Sep 17 00:00:00 2001 From: Ji Kim <111468570+jikim-msft@users.noreply.github.com> Date: Thu, 13 Feb 2025 01:35:29 +0000 Subject: [PATCH 56/56] concise test case & remove function --- .../SharedTreeVisualizer.ts | 28 +- .../src/test/DefaultVisualizers.spec.ts | 1022 ++++------------- 2 files changed, 207 insertions(+), 843 deletions(-) diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts index 4af8775f55ad..56be69ff1baf 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/SharedTreeVisualizer.ts @@ -177,24 +177,6 @@ interface FieldSchemaProperties { isRequired: boolean | undefined; } -/** - * Extracts and stores allowed types & kind for each field ({@link SimpleFieldSchema}) of a node schema ({@link SimpleObjectNodeSchema}). - */ -function getFieldTooltipProperties( - schema: SimpleObjectNodeSchema, -): Record { - const result: Record = {}; - - for (const [fieldKey, treeFieldSimpleSchema] of Object.entries(schema.fields)) { - result[fieldKey] = { - allowedTypes: treeFieldSimpleSchema.allowedTypes, - isRequired: treeFieldSimpleSchema.kind === FieldKind.Required ? true : false, - }; - } - - return result; -} - /** * Processes and visualizes the fields of a verbose tree node. * @@ -241,7 +223,13 @@ async function visualizeObjectNode( { allowedTypes, isRequired }: FieldSchemaProperties, visualizeChildData: VisualizeChildData, ): Promise { - const objectNodeSchemaProperties = getFieldTooltipProperties(schema); + const objectNodeSchemaProperties: Record = {}; + for (const [fieldKey, treeFieldSimpleSchema] of Object.entries(schema.fields)) { + objectNodeSchemaProperties[fieldKey] = { + allowedTypes: treeFieldSimpleSchema.allowedTypes, + isRequired: treeFieldSimpleSchema.kind === FieldKind.Required ? true : false, + }; + } return { schema: { @@ -270,13 +258,13 @@ async function visualizeMapNode( visualizeChildData: VisualizeChildData, ): Promise { const mapNodeSchemaProperties: Record = {}; - for (const key of Object.keys(tree.fields)) { mapNodeSchemaProperties[key] = { allowedTypes: schema.allowedTypes, isRequired: undefined, }; } + return { schema: { schemaName: tree.type, diff --git a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts index 675db071f25d..9c71fa5beadc 100644 --- a/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts +++ b/packages/tools/devtools/devtools-core/src/test/DefaultVisualizers.spec.ts @@ -433,7 +433,7 @@ describe("DefaultVisualizers unit tests", () => { expect(result).to.deep.equal(expected); }); - it("SharedTree: Array of Leaves", async () => { + it("SharedTree: Array", async () => { const factory = SharedTree.getFactory(); const builder = new SchemaFactory("shared-tree-test"); @@ -443,13 +443,13 @@ describe("DefaultVisualizers unit tests", () => { ); class RootNodeSchema extends builder.object("root-item", { - foo: builder.array([builder.number, builder.string]), + foo: builder.optional(builder.array([builder.number, builder.string])), }) {} const view = sharedTree.viewWith(new TreeViewConfiguration({ schema: RootNodeSchema })); view.initialize( new RootNodeSchema({ - foo: [0, 1, 2, 3, "hello", "world"], + foo: [1, "hello world"], }), ); @@ -463,25 +463,6 @@ describe("DefaultVisualizers unit tests", () => { foo: { children: { "0": { - value: 0, - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.number", - }, - allowedTypes: { - value: "com.fluidframework.leaf.number | com.fluidframework.leaf.string", - nodeKind: "ValueNode", - }, - }, - }, - }, - }, - "1": { value: 1, nodeKind: "ValueNode", tooltipContents: { @@ -500,65 +481,8 @@ describe("DefaultVisualizers unit tests", () => { }, }, }, - "2": { - value: 2, - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.number", - }, - allowedTypes: { - value: "com.fluidframework.leaf.number | com.fluidframework.leaf.string", - nodeKind: "ValueNode", - }, - }, - }, - }, - }, - "3": { - value: 3, - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.number", - }, - allowedTypes: { - value: "com.fluidframework.leaf.number | com.fluidframework.leaf.string", - nodeKind: "ValueNode", - }, - }, - }, - }, - }, - "4": { - value: "hello", - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.string", - }, - allowedTypes: { - value: "com.fluidframework.leaf.number | com.fluidframework.leaf.string", - nodeKind: "ValueNode", - }, - }, - }, - }, - }, - "5": { - value: "world", + "1": { + value: "hello world", nodeKind: "ValueNode", tooltipContents: { schema: { @@ -588,12 +512,13 @@ describe("DefaultVisualizers unit tests", () => { 'shared-tree-test.Array<["com.fluidframework.leaf.number","com.fluidframework.leaf.string"]>', }, allowedTypes: { - value: "com.fluidframework.leaf.number | com.fluidframework.leaf.string", + value: + 'shared-tree-test.Array<["com.fluidframework.leaf.number","com.fluidframework.leaf.string"]>', nodeKind: "ValueNode", }, isRequired: { nodeKind: "ValueNode", - value: "true", + value: "false", }, }, }, @@ -623,7 +548,6 @@ describe("DefaultVisualizers unit tests", () => { fluidObjectId: "test", typeMetadata: "SharedTree", }; - expect(result).to.deep.equal(expected); }); @@ -637,7 +561,7 @@ describe("DefaultVisualizers unit tests", () => { ); class RootNodeSchema extends builder.object("root-item", { - foo: builder.map([builder.string, builder.number, builder.handle]), + foo: builder.map([builder.number, builder.handle]), }) {} const view = sharedTree.viewWith(new TreeViewConfiguration({ schema: RootNodeSchema })); @@ -646,7 +570,6 @@ describe("DefaultVisualizers unit tests", () => { foo: new Map([ ["apple", 1], ["banana", 2], - ["cherry", 3], ]), }), ); @@ -672,8 +595,7 @@ describe("DefaultVisualizers unit tests", () => { value: "com.fluidframework.leaf.number", }, allowedTypes: { - value: - "com.fluidframework.leaf.string | com.fluidframework.leaf.number | com.fluidframework.leaf.handle", + value: "com.fluidframework.leaf.number | com.fluidframework.leaf.handle", nodeKind: "ValueNode", }, }, @@ -692,28 +614,7 @@ describe("DefaultVisualizers unit tests", () => { value: "com.fluidframework.leaf.number", }, allowedTypes: { - value: - "com.fluidframework.leaf.string | com.fluidframework.leaf.number | com.fluidframework.leaf.handle", - nodeKind: "ValueNode", - }, - }, - }, - }, - }, - cherry: { - value: 3, - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.number", - }, - allowedTypes: { - value: - "com.fluidframework.leaf.string | com.fluidframework.leaf.number | com.fluidframework.leaf.handle", + value: "com.fluidframework.leaf.number | com.fluidframework.leaf.handle", nodeKind: "ValueNode", }, }, @@ -729,11 +630,11 @@ describe("DefaultVisualizers unit tests", () => { name: { nodeKind: "ValueNode", value: - 'shared-tree-test.Map<["com.fluidframework.leaf.handle","com.fluidframework.leaf.number","com.fluidframework.leaf.string"]>', + 'shared-tree-test.Map<["com.fluidframework.leaf.handle","com.fluidframework.leaf.number"]>', }, allowedTypes: { value: - "com.fluidframework.leaf.string | com.fluidframework.leaf.number | com.fluidframework.leaf.handle", + 'shared-tree-test.Map<["com.fluidframework.leaf.handle","com.fluidframework.leaf.number"]>', nodeKind: "ValueNode", }, isRequired: { @@ -1059,7 +960,7 @@ describe("DefaultVisualizers unit tests", () => { expect(result).to.deep.equal(expected); }); - it("SharedTree: Complex Nesting", async () => { + it("SharedTree: Array and Map in Object Node", async () => { const factory = SharedTree.getFactory(); const builder = new SchemaFactory("shared-tree-test"); @@ -1068,90 +969,44 @@ describe("DefaultVisualizers unit tests", () => { "test", ); - class BroccoliSchema extends builder.object("broccoli-object-schema", { - alpaca: builder.string, - }) {} - - class AppleSchema extends builder.object("apple-object-schema", { - avocado: [builder.number, builder.string], - broccoli: builder.array(BroccoliSchema), + class WorkItem extends builder.object("work-item", { + title: builder.string, + completed: builder.boolean, + dueDate: builder.string, + assignee: builder.string, + collaborators: builder.optional(builder.array(builder.string)), }) {} - class FooSchema extends builder.object("foo-item", { - apple: builder.array(AppleSchema), - banana: builder.object("banana-object", { - miniBanana: [builder.boolean, builder.string, builder.number], + class TodoWorkspace extends builder.object("todo-workspace", { + categories: builder.object("todo-categories", { + work: [builder.map([WorkItem]), builder.array(WorkItem)], }), - cherry: builder.optional(builder.number), }) {} - class RootNodeSchema extends builder.object("root-item", { - foo: builder.array(FooSchema), - bar: builder.object("bar-item", { - americano: builder.boolean, - bubbleTea: builder.string, - chaiLatte: builder.object("chai-latte-object", { - appleCider: [builder.boolean, builder.string, builder.handle], - }), - dalgona: builder.array( - builder.object("dalgona-object", { - avengers: builder.boolean, - }), - ), - espresso: builder.array([builder.number, builder.string]), + const view = sharedTree.viewWith( + new TreeViewConfiguration({ + schema: builder.optional(TodoWorkspace), }), - baz: [builder.number, builder.string, builder.boolean], - foobar: builder.map([ - builder.string, - builder.number, - builder.handle, - builder.object("map-object", { acorn: builder.boolean }), - ]), - }) {} - - const view = sharedTree.viewWith(new TreeViewConfiguration({ schema: RootNodeSchema })); + ); view.initialize( - new RootNodeSchema({ - foo: [ - { - apple: [{ avocado: 16, broccoli: [{ alpaca: "Llama but cuter." }] }], - banana: { - miniBanana: true, - }, - cherry: 32, - }, - { - apple: [ - { - avocado: "Avacado Advocate.", - broccoli: [{ alpaca: "Llama but not LLM." }], - }, - ], - banana: { - miniBanana: false, + new TodoWorkspace({ + categories: { + work: [ + { + title: "Submit a PR", + completed: false, + dueDate: "2026-01-01", + assignee: "Alice", + collaborators: ["Bob", "Charlie"], }, - cherry: undefined, - }, - ], - bar: { - americano: false, - bubbleTea: "Taro Bubble Tea", - chaiLatte: { - appleCider: true, - }, - dalgona: [ { - avengers: true, + title: "Review a PR", + completed: true, + dueDate: "2025-01-01", + assignee: "David", }, ], - espresso: [256, "FiveHundredTwelve"], }, - baz: 128, - foobar: new Map([ - ["anthropology", 1], - ["biology", 2], - ["choreography", 3], - ]), }), ); @@ -1162,16 +1017,108 @@ describe("DefaultVisualizers unit tests", () => { const expected = { children: { - foo: { + categories: { children: { - "0": { + work: { children: { - apple: { + "0": { children: { - "0": { + title: { + value: "Submit a PR", + nodeKind: "ValueNode", + tooltipContents: { + schema: { + nodeKind: "TreeNode", + children: { + name: { + nodeKind: "ValueNode", + value: "com.fluidframework.leaf.string", + }, + allowedTypes: { + value: "com.fluidframework.leaf.string", + nodeKind: "ValueNode", + }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, + }, + }, + }, + }, + completed: { + value: false, + nodeKind: "ValueNode", + tooltipContents: { + schema: { + nodeKind: "TreeNode", + children: { + name: { + nodeKind: "ValueNode", + value: "com.fluidframework.leaf.boolean", + }, + allowedTypes: { + value: "com.fluidframework.leaf.boolean", + nodeKind: "ValueNode", + }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, + }, + }, + }, + }, + dueDate: { + value: "2026-01-01", + nodeKind: "ValueNode", + tooltipContents: { + schema: { + nodeKind: "TreeNode", + children: { + name: { + nodeKind: "ValueNode", + value: "com.fluidframework.leaf.string", + }, + allowedTypes: { + value: "com.fluidframework.leaf.string", + nodeKind: "ValueNode", + }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, + }, + }, + }, + }, + assignee: { + value: "Alice", + nodeKind: "ValueNode", + tooltipContents: { + schema: { + nodeKind: "TreeNode", + children: { + name: { + nodeKind: "ValueNode", + value: "com.fluidframework.leaf.string", + }, + allowedTypes: { + value: "com.fluidframework.leaf.string", + nodeKind: "ValueNode", + }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, + }, + }, + }, + }, + collaborators: { children: { - avocado: { - value: 16, + "0": { + value: "Bob", nodeKind: "ValueNode", tooltipContents: { schema: { @@ -1179,84 +1126,30 @@ describe("DefaultVisualizers unit tests", () => { children: { name: { nodeKind: "ValueNode", - value: "com.fluidframework.leaf.number", + value: "com.fluidframework.leaf.string", }, allowedTypes: { - value: - "com.fluidframework.leaf.number | com.fluidframework.leaf.string", - nodeKind: "ValueNode", - }, - isRequired: { + value: "com.fluidframework.leaf.string", nodeKind: "ValueNode", - value: "true", }, }, }, }, }, - broccoli: { - children: { - "0": { - children: { - alpaca: { - value: "Llama but cuter.", - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.string", - }, - allowedTypes: { - value: "com.fluidframework.leaf.string", - nodeKind: "ValueNode", - }, - isRequired: { - nodeKind: "ValueNode", - value: "true", - }, - }, - }, - }, - }, - }, - nodeKind: "TreeNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "shared-tree-test.broccoli-object-schema", - }, - allowedTypes: { - value: "shared-tree-test.broccoli-object-schema", - nodeKind: "ValueNode", - }, - }, - }, - }, - }, - }, - nodeKind: "TreeNode", + "1": { + value: "Charlie", + nodeKind: "ValueNode", tooltipContents: { schema: { nodeKind: "TreeNode", children: { name: { nodeKind: "ValueNode", - value: - 'shared-tree-test.Array<["shared-tree-test.broccoli-object-schema"]>', + value: "com.fluidframework.leaf.string", }, allowedTypes: { - value: "shared-tree-test.broccoli-object-schema", - nodeKind: "ValueNode", - }, - isRequired: { + value: "com.fluidframework.leaf.string", nodeKind: "ValueNode", - value: "true", }, }, }, @@ -1270,11 +1163,17 @@ describe("DefaultVisualizers unit tests", () => { children: { name: { nodeKind: "ValueNode", - value: "shared-tree-test.apple-object-schema", + value: + 'shared-tree-test.Array<["com.fluidframework.leaf.string"]>', }, allowedTypes: { - value: "shared-tree-test.apple-object-schema", + value: + 'shared-tree-test.Array<["com.fluidframework.leaf.string"]>', + nodeKind: "ValueNode", + }, + isRequired: { nodeKind: "ValueNode", + value: "false", }, }, }, @@ -1288,24 +1187,42 @@ describe("DefaultVisualizers unit tests", () => { children: { name: { nodeKind: "ValueNode", - value: - 'shared-tree-test.Array<["shared-tree-test.apple-object-schema"]>', + value: "shared-tree-test.work-item", }, allowedTypes: { - value: "shared-tree-test.apple-object-schema", - nodeKind: "ValueNode", - }, - isRequired: { + value: "shared-tree-test.work-item", nodeKind: "ValueNode", - value: "true", }, }, }, }, }, - banana: { + "1": { children: { - miniBanana: { + title: { + value: "Review a PR", + nodeKind: "ValueNode", + tooltipContents: { + schema: { + nodeKind: "TreeNode", + children: { + name: { + nodeKind: "ValueNode", + value: "com.fluidframework.leaf.string", + }, + allowedTypes: { + value: "com.fluidframework.leaf.string", + nodeKind: "ValueNode", + }, + isRequired: { + nodeKind: "ValueNode", + value: "true", + }, + }, + }, + }, + }, + completed: { value: true, nodeKind: "ValueNode", tooltipContents: { @@ -1317,8 +1234,7 @@ describe("DefaultVisualizers unit tests", () => { value: "com.fluidframework.leaf.boolean", }, allowedTypes: { - value: - "com.fluidframework.leaf.boolean | com.fluidframework.leaf.string | com.fluidframework.leaf.number", + value: "com.fluidframework.leaf.boolean", nodeKind: "ValueNode", }, isRequired: { @@ -1329,212 +1245,8 @@ describe("DefaultVisualizers unit tests", () => { }, }, }, - }, - nodeKind: "TreeNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "shared-tree-test.banana-object", - }, - allowedTypes: { - value: "shared-tree-test.banana-object", - nodeKind: "ValueNode", - }, - isRequired: { - nodeKind: "ValueNode", - value: "true", - }, - }, - }, - }, - }, - cherry: { - value: 32, - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.number", - }, - allowedTypes: { - value: "com.fluidframework.leaf.number", - nodeKind: "ValueNode", - }, - isRequired: { - nodeKind: "ValueNode", - value: "false", - }, - }, - }, - }, - }, - }, - nodeKind: "TreeNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "shared-tree-test.foo-item", - }, - allowedTypes: { - value: "shared-tree-test.foo-item", - nodeKind: "ValueNode", - }, - }, - }, - }, - }, - "1": { - children: { - apple: { - children: { - "0": { - children: { - avocado: { - value: "Avacado Advocate.", - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.string", - }, - allowedTypes: { - value: - "com.fluidframework.leaf.number | com.fluidframework.leaf.string", - nodeKind: "ValueNode", - }, - isRequired: { - nodeKind: "ValueNode", - value: "true", - }, - }, - }, - }, - }, - broccoli: { - children: { - "0": { - children: { - alpaca: { - value: "Llama but not LLM.", - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.string", - }, - allowedTypes: { - value: "com.fluidframework.leaf.string", - nodeKind: "ValueNode", - }, - isRequired: { - nodeKind: "ValueNode", - value: "true", - }, - }, - }, - }, - }, - }, - nodeKind: "TreeNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "shared-tree-test.broccoli-object-schema", - }, - allowedTypes: { - value: "shared-tree-test.broccoli-object-schema", - nodeKind: "ValueNode", - }, - }, - }, - }, - }, - }, - nodeKind: "TreeNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: - 'shared-tree-test.Array<["shared-tree-test.broccoli-object-schema"]>', - }, - allowedTypes: { - value: "shared-tree-test.broccoli-object-schema", - nodeKind: "ValueNode", - }, - isRequired: { - nodeKind: "ValueNode", - value: "true", - }, - }, - }, - }, - }, - }, - nodeKind: "TreeNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "shared-tree-test.apple-object-schema", - }, - allowedTypes: { - value: "shared-tree-test.apple-object-schema", - nodeKind: "ValueNode", - }, - }, - }, - }, - }, - }, - nodeKind: "TreeNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: - 'shared-tree-test.Array<["shared-tree-test.apple-object-schema"]>', - }, - allowedTypes: { - value: "shared-tree-test.apple-object-schema", - nodeKind: "ValueNode", - }, - isRequired: { - nodeKind: "ValueNode", - value: "true", - }, - }, - }, - }, - }, - banana: { - children: { - miniBanana: { - value: false, + dueDate: { + value: "2025-01-01", nodeKind: "ValueNode", tooltipContents: { schema: { @@ -1542,11 +1254,10 @@ describe("DefaultVisualizers unit tests", () => { children: { name: { nodeKind: "ValueNode", - value: "com.fluidframework.leaf.boolean", + value: "com.fluidframework.leaf.string", }, allowedTypes: { - value: - "com.fluidframework.leaf.boolean | com.fluidframework.leaf.string | com.fluidframework.leaf.number", + value: "com.fluidframework.leaf.string", nodeKind: "ValueNode", }, isRequired: { @@ -1557,170 +1268,8 @@ describe("DefaultVisualizers unit tests", () => { }, }, }, - }, - nodeKind: "TreeNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "shared-tree-test.banana-object", - }, - allowedTypes: { - value: "shared-tree-test.banana-object", - nodeKind: "ValueNode", - }, - isRequired: { - nodeKind: "ValueNode", - value: "true", - }, - }, - }, - }, - }, - }, - nodeKind: "TreeNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "shared-tree-test.foo-item", - }, - allowedTypes: { - value: "shared-tree-test.foo-item", - nodeKind: "ValueNode", - }, - }, - }, - }, - }, - }, - nodeKind: "TreeNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: 'shared-tree-test.Array<["shared-tree-test.foo-item"]>', - }, - allowedTypes: { - value: "shared-tree-test.foo-item", - nodeKind: "ValueNode", - }, - isRequired: { - nodeKind: "ValueNode", - value: "true", - }, - }, - }, - }, - }, - bar: { - children: { - americano: { - value: false, - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.boolean", - }, - allowedTypes: { - value: "com.fluidframework.leaf.boolean", - nodeKind: "ValueNode", - }, - isRequired: { - nodeKind: "ValueNode", - value: "true", - }, - }, - }, - }, - }, - bubbleTea: { - value: "Taro Bubble Tea", - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.string", - }, - allowedTypes: { - value: "com.fluidframework.leaf.string", - nodeKind: "ValueNode", - }, - isRequired: { - nodeKind: "ValueNode", - value: "true", - }, - }, - }, - }, - }, - chaiLatte: { - children: { - appleCider: { - value: true, - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.boolean", - }, - allowedTypes: { - value: - "com.fluidframework.leaf.boolean | com.fluidframework.leaf.string | com.fluidframework.leaf.handle", - nodeKind: "ValueNode", - }, - isRequired: { - nodeKind: "ValueNode", - value: "true", - }, - }, - }, - }, - }, - }, - nodeKind: "TreeNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "shared-tree-test.chai-latte-object", - }, - allowedTypes: { - value: "shared-tree-test.chai-latte-object", - nodeKind: "ValueNode", - }, - isRequired: { - nodeKind: "ValueNode", - value: "true", - }, - }, - }, - }, - }, - dalgona: { - children: { - "0": { - children: { - avengers: { - value: true, + assignee: { + value: "David", nodeKind: "ValueNode", tooltipContents: { schema: { @@ -1728,10 +1277,10 @@ describe("DefaultVisualizers unit tests", () => { children: { name: { nodeKind: "ValueNode", - value: "com.fluidframework.leaf.boolean", + value: "com.fluidframework.leaf.string", }, allowedTypes: { - value: "com.fluidframework.leaf.boolean", + value: "com.fluidframework.leaf.string", nodeKind: "ValueNode", }, isRequired: { @@ -1750,10 +1299,10 @@ describe("DefaultVisualizers unit tests", () => { children: { name: { nodeKind: "ValueNode", - value: "shared-tree-test.dalgona-object", + value: "shared-tree-test.work-item", }, allowedTypes: { - value: "shared-tree-test.dalgona-object", + value: "shared-tree-test.work-item", nodeKind: "ValueNode", }, }, @@ -1768,75 +1317,11 @@ describe("DefaultVisualizers unit tests", () => { children: { name: { nodeKind: "ValueNode", - value: 'shared-tree-test.Array<["shared-tree-test.dalgona-object"]>', + value: 'shared-tree-test.Array<["shared-tree-test.work-item"]>', }, allowedTypes: { - value: "shared-tree-test.dalgona-object", - nodeKind: "ValueNode", - }, - isRequired: { - nodeKind: "ValueNode", - value: "true", - }, - }, - }, - }, - }, - espresso: { - children: { - "0": { - value: 256, - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.number", - }, - allowedTypes: { - value: - "com.fluidframework.leaf.number | com.fluidframework.leaf.string", - nodeKind: "ValueNode", - }, - }, - }, - }, - }, - "1": { - value: "FiveHundredTwelve", - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.string", - }, - allowedTypes: { - value: - "com.fluidframework.leaf.number | com.fluidframework.leaf.string", - nodeKind: "ValueNode", - }, - }, - }, - }, - }, - }, - nodeKind: "TreeNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", value: - 'shared-tree-test.Array<["com.fluidframework.leaf.number","com.fluidframework.leaf.string"]>', - }, - allowedTypes: { - value: "com.fluidframework.leaf.number | com.fluidframework.leaf.string", + 'shared-tree-test.Map<["shared-tree-test.work-item"]> | shared-tree-test.Array<["shared-tree-test.work-item"]>', nodeKind: "ValueNode", }, isRequired: { @@ -1855,120 +1340,10 @@ describe("DefaultVisualizers unit tests", () => { children: { name: { nodeKind: "ValueNode", - value: "shared-tree-test.bar-item", + value: "shared-tree-test.todo-categories", }, allowedTypes: { - value: "shared-tree-test.bar-item", - nodeKind: "ValueNode", - }, - isRequired: { - nodeKind: "ValueNode", - value: "true", - }, - }, - }, - }, - }, - baz: { - value: 128, - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.number", - }, - allowedTypes: { - value: - "com.fluidframework.leaf.number | com.fluidframework.leaf.string | com.fluidframework.leaf.boolean", - nodeKind: "ValueNode", - }, - isRequired: { - nodeKind: "ValueNode", - value: "true", - }, - }, - }, - }, - }, - foobar: { - children: { - anthropology: { - value: 1, - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.number", - }, - allowedTypes: { - value: - "com.fluidframework.leaf.string | com.fluidframework.leaf.number | com.fluidframework.leaf.handle | shared-tree-test.map-object", - nodeKind: "ValueNode", - }, - }, - }, - }, - }, - biology: { - value: 2, - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.number", - }, - allowedTypes: { - value: - "com.fluidframework.leaf.string | com.fluidframework.leaf.number | com.fluidframework.leaf.handle | shared-tree-test.map-object", - nodeKind: "ValueNode", - }, - }, - }, - }, - }, - choreography: { - value: 3, - nodeKind: "ValueNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: "com.fluidframework.leaf.number", - }, - allowedTypes: { - value: - "com.fluidframework.leaf.string | com.fluidframework.leaf.number | com.fluidframework.leaf.handle | shared-tree-test.map-object", - nodeKind: "ValueNode", - }, - }, - }, - }, - }, - }, - nodeKind: "TreeNode", - tooltipContents: { - schema: { - nodeKind: "TreeNode", - children: { - name: { - nodeKind: "ValueNode", - value: - 'shared-tree-test.Map<["com.fluidframework.leaf.handle","com.fluidframework.leaf.number","com.fluidframework.leaf.string","shared-tree-test.map-object"]>', - }, - allowedTypes: { - value: - "com.fluidframework.leaf.string | com.fluidframework.leaf.number | com.fluidframework.leaf.handle | shared-tree-test.map-object", + value: "shared-tree-test.todo-categories", nodeKind: "ValueNode", }, isRequired: { @@ -1987,15 +1362,15 @@ describe("DefaultVisualizers unit tests", () => { children: { name: { nodeKind: "ValueNode", - value: "shared-tree-test.root-item", + value: "shared-tree-test.todo-workspace", }, allowedTypes: { - value: "shared-tree-test.root-item", + value: "shared-tree-test.todo-workspace", nodeKind: "ValueNode", }, isRequired: { nodeKind: "ValueNode", - value: "true", + value: "false", }, }, }, @@ -2003,6 +1378,7 @@ describe("DefaultVisualizers unit tests", () => { fluidObjectId: "test", typeMetadata: "SharedTree", }; + expect(result).to.deep.equal(expected); });