-
Notifications
You must be signed in to change notification settings - Fork 4
ENG-1189: Prod: Init block prop based schema #666
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| import type { DiscourseRelationSettings } from "~/components/settings/utils/zodSchema"; | ||
| /* eslint-disable @typescript-eslint/naming-convention */ // This is for nodePosition keys | ||
|
|
||
| // TODO: Delete the original default relations in data/defaultRelations.ts when fully migrated. | ||
| const DEFAULT_RELATIONS_BLOCK_PROPS: DiscourseRelationSettings[] = [ | ||
| { | ||
| id: "informs", | ||
| label: "Informs", | ||
| source: "_EVD-node", | ||
| destination: "_QUE-node", | ||
| complement: "Informed By", | ||
| ifConditions: [ | ||
| { | ||
| triples: [ | ||
| ["Page", "is a", "source"], | ||
| ["Block", "references", "Page"], | ||
| ["Block", "is in page", "ParentPage"], | ||
| ["ParentPage", "is a", "destination"], | ||
| ], | ||
| nodePositions: { | ||
| "0": "100 57", | ||
| "1": "100 208", | ||
| "2": "100 345", | ||
| source: "281 57", | ||
| destination: "281 345", | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| id: "supports", | ||
| label: "Supports", | ||
| source: "_EVD-node", | ||
| destination: "_CLM-node", | ||
| complement: "Supported By", | ||
| ifConditions: [ | ||
| { | ||
| triples: [ | ||
| ["Page", "is a", "source"], | ||
| ["Block", "references", "Page"], | ||
| ["SBlock", "references", "SPage"], | ||
| ["SPage", "has title", "SupportedBy"], | ||
| ["SBlock", "has child", "Block"], | ||
| ["PBlock", "references", "ParentPage"], | ||
| ["PBlock", "has child", "SBlock"], | ||
| ["ParentPage", "is a", "destination"], | ||
| ], | ||
| nodePositions: { | ||
| "0": "250 325", | ||
| "1": "100 325", | ||
| "2": "100 200", | ||
| "3": "250 200", | ||
| "4": "400 200", | ||
| "5": "100 75", | ||
| "6": "250 75", | ||
| source: "400 325", | ||
| destination: "400 75", | ||
| }, | ||
| }, | ||
| { | ||
| triples: [ | ||
| ["Page", "is a", "destination"], | ||
| ["Block", "references", "Page"], | ||
| ["SBlock", "references", "SPage"], | ||
| ["SPage", "has title", "Supports"], | ||
| ["SBlock", "has child", "Block"], | ||
| ["PBlock", "references", "ParentPage"], | ||
| ["PBlock", "has child", "SBlock"], | ||
| ["ParentPage", "is a", "source"], | ||
| ], | ||
| nodePositions: { | ||
| "7": "250 325", | ||
| "8": "100 325", | ||
| "9": "100 200", | ||
| "10": "250 200", | ||
| "11": "400 200", | ||
| "12": "100 75", | ||
| "13": "250 75", | ||
| source: "400 75", | ||
| destination: "400 325", | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| id: "opposes", | ||
| label: "Opposes", | ||
| source: "_EVD-node", | ||
| destination: "_CLM-node", | ||
| complement: "Opposed By", | ||
| ifConditions: [ | ||
| { | ||
| triples: [ | ||
| ["Page", "is a", "source"], | ||
| ["Block", "references", "Page"], | ||
| ["SBlock", "references", "SPage"], | ||
| ["SPage", "has title", "OpposedBy"], | ||
| ["SBlock", "has child", "Block"], | ||
| ["PBlock", "references", "ParentPage"], | ||
| ["PBlock", "has child", "SBlock"], | ||
| ["ParentPage", "is a", "destination"], | ||
| ], | ||
| nodePositions: { | ||
| "0": "250 325", | ||
| "1": "100 325", | ||
| "2": "100 200", | ||
| "3": "250 200", | ||
| "4": "400 200", | ||
| "5": "100 75", | ||
| "6": "250 75", | ||
| source: "400 325", | ||
| destination: "400 75", | ||
| }, | ||
| }, | ||
| { | ||
| triples: [ | ||
| ["Page", "is a", "destination"], | ||
| ["Block", "references", "Page"], | ||
| ["SBlock", "references", "SPage"], | ||
| ["SPage", "has title", "Opposes"], | ||
| ["SBlock", "has child", "Block"], | ||
| ["PBlock", "references", "ParentPage"], | ||
| ["PBlock", "has child", "SBlock"], | ||
| ["ParentPage", "is a", "source"], | ||
| ], | ||
| nodePositions: { | ||
| "7": "250 325", | ||
| "8": "100 325", | ||
| "9": "100 200", | ||
| "10": "250 200", | ||
| "11": "400 200", | ||
| "12": "100 75", | ||
| "13": "250 75", | ||
| source: "400 75", | ||
| destination: "400 325", | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| ]; | ||
|
|
||
| export default DEFAULT_RELATIONS_BLOCK_PROPS; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,224 @@ | ||
| import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle"; | ||
| import getShallowTreeByParentUid from "roamjs-components/queries/getShallowTreeByParentUid"; | ||
| import { createPage, createBlock } from "roamjs-components/writes"; | ||
| import setBlockProps from "~/utils/setBlockProps"; | ||
| import getBlockProps from "~/utils/getBlockProps"; | ||
| import INITIAL_NODE_VALUES from "~/data/defaultDiscourseNodes"; | ||
| import { | ||
| DiscourseNodeSchema, | ||
| getTopLevelBlockPropsConfig, | ||
| getPersonalSettingsKey, | ||
| } from "~/components/settings/utils/zodSchema"; | ||
| import { DG_BLOCK_PROP_SETTINGS_PAGE_TITLE, DISCOURSE_NODE_PAGE_PREFIX } from "./zodSchema"; | ||
|
|
||
| const ensurePageExists = async (pageTitle: string): Promise<string> => { | ||
| let pageUid = getPageUidByPageTitle(pageTitle); | ||
|
|
||
| if (!pageUid) { | ||
| pageUid = window.roamAlphaAPI.util.generateUID(); | ||
| await createPage({ | ||
| title: pageTitle, | ||
| uid: pageUid, | ||
| }); | ||
| } | ||
|
|
||
| return pageUid; | ||
| }; | ||
|
|
||
| const ensureBlocksExist = async ( | ||
| pageUid: string, | ||
| blockTexts: string[], | ||
| existingBlockMap: Record<string, string>, | ||
| ): Promise<Record<string, string>> => { | ||
| const missingBlocks = blockTexts.filter( | ||
| (blockText) => !existingBlockMap[blockText], | ||
| ); | ||
|
|
||
| if (missingBlocks.length > 0) { | ||
| const createdBlocks = await Promise.all( | ||
| missingBlocks.map(async (blockText) => { | ||
| const uid = await createBlock({ | ||
| parentUid: pageUid, | ||
| node: { text: blockText }, | ||
| }); | ||
| return { text: blockText, uid }; | ||
| }), | ||
| ); | ||
|
|
||
| createdBlocks.forEach((block) => { | ||
| existingBlockMap[block.text] = block.uid; | ||
| }); | ||
| } | ||
|
|
||
| return existingBlockMap; | ||
| }; | ||
|
|
||
| const buildBlockMap = (pageUid: string): Record<string, string> => { | ||
| const existingChildren = getShallowTreeByParentUid(pageUid); | ||
| const blockMap: Record<string, string> = {}; | ||
| existingChildren.forEach((child) => { | ||
| blockMap[child.text] = child.uid; | ||
| }); | ||
| return blockMap; | ||
| }; | ||
|
|
||
| const initializeSettingsBlockProps = ( | ||
| blockMap: Record<string, string>, | ||
| ): void => { | ||
| const configs = getTopLevelBlockPropsConfig(); | ||
|
|
||
| for (const { key, schema } of configs) { | ||
| const uid = blockMap[key]; | ||
| if (uid) { | ||
| const existingProps = getBlockProps(uid); | ||
| if (!existingProps || Object.keys(existingProps).length === 0) { | ||
| const defaults = schema.parse({}); | ||
| setBlockProps(uid, defaults, false); | ||
| } | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| const initSettingsPageBlocks = async (): Promise<Record<string, string>> => { | ||
| const pageUid = await ensurePageExists(DG_BLOCK_PROP_SETTINGS_PAGE_TITLE); | ||
| const blockMap = buildBlockMap(pageUid); | ||
|
|
||
| const topLevelBlocks = getTopLevelBlockPropsConfig().map(({ key }) => key); | ||
| await ensureBlocksExist(pageUid, topLevelBlocks, blockMap); | ||
|
|
||
| initializeSettingsBlockProps(blockMap); | ||
|
|
||
| return blockMap; | ||
| }; | ||
|
|
||
| const hasNonDefaultNodes = (): boolean => { | ||
| const results = window.roamAlphaAPI.q(` | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's use the existing pattern: const nodes = getDiscourseNodes().filter(excludeDefaultNodes);
if (nodes.length === 0) {It's a single line and if we ever change how discourse nodes are defined, it won't break
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @sid597 We'll need to recreate a proper
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have this same function as the getDiscourseNodes() in the accessors pr, by proper you mean smth similar to the existing one or only in performance? I think we should discuss this in the accessors pr #669
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean As an aside, hasNonDefaultNodes (or the new |
||
| [:find ?uid ?title | ||
| :where | ||
| [?page :node/title ?title] | ||
| [?page :block/uid ?uid] | ||
| [(clojure.string/starts-with? ?title "${DISCOURSE_NODE_PAGE_PREFIX}")]] | ||
| `) as [string, string][]; | ||
|
|
||
| for (const [pageUid] of results) { | ||
| const blockProps = getBlockProps(pageUid); | ||
| if (!blockProps) continue; | ||
|
|
||
| const parsed = DiscourseNodeSchema.safeParse(blockProps); | ||
| if (!parsed.success) continue; | ||
|
|
||
| if (parsed.data.backedBy !== "default") { | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| }; | ||
|
|
||
| const initSingleDiscourseNode = async ( | ||
| node: (typeof INITIAL_NODE_VALUES)[number], | ||
| ): Promise<{ label: string; pageUid: string } | null> => { | ||
| if (!node.text) return null; | ||
|
|
||
| const pageUid = await ensurePageExists( | ||
| `${DISCOURSE_NODE_PAGE_PREFIX}${node.text}`, | ||
| ); | ||
| const existingProps = getBlockProps(pageUid); | ||
|
|
||
| if (!existingProps || Object.keys(existingProps).length === 0) { | ||
| const nodeData = DiscourseNodeSchema.parse({ | ||
| text: node.text, | ||
| type: node.type, | ||
| format: node.format || "", | ||
| shortcut: node.shortcut || "", | ||
| tag: node.tag || "", | ||
| graphOverview: node.graphOverview ?? false, | ||
| canvasSettings: node.canvasSettings || {}, | ||
| backedBy: "default", | ||
| }); | ||
|
|
||
| setBlockProps(pageUid, nodeData, false); | ||
| } | ||
|
|
||
| return { label: node.text, pageUid }; | ||
| }; | ||
|
|
||
| const initDiscourseNodePages = async (): Promise<Record<string, string>> => { | ||
| if (hasNonDefaultNodes()) { | ||
| return {}; | ||
| } | ||
|
|
||
| const results = await Promise.all( | ||
| INITIAL_NODE_VALUES.map((node) => initSingleDiscourseNode(node)), | ||
| ); | ||
|
|
||
| const nodePageUids: Record<string, string> = {}; | ||
| for (const result of results) { | ||
| if (result) { | ||
| nodePageUids[result.label] = result.pageUid; | ||
| } | ||
| } | ||
|
|
||
| return nodePageUids; | ||
| }; | ||
|
|
||
| const printAllSettings = ( | ||
| blockMap: Record<string, string>, | ||
| nodePageUids: Record<string, string>, | ||
| ): void => { | ||
| const configs = getTopLevelBlockPropsConfig(); | ||
| const featureFlagsUid = blockMap[configs.find(({ key }) => key === "Feature Flags")?.key ?? ""]; | ||
| const globalUid = blockMap[configs.find(({ key }) => key === "Global")?.key ?? ""]; | ||
| const personalKey = getPersonalSettingsKey(); | ||
| const personalUid = blockMap[personalKey]; | ||
|
|
||
| const featureFlags = featureFlagsUid ? getBlockProps(featureFlagsUid) : null; | ||
| const globalSettings = globalUid ? getBlockProps(globalUid) : null; | ||
| const personalSettings = personalUid ? getBlockProps(personalUid) : null; | ||
|
|
||
| console.group("🔧 Discourse Graph Settings Initialized (RAW DATA)"); | ||
|
|
||
| console.group(`🚩 Feature Flags (uid: ${featureFlagsUid})`); | ||
| console.log("Raw block props:", JSON.stringify(featureFlags, null, 2)); | ||
| console.groupEnd(); | ||
|
|
||
| console.group(`🌍 Global Settings (uid: ${globalUid})`); | ||
| console.log("Raw block props:", JSON.stringify(globalSettings, null, 2)); | ||
| console.groupEnd(); | ||
|
|
||
| console.group(`👤 Personal Settings (uid: ${personalUid})`); | ||
| console.log("Raw block props:", JSON.stringify(personalSettings, null, 2)); | ||
| console.groupEnd(); | ||
|
|
||
| console.group("📝 Discourse Nodes"); | ||
| for (const [nodeLabel, pageUid] of Object.entries(nodePageUids)) { | ||
| const nodeProps = getBlockProps(pageUid); | ||
| console.group(`${nodeLabel} (uid: ${pageUid})`); | ||
| console.log("Raw block props:", JSON.stringify(nodeProps, null, 2)); | ||
| console.groupEnd(); | ||
| } | ||
| console.groupEnd(); | ||
|
|
||
| const relations = (globalSettings as Record<string, unknown>)?.Relations; | ||
| console.group("🔗 Discourse Relations"); | ||
| console.log("Relations:", JSON.stringify(relations, null, 2)); | ||
| console.groupEnd(); | ||
|
|
||
| console.groupEnd(); | ||
| }; | ||
|
|
||
| export type InitSchemaResult = { | ||
| blockUids: Record<string, string>; | ||
| nodePageUids: Record<string, string>; | ||
| }; | ||
|
|
||
| export const initSchema = async (): Promise<InitSchemaResult> => { | ||
| const blockUids = await initSettingsPageBlocks(); | ||
| const nodePageUids = await initDiscourseNodePages(); | ||
|
|
||
| setTimeout(() => { | ||
| printAllSettings(blockUids, nodePageUids); | ||
| }, 2000); | ||
|
|
||
| return { blockUids, nodePageUids }; | ||
| }; | ||
Uh oh!
There was an error while loading. Please reload this page.