Skip to content

ENG-1301: Import relations when a node is imported#847

Open
trangdoan982 wants to merge 16 commits intomainfrom
eng-1301-import-relations-when-a-node-is-imported
Open

ENG-1301: Import relations when a node is imported#847
trangdoan982 wants to merge 16 commits intomainfrom
eng-1301-import-relations-when-a-node-is-imported

Conversation

@trangdoan982
Copy link
Collaborator

@trangdoan982 trangdoan982 commented Mar 2, 2026

@linear
Copy link

linear bot commented Mar 2, 2026

@supabase
Copy link

supabase bot commented Mar 2, 2026

This pull request has been ignored for the connected project zytfjzqyijgagqxrzbmz because there are no changes detected in packages/database/supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

devin-ai-integration[bot]

This comment was marked as resolved.

@trangdoan982 trangdoan982 requested a review from maparent March 2, 2026 15:38
Copy link
Collaborator

@maparent maparent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of this we'll look at together. Let's plan a joint session.

devin-ai-integration[bot]

This comment was marked as resolved.

graphite-app[bot]

This comment was marked as resolved.

@trangdoan982 trangdoan982 force-pushed the eng-1301-import-relations-when-a-node-is-imported branch from a03a9e9 to da9c498 Compare March 4, 2026 17:20
devin-ai-integration[bot]

This comment was marked as resolved.

@trangdoan982 trangdoan982 force-pushed the eng-1301-import-relations-when-a-node-is-imported branch from da9c498 to 7a86600 Compare March 9, 2026 15:54
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 13 additional findings in Devin Review.

Open in Devin Review

.from("my_concepts")
.select("id, source_local_id")
.in("id", [sourceSchemaId, destSchemaId]);
if (schemaRows && schemaRows.length === 2) {
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 length === 2 check silently skips triple creation for self-referencing or same-schema relations

In importRelationsForImportedNodes, the check conceptSchemas.length === 2 at line 287 fails when sourceData.id === destData.id (a self-referencing relation where both endpoints are the same concept), because .in('id', [X, X]) with duplicate IDs returns only 1 row. Similarly, schemaRows.length === 2 at line 300 fails when sourceSchemaId === destSchemaId (both endpoints share the same node type schema). This silently skips the findOrCreateTriple call, meaning no discourse relation triple is created in settings for these relations. The codebase explicitly supports self-referencing relations — see apps/obsidian/src/components/canvas/utils/relationJsonUtils.ts:91 which handles relation.sourceId === relation.destinationId.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting; I am not sure we want to support that but we don't exclude it yet.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@@ -0,0 +1,176 @@
/**
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this script is mostly for my local testing to create group quickly. i've been using it for a while and it doesn't touch any part of the codebase but happy to delete it as well

@trangdoan982
Copy link
Collaborator Author

@maparent here are some big changes since the last time you reviewed:

  • I addressed your comments on query optimization. Thank you for the notes!
  • import the relation triple as well (not just relation type). the order is import node type -> relation type -> relation triplet. so the triplet will use whatever is resolved from previous steps
  • add the "provisional" marker on the imported relation. but no UI change yet. all imported relations will have this field marked to false for now
  • added importPreview page where we tell users how many schemas and relations are being imported implicitly.
image image
  • biggest change is how we resolve the endpoint of a relation. now every imported relation's source/dest will resolve to Rid -> this way we can parse both local and remote node using 1 method. my argument is that it's also good to know that the relation was created when the node was imported. but let me know what you think.

Cases I tested:

  • import relations with nodes imported from the same vault
  • Vault 1: import node from vault2 -> create a relation btw local node and imported node -> publish local node -> Vault 2 import node from vault 1, see that the relations were resolved correctly

@trangdoan982 trangdoan982 requested a review from maparent March 9, 2026 21:07
Copy link
Collaborator

@maparent maparent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Of my comments, two are real code change requests, and after that I'll approve.
Otherwise, thanks for all the great work with optimizations, and I like how you handled fallbacks within the QueryEngine.
I did some testing, that seems to work great.
In terms of behaviour... I'm actually not sure why we need the preview mode in most cases. We're asking people to confirm a choice they just made. There is one good reason: when we import a schema, because it has unfortunate consequences: We cannot edit or remove it. (Also unhappy with that.)
SO two recommendation:

  1. Make handling imported schemas a very high priority after this is merged.
  2. Could we skip the validation screen when there is no schema import?
    The latter is a design decision, and may need to be validated with Joel, Johnny and/or Michael. So not sure we should block merge on it. But I think it's worth asking: I really don't see the point of that validation step if there's no schema change.

.from("my_concepts")
.select("id, source_local_id")
.in("id", [sourceSchemaId, destSchemaId]);
if (schemaRows && schemaRows.length === 2) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting; I am not sure we want to support that but we don't exclude it yet.

// Batch fetch schema_id for all endpoint concepts
const conceptIdToSchemaId = new Map<number, number>();
if (allEndpointConceptIds.length > 0) {
const { data: conceptSchemas } = await client
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's right but checking: allConceptEndpointIds should be larger than selectedNotes, right?
I had a doubt because of the filter for local+imported nodes in L195.
(Otherwise I'd think about whether we could get the data for this by adding id to the select at line 86... but almost certainly overkill.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes it should >= selectedNodes. you want to add the check here instead of >0 check?


// Check if this triplet already exists in discourseRelations
// We need to check using the mapped local ids (match by id first, then by name)
const resolveLocalNodeTypeId = (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not vital, since not hitting the db, but I'd have cached this, it's still a search per instance. Oh well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what you mean here?

@maparent
Copy link
Collaborator

I'm having second thoughts. I agreed, and I think it was the right call, that imported schemata should not be edited in the short term. But I think that schemata (imported or not) should be delete-able, as long as they have no live instances. Again, this would do much to mitigate the consequences of importation. We'd need buy-on on that, and I'm willing to help to code it, but I think that importing un-delete-able schemata is a design mistake.
Whether we correct this in this PR or another is a judgment call.

@trangdoan982 trangdoan982 requested a review from maparent March 12, 2026 22:41
Comment on lines +310 to +325
const { mapNodeTypeIdToLocal } = await import("./importNodes.js");
mappedSourceNodeTypeId = await mapNodeTypeIdToLocal({
plugin,
client,
sourceSpaceId: spaceId,
sourceSpaceUri: spaceUri,
sourceNodeTypeId: remoteSourceNodeTypeId,
});
mappedDestNodeTypeId = await mapNodeTypeIdToLocal({
plugin,
client,
sourceSpaceId: spaceId,
sourceSpaceUri: spaceUri,
sourceNodeTypeId: remoteDestNodeTypeId,
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect Import Statement Inside Loop

The dynamic import statement is executed inside the relation processing loop:

const { mapNodeTypeIdToLocal } = await import("./importNodes.js");

This will attempt to import the module on every iteration, which is inefficient and could cause issues. The import should be moved outside the function at the top of the file:

import { mapNodeTypeIdToLocal } from "./importNodes.js";

Or if circular dependency is a concern, import it once before the loop starts.

Suggested change
const { mapNodeTypeIdToLocal } = await import("./importNodes.js");
mappedSourceNodeTypeId = await mapNodeTypeIdToLocal({
plugin,
client,
sourceSpaceId: spaceId,
sourceSpaceUri: spaceUri,
sourceNodeTypeId: remoteSourceNodeTypeId,
});
mappedDestNodeTypeId = await mapNodeTypeIdToLocal({
plugin,
client,
sourceSpaceId: spaceId,
sourceSpaceUri: spaceUri,
sourceNodeTypeId: remoteDestNodeTypeId,
});
}
mappedSourceNodeTypeId = await mapNodeTypeIdToLocal({
plugin,
client,
sourceSpaceId: spaceId,
sourceSpaceUri: spaceUri,
sourceNodeTypeId: remoteSourceNodeTypeId,
});
mappedDestNodeTypeId = await mapNodeTypeIdToLocal({
plugin,
client,
sourceSpaceId: spaceId,
sourceSpaceUri: spaceUri,
sourceNodeTypeId: remoteDestNodeTypeId,
});
}

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually this is worth fixing!

@maparent
Copy link
Collaborator

I approved but the checks are all failing. More annoying, the validate check passes locally, and the error message is obviously wrong.
The lint issue is real, and has to do with createGroup.ts. You could fix or remove the file.
If you fix, it's asking about adding a variable to turbo, I would suppress that warning instead.

trangdoan982 and others added 7 commits March 13, 2026 09:48
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@maparent maparent force-pushed the eng-1301-import-relations-when-a-node-is-imported branch from 3bc25c4 to 3479f98 Compare March 13, 2026 13:50
@maparent
Copy link
Collaborator

The build error was fixed by rebasing on main. It seems the CI does its own flawed rebasing.

@maparent
Copy link
Collaborator

Ok still approved but do address the graphite comment, and the lint issue.

Copy link
Collaborator Author

trangdoan982 commented Mar 13, 2026

yes thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants