Skip to content

Commit c0ca4ef

Browse files
authored
ENG-1189: Prod: Init block prop based schema (#666)
* ENG-1189: Prod: Init block prop based schema * don't need this old relic
1 parent 18a6c76 commit c0ca4ef

5 files changed

Lines changed: 408 additions & 13 deletions

File tree

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import type { DiscourseRelationSettings } from "~/components/settings/utils/zodSchema";
2+
/* eslint-disable @typescript-eslint/naming-convention */ // This is for nodePosition keys
3+
4+
// TODO: Delete the original default relations in data/defaultRelations.ts when fully migrated.
5+
const DEFAULT_RELATIONS_BLOCK_PROPS: DiscourseRelationSettings[] = [
6+
{
7+
id: "informs",
8+
label: "Informs",
9+
source: "_EVD-node",
10+
destination: "_QUE-node",
11+
complement: "Informed By",
12+
ifConditions: [
13+
{
14+
triples: [
15+
["Page", "is a", "source"],
16+
["Block", "references", "Page"],
17+
["Block", "is in page", "ParentPage"],
18+
["ParentPage", "is a", "destination"],
19+
],
20+
nodePositions: {
21+
"0": "100 57",
22+
"1": "100 208",
23+
"2": "100 345",
24+
source: "281 57",
25+
destination: "281 345",
26+
},
27+
},
28+
],
29+
},
30+
{
31+
id: "supports",
32+
label: "Supports",
33+
source: "_EVD-node",
34+
destination: "_CLM-node",
35+
complement: "Supported By",
36+
ifConditions: [
37+
{
38+
triples: [
39+
["Page", "is a", "source"],
40+
["Block", "references", "Page"],
41+
["SBlock", "references", "SPage"],
42+
["SPage", "has title", "SupportedBy"],
43+
["SBlock", "has child", "Block"],
44+
["PBlock", "references", "ParentPage"],
45+
["PBlock", "has child", "SBlock"],
46+
["ParentPage", "is a", "destination"],
47+
],
48+
nodePositions: {
49+
"0": "250 325",
50+
"1": "100 325",
51+
"2": "100 200",
52+
"3": "250 200",
53+
"4": "400 200",
54+
"5": "100 75",
55+
"6": "250 75",
56+
source: "400 325",
57+
destination: "400 75",
58+
},
59+
},
60+
{
61+
triples: [
62+
["Page", "is a", "destination"],
63+
["Block", "references", "Page"],
64+
["SBlock", "references", "SPage"],
65+
["SPage", "has title", "Supports"],
66+
["SBlock", "has child", "Block"],
67+
["PBlock", "references", "ParentPage"],
68+
["PBlock", "has child", "SBlock"],
69+
["ParentPage", "is a", "source"],
70+
],
71+
nodePositions: {
72+
"7": "250 325",
73+
"8": "100 325",
74+
"9": "100 200",
75+
"10": "250 200",
76+
"11": "400 200",
77+
"12": "100 75",
78+
"13": "250 75",
79+
source: "400 75",
80+
destination: "400 325",
81+
},
82+
},
83+
],
84+
},
85+
{
86+
id: "opposes",
87+
label: "Opposes",
88+
source: "_EVD-node",
89+
destination: "_CLM-node",
90+
complement: "Opposed By",
91+
ifConditions: [
92+
{
93+
triples: [
94+
["Page", "is a", "source"],
95+
["Block", "references", "Page"],
96+
["SBlock", "references", "SPage"],
97+
["SPage", "has title", "OpposedBy"],
98+
["SBlock", "has child", "Block"],
99+
["PBlock", "references", "ParentPage"],
100+
["PBlock", "has child", "SBlock"],
101+
["ParentPage", "is a", "destination"],
102+
],
103+
nodePositions: {
104+
"0": "250 325",
105+
"1": "100 325",
106+
"2": "100 200",
107+
"3": "250 200",
108+
"4": "400 200",
109+
"5": "100 75",
110+
"6": "250 75",
111+
source: "400 325",
112+
destination: "400 75",
113+
},
114+
},
115+
{
116+
triples: [
117+
["Page", "is a", "destination"],
118+
["Block", "references", "Page"],
119+
["SBlock", "references", "SPage"],
120+
["SPage", "has title", "Opposes"],
121+
["SBlock", "has child", "Block"],
122+
["PBlock", "references", "ParentPage"],
123+
["PBlock", "has child", "SBlock"],
124+
["ParentPage", "is a", "source"],
125+
],
126+
nodePositions: {
127+
"7": "250 325",
128+
"8": "100 325",
129+
"9": "100 200",
130+
"10": "250 200",
131+
"11": "400 200",
132+
"12": "100 75",
133+
"13": "250 75",
134+
source: "400 75",
135+
destination: "400 325",
136+
},
137+
},
138+
],
139+
},
140+
];
141+
142+
export default DEFAULT_RELATIONS_BLOCK_PROPS;
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle";
2+
import getShallowTreeByParentUid from "roamjs-components/queries/getShallowTreeByParentUid";
3+
import { createPage, createBlock } from "roamjs-components/writes";
4+
import setBlockProps from "~/utils/setBlockProps";
5+
import getBlockProps from "~/utils/getBlockProps";
6+
import INITIAL_NODE_VALUES from "~/data/defaultDiscourseNodes";
7+
import {
8+
DiscourseNodeSchema,
9+
getTopLevelBlockPropsConfig,
10+
getPersonalSettingsKey,
11+
} from "~/components/settings/utils/zodSchema";
12+
import { DG_BLOCK_PROP_SETTINGS_PAGE_TITLE, DISCOURSE_NODE_PAGE_PREFIX } from "./zodSchema";
13+
14+
const ensurePageExists = async (pageTitle: string): Promise<string> => {
15+
let pageUid = getPageUidByPageTitle(pageTitle);
16+
17+
if (!pageUid) {
18+
pageUid = window.roamAlphaAPI.util.generateUID();
19+
await createPage({
20+
title: pageTitle,
21+
uid: pageUid,
22+
});
23+
}
24+
25+
return pageUid;
26+
};
27+
28+
const ensureBlocksExist = async (
29+
pageUid: string,
30+
blockTexts: string[],
31+
existingBlockMap: Record<string, string>,
32+
): Promise<Record<string, string>> => {
33+
const missingBlocks = blockTexts.filter(
34+
(blockText) => !existingBlockMap[blockText],
35+
);
36+
37+
if (missingBlocks.length > 0) {
38+
const createdBlocks = await Promise.all(
39+
missingBlocks.map(async (blockText) => {
40+
const uid = await createBlock({
41+
parentUid: pageUid,
42+
node: { text: blockText },
43+
});
44+
return { text: blockText, uid };
45+
}),
46+
);
47+
48+
createdBlocks.forEach((block) => {
49+
existingBlockMap[block.text] = block.uid;
50+
});
51+
}
52+
53+
return existingBlockMap;
54+
};
55+
56+
const buildBlockMap = (pageUid: string): Record<string, string> => {
57+
const existingChildren = getShallowTreeByParentUid(pageUid);
58+
const blockMap: Record<string, string> = {};
59+
existingChildren.forEach((child) => {
60+
blockMap[child.text] = child.uid;
61+
});
62+
return blockMap;
63+
};
64+
65+
const initializeSettingsBlockProps = (
66+
blockMap: Record<string, string>,
67+
): void => {
68+
const configs = getTopLevelBlockPropsConfig();
69+
70+
for (const { key, schema } of configs) {
71+
const uid = blockMap[key];
72+
if (uid) {
73+
const existingProps = getBlockProps(uid);
74+
if (!existingProps || Object.keys(existingProps).length === 0) {
75+
const defaults = schema.parse({});
76+
setBlockProps(uid, defaults, false);
77+
}
78+
}
79+
}
80+
};
81+
82+
const initSettingsPageBlocks = async (): Promise<Record<string, string>> => {
83+
const pageUid = await ensurePageExists(DG_BLOCK_PROP_SETTINGS_PAGE_TITLE);
84+
const blockMap = buildBlockMap(pageUid);
85+
86+
const topLevelBlocks = getTopLevelBlockPropsConfig().map(({ key }) => key);
87+
await ensureBlocksExist(pageUid, topLevelBlocks, blockMap);
88+
89+
initializeSettingsBlockProps(blockMap);
90+
91+
return blockMap;
92+
};
93+
94+
const hasNonDefaultNodes = (): boolean => {
95+
const results = window.roamAlphaAPI.q(`
96+
[:find ?uid ?title
97+
:where
98+
[?page :node/title ?title]
99+
[?page :block/uid ?uid]
100+
[(clojure.string/starts-with? ?title "${DISCOURSE_NODE_PAGE_PREFIX}")]]
101+
`) as [string, string][];
102+
103+
for (const [pageUid] of results) {
104+
const blockProps = getBlockProps(pageUid);
105+
if (!blockProps) continue;
106+
107+
const parsed = DiscourseNodeSchema.safeParse(blockProps);
108+
if (!parsed.success) continue;
109+
110+
if (parsed.data.backedBy !== "default") {
111+
return true;
112+
}
113+
}
114+
115+
return false;
116+
};
117+
118+
const initSingleDiscourseNode = async (
119+
node: (typeof INITIAL_NODE_VALUES)[number],
120+
): Promise<{ label: string; pageUid: string } | null> => {
121+
if (!node.text) return null;
122+
123+
const pageUid = await ensurePageExists(
124+
`${DISCOURSE_NODE_PAGE_PREFIX}${node.text}`,
125+
);
126+
const existingProps = getBlockProps(pageUid);
127+
128+
if (!existingProps || Object.keys(existingProps).length === 0) {
129+
const nodeData = DiscourseNodeSchema.parse({
130+
text: node.text,
131+
type: node.type,
132+
format: node.format || "",
133+
shortcut: node.shortcut || "",
134+
tag: node.tag || "",
135+
graphOverview: node.graphOverview ?? false,
136+
canvasSettings: node.canvasSettings || {},
137+
backedBy: "default",
138+
});
139+
140+
setBlockProps(pageUid, nodeData, false);
141+
}
142+
143+
return { label: node.text, pageUid };
144+
};
145+
146+
const initDiscourseNodePages = async (): Promise<Record<string, string>> => {
147+
if (hasNonDefaultNodes()) {
148+
return {};
149+
}
150+
151+
const results = await Promise.all(
152+
INITIAL_NODE_VALUES.map((node) => initSingleDiscourseNode(node)),
153+
);
154+
155+
const nodePageUids: Record<string, string> = {};
156+
for (const result of results) {
157+
if (result) {
158+
nodePageUids[result.label] = result.pageUid;
159+
}
160+
}
161+
162+
return nodePageUids;
163+
};
164+
165+
const printAllSettings = (
166+
blockMap: Record<string, string>,
167+
nodePageUids: Record<string, string>,
168+
): void => {
169+
const configs = getTopLevelBlockPropsConfig();
170+
const featureFlagsUid = blockMap[configs.find(({ key }) => key === "Feature Flags")?.key ?? ""];
171+
const globalUid = blockMap[configs.find(({ key }) => key === "Global")?.key ?? ""];
172+
const personalKey = getPersonalSettingsKey();
173+
const personalUid = blockMap[personalKey];
174+
175+
const featureFlags = featureFlagsUid ? getBlockProps(featureFlagsUid) : null;
176+
const globalSettings = globalUid ? getBlockProps(globalUid) : null;
177+
const personalSettings = personalUid ? getBlockProps(personalUid) : null;
178+
179+
console.group("🔧 Discourse Graph Settings Initialized (RAW DATA)");
180+
181+
console.group(`🚩 Feature Flags (uid: ${featureFlagsUid})`);
182+
console.log("Raw block props:", JSON.stringify(featureFlags, null, 2));
183+
console.groupEnd();
184+
185+
console.group(`🌍 Global Settings (uid: ${globalUid})`);
186+
console.log("Raw block props:", JSON.stringify(globalSettings, null, 2));
187+
console.groupEnd();
188+
189+
console.group(`👤 Personal Settings (uid: ${personalUid})`);
190+
console.log("Raw block props:", JSON.stringify(personalSettings, null, 2));
191+
console.groupEnd();
192+
193+
console.group("📝 Discourse Nodes");
194+
for (const [nodeLabel, pageUid] of Object.entries(nodePageUids)) {
195+
const nodeProps = getBlockProps(pageUid);
196+
console.group(`${nodeLabel} (uid: ${pageUid})`);
197+
console.log("Raw block props:", JSON.stringify(nodeProps, null, 2));
198+
console.groupEnd();
199+
}
200+
console.groupEnd();
201+
202+
const relations = (globalSettings as Record<string, unknown>)?.Relations;
203+
console.group("🔗 Discourse Relations");
204+
console.log("Relations:", JSON.stringify(relations, null, 2));
205+
console.groupEnd();
206+
207+
console.groupEnd();
208+
};
209+
210+
export type InitSchemaResult = {
211+
blockUids: Record<string, string>;
212+
nodePageUids: Record<string, string>;
213+
};
214+
215+
export const initSchema = async (): Promise<InitSchemaResult> => {
216+
const blockUids = await initSettingsPageBlocks();
217+
const nodePageUids = await initDiscourseNodePages();
218+
219+
setTimeout(() => {
220+
printAllSettings(blockUids, nodePageUids);
221+
}, 2000);
222+
223+
return { blockUids, nodePageUids };
224+
};

apps/roam/src/components/settings/utils/zodSchema.example.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,17 @@ const discourseNodeSettings: DiscourseNodeSettings = {
7171
Confidence: "confidence-attr-uid",
7272
},
7373
overlay: "Status",
74-
index: [
75-
{
76-
type: "filter",
77-
condition: "has attribute",
78-
attribute: "Status",
79-
},
80-
],
74+
index: {
75+
conditions: [
76+
{
77+
type: "clause",
78+
source: "Claim",
79+
relation: "has attribute",
80+
target: "Status",
81+
},
82+
],
83+
selections: [],
84+
},
8185
suggestiveRules: {
8286
template: [],
8387
embeddingRef: "((embed-ref))",

0 commit comments

Comments
 (0)