Skip to content

Commit 30e40b2

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

File tree

4 files changed

+379
-58
lines changed

4 files changed

+379
-58
lines changed

apps/obsidian/src/utils/conceptConversion.ts

Lines changed: 195 additions & 17 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,107 @@ 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 => {
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)
144+
throw new Error(`missing relation type ${relationshipTypeId}`);
145+
const { label, complement } = relationType;
146+
// eslint-disable-next-line @typescript-eslint/naming-convention
147+
const literal_content: Record<string, Json> = {
148+
roles: STANDARD_ROLES,
149+
label,
150+
complement,
151+
};
152+
if (importedFromRid) literal_content.importedFromRid = importedFromRid;
153+
154+
return {
155+
/* eslint-disable @typescript-eslint/naming-convention */
156+
space_id: context.spaceId,
157+
name: `${sourceName} -${label}-> ${destinationName}`,
158+
source_local_id: id,
159+
is_schema: true,
160+
author_local_id: accountLocalId,
161+
created: new Date(created).toISOString(),
162+
last_modified: new Date(modified).toISOString(),
163+
literal_content,
164+
local_reference_content: {
165+
relation_type: relationshipTypeId,
166+
source: sourceId,
167+
destination: destinationId,
52168
},
169+
/* eslint-enable @typescript-eslint/naming-convention */
53170
};
54171
};
55172

@@ -66,21 +183,78 @@ export const discourseNodeInstanceToLocalConcept = ({
66183
accountLocalId: string;
67184
}): LocalConceptDataInput => {
68185
const extraData = getNodeExtraData(nodeData.file, accountLocalId);
69-
const { nodeInstanceId, nodeTypeId, ...otherData } = nodeData.frontmatter;
186+
const { nodeInstanceId, nodeTypeId, importedFromRid, ...otherData } =
187+
nodeData.frontmatter;
188+
// eslint-disable-next-line @typescript-eslint/naming-convention
189+
const literal_content: Record<string, Json> = {
190+
label: nodeData.file.basename,
191+
// eslint-disable-next-line @typescript-eslint/naming-convention
192+
source_data: otherData as unknown as Json,
193+
};
194+
if (importedFromRid && typeof importedFromRid === "string")
195+
literal_content.importedFromRid = importedFromRid;
70196
return {
197+
/* eslint-disable @typescript-eslint/naming-convention */
71198
space_id: context.spaceId,
72199
name: nodeData.file.path,
73200
source_local_id: nodeInstanceId as string,
74201
schema_represented_by_local_id: nodeTypeId as string,
75202
is_schema: false,
76-
literal_content: {
77-
label: nodeData.file.basename,
78-
source_data: otherData as unknown as Json,
79-
},
203+
literal_content,
204+
/* eslint-enable @typescript-eslint/naming-convention */
80205
...extraData,
81206
};
82207
};
83208

209+
export const relationInstanceToLocalConcept = ({
210+
context,
211+
relationTypesById,
212+
allNodesById,
213+
relationInstanceData,
214+
}: {
215+
context: SupabaseContext;
216+
relationTypesById: Record<string, DiscourseRelationType>;
217+
allNodesById: Record<string, DiscourseNodeInVault>;
218+
relationInstanceData: RelationInstance;
219+
}): LocalConceptDataInput | null => {
220+
const { type, created, lastModified, source, destination, importedFromRid } =
221+
relationInstanceData;
222+
const relationType = relationTypesById[type];
223+
224+
if (!relationType) {
225+
console.error("Missing relationType id " + type);
226+
return null;
227+
}
228+
const sourceNode = allNodesById[source];
229+
const destinationNode = allNodesById[destination];
230+
if (
231+
sourceNode?.frontmatter.importedFromRid ||
232+
destinationNode?.frontmatter.importedFromRid
233+
)
234+
return null; // punt relation to imported nodes for now.
235+
// otherwise put the importedFromRid in source, dest.
236+
237+
/* eslint-disable @typescript-eslint/naming-convention */
238+
const literal_content: Record<string, Json> = {};
239+
if (importedFromRid) literal_content.importedFromRid = importedFromRid;
240+
return {
241+
space_id: context.spaceId,
242+
name: `[[${sourceNode ? sourceNode.file.basename : source}]] -${relationType.label}-> [[${destinationNode ? destinationNode.file.basename : destination}]]`,
243+
source_local_id: relationInstanceData.id,
244+
author_local_id: relationInstanceData.author,
245+
schema_represented_by_local_id: type,
246+
is_schema: false,
247+
created: new Date(created).toISOString(),
248+
last_modified: new Date(lastModified ?? created).toISOString(),
249+
literal_content,
250+
local_reference_content: {
251+
source,
252+
destination,
253+
},
254+
/* eslint-enable @typescript-eslint/naming-convention */
255+
};
256+
};
257+
84258
export const relatedConcepts = (concept: LocalConceptDataInput): string[] => {
85259
const relations = Object.values(
86260
concept.local_reference_content || {},
@@ -103,23 +277,26 @@ const orderConceptsRec = (
103277
ordered: LocalConceptDataInput[],
104278
concept: LocalConceptDataInput,
105279
remainder: { [key: string]: LocalConceptDataInput },
280+
processed: Set<string>,
106281
): Set<string> => {
107282
const relatedConceptIds = relatedConcepts(concept);
108283
let missing: Set<string> = new Set();
109284
while (relatedConceptIds.length > 0) {
110285
const relatedConceptId = relatedConceptIds.shift()!;
286+
if (processed.has(relatedConceptId)) continue;
111287
const relatedConcept = remainder[relatedConceptId];
112288
if (relatedConcept === undefined) {
113289
missing.add(relatedConceptId);
114290
} else {
115291
missing = new Set([
116292
...missing,
117-
...orderConceptsRec(ordered, relatedConcept, remainder),
293+
...orderConceptsRec(ordered, relatedConcept, remainder, processed),
118294
]);
119295
delete remainder[relatedConceptId];
120296
}
121297
}
122298
ordered.push(concept);
299+
processed.add(concept.source_local_id!);
123300
delete remainder[concept.source_local_id!];
124301
return missing;
125302
};
@@ -143,14 +320,15 @@ export const orderConceptsByDependency = (
143320
);
144321
const ordered: LocalConceptDataInput[] = [];
145322
let missing: Set<string> = new Set();
323+
const processed: Set<string> = new Set();
146324
while (Object.keys(conceptById).length > 0) {
147325
const first = Object.values(conceptById)[0];
148326
if (!first) break;
149327
missing = new Set([
150328
...missing,
151-
...orderConceptsRec(ordered, first, conceptById),
329+
...orderConceptsRec(ordered, first, conceptById, processed),
152330
]);
153-
if (missing.size > 0) console.error(`missing: ${[...missing]}`);
331+
if (missing.size > 0) console.error(`missing: ${[...missing].join(", ")}`);
154332
}
155333
return { ordered, missing: Array.from(missing) };
156334
};

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)