-
Notifications
You must be signed in to change notification settings - Fork 7
ENG-2142 Map source to the Roam referenced node on pull
#1382
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
Open
sid597
wants to merge
21
commits into
main
from
eng-2142-map-source-to-the-roam-referenced-node-on-pull
Open
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
d3e18ad
ENG-2157 Add decorateTitle helper for rebuilding titles from core_title
sid597 de2811d
ENG-2157 Decorate imported node titles in Obsidian from core_title
sid597 907869b
Keep the incoming title when the format has placeholders core_title c…
sid597 b44e862
ENG-2156 Decorate imported node titles in Roam from core_title
sid597 7df22d0
Refresh the legacy node type cache after creating a type from an import
sid597 fad4f74
ENG-2156 Treat Page, Block and Any as reserved node type names on import
sid597 555d8b5
Merge branch 'main' into eng-2156-decorate-imported-node-titles-in-ro…
sid597 43b9d4a
ENG-2156 Prefer the top-level schema format through a shared dual-rea…
sid597 03f9052
ENG-2156 Correct the resolveSchemaFormat comment on who writes the to…
sid597 f5fad36
ENG-2156 Note that Roam now writes the top-level schema format
sid597 e167222
Merge remote-tracking branch 'origin/main' into eng-2156-decorate-imp…
sid597 08e051e
ENG-2142 Give cross-space slot RIDs the platform subtype
sid597 31854b8
ENG-2142 Name the resolved source in imported Roam titles
sid597 07b61b2
ENG-2142 Show source warnings with warning intent and cover the remai…
sid597 6c32073
Merge remote-tracking branch 'origin/main' into eng-2156-decorate-imp…
sid597 e083e2c
Merge remote-tracking branch 'origin/main' into eng-2156-decorate-imp…
sid597 ebbb69f
Merge remote-tracking branch 'origin/eng-2156-decorate-imported-node-…
sid597 a83cdaf
Merge remote-tracking branch 'origin/main' into codex/workday-2026090…
sid597 d73e869
ENG-2142 Reuse relation schemas with multiple query patterns
sid597 a50f5c0
Return promises explicitly in relation import test mocks
sid597 80acda8
Bind shared source identifiers in ENG-2142 Roam queries
sid597 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| import { beforeEach, describe, expect, it, vi } from "vitest"; | ||
| import { findTargetUid, sharedReferenceRid } from "~/utils/findTargetUid"; | ||
| import { findImportedNodeUidBySourceRid } from "~/utils/importedSourceIdentity"; | ||
|
|
||
| vi.mock("~/utils/importedSourceIdentity", () => ({ | ||
| findImportedNodeUidBySourceRid: vi.fn(), | ||
| })); | ||
|
|
||
| const mockedFindImportedNodeUidBySourceRid = vi.mocked( | ||
| findImportedNodeUidBySourceRid, | ||
| ); | ||
|
|
||
| const LOCAL_GRAPH = "local-graph"; | ||
| const LOCAL_SPACE_URI = `https://roamresearch.com/#/app/${LOCAL_GRAPH}`; | ||
| const OBSIDIAN_SPACE_URI = "obsidian:vault-a"; | ||
| const roamQuery = vi.fn(); | ||
|
|
||
| beforeEach(() => { | ||
| vi.clearAllMocks(); | ||
| (globalThis as { window: unknown }).window = { | ||
| roamAlphaAPI: { graph: { name: LOCAL_GRAPH }, q: roamQuery }, | ||
| }; | ||
| roamQuery.mockReturnValue([]); | ||
| mockedFindImportedNodeUidBySourceRid.mockResolvedValue(null); | ||
| }); | ||
|
|
||
| describe("sharedReferenceRid", () => { | ||
| it("passes a RID through", () => { | ||
| expect( | ||
| sharedReferenceRid( | ||
| "orn:obsidian.note:vault-b/node-6", | ||
| OBSIDIAN_SPACE_URI, | ||
| ), | ||
| ).toBe("orn:obsidian.note:vault-b/node-6"); | ||
| }); | ||
|
|
||
| it("builds a note RID from a local id in an Obsidian space", () => { | ||
| expect(sharedReferenceRid("node-9", OBSIDIAN_SPACE_URI)).toBe( | ||
| "orn:obsidian.note:vault-a/node-9", | ||
| ); | ||
| }); | ||
|
|
||
| it("builds a URL RID from a local id in a Roam space", () => { | ||
| expect( | ||
| sharedReferenceRid( | ||
| "page-uid", | ||
| "https://roamresearch.com/#/app/other-graph", | ||
| ), | ||
| ).toBe("https://roamresearch.com/#/app/other-graph/page-uid"); | ||
| }); | ||
| }); | ||
|
|
||
| describe("findTargetUid", () => { | ||
| it("returns the local id of a RID in this graph when the page exists", async () => { | ||
| roamQuery.mockReturnValue([[1]]); | ||
|
|
||
| await expect( | ||
| findTargetUid(`${LOCAL_SPACE_URI}/page-uid`, OBSIDIAN_SPACE_URI), | ||
| ).resolves.toBe("page-uid"); | ||
| expect(roamQuery).toHaveBeenCalledWith( | ||
| "[:find (?e) :in $ ?uid :where [?e :block/uid ?uid]]", | ||
| "page-uid", | ||
| ); | ||
| expect(mockedFindImportedNodeUidBySourceRid).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it("passes a shared id containing query syntax as data", async () => { | ||
| const sharedId = 'page-uid"] [(= ?e ?e)] ; "'; | ||
| await expect(findTargetUid(sharedId, LOCAL_SPACE_URI)).resolves.toBeNull(); | ||
| expect(roamQuery).toHaveBeenCalledWith( | ||
| "[:find (?e) :in $ ?uid :where [?e :block/uid ?uid]]", | ||
| sharedId, | ||
| ); | ||
| }); | ||
|
|
||
| it("returns null for a RID in this graph whose page is missing", async () => { | ||
| await expect( | ||
| findTargetUid(`${LOCAL_SPACE_URI}/page-uid`, OBSIDIAN_SPACE_URI), | ||
| ).resolves.toBeNull(); | ||
| expect(mockedFindImportedNodeUidBySourceRid).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it("treats a local id of this graph's space as a page uid", async () => { | ||
| roamQuery.mockReturnValue([[1]]); | ||
|
|
||
| await expect(findTargetUid("page-uid", LOCAL_SPACE_URI)).resolves.toBe( | ||
| "page-uid", | ||
| ); | ||
| expect(mockedFindImportedNodeUidBySourceRid).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it("looks up a local id of the publisher's space as an imported note", async () => { | ||
| mockedFindImportedNodeUidBySourceRid.mockResolvedValue("imported-uid"); | ||
|
|
||
| await expect(findTargetUid("node-9", OBSIDIAN_SPACE_URI)).resolves.toBe( | ||
| "imported-uid", | ||
| ); | ||
| expect(mockedFindImportedNodeUidBySourceRid).toHaveBeenCalledWith( | ||
| "orn:obsidian.note:vault-a/node-9", | ||
| ); | ||
| expect(roamQuery).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it("looks up a RID of another space as imported", async () => { | ||
| await expect( | ||
| findTargetUid("orn:obsidian.note:vault-b/node-6", OBSIDIAN_SPACE_URI), | ||
| ).resolves.toBeNull(); | ||
| expect(mockedFindImportedNodeUidBySourceRid).toHaveBeenCalledWith( | ||
| "orn:obsidian.note:vault-b/node-6", | ||
| ); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
apps/roam/src/utils/__tests__/importSharedRelations.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| import { beforeEach, describe, expect, it, vi } from "vitest"; | ||
| import type { DGSupabaseClient } from "@repo/database/lib/client"; | ||
| import type { DiscourseRelation } from "~/utils/getDiscourseRelations"; | ||
| import { importSharedRelations } from "~/utils/importSharedRelations"; | ||
| import getDiscourseRelations from "~/utils/getDiscourseRelations"; | ||
| import { createRelationSchema } from "~/utils/createRelationSchema"; | ||
|
|
||
| vi.hoisted(() => { | ||
| vi.stubGlobal("window", { roamAlphaAPI: { graph: { name: "local" } } }); | ||
| }); | ||
| vi.mock("~/utils/getDiscourseRelations", () => ({ default: vi.fn() })); | ||
| vi.mock("~/utils/getDiscourseNodes", () => ({ | ||
| default: () => [{ type: "local-claim", text: "Claim" }], | ||
| })); | ||
| vi.mock("~/utils/importedSourceIdentity", () => ({ | ||
| getImportedSourceRids: () => Promise.resolve(new Set<string>()), | ||
| findImportedNodeUidBySourceRid: vi.fn(), | ||
| writeImportedSourceIdentity: vi.fn(), | ||
| })); | ||
| vi.mock("~/components/settings/utils/accessors", () => ({ | ||
| createDiscourseNodeType: vi.fn(), | ||
| })); | ||
| vi.mock("~/utils/createRelationSchema", () => ({ | ||
| createRelationSchema: vi.fn(), | ||
| })); | ||
| vi.mock("~/utils/createReifiedBlock", () => ({ | ||
| getReifiedRelations: () => Promise.resolve([]), | ||
| createReifiedRelation: vi.fn(), | ||
| })); | ||
| vi.mock("roamjs-components/writes", () => ({ deleteBlock: vi.fn() })); | ||
| vi.mock("~/utils/discoverSharedRelations", () => ({ | ||
| discoverSharedRelations: () => | ||
| Promise.resolve({ | ||
| relations: [], | ||
| relTypeSchemas: [], | ||
| nodeSchemas: [ | ||
| { | ||
| localId: "claim", | ||
| rid: "orn:obsidian.schema:remote/claim", | ||
| label: "Claim", | ||
| authorId: "author", | ||
| createdAt: new Date("2026-09-07"), | ||
| }, | ||
| ], | ||
| relTripleSchemas: [ | ||
| { | ||
| localId: "supports", | ||
| rid: "orn:obsidian.schema:remote/supports", | ||
| label: "Supports", | ||
| complement: "Supported by", | ||
| sourceType: "claim", | ||
| destinationType: "claim", | ||
| authorId: "author", | ||
| createdAt: new Date("2026-09-07"), | ||
| }, | ||
| ], | ||
| }), | ||
| })); | ||
|
|
||
| const relation = (id: string): DiscourseRelation => ({ | ||
| id, | ||
| label: "Supports", | ||
| complement: "Supported by", | ||
| source: "local-claim", | ||
| destination: "local-claim", | ||
| triples: [], | ||
| }); | ||
| const client = {} as DGSupabaseClient; | ||
|
|
||
| beforeEach(() => vi.clearAllMocks()); | ||
|
|
||
| describe("importSharedRelations schema matching", () => { | ||
| it("reuses one schema when its query patterns produce multiple matches", async () => { | ||
| vi.mocked(getDiscourseRelations).mockReturnValue([ | ||
| { | ||
| ...relation("local-supports"), | ||
| triples: [["source", "references", "destination"]], | ||
| }, | ||
| { | ||
| ...relation("local-supports"), | ||
| triples: [["source", "is in page", "destination"]], | ||
| }, | ||
| ]); | ||
| await expect(importSharedRelations(client, 7)).resolves.toBeUndefined(); | ||
| expect(createRelationSchema).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it("rejects matches to two different schemas", async () => { | ||
| vi.mocked(getDiscourseRelations).mockReturnValue([ | ||
| relation("supports-one"), | ||
| relation("supports-two"), | ||
| ]); | ||
| await expect(importSharedRelations(client, 7)).rejects.toThrow( | ||
| "multiple matches", | ||
| ); | ||
| expect(createRelationSchema).not.toHaveBeenCalled(); | ||
| }); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Warnings are listed with the failures in the same per-node form and counted in the title, so a yellow callout never shows bullets its title does not account for. The warning text says the title was kept, which is what distinguishes it from a failure.