diff --git a/components/roamresearch/actions/add-content-to-daily-note-page/add-content-to-daily-note-page.mjs b/components/roamresearch/actions/add-content-to-daily-note-page/add-content-to-daily-note-page.mjs index 1e896512c4df1..7a390bad1db72 100644 --- a/components/roamresearch/actions/add-content-to-daily-note-page/add-content-to-daily-note-page.mjs +++ b/components/roamresearch/actions/add-content-to-daily-note-page/add-content-to-daily-note-page.mjs @@ -4,7 +4,7 @@ export default { key: "roamresearch-add-content-to-daily-note-page", name: "Add Content To Daily Note Page", description: "Adds content as a child block to a daily note page in Roam Research (access to encrypted and non encrypted graphs). [See the documentation](https://roamresearch.com/#/app/developer-documentation/page/eb8OVhaFC).", - version: "0.0.1", + version: "0.0.2", type: "action", props: { app, @@ -57,7 +57,7 @@ export default { }, }); - $.export("$summary", "Succesfully added content to daily note page."); + $.export("$summary", "Successfully added content to daily note page."); return response; }, }; diff --git a/components/roamresearch/actions/add-content-to-page/add-content-to-page.mjs b/components/roamresearch/actions/add-content-to-page/add-content-to-page.mjs index bbf4fa000c582..dd22bbf9a5606 100644 --- a/components/roamresearch/actions/add-content-to-page/add-content-to-page.mjs +++ b/components/roamresearch/actions/add-content-to-page/add-content-to-page.mjs @@ -4,7 +4,7 @@ export default { key: "roamresearch-add-content-to-page", name: "Add Content To Page", description: "Add content as a child block to an existing or new page in Roam Research (access to encrypted and non encrypted graphs). [See the documentation](https://roamresearch.com/#/app/developer-documentation/page/eb8OVhaFC).", - version: "0.0.1", + version: "0.0.2", type: "action", props: { app, @@ -55,7 +55,7 @@ export default { }, }); - $.export("$summary", "Succesfully added content to page."); + $.export("$summary", "Successfully added content to page."); return response; }, }; diff --git a/components/roamresearch/actions/add-content-underneath-block/add-content-underneath-block.mjs b/components/roamresearch/actions/add-content-underneath-block/add-content-underneath-block.mjs index afc652916a26d..405b0274b0619 100644 --- a/components/roamresearch/actions/add-content-underneath-block/add-content-underneath-block.mjs +++ b/components/roamresearch/actions/add-content-underneath-block/add-content-underneath-block.mjs @@ -4,7 +4,7 @@ export default { key: "roamresearch-add-content-underneath-block", name: "Add Content Underneath Block", description: "Add content underneath an existing block in your Roam Research graph (access to encrypted and non encrypted graphs). [See the documentation](https://roamresearch.com/)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { app, @@ -54,7 +54,7 @@ export default { }, }); - $.export("$summary", "Succesfully added content underneath block."); + $.export("$summary", "Successfully added content underneath block."); return response; }, }; diff --git a/components/roamresearch/actions/append-blocks/append-blocks.mjs b/components/roamresearch/actions/append-blocks/append-blocks.mjs new file mode 100644 index 0000000000000..1a807828ade77 --- /dev/null +++ b/components/roamresearch/actions/append-blocks/append-blocks.mjs @@ -0,0 +1,41 @@ +import utils from "../../common/utils.mjs"; +import app from "../../roamresearch.app.mjs"; + +export default { + key: "roamresearch-append-blocks", + name: "Append Blocks", + description: "Generic append blocks for Roam Research pages. [See the documentation](https://roamresearch.com/#/app/developer-documentation/page/kjnAseQ-K).", + version: "0.0.1", + type: "action", + props: { + app, + location: { + type: "object", + label: "Location", + description: "The location to append the block to. [See the documentation](https://roamresearch.com/#/app/developer-documentation/page/kjnAseQ-K).", + }, + appendData: { + type: "string[]", + label: "Append Data", + description: "The data to append to the block. [See the documentation](https://roamresearch.com/#/app/developer-documentation/page/kjnAseQ-K).", + }, + }, + async run({ $ }) { + const { + app, + location, + appendData, + } = this; + + const response = await app.appendBlocks({ + $, + data: { + location, + ["append-data"]: utils.parseArray(appendData), + }, + }); + + $.export("$summary", "Successfully ran append blocks."); + return response; + }, +}; diff --git a/components/roamresearch/actions/get-page-or-block-data/get-page-or-block-data.mjs b/components/roamresearch/actions/get-page-or-block-data/get-page-or-block-data.mjs index 6f2f3362bb46a..b4fc68ee96a48 100644 --- a/components/roamresearch/actions/get-page-or-block-data/get-page-or-block-data.mjs +++ b/components/roamresearch/actions/get-page-or-block-data/get-page-or-block-data.mjs @@ -4,23 +4,21 @@ export default { key: "roamresearch-get-page-or-block-data", name: "Get Page Or Block Data", description: "Get the data for a page or block in Roam Research (access only to non ecrypted graphs). [See the documentation](https://roamresearch.com/#/app/developer-documentation/page/eb8OVhaFC).", - version: "0.0.1", + version: "0.0.2", type: "action", props: { app, resourceType: { - type: "string", - label: "Resource Type", - description: "The type of resource to get data for.", - options: [ - "page", - "block", + propDefinition: [ + app, + "resourceType", ], }, pageOrBlock: { - type: "string", - label: "Page Title Or Block UID", - description: "The page title of the block uid to get data for. Page title example: `My Page` and Block UID example: `ideWWvTgI`.", + propDefinition: [ + app, + "pageOrBlock", + ], }, }, async run({ $ }) { @@ -47,7 +45,7 @@ export default { return response; } - $.export("$summary", `Succesfully got data for ${resourceType}: \`${pageOrBlock}\`.`); + $.export("$summary", `Successfully got data for ${resourceType}: \`${pageOrBlock}\`.`); return response; }, }; diff --git a/components/roamresearch/actions/pull-many/pull-many.mjs b/components/roamresearch/actions/pull-many/pull-many.mjs new file mode 100644 index 0000000000000..50d4f2a2fbbe2 --- /dev/null +++ b/components/roamresearch/actions/pull-many/pull-many.mjs @@ -0,0 +1,45 @@ +import app from "../../roamresearch.app.mjs"; + +export default { + key: "roamresearch-pull-many", + name: "Pull Many", + description: "Generic pull many for Roam Research pages. [See the documentation](https://roamresearch.com/#/app/developer-documentation/page/mdnjFsqoA).", + version: "0.0.1", + type: "action", + props: { + app, + eids: { + type: "string", + label: "Entity IDs", + description: "The entity IDs to pull. Eg. `[[:block/uid \"08-30-2022\"] [:block/uid \"08-31-2022\"]]`.", + }, + selector: { + type: "string", + label: "Selector", + description: "The selector to pull. Eg. `[:block/uid :node/title :block/string {:block/children [:block/uid :block/string]} {:block/refs [:node/title :block/string :block/uid]}]`.", + }, + }, + async run({ $ }) { + const { + app, + eids, + selector, + } = this; + + const response = await app.pullMany({ + $, + data: { + eids, + selector, + }, + }); + + if (!response.result) { + $.export("$summary", `Failed to pull many data for entity IDs: \`${eids}\`.`); + return response; + } + + $.export("$summary", "Successfully ran pull many."); + return response; + }, +}; diff --git a/components/roamresearch/actions/pull/pull.mjs b/components/roamresearch/actions/pull/pull.mjs new file mode 100644 index 0000000000000..e0e9fd8f6be29 --- /dev/null +++ b/components/roamresearch/actions/pull/pull.mjs @@ -0,0 +1,45 @@ +import app from "../../roamresearch.app.mjs"; + +export default { + key: "roamresearch-pull", + name: "Pull", + description: "Generic pull for Roam Research pages. [See the documentation](https://roamresearch.com/#/app/developer-documentation/page/mdnjFsqoA).", + version: "0.0.1", + type: "action", + props: { + app, + eid: { + type: "string", + label: "Entity ID", + description: "The entity ID to pull. Eg. `[:block/uid \"08-30-2022\"]`.", + }, + selector: { + type: "string", + label: "Selector", + description: "The selector to pull. Eg. `[:block/uid :node/title :block/string {:block/children [:block/uid :block/string]} {:block/refs [:node/title :block/string :block/uid]}]`.", + }, + }, + async run({ $ }) { + const { + app, + eid, + selector, + } = this; + + const response = await app.pull({ + $, + data: { + eid, + selector, + }, + }); + + if (!response.result) { + $.export("$summary", `Failed to pull data for entity ID: \`${eid}\`.`); + return response; + } + + $.export("$summary", "Successfully ran pull."); + return response; + }, +}; diff --git a/components/roamresearch/actions/query/query.mjs b/components/roamresearch/actions/query/query.mjs new file mode 100644 index 0000000000000..e1d9d743474b3 --- /dev/null +++ b/components/roamresearch/actions/query/query.mjs @@ -0,0 +1,40 @@ +import app from "../../roamresearch.app.mjs"; + +export default { + key: "roamresearch-query", + name: "Query", + description: "Generic query for Roam Research pages. [See the documentation](https://roamresearch.com/#/app/developer-documentation/page/mdnjFsqoA).", + version: "0.0.1", + type: "action", + props: { + app, + query: { + type: "string", + label: "Query", + description: "The query to run in Datalog language. Eg. `[:find ?block-uid ?block-str :in $ ?search-string :where [?b :block/uid ?block-uid] [?b :block/string ?block-str] [(clojure.string/includes? ?block-str ?search-string)]]`.", + }, + args: { + type: "string[]", + label: "Arguments", + description: "The arguments to pass to the query. Eg. `apple` as the firs argument.", + }, + }, + async run({ $ }) { + const { + app, + query, + args, + } = this; + + const response = await app.query({ + $, + data: { + query, + args, + }, + }); + + $.export("$summary", "Successfully ran query."); + return response; + }, +}; diff --git a/components/roamresearch/actions/search-title/search-title.mjs b/components/roamresearch/actions/search-title/search-title.mjs index 7d22344be6607..47b7ccfc86365 100644 --- a/components/roamresearch/actions/search-title/search-title.mjs +++ b/components/roamresearch/actions/search-title/search-title.mjs @@ -4,7 +4,7 @@ export default { key: "roamresearch-search-title", name: "Search Title", description: "Search for a title in Roam Research pages (access only to non ecrypted graphs). [See the documentation](https://roamresearch.com/#/app/developer-documentation/page/eb8OVhaFC).", - version: "0.0.1", + version: "0.0.2", type: "action", props: { app, @@ -35,7 +35,7 @@ export default { }, }); - $.export("$summary", `Succesfully searched for title: \`${title}\`.`); + $.export("$summary", `Successfully searched for title: \`${title}\`.`); return response; }, }; diff --git a/components/roamresearch/actions/write/write.mjs b/components/roamresearch/actions/write/write.mjs new file mode 100644 index 0000000000000..dd698c0c6cdda --- /dev/null +++ b/components/roamresearch/actions/write/write.mjs @@ -0,0 +1,185 @@ +import app from "../../roamresearch.app.mjs"; +import utils from "../../common/utils.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + key: "roamresearch-write", + name: "Write", + description: "Generic write for Roam Research pages. [See the documentation](https://roamresearch.com/#/app/developer-documentation/page/mdnjFsqoA).", + version: "0.0.1", + type: "action", + props: { + app, + action: { + type: "string", + label: "Action", + description: "The action to run. Eg. `create-block`.", + options: Object.values(constants.ACTION), + reloadProps: true, + }, + }, + additionalProps() { + const { action } = this; + + if (action === constants.ACTION.CREATE_BLOCK) { + return { + location: { + type: "object", + label: "Location", + description: "The location to create the block where `order` is required and either `parent-uid` or `page-title` is required.", + default: { + ["parent-uid"]: "optional", + ["page-title"]: "optional", + order: "last", + }, + }, + block: { + type: "object", + label: "Block", + description: "The block to create where `string` is required.", + default: { + string: "required", + uid: "optional", + open: "optional", + heading: "optional", + ["text-align"]: "optional", + ["children-view-type"]: "optional", + }, + }, + }; + } + + if (action === constants.ACTION.MOVE_BLOCK) { + return { + location: { + type: "object", + label: "Location", + description: "The location to move the block where `order` is required and either `parent-uid` or `page-title` is required.", + default: { + ["parent-uid"]: "optional", + ["page-title"]: "optional", + order: "last", + }, + }, + block: { + type: "object", + label: "Block", + description: "The block to move where `uid` is required.", + default: { + uid: "required", + }, + }, + }; + } + + if (action === constants.ACTION.UPDATE_BLOCK) { + return { + block: { + type: "object", + label: "Block", + description: "The block to update where `uid` is required.", + default: { + uid: "required", + string: "optional", + open: "optional", + heading: "optional", + ["text-align"]: "optional", + ["children-view-type"]: "optional", + }, + }, + }; + } + + if (action === constants.ACTION.DELETE_BLOCK) { + return { + block: { + type: "object", + label: "Block", + description: "The block to delete where `uid` is required.", + default: { + uid: "required", + }, + }, + }; + } + + if (action === constants.ACTION.CREATE_PAGE) { + return { + page: { + type: "object", + label: "Page", + description: "The page to create where `title` is required.", + default: { + title: "required", + uid: "optional", + ["children-view-type"]: "optional", + }, + }, + }; + } + + if (action === constants.ACTION.UPDATE_PAGE) { + return { + page: { + type: "object", + label: "Page", + description: "The page to update where `uid` is required.", + default: { + uid: "required", + title: "optional", + ["children-view-type"]: "optional", + }, + }, + }; + } + + if (action === constants.ACTION.DELETE_PAGE) { + return { + page: { + type: "object", + label: "Page", + description: "The page to delete where `uid` is required.", + default: { + uid: "required", + }, + }, + }; + } + + if (action === constants.ACTION.BATCH_ACTIONS) { + return { + actions: { + type: "string[]", + label: "Actions", + description: "The actions to run in batch. Eg. `{ \"action\": \"create-block\", \"location\": {...}, \"block\": {...} }`", + }, + }; + } + + return {}; + }, + async run({ $ }) { + const { + app, + action, + location, + block, + page, + actions, + } = this; + + const response = await app.write({ + $, + data: { + action, + location, + block, + page, + actions: utils.parseArray(actions), + }, + }); + + $.export("$summary", "Successfully ran the action."); + return response; + }, +}; diff --git a/components/roamresearch/common/constants.mjs b/components/roamresearch/common/constants.mjs index e65123a69c092..dd068eea7592b 100644 --- a/components/roamresearch/common/constants.mjs +++ b/components/roamresearch/common/constants.mjs @@ -7,9 +7,21 @@ const API = { APPEND: "append-api", }; +const ACTION = { + CREATE_BLOCK: "create-block", + MOVE_BLOCK: "move-block", + UPDATE_BLOCK: "update-block", + DELETE_BLOCK: "delete-block", + CREATE_PAGE: "create-page", + UPDATE_PAGE: "update-page", + DELETE_PAGE: "delete-page", + BATCH_ACTIONS: "batch-actions", +}; + export default { SUBDOMAIN_PLACEHOLDER, BASE_URL, VERSION_PATH, API, + ACTION, }; diff --git a/components/roamresearch/common/utils.mjs b/components/roamresearch/common/utils.mjs new file mode 100644 index 0000000000000..5b2a021c30b86 --- /dev/null +++ b/components/roamresearch/common/utils.mjs @@ -0,0 +1,57 @@ +import { ConfigurationError } from "@pipedream/platform"; + +const parseJson = (input) => { + const parse = (value) => { + if (typeof(value) === "string") { + try { + return parseJson(JSON.parse(value)); + } catch (e) { + return value; + } + } else if (typeof(value) === "object" && value !== null) { + return Object.entries(value) + .reduce((acc, [ + key, + val, + ]) => Object.assign(acc, { + [key]: parse(val), + }), {}); + } + return value; + }; + + return parse(input); +}; + +function parseArray(value) { + try { + if (!value) { + return; + } + + if (Array.isArray(value)) { + return value; + } + + const parsedValue = JSON.parse(value); + + if (!Array.isArray(parsedValue)) { + throw new Error("Not an array"); + } + + return parsedValue; + + } catch (e) { + throw new ConfigurationError("Make sure the custom expression contains a valid array object"); + } +} + +function getNestedProperty(obj, propertyString) { + const properties = propertyString.split("."); + return properties.reduce((prev, curr) => prev?.[curr], obj); +} + +export default { + parseArray: (value) => parseArray(value)?.map(parseJson), + getNestedProperty, +}; diff --git a/components/roamresearch/package.json b/components/roamresearch/package.json index 76ce74990ec36..9cf9052e11646 100644 --- a/components/roamresearch/package.json +++ b/components/roamresearch/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/roamresearch", - "version": "0.1.0", + "version": "0.1.1", "description": "Pipedream roamresearch Components", "main": "roamresearch.app.mjs", "keywords": [ diff --git a/components/roamresearch/roamresearch.app.mjs b/components/roamresearch/roamresearch.app.mjs index 16e77448428c1..92763d72086b6 100644 --- a/components/roamresearch/roamresearch.app.mjs +++ b/components/roamresearch/roamresearch.app.mjs @@ -5,6 +5,20 @@ export default { type: "app", app: "roamresearch", propDefinitions: { + resourceType: { + type: "string", + label: "Resource Type", + description: "The type of resource to get data for.", + options: [ + "page", + "block", + ], + }, + pageOrBlock: { + type: "string", + label: "Page Title Or Block UID", + description: "The page title of the block uid to get data for. Page title example: `My Page` and Block UID example: `ideWWvTgI`.", + }, content: { type: "string", label: "Content", @@ -63,5 +77,17 @@ export default { ...args, }); }, + pullMany(args = {}) { + return this.post({ + path: "/pull-many", + ...args, + }); + }, + write(args = {}) { + return this.post({ + path: "/write", + ...args, + }); + }, }, }; diff --git a/components/roamresearch/sources/new-modified-linked-reference/new-modified-linked-reference.mjs b/components/roamresearch/sources/new-modified-linked-reference/new-modified-linked-reference.mjs new file mode 100644 index 0000000000000..1f06de7c21bfc --- /dev/null +++ b/components/roamresearch/sources/new-modified-linked-reference/new-modified-linked-reference.mjs @@ -0,0 +1,94 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import app from "../../roamresearch.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "roamresearch-new-modified-linked-reference", + name: "New Modified Linked Reference", + description: "Emit new event for each new or modified linked reference in Roam Research.", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + app, + db: "$.service.db", + timer: { + type: "$.interface.timer", + label: "Polling Schedule", + description: "How often to poll the API", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + resourceType: { + propDefinition: [ + app, + "resourceType", + ], + }, + pageOrBlock: { + propDefinition: [ + app, + "pageOrBlock", + ], + }, + }, + methods: { + getResourcesName() { + return "result.:block/_refs"; + }, + getResourcesFn() { + return this.app.pull; + }, + getResourcesFnArgs() { + const { + resourceType, + pageOrBlock, + } = this; + + const attribute = resourceType === "page" + ? ":node/title" + : ":block/uid"; + + return { + data: { + selector: `[${attribute} :block/string :block/order :edit/time {:block/_refs ...}]`, + eid: `[${attribute} "${pageOrBlock}"]`, + }, + }; + }, + generateMeta(resource) { + const ts = resource[":edit/time"]; + return { + id: ts, + summary: `Link Reference: ${resource[":block/string"]}`, + ts, + }; + }, + processResource(resource) { + const meta = this.generateMeta(resource); + this.$emit(resource, meta); + }, + }, + async run() { + const { + getResourcesFn, + getResourcesName, + getResourcesFnArgs, + processResource, + } = this; + + const resourcesFn = getResourcesFn(); + const response = await resourcesFn(getResourcesFnArgs()); + const resources = utils.getNestedProperty(response, getResourcesName()); + + if (!resources) { + console.log("No resources found"); + return; + } + + Array.from(resources) + .reverse() + .forEach(processResource); + }, +};