Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tree): Add the ability to associate metadata with Node Schema #23321

Draft
wants to merge 53 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
111f37f
feat: Support metadata on Object node schema
Josmithr Nov 26, 2024
e611fdd
feat: Add metadata support to map node schema
Josmithr Nov 26, 2024
72e2398
feat: Add metadata support to array node schema
Josmithr Nov 26, 2024
cbf1056
test: Recursive schema tests
Josmithr Nov 26, 2024
adcf801
feat: Support metadata in simple schema domain, and map "description"…
Josmithr Nov 26, 2024
167f538
docs: Expand notes
Josmithr Dec 2, 2024
212eea5
docs: InheritDoc
Josmithr Dec 2, 2024
233db92
docs: Update terminology
Josmithr Dec 2, 2024
f569711
revert: Unintended change
Josmithr Dec 2, 2024
c6455af
refactor: Apply suggestions from code review
Josmithr Dec 3, 2024
37e6088
docs: Update API reports
Josmithr Dec 3, 2024
ccc0be8
refactor: Consistent typing
Josmithr Dec 3, 2024
fb10e72
tools: Update spelling dictionary
Josmithr Dec 10, 2024
9e1e1fe
refactor: Expose `withMetadata` function to replace new SchemaFactory…
Josmithr Dec 10, 2024
471ae1d
refactor: Remove unused type
Josmithr Dec 10, 2024
fc48fe1
docs: Update API reports
Josmithr Dec 10, 2024
957e202
docs: Update changeset wording
Josmithr Dec 10, 2024
8abd211
revert: Unwanted changes
Josmithr Dec 11, 2024
594b1d7
remove: Unused import
Josmithr Dec 11, 2024
a94f4c2
refactor(test): Replace withMetadata tests with a single test
Josmithr Dec 11, 2024
17eca89
improvement: Make metadata properties readonly
Josmithr Dec 11, 2024
6526065
refactor: More consistent typing
Josmithr Dec 11, 2024
7278573
docs: Add function documentation
Josmithr Dec 11, 2024
465643b
refactor: Stricter typing and remove readonly type modification
Josmithr Dec 11, 2024
d477c93
test: Simplify test
Josmithr Dec 11, 2024
6a20d59
revert: Unwanted change
Josmithr Dec 11, 2024
901db32
refactor: Simplify typing
Josmithr Dec 11, 2024
2be529d
remove: Unused import
Josmithr Dec 11, 2024
6a76aa2
refactor: Type param ordering
Josmithr Dec 11, 2024
aadfddf
docs: Small changeset wording update
Josmithr Dec 11, 2024
d67d5c9
docs: Update comment
Josmithr Dec 11, 2024
5c3d75d
docs: Update API reports
Josmithr Dec 11, 2024
0d273a0
revert: Type signature change
Josmithr Dec 11, 2024
6e4ea43
docs: Update changeset wording
Josmithr Dec 11, 2024
61cbeea
docs: Update changeset to make potential breaking nature more explicit
Josmithr Dec 11, 2024
93a30e6
docs: Update wording
Josmithr Dec 11, 2024
845082a
docs: Fix comment
Josmithr Dec 11, 2024
fe46a27
docs: Restore removed test condition, but commented out with an expla…
Josmithr Dec 11, 2024
ed4fb02
test: Expand test
Josmithr Dec 11, 2024
2a9ade4
docs: Remove comment
Josmithr Dec 11, 2024
a294afc
docs: Add link to previous release notes in changeset
Josmithr Dec 11, 2024
af07d78
refactor: Required parameter
Josmithr Dec 12, 2024
79c0716
fix(docs): Configure SWA to never emit trailing slashes for website U…
Josmithr Dec 11, 2024
d4e8232
fix(docs): Better trailing slash omission (#23304)
Josmithr Dec 11, 2024
ef4c6d6
refactor: Restore to original API model
Josmithr Dec 12, 2024
2ddb09e
docs: Update API reports
Josmithr Dec 12, 2024
9d58123
docs: Update changeset
Josmithr Dec 12, 2024
127a6b2
Merge branch 'main' into tree/node-schema-metadata-3
Josmithr Dec 13, 2024
152396e
Merge branch 'main' into tree/node-schema-metadata-3
Josmithr Dec 19, 2024
71f4d8e
refactor: Move metadata support for Object nodes down to alpha factory
Josmithr Dec 19, 2024
aab58a7
refactor: Move remaining method updates down to subclass
Josmithr Dec 19, 2024
93d0ab2
docs: Param comments
Josmithr Dec 19, 2024
704277e
feat(ai-collab example) Leverage new alpha methods
Josmithr Dec 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .changeset/grey-triangles-shout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
"@fluidframework/tree": minor
---
---
section: tree
---

Metadata can be associated with Node Schema

Users of TreeView can now specify metadata when creating Node Schema.
This metadata may include system-understood properties like `description`.

Example:

```typescript

class Point extends schemaFactory.object("Point", {
x: schemaFactory.required(schemaFactory.number),
y: schemaFactory.required(schemaFactory.number),
},
{
metadata: {
description: "A point in 2D space",
},
}) {}

```

Functionality like the experimental conversion of Tree Schema to [JSON Schema](https://json-schema.org/) ([getJsonSchema](https://github.com/microsoft/FluidFramework/releases/tag/client_v2.4.0#user-content-metadata-can-now-be-associated-with-field-schema-22564)) leverages such system-understood metadata to generate useful information.
In the case of the `description` property, it is mapped directly to the `description` property supported by JSON Schema.

Custom, user-defined properties can also be specified.
These properties will not be used by the system by default, but can be used to associate common application-specific properties with Node Schema.

#### Example

An application is implementing search functionality.
By default, the app author wishes for all app content to be potentially indexable by search, unless otherwise specified.
They can leverage schema metadata to decorate types of nodes that should be ignored by search, and leverage that information when walking the tree during a search.

```typescript

interface AppMetadata {
/**
* Whether or not nodes of this type should be ignored by search.
* @defaultValue `false`
*/
searchIgnore?: boolean;
}

class Point extends schemaFactory.object("Point", {
x: schemaFactory.required(schemaFactory.number),
y: schemaFactory.required(schemaFactory.number),
},
{
metadata: {
description: "A point in 2D space",
custom: {
searchIgnore: true,
},
}
}) {}

```

Search can then be implemented to look for the appropriate metadata, and leverage it to omit the unwanted position data from search.

#### Potential for breaking existing code

These changes add the new property "metadata" to the base type from which all node schema derive.
If you have existing node schema subclasses that include a property of this name, there is a chance for potential conflict here that could be breaking.
If you encounter issues here, consider renaming your property or leveraging the new metadata support.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"handletable",
"incrementality",
"injective",
"insertable",
"losslessly",
"mitigations",
"mocharc",
Expand All @@ -61,7 +62,7 @@
"unacked",
"unaugmented",
"undoprovider",
"unsequenced",
"unsequenced"
],

// Enable biome as default formatter, and disable rules that disagree with it
Expand Down
170 changes: 95 additions & 75 deletions examples/apps/ai-collab/src/types/sharedTreeAppSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,99 +3,119 @@
* Licensed under the MIT License.
*/

import {
SchemaFactory,
Tree,
TreeViewConfiguration,
type TreeNode,
} from "@fluidframework/tree";
import { Tree, TreeViewConfiguration, type TreeNode } from "@fluidframework/tree";
import { SchemaFactoryAlpha } from "@fluidframework/tree/alpha";
import { SharedTree } from "fluid-framework";

// The string passed to the SchemaFactory should be unique
const sf = new SchemaFactory("ai-collab-sample-application");
const sf = new SchemaFactoryAlpha("ai-collab-sample-application");

// NOTE that there is currently a bug with the ai-collab library that requires us to rearrange the keys of each type to not have the same first key.

export class SharedTreeTask extends sf.object("Task", {
title: sf.required(sf.string, {
export class SharedTreeTask extends sf.object(
"Task",
{
title: sf.required(sf.string, {
metadata: {
description: `The title of the task.`,
},
}),
id: sf.identifier,
description: sf.required(sf.string, {
metadata: {
description: `The description of the task.`,
},
}),
priority: sf.required(sf.string, {
metadata: {
description: `The priority of the task which can ONLY be one of three levels: "Low", "Medium", "High" (case-sensitive).`,
},
}),
complexity: sf.required(sf.number, {
metadata: {
description: `The complexity of the task as a fibonacci number.`,
},
}),
status: sf.required(sf.string, {
metadata: {
description: `The status of the task which can ONLY be one of the following values: "To Do", "In Progress", "Done" (case-sensitive).`,
},
}),
assignee: sf.required(sf.string, {
metadata: {
description: `The name of the tasks assignee e.g. "Bob" or "Alice".`,
},
}),
},
{
metadata: {
description: `The title of the task.`,
description: `A task that can be assigned to an engineer.`,
},
}),
id: sf.identifier,
description: sf.required(sf.string, {
metadata: {
description: `The description of the task.`,
},
}),
priority: sf.required(sf.string, {
metadata: {
description: `The priority of the task which can ONLY be one of three levels: "Low", "Medium", "High" (case-sensitive).`,
},
}),
complexity: sf.required(sf.number, {
metadata: {
description: `The complexity of the task as a fibonacci number.`,
},
}),
status: sf.required(sf.string, {
metadata: {
description: `The status of the task which can ONLY be one of the following values: "To Do", "In Progress", "Done" (case-sensitive).`,
},
}),
assignee: sf.required(sf.string, {
metadata: {
description: `The name of the tasks assignee e.g. "Bob" or "Alice".`,
},
}),
}) {}
},
) {}

export class SharedTreeTaskList extends sf.array("TaskList", SharedTreeTask) {}

export class SharedTreeEngineer extends sf.object("Engineer", {
name: sf.required(sf.string, {
metadata: {
description: `The name of an engineer whom can be assigned to a task.`,
},
}),
id: sf.identifier,
skills: sf.required(sf.string, {
metadata: {
description: `A description of the engineers skills which influence what types of tasks they should be assigned to.`,
},
}),
maxCapacity: sf.required(sf.number, {
export class SharedTreeEngineer extends sf.object(
"Engineer",
{
name: sf.required(sf.string, {
metadata: {
description: `The name of the engineer.`,
},
}),
id: sf.identifier,
skills: sf.required(sf.string, {
metadata: {
description: `A description of the engineer's skills, which influence what types of tasks they should be assigned to.`,
},
}),
maxCapacity: sf.required(sf.number, {
metadata: {
description: `The maximum capacity of tasks this engineer can handle, measured in task complexity points.`,
},
}),
},
{
metadata: {
description: `The maximum capacity of tasks this engineer can handle measured in in task complexity points.`,
description: `An engineer to whom tasks may be assigned.`,
},
}),
}) {}
},
) {}

export class SharedTreeEngineerList extends sf.array("EngineerList", SharedTreeEngineer) {}

export class SharedTreeTaskGroup extends sf.object("TaskGroup", {
description: sf.required(sf.string, {
metadata: {
description: `The description of the task group, which is a collection of tasks and engineers that can be assigned to said tasks.`,
},
}),
id: sf.identifier,
title: sf.required(sf.string, {
export class SharedTreeTaskGroup extends sf.object(
"TaskGroup",
{
description: sf.required(sf.string, {
metadata: {
description: `The description of the task group.`,
},
}),
id: sf.identifier,
title: sf.required(sf.string, {
metadata: {
description: `The title of the task group.`,
},
}),
tasks: sf.required(SharedTreeTaskList, {
metadata: {
description: `The lists of tasks within this task group.`,
},
}),
engineers: sf.required(SharedTreeEngineerList, {
metadata: {
description: `The lists of engineers within this task group to whom tasks may be assigned.`,
},
}),
},
{
metadata: {
description: `The title of the task group.`,
description: "A collection of tasks and engineers to whom tasks may be assigned.",
},
}),
tasks: sf.required(SharedTreeTaskList, {
metadata: {
description: `The lists of tasks within this task group.`,
},
}),
engineers: sf.required(SharedTreeEngineerList, {
metadata: {
description: `The lists of engineers within this task group which can be assigned to tasks.`,
},
}),
}) {}
},
) {}

export class SharedTreeTaskGroupList extends sf.array("TaskGroupList", SharedTreeTaskGroup) {}

Expand Down
Loading
Loading