Skip to content

Commit 4990c69

Browse files
committed
eng-1344 f10b upload obsidian relations and their schemas
1 parent 6983308 commit 4990c69

4 files changed

Lines changed: 380 additions & 58 deletions

File tree

apps/obsidian/src/utils/conceptConversion.ts

Lines changed: 196 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
/* eslint-disable @typescript-eslint/naming-convention */
21
import type { TFile } from "obsidian";
3-
import type { DiscourseNode } from "~/types";
2+
import type {
3+
DiscourseNode,
4+
DiscourseRelation,
5+
DiscourseRelationType,
6+
RelationInstance,
7+
} from "~/types";
48
import type { SupabaseContext } from "./supabaseContext";
9+
import type { DiscourseNodeInVault } from "./getDiscourseNodes";
510
import type { LocalConceptDataInput } from "@repo/database/inputTypes";
611
import type { ObsidianDiscourseNodeData } from "./syncDgNodesToSupabase";
712
import type { Json } from "@repo/database/dbTypes";
8-
import DiscourseGraphPlugin from "..";
13+
import { DGSupabaseClient } from "@repo/database/lib/client";
914

1015
/**
1116
* Get extra data (author, timestamps) from file metadata
@@ -14,6 +19,7 @@ const getNodeExtraData = (
1419
file: TFile,
1520
accountLocalId: string,
1621
): {
22+
/* eslint-disable @typescript-eslint/naming-convention */
1723
author_local_id: string;
1824
created: string;
1925
last_modified: string;
@@ -23,6 +29,7 @@ const getNodeExtraData = (
2329
created: new Date(file.stat.ctime).toISOString(),
2430
last_modified: new Date(file.stat.mtime).toISOString(),
2531
};
32+
/* eslint-enable @typescript-eslint/naming-convention */
2633
};
2734

2835
export const discourseNodeSchemaToLocalConcept = ({
@@ -34,8 +41,23 @@ export const discourseNodeSchemaToLocalConcept = ({
3441
node: DiscourseNode;
3542
accountLocalId: string;
3643
}): LocalConceptDataInput => {
37-
const { description, template, id, name, created, modified, ...otherData } =
38-
node;
44+
const {
45+
description,
46+
template,
47+
id,
48+
name,
49+
created,
50+
modified,
51+
importedFromRid,
52+
...otherData
53+
} = node;
54+
/* eslint-disable @typescript-eslint/naming-convention */
55+
const literal_content: Record<string, Json> = {
56+
label: name,
57+
source_data: otherData,
58+
};
59+
if (template) literal_content.template = template;
60+
if (importedFromRid) literal_content.importedFromRid = importedFromRid;
3961
return {
4062
space_id: context.spaceId,
4163
name,
@@ -45,11 +67,107 @@ export const discourseNodeSchemaToLocalConcept = ({
4567
created: new Date(created).toISOString(),
4668
last_modified: new Date(modified).toISOString(),
4769
description: description,
48-
literal_content: {
49-
label: name,
50-
template: template,
51-
source_data: otherData,
70+
literal_content,
71+
/* eslint-enable @typescript-eslint/naming-convention */
72+
};
73+
};
74+
75+
const STANDARD_ROLES = ["source", "destination"];
76+
77+
export const discourseRelationTypeToLocalConcept = ({
78+
context,
79+
relationType,
80+
accountLocalId,
81+
}: {
82+
context: SupabaseContext;
83+
relationType: DiscourseRelationType;
84+
accountLocalId: string;
85+
}): LocalConceptDataInput => {
86+
const {
87+
id,
88+
label,
89+
complement,
90+
created,
91+
modified,
92+
importedFromRid,
93+
...otherData
94+
} = relationType;
95+
// eslint-disable-next-line @typescript-eslint/naming-convention
96+
const literal_content: Record<string, Json> = {
97+
roles: STANDARD_ROLES,
98+
label,
99+
complement,
100+
// eslint-disable-next-line @typescript-eslint/naming-convention
101+
source_data: otherData,
102+
};
103+
if (importedFromRid) literal_content.importedFromRid = importedFromRid;
104+
105+
return {
106+
/* eslint-disable @typescript-eslint/naming-convention */
107+
space_id: context.spaceId,
108+
name: label,
109+
source_local_id: id,
110+
is_schema: true,
111+
author_local_id: accountLocalId,
112+
created: new Date(created).toISOString(),
113+
last_modified: new Date(modified).toISOString(),
114+
literal_content,
115+
/* eslint-enable @typescript-eslint/naming-convention */
116+
};
117+
};
118+
119+
export const discourseRelationTripleSchemaToLocalConcept = ({
120+
context,
121+
relation,
122+
accountLocalId,
123+
nodeTypesById,
124+
relationTypesById,
125+
}: {
126+
context: SupabaseContext;
127+
relation: DiscourseRelation;
128+
accountLocalId: string;
129+
nodeTypesById: Record<string, DiscourseNode>;
130+
relationTypesById: Record<string, DiscourseRelationType>;
131+
}): LocalConceptDataInput => {
132+
const {
133+
id,
134+
relationshipTypeId,
135+
sourceId,
136+
destinationId,
137+
created,
138+
modified,
139+
importedFromRid,
140+
} = relation;
141+
const sourceName = nodeTypesById[sourceId]?.name ?? sourceId;
142+
const destinationName = nodeTypesById[destinationId]?.name ?? destinationId;
143+
const relationType = relationTypesById[relationshipTypeId];
144+
if (!relationType)
145+
throw new Error(`missing relation type ${relationshipTypeId}`);
146+
const { label, complement } = relationType;
147+
// eslint-disable-next-line @typescript-eslint/naming-convention
148+
const literal_content: Record<string, Json> = {
149+
roles: STANDARD_ROLES,
150+
label,
151+
complement,
152+
};
153+
if (importedFromRid) literal_content.importedFromRid = importedFromRid;
154+
155+
return {
156+
/* eslint-disable @typescript-eslint/naming-convention */
157+
space_id: context.spaceId,
158+
name: `${sourceName} -${label}-> ${destinationName}`,
159+
source_local_id: id,
160+
is_schema: true,
161+
author_local_id: accountLocalId,
162+
created: new Date(created).toISOString(),
163+
last_modified: new Date(modified).toISOString(),
164+
literal_content,
165+
local_reference_content: {
166+
relation_type: relationshipTypeId,
167+
source: sourceId,
168+
destination: destinationId,
52169
},
170+
/* eslint-enable @typescript-eslint/naming-convention */
53171
};
54172
};
55173

@@ -66,21 +184,78 @@ export const discourseNodeInstanceToLocalConcept = ({
66184
accountLocalId: string;
67185
}): LocalConceptDataInput => {
68186
const extraData = getNodeExtraData(nodeData.file, accountLocalId);
69-
const { nodeInstanceId, nodeTypeId, ...otherData } = nodeData.frontmatter;
187+
const { nodeInstanceId, nodeTypeId, importedFromRid, ...otherData } =
188+
nodeData.frontmatter;
189+
// eslint-disable-next-line @typescript-eslint/naming-convention
190+
const literal_content: Record<string, Json> = {
191+
label: nodeData.file.basename,
192+
// eslint-disable-next-line @typescript-eslint/naming-convention
193+
source_data: otherData as unknown as Json,
194+
};
195+
if (importedFromRid && typeof importedFromRid === "string")
196+
literal_content.importedFromRid = importedFromRid;
70197
return {
198+
/* eslint-disable @typescript-eslint/naming-convention */
71199
space_id: context.spaceId,
72200
name: nodeData.file.path,
73201
source_local_id: nodeInstanceId as string,
74202
schema_represented_by_local_id: nodeTypeId as string,
75203
is_schema: false,
76-
literal_content: {
77-
label: nodeData.file.basename,
78-
source_data: otherData as unknown as Json,
79-
},
204+
literal_content,
205+
/* eslint-enable @typescript-eslint/naming-convention */
80206
...extraData,
81207
};
82208
};
83209

210+
export const relationInstanceToLocalConcept = ({
211+
context,
212+
relationTypesById,
213+
allNodesById,
214+
relationInstanceData,
215+
}: {
216+
context: SupabaseContext;
217+
relationTypesById: Record<string, DiscourseRelationType>;
218+
allNodesById: Record<string, DiscourseNodeInVault>;
219+
relationInstanceData: RelationInstance;
220+
}): LocalConceptDataInput | null => {
221+
const { type, created, lastModified, source, destination, importedFromRid } =
222+
relationInstanceData;
223+
const relationType = relationTypesById[type];
224+
225+
if (!relationType) {
226+
console.error("Missing relationType id " + type);
227+
return null;
228+
}
229+
const sourceNode = allNodesById[source];
230+
const destinationNode = allNodesById[destination];
231+
if (
232+
sourceNode?.frontmatter.importedFromRid ||
233+
destinationNode?.frontmatter.importedFromRid
234+
)
235+
return null; // punt relation to imported nodes for now.
236+
// otherwise put the importedFromRid in source, dest.
237+
238+
/* eslint-disable @typescript-eslint/naming-convention */
239+
const literal_content: Record<string, Json> = {};
240+
if (importedFromRid) literal_content.importedFromRid = importedFromRid;
241+
return {
242+
space_id: context.spaceId,
243+
name: `[[${sourceNode ? sourceNode.file.basename : source}]] -${relationType.label}-> [[${destinationNode ? destinationNode.file.basename : destination}]]`,
244+
source_local_id: relationInstanceData.id,
245+
author_local_id: relationInstanceData.author,
246+
schema_represented_by_local_id: type,
247+
is_schema: false,
248+
created: new Date(created).toISOString(),
249+
last_modified: new Date(lastModified ?? created).toISOString(),
250+
literal_content,
251+
local_reference_content: {
252+
source,
253+
destination,
254+
},
255+
/* eslint-enable @typescript-eslint/naming-convention */
256+
};
257+
};
258+
84259
export const relatedConcepts = (concept: LocalConceptDataInput): string[] => {
85260
const relations = Object.values(
86261
concept.local_reference_content || {},
@@ -103,23 +278,26 @@ const orderConceptsRec = (
103278
ordered: LocalConceptDataInput[],
104279
concept: LocalConceptDataInput,
105280
remainder: { [key: string]: LocalConceptDataInput },
281+
processed: Set<string>,
106282
): Set<string> => {
107283
const relatedConceptIds = relatedConcepts(concept);
108284
let missing: Set<string> = new Set();
109285
while (relatedConceptIds.length > 0) {
110286
const relatedConceptId = relatedConceptIds.shift()!;
287+
if (processed.has(relatedConceptId)) continue;
111288
const relatedConcept = remainder[relatedConceptId];
112289
if (relatedConcept === undefined) {
113290
missing.add(relatedConceptId);
114291
} else {
115292
missing = new Set([
116293
...missing,
117-
...orderConceptsRec(ordered, relatedConcept, remainder),
294+
...orderConceptsRec(ordered, relatedConcept, remainder, processed),
118295
]);
119296
delete remainder[relatedConceptId];
120297
}
121298
}
122299
ordered.push(concept);
300+
processed.add(concept.source_local_id!);
123301
delete remainder[concept.source_local_id!];
124302
return missing;
125303
};
@@ -143,14 +321,15 @@ export const orderConceptsByDependency = (
143321
);
144322
const ordered: LocalConceptDataInput[] = [];
145323
let missing: Set<string> = new Set();
324+
const processed: Set<string> = new Set();
146325
while (Object.keys(conceptById).length > 0) {
147326
const first = Object.values(conceptById)[0];
148327
if (!first) break;
149328
missing = new Set([
150329
...missing,
151-
...orderConceptsRec(ordered, first, conceptById),
330+
...orderConceptsRec(ordered, first, conceptById, processed),
152331
]);
153-
if (missing.size > 0) console.error(`missing: ${[...missing]}`);
332+
if (missing.size > 0) console.error(`missing: ${[...missing].join(", ")}`);
154333
}
155334
return { ordered, missing: Array.from(missing) };
156335
};

apps/obsidian/src/utils/relationsStore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type DiscourseGraphPlugin from "~/index";
44
import { ensureNodeInstanceId } from "~/utils/nodeInstanceId";
55
import { checkAndCreateFolder } from "~/utils/file";
66
import { getVaultId } from "./supabaseContext";
7-
import { RelationInstance } from "../types";
7+
import type { RelationInstance } from "~/types";
88

99
const RELATIONS_FILE_NAME = "relations.json";
1010
const RELATIONS_FILE_VERSION = 1;

0 commit comments

Comments
 (0)