Skip to content

Commit 4d43317

Browse files
authored
eng-1344 f10b upload obsidian relations and their schemas (#721)
1 parent 4f5eb59 commit 4d43317

4 files changed

Lines changed: 453 additions & 73 deletions

File tree

apps/obsidian/src/utils/conceptConversion.ts

Lines changed: 219 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
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 "..";
913

1014
/**
1115
* Get extra data (author, timestamps) from file metadata
@@ -14,6 +18,7 @@ const getNodeExtraData = (
1418
file: TFile,
1519
accountLocalId: string,
1620
): {
21+
/* eslint-disable @typescript-eslint/naming-convention */
1722
author_local_id: string;
1823
created: string;
1924
last_modified: string;
@@ -23,6 +28,7 @@ const getNodeExtraData = (
2328
created: new Date(file.stat.ctime).toISOString(),
2429
last_modified: new Date(file.stat.mtime).toISOString(),
2530
};
31+
/* eslint-enable @typescript-eslint/naming-convention */
2632
};
2733

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

@@ -66,21 +182,83 @@ export const discourseNodeInstanceToLocalConcept = ({
66182
accountLocalId: string;
67183
}): LocalConceptDataInput => {
68184
const extraData = getNodeExtraData(nodeData.file, accountLocalId);
69-
const { nodeInstanceId, nodeTypeId, ...otherData } = nodeData.frontmatter;
185+
const { nodeInstanceId, nodeTypeId, importedFromRid, ...otherData } =
186+
nodeData.frontmatter;
187+
// eslint-disable-next-line @typescript-eslint/naming-convention
188+
const literal_content: Record<string, Json> = {
189+
label: nodeData.file.basename,
190+
// eslint-disable-next-line @typescript-eslint/naming-convention
191+
source_data: otherData as unknown as Json,
192+
};
193+
if (importedFromRid && typeof importedFromRid === "string")
194+
literal_content.importedFromRid = importedFromRid;
70195
return {
196+
/* eslint-disable @typescript-eslint/naming-convention */
71197
space_id: context.spaceId,
72198
name: nodeData.file.path,
73199
source_local_id: nodeInstanceId as string,
74200
schema_represented_by_local_id: nodeTypeId as string,
75201
is_schema: false,
76-
literal_content: {
77-
label: nodeData.file.basename,
78-
source_data: otherData as unknown as Json,
79-
},
202+
literal_content,
203+
/* eslint-enable @typescript-eslint/naming-convention */
80204
...extraData,
81205
};
82206
};
83207

208+
export const relationInstanceToLocalConcept = ({
209+
context,
210+
relationTypesById,
211+
allNodesById,
212+
relationInstanceData,
213+
}: {
214+
context: SupabaseContext;
215+
relationTypesById: Record<string, DiscourseRelationType>;
216+
allNodesById: Record<string, DiscourseNodeInVault>;
217+
relationInstanceData: RelationInstance;
218+
}): LocalConceptDataInput | null => {
219+
const { type, created, lastModified, source, destination, importedFromRid } =
220+
relationInstanceData;
221+
const relationType = relationTypesById[type];
222+
223+
if (!relationType) {
224+
console.error("Missing relationType id " + type);
225+
return null;
226+
}
227+
const sourceNode = allNodesById[source];
228+
const destinationNode = allNodesById[destination];
229+
if (sourceNode === undefined || destinationNode === undefined) {
230+
console.error("Cannot find the nodes");
231+
return null;
232+
}
233+
234+
if (
235+
sourceNode.frontmatter.importedFromRid ||
236+
destinationNode.frontmatter.importedFromRid
237+
)
238+
return null; // punt relation to imported nodes for now.
239+
// otherwise put the importedFromRid in source, dest.
240+
241+
/* eslint-disable @typescript-eslint/naming-convention */
242+
const literal_content: Record<string, Json> = {};
243+
if (importedFromRid) literal_content.importedFromRid = importedFromRid;
244+
return {
245+
space_id: context.spaceId,
246+
name: `[[${sourceNode.file.basename}]] -${relationType.label}-> [[${destinationNode.file.basename}]]`,
247+
source_local_id: relationInstanceData.id,
248+
author_local_id: relationInstanceData.author,
249+
schema_represented_by_local_id: type,
250+
is_schema: false,
251+
created: new Date(created).toISOString(),
252+
last_modified: new Date(lastModified ?? created).toISOString(),
253+
literal_content,
254+
local_reference_content: {
255+
source,
256+
destination,
257+
},
258+
/* eslint-enable @typescript-eslint/naming-convention */
259+
};
260+
};
261+
84262
export const relatedConcepts = (concept: LocalConceptDataInput): string[] => {
85263
const relations = Object.values(
86264
concept.local_reference_content || {},
@@ -99,27 +277,40 @@ export const relatedConcepts = (concept: LocalConceptDataInput): string[] => {
99277
* schema_represented_by_local_id or local_reference_content — so that id
100278
* must equal some concept's source_local_id or it is reported as "missing".
101279
*/
102-
const orderConceptsRec = (
103-
ordered: LocalConceptDataInput[],
104-
concept: LocalConceptDataInput,
105-
remainder: { [key: string]: LocalConceptDataInput },
106-
): Set<string> => {
280+
const orderConceptsRec = ({
281+
ordered,
282+
concept,
283+
remainder,
284+
processed,
285+
}: {
286+
ordered: LocalConceptDataInput[];
287+
concept: LocalConceptDataInput;
288+
remainder: { [key: string]: LocalConceptDataInput };
289+
processed: Set<string>;
290+
}): Set<string> => {
107291
const relatedConceptIds = relatedConcepts(concept);
108292
let missing: Set<string> = new Set();
109293
while (relatedConceptIds.length > 0) {
110294
const relatedConceptId = relatedConceptIds.shift()!;
295+
if (processed.has(relatedConceptId)) continue;
111296
const relatedConcept = remainder[relatedConceptId];
112297
if (relatedConcept === undefined) {
113298
missing.add(relatedConceptId);
114299
} else {
115300
missing = new Set([
116301
...missing,
117-
...orderConceptsRec(ordered, relatedConcept, remainder),
302+
...orderConceptsRec({
303+
ordered,
304+
concept: relatedConcept,
305+
remainder,
306+
processed,
307+
}),
118308
]);
119309
delete remainder[relatedConceptId];
120310
}
121311
}
122312
ordered.push(concept);
313+
processed.add(concept.source_local_id!);
123314
delete remainder[concept.source_local_id!];
124315
return missing;
125316
};
@@ -143,14 +334,20 @@ export const orderConceptsByDependency = (
143334
);
144335
const ordered: LocalConceptDataInput[] = [];
145336
let missing: Set<string> = new Set();
337+
const processed: Set<string> = new Set();
146338
while (Object.keys(conceptById).length > 0) {
147339
const first = Object.values(conceptById)[0];
148340
if (!first) break;
149341
missing = new Set([
150342
...missing,
151-
...orderConceptsRec(ordered, first, conceptById),
343+
...orderConceptsRec({
344+
ordered,
345+
concept: first,
346+
remainder: conceptById,
347+
processed,
348+
}),
152349
]);
153-
if (missing.size > 0) console.error(`missing: ${[...missing]}`);
350+
if (missing.size > 0) console.error(`missing: ${[...missing].join(", ")}`);
154351
}
155352
return { ordered, missing: Array.from(missing) };
156353
};

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)