-
Notifications
You must be signed in to change notification settings - Fork 229
feat(data-modeling): add add field button to object in diagram COMPASS-9742 #7300
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
base: main
Are you sure you want to change the base?
Conversation
packages/compass-data-modeling/src/components/diagram/object-field-type.tsx
Show resolved
Hide resolved
export const getBaseFieldsFromSchema = ({ | ||
jsonSchema, | ||
}: { | ||
jsonSchema: MongoDBJSONSchema; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't love two functions that do pretty much the same thing, but it seemed better than doing something like:
export const getFieldsFromSchema = ({
jsonSchema,
renderOptions,
}: {
jsonSchema: MongoDBJSONSchema;
renderOptions?: {
highlightedFields: FieldPath[];
selectedField?: FieldPath;
onClickAddNestedField: (parentFieldPath: string[]) => void;
};
}): NodeProps['fields'] => {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Big disclaimer that I'm just sharing some thoughts on this. I think what you have here is probably good enough (at least for now for sure), but as it bothers you I thought I'd try to provide some guidance here:
You are right that having two separate functions like that is not great: there's now no easy way to make sure that what we get for calculation purposes is the same as what we get for rendering purposes. That's not the only solution for this problem though 🙂 I mentioned this before, it's all about how you compose this: you have "default" level here, just pure data that contains serializeable information that should be enough to render this, and then you have "rendering" level that takes this data (maybe more stuff that's only rendering related, like those callbacks) and maps this to UI. The latter doesn't even need to live outside of React code FWIW, but if needed the logic of this mapping can be encapsulated into a function too.
So to summarise something that would allow you to compose the code in a way that builds up on the "base" functions and adds UI on top of it would probably be the cleanest way to deal with this instead of keeping the "base" completely separate. You can even have more "layers" here, why not, it doesn't really all need to happen in just two steps. Something like this pseudo-code:
type Node = NodeProps /* & { any extra stuff we need as long as it doesn't conflict with NodeProps } */
type Field = NodeProps['fields'][number]
function getBaseNode(collection: Collection): Node;
function getBaseFields(collection: Collection): Field[];
function getBaseNodes(collections: Collection[]): Node[] {
return collections.map(collection => {
return {
...getBaseNode(collection),
fields: getBaseFields(collection)
}
});
}
function getUINode(node: Node): Node
function getUIFields(fields: Field[]): Field[]
function getNodesForUI(nodes: Node[]) {
return nodes.map(node => {
return {
...getUINode(node),
fields: getUIFields(fields)
}
})
}
This has a slight downside of iterating over fields multiple times, but I would be hesitant to consider this an issue unless we really see this performing badly and even if it does, now you have shared building blocks to build two separate functions, but share the logic, we also have memoization tools to help us deal with that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds functionality to allow users to add fields to object types directly from the diagram view in the data modeling feature. The implementation includes both the UI components to display "add field" buttons on object fields and the corresponding E2E tests to validate the new functionality.
Key changes:
- Added "add field" button to object field types in the diagram
- Created new E2E test helper functions for diagram interaction
- Extended field editing functionality to support nested field creation
Reviewed Changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.
Show a summary per file
File | Description |
---|---|
packages/compass-e2e-tests/tests/data-modeling-tab.test.ts | Added comprehensive E2E test for field editing and new drag helper function |
packages/compass-e2e-tests/helpers/selectors.ts | Added selectors for new add field buttons and field type combobox |
packages/compass-e2e-tests/helpers/commands/set-multi-combo-box-value.ts | New helper command for setting multiple combobox values |
packages/compass-data-modeling/src/utils/schema.ts | Enhanced field name generation to support nested object paths |
packages/compass-data-modeling/src/utils/nodes-and-edges.tsx | Added object field type component and nested field functionality |
packages/compass-data-modeling/src/components/diagram/object-field-type.tsx | New component rendering object type with add field button |
packages/compass-data-modeling/src/store/diagram.ts | Added action handler for creating nested fields |
packages/compass-data-modeling/src/components/diagram-editor.tsx | Connected nested field creation to the diagram editor |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
packages/compass-data-modeling/src/utils/nodes-and-edges.spec.tsx
Outdated
Show resolved
Hide resolved
export const getBaseFieldsFromSchema = ({ | ||
jsonSchema, | ||
}: { | ||
jsonSchema: MongoDBJSONSchema; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Big disclaimer that I'm just sharing some thoughts on this. I think what you have here is probably good enough (at least for now for sure), but as it bothers you I thought I'd try to provide some guidance here:
You are right that having two separate functions like that is not great: there's now no easy way to make sure that what we get for calculation purposes is the same as what we get for rendering purposes. That's not the only solution for this problem though 🙂 I mentioned this before, it's all about how you compose this: you have "default" level here, just pure data that contains serializeable information that should be enough to render this, and then you have "rendering" level that takes this data (maybe more stuff that's only rendering related, like those callbacks) and maps this to UI. The latter doesn't even need to live outside of React code FWIW, but if needed the logic of this mapping can be encapsulated into a function too.
So to summarise something that would allow you to compose the code in a way that builds up on the "base" functions and adds UI on top of it would probably be the cleanest way to deal with this instead of keeping the "base" completely separate. You can even have more "layers" here, why not, it doesn't really all need to happen in just two steps. Something like this pseudo-code:
type Node = NodeProps /* & { any extra stuff we need as long as it doesn't conflict with NodeProps } */
type Field = NodeProps['fields'][number]
function getBaseNode(collection: Collection): Node;
function getBaseFields(collection: Collection): Field[];
function getBaseNodes(collections: Collection[]): Node[] {
return collections.map(collection => {
return {
...getBaseNode(collection),
fields: getBaseFields(collection)
}
});
}
function getUINode(node: Node): Node
function getUIFields(fields: Field[]): Field[]
function getNodesForUI(nodes: Node[]) {
return nodes.map(node => {
return {
...getUINode(node),
fields: getUIFields(fields)
}
})
}
This has a slight downside of iterating over fields multiple times, but I would be hesitant to consider this an issue unless we really see this performing badly and even if it does, now you have shared building blocks to build two separate functions, but share the logic, we also have memoization tools to help us deal with that.
@@ -183,6 +186,8 @@ const DiagramContent: React.FunctionComponent<{ | |||
: undefined, | |||
onClickAddNewFieldToCollection: () => | |||
onAddNewFieldToCollection(coll.ns), | |||
onClickAddNestedField: (parentFieldPath: string[]) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was testing that the next field name logic is working fine and managed to get into some weird state where if I rename the field and then without unfocusing first I click on the "add new field" button, the new field is created with the name "interact" in the diagram, but the drawer shows the correct one, I can't figure out where the "interact" part is even coming from
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ohhh, I was rewatching the gif and it seems to replace the next field in the document that is a sibling of an object this is being added for
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ooo nice catch
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trying to get my compass review legs under me, read through and looks, good Sergey caught some more interesting things than I ^
curious about one small thing
const fieldPathToTraverse = [...parentFieldPath]; | ||
let parentJSONSchema: MongoDBJSONSchema | undefined = jsonSchema; | ||
while (fieldPathToTraverse.length > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NBD but it stands out to me that we clone the array and shift out, but don't actually rely on the modification of the array later? could this just be for-of over the input array?
COMPASS-9742
Adds some field editing e2e testing as well. Left two comments/questions for reviewers.
add.field.to.object.mp4