diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml deleted file mode 100644 index 9dfcdd96923de..0000000000000 --- a/.github/workflows/docs.yaml +++ /dev/null @@ -1,37 +0,0 @@ -name: Pull Request Checks - -# -# Documentation: -# https://help.github.com/en/articles/workflow-syntax-for-github-actions -# - -on: - pull_request: - branches: - - master - paths: - - 'docs/**' - -jobs: - check_broken_links: - name: Ensure no broken links end up in docs - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4.1.7 - name: Checkout repo - with: - # See https://github.com/actions/checkout#checkout-v2 - # This will be slow. The intent is to fetch all commits - # since the merge-base (the commit where we branched off) - # so we can check the git diff against all changed files. - # By default, the checkout action only returns the last commit, - # There's no native way to do this in the checkout action, so - # we have to fetch the entire history. See - # https://github.com/actions/checkout/issues/266#issuecomment-638346893 - fetch-depth: 0 - - name: Install docs deps and check broken links - run: |- - cd docs - yarn install - npx vuepress check-md docs/ \ No newline at end of file diff --git a/.github/workflows/pipedream-sdk-markdown-lint.yaml b/.github/workflows/pipedream-sdk-markdown-lint.yaml new file mode 100644 index 0000000000000..8332eee516a6b --- /dev/null +++ b/.github/workflows/pipedream-sdk-markdown-lint.yaml @@ -0,0 +1,24 @@ +name: Lint SDK Markdown Files + +on: + pull_request: + types: [opened, edited, synchronize] + paths: + - 'packages/sdk/**/*.md' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Lint markdown files + uses: DavidAnson/markdownlint-cli2-action@v17 + with: + globs: 'packages/sdk/**/*.md' diff --git a/.github/workflows/pipedream-sdk-test.yaml b/.github/workflows/pipedream-sdk-test.yaml index 6cf15d4048966..32abb9bfbc939 100644 --- a/.github/workflows/pipedream-sdk-test.yaml +++ b/.github/workflows/pipedream-sdk-test.yaml @@ -6,13 +6,17 @@ on: paths: - 'packages/sdk/**' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v3 diff --git a/components/activecampaign/actions/create-or-update-contact/create-or-update-contact.mjs b/components/activecampaign/actions/create-or-update-contact/create-or-update-contact.mjs index 76d79654413e4..9265a9c47d380 100644 --- a/components/activecampaign/actions/create-or-update-contact/create-or-update-contact.mjs +++ b/components/activecampaign/actions/create-or-update-contact/create-or-update-contact.mjs @@ -5,7 +5,7 @@ export default { key: "activecampaign-create-or-update-contact", name: "Create or Update Contact", description: "Creates a new contact or updates an existing contact. [See the documentation](https://developers.activecampaign.com/reference/sync-a-contacts-data).", - version: "0.2.5", + version: "0.2.6", type: "action", async run({ $ }) { const response = diff --git a/components/activecampaign/package.json b/components/activecampaign/package.json index 195d5ebd31a0f..50f4bcb083de8 100644 --- a/components/activecampaign/package.json +++ b/components/activecampaign/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/activecampaign", - "version": "0.4.4", + "version": "0.4.5", "description": "Pipedream Activecampaing Components", "main": "activecampaign.app.mjs", "keywords": [ @@ -14,7 +14,7 @@ "access": "public" }, "dependencies": { - "@pipedream/platform": "^1.6.0", + "@pipedream/platform": "^3.0.3", "inflection": "^3.0.0" } } diff --git a/components/adp/adp.app.mjs b/components/adp/adp.app.mjs new file mode 100644 index 0000000000000..626520486cc90 --- /dev/null +++ b/components/adp/adp.app.mjs @@ -0,0 +1,11 @@ +export default { + type: "app", + app: "adp", + propDefinitions: {}, + methods: { + // this.$auth contains connected account data + authKeys() { + console.log(Object.keys(this.$auth)); + }, + }, +}; \ No newline at end of file diff --git a/components/air/air.app.mjs b/components/air/air.app.mjs index 950ad8517ab6f..78ca544043ae9 100644 --- a/components/air/air.app.mjs +++ b/components/air/air.app.mjs @@ -8,4 +8,4 @@ export default { console.log(Object.keys(this.$auth)); }, }, -}; \ No newline at end of file +}; diff --git a/components/alchemy/.gitignore b/components/alchemy/.gitignore deleted file mode 100644 index ec761ccab7595..0000000000000 --- a/components/alchemy/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.js -*.mjs -dist \ No newline at end of file diff --git a/components/alchemy/alchemy.app.mjs b/components/alchemy/alchemy.app.mjs new file mode 100644 index 0000000000000..584340384182d --- /dev/null +++ b/components/alchemy/alchemy.app.mjs @@ -0,0 +1,58 @@ +import { axios } from "@pipedream/platform"; +import constants from "./common/constants.mjs"; + +export default { + type: "app", + app: "alchemy", + propDefinitions: { + network: { + type: "string", + label: "Network", + description: "Network of the webhook", + options: constants.NETWORKS, + }, + query: { + type: "string", + label: "GraphQL Query", + description: "Create a custom GraphQL query or select `Full Block Receipts` to get all log events for every new block", + options: [ + { + label: "Full Block Receipts", + value: constants.FULL_BLOCK_RECEIPTS, + }, + ], + }, + }, + methods: { + _baseUrl() { + return "https://dashboard.alchemy.com/api"; + }, + _makeRequest({ + $ = this, + path, + ...opts + }) { + return axios($, { + url: `${this._baseUrl()}${path}`, + headers: { + "X-Alchemy-Token": this.$auth.auth_token, + }, + ...opts, + }); + }, + createWebhook(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/create-webhook", + ...opts, + }); + }, + deleteWebhook(opts = {}) { + return this._makeRequest({ + method: "DELETE", + path: "/delete-webhook", + ...opts, + }); + }, + }, +}; diff --git a/components/alchemy/app/alchemy.app.ts b/components/alchemy/app/alchemy.app.ts deleted file mode 100644 index 5d8ef75487b4a..0000000000000 --- a/components/alchemy/app/alchemy.app.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineApp } from "@pipedream/types"; - -export default defineApp({ - type: "app", - app: "alchemy", - propDefinitions: {}, - methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); - }, - }, -}); diff --git a/components/alchemy/common/constants.mjs b/components/alchemy/common/constants.mjs new file mode 100644 index 0000000000000..9d64361282e57 --- /dev/null +++ b/components/alchemy/common/constants.mjs @@ -0,0 +1,82 @@ +const NETWORKS = [ + "ETH_MAINNET", + "ETH_SEPOLIA", + "ETH_HOLESKY", + "ARBMAINNET", + "ARBSEPOLIA", + "ARBNOVA_MAINNET", + "MATICMAINNET", + "MATICMUMBAI", + "OPTMAINNET", + "OPTGOERLI", + "BASE_MAINNET", + "BASE_SEPOLIA", + "ZKSYNC_MAINNET", + "ZKSYNC_SEPOLIA", + "LINEA_MAINNET", + "LINEA_SEPOLIA", + "GNOSIS_MAINNET", + "GNOSIS_CHIADO", + "FANTOM_MAINNET", + "FANTOM_TESTNET", + "METIS_MAINNET", + "BLAST_MAINNET", + "BLAST_SEPOLIA", + "SHAPE_SEPOLIA", + "ZETACHAIN_MAINNET", + "ZETACHAIN_TESTNET", + "WORLDCHAIN_MAINNET", + "WORLDCHAIN_SEPOLIA", + "BNB_MAINNET", + "BNB_TESTNET", + "AVAX_MAINNET", + "AVAX_FUJI", + "SONEIUM_MINATO", + "GEIST_POLTER", +]; + +const FULL_BLOCK_RECEIPTS = ` +{ + block { + hash, + number, + timestamp, + logs(filter: {addresses: [], topics: []}) { + data, + topics, + index, + account { + address + }, + transaction { + hash, + nonce, + index, + from { + address + }, + to { + address + }, + value, + gasPrice, + maxFeePerGas, + maxPriorityFeePerGas, + gas, + status, + gasUsed, + cumulativeGasUsed, + effectiveGasPrice, + createdContract { + address + } + } + } + } +} +`; + +export default { + NETWORKS, + FULL_BLOCK_RECEIPTS, +}; diff --git a/components/alchemy/package.json b/components/alchemy/package.json index d85cd23208478..747a6d1d2aca0 100644 --- a/components/alchemy/package.json +++ b/components/alchemy/package.json @@ -1,18 +1,18 @@ { "name": "@pipedream/alchemy", - "version": "0.0.3", + "version": "0.1.0", "description": "Pipedream Alchemy Components", - "main": "dist/app/alchemy.app.mjs", + "main": "alchemy.app.mjs", "keywords": [ "pipedream", "alchemy" ], - "files": [ - "dist" - ], "homepage": "https://pipedream.com/apps/alchemy", "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } } diff --git a/components/alchemy/sources/new-graphql-query-instant/new-graphql-query-instant.mjs b/components/alchemy/sources/new-graphql-query-instant/new-graphql-query-instant.mjs new file mode 100644 index 0000000000000..ae34fa5ee29ff --- /dev/null +++ b/components/alchemy/sources/new-graphql-query-instant/new-graphql-query-instant.mjs @@ -0,0 +1,77 @@ +import alchemy from "../../alchemy.app.mjs"; + +export default { + key: "alchemy-new-graphql-query-instant", + name: "New GraphQL Query (Instant)", + description: "Emit new event when a new GraphQL query is uploaded to Alchemy's Custom Webhook service. [See the documentation](https://docs.alchemy.com/reference/create-webhook)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + alchemy, + db: "$.service.db", + http: { + type: "$.interface.http", + customResponse: true, + }, + network: { + propDefinition: [ + alchemy, + "network", + ], + }, + query: { + propDefinition: [ + alchemy, + "query", + ], + }, + }, + hooks: { + async activate() { + const { data: { id } } = await this.alchemy.createWebhook({ + data: { + network: this.network, + webhook_type: "GRAPHQL", + webhook_url: this.http.endpoint, + graphql_query: this.query, + }, + }); + this._setHookId(id); + }, + async deactivate() { + const hookId = this._getHookId(); + if (hookId) { + await this.alchemy.deleteWebhook({ + params: { + webhook_id: hookId, + }, + }); + } + }, + }, + methods: { + _getHookId() { + return this.db.get("hookId"); + }, + _setHookId(hookId) { + this.db.set("hookId", hookId); + }, + generateMeta(body) { + return { + id: body.id, + summary: `New Event ID: ${body.id}`, + ts: Date.parse(body.createdAt), + }; + }, + }, + async run(event) { + this.http.respond({ + status: 200, + }); + + const { body } = event; + const meta = this.generateMeta(body); + this.$emit(body, meta); + }, +}; diff --git a/components/anthropic/actions/chat/chat.mjs b/components/anthropic/actions/chat/chat.mjs index 36bf9eb896c63..bce1c267a3108 100644 --- a/components/anthropic/actions/chat/chat.mjs +++ b/components/anthropic/actions/chat/chat.mjs @@ -3,7 +3,7 @@ import constants from "../common/constants.mjs"; export default { name: "Chat", - version: "0.0.8", + version: "0.0.9", key: "anthropic-chat", description: "The Chat API. [See the documentation](https://docs.anthropic.com/claude/reference/messages_post)", type: "action", diff --git a/components/anthropic/actions/common/constants.mjs b/components/anthropic/actions/common/constants.mjs index 1d6813a287ef2..8d5bf78792d1c 100644 --- a/components/anthropic/actions/common/constants.mjs +++ b/components/anthropic/actions/common/constants.mjs @@ -1,5 +1,6 @@ export default { MESSAGE_MODELS: [ + "claude-3-5-sonnet-20241022", "claude-3-5-sonnet-20240620", "claude-3-opus-20240229", "claude-3-sonnet-20240229", diff --git a/components/anthropic/package.json b/components/anthropic/package.json index b65d2d3584701..9dbbf034b806b 100644 --- a/components/anthropic/package.json +++ b/components/anthropic/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/anthropic", - "version": "0.0.10", + "version": "0.0.11", "description": "Pipedream Anthropic (Claude) Components", "main": "anthropic.app.mjs", "keywords": [ diff --git a/components/apollo_io/actions/add-contacts-to-sequence/add-contacts-to-sequence.mjs b/components/apollo_io/actions/add-contacts-to-sequence/add-contacts-to-sequence.mjs index 9e23ad1bfaf0a..84b7a910efe7d 100644 --- a/components/apollo_io/actions/add-contacts-to-sequence/add-contacts-to-sequence.mjs +++ b/components/apollo_io/actions/add-contacts-to-sequence/add-contacts-to-sequence.mjs @@ -5,7 +5,7 @@ export default { name: "Add Contacts to Sequence", description: "Adds one or more contacts to a sequence in Apollo.io. [See the documentation](https://apolloio.github.io/apollo-api-docs/?shell#add-contacts-to-sequence)", type: "action", - version: "0.0.2", + version: "0.0.3", props: { app, sequenceId: { diff --git a/components/apollo_io/actions/create-account/create-account.mjs b/components/apollo_io/actions/create-account/create-account.mjs index 15fc8f19445e6..144709ebe3600 100644 --- a/components/apollo_io/actions/create-account/create-account.mjs +++ b/components/apollo_io/actions/create-account/create-account.mjs @@ -5,7 +5,7 @@ export default { name: "Create Account", description: "Creates a new account in Apollo.io. [See the documentation](https://apolloio.github.io/apollo-api-docs/?shell#create-an-account)", type: "action", - version: "0.0.2", + version: "0.0.3", props: { app, name: { diff --git a/components/apollo_io/actions/create-contact/create-contact.mjs b/components/apollo_io/actions/create-contact/create-contact.mjs index 8c8cd51617cea..a5467c95557f8 100644 --- a/components/apollo_io/actions/create-contact/create-contact.mjs +++ b/components/apollo_io/actions/create-contact/create-contact.mjs @@ -5,7 +5,7 @@ export default { name: "Create Contact", description: "Creates a new contact in Apollo.io. [See the documentation](https://apolloio.github.io/apollo-api-docs/?shell#create-a-contact)", type: "action", - version: "0.0.3", + version: "0.0.4", props: { app, email: { diff --git a/components/apollo_io/actions/create-opportunity/create-opportunity.mjs b/components/apollo_io/actions/create-opportunity/create-opportunity.mjs index 36611eb33daaf..25c414cdb8df3 100644 --- a/components/apollo_io/actions/create-opportunity/create-opportunity.mjs +++ b/components/apollo_io/actions/create-opportunity/create-opportunity.mjs @@ -5,7 +5,7 @@ export default { name: "Create Opportunity", description: "Creates a new opportunity in Apollo.io. [See the documentation](https://apolloio.github.io/apollo-api-docs/?shell#create-opportunity)", type: "action", - version: "0.0.1", + version: "0.0.2", props: { app, ownerId: { diff --git a/components/apollo_io/actions/create-update-contact/create-update-contact.mjs b/components/apollo_io/actions/create-update-contact/create-update-contact.mjs new file mode 100644 index 0000000000000..3c23feed64a0e --- /dev/null +++ b/components/apollo_io/actions/create-update-contact/create-update-contact.mjs @@ -0,0 +1,121 @@ +import app from "../../apollo_io.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "apollo_io-create-update-contact", + name: "Create Or Update Contact", + description: "Creates or updates a specific contact. If the contact email already exists, it's updated. Otherwise, a new contact is created. [See the documentation](https://apolloio.github.io/apollo-api-docs/?shell#create-a-contact)", + type: "action", + version: "0.0.1", + props: { + app, + email: { + propDefinition: [ + app, + "email", + ], + }, + firstName: { + propDefinition: [ + app, + "firstName", + ], + }, + lastName: { + propDefinition: [ + app, + "lastName", + ], + }, + title: { + propDefinition: [ + app, + "title", + ], + }, + accountId: { + propDefinition: [ + app, + "accountId", + ], + optional: true, + }, + websiteUrl: { + propDefinition: [ + app, + "websiteUrl", + ], + }, + labelNames: { + propDefinition: [ + app, + "labelNames", + ], + }, + contactStageId: { + propDefinition: [ + app, + "contactStageId", + ], + optional: true, + }, + address: { + propDefinition: [ + app, + "address", + ], + }, + phone: { + propDefinition: [ + app, + "phone", + ], + }, + }, + async run({ $: step }) { + let contact = {}; + let action = "created"; + let data = utils.cleanObject({ + email: this.email, + first_name: this.firstName, + last_name: this.lastName, + title: this.title, + account_id: this.accountId, + website_url: this.websiteUrl, + label_names: this.labelNames, + contact_stage_id: this.contactStageId, + present_raw_address: this.address, + direct_phone: this.phone, + }); + + const { contacts } = await this.app.listContacts({ + params: { + q_keywords: this.email, + }, + }); + + if (contacts.length) { + action = "updated"; + contact = contacts[0]; + + await this.app.updateContact({ + step, + contactId: contact.id, + data: { + ...contact, + ...data, + }, + }); + } else { + const response = await this.app.createContact({ + step, + data, + }); + contact = response.contact; + } + + step.export("$summary", `Successfully ${action} contact with ID ${contact.id}`); + + return contact; + }, +}; diff --git a/components/apollo_io/actions/get-opportunity/get-opportunity.mjs b/components/apollo_io/actions/get-opportunity/get-opportunity.mjs index 9d3aa6191ba3b..3ecb429ed1480 100644 --- a/components/apollo_io/actions/get-opportunity/get-opportunity.mjs +++ b/components/apollo_io/actions/get-opportunity/get-opportunity.mjs @@ -5,7 +5,7 @@ export default { name: "Get Opportunity", description: "Gets a specific opportunity in Apollo.io. [See the documentation](https://apolloio.github.io/apollo-api-docs/?shell#view-opportunity)", type: "action", - version: "0.0.1", + version: "0.0.2", props: { app, opportunityId: { diff --git a/components/apollo_io/actions/people-enrichment/people-enrichment.mjs b/components/apollo_io/actions/people-enrichment/people-enrichment.mjs index a93693a6fe2a6..dbfaaabb5d116 100644 --- a/components/apollo_io/actions/people-enrichment/people-enrichment.mjs +++ b/components/apollo_io/actions/people-enrichment/people-enrichment.mjs @@ -5,7 +5,7 @@ export default { name: "People Enrichment", description: "Enriches a person's information, the more information you pass in, the more likely we can find a match. [See the documentation](https://apolloio.github.io/apollo-api-docs/?shell#people-enrichment)", type: "action", - version: "0.0.3", + version: "0.0.4", props: { app, firstName: { diff --git a/components/apollo_io/actions/search-accounts/search-accounts.mjs b/components/apollo_io/actions/search-accounts/search-accounts.mjs index a05a451375d82..3ab3e7ec188cd 100644 --- a/components/apollo_io/actions/search-accounts/search-accounts.mjs +++ b/components/apollo_io/actions/search-accounts/search-accounts.mjs @@ -6,7 +6,7 @@ export default { name: "Search For Accounts", description: "Search for accounts in Apollo.io. [See the documentation](https://apolloio.github.io/apollo-api-docs/?shell#search-for-accounts)", type: "action", - version: "0.0.1", + version: "0.0.2", props: { app, search: { diff --git a/components/apollo_io/actions/search-contacts/search-contacts.mjs b/components/apollo_io/actions/search-contacts/search-contacts.mjs index 902752e32497c..64cdf64094156 100644 --- a/components/apollo_io/actions/search-contacts/search-contacts.mjs +++ b/components/apollo_io/actions/search-contacts/search-contacts.mjs @@ -6,7 +6,7 @@ export default { name: "Search For Contacts", description: "Search for contacts in Apollo.io. [See the documentation](https://apolloio.github.io/apollo-api-docs/?shell#search-for-contacts)", type: "action", - version: "0.0.1", + version: "0.0.2", props: { app, search: { diff --git a/components/apollo_io/actions/search-sequences/search-sequences.mjs b/components/apollo_io/actions/search-sequences/search-sequences.mjs index be62181650603..efe150e808fa6 100644 --- a/components/apollo_io/actions/search-sequences/search-sequences.mjs +++ b/components/apollo_io/actions/search-sequences/search-sequences.mjs @@ -6,7 +6,7 @@ export default { name: "Search For Sequences", description: "Search for sequences in Apollo.io. [See the documentation](https://apolloio.github.io/apollo-api-docs/?shell#search-for-sequences)", type: "action", - version: "0.0.1", + version: "0.0.2", props: { app, search: { diff --git a/components/apollo_io/actions/update-account-stage/update-account-stage.mjs b/components/apollo_io/actions/update-account-stage/update-account-stage.mjs index 35a9a8879b435..41ddcb560a606 100644 --- a/components/apollo_io/actions/update-account-stage/update-account-stage.mjs +++ b/components/apollo_io/actions/update-account-stage/update-account-stage.mjs @@ -5,7 +5,7 @@ export default { name: "Update Account Stage", description: "Updates the stage of one or more accounts in Apollo.io. [See the documentation](https://apolloio.github.io/apollo-api-docs/?shell#update-account-stage)", type: "action", - version: "0.0.2", + version: "0.0.3", props: { app, accountIds: { diff --git a/components/apollo_io/actions/update-account/update-account.mjs b/components/apollo_io/actions/update-account/update-account.mjs index 576f925231a35..cc4e7f46de2f2 100644 --- a/components/apollo_io/actions/update-account/update-account.mjs +++ b/components/apollo_io/actions/update-account/update-account.mjs @@ -6,7 +6,7 @@ export default { name: "Update Account", description: "Updates an existing account in Apollo.io. [See the documentation](https://apolloio.github.io/apollo-api-docs/?shell#update-an-account)", type: "action", - version: "0.0.2", + version: "0.0.3", props: { app, accountId: { diff --git a/components/apollo_io/actions/update-contact-stage/update-contact-stage.mjs b/components/apollo_io/actions/update-contact-stage/update-contact-stage.mjs index 4cfd984933c46..3ece01c6e686d 100644 --- a/components/apollo_io/actions/update-contact-stage/update-contact-stage.mjs +++ b/components/apollo_io/actions/update-contact-stage/update-contact-stage.mjs @@ -5,7 +5,7 @@ export default { name: "Update Contact Stage", description: "Updates the stage of one or more contacts in Apollo.io. [See the documentation](https://apolloio.github.io/apollo-api-docs/?shell#update-contact-stage)", type: "action", - version: "0.0.2", + version: "0.0.3", props: { app, contactIds: { diff --git a/components/apollo_io/actions/update-contact/update-contact.mjs b/components/apollo_io/actions/update-contact/update-contact.mjs index 04ec17fe96fb2..248c6b35d79e7 100644 --- a/components/apollo_io/actions/update-contact/update-contact.mjs +++ b/components/apollo_io/actions/update-contact/update-contact.mjs @@ -6,7 +6,7 @@ export default { name: "Update Contact", description: "Updates an existing contact in Apollo.io. [See the documentation](https://apolloio.github.io/apollo-api-docs/?shell#update-a-contact)", type: "action", - version: "0.0.3", + version: "0.0.4", props: { app, contactId: { diff --git a/components/apollo_io/actions/update-opportunity/update-opportunity.mjs b/components/apollo_io/actions/update-opportunity/update-opportunity.mjs index dc40320f2d756..28ef5aabc6fa2 100644 --- a/components/apollo_io/actions/update-opportunity/update-opportunity.mjs +++ b/components/apollo_io/actions/update-opportunity/update-opportunity.mjs @@ -5,7 +5,7 @@ export default { name: "Update Opportunity", description: "Updates a new opportunity in Apollo.io. [See the documentation](https://apolloio.github.io/apollo-api-docs/?shell#update-opportunity)", type: "action", - version: "0.0.1", + version: "0.0.2", props: { app, opportunityId: { diff --git a/components/apollo_io/package.json b/components/apollo_io/package.json index 6d4ae79079fea..48822e078ffbf 100644 --- a/components/apollo_io/package.json +++ b/components/apollo_io/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/apollo_io", - "version": "0.3.0", + "version": "0.4.0", "description": "Pipedream Apollo.io Components", "main": "apollo_io.app.mjs", "keywords": [ @@ -13,7 +13,7 @@ "access": "public" }, "dependencies": { - "@pipedream/platform": "^1.6.0", + "@pipedream/platform": "^3.0.3", "md5": "^2.3.0" } } diff --git a/components/apollo_io/sources/account-created/account-created.mjs b/components/apollo_io/sources/account-created/account-created.mjs index 282fd099a06bd..ad39bc2776f78 100644 --- a/components/apollo_io/sources/account-created/account-created.mjs +++ b/components/apollo_io/sources/account-created/account-created.mjs @@ -6,7 +6,7 @@ export default { name: "Account Created", description: "Triggers when an account is created. [See the documentation](https://apolloio.github.io/apollo-api-docs/?shell#search-for-accounts)", type: "source", - version: "0.0.2", + version: "0.0.3", dedupe: "unique", props: { ...common.props, diff --git a/components/apollo_io/sources/account-updated/account-updated.mjs b/components/apollo_io/sources/account-updated/account-updated.mjs index a9775226a870d..f2b1afe8d22a5 100644 --- a/components/apollo_io/sources/account-updated/account-updated.mjs +++ b/components/apollo_io/sources/account-updated/account-updated.mjs @@ -7,7 +7,7 @@ export default { name: "Account Updated", description: "Triggers when an account is updated. [See the documentation](https://apolloio.github.io/apollo-api-docs/?shell#search-for-contacts)", type: "source", - version: "0.0.2", + version: "0.0.3", dedupe: "unique", props: { ...common.props, diff --git a/components/apollo_io/sources/contact-created/contact-created.mjs b/components/apollo_io/sources/contact-created/contact-created.mjs index e0bb9faca9eb6..a8978e5c9a349 100644 --- a/components/apollo_io/sources/contact-created/contact-created.mjs +++ b/components/apollo_io/sources/contact-created/contact-created.mjs @@ -6,7 +6,7 @@ export default { name: "Contact Created", description: "Triggers when a contact is created. [See the documentation](https://apolloio.github.io/apollo-api-docs/?shell#search-for-contacts)", type: "source", - version: "0.0.4", + version: "0.0.5", dedupe: "unique", methods: { ...common.methods, diff --git a/components/apollo_io/sources/contact-updated/contact-updated.mjs b/components/apollo_io/sources/contact-updated/contact-updated.mjs index 45b49d4819695..7b4b7619dee54 100644 --- a/components/apollo_io/sources/contact-updated/contact-updated.mjs +++ b/components/apollo_io/sources/contact-updated/contact-updated.mjs @@ -6,7 +6,7 @@ export default { name: "Contact Updated", description: "Triggers when a contact is updated. [See the documentation](https://apolloio.github.io/apollo-api-docs/?shell#search-for-contacts)", type: "source", - version: "0.0.4", + version: "0.0.5", dedupe: "unique", methods: { ...common.methods, diff --git a/components/attio/actions/create-note/create-note.mjs b/components/attio/actions/create-note/create-note.mjs index 6811657df129e..923c548133fde 100644 --- a/components/attio/actions/create-note/create-note.mjs +++ b/components/attio/actions/create-note/create-note.mjs @@ -4,7 +4,7 @@ export default { key: "attio-create-note", name: "Create Note", description: "Creates a new note for a given record. The note will be linked to the specified record. [See the documentation](https://developers.attio.com/reference/post_v2-notes)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { attio, diff --git a/components/attio/actions/create-update-record/create-update-record.mjs b/components/attio/actions/create-update-record/create-update-record.mjs index 648a2c8b96c87..e0abe3c586fd1 100644 --- a/components/attio/actions/create-update-record/create-update-record.mjs +++ b/components/attio/actions/create-update-record/create-update-record.mjs @@ -5,7 +5,7 @@ export default { key: "attio-create-update-record", name: "Create or Update Record", description: "Creates or updates a specific record such as a person or a deal. If the record already exists, it's updated. Otherwise, a new record is created. [See the documentation](https://developers.attio.com/reference/put_v2-objects-object-records)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { attio, diff --git a/components/attio/actions/delete-list-entry/delete-list-entry.mjs b/components/attio/actions/delete-list-entry/delete-list-entry.mjs index 5c866be65fade..3b98149d16d05 100644 --- a/components/attio/actions/delete-list-entry/delete-list-entry.mjs +++ b/components/attio/actions/delete-list-entry/delete-list-entry.mjs @@ -4,7 +4,7 @@ export default { key: "attio-delete-list-entry", name: "Delete List Entry", description: "Deletes an existing entry from a specific list. [See the documentation](https://developers.attio.com/reference/delete_v2-lists-list-entries-entry-id)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { attio, diff --git a/components/attio/attio.app.mjs b/components/attio/attio.app.mjs index ecd1af069dacc..27f2fb1027f24 100644 --- a/components/attio/attio.app.mjs +++ b/components/attio/attio.app.mjs @@ -64,7 +64,15 @@ export default { offset: page * DEFAULT_LIMIT, }, }); - return data?.map(({ id }) => id.record_id) || []; + return data?.map(({ + id, values, + }) => ({ + value: id.record_id, + label: (values?.name?.length && (values.name[0].value || values.name[0].full_name)) + ?? (values?.domains?.length && values.domains[0].domain) + ?? (values?.email_addresses?.length && values.email_addresses[0].email_address) + ?? values?.id?.record_id, + })) || []; }, }, attributeId: { diff --git a/components/attio/package.json b/components/attio/package.json index 247666fdb730f..a817f7de21633 100644 --- a/components/attio/package.json +++ b/components/attio/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/attio", - "version": "0.1.0", + "version": "0.1.1", "description": "Pipedream Attio Components", "main": "attio.app.mjs", "keywords": [ diff --git a/components/attio/sources/new-list-entry-instant/new-list-entry-instant.mjs b/components/attio/sources/new-list-entry-instant/new-list-entry-instant.mjs index ebb0c2409497d..886af1869c311 100644 --- a/components/attio/sources/new-list-entry-instant/new-list-entry-instant.mjs +++ b/components/attio/sources/new-list-entry-instant/new-list-entry-instant.mjs @@ -6,7 +6,7 @@ export default { key: "attio-new-list-entry-instant", name: "New List Entry (Instant)", description: "Emit new event when a record, such as person, company, or deal, is added to a list", - version: "0.0.1", + version: "0.0.2", type: "source", dedupe: "unique", props: { diff --git a/components/attio/sources/new-record-created-instant/new-record-created-instant.mjs b/components/attio/sources/new-record-created-instant/new-record-created-instant.mjs index 9e374d218e0c4..6bf50c00fb8eb 100644 --- a/components/attio/sources/new-record-created-instant/new-record-created-instant.mjs +++ b/components/attio/sources/new-record-created-instant/new-record-created-instant.mjs @@ -6,7 +6,7 @@ export default { key: "attio-new-record-created-instant", name: "New Record Created (Instant)", description: "Emit new event when new record, such as person, company or deal gets created", - version: "0.0.1", + version: "0.0.2", type: "source", dedupe: "unique", props: { diff --git a/components/attio/sources/record-updated-instant/record-updated-instant.mjs b/components/attio/sources/record-updated-instant/record-updated-instant.mjs index e94b7b17efce7..cfe60ad7bdeef 100644 --- a/components/attio/sources/record-updated-instant/record-updated-instant.mjs +++ b/components/attio/sources/record-updated-instant/record-updated-instant.mjs @@ -6,7 +6,7 @@ export default { key: "attio-record-updated-instant", name: "Record Updated (Instant)", description: "Emit new event when values on a record, such as person, company or deal, are updated", - version: "0.0.1", + version: "0.0.2", type: "source", dedupe: "unique", props: { diff --git a/components/autoblogger/actions/get-blogposts/get-blogposts.mjs b/components/autoblogger/actions/get-blogposts/get-blogposts.mjs new file mode 100644 index 0000000000000..646f687bd6295 --- /dev/null +++ b/components/autoblogger/actions/get-blogposts/get-blogposts.mjs @@ -0,0 +1,22 @@ +import app from "../../autoblogger.app.mjs"; + +export default { + key: "autoblogger-get-blogposts", + name: "Get Blogposts", + description: "Retrieves blogposts using the API key. [See the documentation](https://u.pcloud.link/publink/show?code=XZdjuv0ZtabS8BN58thUiE8FGjznajoMc6Qy)", + version: "0.0.1", + type: "action", + props: { + app, + }, + + async run({ $ }) { + const response = await this.app.getBlogposts({ + $, + }); + + $.export("$summary", `Successfully retrieved ${response.length} blogposts`); + + return response; + }, +}; diff --git a/components/autoblogger/actions/validate-api-key/validate-api-key.mjs b/components/autoblogger/actions/validate-api-key/validate-api-key.mjs new file mode 100644 index 0000000000000..9fca04302f918 --- /dev/null +++ b/components/autoblogger/actions/validate-api-key/validate-api-key.mjs @@ -0,0 +1,23 @@ +import app from "../../autoblogger.app.mjs"; + +export default { + key: "autoblogger-validate-api-key", + name: "Validate API Key", + description: "Validates the provided API key. [See the documentation](https://u.pcloud.link/publink/show?code=XZdjuv0ZtabS8BN58thUiE8FGjznajoMc6Qy)", + version: "0.0.1", + type: "action", + props: { + app, + }, + async run({ $ }) { + const response = await this.app.validateKey({ + $, + }); + + $.export("$summary", `API Key ${response.is_valid + ? "is" + : "isn't"} valid`); + + return response; + }, +}; diff --git a/components/autoblogger/autoblogger.app.mjs b/components/autoblogger/autoblogger.app.mjs index 9e54a87dfec80..408803f971739 100644 --- a/components/autoblogger/autoblogger.app.mjs +++ b/components/autoblogger/autoblogger.app.mjs @@ -1,11 +1,41 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "autoblogger", propDefinitions: {}, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://autoblogger-api.otherweb.com"; + }, + async _makeRequest(opts = {}) { + const { + $ = this, + path, + headers, + ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + url: this._baseUrl() + path, + headers: { + ...headers, + "x-api-key": `${this.$auth.api_key}`, + "Accept": "application/json", + }, + }); + }, + async validateKey(args = {}) { + return this._makeRequest({ + path: "/api/v1/site/validate/apikey", + ...args, + }); + }, + async getBlogposts(args = {}) { + return this._makeRequest({ + path: "/api/v1/blogposts", + ...args, + }); }, }, }; diff --git a/components/autoblogger/package.json b/components/autoblogger/package.json index 2db61f7a014ae..3e5ddee0c3e8e 100644 --- a/components/autoblogger/package.json +++ b/components/autoblogger/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/autoblogger", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream AutoBlogger Components", "main": "autoblogger.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } -} \ No newline at end of file +} diff --git a/components/azure_sql/actions/execute-query/execute-query.mjs b/components/azure_sql/actions/execute-query/execute-query.mjs index 8903f8fdb59c9..42bbdc31adea4 100644 --- a/components/azure_sql/actions/execute-query/execute-query.mjs +++ b/components/azure_sql/actions/execute-query/execute-query.mjs @@ -5,7 +5,7 @@ export default { name: "Execute Query", description: "Executes a SQL query and returns the results. [See the documentation](https://learn.microsoft.com/en-us/sql/t-sql/queries/select-transact-sql?view=azuresqldb-current)", type: "action", - version: "0.0.5", + version: "0.0.6", props: { app, query: { @@ -20,18 +20,26 @@ export default { description: "The inputs to the query. These will be available as @input_parameter in the query. For example, if you provide an input named 'id', you can use @id in the query.", optional: true, }, + requestTimeout: { + propDefinition: [ + app, + "requestTimeout", + ], + }, }, run({ $ }) { const { app, inputs, query, + requestTimeout, } = this; return app.executeQuery({ $, query, inputs, + requestTimeout, summary: () => "Successfully executed query.", }); }, diff --git a/components/azure_sql/actions/execute-raw-query/execute-raw-query.mjs b/components/azure_sql/actions/execute-raw-query/execute-raw-query.mjs index 3c0937967ec81..04f303c1c4e7b 100644 --- a/components/azure_sql/actions/execute-raw-query/execute-raw-query.mjs +++ b/components/azure_sql/actions/execute-raw-query/execute-raw-query.mjs @@ -5,10 +5,9 @@ export default { name: "Execute SQL Query", description: "Execute a custom SQL query. See [our docs](https://pipedream.com/docs/databases/working-with-sql) to learn more about working with SQL in Pipedream.", type: "action", - version: "0.0.4", + version: "0.0.5", props: { app, - // eslint-disable-next-line pipedream/props-description sql: { type: "sql", auth: { @@ -16,9 +15,16 @@ export default { }, label: "SQL Query", }, + requestTimeout: { + propDefinition: [ + app, + "requestTimeout", + ], + }, }, async run({ $ }) { const args = this.app.executeQueryAdapter(this.sql); + args.requestTimeout = this.requestTimeout; const response = await this.app.executeQuery(args); $.export("$summary", "Successfully executed query."); return response; diff --git a/components/azure_sql/actions/insert-row/insert-row.mjs b/components/azure_sql/actions/insert-row/insert-row.mjs index a64dde2806345..78bcaf1f88acc 100644 --- a/components/azure_sql/actions/insert-row/insert-row.mjs +++ b/components/azure_sql/actions/insert-row/insert-row.mjs @@ -4,7 +4,7 @@ export default { key: "azure_sql-insert-row", name: "Insert Row", description: "Inserts a new row in a table. [See the documentation](https://learn.microsoft.com/en-us/sql/t-sql/statements/insert-transact-sql?view=azuresqldb-current)", - version: "0.0.5", + version: "0.0.6", type: "action", props: { app, @@ -16,6 +16,12 @@ export default { ], reloadProps: true, }, + requestTimeout: { + propDefinition: [ + app, + "requestTimeout", + ], + }, }, async additionalProps() { const { @@ -41,6 +47,7 @@ export default { const { app, table, + requestTimeout, ...inputs } = this; @@ -48,6 +55,7 @@ export default { $, table, inputs, + requestTimeout, summary: () => "Successfully inserted row.", }); }, diff --git a/components/azure_sql/azure_sql.app.mjs b/components/azure_sql/azure_sql.app.mjs index bf34b1933d1a9..16b48386e4d47 100644 --- a/components/azure_sql/azure_sql.app.mjs +++ b/components/azure_sql/azure_sql.app.mjs @@ -28,6 +28,13 @@ export default { return recordset.map(({ COLUMN_NAME: columnName }) => columnName); }, }, + requestTimeout: { + type: "integer", + label: "Request Timeout", + description: "The timeout for query execution in seconds. Default is 60 seconds if not specified.", + optional: true, + default: 60, + }, }, methods: { ...sqlProxy.methods, @@ -37,7 +44,7 @@ export default { }, getConfig() { const { - host, username, password, port, database, + host, username, password, port, database, requestTimeout, } = this.$auth; return { user: username, @@ -51,7 +58,7 @@ export default { options: { encrypt: true, port: Number(port), - requestTimeout: 60000, + requestTimeout: (requestTimeout || 60) * 1000, // Convert to milliseconds }, }; }, @@ -148,10 +155,16 @@ export default { }, async executeQuery(preparedStatement = {}) { let connection; - const { query } = preparedStatement; + const { + query, requestTimeout, + } = preparedStatement; const inputs = preparedStatement?.inputs || {}; try { - connection = await sql.connect(this.getClientConfiguration()); + const config = this.getConfig(); + if (requestTimeout) { + config.options.requestTimeout = requestTimeout * 1000; // Convert to milliseconds + } + connection = await sql.connect(config); const input = Object.entries(inputs) .reduce((req, inputArgs) => diff --git a/components/azure_sql/package.json b/components/azure_sql/package.json index d2a299f1904fe..2eb548a18e497 100644 --- a/components/azure_sql/package.json +++ b/components/azure_sql/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/azure_sql", - "version": "0.1.4", + "version": "0.1.5", "description": "Pipedream Microsoft Azure SQL Database Components", "main": "azure_sql.app.mjs", "keywords": [ diff --git a/components/azure_sql/sources/new-column/new-column.mjs b/components/azure_sql/sources/new-column/new-column.mjs index a6d6747ee0fe3..cd9286c62076c 100644 --- a/components/azure_sql/sources/new-column/new-column.mjs +++ b/components/azure_sql/sources/new-column/new-column.mjs @@ -7,7 +7,7 @@ export default { name: "New Column", description: "Triggers when a new column is added to a table.", type: "source", - version: "0.0.5", + version: "0.0.6", dedupe: "unique", props: { ...common.props, diff --git a/components/azure_sql/sources/new-or-updated-row/new-or-updated-row.mjs b/components/azure_sql/sources/new-or-updated-row/new-or-updated-row.mjs index 8f3e65214e561..bef6d6b1ebdea 100644 --- a/components/azure_sql/sources/new-or-updated-row/new-or-updated-row.mjs +++ b/components/azure_sql/sources/new-or-updated-row/new-or-updated-row.mjs @@ -7,7 +7,7 @@ export default { name: "New or Updated Row", description: "Triggers when a new row is added or an existing row is updated.", type: "source", - version: "0.0.5", + version: "0.0.6", dedupe: "unique", props: { ...common.props, diff --git a/components/beaconchain/actions/get-epoch/get-epoch.mjs b/components/beaconchain/actions/get-epoch/get-epoch.mjs new file mode 100644 index 0000000000000..fb75c4fe52e13 --- /dev/null +++ b/components/beaconchain/actions/get-epoch/get-epoch.mjs @@ -0,0 +1,42 @@ +import app from "../../beaconchain.app.mjs"; + +export default { + key: "beaconchain-get-epoch", + name: "Get Epoch", + description: "Returns information for a specified epoch by the epoch number or an epoch tag (can be latest or finalized). [See the documentation](https://beaconcha.in/api/v1/docs/index.html#/Epoch/get_api_v1_epoch__epoch_)", + version: "0.0.1", + type: "action", + props: { + app, + epoch: { + propDefinition: [ + app, + "epoch", + ], + }, + }, + methods: { + getEpoch({ + epoch, ...args + } = {}) { + return this.app._makeRequest({ + path: `/epoch/${epoch}`, + ...args, + }); + }, + }, + async run({ $ }) { + const { + getEpoch, + epoch, + } = this; + + const response = await getEpoch({ + $, + epoch, + }); + + $.export("$summary", `Successfully retrieved epoch \`${response.data.epoch}\`.`); + return response; + }, +}; diff --git a/components/beaconchain/actions/get-execution-blocks/get-execution-blocks.mjs b/components/beaconchain/actions/get-execution-blocks/get-execution-blocks.mjs new file mode 100644 index 0000000000000..849ab5104a46e --- /dev/null +++ b/components/beaconchain/actions/get-execution-blocks/get-execution-blocks.mjs @@ -0,0 +1,43 @@ +import app from "../../beaconchain.app.mjs"; + +export default { + key: "beaconchain-get-execution-blocks", + name: "Get Execution Blocks", + description: "Retrieve execution blocks by execution block number. [See the documentation](https://beaconcha.in/api/v1/docs/index.html#/Execution/get_api_v1_execution_block__blockNumber_)", + version: "0.0.1", + type: "action", + props: { + app, + blockNumbers: { + type: "string[]", + label: "Block Number", + description: "Enter one or more execution block numbers, up to a maximum of 100.", + }, + }, + methods: { + getExecutionBlocks({ + blockNumber, ...args + } = {}) { + return this.app._makeRequest({ + path: `/execution/block/${blockNumber}`, + ...args, + }); + }, + }, + async run({ $ }) { + const { + getExecutionBlocks, + blockNumbers, + } = this; + + const response = await getExecutionBlocks({ + $, + blockNumber: Array.isArray(blockNumbers) + ? blockNumbers.map((value) => value.trim()).join(",") + : blockNumbers, + }); + + $.export("$summary", "Successfully retrieved execution blocks."); + return response; + }, +}; diff --git a/components/beaconchain/actions/get-slots/get-slots.mjs b/components/beaconchain/actions/get-slots/get-slots.mjs new file mode 100644 index 0000000000000..59eb8a851554d --- /dev/null +++ b/components/beaconchain/actions/get-slots/get-slots.mjs @@ -0,0 +1,43 @@ +import app from "../../beaconchain.app.mjs"; + +export default { + key: "beaconchain-get-slots", + name: "Get Slots", + description: "Returns all slots for a specified epoch. [See the documentation](https://beaconcha.in/api/v1/docs/index.html#/Epoch/get_api_v1_epoch__epoch__slots)", + version: "0.0.1", + type: "action", + props: { + app, + epoch: { + description: "Returns all slots for a specified epoch.", + propDefinition: [ + app, + "epoch", + ], + }, + }, + methods: { + getEpochSlots({ + epoch, ...args + } = {}) { + return this.app._makeRequest({ + path: `/epoch/${epoch}/slots`, + ...args, + }); + }, + }, + async run({ $ }) { + const { + getEpochSlots, + epoch, + } = this; + + const response = await getEpochSlots({ + $, + epoch, + }); + + $.export("$summary", `Successfully retrieved \`${response.data.length}\` slot(s).`); + return response; + }, +}; diff --git a/components/beaconchain/actions/get-validators/get-validators.mjs b/components/beaconchain/actions/get-validators/get-validators.mjs new file mode 100644 index 0000000000000..a9eb627e7bcc0 --- /dev/null +++ b/components/beaconchain/actions/get-validators/get-validators.mjs @@ -0,0 +1,43 @@ +import app from "../../beaconchain.app.mjs"; + +export default { + key: "beaconchain-get-validators", + name: "Get Validators", + description: "Returns information for all validators up to 100 by index or public key. [See the documentation](https://beaconcha.in/api/v1/docs/index.html#/Validator/get_api_v1_validator__indexOrPubkey_).", + version: "0.0.1", + type: "action", + props: { + app, + indexesOrPubkeys: { + type: "string[]", + label: "Validator Indexes Or Public Keys", + description: "Enter up to 100 validator indices or public keys.", + }, + }, + methods: { + getValidators({ + indexOrPubkey, ...args + } = {}) { + return this.app._makeRequest({ + path: `/validator/${indexOrPubkey}`, + ...args, + }); + }, + }, + async run({ $ }) { + const { + getValidators, + indexesOrPubkeys, + } = this; + + const response = await getValidators({ + $, + indexOrPubkey: Array.isArray(indexesOrPubkeys) + ? indexesOrPubkeys.map((value) => value.trim()).join(",") + : indexesOrPubkeys, + }); + + $.export("$summary", "Successfully retrieved validators."); + return response; + }, +}; diff --git a/components/beaconchain/beaconchain.app.mjs b/components/beaconchain/beaconchain.app.mjs new file mode 100644 index 0000000000000..887f2e4ade733 --- /dev/null +++ b/components/beaconchain/beaconchain.app.mjs @@ -0,0 +1,40 @@ +import { axios } from "@pipedream/platform"; +import constants from "./common/constants.mjs"; + +export default { + type: "app", + app: "beaconchain", + propDefinitions: { + epoch: { + type: "string", + label: "Epoch", + description: "Epoch number, the string `latest` or the string `finalized`.", + }, + }, + methods: { + getUrl(path) { + return `${constants.BASE_URL}${constants.VERSION_PATH}${path}`; + }, + getHeaders(headers) { + return { + ...headers, + "apikey": `${this.$auth.api_key}`, + }; + }, + _makeRequest({ + $ = this, path, headers, ...args + } = {}) { + return axios($, { + ...args, + url: this.getUrl(path), + headers: this.getHeaders(headers), + }); + }, + post(args = {}) { + return this._makeRequest({ + method: "POST", + ...args, + }); + }, + }, +}; diff --git a/components/beaconchain/common/constants.mjs b/components/beaconchain/common/constants.mjs new file mode 100644 index 0000000000000..3fc051957d671 --- /dev/null +++ b/components/beaconchain/common/constants.mjs @@ -0,0 +1,7 @@ +const BASE_URL = "https://beaconcha.in"; +const VERSION_PATH = "/api/v1"; + +export default { + BASE_URL, + VERSION_PATH, +}; diff --git a/components/beaconchain/package.json b/components/beaconchain/package.json new file mode 100644 index 0000000000000..b15f4a742e4ca --- /dev/null +++ b/components/beaconchain/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/beaconchain", + "version": "0.1.0", + "description": "Pipedream Beaconchain Components", + "main": "beaconchain.app.mjs", + "keywords": [ + "pipedream", + "beaconchain" + ], + "homepage": "https://pipedream.com/apps/beaconchain", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "3.0.3" + } +} diff --git a/components/bippybox/actions/activate-box/activate-box.mjs b/components/bippybox/actions/activate-box/activate-box.mjs new file mode 100644 index 0000000000000..7c8859fc44a33 --- /dev/null +++ b/components/bippybox/actions/activate-box/activate-box.mjs @@ -0,0 +1,39 @@ +import app from "../../bippybox.app.mjs"; + +export default { + key: "bippybox-activate-box", + name: "Activate Box", + description: "Triggers the BippyBox to play an audio file. [See the documentation](https://bippybox.io/docs/).", + version: "0.0.1", + type: "action", + props: { + app, + device: { + type: "string", + label: "Device", + description: "The device identifier. Eg. `DEVICE123`.", + }, + url: { + type: "string", + label: "URL", + description: "The URL of the audio file to play. Eg. `https://storage.example.com/users/exampleUserUID67890/audio/SampleAudioFile.wav?alt=media&token=exampleToken123456`.", + }, + }, + async run({ $ }) { + const { + app, + device, + url, + } = this; + + const response = await app.activateBox({ + $, + data: { + device, + URL: url, + }, + }); + $.export("$summary", "Successfully activated BippyBox."); + return response; + }, +}; diff --git a/components/bippybox/bippybox.app.mjs b/components/bippybox/bippybox.app.mjs index a28fdbc083d01..52c5cd0c53e20 100644 --- a/components/bippybox/bippybox.app.mjs +++ b/components/bippybox/bippybox.app.mjs @@ -1,11 +1,46 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "bippybox", - propDefinitions: {}, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + getUrl(path) { + return `https://websocket.bippybox.io${path}`; + }, + getHeaders(headers) { + return { + ...headers, + "Content-Type": "application/json", + "x-api-key": this.$auth.api_key, + }; + }, + _makeRequest({ + $ = this, path, headers, ...args + } = {}) { + return axios($, { + ...args, + url: this.getUrl(path), + headers: this.getHeaders(headers), + }); + }, + post(args = {}) { + return this._makeRequest({ + method: "POST", + ...args, + }); + }, + activateBox({ + data, ...args + } = {}) { + const { uid } = this.$auth; + return this.post({ + path: "/send", + data: { + ...data, + uid, + }, + ...args, + }); }, }, -}; \ No newline at end of file +}; diff --git a/components/bippybox/package.json b/components/bippybox/package.json index 4d0a22c696fc8..c69829f985d38 100644 --- a/components/bippybox/package.json +++ b/components/bippybox/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/bippybox", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream BippyBox Components", "main": "bippybox.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "3.0.3" } -} \ No newline at end of file +} diff --git a/components/campaign_monitor/actions/add-subscriber/add-subscriber.mjs b/components/campaign_monitor/actions/add-subscriber/add-subscriber.mjs new file mode 100644 index 0000000000000..4d5e440692e1d --- /dev/null +++ b/components/campaign_monitor/actions/add-subscriber/add-subscriber.mjs @@ -0,0 +1,84 @@ +import campaignMonitor from "../../campaign_monitor.app.mjs"; + +export default { + key: "campaign_monitor-add-subscriber", + name: "Add Subscriber", + description: "Creates a new subscriber on a specific list. [See the documentation](https://www.campaignmonitor.com/api/v3-3/subscribers/)", + version: "0.0.1", + type: "action", + props: { + campaignMonitor, + clientId: { + propDefinition: [ + campaignMonitor, + "clientId", + ], + }, + listId: { + propDefinition: [ + campaignMonitor, + "listId", + (c) => ({ + clientId: c.clientId, + }), + ], + }, + email: { + type: "string", + label: "Email", + description: "The email address of the subscriber", + }, + name: { + type: "string", + label: "Name", + description: "The name of the subscriber", + optional: true, + }, + phone: { + type: "string", + label: "Phone", + description: "The phone number of the subscriber. Example: `+5012398752`", + optional: true, + }, + consentToTrack: { + propDefinition: [ + campaignMonitor, + "consentToTrack", + ], + }, + consentToSendSMS: { + type: "string", + label: "Consent to Send SMS", + description: "Indicates if consent has been granted by the subscriber to receive Sms", + options: [ + "Yes", + "No", + "Unchanged", + ], + default: "Unchanged", + optional: true, + }, + resubscribe: { + type: "boolean", + label: "Resubscribe", + description: "Resubscribe if the email address has previously been unsubscribed", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.campaignMonitor.createSubscriber({ + $, + listId: this.listId, + data: { + EmailAddress: this.email, + Name: this.name, + MobileNumber: this.phone, + ConsentToTrack: this.consentToTrack, + ConsentToSendSms: this.consentToSendSMS, + Resubscribe: this.resubscribe, + }, + }); + $.export("$summary", `Successfully added subscriber ${this.email}`); + return response; + }, +}; diff --git a/components/campaign_monitor/actions/send-smart-transactional-email/send-smart-transactional-email.mjs b/components/campaign_monitor/actions/send-smart-transactional-email/send-smart-transactional-email.mjs new file mode 100644 index 0000000000000..3507386a189f6 --- /dev/null +++ b/components/campaign_monitor/actions/send-smart-transactional-email/send-smart-transactional-email.mjs @@ -0,0 +1,64 @@ +import campaignMonitor from "../../campaign_monitor.app.mjs"; + +export default { + key: "campaign_monitor-send-smart-transactional-email", + name: "Send Smart Transactional Email", + description: "Sends an intelligent transactional email to a specified recipient. [See the documentation](https://www.campaignmonitor.com/api/v3-3/transactional/#send-smart-email)", + version: "0.0.1", + type: "action", + props: { + campaignMonitor, + clientId: { + propDefinition: [ + campaignMonitor, + "clientId", + ], + }, + smartEmailId: { + propDefinition: [ + campaignMonitor, + "smartEmailId", + (c) => ({ + clientId: c.clientId, + }), + ], + }, + to: { + type: "string", + label: "To", + description: "An array of email addresses to send the email to", + }, + cc: { + type: "string", + label: "CC", + description: "An array of email address to carbon copy the email to", + optional: true, + }, + bcc: { + type: "string", + label: "BCC", + description: "An array of email address to blind carbon copy the email to", + optional: true, + }, + consentToTrack: { + propDefinition: [ + campaignMonitor, + "consentToTrack", + ], + }, + }, + async run({ $ }) { + const response = await this.campaignMonitor.sendSmartEmail({ + $, + smartEmailId: this.smartEmailId, + data: { + To: this.to, + CC: this.cc, + BCC: this.bcc, + ConsentToTrack: this.consentToTrack, + }, + }); + $.export("$summary", "Successfully sent smart transactional email"); + return response; + }, +}; diff --git a/components/campaign_monitor/actions/unsubscribe/unsubscribe.mjs b/components/campaign_monitor/actions/unsubscribe/unsubscribe.mjs new file mode 100644 index 0000000000000..fb5d562932f90 --- /dev/null +++ b/components/campaign_monitor/actions/unsubscribe/unsubscribe.mjs @@ -0,0 +1,47 @@ +import campaignMonitor from "../../campaign_monitor.app.mjs"; + +export default { + key: "campaign_monitor-unsubscribe", + name: "Unsubscribe", + description: "Removes a subscriber from a mailing list given their email address. [See the documentation](https://www.campaignmonitor.com/api/v3-3/subscribers/#unsubscribing-a-subscriber)", + version: "0.0.1", + type: "action", + props: { + campaignMonitor, + clientId: { + propDefinition: [ + campaignMonitor, + "clientId", + ], + }, + listId: { + propDefinition: [ + campaignMonitor, + "listId", + (c) => ({ + clientId: c.clientId, + }), + ], + }, + subscriber: { + propDefinition: [ + campaignMonitor, + "subscriber", + (c) => ({ + listId: c.listId, + }), + ], + }, + }, + async run({ $ }) { + const response = await this.campaignMonitor.unsubscribeSubscriber({ + $, + listId: this.listId, + data: { + EmailAddress: this.subscriber, + }, + }); + $.export("$summary", `Successfully unsubscribed ${this.subscriber}`); + return response; + }, +}; diff --git a/components/campaign_monitor/campaign_monitor.app.mjs b/components/campaign_monitor/campaign_monitor.app.mjs new file mode 100644 index 0000000000000..ead348f6733b7 --- /dev/null +++ b/components/campaign_monitor/campaign_monitor.app.mjs @@ -0,0 +1,270 @@ +import { axios } from "@pipedream/platform"; +const DEFAULT_PAGE_SIZE = 1000; + +export default { + type: "app", + app: "campaign_monitor", + propDefinitions: { + clientId: { + type: "string", + label: "Client ID", + description: "The ID of the client", + async options() { + const clients = await this.listClients(); + return clients?.map(({ + ClientID: value, Name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + campaignId: { + type: "string", + label: "Campaign ID", + description: "The ID of a sent or scheduled campaign", + async options({ clientId }) { + const campaigns = await this.listCampaigns(clientId); + return campaigns?.map(({ + CampaignID: value, Name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + subscriber: { + type: "string", + label: "Subscriber", + description: "The email address of the subscriber", + async options({ + listId, page, + }) { + const { Results: subscribers } = await this.listSubscribers({ + listId, + params: { + page: page + 1, + }, + }); + return subscribers?.map(({ + EmailAddress: value, Name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + listId: { + type: "string", + label: "List ID", + description: "The ID of the list", + async options({ clientId }) { + const lists = await this.listLists({ + clientId, + }); + return lists?.map(({ + ListID: value, Name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + smartEmailId: { + type: "string", + label: "Smart Email ID", + description: "The ID of the smart email to send", + async options({ clientId }) { + const emails = await this.listSmartEmails({ + params: { + clientId, + }, + }); + return emails?.map(({ + ID: value, Name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + consentToTrack: { + type: "string", + label: "Consent to Track", + description: "Whether the subscriber has given permission to have their email opens and clicks tracked", + options: [ + "Yes", + "No", + "Unchanged", + ], + default: "Unchanged", + optional: true, + }, + }, + methods: { + _baseUrl() { + return "https://api.createsend.com/api/v3.3"; + }, + async _makeRequest(opts = {}) { + const { + $ = this, + path, + ...otherOpts + } = opts; + const requestFn = async () => { + return await axios($, { + ...otherOpts, + url: `${this._baseUrl()}${path}`, + headers: { + Authorization: `Bearer ${this.$auth.oauth_access_token}`, + }, + }); + }; + return await this.retryWithExponentialBackoff(requestFn); + }, + // The API has been observed to occasionally return - + // {"Code":120,"Message":"Invalid OAuth Token"} + // Retry if a 401 Unauthorized or 429 (Rate limit exceeded) + // status is returned + async retryWithExponentialBackoff(requestFn, retries = 3, backoff = 500) { + try { + return await requestFn(); + } catch (error) { + if (retries > 0 && (error.response?.status === 401 || error.response?.status === 429)) { + await new Promise((resolve) => setTimeout(resolve, backoff)); + return this.retryWithExponentialBackoff(requestFn, retries - 1, backoff * 2); + } + throw error; + } + }, + listClients(opts = {}) { + return this._makeRequest({ + path: "/clients.json", + ...opts, + }); + }, + async listCampaigns(clientId) { + const scheduledCampaigns = await this.listScheduledCampaigns({ + clientId, + }); + const { Results: sentCampaigns } = await this.listSentCampaigns({ + clientId, + params: { + pagesize: DEFAULT_PAGE_SIZE, + }, + }); + return [ + ...scheduledCampaigns, + ...sentCampaigns, + ]; + }, + listSentCampaigns({ + clientId, ...opts + }) { + return this._makeRequest({ + path: `/clients/${clientId}/campaigns.json`, + ...opts, + }); + }, + listScheduledCampaigns({ + clientId, ...opts + }) { + return this._makeRequest({ + path: `/clients/${clientId}/scheduled.json`, + ...opts, + }); + }, + listBounces({ + campaignId, ...opts + }) { + return this._makeRequest({ + path: `/campaigns/${campaignId}/bounces.json`, + ...opts, + }); + }, + listOpens({ + campaignId, ...opts + }) { + return this._makeRequest({ + path: `/campaigns/${campaignId}/opens.json`, + ...opts, + }); + }, + listSubscribers({ + listId, ...opts + }) { + return this._makeRequest({ + path: `/lists/${listId}/active.json`, + ...opts, + }); + }, + listLists({ + clientId, ...opts + }) { + return this._makeRequest({ + path: `/clients/${clientId}/lists.json`, + ...opts, + }); + }, + listSmartEmails(opts = {}) { + return this._makeRequest({ + path: "/transactional/smartEmail", + ...opts, + }); + }, + sendSmartEmail({ + smartEmailId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/transactional/smartEmail/${smartEmailId}/send`, + ...opts, + }); + }, + unsubscribeSubscriber({ + listId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/subscribers/${listId}/unsubscribe.json`, + ...opts, + }); + }, + createSubscriber({ + listId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/subscribers/${listId}.json`, + ...opts, + }); + }, + async *paginate({ + fn, + args, + max, + }) { + args = { + ...args, + params: { + ...args?.params, + pagesize: DEFAULT_PAGE_SIZE, + }, + }; + let hasMore, count = 0; + do { + const { + Results: results, NumberOfPages: numPages, + } = await fn(args); + for (const item of results) { + yield item; + if (max && ++count >= max) { + return; + } + } + hasMore = args.params.page < numPages; + args.params.page++; + } while (hasMore); + }, + }, +}; diff --git a/components/campaign_monitor/package.json b/components/campaign_monitor/package.json new file mode 100644 index 0000000000000..2a5c8328cfbcc --- /dev/null +++ b/components/campaign_monitor/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/campaign_monitor", + "version": "0.1.0", + "description": "Pipedream Campaign Monitor Components", + "main": "campaign_monitor.app.mjs", + "keywords": [ + "pipedream", + "campaign_monitor" + ], + "homepage": "https://pipedream.com/apps/campaign_monitor", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" + } +} diff --git a/components/campaign_monitor/sources/common/base.mjs b/components/campaign_monitor/sources/common/base.mjs new file mode 100644 index 0000000000000..df120d4c3fe96 --- /dev/null +++ b/components/campaign_monitor/sources/common/base.mjs @@ -0,0 +1,82 @@ +import campaignMonitor from "../../campaign_monitor.app.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + props: { + campaignMonitor, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + clientId: { + propDefinition: [ + campaignMonitor, + "clientId", + ], + }, + }, + hooks: { + async deploy() { + await this.processEvent(25); + }, + }, + methods: { + _getLastTs() { + return this.db.get("lastTs") || 0; + }, + _setLastTs(lastTs) { + this.db.set("lastTs", lastTs); + }, + getArgs() { + return {}; + }, + getTsField() { + return "Date"; + }, + getResourceFn() { + throw new Error("getResourceFn is not implemented"); + }, + generateMeta() { + throw new Error("generateMeta is not implemented"); + }, + async processEvent(max) { + const lastTs = this._getLastTs(); + const fn = this.getResourceFn(); + const args = this.getArgs(); + const tsField = this.getTsField(); + + const results = this.campaignMonitor.paginate({ + fn, + args, + max, + }); + + const items = []; + for await (const item of results) { + const ts = Date.parse(item[tsField]); + if (ts >= lastTs) { + items.push(item); + } else { + break; + } + } + + if (!items?.length) { + return; + } + + this._setLastTs(Date.parse(items[0][tsField])); + + items.forEach((item) => { + const meta = this.generateMeta(item); + this.$emit(item, meta); + }); + }, + }, + async run() { + await this.processEvent(); + }, +}; diff --git a/components/campaign_monitor/sources/new-bounce/new-bounce.mjs b/components/campaign_monitor/sources/new-bounce/new-bounce.mjs new file mode 100644 index 0000000000000..868af3f4d7f31 --- /dev/null +++ b/components/campaign_monitor/sources/new-bounce/new-bounce.mjs @@ -0,0 +1,46 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "campaign_monitor-new-bounce", + name: "New Bounce", + description: "Emit new event when a campaign email bounces", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + campaignId: { + propDefinition: [ + common.props.campaignMonitor, + "campaignId", + (c) => ({ + clientId: c.clientId, + }), + ], + }, + }, + methods: { + ...common.methods, + getResourceFn() { + return this.campaignMonitor.listBounces; + }, + getArgs() { + return { + campaignId: this.campaignId, + params: { + orderfield: "date", + orderdirection: "desc", + }, + }; + }, + generateMeta(bounce) { + const ts = Date.parse(bounce[this.getTsField()]); + return { + id: `${bounce.EmailAddress}-${ts}`, + summary: `New Bounce: ${bounce.EmailAddress}`, + ts, + }; + }, + }, +}; diff --git a/components/campaign_monitor/sources/new-email-open/new-email-open.mjs b/components/campaign_monitor/sources/new-email-open/new-email-open.mjs new file mode 100644 index 0000000000000..199bf6ed132ee --- /dev/null +++ b/components/campaign_monitor/sources/new-email-open/new-email-open.mjs @@ -0,0 +1,46 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "campaign_monitor-new-email-open", + name: "New Email Open", + description: "Emit new event when an email from a campaign is opened", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + campaignId: { + propDefinition: [ + common.props.campaignMonitor, + "campaignId", + (c) => ({ + clientId: c.clientId, + }), + ], + }, + }, + methods: { + ...common.methods, + getResourceFn() { + return this.campaignMonitor.listOpens; + }, + getArgs() { + return { + campaignId: this.campaignId, + params: { + orderfield: "date", + orderdirection: "desc", + }, + }; + }, + generateMeta(open) { + const ts = Date.parse(open[this.getTsField()]); + return { + id: `${open.EmailAddress}-${ts}`, + summary: `New Email Open: ${open.EmailAddress}`, + ts, + }; + }, + }, +}; diff --git a/components/campaign_monitor/sources/new-subscriber/new-subscriber.mjs b/components/campaign_monitor/sources/new-subscriber/new-subscriber.mjs new file mode 100644 index 0000000000000..5149782b83839 --- /dev/null +++ b/components/campaign_monitor/sources/new-subscriber/new-subscriber.mjs @@ -0,0 +1,45 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "campaign_monitor-new-subscriber", + name: "New Subscriber Added", + description: "Emit new event when a new subscriber is added to a specific list", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + listId: { + propDefinition: [ + common.props.campaignMonitor, + "listId", + (c) => ({ + clientId: c.clientId, + }), + ], + }, + }, + methods: { + ...common.methods, + getResourceFn() { + return this.campaignMonitor.listSubscribers; + }, + getArgs() { + return { + listId: this.listId, + params: { + orderfield: "date", + orderdirection: "desc", + }, + }; + }, + generateMeta(subscriber) { + return { + id: subscriber.EmailAddress, + summary: `New Subscriber: ${subscriber.EmailAddress}`, + ts: Date.parse(subscriber[this.getTsField()]), + }; + }, + }, +}; diff --git a/components/cats/cats.app.mjs b/components/cats/cats.app.mjs new file mode 100644 index 0000000000000..35105682c0dee --- /dev/null +++ b/components/cats/cats.app.mjs @@ -0,0 +1,11 @@ +export default { + type: "app", + app: "cats", + propDefinitions: {}, + methods: { + // this.$auth contains connected account data + authKeys() { + console.log(Object.keys(this.$auth)); + }, + }, +}; diff --git a/components/cats/package.json b/components/cats/package.json new file mode 100644 index 0000000000000..08d007c23d152 --- /dev/null +++ b/components/cats/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pipedream/cats", + "version": "0.0.1", + "description": "Pipedream CATS Components", + "main": "cats.app.mjs", + "keywords": [ + "pipedream", + "cats" + ], + "homepage": "https://pipedream.com/apps/cats", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/components/checkvist/actions/create-list-item/create-list-item.mjs b/components/checkvist/actions/create-list-item/create-list-item.mjs new file mode 100644 index 0000000000000..766898d83d170 --- /dev/null +++ b/components/checkvist/actions/create-list-item/create-list-item.mjs @@ -0,0 +1,79 @@ +import checkvist from "../../checkvist.app.mjs"; +import { STATUS_OPTIONS } from "../../common/constants.mjs"; +import { parseObject } from "../../common/utils.mjs"; + +export default { + key: "checkvist-create-list-item", + name: "Create List Item", + description: "Creates a new list item within a specified list. [See the documentation](https://checkvist.com/auth/api)", + version: "0.0.1", + type: "action", + props: { + checkvist, + listId: { + propDefinition: [ + checkvist, + "listId", + ], + }, + content: { + type: "string", + label: "Content", + description: "Block of text containing items to add. Indentations indicate nested list items.", + }, + parentId: { + propDefinition: [ + checkvist, + "parentId", + ({ listId }) => ({ + listId, + }), + ], + optional: true, + }, + tags: { + type: "string[]", + label: "Tags", + description: "An array of tags.", + optional: true, + }, + dueDate: { + type: "string", + label: "Due Date", + description: "Due for the task, in Checkvist's smart syntax format.", + optional: true, + }, + position: { + type: "integer", + label: "Position", + description: "1-based position of the task (omit to add to the end of the list).", + optional: true, + }, + status: { + type: "string", + label: "Status", + description: "Task status", + options: STATUS_OPTIONS, + optional: true, + }, + }, + async run({ $ }) { + const response = await this.checkvist.createListItem({ + $, + listId: this.listId, + data: { + task: { + content: this.content, + parent_id: this.parentId || 0, + tags: parseObject(this.tags)?.join(","), + due_date: this.dueDate, + position: this.position, + status: this.status, + }, + }, + }); + + $.export("$summary", `Successfully created a new list item in list with ID ${this.listId}`); + return response; + }, +}; diff --git a/components/checkvist/actions/create-multiple-list-items/create-multiple-list-items.mjs b/components/checkvist/actions/create-multiple-list-items/create-multiple-list-items.mjs new file mode 100644 index 0000000000000..68d94932e3a69 --- /dev/null +++ b/components/checkvist/actions/create-multiple-list-items/create-multiple-list-items.mjs @@ -0,0 +1,77 @@ +import checkvist from "../../checkvist.app.mjs"; +import { + SEPARATE_LINE_OPTIONS, STATUS_OPTIONS, +} from "../../common/constants.mjs"; + +export default { + key: "checkvist-create-multiple-list-items", + name: "Create Multiple List Items", + description: "Enables creation of several list items at once from a block of text. Indentations in the text indicate nested list items. [See the documentation](https://checkvist.com/auth/api)", + version: "0.0.1", + type: "action", + props: { + checkvist, + listId: { + propDefinition: [ + checkvist, + "listId", + ], + }, + itemsContent: { + type: "string", + label: "Content Items", + description: "list items in the same format, as supported by [Checkvist's import function](https://checkvist.com/help#import).", + }, + parentId: { + propDefinition: [ + checkvist, + "parentId", + ({ listId }) => ({ + listId, + }), + ], + optional: true, + }, + position: { + type: "integer", + label: "Position", + description: "1-based position of the task (omit to add to the end of the list).", + optional: true, + }, + parseTasks: { + type: "boolean", + label: "Parse Tasks", + description: "If true, recognize **^due** and **#tags** syntax in imported list items", + }, + separateWithEmptyLine: { + type: "string", + label: "Separate With Empty Line", + description: "Select value for List items separator.", + options: SEPARATE_LINE_OPTIONS, + }, + status: { + type: "string", + label: "Status", + description: "Task status", + options: STATUS_OPTIONS, + optional: true, + }, + }, + async run({ $ }) { + const response = await this.checkvist.createMultipleListItems({ + $, + listId: this.listId, + data: { + import_content: this.itemsContent, + parent_id: this.parentId, + position: this.position, + parse_tasks: this.parseTasks, + separate_with_empty_line: this.separateWithEmptyLine, + status: this.status, + }, + }); + + $.export("$summary", "Successfully created multiple list items"); + return response; + }, +}; diff --git a/components/checkvist/actions/create-new-list/create-new-list.mjs b/components/checkvist/actions/create-new-list/create-new-list.mjs new file mode 100644 index 0000000000000..bf2cb176a63d1 --- /dev/null +++ b/components/checkvist/actions/create-new-list/create-new-list.mjs @@ -0,0 +1,35 @@ +import checkvist from "../../checkvist.app.mjs"; + +export default { + key: "checkvist-create-new-list", + name: "Create New List", + description: "Creates a new list in Checkvist. [See the documentation](https://checkvist.com/auth/api)", + version: "0.0.1", + type: "action", + props: { + checkvist, + name: { + type: "string", + label: "List Name", + description: "Name of the new list to be created", + }, + public: { + type: "boolean", + label: "Public", + description: "true for checklist which can be accessed in read-only mode by anyone. Access to such checklists doesn't require authentication.", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.checkvist.createList({ + $, + data: { + name: this.name, + public: this.public, + }, + }); + + $.export("$summary", `Successfully created a new list: ${this.name}`); + return response; + }, +}; diff --git a/components/checkvist/checkvist.app.mjs b/components/checkvist/checkvist.app.mjs index 58dca2a8a09c7..ec6fd50f67e55 100644 --- a/components/checkvist/checkvist.app.mjs +++ b/components/checkvist/checkvist.app.mjs @@ -1,11 +1,101 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "checkvist", - propDefinitions: {}, + propDefinitions: { + listId: { + type: "string", + label: "List ID", + description: "Select a list to monitor for new items", + async options() { + const lists = await this.getLists({ + params: { + skip_stats: true, + }, + }); + return lists.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + parentId: { + type: "string", + label: "Parent task ID", + description: "Empty for root-level tasks", + async options({ listId }) { + const items = await this.getListItems({ + listId, + }); + return items.map(({ + id: value, content: label, + }) => ({ + label, + value, + })); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://checkvist.com"; + }, + _auth() { + return { + username: `${this.$auth.username}`, + password: `${this.$auth.api_key}`, + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + auth: this._auth(), + ...opts, + }); + }, + getLists(opts = {}) { + return this._makeRequest({ + path: "/checklists.json", + ...opts, + }); + }, + getListItems({ + listId, ...opts + }) { + return this._makeRequest({ + path: `/checklists/${listId}/tasks.json`, + ...opts, + }); + }, + createList(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/checklists.json", + ...opts, + }); + }, + createListItem({ + listId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/checklists/${listId}/tasks.json`, + ...opts, + }); + }, + createMultipleListItems({ + listId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/checklists/${listId}/import.json`, + ...opts, + }); }, }, }; diff --git a/components/checkvist/common/constants.mjs b/components/checkvist/common/constants.mjs new file mode 100644 index 0000000000000..406c51135bcf9 --- /dev/null +++ b/components/checkvist/common/constants.mjs @@ -0,0 +1,25 @@ +export const STATUS_OPTIONS = [ + { + label: "Open", + value: "0", + }, + { + label: "Closed", + value: "1", + }, + { + label: "Invalidated", + value: "2", + }, +]; + +export const SEPARATE_LINE_OPTIONS = [ + { + label: "Separate with empty line", + value: "singleItem", + }, + { + label: "One item per line", + value: "multipleItems", + }, +]; diff --git a/components/checkvist/common/utils.mjs b/components/checkvist/common/utils.mjs new file mode 100644 index 0000000000000..dcc9cc61f6f41 --- /dev/null +++ b/components/checkvist/common/utils.mjs @@ -0,0 +1,24 @@ +export const parseObject = (obj) => { + if (!obj) return undefined; + + if (Array.isArray(obj)) { + return obj.map((item) => { + if (typeof item === "string") { + try { + return JSON.parse(item); + } catch (e) { + return item; + } + } + return item; + }); + } + if (typeof obj === "string") { + try { + return JSON.parse(obj); + } catch (e) { + return obj; + } + } + return obj; +}; diff --git a/components/checkvist/package.json b/components/checkvist/package.json new file mode 100644 index 0000000000000..11c99b63033b3 --- /dev/null +++ b/components/checkvist/package.json @@ -0,0 +1,19 @@ +{ + "name": "@pipedream/checkvist", + "version": "0.1.0", + "description": "Pipedream Checkvist Components", + "main": "checkvist.app.mjs", + "keywords": [ + "pipedream", + "checkvist" + ], + "homepage": "https://pipedream.com/apps/checkvist", + "author": "Pipedream (https://pipedream.com/)", + "gitHead": "e12480b94cc03bed4808ebc6b13e7fdb3a1ba535", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" + } +} diff --git a/components/checkvist/sources/common/base.mjs b/components/checkvist/sources/common/base.mjs new file mode 100644 index 0000000000000..70fe935687f2a --- /dev/null +++ b/components/checkvist/sources/common/base.mjs @@ -0,0 +1,69 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import checkvist from "../../checkvist.app.mjs"; + +export default { + props: { + checkvist, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + _getLastId() { + return this.db.get("lastId") || 0; + }, + _setLastId(lastId) { + this.db.set("lastId", lastId); + }, + getArgs() { + return {}; + }, + async emitEvent(maxResults = false) { + const lastId = this._getLastId(); + const fn = this.getFunction(); + + const response = await fn({ + ...this.getArgs(), + params: { + order: "id:desc", + }, + }); + + let responseArray = []; + for (const item of response) { + if (item.id <= lastId) break; + responseArray.push(item); + } + + if (responseArray.length) { + if (maxResults && (responseArray.length > maxResults)) { + responseArray.length = maxResults; + } + this._setLastId(responseArray[0].id); + } + + for (const item of responseArray.reverse()) { + const summary = this.getSummary(item); + this.$emit(item, { + id: item.id, + summary: summary.length > 40 + ? `${summary.slice(0, 39)}...` + : summary, + ts: Date.parse(item.created_at), + }); + } + }, + }, + hooks: { + async deploy() { + await this.emitEvent(25); + }, + }, + async run() { + await this.emitEvent(); + }, +}; diff --git a/components/checkvist/sources/new-list-item/new-list-item.mjs b/components/checkvist/sources/new-list-item/new-list-item.mjs new file mode 100644 index 0000000000000..479fd1087ed01 --- /dev/null +++ b/components/checkvist/sources/new-list-item/new-list-item.mjs @@ -0,0 +1,36 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "checkvist-new-list-item", + name: "New List Item Added", + description: "Emit new event when a new list item is added in a selected list.", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + listId: { + propDefinition: [ + common.props.checkvist, + "listId", + ], + }, + }, + methods: { + ...common.methods, + getFunction() { + return this.checkvist.getListItems; + }, + getArgs() { + return { + listId: this.listId, + }; + }, + getSummary(item) { + return `New Item: ${item.content}`; + }, + }, + sampleEmit, +}; diff --git a/components/checkvist/sources/new-list-item/test-event.mjs b/components/checkvist/sources/new-list-item/test-event.mjs new file mode 100644 index 0000000000000..f9704b6259bcd --- /dev/null +++ b/components/checkvist/sources/new-list-item/test-event.mjs @@ -0,0 +1,21 @@ +export default { + "id": 66743402, + "parent_id": 0, + "checklist_id": 910790, + "status": 0, + "position": 9, + "tasks": [], + "update_line": "created by Username", + "updated_at": "2024/11/01 15:37:54 +0000", + "created_at": "2024/11/01 15:37:54 +0000", + "due": null, + "content": "Root task", + "collapsed": false, + "comments_count": 0, + "assignee_ids": [], + "details": {}, + "link_ids": [], + "backlink_ids": [], + "tags": {}, + "tags_as_text": "" +} \ No newline at end of file diff --git a/components/checkvist/sources/new-list/new-list.mjs b/components/checkvist/sources/new-list/new-list.mjs new file mode 100644 index 0000000000000..04141a62cdc3a --- /dev/null +++ b/components/checkvist/sources/new-list/new-list.mjs @@ -0,0 +1,22 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "checkvist-new-list", + name: "New List Created", + description: "Emit new event when a new list is created in your Checkvist account.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getFunction() { + return this.checkvist.getLists; + }, + getSummary(list) { + return `New list: ${list.name}`; + }, + }, + sampleEmit, +}; diff --git a/components/checkvist/sources/new-list/test-event.mjs b/components/checkvist/sources/new-list/test-event.mjs new file mode 100644 index 0000000000000..53ab1208d8ab0 --- /dev/null +++ b/components/checkvist/sources/new-list/test-event.mjs @@ -0,0 +1,20 @@ +export default { + "id": 910780, + "name": "Introduction to Checkvist", + "updated_at": "2024/10/31 18:49:45 +0000", + "public": false, + "options": 2, + "created_at": "2024/10/31 18:49:45 +0000", + "markdown?": true, + "archived": false, + "read_only": false, + "user_count": 1, + "user_updated_at": "2024/10/31 18:49:45 +0000", + "related_task_ids": null, + "percent_completed": 0, + "task_count": 44, + "task_completed": 0, + "item_count": 68, + "tags": {}, + "tags_as_text": "" +} \ No newline at end of file diff --git a/components/cloze/.gitignore b/components/cloze/.gitignore deleted file mode 100644 index ec761ccab7595..0000000000000 --- a/components/cloze/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.js -*.mjs -dist \ No newline at end of file diff --git a/components/cloze/actions/create-note/create-note.mjs b/components/cloze/actions/create-note/create-note.mjs new file mode 100644 index 0000000000000..71e6b7059366e --- /dev/null +++ b/components/cloze/actions/create-note/create-note.mjs @@ -0,0 +1,81 @@ +import app from "../../cloze.app.mjs"; + +export default { + key: "cloze-create-note", + name: "Create Note", + description: "Creates a note in Cloze. [See the documentation](https://api.cloze.com/api-docs/#!/Content/post_v1_createcontent).", + version: "0.0.1", + type: "action", + props: { + app, + uniqueId: { + type: "string", + label: "Unique ID", + description: "A unique identifier for this content record. This will often be the unique Id in an external system so that updates can be matched up with the record in Cloze.", + }, + source: { + type: "string", + label: "Source", + description: "The source that this content record originally came from (Eg. `todoist.com`). Must be a valid domain.", + }, + date: { + type: "string", + label: "Date", + description: "When the content should show up in the timeline. Can be a string or a UTC timestamp in ms since the epoch. Eg. `2021-01-01` or `1609459200000`.", + optional: true, + }, + from: { + type: "string", + label: "From", + description: "From address for this content record (the address of the person created the record). This can be an email address, phone number, social handle or app link (Eg. `na16.salesforce.com:006j000000Pkp1d`)", + optional: true, + }, + subject: { + type: "string", + label: "Subject", + description: "Subject of the communication record.", + optional: true, + }, + body: { + type: "string", + label: "Body", + description: "Body text of the communication record.", + }, + additionalData: { + type: "object", + label: "Additional Data", + description: "Additional details for the note in JSON format. [See the documentation](https://api.cloze.com/api-docs/#!/Content/post_v1_createcontent).", + optional: true, + }, + }, + async run({ $ }) { + const { + app, + uniqueId, + date, + from, + source, + subject, + body, + additionalData, + } = this; + + const response = await app.addContentRecord({ + $, + data: { + uniqueid: uniqueId, + date, + style: "note", + from, + source, + subject, + body, + ...additionalData, + }, + }); + + $.export("$summary", "Successfully created note."); + + return response; + }, +}; diff --git a/components/cloze/actions/create-update-company/create-update-company.mjs b/components/cloze/actions/create-update-company/create-update-company.mjs new file mode 100644 index 0000000000000..06ed206a4bdfd --- /dev/null +++ b/components/cloze/actions/create-update-company/create-update-company.mjs @@ -0,0 +1,148 @@ +import app from "../../cloze.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "cloze-create-update-company", + name: "Create Or Update Company", + description: "Create a new company or enhance an existing company within Cloze. Companies can be created with just a domain name or both a name and another unique identifier such as a phone number and email address. [See the documentation](https://api.cloze.com/api-docs/#!/Relations_-_Companies/post_v1_companies_create).", + version: "0.0.1", + type: "action", + props: { + app, + name: { + type: "string", + label: "Company Name", + description: "The name of the company.", + optional: true, + }, + emails: { + type: "string[]", + label: "Emails", + description: "The emails of the company. Each email should be a JSON object with `value` key. [See the documentation](https://api.cloze.com/api-docs/#!/Relations_-_Companies/post_v1_companies_create).", + optional: true, + }, + phones: { + type: "string[]", + label: "Phones", + description: "The phones of the company. Each phone should be a JSON object with `value` key. [See the documentation](https://api.cloze.com/api-docs/#!/Relations_-_Companies/post_v1_companies_create).", + optional: true, + }, + domains: { + type: "string[]", + label: "Domains", + description: "The domains of the company.", + optional: true, + }, + segment: { + type: "string", + label: "Segment", + description: "The segment of the company.", + optional: true, + options: [ + "customer", + "partner", + "supplier", + "investor", + "advisor", + "competitor", + "custom1", + "custom2", + "custom3", + "custom4", + "custom5", + "coworker", + "family", + "friend", + "network", + "personal1", + "personal2", + ], + }, + step: { + type: "string", + label: "Step", + description: "Unique Id of Next Step", + optional: true, + }, + stage: { + type: "string", + label: "Stage", + description: "The stage of the company.", + optional: true, + options: [ + { + label: "Lead Stage", + value: "lead", + }, + { + label: "Potential Stage", + value: "future", + }, + { + label: "Active Stage", + value: "current", + }, + { + label: "Inactive Stage", + value: "past", + }, + { + label: "Lost Stage", + value: "out", + }, + ], + }, + assignTo: { + type: "string", + label: "Assign To", + description: "Assign this company to this team member.", + optional: true, + }, + additionalData: { + type: "object", + label: "Additional Data", + description: "Additional details for the company in JSON format. [See the documentation](https://api.cloze.com/api-docs/#!/Relations_-_Companies/post_v1_companies_create).", + optional: true, + }, + }, + methods: { + createCompany(args = {}) { + return this.app.post({ + path: "/companies/create", + ...args, + }); + }, + }, + async run({ $ }) { + const { + createCompany, + name, + emails, + phones, + domains, + segment, + step, + stage, + assignTo, + additionalData, + } = this; + + const response = await createCompany({ + $, + data: { + name, + emails: utils.parseArray(emails), + phones: utils.parseArray(phones), + domains, + segment, + step, + stage, + assignTo, + ...additionalData, + }, + }); + + $.export("$summary", "Successfully created/updated company."); + return response; + }, +}; diff --git a/components/cloze/actions/create-update-project/create-update-project.mjs b/components/cloze/actions/create-update-project/create-update-project.mjs new file mode 100644 index 0000000000000..ab25a8ea2cc7c --- /dev/null +++ b/components/cloze/actions/create-update-project/create-update-project.mjs @@ -0,0 +1,115 @@ +import app from "../../cloze.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "cloze-create-update-project", + name: "Create Or Update Project", + description: "Create a new project or merge updates into an existing one. [See the documentation](https://api.cloze.com/api-docs/#!/Relations_-_Projects/post_v1_projects_create).", + version: "0.0.1", + type: "action", + props: { + app, + name: { + type: "string", + label: "Project Name", + description: "The name of the project.", + }, + appLinks: { + type: "string[]", + label: "App Links", + description: "The app links of the project. Each app link should be a JSON object with at least `source` and `uniqueid` keys. [See the documentation](https://api.cloze.com/api-docs/#!/Relations_-_Projects/post_v1_projects_create).", + optional: true, + default: [ + JSON.stringify({ + source: "na16.salesforce.com", + uniqueid: "sdf234v", + }), + ], + }, + summary: { + type: "string", + label: "Project Summary", + description: "The summary of the project.", + optional: true, + }, + stage: { + type: "string", + label: "Stage", + description: "The stage of the project.", + optional: true, + options: [ + { + label: "Potential Stage", + value: "future", + }, + { + label: "Active Stage", + value: "current", + }, + { + label: "Won or Done stage", + value: "won", + }, + { + label: "Lost Stage", + value: "lost", + }, + ], + }, + segment: { + type: "string", + label: "Segment", + description: "The segment of the project.", + optional: true, + options: [ + "project", + "project1", + "project2", + "project3", + "project4", + "project5", + ], + }, + additionalData: { + type: "object", + label: "Additional Data", + description: "Additional details for the project in JSON format. [See the documentation](https://api.cloze.com/api-docs/#!/Relations_-_Projects/post_v1_projects_create).", + optional: true, + }, + }, + methods: { + createProject(args = {}) { + return this.app.post({ + path: "/projects/create", + ...args, + }); + }, + }, + async run({ $ }) { + const { + createProject, + name, + appLinks, + summary, + stage, + segment, + additionalData, + } = this; + + const response = await createProject({ + $, + data: { + name, + appLinks: utils.parseArray(appLinks), + summary, + stage, + segment, + ...additionalData, + }, + }); + + $.export("$summary", "Successfully created/updated project."); + + return response; + }, +}; diff --git a/components/cloze/app/cloze.app.ts b/components/cloze/app/cloze.app.ts deleted file mode 100644 index d510120638d18..0000000000000 --- a/components/cloze/app/cloze.app.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineApp } from "@pipedream/types"; - -export default defineApp({ - type: "app", - app: "cloze", - propDefinitions: {}, - methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); - }, - }, -}); \ No newline at end of file diff --git a/components/cloze/cloze.app.mjs b/components/cloze/cloze.app.mjs new file mode 100644 index 0000000000000..cb6a97dfde6d8 --- /dev/null +++ b/components/cloze/cloze.app.mjs @@ -0,0 +1,45 @@ +import { axios } from "@pipedream/platform"; +import constants from "./common/constants.mjs"; + +export default { + type: "app", + app: "cloze", + methods: { + getUrl(path) { + return `${constants.BASE_URL}${constants.VERSION_PATH}${path}`; + }, + getHeaders(headers) { + return { + Authorization: `Bearer ${this.$auth.oauth_access_token}`, + ...headers, + }; + }, + async _makeRequest({ + $ = this, path, headers, ...args + } = {}) { + const response = await axios($, { + ...args, + url: this.getUrl(path), + headers: this.getHeaders(headers), + }); + + if (response.errorcode) { + throw new Error(JSON.stringify(response, null, 2)); + } + + return response; + }, + post(args = {}) { + return this._makeRequest({ + method: "POST", + ...args, + }); + }, + addContentRecord(args = {}) { + return this.post({ + path: "/createcontent", + ...args, + }); + }, + }, +}; diff --git a/components/cloze/common/constants.mjs b/components/cloze/common/constants.mjs new file mode 100644 index 0000000000000..24ae1690ba99f --- /dev/null +++ b/components/cloze/common/constants.mjs @@ -0,0 +1,9 @@ +const BASE_URL = "https://api.cloze.com"; +const VERSION_PATH = "/v1"; +const WEBHOOK_ID = "webhookId"; + +export default { + BASE_URL, + VERSION_PATH, + WEBHOOK_ID, +}; diff --git a/components/cloze/common/utils.mjs b/components/cloze/common/utils.mjs new file mode 100644 index 0000000000000..650af25ed3a8e --- /dev/null +++ b/components/cloze/common/utils.mjs @@ -0,0 +1,51 @@ +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"); + } +} + +export default { + parseArray: (value) => parseArray(value)?.map(parseJson), +}; diff --git a/components/cloze/package.json b/components/cloze/package.json index 82ec92999670b..6c94bb0443f38 100644 --- a/components/cloze/package.json +++ b/components/cloze/package.json @@ -1,16 +1,18 @@ { "name": "@pipedream/cloze", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Cloze Components", - "main": "dist/app/cloze.app.mjs", + "main": "cloze.app.mjs", "keywords": [ "pipedream", "cloze" ], - "files": ["dist"], "homepage": "https://pipedream.com/apps/cloze", "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "3.0.3" } -} \ No newline at end of file +} diff --git a/components/cloze/sources/common/events.mjs b/components/cloze/sources/common/events.mjs new file mode 100644 index 0000000000000..4b7a4d3f4a0c8 --- /dev/null +++ b/components/cloze/sources/common/events.mjs @@ -0,0 +1,8 @@ +export default { + PERSON_CHANGE: "person.change", + PROJECT_CHANGE: "project.change", + COMPANY_CHANGE: "company.change", + PERSON_AUDIT_CHANGE: "person.audit.change", + PROJECT_AUDIT_CHANGE: "project.audit.change", + COMPANY_AUDIT_CHANGE: "company.audit.change", +}; diff --git a/components/cloze/sources/common/webhook.mjs b/components/cloze/sources/common/webhook.mjs new file mode 100644 index 0000000000000..5ff0146bcff6f --- /dev/null +++ b/components/cloze/sources/common/webhook.mjs @@ -0,0 +1,118 @@ +import { ConfigurationError } from "@pipedream/platform"; +import app from "../../cloze.app.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + props: { + app, + db: "$.service.db", + http: { + type: "$.interface.http", + customResponse: true, + }, + scope: { + type: "string", + label: "Scope", + description: "Scope of subscription, changes to the user's local person, project, and company may be monitored, or team relations may be monitored, or team hierarchies can be monitored. Can be `local`, `team`, `hierarchy:/X/Y/Z` or `hierarchy:/X/Y/Z/*`", + options: [ + "local", + "team", + ], + default: "local", + }, + }, + hooks: { + async activate() { + const { + createWebhook, + setWebhookId, + http: { endpoint: targetUrl }, + getEventName, + scope, + } = this; + + const response = + await createWebhook({ + data: { + event: getEventName(), + target_url: targetUrl, + scope, + }, + }); + + setWebhookId(response.uniqueid); + }, + async deactivate() { + const { + getWebhookId, + deleteWebhook, + getEventName, + } = this; + + const webhookId = getWebhookId(); + if (webhookId) { + await deleteWebhook({ + data: { + uniqueid: webhookId, + event: getEventName(), + }, + }); + } + }, + }, + methods: { + setWebhookId(value) { + this.db.set(constants.WEBHOOK_ID, value); + }, + getWebhookId() { + return this.db.get(constants.WEBHOOK_ID); + }, + generateMeta() { + throw new ConfigurationError("generateMeta is not implemented"); + }, + getEventName() { + throw new ConfigurationError("getEventName is not implemented"); + }, + processResource(events) { + events.forEach((event) => { + this.$emit(event, this.generateMeta(event)); + }); + }, + createWebhook(args = {}) { + return this.app.post({ + debug: true, + path: "/subscribe", + ...args, + }); + }, + deleteWebhook(args = {}) { + return this.app.post({ + debug: true, + path: "/unsubscribe", + ...args, + }); + }, + }, + async run({ + body, headers, + }) { + const { + getWebhookId, + http, + } = this; + + if (headers["x-cloze-subscription-id"] !== getWebhookId()) { + return console.log("Webhook ID does not match with Cloze subscription ID"); + } + + http.respond({ + status: 200, + body: "OK", + headers: { + "content-type": "text/plain", + }, + }); + + this.processResource(body); + }, +}; diff --git a/components/cloze/sources/company-change-instant/company-change-instant.mjs b/components/cloze/sources/company-change-instant/company-change-instant.mjs new file mode 100644 index 0000000000000..416e3fa5c8f4b --- /dev/null +++ b/components/cloze/sources/company-change-instant/company-change-instant.mjs @@ -0,0 +1,27 @@ +import common from "../common/webhook.mjs"; +import events from "../common/events.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "cloze-company-change-instant", + name: "Company Change (Instant)", + description: "Emit new event when significant changes regarding a company are detected. [See the documentation](https://api.cloze.com/api-docs/#!/Webhooks/post_v1_subscribe).", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEventName() { + return events.COMPANY_CHANGE; + }, + generateMeta(event) { + return { + id: event?.company.syncKey, + summary: "New Company Change", + ts: event?.company.lastChanged, + }; + }, + }, + sampleEmit, +}; diff --git a/components/cloze/sources/company-change-instant/test-event.mjs b/components/cloze/sources/company-change-instant/test-event.mjs new file mode 100644 index 0000000000000..1342a328c51bd --- /dev/null +++ b/components/cloze/sources/company-change-instant/test-event.mjs @@ -0,0 +1,22 @@ +export default { + "company": { + "syncKey": "nL8WlFFbFuVvlZfLWFEH3tSsTYZqUQ50qNyfMUi8zFA", + "name": "test 1 upd 3", + "visibility": "visible", + "views": [ + "my", + "defined" + ], + "firstSeen": 1731443475576, + "lastChanged": 1731453402556, + "domains": [ + "test.com" + ], + "segment": "none", + "stage": "none", + "step": "none", + "assignee": "test@test.com" + }, + "changes": {}, + "event": "company.change" +}; diff --git a/components/cloze/sources/person-change-instant/person-change-instant.mjs b/components/cloze/sources/person-change-instant/person-change-instant.mjs new file mode 100644 index 0000000000000..7f7d88d0f0f05 --- /dev/null +++ b/components/cloze/sources/person-change-instant/person-change-instant.mjs @@ -0,0 +1,27 @@ +import common from "../common/webhook.mjs"; +import events from "../common/events.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "cloze-person-change-instant", + name: "Person Change (Instant)", + description: "Emit new event when significant changes happen to a person. [See the documentation](https://api.cloze.com/api-docs/#!/Webhooks/post_v1_subscribe).", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEventName() { + return events.PERSON_CHANGE; + }, + generateMeta(event) { + return { + id: event?.person.syncKey, + summary: "New Person Change", + ts: event?.person.lastChanged, + }; + }, + }, + sampleEmit, +}; diff --git a/components/cloze/sources/person-change-instant/test-event.mjs b/components/cloze/sources/person-change-instant/test-event.mjs new file mode 100644 index 0000000000000..97b22de9bee08 --- /dev/null +++ b/components/cloze/sources/person-change-instant/test-event.mjs @@ -0,0 +1,22 @@ +export default { + "person": { + "syncKey": "nL8WlasdFbFuVvlZfLWFEH3tSsTYZqUQ50qNyfMUi8zFA", + "name": "test 1 upd 3", + "visibility": "visible", + "views": [ + "my", + "defined" + ], + "firstSeen": 1731443475576, + "lastChanged": 1731453402556, + "domains": [ + "test.com" + ], + "segment": "none", + "stage": "none", + "step": "none", + "assignee": "test@test.com", + }, + "changes": {}, + "event": "person.change" +}; diff --git a/components/cloze/sources/project-change-instant/project-change-instant.mjs b/components/cloze/sources/project-change-instant/project-change-instant.mjs new file mode 100644 index 0000000000000..d787091b2e2b9 --- /dev/null +++ b/components/cloze/sources/project-change-instant/project-change-instant.mjs @@ -0,0 +1,27 @@ +import common from "../common/webhook.mjs"; +import events from "../common/events.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "cloze-project-change-instant", + name: "Project Change (Instant)", + description: "Emit new event when a significant change occurs in a project. [See the documentation](https://api.cloze.com/api-docs/#!/Webhooks/post_v1_subscribe).", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEventName() { + return events.PROJECT_CHANGE; + }, + generateMeta(event) { + return { + id: event?.project.syncKey, + summary: "New Project Change", + ts: event?.project.lastChanged, + }; + }, + }, + sampleEmit, +}; diff --git a/components/cloze/sources/project-change-instant/test-event.mjs b/components/cloze/sources/project-change-instant/test-event.mjs new file mode 100644 index 0000000000000..579c6f86eedde --- /dev/null +++ b/components/cloze/sources/project-change-instant/test-event.mjs @@ -0,0 +1,22 @@ +export default { + "project": { + "syncKey": "nL8WlFFbFuVvlZfLWFEH3tSsTYZqUQ50qNyfMUi8zFA", + "name": "test 1 upd 3", + "visibility": "visible", + "views": [ + "my", + "defined" + ], + "firstSeen": 1731443475576, + "lastChanged": 1731453402556, + "domains": [ + "test.com" + ], + "segment": "none", + "stage": "none", + "step": "none", + "assignee": "test@test.com", + }, + "changes": {}, + "event": "project.change" +}; diff --git a/components/columns_ai/actions/build-graph-from-scratch/build-graph-from-scratch.mjs b/components/columns_ai/actions/build-graph-from-scratch/build-graph-from-scratch.mjs new file mode 100644 index 0000000000000..cc9adec34625f --- /dev/null +++ b/components/columns_ai/actions/build-graph-from-scratch/build-graph-from-scratch.mjs @@ -0,0 +1,152 @@ +import { ChartType } from "columns-graph-model"; +import app from "../../columns_ai.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "columns_ai-build-graph-from-scratch", + name: "Build Graph From Scratch", + description: "Builds a graph object from scratch and publishes it. [See the documentation](https://github.com/varchar-io/vaas?tab=readme-ov-file#basic-usage)", + version: "0.0.2", + type: "action", + props: { + app, + name: { + propDefinition: [ + app, + "name", + ], + }, + chartType: { + type: "string", + label: "Chart Type", + description: "The type of chart to construct", + async options() { + return [ + { + label: "Bar", + value: ChartType.BAR, + }, + { + label: "Pie", + value: ChartType.PIE, + }, + { + label: "Doughnut", + value: ChartType.DOUGHNUT, + }, + { + label: "Line", + value: ChartType.LINE, + }, + { + label: "Area", + value: ChartType.AREA, + }, + { + label: "Scatter", + value: ChartType.SCATTER, + }, + { + label: "Bar Race", + value: ChartType.BAR_RACE, + }, + { + label: "Boxplot", + value: ChartType.BOXPLOT, + }, + { + label: "Column", + value: ChartType.COLUMN, + }, + { + label: "Dot", + value: ChartType.DOT, + }, + { + label: "Gauge", + value: ChartType.GAUGE, + }, + { + label: "Map", + value: ChartType.MAP, + }, + { + label: "Radar", + value: ChartType.RADAR, + }, + { + label: "Summary", + value: ChartType.SUMMARY, + }, + { + label: "Table", + value: ChartType.TABLE, + }, + { + label: "Timeline", + value: ChartType.TIMELINE, + }, + { + label: "Timeline Area", + value: ChartType.TIMELINE_AREA, + }, + { + label: "Timeline Bar", + value: ChartType.TIMELINE_BAR, + }, + { + label: "Tree", + value: ChartType.TREE, + }, + { + label: "Wordcloud", + value: ChartType.WORDCLOUD, + }, + ]; + }, + }, + keys: { + propDefinition: [ + app, + "keys", + ], + }, + metrics: { + propDefinition: [ + app, + "metrics", + ], + }, + rows: { + propDefinition: [ + app, + "rows", + ], + }, + }, + async run({ $ }) { + const { + app, + name, + chartType, + keys, + metrics, + rows, + } = this; + + const graph = await app.createGraphFromScratch({ + chartType, + keys, + metrics, + rows: utils.parseArray(rows), + }); + + const response = await app.publishGraph({ + name, + graph, + }); + + $.export("$summary", "Successfully built and published the graph from scratch."); + return response; + }, +}; diff --git a/components/columns_ai/actions/build-graph-from-template/build-graph-from-template.mjs b/components/columns_ai/actions/build-graph-from-template/build-graph-from-template.mjs new file mode 100644 index 0000000000000..62fa048820218 --- /dev/null +++ b/components/columns_ai/actions/build-graph-from-template/build-graph-from-template.mjs @@ -0,0 +1,67 @@ +import app from "../../columns_ai.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "columns_ai-build-graph-from-template", + name: "Build Graph From Template", + description: "Builds a graph object from a template and publishes it. [See the documentation](https://github.com/varchar-io/vaas?tab=readme-ov-file#basic-usage).", + version: "0.0.2", + type: "action", + props: { + app, + name: { + propDefinition: [ + app, + "name", + ], + }, + visualId: { + type: "string", + label: "Visual ID", + description: "The ID of an existing graph template on Columns. Eg. `U6tALuJ3cTdPFw` wher it can be taken from the URL `https://columns.ai/visual/view/U6tALuJ3cTdPFw`.", + }, + keys: { + propDefinition: [ + app, + "keys", + ], + }, + metrics: { + propDefinition: [ + app, + "metrics", + ], + }, + rows: { + propDefinition: [ + app, + "rows", + ], + }, + }, + async run({ $ }) { + const { + app, + name, + visualId, + keys, + metrics, + rows, + } = this; + + const graph = await app.createGraphFromTemplate({ + visualId, + keys, + metrics, + rows: utils.parseArray(rows), + }); + + const response = await app.publishGraph({ + name, + graph, + }); + + $.export("$summary", "Successfully built and published the graph from template."); + return response; + }, +}; diff --git a/components/columns_ai/columns_ai.app.mjs b/components/columns_ai/columns_ai.app.mjs index 0111513c3f0a3..b656937fdf7cb 100644 --- a/components/columns_ai/columns_ai.app.mjs +++ b/components/columns_ai/columns_ai.app.mjs @@ -1,11 +1,65 @@ +import { ChartType } from "columns-graph-model"; +import { Columns } from "columns-sdk"; + export default { type: "app", app: "columns_ai", - propDefinitions: {}, + propDefinitions: { + name: { + type: "string", + label: "Graph Name", + description: "The name of the graph", + }, + keys: { + type: "string[]", + label: "Keys", + description: "An array of keys for the data rows.", + }, + metrics: { + type: "string[]", + label: "Metrics", + description: "An array of metrics for the data rows.", + }, + rows: { + type: "string[]", + label: "Rows", + description: "An array of data objects, where each object should be a JSON string. Eg. `{ \"metric\": 4000, \"key\": \"US\", \"parent\": null }`.", + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + getColumns() { + return new Columns(this.$auth.api_key); + }, + async createGraphFromScratch({ + keys = [], metrics = [], rows = [], chartType = ChartType.COLUMN, + } = {}) { + const columns = this.getColumns(); + const data = columns.data(keys, metrics, rows); + const graph = columns.graph(data); + + graph.type = chartType; + + return graph; + }, + async createGraphFromTemplate({ + visualId, keys = [], metrics = [], rows = [], + } = {}) { + const columns = this.getColumns(); + const graph = await columns.template(visualId); + + if (!graph) { + throw new Error(`Failed to load template from Columns: ${visualId}`); + } + + graph.data = columns.data(keys, metrics, rows); + + return graph; + }, + publishGraph({ + name, graph, + } = {}) { + const columns = this.getColumns(); + return columns.publish(name, graph); }, }, }; diff --git a/components/columns_ai/common/utils.mjs b/components/columns_ai/common/utils.mjs new file mode 100644 index 0000000000000..f48102438db77 --- /dev/null +++ b/components/columns_ai/common/utils.mjs @@ -0,0 +1,51 @@ +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"); + } +} + +export default { + parseArray: (value) => parseArray(value).map(parseJson), +}; diff --git a/components/columns_ai/package.json b/components/columns_ai/package.json index bd40d3dc3b5d5..a23a70d680d7b 100644 --- a/components/columns_ai/package.json +++ b/components/columns_ai/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/columns_ai", - "version": "0.0.1", + "version": "0.1.2", "description": "Pipedream Columns Ai Components", "main": "columns_ai.app.mjs", "keywords": [ @@ -11,5 +11,9 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "columns-graph-model": "^1.1.4", + "columns-sdk": "^0.0.6" } -} \ No newline at end of file +} diff --git a/components/crowdin/.gitignore b/components/crowdin/.gitignore deleted file mode 100644 index ec761ccab7595..0000000000000 --- a/components/crowdin/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.js -*.mjs -dist \ No newline at end of file diff --git a/components/crowdin/actions/add-file/add-file.mjs b/components/crowdin/actions/add-file/add-file.mjs new file mode 100644 index 0000000000000..b5e7703dc6cd0 --- /dev/null +++ b/components/crowdin/actions/add-file/add-file.mjs @@ -0,0 +1,120 @@ +import fs from "fs"; +import { TYPE_OPTIONS } from "../../common/constants.mjs"; +import { + checkTmp, + parseObject, +} from "../../common/utils.mjs"; +import crowdin from "../../crowdin.app.mjs"; + +export default { + key: "crowdin-add-file", + name: "Add File to Project", + description: "Adds a file into the created project. [See the documentation](https://developer.crowdin.com/api/v2/#tag/source-files/operation/api.projects.files.post)", + version: "0.0.1", + type: "action", + props: { + crowdin, + projectId: { + propDefinition: [ + crowdin, + "projectId", + ], + }, + file: { + type: "string", + label: "File", + description: "The path to the file saved to the `/tmp` directory (e.g. `/tmp/example.jpg`) to process. [See the documentation](https://pipedream.com/docs/workflows/steps/code/nodejs/working-with-files/#the-tmp-directory).", + }, + name: { + type: "string", + label: "Name", + description: "The name of the file in Crowdin. **Note:** Can't contain `\\ / : * ? \" < > |` symbols. `ZIP` files are not allowed.", + }, + branchId: { + propDefinition: [ + crowdin, + "branchId", + (c) => ({ + projectId: c.projectId, + }), + ], + }, + directoryId: { + propDefinition: [ + crowdin, + "directoryId", + (c) => ({ + projectId: c.projectId, + }), + ], + }, + title: { + type: "string", + label: "Title", + description: "Use to provide more details for translators. Title is available in UI only", + optional: true, + }, + context: { + type: "string", + label: "Context", + description: "Use to provide context about whole file", + optional: true, + }, + type: { + type: "string", + label: "File Type", + description: "The type of the file. **Note:** Use `docx` type to import each cell as a separate source string for XLSX file. Default is `auto`", + options: TYPE_OPTIONS, + optional: true, + }, + parserVersion: { + type: "integer", + label: "Parser Version", + description: "Using latest parser version by default. **Note:** Must be used together with `type`.", + optional: true, + }, + attachLabelIds: { + propDefinition: [ + crowdin, + "attachLabelIds", + (c) => ({ + projectId: c.projectId, + }), + ], + }, + }, + async run({ $ }) { + const { + crowdin, + attachLabelIds, + projectId, + file, + ...data + } = this; + + const fileBinary = fs.readFileSync(checkTmp(file)); + const crowdinFilename = file.startsWith("/tmp/") + ? file.slice(5) + : file; + + const fileResponse = await crowdin.createStorage({ + data: Buffer.from(fileBinary, "binary"), + headers: { + "Crowdin-API-FileName": encodeURI(crowdinFilename), + "Content-Type": "application/octet-stream", + }, + }); + + const response = await crowdin.uploadFileToProject({ + $, + projectId, + data: { + ...data, + storageId: fileResponse.data.id, + attachLabelIds: parseObject(attachLabelIds), + }, + }); + $.export("$summary", `Successfully uploaded file: ${this.name}`); + return response; + }, +}; diff --git a/components/crowdin/actions/create-project/create-project.mjs b/components/crowdin/actions/create-project/create-project.mjs new file mode 100644 index 0000000000000..004cd4499cb7f --- /dev/null +++ b/components/crowdin/actions/create-project/create-project.mjs @@ -0,0 +1,184 @@ +import { ConfigurationError } from "@pipedream/platform"; +import { + LANGUAGE_ACCESS_POLICY_OPTIONS, + TAGS_DETECTION_OPTIONS, + VISIBILITY_OPTIONS, +} from "../../common/constants.mjs"; +import { parseObject } from "../../common/utils.mjs"; +import crowdin from "../../crowdin.app.mjs"; + +export default { + key: "crowdin-create-project", + name: "Create Project", + description: "Creates a new project within Crowdin. [See the documentation](https://support.crowdin.com/developer/api/v2/#/projects-api/create-project)", + version: "0.0.1", + type: "action", + props: { + crowdin, + name: { + type: "string", + label: "Project Name", + description: "The name of the project to be created", + }, + sourceLanguageId: { + propDefinition: [ + crowdin, + "sourceLanguageId", + ], + }, + targetLanguageIds: { + propDefinition: [ + crowdin, + "sourceLanguageId", + ], + type: "string[]", + label: "Target Language IDs", + description: "Array of target language IDs", + optional: true, + }, + identifier: { + type: "string", + label: "Identifier", + description: "A custom identifier for the project", + optional: true, + }, + visibility: { + type: "string", + label: "Visibility", + description: "Defines how users can join the project.", + options: VISIBILITY_OPTIONS, + optional: true, + }, + languageAccessPolicy: { + type: "string", + label: "Language Access Policy", + description: "Defines access to project languages.", + optional: true, + options: LANGUAGE_ACCESS_POLICY_OPTIONS, + }, + cname: { + type: "string", + label: "Custom Domain Name", + description: "Custom domain name for the project.", + optional: true, + }, + description: { + type: "string", + label: "Project Description", + description: "The description of the project.", + optional: true, + }, + tagsDetection: { + type: "string", + label: "Tags Detection", + description: "The type of the tags detection.", + options: TAGS_DETECTION_OPTIONS, + optional: true, + }, + isMtAllowed: { + type: "boolean", + label: "Allow Machine Translation", + description: "Allows machine translations to be visible for translators. Default is **true**.", + optional: true, + }, + taskBasedAccessControl: { + type: "boolean", + label: "Task Based Access Control", + description: "Allow project members to work with tasks they're assigned to. Default is **false**.", + optional: true, + default: false, + }, + autoSubstitution: { + type: "boolean", + label: "Auto Substitution", + description: "Allows auto-substitution. Default is **true**.", + optional: true, + default: true, + }, + autoTranslateDialects: { + type: "boolean", + label: "Auto Translate Dialects", + description: "Automatically fill in regional dialects. Default is **false**.", + optional: true, + }, + publicDownloads: { + type: "boolean", + label: "Public Downloads", + description: "Allows translators to download source files. Default is **true**.", + optional: true, + }, + hiddenStringsProofreadersAccess: { + type: "boolean", + label: "Hidden Strings Proofreaders Access", + description: "Allows proofreaders to work with hidden strings. Default is **true**.", + optional: true, + default: true, + }, + useGlobalTm: { + type: "boolean", + label: "Use Global Translation Memory", + description: "If true, machine translations from connected MT engines will appear as suggestions. Default is **true**.", + optional: true, + }, + showTmSuggestionsDialects: { + type: "boolean", + label: "Show TM Suggestions for Dialects", + description: "Show primary language TM suggestions for dialects if there are no dialect-specific ones. Default is **true**.", + optional: true, + default: true, + }, + skipUntranslatedStrings: { + type: "boolean", + label: "Skip Untranslated Strings", + description: "Defines whether to skip untranslated strings.", + optional: true, + }, + exportApprovedOnly: { + type: "boolean", + label: "Export Approved Only", + description: "Defines whether to export only approved strings.", + optional: true, + }, + qaCheckIsActive: { + type: "boolean", + label: "QA Check Is Active", + description: "If true, QA checks are active. Default is **true**.", + optional: true, + }, + type: { + type: "string", + label: "Type", + description: "Defines the project type. To create a file-based project, use 0.", + options: [ + "0", + "1", + ], + optional: true, + }, + }, + async run({ $ }) { + try { + const { + crowdin, + type, + targetLanguageIds, + tagsDetection, + ...data + } = this; + + const response = await crowdin.createProject({ + $, + data: { + ...data, + type: parseInt(type), + targetLanguageIds: parseObject(targetLanguageIds), + tagsDetection: parseInt(tagsDetection), + }, + }); + $.export("$summary", `Project created successfully with Id: ${response.data.id}`); + return response; + } catch ({ response }) { + throw new ConfigurationError(response.data.errors[0]?.error?.errors[0]?.message); + } + }, +}; diff --git a/components/crowdin/actions/translate-via-machine-translation/translate-via-machine-translation.mjs b/components/crowdin/actions/translate-via-machine-translation/translate-via-machine-translation.mjs new file mode 100644 index 0000000000000..e6f46ed2157bc --- /dev/null +++ b/components/crowdin/actions/translate-via-machine-translation/translate-via-machine-translation.mjs @@ -0,0 +1,60 @@ +import { LANGUAGE_R_PROVIDER_OPTIONS } from "../../common/constants.mjs"; +import crowdin from "../../crowdin.app.mjs"; + +export default { + key: "crowdin-translate-via-machine-translation", + name: "Translate via Machine Translation", + description: "Performs machine translation of the uploaded files. [See the documentation](https://support.crowdin.com/developer/api/v2/)", + version: "0.0.1", + type: "action", + props: { + crowdin, + mtId: { + propDefinition: [ + crowdin, + "mtId", + ], + }, + targetLanguageId: { + propDefinition: [ + crowdin, + "sourceLanguageId", + ], + type: "string", + label: "Target Language ID", + description: "The language ID for the target translation language", + }, + languageRecognitionProvider: { + type: "string", + label: "Language Recognition Provider", + description: "Select a provider for language recognition **Note:** Is required if the source language is not selected", + options: LANGUAGE_R_PROVIDER_OPTIONS, + }, + sourceLanguageId: { + propDefinition: [ + crowdin, + "sourceLanguageId", + ], + }, + strings: { + type: "string[]", + label: "Strings", + description: "Array of strings to be translated. **Note:** You can translate up to 100 strings at a time.", + }, + }, + async run({ $ }) { + const response = await this.crowdin.performMachineTranslation({ + $, + mtId: this.mtId, + data: { + targetLanguageId: this.targetLanguageId, + strings: this.strings, + languageRecognitionProvider: this.languageRecognitionProvider, + sourceLanguageId: this.sourceLanguageId, + }, + }); + + $.export("$summary", "Successfully performed machine translation"); + return response; + }, +}; diff --git a/components/crowdin/app/crowdin.app.ts b/components/crowdin/app/crowdin.app.ts deleted file mode 100644 index 680d55b717589..0000000000000 --- a/components/crowdin/app/crowdin.app.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineApp } from "@pipedream/types"; - -export default defineApp({ - type: "app", - app: "crowdin", - propDefinitions: {}, - methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); - }, - }, -}); \ No newline at end of file diff --git a/components/crowdin/common/constants.mjs b/components/crowdin/common/constants.mjs new file mode 100644 index 0000000000000..21964b4eb113b --- /dev/null +++ b/components/crowdin/common/constants.mjs @@ -0,0 +1,266 @@ +export const LIMIT = 500; + +export const VISIBILITY_OPTIONS = [ + { + label: "Open - anyone can join the project", + value: "open", + }, + { + label: "Private - only invited users can join the project", + value: "private", + }, +]; + +export const LANGUAGE_ACCESS_POLICY_OPTIONS = [ + { + label: "Open - each project user can access all project languages", + value: "open", + }, + { + label: "Moderate - users should join each project language separately", + value: "moderate", + }, +]; + +export const TAGS_DETECTION_OPTIONS = [ + { + label: "Auto", + value: "0", + }, + { + label: "Count Tags", + value: "1", + }, + { + label: "Skip Tags", + value: "2", + }, +]; + +export const LANGUAGE_R_PROVIDER_OPTIONS = [ + "crowdin", + "engine", +]; + +export const TYPE_OPTIONS = [ + { + label: "Try to detect file type by extension or MIME type", + value: "auto", + }, + { + label: "Android (*.xml)", + value: "android", + }, + { + label: "Mac OS X / iOS (*.strings)", + value: "macosx", + }, + { + label: ".NET, Windows Phone (*.resx)", + value: "resx", + }, + { + label: "Java (*.properties)", + value: "properties", + }, + { + label: "GNU GetText (*.po, *.pot)", + value: "gettext", + }, + { + label: "Ruby On Rails (*.yaml, *.yml)", + value: "yaml", + }, + { + label: "Hypertext Preprocessor (*.php)", + value: "php", + }, + { + label: "Generic JSON (*.json)", + value: "json", + }, + { + label: "Generic XML (*.xml)", + value: "xml", + }, + { + label: "Generic INI (*.ini)", + value: "ini", + }, + { + label: "Windows Resources (*.rc)", + value: "rc", + }, + { + label: "Windows 8 Metro (*.resw)", + value: "resw", + }, + { + label: "Windows 8 Metro (*.resjson)", + value: "resjson", + }, + { + label: "Nokia Qt (*.ts)", + value: "qtts", + }, + { + label: "Joomla localizable resources (*.ini)", + value: "joomla", + }, + { + label: "Google Chrome Extension (*.json)", + value: "chrome", + }, + { + label: "Mozilla DTD (*.dtd)", + value: "dtd", + }, + { + label: "Delphi DKLang (*.dklang)", + value: "dklang", + }, + { + label: "Flex (*.properties)", + value: "flex", + }, + { + label: "NSIS Installer Resources (*.nsh)", + value: "nsh", + }, + { + label: "WiX Installer (*.wxl)", + value: "wxl", + }, + { + label: "XLIFF (*.xliff, *.xlf)", + value: "xliff", + }, + { + label: "XLIFF 2.0 (*.xliff, *.xlf)", + value: "xliff_two", + }, + { + label: "HTML (*.html, *.htm, *.xhtml, *.xhtm, *.xht, *.hbs, *.liquid)", + value: "html", + }, + { + label: "Haml (*.haml)", + value: "haml", + }, + { + label: "Plain Text (*.txt)", + value: "txt", + }, + { + label: "Comma Separated Values (*.csv)", + value: "csv", + }, + { + label: "Markdown (*.md, *.text, *.markdown...)", + value: "md", + }, + { + label: "MadCap Flare (*.flnsp, .flpgpl .fltoc)", + value: "flsnp", + }, + { + label: "Jekyll HTML (*.html)", + value: "fm_html", + }, + { + label: "Jekyll Markdown (*.md)", + value: "fm_md", + }, + { + label: "MediaWiki (*.wiki, *.wikitext, *.mediawiki)", + value: "mediawiki", + }, + { + label: "Microsoft Office, OpenOffice.org Documents, Adobe InDesign, Adobe FrameMaker(*.docx, *.dotx, *.docm, *.dotm, *.xlsx, *.xltx, *.xlsm, *.xltm, *.pptx, *.potx, *.ppsx, *.pptm, *.potm, *.ppsm, *.odt, *.ods, *.odg, *.odp, *.imdl, *.mif)", + value: "docx", + }, + { + label: "Microsoft Excel (*.xlsx)", + value: "xlsx", + }, + { + label: "Youtube .sbv (*.sbv)", + value: "sbv", + }, + { + label: "Play Framework", + value: "properties_play", + }, + { + label: "Java Application (*.xml)", + value: "properties_xml", + }, + { + label: "Maxthon Browser (*.ini)", + value: "maxthon", + }, + { + label: "Go (*.gotext.json)", + value: "go_json", + }, + { + label: "DITA Document (*.dita, *.ditamap)", + value: "dita", + }, + { + label: "Adobe FrameMaker (*.mif)", + value: "mif", + }, + { + label: "Adobe InDesign (*.idml)", + value: "idml", + }, + { + label: "iOS (*.stringsdict)", + value: "stringsdict", + }, + { + label: "Mac OS property list (*.plist)", + value: "plist", + }, + { + label: "Video Subtitling and WebVTT (*.vtt)", + value: "vtt", + }, + { + label: "Steamworks Localization Valve Data File (*.vdf)", + value: "vdf", + }, + { + label: "SubRip .srt (*.srt)", + value: "srt", + }, + { + label: "Salesforce (*.stf)", + value: "stf", + }, + { + label: "Toml (*.toml)", + value: "toml", + }, + { + label: "Contentful (*.json)", + value: "contentful_rt", + }, + { + label: "SVG (*.svg)", + value: "svg", + }, + { + label: "JavaScript (*.js)", + value: "js", + }, + { + label: "CoffeeScript (*.coffee)", + value: "coffee", + }, + { + label: "NestJS i18n", + value: "nestjs_i18n", + }, +]; diff --git a/components/crowdin/common/utils.mjs b/components/crowdin/common/utils.mjs new file mode 100644 index 0000000000000..32b6f5a875df7 --- /dev/null +++ b/components/crowdin/common/utils.mjs @@ -0,0 +1,31 @@ +export const parseObject = (obj) => { + if (!obj) return undefined; + + if (Array.isArray(obj)) { + return obj.map((item) => { + if (typeof item === "string") { + try { + return JSON.parse(item); + } catch (e) { + return item; + } + } + return item; + }); + } + if (typeof obj === "string") { + try { + return JSON.parse(obj); + } catch (e) { + return obj; + } + } + return obj; +}; + +export const checkTmp = (filename) => { + if (filename.indexOf("/tmp") === -1) { + return `/tmp/${filename}`; + } + return filename; +}; diff --git a/components/crowdin/crowdin.app.mjs b/components/crowdin/crowdin.app.mjs new file mode 100644 index 0000000000000..2829d52aa45d9 --- /dev/null +++ b/components/crowdin/crowdin.app.mjs @@ -0,0 +1,306 @@ +import { axios } from "@pipedream/platform"; +import { LIMIT } from "./common/constants.mjs"; + +export default { + type: "app", + app: "crowdin", + propDefinitions: { + projectId: { + type: "string", + label: "Project ID", + description: "The ID of the project", + async options({ page }) { + const { data } = await this.listProjects({ + params: { + limit: LIMIT, + offset: LIMIT * page, + }, + }); + return data.map(({ + data: { + id: value, name: label, + }, + }) => ({ + label, + value, + })); + }, + }, + directoryId: { + type: "string", + label: "Directory ID", + description: "The ID of the directory. **Note:** Can't be used with `Branch Id` in same request", + optional: true, + async options({ + page, projectId, + }) { + const { data } = await this.listDirectories({ + projectId, + params: { + limit: LIMIT, + offset: LIMIT * page, + }, + }); + return data.map(({ + data: { + id: value, name: label, + }, + }) => ({ + label, + value, + })); + }, + }, + sourceLanguageId: { + type: "string", + label: "Source Language ID", + description: "The language ID of the source language", + async options({ page }) { + const { data } = await this.listSupportedLanguages({ + params: { + limit: LIMIT, + offset: LIMIT * page, + }, + }); + return data.map(({ + data: { + id: value, name: label, + }, + }) => ({ + label, + value, + })); + }, + }, + storageId: { + type: "integer", + label: "Storage ID", + description: "The ID of the storage", + async options({ page }) { + const { data } = await this.listStorages({ + params: { + limit: LIMIT, + offset: LIMIT * page, + }, + }); + return data.map(({ + data: { + id: value, fileName: label, + }, + }) => ({ + label, + value, + })); + }, + }, + branchId: { + type: "string", + label: "Branch ID", + description: "Defines branch to which file will be added. **Note:** Can't be used with `Directory Id` in same request", + optional: true, + async options({ + page, projectId, + }) { + const { data } = await this.listBranches({ + projectId, + params: { + limit: LIMIT, + offset: LIMIT * page, + }, + }); + return data.map(({ + data: { + id: value, name, title, + }, + }) => ({ + label: `${title} - ${name}`, + value, + })); + }, + }, + attachLabelIds: { + type: "string[]", + label: "Attach Label IDs", + description: "The IDs of the labels to attach", + optional: true, + async options({ + page, projectId, + }) { + const { data } = await this.listLabels({ + projectId, + params: { + limit: LIMIT, + offset: LIMIT * page, + }, + }); + return data.map(({ + data: { + id: value, title: label, + }, + }) => ({ + label, + value, + })); + }, + }, + mtId: { + type: "string", + label: "Machine Translation ID", + description: "The ID of the machine translation engine", + async options({ page }) { + const { data } = await this.listMTs({ + params: { + limit: LIMIT, + offset: LIMIT * page, + }, + }); + return data.map(({ + data: { + id: value, name: label, + }, + }) => ({ + label, + value, + })); + }, + }, + }, + methods: { + _baseUrl() { + return "https://api.crowdin.com/api/v2"; + }, + _headers(headers = {}) { + return { + Authorization: `Bearer ${this.$auth.oauth_access_token}`, + ...headers, + }; + }, + _makeRequest({ + $ = this, path, headers, ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + headers: this._headers(headers), + ...opts, + }); + }, + listProjects(opts = {}) { + return this._makeRequest({ + path: "/projects", + ...opts, + }); + }, + listDirectories({ + projectId, ...opts + }) { + return this._makeRequest({ + path: `/projects/${projectId}/directories`, + ...opts, + }); + }, + listSupportedLanguages(opts = {}) { + return this._makeRequest({ + path: "/languages", + ...opts, + }); + }, + listStorages(opts = {}) { + return this._makeRequest({ + path: "/storages", + ...opts, + }); + }, + listBranches({ + projectId, ...opts + }) { + return this._makeRequest({ + path: `/projects/${projectId}/branches`, + ...opts, + }); + }, + listLabels({ + projectId, ...opts + }) { + return this._makeRequest({ + path: `/projects/${projectId}/labels`, + ...opts, + }); + }, + listMTs(opts = {}) { + return this._makeRequest({ + path: "/mts", + ...opts, + }); + }, + createProject(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/projects", + ...opts, + }); + }, + createStorage(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/storages", + ...opts, + }); + }, + uploadFileToProject({ + projectId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/projects/${projectId}/files`, + ...opts, + }); + }, + performMachineTranslation({ + mtId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/mts/${mtId}/translations`, + ...opts, + }); + }, + createWebhook({ + projectId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/projects/${projectId}/webhooks`, + ...opts, + }); + }, + deleteWebhook({ + projectId, webhookId, + }) { + return this._makeRequest({ + method: "DELETE", + path: `/projects/${projectId}/webhooks/${webhookId}`, + }); + }, + async *paginate({ + fn, params = {}, ...opts + }) { + let hasMore = false; + let page = 0; + + do { + params.limit = LIMIT; + params.offset = LIMIT * page++; + const { data } = await fn({ + params, + ...opts, + }); + for (const d of data) { + yield d; + } + + hasMore = data.length; + + } while (hasMore); + }, + }, +}; diff --git a/components/crowdin/package.json b/components/crowdin/package.json index 33034ea5d16a9..2b917337bf538 100644 --- a/components/crowdin/package.json +++ b/components/crowdin/package.json @@ -1,16 +1,19 @@ { "name": "@pipedream/crowdin", - "version": "0.0.2", + "version": "0.1.0", "description": "Pipedream Crowdin Components", - "main": "dist/app/crowdin.app.mjs", + "main": "crowdin.app.mjs", "keywords": [ "pipedream", "crowdin" ], - "files": ["dist"], "homepage": "https://pipedream.com/apps/crowdin", "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3", + "fs": "^0.0.1-security" } } diff --git a/components/crowdin/sources/common/base-instant.mjs b/components/crowdin/sources/common/base-instant.mjs new file mode 100644 index 0000000000000..13341ebf7cc50 --- /dev/null +++ b/components/crowdin/sources/common/base-instant.mjs @@ -0,0 +1,68 @@ +import crowdin from "../../crowdin.app.mjs"; + +export default { + props: { + crowdin, + http: { + type: "$.interface.http", + customResponse: true, + }, + db: "$.service.db", + name: { + type: "string", + label: "Name", + description: "The webhook name.", + }, + projectId: { + propDefinition: [ + crowdin, + "projectId", + ], + }, + }, + methods: { + _getHookId() { + return this.db.get("hookId"); + }, + _setHookId(hookId) { + this.db.set("hookId", hookId); + }, + getExtraData() { + return {}; + }, + }, + hooks: { + async activate() { + const response = await this.crowdin.createWebhook({ + projectId: this.projectId, + data: { + name: this.name, + url: this.http.endpoint, + events: this.getEvents(), + requestType: "POST", + }, + }); + this._setHookId(response.data.id); + }, + async deactivate() { + const webhookId = this._getHookId(); + await this.crowdin.deleteWebhook({ + projectId: this.projectId, + webhookId, + }); + }, + }, + async run({ body }) { + + this.http.respond({ + status: 200, + }); + + const ts = Date.parse(new Date()); + this.$emit(body, { + id: `${body.comment?.id || ts}`, + summary: this.getSummary(body), + ts: ts, + }); + }, +}; diff --git a/components/crowdin/sources/common/base.mjs b/components/crowdin/sources/common/base.mjs new file mode 100644 index 0000000000000..9709d575c5d06 --- /dev/null +++ b/components/crowdin/sources/common/base.mjs @@ -0,0 +1,66 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import crowdin from "../../crowdin.app.mjs"; + +export default { + props: { + crowdin, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + projectId: { + propDefinition: [ + crowdin, + "projectId", + ], + }, + }, + methods: { + _getLastId() { + return this.db.get("lastId") || 0; + }, + _setLastId(lastId) { + this.db.set("lastId", lastId); + }, + async emitEvent(maxResults = false) { + const lastId = this._getLastId(); + + const response = this.crowdin.paginate({ + projectId: this.projectId, + ...this.getArgs(), + }); + + let responseArray = []; + for await (const item of response) { + if (item.data.id <= lastId) break; + responseArray.push(item.data); + } + + if (responseArray.length) { + if (maxResults && (responseArray.length > maxResults)) { + responseArray.length = maxResults; + } + this._setLastId(responseArray[0].id); + } + + for (const item of responseArray.reverse()) { + this.$emit(item, { + id: item.id, + summary: this.getSummary(item), + ts: Date.parse(item.createdAt), + }); + } + }, + }, + hooks: { + async deploy() { + await this.emitEvent(25); + }, + }, + async run() { + await this.emitEvent(); + }, +}; diff --git a/components/crowdin/sources/file-approved-instant/file-approved-instant.mjs b/components/crowdin/sources/file-approved-instant/file-approved-instant.mjs new file mode 100644 index 0000000000000..1672800d09e5a --- /dev/null +++ b/components/crowdin/sources/file-approved-instant/file-approved-instant.mjs @@ -0,0 +1,24 @@ +import common from "../common/base-instant.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "crowdin-file-approved-instant", + name: "New File Approved (Instant)", + description: "Emit new event when a file is fully translated and approved.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEvents() { + return [ + "project.approved", + ]; + }, + getSummary(body) { + return `File approved for project ID: ${body.project.id}`; + }, + }, + sampleEmit, +}; diff --git a/components/crowdin/sources/file-approved-instant/test-event.mjs b/components/crowdin/sources/file-approved-instant/test-event.mjs new file mode 100644 index 0000000000000..bc81fc2ff5f3e --- /dev/null +++ b/components/crowdin/sources/file-approved-instant/test-event.mjs @@ -0,0 +1,36 @@ +export default { + "event": "project.approved", + "project": { + "id": "123123", + "userId": "123123", + "sourceLanguageId": "en", + "targetLanguageIds": [ + "ru", + "pt-BR" + ], + "identifier": "project-01", + "name": "Project 01", + "createdAt": "2024-10-28T13:54:03+00:00", + "updatedAt": "2024-10-30T14:59:43+00:00", + "lastActivity": "2024-10-30T16:35:02+00:00", + "description": "", + "url": "https://crowdin.com/project/project-01", + "cname": null, + "languageAccessPolicy": "open", + "visibility": "private", + "publicDownloads": true + }, + "targetLanguage": { + "id": "en", + "name": "English", + "editorCode": "en", + "twoLettersCode": "en", + "threeLettersCode": "eng", + "locale": "en", + "androidCode": "en", + "osxCode": "en.lproj", + "osxLocale": "en", + "textDirection": "ltr", + "dialectOf": null + } +} \ No newline at end of file diff --git a/components/crowdin/sources/new-comment-issue-instant/new-comment-issue-instant.mjs b/components/crowdin/sources/new-comment-issue-instant/new-comment-issue-instant.mjs new file mode 100644 index 0000000000000..17098ecb6a80d --- /dev/null +++ b/components/crowdin/sources/new-comment-issue-instant/new-comment-issue-instant.mjs @@ -0,0 +1,24 @@ +import common from "../common/base-instant.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "crowdin-new-comment-issue-instant", + name: "New Comment or Issue Added (Instant)", + description: "Emit new event when a user adds a comment or an issue in Crowdin.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEvents() { + return [ + "stringComment.created", + ]; + }, + getSummary(body) { + return `New comment or issue in project: ${body.comment.string.project.id}`; + }, + }, + sampleEmit, +}; diff --git a/components/crowdin/sources/new-comment-issue-instant/test-event.mjs b/components/crowdin/sources/new-comment-issue-instant/test-event.mjs new file mode 100644 index 0000000000000..18737adfb28e4 --- /dev/null +++ b/components/crowdin/sources/new-comment-issue-instant/test-event.mjs @@ -0,0 +1,91 @@ +export default { + "event": "stringComment.created", + "comment": { + "id": "4", + "text": "text 01", + "type": "issue", + "issueType": "translation_mistake", + "issueStatus": "unresolved", + "resolvedAt": null, + "createdAt": "2024-10-30T17:06:06+00:00", + "string": { + "id": "2", + "identifier": "5ad6c6099c2bc7211eb8151483826e80", + "key": "NFDBB2FA9", + "text": "Text file", + "type": "text", + "context": "NFDBB2FA9", + "maxLength": "0", + "isHidden": false, + "isDuplicate": false, + "masterStringId": null, + "revision": "1", + "hasPlurals": false, + "labelIds": [], + "url": "https://crowdin.com/editor/project-01/14/en-ptbr#2", + "createdAt": "2024-10-28T14:18:30+00:00", + "updatedAt": null, + "file": { + "id": "14", + "name": "file.docx", + "title": null, + "type": "docx13", + "path": "/Folder 01/dummy.docx", + "status": "active", + "revision": "1", + "branchId": null, + "directoryId": "2" + }, + "project": { + "id": "123123", + "userId": "123123", + "sourceLanguageId": "en", + "targetLanguageIds": [ + "ru", + "pt-BR", + "yi", + "am", + "es-PR", + "es-NI", + "ar-YE" + ], + "identifier": "project-01", + "name": "Project 01", + "createdAt": "2024-10-28T13:54:03+00:00", + "updatedAt": "2024-10-30T16:40:17+00:00", + "lastActivity": "2024-10-30T17:03:02+00:00", + "description": "", + "url": "https://crowdin.com/project/project-01", + "cname": null, + "languageAccessPolicy": "open", + "visibility": "private", + "publicDownloads": true + } + }, + "targetLanguage": { + "id": "pt-BR", + "name": "Portuguese, Brazilian", + "editorCode": "ptbr", + "twoLettersCode": "pt", + "threeLettersCode": "por", + "locale": "pt-BR", + "androidCode": "pt-rBR", + "osxCode": "pt-BR.lproj", + "osxLocale": "pt_BR", + "textDirection": "ltr", + "dialectOf": null + }, + "user": { + "id": "16645627", + "username": "username", + "fullName": "username", + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/123123/small/f1fb36bf9de3ded05b455e88ac9c77cf_default.png" + }, + "commentResolver": { + "id": null, + "username": null, + "fullName": null, + "avatarUrl": null + } + } +} \ No newline at end of file diff --git a/components/crowdin/sources/new-directory/new-directory.mjs b/components/crowdin/sources/new-directory/new-directory.mjs new file mode 100644 index 0000000000000..63a5600c94051 --- /dev/null +++ b/components/crowdin/sources/new-directory/new-directory.mjs @@ -0,0 +1,27 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "crowdin-new-directory", + name: "New Directory Created", + description: "Emit new event when a new directory is created.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getArgs() { + return { + fn: this.crowdin.listDirectories, + params: { + orderBy: "createdAt desc", + }, + }; + }, + getSummary(item) { + return `New Directory: ${item.name}`; + }, + }, + sampleEmit, +}; diff --git a/components/crowdin/sources/new-directory/test-event.mjs b/components/crowdin/sources/new-directory/test-event.mjs new file mode 100644 index 0000000000000..9b19e5716e62c --- /dev/null +++ b/components/crowdin/sources/new-directory/test-event.mjs @@ -0,0 +1,15 @@ +export default { + "data": { + "id": 4, + "projectId": 2, + "branchId": 34, + "directoryId": null, + "name": "main", + "title": "", + "exportPattern": "/localization/%locale%/%file_name%", + "path": "/main", + "priority": "normal", + "createdAt": "2019-09-19T14:14:00+00:00", + "updatedAt": "2019-09-19T14:14:00+00:00" + } +} \ No newline at end of file diff --git a/components/danny_test_app/danny_test_app.app.mjs b/components/danny_test_app/danny_test_app.app.mjs new file mode 100644 index 0000000000000..8141f60595dd6 --- /dev/null +++ b/components/danny_test_app/danny_test_app.app.mjs @@ -0,0 +1,11 @@ +export default { + type: "app", + app: "danny_test_app", + propDefinitions: {}, + methods: { + // this.$auth contains connected account data + authKeys() { + console.log(Object.keys(this.$auth)); + }, + }, +}; \ No newline at end of file diff --git a/components/discord_bot/package.json b/components/discord_bot/package.json index 49ec80054ecdb..357cc70699011 100644 --- a/components/discord_bot/package.json +++ b/components/discord_bot/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/discord_bot", - "version": "0.5.5", + "version": "0.6.0", "description": "Pipedream Discord_bot Components", "main": "discord_bot.app.js", "keywords": [ @@ -10,7 +10,7 @@ "homepage": "https://pipedream.com/apps/discord_bot", "author": "Pipedream (https://pipedream.com/)", "dependencies": { - "@pipedream/platform": "^1.6.0", + "@pipedream/platform": "^3.0.3", "form-data": "^4.0.0", "lodash.maxby": "^4.6.0" }, diff --git a/components/discord_bot/sources/new-forum-thread-message/new-forum-thread-message.mjs b/components/discord_bot/sources/new-forum-thread-message/new-forum-thread-message.mjs index 604978fde790b..5466e5abfe46d 100644 --- a/components/discord_bot/sources/new-forum-thread-message/new-forum-thread-message.mjs +++ b/components/discord_bot/sources/new-forum-thread-message/new-forum-thread-message.mjs @@ -8,9 +8,9 @@ export default { ...common, key: "discord_bot-new-forum-thread-message", name: "New Forum Thread Message", - description: "Emit new event for each forum thread message posted. Note that your bot must have the `MESSAGE_CONTENT` privilege intent to see the message content, [see the docs here](https://discord.com/developers/docs/topics/gateway#message-content-intent).", + description: "Emit new event for each forum thread message posted. Note that your bot must have the `MESSAGE_CONTENT` privilege intent to see the message content. [See the documentation](https://discord.com/developers/docs/topics/gateway#message-content-intent).", type: "source", - version: "0.0.3", + version: "0.0.4", dedupe: "unique", // Dedupe events based on the Discord message ID props: { ...common.props, @@ -36,6 +36,14 @@ export default { description: "Select the forum you want to watch.", }, }, + methods: { + ...common.methods, + getChannel(id) { + return this.discord._makeRequest({ + path: `/channels/${id}`, + }); + }, + }, async run({ $ }) { // We store a cursor to the last message ID let lastMessageIDs = this._getLastMessageIDs(); @@ -107,6 +115,21 @@ export default { console.log(`${messages.length} new messages in thread ${channelId}`); + messages = await Promise.all(messages.map(async (message) => ({ + ...message, + thread: await this.getChannel(message.channel_id), + }))); + + const { available_tags: availableTags = [] } = await this.getChannel(this.forumId); + for (const message of messages) { + if (!message.thread.applied_tags) { + message.thread.applied_tags = []; + } + message.thread.applied_tags = message.thread.applied_tags.map((tagId) => ({ + ...availableTags.find(({ id }) => id === tagId), + })); + } + messages.reverse().forEach((message) => { this.$emit(message, { id: message.id, // dedupes events based on this ID diff --git a/components/discord_bot/sources/new-forum-thread-message/test-event.mjs b/components/discord_bot/sources/new-forum-thread-message/test-event.mjs index 71fae6e2a5ef5..1a8687e5f2fae 100644 --- a/components/discord_bot/sources/new-forum-thread-message/test-event.mjs +++ b/components/discord_bot/sources/new-forum-thread-message/test-event.mjs @@ -28,5 +28,45 @@ export default { "edited_timestamp": null, "flags": 0, "components": [], - "position": 13 -} \ No newline at end of file + "position": 13, + "thread": { + "id": "1301256410990116917", + "type": 11, + "last_message_id": "1301256410990116917", + "flags": 0, + "guild_id": "901259362205589565", + "name": "hello world", + "parent_id": "1301256016934994024", + "rate_limit_per_user": 0, + "bitrate": 64000, + "user_limit": 0, + "rtc_region": null, + "owner_id": "867892178135023656", + "thread_metadata": { + "archived": false, + "archive_timestamp": "2024-10-30T18:48:24.555000+00:00", + "auto_archive_duration": 4320, + "locked": false, + "create_timestamp": "2024-10-30T18:48:24.555000+00:00", + }, + "message_count": 0, + "member_count": 1, + "total_message_sent": 0, + "applied_tags": [ + { + "id": "1301256232052457563", + "name": "tag", + "moderated": false, + "emoji_id": null, + "emoji_name": null, + }, + { + "id": "1301281978968178759", + "name": "tag2", + "moderated": false, + "emoji_id": null, + "emoji_name": null, + }, + ], + }, +}; diff --git a/components/discord_bot/sources/new-tag-added-to-thread/new-tag-added-to-thread.mjs b/components/discord_bot/sources/new-tag-added-to-thread/new-tag-added-to-thread.mjs new file mode 100644 index 0000000000000..cb5b8dc58303f --- /dev/null +++ b/components/discord_bot/sources/new-tag-added-to-thread/new-tag-added-to-thread.mjs @@ -0,0 +1,87 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import common from "../common.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "discord_bot-new-tag-added-to-thread", + name: "New Tag Added to Forum Thread", + description: "Emit new event when a new tag is added to a thread", + type: "source", + version: "0.0.1", + dedupe: "unique", + props: { + ...common.props, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + hooks: { + async deploy() { + const tags = {}; + const { threads } = await this.discord.listThreads({ + guildId: this.guildId, + }); + threads.forEach((thread) => { + if (thread?.applied_tags) { + tags[thread.id] = thread?.applied_tags; + } + }); + this._setTags(tags); + }, + }, + methods: { + ...common.methods, + _getTags() { + return this.db.get("tags") || {}; + }, + _setTags(tags) { + this.db.set("tags", tags); + }, + generateMeta(thread) { + return { + id: thread.id, + summary: `New tag in thread ${thread.id}`, + ts: Date.now(), + }; + }, + getChannel(id) { + return this.discord._makeRequest({ + path: `/channels/${id}`, + }); + }, + }, + async run() { + let tags = this._getTags(); + + const { threads } = await this.discord.listThreads({ + guildId: this.guildId, + }); + + for (const thread of threads) { + if (!thread.applied_tags) { + continue; + } + if (thread.applied_tags.some((tag) => !tags[thread.id] || !tags[thread.id].includes(tag))) { + tags[thread.id] = thread.applied_tags; + + const { available_tags: availableTags = [] } = await this.getChannel(thread.parent_id); + + thread.applied_tags = thread.applied_tags.map((tagId) => ({ + ...availableTags.find(({ id }) => id === tagId), + })); + + const meta = this.generateMeta(thread); + this.$emit(thread, meta); + + } + } + + this._setTags(tags); + }, + sampleEmit, +}; diff --git a/components/discord_bot/sources/new-tag-added-to-thread/test-event.mjs b/components/discord_bot/sources/new-tag-added-to-thread/test-event.mjs new file mode 100644 index 0000000000000..f6d7bcabf1448 --- /dev/null +++ b/components/discord_bot/sources/new-tag-added-to-thread/test-event.mjs @@ -0,0 +1,47 @@ +export default { + "id": "1301256410990116917", + "type": 11, + "last_message_id": "1301256410990116917", + "flags": 0, + "guild_id": "901259362205589565", + "name": "hello world", + "parent_id": "1301256016934994024", + "rate_limit_per_user": 0, + "bitrate": 64000, + "user_limit": 0, + "rtc_region": null, + "owner_id": "867892178135023656", + "thread_metadata": { + "archived": false, + "archive_timestamp": "2024-10-30T18:48:24.555000+00:00", + "auto_archive_duration": 4320, + "locked": false, + "create_timestamp": "2024-10-30T18:48:24.555000+00:00" + }, + "message_count": 0, + "member_count": 1, + "total_message_sent": 0, + "applied_tags": [ + { + "id": "1301256232052457563", + "name": "tag", + "moderated": false, + "emoji_id": null, + "emoji_name": null, + }, + { + "id": "1301282004998033428", + "name": "tag3", + "moderated": false, + "emoji_id": null, + "emoji_name": null, + }, + { + "id": "1301281978968178759", + "name": "tag2", + "moderated": false, + "emoji_id": null, + "emoji_name": null, + }, + ], +}; diff --git a/components/e2b/actions/run-code/run-code.mjs b/components/e2b/actions/run-code/run-code.mjs new file mode 100644 index 0000000000000..ad2280fcd842d --- /dev/null +++ b/components/e2b/actions/run-code/run-code.mjs @@ -0,0 +1,29 @@ +import app from "../../e2b.app.mjs"; + +export default { + key: "e2b-run-code", + name: "Run Code", + description: "Run or interpret code using the E2B service. [See the documentation](https://www.npmjs.com/package/e2b).", + version: "0.0.1", + type: "action", + props: { + app, + code: { + type: "string", + label: "Code", + description: "The code that will be interpreted by the E2B service. Eg. `print('Hello, World!')`.", + }, + }, + async run({ $ }) { + const { + app, + code, + } = this; + + const response = await app.runCode(code); + + $.export("$summary", "Successfully interpreted code."); + + return response; + }, +}; diff --git a/components/e2b/e2b.app.mjs b/components/e2b/e2b.app.mjs new file mode 100644 index 0000000000000..75f0dfd9efaf7 --- /dev/null +++ b/components/e2b/e2b.app.mjs @@ -0,0 +1,16 @@ +import { Sandbox } from "@e2b/code-interpreter"; + +export default { + type: "app", + app: "e2b", + methods: { + getSandbox() { + process.env.E2B_API_KEY = this.$auth.api_key; + return Sandbox.create(); + }, + async runCode(code) { + const sandbox = await this.getSandbox(); + return sandbox.runCode(code); + }, + }, +}; diff --git a/components/e2b/package.json b/components/e2b/package.json new file mode 100644 index 0000000000000..2387f95e4cef5 --- /dev/null +++ b/components/e2b/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/e2b", + "version": "0.1.0", + "description": "Pipedream E2B Components", + "main": "e2b.app.mjs", + "keywords": [ + "pipedream", + "e2b" + ], + "homepage": "https://pipedream.com/apps/e2b", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@e2b/code-interpreter": "^1.0.3" + } +} diff --git a/components/enrichley/actions/validate-email/validate-email.mjs b/components/enrichley/actions/validate-email/validate-email.mjs new file mode 100644 index 0000000000000..30040ae3933d1 --- /dev/null +++ b/components/enrichley/actions/validate-email/validate-email.mjs @@ -0,0 +1,27 @@ +import enrichley from "../../enrichley.app.mjs"; + +export default { + key: "enrichley-validate-email", + name: "Validate Email", + description: "Checks the validity of a single email address using Enrichley. [See the documentation](https://enrichley.readme.io/reference/validatesingleemail)", + version: "0.0.1", + type: "action", + props: { + enrichley, + email: { + type: "string", + label: "Email", + description: "The email address to validate", + }, + }, + async run({ $ }) { + const response = await this.enrichley.validateEmail({ + $, + data: { + email: this.email, + }, + }); + $.export("$summary", `Successfully retrieved status for email: ${this.email}`); + return response; + }, +}; diff --git a/components/enrichley/enrichley.app.mjs b/components/enrichley/enrichley.app.mjs index 2b98eec2fc630..6c9e6fd8c91b0 100644 --- a/components/enrichley/enrichley.app.mjs +++ b/components/enrichley/enrichley.app.mjs @@ -1,11 +1,32 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "enrichley", - propDefinitions: {}, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.enrichley.io/api/v1"; + }, + _makeRequest({ + $ = this, + path, + ...opts + }) { + return axios($, { + url: `${this._baseUrl()}${path}`, + headers: { + "Accept": "application/json", + "X-API-Key": `${this.$auth.api_key}`, + }, + ...opts, + }); + }, + validateEmail(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/validate-single-email", + ...opts, + }); }, }, -}; \ No newline at end of file +}; diff --git a/components/enrichley/package.json b/components/enrichley/package.json index 59d4369b6aada..ecff527d26545 100644 --- a/components/enrichley/package.json +++ b/components/enrichley/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/enrichley", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Enrichley Components", "main": "enrichley.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } -} \ No newline at end of file +} diff --git a/components/equifax/equifax.app.mjs b/components/equifax/equifax.app.mjs new file mode 100644 index 0000000000000..560144a8ec1b3 --- /dev/null +++ b/components/equifax/equifax.app.mjs @@ -0,0 +1,11 @@ +export default { + type: "app", + app: "equifax", + propDefinitions: {}, + methods: { + // this.$auth contains connected account data + authKeys() { + console.log(Object.keys(this.$auth)); + }, + }, +}; \ No newline at end of file diff --git a/components/equifax/package.json b/components/equifax/package.json new file mode 100644 index 0000000000000..9b1b20ff5710d --- /dev/null +++ b/components/equifax/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pipedream/equifax", + "version": "0.0.1", + "description": "Pipedream Equifax Components", + "main": "equifax.app.mjs", + "keywords": [ + "pipedream", + "equifax" + ], + "homepage": "https://pipedream.com/apps/equifax", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/components/fal_ai/actions/add-request-to-queue/add-request-to-queue.mjs b/components/fal_ai/actions/add-request-to-queue/add-request-to-queue.mjs new file mode 100644 index 0000000000000..4e7370c783b83 --- /dev/null +++ b/components/fal_ai/actions/add-request-to-queue/add-request-to-queue.mjs @@ -0,0 +1,124 @@ +import app from "../../fal_ai.app.mjs"; + +export default { + key: "fal_ai-add-request-to-queue", + name: "Add Request to Queue", + description: "Adds a request to the queue for asynchronous processing, including specifying a webhook URL for receiving updates. [See the documentation](https://fal.ai/docs/model-endpoints/queue#queue-endpoints).", + version: "0.0.1", + type: "action", + props: { + app, + appId: { + propDefinition: [ + app, + "appId", + ], + }, + data: { + type: "object", + label: "Data", + description: "Additional data to include with the request. [See the documentation](https://fal.ai/models/fal-ai/lora/api#schema-input) for more input fields.", + default: { + model_name: "stabilityai/stable-diffusion-xl-base-1.0", + prompt: "Photo of a european medieval 40 year old queen, silver hair, highly detailed face, detailed eyes, head shot, intricate crown, age spots, wrinkles", + }, + }, + reRunEnabled: { + type: "boolean", + label: "Rerun Enabled", + description: "Enable the step to rerun to retrieve the request response. [See the documentation](https://pipedream.com/docs/code/nodejs/rerun/#flowrerun).", + optional: true, + reloadProps: true, + default: false, + }, + }, + additionalProps() { + if (this.reRunEnabled) { + return { + reRunTimeoutInSecs: { + type: "integer", + label: "Rerun Timeout", + description: "The time in seconds to wait before rerunning the step to retrieve the request response. Eg. `30`. [See the documentation](https://pipedream.com/docs/code/nodejs/rerun/#flowrerun).", + optional: true, + min: 10, + }, + }; + } + + return { + falWebhook: { + type: "string", + label: "Webhook URL", + description: "The URL to receive updates via webhook.", + optional: true, + }, + }; + }, + methods: { + addToQueue({ + appId, ...args + } = {}) { + return this.app.post({ + path: `/${appId}`, + ...args, + }); + }, + }, + async run({ $ }) { + const { + context: { + run: { + runs, + callback_request: callbackRequest, + }, + }, + } = $; + + const { + app, + addToQueue, + appId, + data, + falWebhook, + reRunEnabled, + reRunTimeoutInSecs, + } = this; + + if (!reRunEnabled) { + const response = await addToQueue({ + $, + appId, + params: { + fal_webhook: falWebhook, + }, + data, + }); + + $.export("$summary", `Successfully added the request to the queue with ID \`${response.request_id}\`.`); + return response; + } + + if (runs === 1) { + const timeout = 1000 * (reRunTimeoutInSecs || 10); + const { resume_url: resumeUrl } = $.flow.rerun(timeout, null, 1); + + return addToQueue({ + $, + appId, + params: { + fal_webhook: resumeUrl, + }, + data, + }); + } + + const response = await app.getRequestResponse({ + $, + appId, + requestId: callbackRequest.body?.request_id, + }); + + $.export("$summary", "Successfully retrieved the request response."); + return response; + }, +}; diff --git a/components/fal_ai/actions/cancel-request/cancel-request.mjs b/components/fal_ai/actions/cancel-request/cancel-request.mjs new file mode 100644 index 0000000000000..237e56cff93a3 --- /dev/null +++ b/components/fal_ai/actions/cancel-request/cancel-request.mjs @@ -0,0 +1,50 @@ +import app from "../../fal_ai.app.mjs"; + +export default { + key: "fal_ai-cancel-request", + name: "Cancel Request", + description: "Cancels a request in the queue. This allows you to stop a long-running task if it's no longer needed. [See the documentation](https://fal.ai/docs/model-endpoints/queue#queue-endpoints).", + version: "0.0.1", + type: "action", + props: { + app, + appId: { + propDefinition: [ + app, + "appId", + ], + }, + requestId: { + propDefinition: [ + app, + "requestId", + ], + }, + }, + methods: { + cancelRequest({ + appId, requestId, ...args + } = {}) { + return this.app.put({ + path: `/${appId}/requests/${requestId}/cancel`, + ...args, + }); + }, + }, + async run({ $ }) { + const { + cancelRequest, + appId, + requestId, + } = this; + + const response = await cancelRequest({ + $, + appId, + requestId, + }); + + $.export("$summary", "Successfully canceled request."); + return response; + }, +}; diff --git a/components/fal_ai/actions/get-request-response/get-request-response.mjs b/components/fal_ai/actions/get-request-response/get-request-response.mjs new file mode 100644 index 0000000000000..1869e9d5c2c03 --- /dev/null +++ b/components/fal_ai/actions/get-request-response/get-request-response.mjs @@ -0,0 +1,40 @@ +import app from "../../fal_ai.app.mjs"; + +export default { + key: "fal_ai-get-request-response", + name: "Get Request Response", + description: "Gets the response of a completed request in the queue. This retrieves the results of your asynchronous task. [See the documentation](https://fal.ai/docs/model-endpoints/queue#queue-endpoints).", + version: "0.0.1", + type: "action", + props: { + app, + appId: { + propDefinition: [ + app, + "appId", + ], + }, + requestId: { + propDefinition: [ + app, + "requestId", + ], + }, + }, + async run({ $ }) { + const { + app, + appId, + requestId, + } = this; + + const response = await app.getRequestResponse({ + $, + appId, + requestId, + }); + + $.export("$summary", "Successfully retrieved the request response."); + return response; + }, +}; diff --git a/components/fal_ai/actions/get-request-status/get-request-status.mjs b/components/fal_ai/actions/get-request-status/get-request-status.mjs new file mode 100644 index 0000000000000..9a96f44b7c775 --- /dev/null +++ b/components/fal_ai/actions/get-request-status/get-request-status.mjs @@ -0,0 +1,53 @@ +import app from "../../fal_ai.app.mjs"; + +export default { + key: "fal_ai-get-request-status", + name: "Get Request Status", + description: "Gets the status of a request in the queue. This allows you to monitor the progress of your asynchronous tasks. [See the documentation](https://fal.ai/docs/model-endpoints/queue#queue-endpoints).", + version: "0.0.1", + type: "action", + props: { + app, + appId: { + propDefinition: [ + app, + "appId", + ], + }, + requestId: { + propDefinition: [ + app, + "requestId", + ], + }, + logs: { + propDefinition: [ + app, + "logs", + ], + }, + }, + async run({ $ }) { + const { + app, + appId, + requestId, + logs, + } = this; + + const response = await app.getRequestStatus({ + $, + appId, + requestId, + params: { + logs: logs + ? 1 + : undefined, + }, + }); + + $.export("$summary", `Successfully retrieved status as \`${response.status}\`.`); + + return response; + }, +}; diff --git a/components/fal_ai/fal_ai.app.mjs b/components/fal_ai/fal_ai.app.mjs new file mode 100644 index 0000000000000..b097f098ed170 --- /dev/null +++ b/components/fal_ai/fal_ai.app.mjs @@ -0,0 +1,73 @@ +import { axios } from "@pipedream/platform"; + +export default { + type: "app", + app: "fal_ai", + propDefinitions: { + appId: { + type: "string", + label: "App ID", + description: "The unique identifier for the app. Eg. `lora`.", + }, + requestId: { + type: "string", + label: "Request ID", + description: "The unique identifier for the request.", + }, + logs: { + type: "boolean", + label: "Enable Logs", + description: "Specify if logs should be enabled for the request status.", + optional: true, + }, + }, + methods: { + getUrl(path) { + return `https://queue.fal.run/fal-ai${path}`; + }, + getHeaders(headers) { + return { + ...headers, + "Authorization": `Key ${this.$auth.api_key}`, + "Content-Type": "application/json", + }; + }, + _makeRequest({ + $ = this, path, headers, ...args + } = {}) { + return axios($, { + ...args, + url: this.getUrl(path), + headers: this.getHeaders(headers), + }); + }, + post(args = {}) { + return this._makeRequest({ + method: "POST", + ...args, + }); + }, + put(args = {}) { + return this._makeRequest({ + method: "PUT", + ...args, + }); + }, + getRequestStatus({ + appId, requestId, ...args + } = {}) { + return this._makeRequest({ + path: `/${appId}/requests/${requestId}/status`, + ...args, + }); + }, + getRequestResponse({ + appId, requestId, ...args + } = {}) { + return this._makeRequest({ + path: `/${appId}/requests/${requestId}`, + ...args, + }); + }, + }, +}; diff --git a/components/fal_ai/package.json b/components/fal_ai/package.json new file mode 100644 index 0000000000000..d0a3a59b08d02 --- /dev/null +++ b/components/fal_ai/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/fal_ai", + "version": "0.1.0", + "description": "Pipedream fal.ai Components", + "main": "fal_ai.app.mjs", + "keywords": [ + "pipedream", + "fal_ai" + ], + "homepage": "https://pipedream.com/apps/fal_ai", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "3.0.3" + } +} diff --git a/components/fiserv/fiserv.app.mjs b/components/fiserv/fiserv.app.mjs new file mode 100644 index 0000000000000..d966990507988 --- /dev/null +++ b/components/fiserv/fiserv.app.mjs @@ -0,0 +1,11 @@ +export default { + type: "app", + app: "fiserv", + propDefinitions: {}, + methods: { + // this.$auth contains connected account data + authKeys() { + console.log(Object.keys(this.$auth)); + }, + }, +}; \ No newline at end of file diff --git a/components/fiserv/package.json b/components/fiserv/package.json new file mode 100644 index 0000000000000..818f823ed6bad --- /dev/null +++ b/components/fiserv/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pipedream/fiserv", + "version": "0.0.1", + "description": "Pipedream Fiserv Components", + "main": "fiserv.app.mjs", + "keywords": [ + "pipedream", + "fiserv" + ], + "homepage": "https://pipedream.com/apps/fiserv", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/components/flexisign/actions/send-document-using-template/send-document-using-template.mjs b/components/flexisign/actions/send-document-using-template/send-document-using-template.mjs new file mode 100644 index 0000000000000..7b756883e9b9c --- /dev/null +++ b/components/flexisign/actions/send-document-using-template/send-document-using-template.mjs @@ -0,0 +1,67 @@ +import { snakeCaseToTitleCase } from "../../common/utils.mjs"; +import flexisign from "../../flexisign.app.mjs"; + +export default { + key: "flexisign-send-document-using-template", + name: "Send Document Using Template", + description: "Sends a signature request to the specified recipients for a document generated from a template. [See the documentation](https://flexisign.io/app/integrations/flexisignapi)", + version: "0.0.1", + type: "action", + props: { + flexisign, + templateId: { + propDefinition: [ + flexisign, + "templateId", + ], + reloadProps: true, + }, + }, + async additionalProps() { + const props = {}; + if (this.templateId) { + const { data: { bodyStructure } } = await this.flexisign.getTemplateDetails({ + params: { + templateId: this.templateId, + }, + }); + + for (const [ + key, + value, + ] of Object.entries(bodyStructure)) { + if ([ + "templateId", + "recipientsCount", + ].includes(key)) continue; + + const title = snakeCaseToTitleCase(key); + props[key] = { + type: typeof value === "number" + ? "integer" + : "string", + label: title, + description: title, + default: typeof value === "number" + ? value + : undefined, + }; + } + } + return props; + }, + async run({ $ }) { + const { + flexisign, + ...data + } = this; + + const response = await flexisign.sendSignatureRequest({ + $, + data, + }); + + $.export("$summary", `Signature request sent for template ID: ${this.templateId}`); + return response; + }, +}; diff --git a/components/flexisign/common/utils.mjs b/components/flexisign/common/utils.mjs new file mode 100644 index 0000000000000..8b11b6b3ecf24 --- /dev/null +++ b/components/flexisign/common/utils.mjs @@ -0,0 +1,4 @@ +export const snakeCaseToTitleCase = (s) => + s.replace(/^_*(.)|_+(.)/g, (s, c, d) => c + ? c.toUpperCase() + : " " + d.toUpperCase()); diff --git a/components/flexisign/flexisign.app.mjs b/components/flexisign/flexisign.app.mjs new file mode 100644 index 0000000000000..6508323c4305f --- /dev/null +++ b/components/flexisign/flexisign.app.mjs @@ -0,0 +1,60 @@ +import { axios } from "@pipedream/platform"; + +export default { + type: "app", + app: "flexisign", + propDefinitions: { + templateId: { + type: "string", + label: "Template ID", + description: "The ID of the template to generate the document from", + async options() { + const { data: { list } } = await this.listTemplates(); + return list.map(({ + _id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + }, + methods: { + _baseUrl() { + return "https://api.flexisign.io/v1"; + }, + _headers() { + return { + "api-key": `${this.$auth.api_key}`, + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + headers: this._headers(), + ...opts, + }); + }, + listTemplates(opts = {}) { + return this._makeRequest({ + path: "/templates/all", + ...opts, + }); + }, + getTemplateDetails(opts = {}) { + return this._makeRequest({ + path: "/template", + ...opts, + }); + }, + sendSignatureRequest(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/template/create-document", + ...opts, + }); + }, + }, +}; diff --git a/components/flexisign/package.json b/components/flexisign/package.json new file mode 100644 index 0000000000000..1ab1776a0f90f --- /dev/null +++ b/components/flexisign/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/flexisign", + "version": "0.1.0", + "description": "Pipedream FlexiSign Components", + "main": "flexisign.app.mjs", + "keywords": [ + "pipedream", + "flexisign" + ], + "homepage": "https://pipedream.com/apps/flexisign", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" + } +} diff --git a/components/gainsight_nxt/actions/create-or-update-company/create-or-update-company.mjs b/components/gainsight_nxt/actions/create-or-update-company/create-or-update-company.mjs new file mode 100644 index 0000000000000..9e0f536672c31 --- /dev/null +++ b/components/gainsight_nxt/actions/create-or-update-company/create-or-update-company.mjs @@ -0,0 +1,133 @@ +import { parseObjectEntries } from "../../common/utils.mjs"; +import app from "../../gainsight_nxt.app.mjs"; + +export default { + key: "gainsight_nxt-create-or-update-company", + name: "Create or Update Company", + description: "Create or update a company record. [See the documentation](https://support.gainsight.com/gainsight_nxt/API_and_Developer_Docs/Company_and_Relationship_API/Company_API_Documentation#Parameters)", + version: "0.0.1", + type: "action", + props: { + app, + name: { + type: "string", + label: "Name", + description: "The name of the company. If a company record with this name exists, it will be updated, otherwise a new one will be created.", + }, + industry: { + type: "string", + label: "Industry", + description: "The industry name of the company.", + optional: true, + }, + arr: { + type: "string", + label: "Annual Recurring Revenue (ARR)", + description: "The annual recurring revenue of the company, as a currency value.", + optional: true, + }, + employees: { + type: "integer", + label: "Employees", + description: "The number of employees the company has.", + optional: true, + }, + lifecycleInWeeks: { + type: "integer", + label: "Life Cycle in Weeks", + description: "The number of weeks the entire process goes through.", + optional: true, + }, + originalContractDate: { + type: "string", + label: "Original Contract Date", + description: "The date the engagement with the client started, in `YYYY-MM-DD` format.", + optional: true, + }, + renewalDate: { + type: "string", + label: "Renewal Date", + description: "The upcoming renewal date of the contract, in `YYYY-MM-DD` format.", + optional: true, + }, + stage: { + type: "string", + label: "Stage", + description: "The current stage of the company in the sales pipeline.", + optional: true, + options: [ + "New Customer", + "Kicked Off", + "Launched", + "Adopting", + "Will Churn", + "Churn", + ], + }, + status: { + type: "string", + label: "Status", + description: "The current status of the company.", + optional: true, + options: [ + "Active", + "Inactive", + "Churn", + ], + }, + additionalOptions: { + type: "object", + label: "Additional Options", + description: + "Additional parameters to send in the request. [See the documentation](https://support.gainsight.com/gainsight_nxt/API_and_Developer_Docs/Company_and_Relationship_API/Company_API_Documentation#Parameters) for available parameters. Values will be parsed as JSON where applicable.", + optional: true, + }, + }, + async run({ $ }) { + const data = { + records: [ + { + Name: this.name, + Industry: this.industry, + ARR: this.arr, + Employees: this.employees, + LifecycleInWeeks: this.lifecycleInWeeks, + OriginalContractDate: this.originalContractDate, + RenewalDate: this.renewalDate, + Stage: this.stage, + Status: this.status, + ...(this.additionalOptions && parseObjectEntries(this.additionalOptions)), + }, + ], + }; + + let summary = ""; + let result; + try { + const updateReq = await this.app.updateCompany({ + $, + data, + }); + result = updateReq; + summary = updateReq.result === true + ? `Successfully updated company '${this.name}'` + : `Error updating company '${this.name}'`; + } + catch (err) { + const createReq = await this.app.createCompany({ + $, + data, + }); + result = createReq; + summary = createReq.result === true + ? `Successfully created company '${this.name}'` + : `Error creating company '${this.name}'`; + } + + $.export( + "$summary", + summary, + ); + return result; + }, +}; diff --git a/components/gainsight_nxt/actions/create-or-update-custom-object/create-or-update-custom-object.mjs b/components/gainsight_nxt/actions/create-or-update-custom-object/create-or-update-custom-object.mjs new file mode 100644 index 0000000000000..f097bcae5e394 --- /dev/null +++ b/components/gainsight_nxt/actions/create-or-update-custom-object/create-or-update-custom-object.mjs @@ -0,0 +1,73 @@ +import app from "../../gainsight_nxt.app.mjs"; + +export default { + key: "gainsight_nxt-create-or-update-custom-object", + name: "Create or Update Custom Object", + description: "Create or update a custom object record. [See the documentation](https://support.gainsight.com/gainsight_nxt/API_and_Developer_Docs/Custom_Object_API/Gainsight_Custom_Object_API_Documentation#Insert_API)", + version: "0.0.1", + type: "action", + props: { + app, + objectName: { + propDefinition: [ + app, + "objectName", + ], + }, + infoBox: { + type: "alert", + alertType: "info", + content: "Custom object fields may be suffixed with `__gc`, e.g. if you've named your field \"Object Name\", its key would be `Object_Name__gc`. Check the object configuration in the Gainsight platform for the correct field names.", + }, + fields: { + type: "string[]", + label: "Key Field(s)", + description: "The field(s) which identify this object (max 3), e.g. `fieldName1`. If a record with the same key field(s) exists, it will be updated, otherwise a new one will be created.", + }, + fieldValues: { + type: "object", + label: "Field Values", + description: "The record data to create or update, as key-value pairs.", + }, + }, + async run({ $ }) { + const { objectName } = this; + const data = { + records: [ + this.fieldValues, + ], + }; + + let summary = ""; + let result; + try { + result = await this.app.updateCustomObject({ + $, + objectName, + data, + params: { + keys: this.fields.join?.() ?? this.fields, + }, + }); + summary = result.result === true + ? `Successfully updated custom object ${objectName}` + : `Error updating custom object ${objectName}`; + } + catch (err) { + result = await this.app.createCustomObject({ + $, + objectName, + data, + }); + summary = result.result === true + ? `Successfully created custom object ${objectName}` + : `Error creating custom object ${objectName}`; + } + + $.export( + "$summary", + summary, + ); + return result; + }, +}; diff --git a/components/gainsight_nxt/actions/create-or-update-person/create-or-update-person.mjs b/components/gainsight_nxt/actions/create-or-update-person/create-or-update-person.mjs new file mode 100644 index 0000000000000..64042f8158fb2 --- /dev/null +++ b/components/gainsight_nxt/actions/create-or-update-person/create-or-update-person.mjs @@ -0,0 +1,63 @@ +import app from "../../gainsight_nxt.app.mjs"; + +export default { + key: "gainsight_nxt-create-or-update-person", + name: "Create or Update Person", + description: "Create or update a person's record. [See the documentation](https://support.gainsight.com/gainsight_nxt/API_and_Developer_Docs/Person_API/People_API_Documentation#Person)", + version: "0.0.1", + type: "action", + props: { + app, + email: { + type: "string", + label: "Email", + description: "The email address of the person. If a record with this email exists, it will be updated, otherwise a new one will be created.", + }, + firstName: { + type: "string", + label: "First Name", + description: "The first name of the person.", + optional: true, + }, + lastName: { + type: "string", + label: "Last Name", + description: "The last name of the person.", + optional: true, + }, + linkedinUrl: { + type: "string", + label: "LinkedIn URL", + description: "The LinkedIn URL of the person.", + optional: true, + }, + location: { + type: "string", + label: "Location", + description: "The location of the person.", + optional: true, + }, + additionalFields: { + type: "object", + label: "Additional Fields", + description: "Additional fields to include in the request body. [See the documentation](https://support.gainsight.com/gainsight_nxt/API_and_Developer_Docs/Person_API/People_API_Documentation#Person) for all available fields.", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.app.createOrUpdatePerson({ + $, + data: { + Email: this.email, + FirstName: this.firstName, + LastName: this.lastName, + LinkedinUrl: this.linkedinUrl, + Location: this.location, + ...this.additionalFields, + }, + }); + + $.export("$summary", `Successfully upserted person with email ${this.email}`); + return response; + }, +}; diff --git a/components/gainsight_nxt/common/utils.mjs b/components/gainsight_nxt/common/utils.mjs new file mode 100644 index 0000000000000..9c3cdbf569744 --- /dev/null +++ b/components/gainsight_nxt/common/utils.mjs @@ -0,0 +1,22 @@ +function optionalParseAsJSON(value) { + try { + return JSON.parse(value); + } catch (e) { + return value; + } +} + +export function parseObjectEntries(value) { + const obj = typeof value === "string" + ? JSON.parse(value) + : value; + return Object.fromEntries( + Object.entries(obj).map(([ + key, + value, + ]) => [ + key, + optionalParseAsJSON(value), + ]), + ); +} diff --git a/components/gainsight_nxt/gainsight_nxt.app.mjs b/components/gainsight_nxt/gainsight_nxt.app.mjs index 7e2c5ff525189..aef6c222e128a 100644 --- a/components/gainsight_nxt/gainsight_nxt.app.mjs +++ b/components/gainsight_nxt/gainsight_nxt.app.mjs @@ -1,11 +1,92 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "gainsight_nxt", - propDefinitions: {}, + propDefinitions: { + objectName: { + type: "string", + label: "Custom Object", + description: "The name of the custom object.", + async options() { + const { data } = await this.listCustomObjects(); + return data?.filter?.((obj) => obj.objectType === "CUSTOM").map(( { + label, objectName, + }) => ({ + label, + value: objectName, + })); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return `${this.$auth.customer_domain}/v1`; + }, + async _makeRequest({ + $ = this, + path, + headers = {}, + ...otherOpts + } = {}) { + return axios($, { + ...otherOpts, + url: this._baseUrl() + path, + headers: { + "content-type": "application/json", + "accept": "application/json, text/plain, */*", + "accept-language": "en-GB,en-US;q=0.9,en;q=0.8", + "accesskey": `${this.$auth.access_key}`, + ...headers, + }, + }); + }, + async updateCompany(args) { + return this._makeRequest({ + path: "/data/objects/Company", + method: "PUT", + params: { + keys: "Name", + }, + ...args, + }); + }, + async createCompany(args) { + return this._makeRequest({ + path: "/data/objects/Company", + method: "POST", + ...args, + }); + }, + async createOrUpdatePerson(args) { + return this._makeRequest({ + path: "/peoplemgmt/v1.0/people", + method: "PUT", + ...args, + }); + }, + async listCustomObjects() { + return this._makeRequest({ + path: "/meta/services/objects/list", + }); + }, + async updateCustomObject({ + objectName, ...args + }) { + return this._makeRequest({ + path: `/data/objects/${objectName}`, + method: "PUT", + ...args, + }); + }, + async createCustomObject({ + objectName, ...args + }) { + return this._makeRequest({ + path: `/data/objects/${objectName}`, + method: "POST", + ...args, + }); }, }, }; diff --git a/components/gainsight_nxt/package.json b/components/gainsight_nxt/package.json index af6a3f4ee80c8..567d30336b6b0 100644 --- a/components/gainsight_nxt/package.json +++ b/components/gainsight_nxt/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/gainsight_nxt", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Gainsight Components", "main": "gainsight_nxt.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } -} \ No newline at end of file +} diff --git a/components/gainsight_px/actions/create-account/create-account.mjs b/components/gainsight_px/actions/create-account/create-account.mjs new file mode 100644 index 0000000000000..02d48e1979306 --- /dev/null +++ b/components/gainsight_px/actions/create-account/create-account.mjs @@ -0,0 +1,68 @@ +import app from "../../gainsight_px.app.mjs"; + +export default { + key: "gainsight_px-create-account", + name: "Create Account", + description: "Create a new account with the given data. [See the documentation](https://gainsightpx.docs.apiary.io/#reference/accounts/v1accounts/create-account)", + version: "0.0.1", + type: "action", + props: { + app, + id: { + propDefinition: [ + app, + "id", + ], + }, + name: { + propDefinition: [ + app, + "name", + ], + }, + propertyKeys: { + propDefinition: [ + app, + "propertyKeys", + ], + }, + countryName: { + propDefinition: [ + app, + "countryName", + ], + }, + stateName: { + propDefinition: [ + app, + "stateName", + ], + }, + city: { + propDefinition: [ + app, + "city", + ], + }, + }, + + async run({ $ }) { + const response = await this.app.createAccount({ + $, + data: { + id: this.id, + name: this.name, + propertyKeys: this.propertyKeys, + location: { + countryName: this.countryName, + stateName: this.stateName, + city: this.city, + }, + }, + }); + + $.export("$summary", `Successfully created account with the name '${this.name}'`); + + return response; + }, +}; diff --git a/components/gainsight_px/actions/create-user/create-user.mjs b/components/gainsight_px/actions/create-user/create-user.mjs new file mode 100644 index 0000000000000..1112a0987d22f --- /dev/null +++ b/components/gainsight_px/actions/create-user/create-user.mjs @@ -0,0 +1,68 @@ +import app from "../../gainsight_px.app.mjs"; + +export default { + key: "gainsight_px-create-user", + name: "Create User", + description: "Creates a new user with the given data. [See the documentation](https://gainsightpx.docs.apiary.io/#reference/users/v1users/create-user)", + version: "0.0.1", + type: "action", + props: { + app, + id: { + propDefinition: [ + app, + "id", + ], + label: "Identify ID", + description: "Identifier of the user", + }, + propertyKeys: { + propDefinition: [ + app, + "propertyKeys", + ], + }, + type: { + propDefinition: [ + app, + "type", + ], + }, + email: { + propDefinition: [ + app, + "email", + ], + }, + firstName: { + propDefinition: [ + app, + "firstName", + ], + }, + lastName: { + propDefinition: [ + app, + "lastName", + ], + }, + }, + + async run({ $ }) { + const response = await this.app.createUser({ + $, + data: { + identifyId: this.id, + propertyKeys: this.propertyKeys, + type: this.type, + email: this.email, + firstName: this.firstName, + lastName: this.lastName, + }, + }); + + $.export("$summary", `Successfully created user with ID '${this.id}'`); + + return response; + }, +}; diff --git a/components/gainsight_px/actions/delete-user/delete-user.mjs b/components/gainsight_px/actions/delete-user/delete-user.mjs new file mode 100644 index 0000000000000..2e5471c93770f --- /dev/null +++ b/components/gainsight_px/actions/delete-user/delete-user.mjs @@ -0,0 +1,31 @@ +import app from "../../gainsight_px.app.mjs"; + +export default { + key: "gainsight_px-delete-user", + name: "Delete User", + description: "Deletes a user with he specified identifyId. [See the documentation](https://gainsightpx.docs.apiary.io/#reference/users/v1usersdelete/delete-user)", + version: "0.0.1", + type: "action", + props: { + app, + identifyId: { + propDefinition: [ + app, + "identifyId", + ], + }, + }, + + async run({ $ }) { + const response = await this.app.deleteUser({ + $, + data: { + identifyId: this.identifyId, + }, + }); + + $.export("$summary", `Successfully deleted user with ID ${this.identifyId}`); + + return response; + }, +}; diff --git a/components/gainsight_px/common/contants.mjs b/components/gainsight_px/common/contants.mjs new file mode 100644 index 0000000000000..b642cd32c903a --- /dev/null +++ b/components/gainsight_px/common/contants.mjs @@ -0,0 +1,8 @@ +export default { + USER_TYPES: [ + "LEAD", + "USER", + "VISITOR", + "EMPTY_USER_TYPE", + ], +}; diff --git a/components/gainsight_px/gainsight_px.app.mjs b/components/gainsight_px/gainsight_px.app.mjs index a871bea2efb9a..ba14f54206449 100644 --- a/components/gainsight_px/gainsight_px.app.mjs +++ b/components/gainsight_px/gainsight_px.app.mjs @@ -1,11 +1,131 @@ +import { axios } from "@pipedream/platform"; +import contants from "./common/contants.mjs"; + export default { type: "app", app: "gainsight_px", - propDefinitions: {}, + propDefinitions: { + id: { + type: "string", + label: "ID", + description: "Unique identifier for the account", + }, + name: { + type: "string", + label: "Name", + description: "Name associated with the account", + }, + propertyKeys: { + type: "string[]", + label: "Property Keys", + description: "At least one tag key. The key can be found by clicking on `Administration` >`Set Up` > `Products` > Tag Key. For example: AP-xxx-1", + }, + countryName: { + type: "string", + label: "County Name", + description: "Name of the country associated with the account", + optional: true, + }, + stateName: { + type: "string", + label: "State Name", + description: "Name of the State associated with the account", + optional: true, + }, + city: { + type: "string", + label: "City", + description: "City associated with the account", + optional: true, + }, + identifyId: { + type: "string", + label: "Identify ID", + description: "Identifier of the user", + async options() { + const response = await this.listUsers(); + const userIds = response.users; + return userIds.map(({ + identifyId, email, + }) => ({ + label: email, + value: identifyId, + })); + }, + }, + type: { + type: "string", + label: "User Type", + description: "Type of the user", + options: contants.USER_TYPES, + optional: true, + }, + email: { + type: "string", + label: "Email", + description: "Email of the user", + optional: true, + }, + firstName: { + type: "string", + label: "First Name", + description: "First Name of the user", + optional: true, + }, + lastName: { + type: "string", + label: "Last Name", + description: "Last Name of the user", + optional: true, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return this.$auth.base_endpoint; + }, + async _makeRequest(opts = {}) { + const { + $ = this, + path, + headers, + ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + url: this._baseUrl() + path, + headers: { + ...headers, + "X-APTRINSIC-API-KEY": `${this.$auth.api_key}`, + "Accept": "application/json", + }, + }); + }, + async createAccount(args = {}) { + return this._makeRequest({ + path: "/accounts", + method: "post", + ...args, + }); + }, + async deleteUser(args = {}) { + return this._makeRequest({ + path: "/users/delete", + method: "delete", + ...args, + }); + }, + async createUser(args = {}) { + return this._makeRequest({ + path: "/users", + method: "post", + ...args, + }); + }, + async listUsers(args = {}) { + return this._makeRequest({ + path: "/users", + ...args, + }); }, }, }; diff --git a/components/gainsight_px/package.json b/components/gainsight_px/package.json index d206532557469..0e0afb17bbd18 100644 --- a/components/gainsight_px/package.json +++ b/components/gainsight_px/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/gainsight_px", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Gainsight PX Components", "main": "gainsight_px.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } -} \ No newline at end of file +} diff --git a/components/github/README.md b/components/github/README.md index 68ae6146a886c..e35fe21d8ba4e 100644 --- a/components/github/README.md +++ b/components/github/README.md @@ -74,7 +74,7 @@ Workflows are a sequence of linear [steps](https://pipedream.com/docs/workflows/ - [New Review Request](https://pipedream.com/new?h=eyJuIjoiTmV3IFJldmlldyBSZXF1ZXN0IHdpdGggdGhlIEdpdEh1YiBBUEkiLCJ2IjoyLCJ0IjpbInNjX01laUU2MFEiXSwicyI6W10sImMiOnt9fQ) - Triggers an event when you or a team you're a member of are requested to review a pull request. ([code](https://github.com/PipedreamHQ/pipedream/blob/master/components/github/sources/new-review-request/new-review-request.mjs)) - [New Security Alert](https://pipedream.com/new?h=eyJuIjoiTmV3IFNlY3VyaXR5IEFsZXJ0IHdpdGggdGhlIEdpdEh1YiBBUEkiLCJ2IjoyLCJ0IjpbInNjX0I1aXdWZTQiXSwicyI6W10sImMiOnt9fQ) - Triggers an event when GitHub discovers a security vulnerability in one of your repositories. ([code](https://github.com/PipedreamHQ/pipedream/blob/master/components/github/sources/new-security-alert/new-security-alert.mjs)) - [New Star By User](https://pipedream.com/new?h=eyJuIjoiTmV3IFN0YXIgQnkgVXNlciB3aXRoIHRoZSBHaXRIdWIgQVBJIiwidiI6MiwidCI6WyJzY18xTGlLbEJkIl0sInMiOltdLCJjIjp7fX0) - Triggers an event when the specified user stars a repository. ([code](https://github.com/PipedreamHQ/pipedream/blob/master/components/github/sources/new-star-by-user/new-star-by-user.mjs)) -- [New Stars](https://pipedream.com/new?h=eyJuIjoiTmV3IFN0YXJzIHdpdGggdGhlIEdpdEh1YiBBUEkiLCJ2IjoyLCJ0IjpbInNjX3gwaXB5VlciXSwicyI6W10sImMiOnt9fQ) - Triggers an event when a repository is starred. ([code](https://github.com/PipedreamHQ/pipedream/blob/master/components/github/sources/new-stars/new-stars.mjs)) +- [New Star](https://pipedream.com/new?h=eyJuIjoiTmV3IFN0YXJzIHdpdGggdGhlIEdpdEh1YiBBUEkiLCJ2IjoyLCJ0IjpbInNjX3gwaXB5VlciXSwicyI6W10sImMiOnt9fQ) - Triggers an event when a repository is starred. ([code](https://github.com/PipedreamHQ/pipedream/blob/master/components/github/sources/new-star/new-star.mjs)) - [New Team](https://pipedream.com/new?h=eyJuIjoiTmV3IFRlYW0gd2l0aCB0aGUgR2l0SHViIEFQSSIsInYiOjIsInQiOlsic2NfRHBpV3ZPSyJdLCJzIjpbXSwiYyI6e319) - Triggers an event when the user is added to a new team. ([code](https://github.com/PipedreamHQ/pipedream/blob/master/components/github/sources/new-team/new-team.mjs)) - [New Webhook Event (Instant)](https://pipedream.com/new?h=eyJuIjoiTmV3IFdlYmhvb2sgRXZlbnQgKEluc3RhbnQpIHdpdGggdGhlIEdpdEh1YiBBUEkiLCJ2IjoyLCJ0IjpbInNjXzhuaWE1TVkiXSwicyI6W10sImMiOnt9fQ) - Triggers an event for each selected event type. ([code](https://github.com/PipedreamHQ/pipedream/blob/master/components/github/sources/webhook-events/webhook-events.mjs)) diff --git a/components/gocanvas/actions/create-dispatch/create-dispatch.mjs b/components/gocanvas/actions/create-dispatch/create-dispatch.mjs new file mode 100644 index 0000000000000..cc21e5d520f90 --- /dev/null +++ b/components/gocanvas/actions/create-dispatch/create-dispatch.mjs @@ -0,0 +1,49 @@ +import gocanvas from "../../gocanvas.app.mjs"; + +export default { + key: "gocanvas-create-dispatch", + name: "Create Dispatch", + description: "Creates a dispatch item in GoCanvas. [See the documentation](https://help.gocanvas.com/hc/en-us/article_attachments/26468076609559)", + version: "0.0.1", + type: "action", + props: { + gocanvas, + form: { + propDefinition: [ + gocanvas, + "form", + ], + description: "The name of the form you want to create a prepopulated submission for", + }, + entries: { + type: "object", + label: "Entries", + description: `DIEntry elements consisting of label/value pairs. + \n Label: Either the Export Label or plain Label field attribute (caseinsensitive) as defined in the form builder. + \n Value: The value assigned to this Dispatch Item entry. + `, + }, + }, + async run({ $ }) { + let entriesString = ""; + for (const [ + key, + value, + ] of Object.entries(this.entries)) { + entriesString += ``; + } + const response = await this.gocanvas.dispatchItems({ + $, + data: ` + + + + ${entriesString} + + + `, + }); + $.export("$summary", `Successfully created dispatch item in ${this.form}`); + return response; + }, +}; diff --git a/components/gocanvas/actions/create-or-update-reference-data/create-or-update-reference-data.mjs b/components/gocanvas/actions/create-or-update-reference-data/create-or-update-reference-data.mjs new file mode 100644 index 0000000000000..3280cc055cde4 --- /dev/null +++ b/components/gocanvas/actions/create-or-update-reference-data/create-or-update-reference-data.mjs @@ -0,0 +1,71 @@ +import gocanvas from "../../gocanvas.app.mjs"; +import { parse } from "csv-parse/sync"; +import { ConfigurationError } from "@pipedream/platform"; + +export default { + key: "gocanvas-create-or-update-reference-data", + name: "Create or Update Reference Data", + description: "Creates or updates GoCanvas reference data. [See the documentation](https://help.gocanvas.com/hc/en-us/article_attachments/26468076609559)", + version: "0.0.1", + type: "action", + props: { + gocanvas, + name: { + type: "string", + label: "Reference Data Name", + description: "The attribute name of the dataset to operate on. Will be created if it doesn't already exist.", + }, + data: { + type: "string", + label: "Data", + description: `A string of comma separated values representing the data to create/update. **Include Column names**: + \n Example: + \n Column1,Column2,Column3 + \n Data1Column1,Data1Column2,Data1Column3 + \n Data2Column1,Data2Column2,Data3Column3 + `, + }, + }, + methods: { + csvToXml(data) { + const records = parse(data, { + columns: true, + trim: true, + }); + + if (!records?.length) { + throw new ConfigurationError("No data items found to create/update. Please enter column names and at least 1 row of data."); + } + + // Extract columns + const columns = Object.keys(records[0]); + let result = ""; + result += columns.map((col) => `${col}`).join(""); + result += "\n\n"; + + // Extract rows + result += records + .map((row) => { + const rowValues = columns.map((col) => `${row[col]}`).join(""); + return ` ${rowValues}`; + }) + .join("\n"); + + result += "\n"; + return result; + }, + }, + async run({ $ }) { + const response = await this.gocanvas.createUpdateReferenceData({ + $, + data: ` + + + ${await this.csvToXml(this.data)} + + `, + }); + $.export("$summary", "Successfully created/updated reference data"); + return response; + }, +}; diff --git a/components/gocanvas/actions/delete-dispatch/delete-dispatch.mjs b/components/gocanvas/actions/delete-dispatch/delete-dispatch.mjs new file mode 100644 index 0000000000000..ddafebef637d6 --- /dev/null +++ b/components/gocanvas/actions/delete-dispatch/delete-dispatch.mjs @@ -0,0 +1,46 @@ +import gocanvas from "../../gocanvas.app.mjs"; + +export default { + key: "gocanvas-delete-dispatch", + name: "Delete Dispatch", + description: "Removes a specific dispatch from GoCanvas. [See the documentation](https://help.gocanvas.com/hc/en-us/article_attachments/26468076609559)", + version: "0.0.1", + type: "action", + props: { + gocanvas, + form: { + propDefinition: [ + gocanvas, + "form", + ], + }, + dispatchId: { + propDefinition: [ + gocanvas, + "dispatchId", + (c) => ({ + form: c.form, + }), + ], + }, + }, + async run({ $ }) { + const description = await this.gocanvas.getDispatchDescription({ + $, + dispatchId: this.dispatchId, + }); + const response = await this.gocanvas.dispatchItems({ + $, + data: ` + + + + + + + `, + }); + $.export("$summary", `Successfully deleted dispatch with ID ${this.dispatchId}`); + return response; + }, +}; diff --git a/components/gocanvas/gocanvas.app.mjs b/components/gocanvas/gocanvas.app.mjs index 45e1ba69a4cb3..40ec3505be34a 100644 --- a/components/gocanvas/gocanvas.app.mjs +++ b/components/gocanvas/gocanvas.app.mjs @@ -1,11 +1,151 @@ +import { axios } from "@pipedream/platform"; +import xml2js from "xml2js"; + export default { type: "app", app: "gocanvas", - propDefinitions: {}, + propDefinitions: { + form: { + type: "string", + label: "Form", + description: "The identifier of a form", + async options() { + const forms = await this.listForms(); + return forms?.map((form) => form.Name[0]) || []; + }, + }, + dispatchId: { + type: "string", + label: "Dispatch ID", + description: "Identifier of a dispatch", + async options({ + page, form, + }) { + const dispatches = await this.getActiveDispatches({ + form, + data: { + page: page + 1, + }, + }); + return dispatches?.map(({ + $, Description: desc, + }) => ({ + value: $.Id, + label: desc[0], + })) || []; + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://www.gocanvas.com/apiv2/"; + }, + _makeRequest(opts = {}) { + const { + $ = this, + path, + params, + ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + url: `${this._baseUrl()}${path}`, + params: { + ...params, + username: `${this.$auth.username}`, + }, + headers: { + Authorization: `Bearer ${this.$auth.api_key}`, + }, + }); + }, + async getActiveDispatches({ + form, ...opts + }) { + const response = await this._makeRequest({ + path: "/dispatch_export", + ...opts, + }); + const { CanvasResult: { Dispatches: dispatches } } = await new xml2js + .Parser().parseStringPromise(response); + if (!dispatches?.length) { + return []; + } + return dispatches + .flatMap((d) => d.Dispatch || []) + .filter((d) => d.Status[0] !== "deleted") + .filter((d) => !form || d.Form[0] === form); + }, + async listForms(opts = {}) { + const response = await this._makeRequest({ + path: "/forms", + ...opts, + }); + const { CanvasResult: { Forms: forms } } = await new xml2js + .Parser().parseStringPromise(response); + if (!forms?.length) { + return []; + } + return forms.flatMap((form) => form.Form || []); + }, + async listSubmissions(opts = {}) { + const response = await this._makeRequest({ + path: "/submissions", + ...opts, + }); + const { CanvasResult: { Submissions: submissions } } = await new xml2js + .Parser().parseStringPromise(response); + if (!submissions?.length) { + return []; + } + return submissions.flatMap((sub) => sub.Submission || []); + }, + async getDispatchDescription({ + dispatchId, ...opts + }) { + const dispatches = await this.getActiveDispatches({ + ...opts, + }); + const dispatch = dispatches.find(({ $ }) => $.Id === dispatchId); + return dispatch.Description[0]; + }, + dispatchItems(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/dispatch_items", + ...opts, + }); + }, + createUpdateReferenceData(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/reference_datas", + ...opts, + }); + }, + async *paginate({ + fn, + params, + max, + }) { + params = { + ...params, + page: 1, + }; + let total, count = 0; + do { + const results = await fn({ + params, + }); + for (const item of results) { + yield item; + if (max && ++count >= max) { + return; + } + } + total = results?.length; + params.page++; + } while (total); }, }, }; diff --git a/components/gocanvas/package.json b/components/gocanvas/package.json new file mode 100644 index 0000000000000..d5437280de080 --- /dev/null +++ b/components/gocanvas/package.json @@ -0,0 +1,20 @@ +{ + "name": "@pipedream/gocanvas", + "version": "0.0.1", + "description": "Pipedream GoCanvas Components", + "main": "gocanvas.app.mjs", + "keywords": [ + "pipedream", + "gocanvas" + ], + "homepage": "https://pipedream.com/apps/gocanvas", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3", + "csv-parse": "^5.5.6", + "xml2js": "^0.6.2" + } +} diff --git a/components/gocanvas/sources/new-submission-received/new-submission-received.mjs b/components/gocanvas/sources/new-submission-received/new-submission-received.mjs new file mode 100644 index 0000000000000..5b655fb8f18df --- /dev/null +++ b/components/gocanvas/sources/new-submission-received/new-submission-received.mjs @@ -0,0 +1,103 @@ +import gocanvas from "../../gocanvas.app.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + key: "gocanvas-new-submission-received", + name: "New Submission Recieved", + description: "Emit new event when a new submission is uploaded to GoCanvas.", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + gocanvas, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + form: { + propDefinition: [ + gocanvas, + "form", + ], + }, + }, + hooks: { + async deploy() { + await this.processEvent(25); + }, + }, + methods: { + _getLastSubmissionDate() { + return this.db.get("lastSubmissionDate"); + }, + _setLastSubmissionDate(lastSubmissionDate) { + this.db.set("lastSubmissionDate", lastSubmissionDate); + }, + generateMeta(submission) { + return { + id: submission.ResponseID, + summary: `New Submission: ${submission.ResponseID}`, + ts: Date.parse(submission.Date), + }; + }, + currentDate() { + const currentDate = new Date(); + return `${String(currentDate.getMonth() + 1) + .padStart(2, "0")}/${String(currentDate.getDate()) + .padStart(2, "0")}/${currentDate.getFullYear()}`; + }, + formatResponse(obj) { + if (Array.isArray(obj) && obj.length === 1) { + return this.formatResponse(obj[0]); + } else if (typeof obj === "object" && obj !== null) { + return Object.fromEntries( + Object.entries(obj).map(([ + key, + value, + ]) => [ + key, + this.formatResponse(value), + ]), + ); + } else { + return obj; + } + }, + async processEvent(max) { + let lastSubmissionDate = this._getLastSubmissionDate(); + + const params = { + form_name: this.form, + }; + if (lastSubmissionDate) { + params.begin_date = new Date(lastSubmissionDate).toLocaleDateString("en-US"); + params.end_date = this.currentDate(); + } + + const results = this.gocanvas.paginate({ + fn: this.gocanvas.listSubmissions, + params, + max, + }); + + for await (const result of results) { + const submission = this.formatResponse(result); + const meta = this.generateMeta(submission); + this.$emit(submission, meta); + + if (!lastSubmissionDate + || Date.parse(submission.Date) >= Date.parse(lastSubmissionDate)) { + lastSubmissionDate = submission.Date; + } + } + + this._setLastSubmissionDate(lastSubmissionDate); + }, + }, + async run() { + await this.processEvent(); + }, +}; diff --git a/components/google_ad_manager/google_ad_manager.app.mjs b/components/google_ad_manager/google_ad_manager.app.mjs new file mode 100644 index 0000000000000..63733ec6000d3 --- /dev/null +++ b/components/google_ad_manager/google_ad_manager.app.mjs @@ -0,0 +1,11 @@ +export default { + type: "app", + app: "google_ad_manager", + propDefinitions: {}, + methods: { + // this.$auth contains connected account data + authKeys() { + console.log(Object.keys(this.$auth)); + }, + }, +}; \ No newline at end of file diff --git a/components/google_ad_manager/package.json b/components/google_ad_manager/package.json new file mode 100644 index 0000000000000..047d8f0d5d629 --- /dev/null +++ b/components/google_ad_manager/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pipedream/google_ad_manager", + "version": "0.0.1", + "description": "Pipedream Google Ad Manager Components", + "main": "google_ad_manager.app.mjs", + "keywords": [ + "pipedream", + "google_ad_manager" + ], + "homepage": "https://pipedream.com/apps/google_ad_manager", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/components/google_sheets/actions/find-row/find-row.mjs b/components/google_sheets/actions/find-row/find-row.mjs index 735c67af9264d..f25c00d04d040 100644 --- a/components/google_sheets/actions/find-row/find-row.mjs +++ b/components/google_sheets/actions/find-row/find-row.mjs @@ -7,7 +7,7 @@ export default { key: "google_sheets-find-row", name: "Find Row", description: "Find one or more rows by a column and value. [See the documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/get)", - version: "0.2.8", + version: "0.2.9", type: "action", props: { googleSheets, @@ -64,6 +64,10 @@ export default { range: `${worksheet?.properties?.title}!${this.column}:${this.column}`, })).data.values; + if (!colValues?.length) { + return []; + } + const rows = []; const result = colValues.reduce((values, value, index) => { if (value == this.value) { @@ -74,7 +78,7 @@ export default { }); } return rows; - }); + }, []); if (!this.exportRow) { return result; diff --git a/components/google_sheets/package.json b/components/google_sheets/package.json index 061603797ee13..b33395df06fdf 100644 --- a/components/google_sheets/package.json +++ b/components/google_sheets/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/google_sheets", - "version": "0.7.9", + "version": "0.7.10", "description": "Pipedream Google_sheets Components", "main": "google_sheets.app.mjs", "keywords": [ diff --git a/components/help_scout/actions/add-note/add-note.mjs b/components/help_scout/actions/add-note/add-note.mjs new file mode 100644 index 0000000000000..ebb02f2770e0b --- /dev/null +++ b/components/help_scout/actions/add-note/add-note.mjs @@ -0,0 +1,43 @@ +import helpScout from "../../help_scout.app.mjs"; + +export default { + key: "help_scout-add-note", + name: "Add Note to Conversation", + description: "Adds a note to an existing conversation in Help Scout. [See the documentation](https://developer.helpscout.com/mailbox-api/endpoints/conversations/threads/note/)", + version: "0.0.1", + type: "action", + props: { + helpScout, + conversationId: { + propDefinition: [ + helpScout, + "conversationId", + ], + }, + text: { + propDefinition: [ + helpScout, + "text", + ], + }, + userId: { + propDefinition: [ + helpScout, + "userId", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.helpScout.addNoteToConversation({ + $, + conversationId: this.conversationId, + data: { + text: this.text, + user: this.userId, + }, + }); + $.export("$summary", `Successfully added note to conversation ID: ${this.conversationId}`); + return response; + }, +}; diff --git a/components/help_scout/actions/create-customer/create-customer.mjs b/components/help_scout/actions/create-customer/create-customer.mjs new file mode 100644 index 0000000000000..cec309bb7c831 --- /dev/null +++ b/components/help_scout/actions/create-customer/create-customer.mjs @@ -0,0 +1,205 @@ +import { ConfigurationError } from "@pipedream/platform"; +import { + GENDER_OPTIONS, + PHOTO_TYPE_OPTIONS, +} from "../../common/constants.mjs"; +import { + cleanObject, + parseObject, +} from "../../common/utils.mjs"; +import helpScout from "../../help_scout.app.mjs"; + +export default { + key: "help_scout-create-customer", + name: "Create Customer", + description: "Creates a new customer record in Help Scout. [See the documentation](https://developer.helpscout.com/mailbox-api/endpoints/customers/create/)", + version: "0.0.1", + type: "action", + props: { + helpScout, + firstName: { + type: "string", + label: "First Name", + description: "First name of the customer. When defined it must be between 1 and 40 characters.", + optional: true, + }, + lastName: { + type: "string", + label: "Last Name", + description: "Last name of the customer. When defined it must be between 1 and 40 characters.", + optional: true, + }, + phone: { + type: "string", + label: "Phone", + description: "The phone number that will be used when creating a new customer.", + optional: true, + }, + photoUrl: { + type: "string", + label: "Photo URL", + description: "URL of the customer's photo. Max length 200 characters.", + optional: true, + }, + jobTitle: { + type: "string", + label: "Job Title", + description: "Job title. Max length 60 characters.", + optional: true, + }, + photoType: { + type: "string", + label: "Photo Type", + description: "The type of photo.", + options: PHOTO_TYPE_OPTIONS, + optional: true, + }, + background: { + type: "string", + label: "Background", + description: "This is the Notes field from the user interface. Max length 200 characters.", + optional: true, + }, + location: { + type: "string", + label: "Location", + description: "Location of the customer. Max length 60 characters.", + optional: true, + }, + organization: { + type: "string", + label: "Organization", + description: "Organization. Max length 60 characters.", + optional: true, + }, + gender: { + type: "string", + label: "Gender", + description: "Gender of this customer.", + options: GENDER_OPTIONS, + optional: true, + }, + age: { + type: "string", + label: "Age", + description: "Customer's age.", + optional: true, + }, + emails: { + type: "string[]", + label: "Emails", + description: "List of email entries. **Format: {\"type\":\"home\",\"value\":\"customer@email.com\"}**. see [Create Email](https://developer.helpscout.com/mailbox-api/endpoints/customers/emails/create) for the object documentation.", + optional: true, + }, + phones: { + type: "string[]", + label: "Phones", + description: "List of phone entries. **Format: {\"type\":\"work\",\"value\":\"222-333-4444\"}**. see [Create Phone](https://developer.helpscout.com/mailbox-api/endpoints/customers/phones/create) for the object documentation.", + optional: true, + }, + chats: { + type: "string[]", + label: "Chats", + description: "List of chat entries. **Format: {\"type\":\"aim\",\"value\":\"jsprout\"}**. see [Create Chat Handle](https://developer.helpscout.com/mailbox-api/endpoints/customers/chat_handles/create) for the object documentation.", + optional: true, + }, + socialProfiles: { + type: "string[]", + label: "Social Profiles", + description: "List of social profile entries. **Format: {\"type\":\"googleplus\",\"value\":\"https://api.helpscout.net/+HelpscoutNet\"}**. see [Create Social Profile](https://developer.helpscout.com/mailbox-api/endpoints/customers/social_profiles/create) for the object documentation.", + optional: true, + }, + websites: { + type: "string[]", + label: "Websites", + description: "List of websites entries. **Format: {\"value\":\"https://api.helpscout.net/\"}**. see [Create Website](https://developer.helpscout.com/mailbox-api/endpoints/customers/websites/create) for the object documentation.", + optional: true, + }, + addressCity: { + type: "string", + label: "Address City", + description: "The city of the customer.", + optional: true, + }, + addressState: { + type: "string", + label: "Address State", + description: "The state of the customer.", + optional: true, + }, + addressPostalCode: { + type: "string", + label: "Address Postal Code", + description: "The postal code of the customer.", + optional: true, + }, + addressCountry: { + type: "string", + label: "Address Country", + description: "The [ISO 3166 Alpha-2 code](https://www.iban.com/country-codes) country of the customer.", + optional: true, + }, + addressLines: { + type: "string[]", + label: "Address Lines", + description: "A list of address lines.", + optional: true, + }, + properties: { + type: "string[]", + label: "Properties", + description: "List of social profile entries. **Format: {\"type\":\"googleplus\",\"value\":\"https://api.helpscout.net/+HelpscoutNet\"}**. see [Create Social Profile](https://developer.helpscout.com/mailbox-api/endpoints/customers/social_profiles/create) for the object documentation.", + optional: true, + }, + }, + async run({ $ }) { + const address = cleanObject({ + city: this.addressCity, + state: this.addressState, + postalCode: this.addressPostalCode, + country: this.addressCountry, + lines: parseObject(this.addressLines), + properties: parseObject(this.properties), + }); + + let data = {}; + + data = cleanObject({ + firstName: this.firstName, + lastName: this.lastName, + phone: this.phone, + photoUrl: this.photoUrl, + jobTitle: this.jobTitle, + photoType: this.photoType, + background: this.background, + location: this.location, + organization: this.organization, + gender: this.gender, + age: this.age, + emails: parseObject(this.emails), + phones: parseObject(this.phones), + chats: parseObject(this.chats), + socialProfiles: parseObject(this.socialProfiles), + websites: parseObject(this.websites), + }); + + if (Object.keys(address).length) data.address = address; + + if (!Object.keys(data).length) { + throw new ConfigurationError("At least one field or customer entry must be defined."); + } + + try { + const response = await this.helpScout.createCustomer({ + $, + data, + }); + + $.export("$summary", "Successfully created the new customer."); + return response; + } catch ({ message }) { + const error = JSON.parse(message)._embedded.errors[0]; + throw new ConfigurationError(`Path: ${error.path} - ${error.message}`); + } + }, +}; diff --git a/components/help_scout/actions/send-reply/send-reply.mjs b/components/help_scout/actions/send-reply/send-reply.mjs new file mode 100644 index 0000000000000..61673ae81a8b5 --- /dev/null +++ b/components/help_scout/actions/send-reply/send-reply.mjs @@ -0,0 +1,53 @@ +import helpScout from "../../help_scout.app.mjs"; + +export default { + key: "help_scout-send-reply", + name: "Send Reply", + description: "Sends a reply to a conversation. Be careful as this sends an actual email to the customer. [See the documentation](https://developer.helpscout.com/mailbox-api/endpoints/conversations/threads/reply/)", + version: "0.0.1", + type: "action", + props: { + helpScout, + conversationId: { + propDefinition: [ + helpScout, + "conversationId", + ], + }, + customerId: { + propDefinition: [ + helpScout, + "customerId", + ], + }, + text: { + propDefinition: [ + helpScout, + "text", + ], + description: "The content of the reply.", + }, + draft: { + type: "boolean", + label: "Draft", + description: "If set to true, a draft reply is created.", + default: false, + }, + }, + async run({ $ }) { + const response = await this.helpScout.sendReplyToConversation({ + $, + conversationId: this.conversationId, + data: { + customer: { + id: this.customerId, + }, + text: this.text, + draft: this.draft, + }, + }); + + $.export("$summary", `Reply sent successfully to conversation ID: ${this.conversationId}`); + return response; + }, +}; diff --git a/components/help_scout/common/constants.mjs b/components/help_scout/common/constants.mjs new file mode 100644 index 0000000000000..76d42ac05aaa4 --- /dev/null +++ b/components/help_scout/common/constants.mjs @@ -0,0 +1,16 @@ +export const PHOTO_TYPE_OPTIONS = [ + "unknown", + "gravatar", + "twitter", + "facebook", + "googleprofile", + "googleplus", + "linkedin", + "instagram", +]; + +export const GENDER_OPTIONS = [ + "male", + "female", + "unknown", +]; diff --git a/components/help_scout/common/utils.mjs b/components/help_scout/common/utils.mjs new file mode 100644 index 0000000000000..8fabcd59a844c --- /dev/null +++ b/components/help_scout/common/utils.mjs @@ -0,0 +1,33 @@ +export const parseObject = (obj) => { + if (!obj) return undefined; + + if (Array.isArray(obj)) { + return obj.map((item) => { + if (typeof item === "string") { + try { + return JSON.parse(item); + } catch (e) { + return item; + } + } + return item; + }); + } + if (typeof obj === "string") { + try { + return JSON.parse(obj); + } catch (e) { + return obj; + } + } + return obj; +}; + +export const cleanObject = (o) => { + for (var k in o || {}) { + if (typeof o[k] === "undefined") { + delete o[k]; + } + } + return o; +}; diff --git a/components/help_scout/help_scout.app.mjs b/components/help_scout/help_scout.app.mjs index 7f6e469dcb48f..4b8b4ad42e146 100644 --- a/components/help_scout/help_scout.app.mjs +++ b/components/help_scout/help_scout.app.mjs @@ -1,11 +1,150 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "help_scout", - propDefinitions: {}, + propDefinitions: { + agentId: { + type: "string", + label: "Agent ID", + description: "ID of the agent to whom the conversation is assigned.", + }, + conversationId: { + type: "string", + label: "Conversation ID", + description: "The unique identifier of the conversation.", + async options({ page }) { + const { _embedded: { conversations } } = await this.listConversations({ + params: { + page: page + 1, + }, + }); + + return conversations.map(({ + id: value, subject, primaryCustomer: { email }, + }) => ({ + label: `${subject}(${value}) - ${email}`, + value, + })); + }, + }, + customerId: { + type: "string", + label: "Customer ID", + description: "The unique identifier of the customer.", + async options({ page }) { + const { _embedded: { customers } } = await this.listCustomers({ + params: { + page: page + 1, + }, + }); + + return customers.map(({ + id: value, firstName, lastName, _embedded: { emails }, + }) => ({ + label: `${firstName} ${lastName} - ${emails[0].id}`, + value, + })); + }, + }, + userId: { + type: "string", + label: "User ID", + description: "The unique identifier of the user.", + async options({ page }) { + const { _embedded: { users } } = await this.listUsers({ + params: { + page: page + 1, + }, + }); + + return users.map(({ + id: value, email: label, + }) => ({ + label, + value, + })); + }, + }, + text: { + type: "string", + label: "Text", + description: "The content of the note.", + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.helpscout.net/v2"; + }, + _headers() { + return { + Authorization: `Bearer ${this.$auth.oauth_access_token}`, + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + headers: this._headers(), + ...opts, + }); + }, + listConversations(opts = {}) { + return this._makeRequest({ + path: "/conversations", + ...opts, + }); + }, + listCustomers(opts = {}) { + return this._makeRequest({ + path: "/customers", + ...opts, + }); + }, + listUsers(opts = {}) { + return this._makeRequest({ + path: "/users", + ...opts, + }); + }, + createWebhook(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/webhooks", + ...opts, + }); + }, + deleteWebhook(webhookId) { + return this._makeRequest({ + method: "DELETE", + path: `/webhooks/${webhookId}`, + }); + }, + addNoteToConversation({ + conversationId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/conversations/${conversationId}/notes`, + ...opts, + }); + }, + createCustomer(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/customers", + ...opts, + }); + }, + sendReplyToConversation({ + conversationId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/conversations/${conversationId}/reply`, + ...opts, + }); }, }, }; diff --git a/components/help_scout/package.json b/components/help_scout/package.json new file mode 100644 index 0000000000000..2cbc959a07578 --- /dev/null +++ b/components/help_scout/package.json @@ -0,0 +1,20 @@ +{ + "name": "@pipedream/help_scout", + "version": "0.1.0", + "description": "Pipedream Help Scout Components", + "main": "help_scout.app.mjs", + "keywords": [ + "pipedream", + "help_scout" + ], + "homepage": "https://pipedream.com/apps/help_scout", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3", + "crypto": "^1.0.1" + } +} + diff --git a/components/help_scout/sources/common/base.mjs b/components/help_scout/sources/common/base.mjs new file mode 100644 index 0000000000000..90329148e98d3 --- /dev/null +++ b/components/help_scout/sources/common/base.mjs @@ -0,0 +1,84 @@ +import crypto from "crypto"; +import helpScout from "../../help_scout.app.mjs"; + +export default { + props: { + helpScout, + http: { + type: "$.interface.http", + customResponse: true, + }, + db: "$.service.db", + label: { + type: "string", + label: "Label", + description: "Label associated with this WebHook for better clarity.", + optional: true, + }, + }, + methods: { + _getHookId() { + return this.db.get("hookId"); + }, + _setHookId(hookId) { + this.db.set("hookId", hookId); + }, + _getSecret() { + return this.db.get("secret"); + }, + _setSecret(secret) { + this.db.set("secret", secret); + }, + getExtraData() { + return {}; + }, + }, + hooks: { + async activate() { + const secret = crypto.randomBytes(64).toString("hex"); + const { headers } = await this.helpScout.createWebhook({ + returnFullResponse: true, + data: { + url: this.http.endpoint, + events: this.getEventType(), + label: this.label, + secret, + }, + }); + this._setSecret(secret); + this._setHookId(headers["resource-id"]); + }, + async deactivate() { + const webhookId = this._getHookId(); + await this.helpScout.deleteWebhook(webhookId); + }, + }, + async run({ + bodyRaw, body, headers, + }) { + const hsSignature = headers["x-helpscout-signature"]; + if (hsSignature) { + const secret = this._getSecret(); + const hash = crypto.createHmac("sha1", secret) + .update(bodyRaw) + .digest("base64"); + + if (hash != hsSignature) { + return this.http.respond({ + status: 400, + }); + } + } + + const ts = Date.parse(new Date()); + this.$emit(body, { + id: body.id, + summary: this.getSummary(body), + ts: ts, + }); + + return this.http.respond({ + status: 200, + }); + }, +}; diff --git a/components/help_scout/sources/new-conversation-assigned-instant/new-conversation-assigned-instant.mjs b/components/help_scout/sources/new-conversation-assigned-instant/new-conversation-assigned-instant.mjs new file mode 100644 index 0000000000000..947d6252fa4e8 --- /dev/null +++ b/components/help_scout/sources/new-conversation-assigned-instant/new-conversation-assigned-instant.mjs @@ -0,0 +1,24 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "help_scout-new-conversation-assigned-instant", + name: "New Conversation Assigned (Instant)", + description: "Emit new event when a conversation is assigned to an agent. [See the documentation](https://developer.helpscout.com/)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEventType() { + return [ + "convo.assigned", + ]; + }, + getSummary(body) { + return `New conversation assigned to ${body.assignee.email}`; + }, + }, + sampleEmit, +}; diff --git a/components/help_scout/sources/new-conversation-assigned-instant/test-event.mjs b/components/help_scout/sources/new-conversation-assigned-instant/test-event.mjs new file mode 100644 index 0000000000000..1935a35e8e9e9 --- /dev/null +++ b/components/help_scout/sources/new-conversation-assigned-instant/test-event.mjs @@ -0,0 +1,137 @@ +export default { + "id": 291938, + "type": "email", + "folderId": "1234", + "isDraft": "false", + "number": 349, + "owner": { + "id": 1234, + "firstName": "Jack", + "lastName": "Sprout", + "email": "jack.sprout@gmail.com", + "phone": null, + "type": "user" + }, + "mailbox": { + "id": 1234, + "name": "My Mailbox" + }, + "customer": { + "id": 29418, + "firstName": "Vernon", + "lastName": "Bear", + "email": "vbear@mywork.com", + "phone": "800-555-1212", + "type": "customer" + }, + "threadCount": 4, + "status": "active", + "subject": "I need help!", + "preview": "Hello, I tried to download the file off your site...", + "createdBy": { + "id": 29418, + "firstName": "Vernon", + "lastName": "Bear", + "email": "vbear@mywork.com", + "phone": null, + "type": "customer" + }, + "createdAt": "2012-07-23T12:34:12Z", + "modifiedAt": "2012-07-24T20:18:33Z", + "closedAt": null, + "closedBy": null, + "source": { + "type": "email", + "via": "customer" + }, + "cc": [ + "cc1@somewhere.com", + "cc2@somewhere.com" + ], + "bcc": [ + "bcc1@somewhere.com", + "bcc2@somewhere.com" + ], + "tags": [ + "tag1", + "tag2" + ], + "customFields": [ + { + "fieldId": 1, + "name": "Team", + "value": "Development" + }, + { + "fieldId": 2, + "name": "Customer Disposition", + "value": "Happy" + } + ], + "threads": [ + { + "id": 88171881, + "assignedTo": { + "id": 1234, + "firstName": "Jack", + "lastName": "Sprout", + "email": "jack.sprout@gmail.com", + "phone": null, + "type": "user" + }, + "status": "active", + "createdAt": "2012-07-23T12:34:12Z", + "createdBy": { + "id": 1234, + "firstName": "Jack", + "lastName": "Sprout", + "email": "jack.sprout@gmail.com", + "phone": null, + "type": "user" + }, + "source": { + "type": "web", + "via": "user" + }, + "type": "message", + "state": "published", + "customer": { + "id": 29418, + "firstName": "Vernon", + "lastName": "Bear", + "email": "vbear@mywork.com", + "phone": "800-555-1212", + "type": "customer" + }, + "fromMailbox": null, + "body": "This is what I have to say. Thank you.", + "to": [ + "customer@somewhere.com" + ], + "cc": [ + "cc1@somewhere.com", + "cc2@somewhere.com" + ], + "bcc": [ + "bcc1@somewhere.com", + "bcc2@somewhere.com" + ], + "attachments": [ + { + "id": 12391, + "mimeType": "image/jpeg", + "filename": "logo.jpg", + "size": 22, + "width": 160, + "height": 160, + "url": "https://secure.helpscout.net/some-url/logo.jpg" + } + ], + "tags": [ + "tag1", + "tag2", + "tag3" + ] + } + ] +} \ No newline at end of file diff --git a/components/help_scout/sources/new-conversation-created-instant/new-conversation-created-instant.mjs b/components/help_scout/sources/new-conversation-created-instant/new-conversation-created-instant.mjs new file mode 100644 index 0000000000000..37f0b97e12d52 --- /dev/null +++ b/components/help_scout/sources/new-conversation-created-instant/new-conversation-created-instant.mjs @@ -0,0 +1,24 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "help_scout-new-conversation-created-instant", + name: "New Conversation Created (Instant)", + description: "Emit new event when a new conversation is created.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEventType() { + return [ + "convo.created", + ]; + }, + getSummary(body) { + return `New conversation created: ${body.subject}`; + }, + }, + sampleEmit, +}; diff --git a/components/help_scout/sources/new-conversation-created-instant/test-event.mjs b/components/help_scout/sources/new-conversation-created-instant/test-event.mjs new file mode 100644 index 0000000000000..d8cc91fd9e8de --- /dev/null +++ b/components/help_scout/sources/new-conversation-created-instant/test-event.mjs @@ -0,0 +1,137 @@ +export default { + "id": 291938, + "type": "email", + "folderId": "1234", + "isDraft": "false", + "number": 349, + "owner": { + "id": 1234, + "firstName": "Jack", + "lastName": "Sprout", + "email": "jack.sprout@gmail.com", + "phone": null, + "type": "user" + }, + "mailbox": { + "id": 1234, + "name": "My Mailbox" + }, + "customer": { + "id": 29418, + "firstName": "Vernon", + "lastName": "Bear", + "email": "vbear@mywork.com", + "phone": "800-555-1212", + "type": "customer" + }, + "threadCount": 4, + "status": "active", + "subject": "I need help!", + "preview": "Hello, I tried to download the file off your site...", + "createdBy": { + "id": 29418, + "firstName": "Vernon", + "lastName": "Bear", + "email": "vbear@mywork.com", + "phone": null, + "type": "customer" + }, + "createdAt": "2012-07-23T12:34:12Z", + "modifiedAt": "2012-07-24T20:18:33Z", + "closedAt": null, + "closedBy": null, + "source": { + "type": "email", + "via": "customer" + }, + "cc": [ + "cc1@somewhere.com", + "cc2@somewhere.com" + ], + "bcc": [ + "bcc1@somewhere.com", + "bcc2@somewhere.com" + ], + "tags": [ + "tag1", + "tag2" + ], + "customFields": [ + { + "fieldId": 1, + "name": "Team", + "value": "Development" + }, + { + "fieldId": 2, + "name": "Customer Disposition", + "value": "Happy" + } + ], + "threads": [ + { + "id": 88171881, + "assignedTo": { + "id": 1234, + "firstName": "Jack", + "lastName": "Sprout", + "email": "jack.sprout@gmail.com", + "phone": null, + "type": "user" + }, + "status": "active", + "createdAt": "2012-07-23T12:34:12Z", + "createdBy": { + "id": 1234, + "firstName": "Jack", + "lastName": "Sprout", + "email": "jack.sprout@gmail.com", + "phone": null, + "type": "user" + }, + "source": { + "type": "web", + "via": "user" + }, + "type": "message", + "state": "published", + "customer": { + "id": 29418, + "firstName": "Vernon", + "lastName": "Bear", + "email": "vbear@mywork.com", + "phone": "800-555-1212", + "type": "customer" + }, + "fromMailbox": null, + "body": "This is what I have to say. Thank you.", + "to": [ + "customer@somewhere.com" + ], + "cc": [ + "cc1@somewhere.com", + "cc2@somewhere.com" + ], + "bcc": [ + "bcc1@somewhere.com", + "bcc2@somewhere.com" + ], + "attachments": [ + { + "id": 12391, + "mimeType": "image/jpeg", + "filename": "logo.jpg", + "size": 22, + "width": 160, + "height": 160, + "url": "https://secure.helpscout.net/some-url/logo.jpg" + } + ], + "tags": [ + "tag1", + "tag2", + "tag3" + ] + } + ] + } \ No newline at end of file diff --git a/components/help_scout/sources/new-customer-instant/new-customer-instant.mjs b/components/help_scout/sources/new-customer-instant/new-customer-instant.mjs new file mode 100644 index 0000000000000..766f7a78b6863 --- /dev/null +++ b/components/help_scout/sources/new-customer-instant/new-customer-instant.mjs @@ -0,0 +1,24 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "help_scout-new-customer-instant", + name: "New Customer Added (Instant)", + description: "Emit new event when a new customer is added.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEventType() { + return [ + "customer.created", + ]; + }, + getSummary(body) { + return `New customer created: ${body.firstName} ${body.lastName} - ${body._embedded.emails[0].value}`; + }, + }, + sampleEmit, +}; diff --git a/components/help_scout/sources/new-customer-instant/test-event.mjs b/components/help_scout/sources/new-customer-instant/test-event.mjs new file mode 100644 index 0000000000000..7070a334032db --- /dev/null +++ b/components/help_scout/sources/new-customer-instant/test-event.mjs @@ -0,0 +1,58 @@ +export default { + "id": 29418, + "firstName": "Vernon", + "lastName": "Bear", + "photoUrl": "http://twitter.com/img/some-avatar.jpg", + "photoType": "twitter", + "gender": "Male", + "age": "30-35", + "organization": "Acme, Inc", + "jobTitle": "CEO and Co-Founder", + "location": "Greater Dallas/FT Worth Area", + "background": "I've worked with Vernon before and he's really great.", + "createdAt": "2012-07-23T12:34:12Z", + "modifiedAt": "2012-07-24T20:18:33Z", + "address": { + "id": 1234, + "lines": [ + "123 West Main St", + "Suite 123" + ], + "city": "Dallas", + "state": "TX", + "postcalCode": "74206", + "country": "US", + "createdAt": "2012-07-23T12:34:12Z", + "modifiedAt": "2012-07-24T20:18:33Z" + }, + "socialProfiles": [ + { + "id": 9184, + "value": "https://twitter.com/helpscout", + "type": "twitter" + }, + ], + "emails": [{ + "id": 98131, + "value": "vbear@mywork.com", + "location": "work" + }, + ], + "phones": [{ + "id": 22381, + "value": "222-333-4444", + "location": "home" + }, + ], + "chats": [{ + "id": 77183, + "value": "jsprout", + "type": "aim" + }, + ], + "websites": [{ + "id": 5584, + "value": "http://www.somewhere.com" + }, + ] +} \ No newline at end of file diff --git a/components/help_scout/yarn.lock b/components/help_scout/yarn.lock new file mode 100644 index 0000000000000..144b3d8041ea3 --- /dev/null +++ b/components/help_scout/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +crypto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037" + integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig== diff --git a/components/helper_functions/actions/trigger-workflow-for-each/trigger-workflow-for-each.mjs b/components/helper_functions/actions/trigger-workflow-for-each/trigger-workflow-for-each.mjs new file mode 100644 index 0000000000000..4895f1701d7f4 --- /dev/null +++ b/components/helper_functions/actions/trigger-workflow-for-each/trigger-workflow-for-each.mjs @@ -0,0 +1,41 @@ +import helperFunctions from "../../helper_functions.app.mjs"; + +export default { + key: "helper_functions-trigger-workflow-for-each", + name: "Trigger Workflow For Each", + description: "Trigger another Pipedream workflow in your workspace for each object in an array.", + version: "0.0.1", + type: "action", + props: { + helperFunctions, + workflowId: { + type: "string", + label: "Workflow ID", + description: "The ID of the workflow to trigger. Workflow IDs are formatted as `p_******` and you can find a workflow's ID within the workflow builder URL.", + }, + objects: { + type: "any", + label: "Array of Objects", + description: "Use a custom expression (`{{steps.code.objects}}`) to reference an array of objects exported from a previous step to send to the triggered workflow.", + }, + }, + async run({ $ }) { + const { + workflowId, + objects, + } = this; + + try { + const results = []; + const triggerPromises = objects.map((object) => $.flow.trigger(workflowId, object)); + for await (const result of triggerPromises) { + results.push(result); + } + $.export("$summary", `Successfully triggered workflow ID **${workflowId}**`); + return results; + } catch (error) { + $.export("$summary", `Failed to trigger workflow ID **${workflowId}**.`); + throw error; + } + }, +}; diff --git a/components/helper_functions/package.json b/components/helper_functions/package.json index b059212611236..8eb5e60ee2519 100644 --- a/components/helper_functions/package.json +++ b/components/helper_functions/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/helper_functions", - "version": "0.4.1", + "version": "0.4.2", "description": "Pipedream Helper_functions Components", "main": "helper_functions.app.mjs", "keywords": [ diff --git a/components/heroku/common/constants.mjs b/components/heroku/common/constants.mjs new file mode 100644 index 0000000000000..c446d21da03ca --- /dev/null +++ b/components/heroku/common/constants.mjs @@ -0,0 +1,46 @@ +const ENTITIES = [ + { + value: "api:addon-attachment", + label: "addon-attachment - An add-on has been attached or removed from the app", + }, + { + value: "api:addon", + label: "addon - An add-on for the app has been newly provisioned or deleted, or its details have been modified", + }, + { + value: "api:app", + label: "app - The app itself has been provisioned or deleted, or its details have been modified", + }, + { + value: "api:build", + label: "build - A new build for the app has been initiated or the build’s status has changed since the last notification", + }, + { + value: "api:collaborator", + label: "collaborator - A collaborator has been added or removed from the app, or an existing collaborator’s details have been modified", + }, + { + value: "api:domain", + label: "domain - Custom domain details have been added or removed from the app", + }, + { + value: "api:dyno", + label: "dyno - A new dyno has begun running for the app", + }, + { + value: "api:formation", + label: "formation - The dyno formation for a particular process type has been modified", + }, + { + value: "api:release", + label: "release - A new release for the app has been initiated or the release’s status has changed since the last notification", + }, + { + value: "api:sni-endpoint", + label: "sni-endpoint - An SNI endpoint has been specified or removed for the app, or the existing SNI endpoint’s details have been modified", + }, +]; + +export default { + ENTITIES, +}; diff --git a/components/heroku/heroku.app.mjs b/components/heroku/heroku.app.mjs index f6921cc192a59..c994c684b3f58 100644 --- a/components/heroku/heroku.app.mjs +++ b/components/heroku/heroku.app.mjs @@ -1,11 +1,71 @@ +import { axios } from "@pipedream/platform"; +import constants from "./common/constants.mjs"; + export default { type: "app", app: "heroku", - propDefinitions: {}, + propDefinitions: { + appId: { + type: "string", + label: "App ID", + description: "The ID of the app", + async options() { + const apps = await this.listApps(); + return apps?.map((app) => ({ + label: app.name, + value: app.id, + })) || []; + }, + }, + entities: { + type: "string[]", + label: "Entities", + description: "The entity or entities to subscribe to", + options: constants.ENTITIES, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.heroku.com"; + }, + _makeRequest(opts = {}) { + const { + $ = this, + path, + ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + url: `${this._baseUrl()}${path}`, + headers: { + Authorization: `Bearer ${this.$auth.oauth_access_token}`, + Accept: "application/vnd.heroku+json; version=3", + }, + }); + }, + listApps(opts = {}) { + return this._makeRequest({ + path: "/apps", + ...opts, + }); + }, + createWebhookSubscription({ + appId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/apps/${appId}/webhooks`, + ...opts, + }); + }, + deleteWebhookSubscription({ + appId, hookId, ...opts + }) { + return this._makeRequest({ + method: "DELETE", + path: `/apps/${appId}/webhooks/${hookId}`, + ...opts, + }); }, }, }; diff --git a/components/heroku/package.json b/components/heroku/package.json new file mode 100644 index 0000000000000..ad831ce609311 --- /dev/null +++ b/components/heroku/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/heroku", + "version": "0.0.1", + "description": "Pipedream Heroku Components", + "main": "heroku.app.mjs", + "keywords": [ + "pipedream", + "heroku" + ], + "homepage": "https://pipedream.com/apps/heroku", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" + } +} diff --git a/components/heroku/sources/new-webhook-event-instant/new-webhook-event-instant.mjs b/components/heroku/sources/new-webhook-event-instant/new-webhook-event-instant.mjs new file mode 100644 index 0000000000000..89f2dc4f9b745 --- /dev/null +++ b/components/heroku/sources/new-webhook-event-instant/new-webhook-event-instant.mjs @@ -0,0 +1,74 @@ +import heroku from "../../heroku.app.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + key: "heroku-new-webhook-event-instant", + name: "New Webhook Event (Instant)", + description: "Emit new event on each webhook event. [See the documentation](https://devcenter.heroku.com/articles/app-webhooks-schema#webhook-create)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + heroku, + http: "$.interface.http", + db: "$.service.db", + appId: { + propDefinition: [ + heroku, + "appId", + ], + }, + entities: { + propDefinition: [ + heroku, + "entities", + ], + }, + }, + hooks: { + async activate() { + const { id } = await this.heroku.createWebhookSubscription({ + appId: this.appId, + data: { + include: this.entities, + level: "notify", + url: this.http.endpoint, + }, + }); + this._setHookId(id); + }, + async deactivate() { + const hookId = this._getHookId(); + if (hookId) { + await this.heroku.deleteWebhookSubscription({ + appId: this.appId, + hookId, + }); + } + }, + }, + methods: { + _getHookId() { + return this.db.get("hookId"); + }, + _setHookId(hookId) { + this.db.set("hookId", hookId); + }, + generateMeta(event) { + return { + id: event.id, + summary: `New ${event.webhook_metadata.event.include} - ${event.action} Event`, + ts: Date.now(), + }; + }, + }, + async run(event) { + const { body } = event; + if (!body) { + return; + } + const meta = this.generateMeta(body); + this.$emit(body, meta); + }, + sampleEmit, +}; diff --git a/components/heroku/sources/new-webhook-event-instant/test-event.mjs b/components/heroku/sources/new-webhook-event-instant/test-event.mjs new file mode 100644 index 0000000000000..fa44155a44ad1 --- /dev/null +++ b/components/heroku/sources/new-webhook-event-instant/test-event.mjs @@ -0,0 +1,69 @@ +export default { + "id": "a5b27512-1f14-4f45-bc26-fe6bf03686fe", + "data": { + "id": "9a4eaeed-24cc-4b23-a8ca-7867b251eaf3", + "acm": false, + "name": "", + "team": null, + "owner": { + "id": "7b25912d-b147-48d6-b03d-f0fc72be381b", + "email": "" + }, + "space": null, + "stack": { + "id": "74cfe988-7527-4ca9-9667-77bb9f3029cf", + "name": "heroku-24" + }, + "region": { + "id": "59accabd-516d-4f0e-83e6-6e3757701145", + "name": "us" + }, + "git_url": "", + "web_url": "", + "repo_size": null, + "slug_size": null, + "created_at": "2024-11-07T16:38:38Z", + "updated_at": "2024-11-07T17:14:52Z", + "archived_at": null, + "build_stack": { + "id": "74cfe988-7527-4ca9-9667-77bb9f3029cf", + "name": "heroku-24" + }, + "maintenance": false, + "released_at": "2024-11-07T16:38:39Z", + "organization": null, + "internal_routing": null, + "buildpack_provided_description": null + }, + "actor": { + "id": "7b25912d-b147-48d6-b03d-f0fc72be381b", + "email": "" + }, + "action": "update", + "version": "application/vnd.heroku+json; version=3", + "resource": "app", + "sequence": null, + "created_at": "2024-11-07T17:14:52.475272Z", + "updated_at": "2024-11-07T17:14:52.475276Z", + "published_at": "2024-11-07T17:14:52Z", + "previous_data": { + "name": "", + "git_url": "", + "updated_at": "2024-11-07T17:13:42Z" + }, + "webhook_metadata": { + "attempt": { + "id": "16e90948-01d8-4ba0-97de-5868a4aed0bf" + }, + "delivery": { + "id": "bbf72057-15f2-40b0-87c7-99ccf95e1666" + }, + "event": { + "id": "a5b27512-1f14-4f45-bc26-fe6bf03686fe", + "include": "api:app" + }, + "webhook": { + "id": "c385bb39-42ab-49c6-9255-108ff4cabdbd" + } + } +} \ No newline at end of file diff --git a/components/heyy/actions/create-contact/create-contact.mjs b/components/heyy/actions/create-contact/create-contact.mjs new file mode 100644 index 0000000000000..a377a2636dff7 --- /dev/null +++ b/components/heyy/actions/create-contact/create-contact.mjs @@ -0,0 +1,102 @@ +import { ConfigurationError } from "@pipedream/platform"; +import app from "../../heyy.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "heyy-create-contact", + name: "Create Contact", + description: "Creates a new contact for the business. [See the documentation](https://documenter.getpostman.com/view/27408936/2sA2r3a6DW#a1249b8d-10cf-446a-be35-eb8793ffa967).", + version: "0.0.1", + type: "action", + props: { + app, + phoneNumber: { + propDefinition: [ + app, + "phoneNumber", + ], + }, + firstName: { + propDefinition: [ + app, + "firstName", + ], + }, + lastName: { + propDefinition: [ + app, + "lastName", + ], + }, + email: { + propDefinition: [ + app, + "email", + ], + }, + labels: { + propDefinition: [ + app, + "labels", + ], + }, + attributes: { + propDefinition: [ + app, + "attributes", + ], + }, + }, + methods: { + createContact(args = {}) { + return this.app.post({ + path: "/contacts", + ...args, + }); + }, + }, + async run({ $ }) { + const { + createContact, + phoneNumber, + firstName, + lastName, + email, + labels, + attributes, + } = this; + + if (!utils.isPhoneNumberValid(phoneNumber)) { + throw new ConfigurationError(`The phone number \`${phoneNumber}\` is invalid. Please provide a valid phone number.`); + } + + const response = await createContact({ + $, + data: { + phoneNumber, + firstName, + lastName, + email, + ...(labels?.length && { + labels: labels.map((name) => ({ + name, + })), + }), + attributes: + attributes && Object.entries(attributes) + .reduce((acc, [ + externalId, + value, + ]) => ([ + ...acc, + { + externalId, + value, + }, + ]), []), + }, + }); + $.export("$summary", `Successfully created contact with ID \`${response.data.id}\`.`); + return response; + }, +}; diff --git a/components/heyy/actions/send-whatsapp-message/send-whatsapp-message.mjs b/components/heyy/actions/send-whatsapp-message/send-whatsapp-message.mjs new file mode 100644 index 0000000000000..7ea702a060f37 --- /dev/null +++ b/components/heyy/actions/send-whatsapp-message/send-whatsapp-message.mjs @@ -0,0 +1,161 @@ +import app from "../../heyy.app.mjs"; +import constants from "../../common/constants.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "heyy-send-whatsapp-message", + name: "Send WhatsApp Message", + description: "Sends a WhatsApp message to a contact. [See the documentation](https://documenter.getpostman.com/view/27408936/2sa2r3a6dw)", + version: "0.0.1", + type: "action", + props: { + app, + channelId: { + propDefinition: [ + app, + "channelId", + ], + }, + phoneNumber: { + label: "Phone Number", + description: "The phone number of the contact.", + propDefinition: [ + app, + "contactId", + () => ({ + mapper: ({ + firstName, phoneNumber: value, + }) => ({ + label: firstName || value, + value, + }), + }), + ], + }, + msgType: { + type: "string", + label: "Message Type", + description: "The type of message to send.", + options: Object.values(constants.MSG_TYPE), + reloadProps: true, + }, + }, + additionalProps() { + const { msgType } = this; + + const bodyText = { + type: "string", + label: "Body Text", + description: "The text of the message to send.", + }; + + if (msgType === constants.MSG_TYPE.TEXT) { + return { + bodyText, + }; + } + + if (msgType === constants.MSG_TYPE.IMAGE) { + return { + bodyText, + fileId: { + type: "string", + label: "File ID", + description: "The ID of the file to attach to the message.", + }, + }; + } + + if (msgType === constants.MSG_TYPE.TEMPLATE) { + return { + messageTemplateId: { + type: "string", + label: "Message Template ID", + description: "The ID of the message template to use.", + optional: true, + options: async ({ page }) => { + const { data: { messageTemplates } } = await this.app.getMessageTemplates({ + params: { + page, + sortBy: "updatedAt", + order: "DESC", + }, + }); + return messageTemplates.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + }; + } + + if (msgType === constants.MSG_TYPE.INTERACTIVE) { + return { + bodyText, + buttons: { + type: "string[]", + label: "Buttons", + description: "The buttons to include in the message. Each row should have a JSON formated string. Eg. `{ \"id\": \"STRING\", \"title\": \"STRING\" }`.", + }, + headerText: { + type: "string", + label: "Header Text", + description: "The header text of the message to send.", + optional: true, + }, + footerText: { + type: "string", + label: "Footer Text", + description: "The footer text of the message to send.", + optional: true, + }, + }; + } + + return {}; + }, + methods: { + sendWhatsappMessage({ + channelId, ...args + } = {}) { + return this.app.post({ + path: `/${channelId}/whatsapp_messages/send`, + ...args, + }); + }, + }, + async run({ $ }) { + const { + sendWhatsappMessage, + channelId, + phoneNumber, + msgType, + bodyText, + fileId, + messageTemplateId, + headerText, + footerText, + buttons, + } = this; + + const response = await sendWhatsappMessage({ + $, + channelId, + data: { + phoneNumber, + type: msgType, + bodyText, + fileId, + messageTemplateId, + headerText, + footerText, + buttons: utils.parseArray(buttons), + }, + }); + $.export("$summary", "Succesfully sent WhatsApp message."); + return response; + }, +}; diff --git a/components/heyy/actions/update-contact/update-contact.mjs b/components/heyy/actions/update-contact/update-contact.mjs new file mode 100644 index 0000000000000..156c5669b2099 --- /dev/null +++ b/components/heyy/actions/update-contact/update-contact.mjs @@ -0,0 +1,115 @@ +import { ConfigurationError } from "@pipedream/platform"; +import app from "../../heyy.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "heyy-update-contact", + name: "Update Contact", + description: "Updates the details of a contact under your business. [See the documentation](https://documenter.getpostman.com/view/27408936/2sA2r3a6DW#5a5ee22b-c16e-4d46-ae5d-3844b6501a34).", + version: "0.0.1", + type: "action", + props: { + app, + contactId: { + propDefinition: [ + app, + "contactId", + ], + }, + phoneNumber: { + optional: true, + propDefinition: [ + app, + "phoneNumber", + ], + }, + firstName: { + optional: true, + propDefinition: [ + app, + "firstName", + ], + }, + lastName: { + propDefinition: [ + app, + "lastName", + ], + }, + email: { + propDefinition: [ + app, + "email", + ], + }, + labels: { + propDefinition: [ + app, + "labels", + ], + }, + attributes: { + propDefinition: [ + app, + "attributes", + ], + }, + }, + methods: { + updateContact({ + contactId, ...args + } = {}) { + return this.app.put({ + path: `/contacts/${contactId}`, + ...args, + }); + }, + }, + async run({ $ }) { + const { + updateContact, + contactId, + phoneNumber, + firstName, + lastName, + email, + labels, + attributes, + } = this; + + if (phoneNumber && !utils.isPhoneNumberValid(phoneNumber)) { + throw new ConfigurationError(`The phone number \`${phoneNumber}\` is invalid. Please provide a valid phone number.`); + } + + const response = await updateContact({ + $, + contactId, + data: { + phoneNumber, + firstName, + lastName, + email, + ...(labels?.length && { + labels: labels.map((name) => ({ + name, + })), + }), + attributes: + attributes && Object.entries(attributes) + .reduce((acc, [ + externalId, + value, + ]) => ([ + ...acc, + { + externalId, + value, + }, + ]), []), + }, + }); + + $.export("$summary", `Successfully updated contact with ID \`${response.data.id}\`.`); + return response; + }, +}; diff --git a/components/heyy/actions/upload-file/upload-file.mjs b/components/heyy/actions/upload-file/upload-file.mjs new file mode 100644 index 0000000000000..6869a3a417ee5 --- /dev/null +++ b/components/heyy/actions/upload-file/upload-file.mjs @@ -0,0 +1,62 @@ +import { createReadStream } from "fs"; +import FormData from "form-data"; +import app from "../../heyy.app.mjs"; + +export default { + key: "heyy-upload-file", + name: "Upload File", + description: "Uploads a file. [See the documentation](https://documenter.getpostman.com/view/27408936/2sA2r3a6DW#67e41b81-318c-4ed0-be78-e92fd39f3530).", + version: "0.0.1", + type: "action", + props: { + app, + filePath: { + type: "string", + label: "File Path", + description: "The file to be uploaded, please provide a file from `/tmp`. To upload a file to `/tmp` folder, please follow the doc [here](https://pipedream.com/docs/code/nodejs/working-with-files/#writing-a-file-to-tmp)", + }, + format: { + type: "string", + label: "Format", + description: "The format of the file to be uploaded.", + options: [ + "IMAGE", + "VIDEO", + "DOCUMENT", + ], + }, + }, + methods: { + uploadFile(args = {}) { + return this.app.post({ + path: "/upload_file", + ...args, + }); + }, + }, + async run({ $ }) { + const { + uploadFile, + filePath, + format, + } = this; + + const file = filePath.startsWith("/tmp") + ? filePath + : `/tmp/${filePath}`; + + const data = new FormData(); + data.append("file", createReadStream(file)); + data.append("format", format); + + const response = await uploadFile({ + $, + headers: { + "Content-Type": "multipart/form-data", + }, + data, + }); + $.export("$summary", `Succesfully uploaded file with ID \`${response.id}\`.`); + return response; + }, +}; diff --git a/components/heyy/common/constants.mjs b/components/heyy/common/constants.mjs new file mode 100644 index 0000000000000..a137b766d45b6 --- /dev/null +++ b/components/heyy/common/constants.mjs @@ -0,0 +1,21 @@ +const BASE_URL = "https://api.hey-y.io"; +const VERSION_PATH = "/api/v2.0"; +const LAST_CREATED_AT = "lastCreatedAt"; +const DEFAULT_MAX = 600; +const WEBHOOK_ID = "webhookId"; + +const MSG_TYPE = { + TEXT: "TEXT", + IMAGE: "IMAGE", + TEMPLATE: "TEMPLATE", + INTERACTIVE: "INTERACTIVE", +}; + +export default { + BASE_URL, + VERSION_PATH, + DEFAULT_MAX, + LAST_CREATED_AT, + MSG_TYPE, + WEBHOOK_ID, +}; diff --git a/components/heyy/common/utils.mjs b/components/heyy/common/utils.mjs new file mode 100644 index 0000000000000..9c78ff9021a00 --- /dev/null +++ b/components/heyy/common/utils.mjs @@ -0,0 +1,60 @@ +import { ConfigurationError } from "@pipedream/platform"; + +function isJson(value) { + try { + JSON.parse(value); + } catch (e) { + return false; + } + + return true; +} + +function parse(value) { + if (!Object.keys(value).length) { + throw new ConfigurationError("Please provide at least one object property."); + } + + if (typeof(value) === "object") { + return value; + } + + if (isJson(value)) { + return JSON.parse(value); + } + + throw new ConfigurationError("Make sure the custom expression contains a valid object"); +} + +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 isPhoneNumberValid(phoneNumber) { + const pattern = new RegExp("^\\+[1-9]{1}[0-9]{0,2}[2-9]{1}[0-9]{2}[2-9]{1}[0-9]{2}[0-9]{4}$"); + return pattern.test(phoneNumber); +} + +export default { + parseArray: (value) => parseArray(value)?.map(parse), + isPhoneNumberValid, +}; diff --git a/components/heyy/heyy.app.mjs b/components/heyy/heyy.app.mjs index 20d9d05957f47..277b8886d2427 100644 --- a/components/heyy/heyy.app.mjs +++ b/components/heyy/heyy.app.mjs @@ -1,11 +1,154 @@ +import { axios } from "@pipedream/platform"; +import constants from "./common/constants.mjs"; + export default { type: "app", app: "heyy", - propDefinitions: {}, + propDefinitions: { + phoneNumber: { + type: "string", + label: "Phone Number", + description: "The phone number of the contact. It must be in E.164 format. Eg: `+14155552671`. For more information please see [here](https://en.wikipedia.org/wiki/E.164).", + }, + firstName: { + type: "string", + label: "First Name", + description: "The first name of the contact.", + }, + lastName: { + type: "string", + label: "Last Name", + description: "The last name of the contact.", + optional: true, + }, + email: { + type: "string", + label: "Email", + description: "The email address of the contact.", + optional: true, + }, + labels: { + type: "string[]", + label: "Labels", + description: "The labels associated with the contact.", + optional: true, + async options() { + const { data } = await this.getLabels(); + return data.map(({ name }) => name); + }, + }, + attributes: { + type: "object", + label: "Attributes", + description: "The attributes associated with the contact.", + optional: true, + }, + contactId: { + type: "string", + label: "Contact ID", + description: "The unique identifier of the contact.", + async options({ + page, + mapper = ({ + id: value, firstName, phoneNumber, + }) => ({ + label: firstName || phoneNumber, + value, + }), + }) { + const { data: { contacts } } = await this.getContacts({ + params: { + page, + sortBy: "updatedAt", + order: "DESC", + }, + }); + return contacts.map(mapper); + }, + }, + channelId: { + type: "string", + label: "Channel ID", + description: "The unique identifier of the channel.", + async options({ + mapper = ({ + id: value, whatsappPhoneNumber: { name: label }, + }) => ({ + label, + value, + }), + }) { + const { data } = await this.getChannels(); + return data.map(mapper); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + getUrl(path) { + return `${constants.BASE_URL}${constants.VERSION_PATH}${path}`; + }, + getHeaders(headers) { + return { + ...headers, + Authorization: `Bearer ${this.$auth.api_token}`, + }; + }, + _makeRequest({ + $ = this, path, headers, ...args + } = {}) { + return axios($, { + ...args, + url: this.getUrl(path), + headers: this.getHeaders(headers), + }); + }, + post(args = {}) { + return this._makeRequest({ + method: "POST", + ...args, + }); + }, + put(args = {}) { + return this._makeRequest({ + method: "PUT", + ...args, + }); + }, + delete(args = {}) { + return this._makeRequest({ + method: "DELETE", + ...args, + }); + }, + getLabels(args = {}) { + return this._makeRequest({ + path: "/labels", + ...args, + }); + }, + getAttributes(args = {}) { + return this._makeRequest({ + path: "/attributes", + ...args, + }); + }, + getContacts(args = {}) { + return this._makeRequest({ + path: "/contacts", + ...args, + }); + }, + getMessageTemplates(args = {}) { + return this._makeRequest({ + path: "/message_templates", + ...args, + }); + }, + getChannels(args = {}) { + return this._makeRequest({ + path: "/channels", + ...args, + }); }, }, -}; \ No newline at end of file +}; diff --git a/components/heyy/package.json b/components/heyy/package.json index fafb4dc7caa92..daadbe845922f 100644 --- a/components/heyy/package.json +++ b/components/heyy/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/heyy", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Heyy Components", "main": "heyy.app.mjs", "keywords": [ @@ -11,5 +11,9 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "3.0.3", + "form-data": "^4.0.1" } -} \ No newline at end of file +} diff --git a/components/heyy/sources/new-incoming-message-instant/new-incoming-message-instant.mjs b/components/heyy/sources/new-incoming-message-instant/new-incoming-message-instant.mjs new file mode 100644 index 0000000000000..16152ae56cb07 --- /dev/null +++ b/components/heyy/sources/new-incoming-message-instant/new-incoming-message-instant.mjs @@ -0,0 +1,87 @@ +import app from "../../heyy.app.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + key: "heyy-new-incoming-message-instant", + name: "New Incoming Message", + description: "Emit new event when a business gets a new incoming message. [See the documentation](https://documenter.getpostman.com/view/27408936/2sA2r3a6DW#eda04a28-4c5b-4709-a3f4-204dba6bcc18).", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + app, + db: "$.service.db", + http: "$.interface.http", + channelId: { + propDefinition: [ + app, + "channelId", + ], + }, + }, + hooks: { + async activate() { + const { + createWebhook, + http: { endpoint: url }, + channelId, + setWebhookId, + } = this; + const response = + await createWebhook({ + data: { + url, + type: "WHATSAPP_MESSAGE_RECEIVED", + channelId, + }, + }); + + setWebhookId(response.data.id); + }, + async deactivate() { + const webhookId = this.getWebhookId(); + if (webhookId) { + await this.deleteWebhook({ + webhookId, + }); + } + }, + }, + methods: { + setWebhookId(value) { + this.db.set(constants.WEBHOOK_ID, value); + }, + getWebhookId() { + return this.db.get(constants.WEBHOOK_ID); + }, + generateMeta({ data: { whatsappMessage: resource } }) { + return { + id: resource.metaMessageId, + summary: `New Incomming Message ${resource.metaMessageId}`, + ts: parseInt(resource.timestamp), + }; + }, + processResource(resource) { + this.$emit(resource, this.generateMeta(resource)); + }, + createWebhook(args = {}) { + return this.app.post({ + debug: true, + path: "/api_webhooks", + ...args, + }); + }, + deleteWebhook({ + webhookId, ...args + } = {}) { + return this.app.delete({ + debug: true, + path: `/api_webhooks/${webhookId}`, + ...args, + }); + }, + }, + async run({ body }) { + this.processResource(body); + }, +}; diff --git a/components/html_to_image/actions/convert-html-to-image/convert-html-to-image.mjs b/components/html_to_image/actions/convert-html-to-image/convert-html-to-image.mjs new file mode 100644 index 0000000000000..7253d884e3011 --- /dev/null +++ b/components/html_to_image/actions/convert-html-to-image/convert-html-to-image.mjs @@ -0,0 +1,50 @@ +import htmlToImage from "../../html_to_image.app.mjs"; + +export default { + key: "html_to_image-convert-html-to-image", + name: "Convert HTML to Image", + description: "Create an image from HTML. [See the documentation](https://docs.htmlcsstoimg.com/html-to-image-api/html-css-to-image-api).", + version: "0.0.1", + type: "action", + props: { + htmlToImage, + htmlContent: { + propDefinition: [ + htmlToImage, + "htmlContent", + ], + }, + cssContent: { + propDefinition: [ + htmlToImage, + "cssContent", + ], + }, + font: { + type: "string", + label: "Font", + description: "Google Font Name that needs to be imported when generating image from HTML & CSS. To pass multiple fonts, separate them with | sign. Example - `Roboto|Georgia`", + optional: true, + }, + quality: { + propDefinition: [ + htmlToImage, + "quality", + ], + }, + }, + async run({ $ }) { + const response = await this.htmlToImage.convertToImage({ + $, + data: { + html_content: this.htmlContent, + css_content: this.cssContent, + font: this.font, + quality: this.quality, + generate_img_url: true, + }, + }); + $.export("$summary", "Successfully converted HTML to image"); + return response; + }, +}; diff --git a/components/html_to_image/actions/convert-html-to-pdf/convert-html-to-pdf.mjs b/components/html_to_image/actions/convert-html-to-pdf/convert-html-to-pdf.mjs new file mode 100644 index 0000000000000..5f040a34b14ee --- /dev/null +++ b/components/html_to_image/actions/convert-html-to-pdf/convert-html-to-pdf.mjs @@ -0,0 +1,71 @@ +import htmlToImage from "../../html_to_image.app.mjs"; + +export default { + key: "html_to_image-convert-html-to-pdf", + name: "Convert HTML to PDF", + description: "Create a PDF file from HTML. [See the documentation](https://docs.htmlcsstoimg.com/html-to-image-api/html-css-to-pdf-api).", + version: "0.0.1", + type: "action", + props: { + htmlToImage, + htmlContent: { + propDefinition: [ + htmlToImage, + "htmlContent", + ], + }, + cssContent: { + propDefinition: [ + htmlToImage, + "cssContent", + ], + }, + paperSize: { + propDefinition: [ + htmlToImage, + "paperSize", + ], + }, + landscape: { + propDefinition: [ + htmlToImage, + "landscape", + ], + }, + displayHeaderFooter: { + propDefinition: [ + htmlToImage, + "displayHeaderFooter", + ], + }, + printBackground: { + propDefinition: [ + htmlToImage, + "printBackground", + ], + }, + preferCssPageSize: { + type: "boolean", + label: "Prefer CSS Page Size", + description: "Get size from CSS styles. Default: `true`", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.htmlToImage.convertToPdf({ + $, + data: { + html_content: this.htmlContent, + css_content: this.cssContent, + paper_size: this.paperSize, + landscape: this.landscape, + displayHeaderFooter: this.displayHeaderFooter, + printBackground: this.printBackground, + preferCssPageSize: this.preferCssPageSize, + generate_pdf_url: true, + }, + }); + $.export("$summary", "Successfully converted HTML to PDF"); + return response; + }, +}; diff --git a/components/html_to_image/actions/convert-url-to-image/convert-url-to-image.mjs b/components/html_to_image/actions/convert-url-to-image/convert-url-to-image.mjs new file mode 100644 index 0000000000000..9b119b1db81b3 --- /dev/null +++ b/components/html_to_image/actions/convert-url-to-image/convert-url-to-image.mjs @@ -0,0 +1,64 @@ +import htmlToImage from "../../html_to_image.app.mjs"; + +export default { + key: "html_to_image-convert-url-to-image", + name: "Convert URL to Image", + description: "Capture a screenshot from a URL. [See the documentation](https://docs.htmlcsstoimg.com/html-to-image-api/screenshot-capture-api).", + version: "0.0.1", + type: "action", + props: { + htmlToImage, + url: { + propDefinition: [ + htmlToImage, + "url", + ], + }, + viewPortWidth: { + type: "integer", + label: "View Port Width", + description: "Width of View Port. Default value is 1080", + optional: true, + }, + viewPortHeight: { + type: "integer", + label: "View Port Height", + description: "Height of View Port. Default value is 720", + optional: true, + }, + quality: { + propDefinition: [ + htmlToImage, + "quality", + ], + }, + fullPage: { + type: "boolean", + label: "Full Page", + description: "Whether to capture full-page screenshot of the URL. Default value is `false`.", + optional: true, + }, + waitUntil: { + propDefinition: [ + htmlToImage, + "waitUntil", + ], + }, + }, + async run({ $ }) { + const response = await this.htmlToImage.convertToImage({ + $, + data: { + url: this.url, + viewPortWidth: this.viewPortWidth, + viewPortHeight: this.viewPortHeight, + quality: this.quality, + full_page: this.fullPage, + wait_till: this.waitUntil, + generate_img_url: true, + }, + }); + $.export("$summary", "Successfully converted URL to image"); + return response; + }, +}; diff --git a/components/html_to_image/actions/convert-url-to-pdf/convert-url-to-pdf.mjs b/components/html_to_image/actions/convert-url-to-pdf/convert-url-to-pdf.mjs new file mode 100644 index 0000000000000..03da750085b99 --- /dev/null +++ b/components/html_to_image/actions/convert-url-to-pdf/convert-url-to-pdf.mjs @@ -0,0 +1,64 @@ +import htmlToImage from "../../html_to_image.app.mjs"; + +export default { + key: "html_to_image-convert-url-to-pdf", + name: "Convert URL to PDF", + description: "Create a PDF from a URL. [See the documentation](https://docs.htmlcsstoimg.com/html-to-image-api/url-to-pdf-api).", + version: "0.0.1", + type: "action", + props: { + htmlToImage, + url: { + propDefinition: [ + htmlToImage, + "url", + ], + }, + paperSize: { + propDefinition: [ + htmlToImage, + "paperSize", + ], + }, + landscape: { + propDefinition: [ + htmlToImage, + "landscape", + ], + }, + displayHeaderFooter: { + propDefinition: [ + htmlToImage, + "displayHeaderFooter", + ], + }, + printBackground: { + propDefinition: [ + htmlToImage, + "printBackground", + ], + }, + waitUntil: { + propDefinition: [ + htmlToImage, + "waitUntil", + ], + }, + }, + async run({ $ }) { + const response = await this.htmlToImage.convertToPdf({ + $, + data: { + url: this.url, + paper_size: this.paperSize, + landscape: this.landscape, + displayHeaderFooter: this.displayHeaderFooter, + printBackground: this.printBackground, + wait_till: this.waitUntil, + generate_pdf_url: true, + }, + }); + $.export("$summary", "Successfully converted URL to PDF"); + return response; + }, +}; diff --git a/components/html_to_image/html_to_image.app.mjs b/components/html_to_image/html_to_image.app.mjs index 1c8e916155766..40a99bec48083 100644 --- a/components/html_to_image/html_to_image.app.mjs +++ b/components/html_to_image/html_to_image.app.mjs @@ -1,11 +1,99 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "html_to_image", - propDefinitions: {}, + propDefinitions: { + url: { + type: "string", + label: "URL", + description: "The URL of the webpage", + }, + htmlContent: { + type: "string", + label: "HTML Content", + description: "HTML Content to be used", + }, + cssContent: { + type: "string", + label: "CSS Content", + description: "CSS Content to be used", + optional: true, + }, + quality: { + type: "integer", + label: "Quality", + description: "Quality of the image should be in the range 30-100. Default value is 30.", + optional: true, + }, + paperSize: { + type: "string", + label: "Paper Size", + description: "Size of the paper", + options: [ + "A3", + "A4", + "A5", + "Letter", + "Legal", + ], + optional: true, + }, + landscape: { + type: "boolean", + label: "Landscape", + description: "Page orientation where the content is formatted horizontally. By default the page orientation is Portrait", + optional: true, + }, + displayHeaderFooter: { + type: "boolean", + label: "Display Header Footer", + description: "Generated PDF with have header and Footer on each page", + optional: true, + }, + printBackground: { + type: "boolean", + label: "Print Background", + description: "Prints any background colors or images used on the web page to the PDF. Its default value is `true`.", + optional: true, + }, + waitUntil: { + type: "integer", + label: "Wait Until", + description: " Number of seconds to wait before capturing a screenshot from the URL. Default value is 0", + optional: true, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.htmlcsstoimg.com/api/v1"; + }, + _makeRequest({ + $ = this, + path, + ...opts + }) { + return axios($, { + url: `${this._baseUrl()}${path}`, + headers: { + "CLIENT-API-KEY": this.$auth.api_key, + }, + ...opts, + }); + }, + convertToImage(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/generateImage", + ...opts, + }); + }, + convertToPdf(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/generatePdf", + ...opts, + }); }, }, }; diff --git a/components/html_to_image/package.json b/components/html_to_image/package.json index 7d1a80dcc3396..a9f9a4a32fabe 100644 --- a/components/html_to_image/package.json +++ b/components/html_to_image/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/html_to_image", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream HTML to Image Components", "main": "html_to_image.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } -} \ No newline at end of file +} diff --git a/components/ibm_cloud_natural_language_understanding/ibm_cloud_natural_language_understanding.app.mjs b/components/ibm_cloud_natural_language_understanding/ibm_cloud_natural_language_understanding.app.mjs new file mode 100644 index 0000000000000..392a93d4b5376 --- /dev/null +++ b/components/ibm_cloud_natural_language_understanding/ibm_cloud_natural_language_understanding.app.mjs @@ -0,0 +1,11 @@ +export default { + type: "app", + app: "ibm_cloud_natural_language_understanding", + propDefinitions: {}, + methods: { + // this.$auth contains connected account data + authKeys() { + console.log(Object.keys(this.$auth)); + }, + }, +}; \ No newline at end of file diff --git a/components/ibm_cloud_natural_language_understanding/package.json b/components/ibm_cloud_natural_language_understanding/package.json new file mode 100644 index 0000000000000..e26e4537978db --- /dev/null +++ b/components/ibm_cloud_natural_language_understanding/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pipedream/ibm_cloud_natural_language_understanding", + "version": "0.0.1", + "description": "Pipedream IBM Cloud - Natural Language Understanding Components", + "main": "ibm_cloud_natural_language_understanding.app.mjs", + "keywords": [ + "pipedream", + "ibm_cloud_natural_language_understanding" + ], + "homepage": "https://pipedream.com/apps/ibm_cloud_natural_language_understanding", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/components/ignisign/actions/create-signature-request/create-signature-request.mjs b/components/ignisign/actions/create-signature-request/create-signature-request.mjs new file mode 100644 index 0000000000000..10f9f8967dfa6 --- /dev/null +++ b/components/ignisign/actions/create-signature-request/create-signature-request.mjs @@ -0,0 +1,139 @@ +import { ConfigurationError } from "@pipedream/platform"; +import FormData from "form-data"; +import fs from "fs"; +import { LANGUAGE_OPTIONS } from "../../common/constants.mjs"; +import { + checkTmp, parseObject, +} from "../../common/utils.mjs"; +import ignisign from "../../ignisign.app.mjs"; + +export default { + key: "ignisign-create-signature-request", + name: "Create Signature Request", + description: "Creates a document signature request through IgniSign. [See the documentation](https://ignisign.io/docs/ignisign-api/init-signature-request)", + version: "0.0.1", + type: "action", + props: { + ignisign, + signerIds: { + propDefinition: [ + ignisign, + "signerIds", + ], + }, + documentLabel: { + type: "string", + label: "Document Label", + description: "A user-friendly label to identify the document.", + optional: true, + }, + documentDescription: { + type: "string", + label: "Document Description", + description: "A detailed, human-readable description of the document.", + optional: true, + }, + documentExternalId: { + type: "string", + label: "Document External Id", + description: "An optional external identifier that can be used to reference the document from external systems. It's a free text. Ignisign's system do not interprete it.", + optional: true, + }, + file: { + type: "string", + label: "Document File", + description: "The file to be uploaded, please provide a file from `/tmp`. To upload a file to `/tmp` folder, please follow the doc [here](https://pipedream.com/docs/code/nodejs/working-with-files/#writing-a-file-to-tmp)", + }, + title: { + type: "string", + label: "Title", + description: "The title of the signature request.", + }, + description: { + type: "string", + label: "Description", + description: "The description of the signature request.", + optional: true, + }, + expirationDateIsActivated: { + type: "boolean", + label: "Expiration Date Is Activated", + description: "Indicates whether the expiration date is activated.", + reloadProps: true, + optional: true, + }, + expirationDate: { + type: "string", + label: "Expiration Date", + description: "The expiration date. The action linked to this date is performed every 5 minutes, at 5, 10, 15... 55.", + optional: true, + hidden: true, + }, + language: { + type: "string", + label: "Language", + description: "Represents the languages for signatures supported by a signature profile.", + options: LANGUAGE_OPTIONS, + optional: true, + }, + }, + async additionalProps(props) { + props.expirationDate.hidden = !this.expirationDateIsActivated; + return {}; + }, + async run({ $ }) { + const data = new FormData(); + + const { signatureRequestId } = await this.ignisign.initSignatureRequest(); + + const { documentId } = await this.ignisign.initDocument({ + data: { + signatureRequestId, + label: this.documentLabel, + description: this.documentDescription, + externalId: this.documentExternalId, + }, + }); + + const path = checkTmp(this.file); + if (!fs.existsSync(path)) { + await this.ignisign.closeSignatureRequest({ + signatureRequestId, + }); + throw new ConfigurationError("File does not exist!"); + } + const file = fs.createReadStream(path); + data.append("file", file); + + await this.ignisign.uploadFile({ + documentId, + data, + headers: data.getHeaders(), + }); + + await this.ignisign.updateSignatureRequest({ + signatureRequestId, + data: { + title: this.title, + description: this.description, + expirationDateIsActivated: this.expirationDateIsActivated, + expirationDate: this.expirationDate, + language: this.language, + documentIds: [ + documentId, + ], + signerIds: parseObject(this.signerIds), + }, + }); + + await this.ignisign.publishSignatureRequest({ + $, + signatureRequestId, + }); + + $.export("$summary", `Successfully published signature request with ID ${signatureRequestId}`); + return { + signatureRequestId, + }; + }, +}; diff --git a/components/ignisign/actions/create-signer/create-signer.mjs b/components/ignisign/actions/create-signer/create-signer.mjs new file mode 100644 index 0000000000000..ff6431d13d2d4 --- /dev/null +++ b/components/ignisign/actions/create-signer/create-signer.mjs @@ -0,0 +1,95 @@ +import ignisign from "../../ignisign.app.mjs"; + +export default { + key: "ignisign-create-signer", + name: "Create Signer", + description: "Creates a new signer entity in IgniSign. [See the documentation](https://ignisign.io/docs/ignisign-api/create-signer)", + version: "0.0.1", + type: "action", + props: { + ignisign, + signerProfileId: { + propDefinition: [ + ignisign, + "signerProfileId", + ], + optional: true, + }, + externalId: { + propDefinition: [ + ignisign, + "externalId", + ], + optional: true, + }, + firstName: { + propDefinition: [ + ignisign, + "firstName", + ], + optional: true, + }, + lastName: { + propDefinition: [ + ignisign, + "lastName", + ], + optional: true, + }, + email: { + propDefinition: [ + ignisign, + "email", + ], + }, + phoneNumber: { + propDefinition: [ + ignisign, + "phoneNumber", + ], + optional: true, + }, + nationality: { + propDefinition: [ + ignisign, + "nationality", + ], + optional: true, + }, + birthDate: { + propDefinition: [ + ignisign, + "birthDate", + ], + optional: true, + }, + birthPlace: { + propDefinition: [ + ignisign, + "birthPlace", + ], + optional: true, + }, + birthCountry: { + propDefinition: [ + ignisign, + "birthCountry", + ], + optional: true, + }, + }, + async run({ $ }) { + const { + ignisign, + ...data + } = this; + + const response = await ignisign.createSigner({ + $, + data, + }); + + $.export("$summary", `Successfully created signer with ID: ${response.signerId}`); + return response; + }, +}; diff --git a/components/ignisign/actions/get-signature-proof/get-signature-proof.mjs b/components/ignisign/actions/get-signature-proof/get-signature-proof.mjs new file mode 100644 index 0000000000000..3904030bca8e7 --- /dev/null +++ b/components/ignisign/actions/get-signature-proof/get-signature-proof.mjs @@ -0,0 +1,47 @@ +import fs from "fs"; +import stream from "stream"; +import { promisify } from "util"; +import ignisign from "../../ignisign.app.mjs"; + +export default { + key: "ignisign-get-signature-proof", + name: "Get Signature Proof", + description: "Retrieves a proof file for a specific signature. [See the documentation](https://ignisign.io/docs/category/ignisign-api)", + version: "0.0.1", + type: "action", + props: { + ignisign, + signatureRequestId: { + propDefinition: [ + ignisign, + "signatureRequestId", + ], + }, + documentId: { + propDefinition: [ + ignisign, + "documentId", + ({ signatureRequestId }) => ({ + signatureRequestId, + }), + ], + withLabel: true, + }, + }, + async run({ $ }) { + const response = await this.ignisign.getSignatureProof({ + $, + documentId: this.documentId.value, + responseType: "stream", + }); + + const pipeline = promisify(stream.pipeline); + await pipeline(response, fs.createWriteStream(`/tmp/${this.documentId.label}`)); + + $.export("$summary", `Successfully retrieved signature proof for request ID: ${this.signatureRequestId} and saved in /tmp directory.`); + return { + filename: this.documentId.label, + filepath: `/tmp/${this.documentId.label}`, + }; + }, +}; diff --git a/components/ignisign/common/constants.mjs b/components/ignisign/common/constants.mjs new file mode 100644 index 0000000000000..bed1b9fdc4632 --- /dev/null +++ b/components/ignisign/common/constants.mjs @@ -0,0 +1,14 @@ +export const LANGUAGE_OPTIONS = [ + "EN", + "FR", + "DE", + "ES", + "IT", + "PT", + "NL", + "PL", + "JA", + "KO", + "AR", + "HE", +]; diff --git a/components/ignisign/common/utils.mjs b/components/ignisign/common/utils.mjs new file mode 100644 index 0000000000000..6c207c1f7fa3d --- /dev/null +++ b/components/ignisign/common/utils.mjs @@ -0,0 +1,36 @@ +export const camelCaseToTitleCase = (text) => { + const result = text.replace(/([A-Z])/g, " $1"); + return result.charAt(0).toUpperCase() + result.slice(1); +}; + +export const checkTmp = (filename) => { + if (!filename.startsWith("/tmp")) { + return `/tmp/${filename}`; + } + return filename; +}; + +export const parseObject = (obj) => { + if (!obj) return undefined; + + if (Array.isArray(obj)) { + return obj.map((item) => { + if (typeof item === "string") { + try { + return JSON.parse(item); + } catch (e) { + return item; + } + } + return item; + }); + } + if (typeof obj === "string") { + try { + return JSON.parse(obj); + } catch (e) { + return obj; + } + } + return obj; +}; diff --git a/components/ignisign/ignisign.app.mjs b/components/ignisign/ignisign.app.mjs new file mode 100644 index 0000000000000..39a125d97d540 --- /dev/null +++ b/components/ignisign/ignisign.app.mjs @@ -0,0 +1,303 @@ +import { axios } from "@pipedream/platform"; + +export default { + type: "app", + app: "ignisign", + propDefinitions: { + signatureProfileId: { + type: "string", + label: "Signature Profile ID", + description: "The unique identifier of the signature profile", + async options() { + const data = await this.listSignatureProfiles(); + + return data.map(({ + _id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + signerProfileId: { + type: "string", + label: "Signer Profile ID", + description: "The unique identifier of the signer profile", + async options() { + const data = await this.listSignerProfiles(); + + return data.map(({ + _id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + signerIds: { + type: "string[]", + label: "Signer IDs", + description: "The unique identifier of the signers", + async options({ page }) { + const { signers } = await this.listSigners({ + params: { + page, + }, + }); + + return signers.map(({ + signerId: value, email: label, + }) => ({ + label, + value, + })); + }, + }, + signatureRequestId: { + type: "string", + label: "Signature Request ID", + description: "The unique identifier of the asignature requests", + async options({ page }) { + const { signatureRequests } = await this.listSignatureRequests({ + params: { + page: page + 1, + }, + }); + + return signatureRequests.filter(({ status }) => status === "COMPLETED").map(({ + _id: value, title: label, + }) => ({ + label, + value, + })); + }, + }, + documentId: { + type: "string", + label: "Document ID", + description: "The unique identifier of the asignature request documents", + async options({ + page, signatureRequestId, + }) { + const { documents } = await this.getSignatureRequestContext({ + signatureRequestId, + params: { + page, + }, + }); + + return documents.map(({ + _id: value, fileName: label, + }) => ({ + label, + value, + })); + }, + }, + externalId: { + type: "string", + label: "External ID", + description: "An external identifier for the signer", + }, + email: { + type: "string", + label: "Email", + description: "The email of the signer", + }, + firstName: { + type: "string", + label: "First Name", + description: "The first name of the signer", + }, + lastName: { + type: "string", + label: "Last Name", + description: "The last name of the signer", + }, + phoneNumber: { + type: "string", + label: "Phone Number", + description: "The phone number of the signer", + }, + nationality: { + type: "string", + label: "Nationality", + description: "The nationality of the signer in ISO 3166-1 alpha-2", + }, + birthDate: { + type: "string", + label: "Birth Date", + description: "The birth date of the signer", + }, + birthPlace: { + type: "string", + label: "Birth Place", + description: "The place of birth of the signer", + }, + birthCountry: { + type: "string", + label: "Birth Country", + description: "The country of birth of the signer in ISO 3166-1 alpha-2", + }, + }, + methods: { + _baseUrl(envs = `/applications/${this.$auth.app_id}/envs/${this.$auth.app_env}`) { + return `https://api.ignisign.io/v4${envs}`; + }, + _headers(headers = {}) { + return { + ...headers, + "Authorization": `Bearer ${this.$auth.oauth_access_token}`, + }; + }, + _makeRequest({ + $ = this, path, headers, envs, ...opts + }) { + return axios($, { + url: this._baseUrl(envs) + path, + headers: this._headers(headers), + ...opts, + }); + }, + createSigner(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/signers", + ...opts, + }); + }, + listSignatureProfiles(opts = {}) { + return this._makeRequest({ + path: "/signature-profiles", + ...opts, + }); + }, + listSignatureRequests(opts = {}) { + return this._makeRequest({ + path: "/signature-requests", + ...opts, + }); + }, + listSigners(opts = {}) { + return this._makeRequest({ + path: "/signers-paginate", + ...opts, + }); + }, + listSignerProfiles(opts = {}) { + return this._makeRequest({ + path: "/signer-profiles", + ...opts, + }); + }, + initDocument(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/init-documents", + ...opts, + }); + }, + initSignatureRequest(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/signature-requests", + ...opts, + }); + }, + updateSignatureRequest({ + signatureRequestId, ...opts + }) { + return this._makeRequest({ + method: "PUT", + path: `/signature-requests/${signatureRequestId}`, + envs: "", + ...opts, + }); + }, + getSignatureRequestContext({ signatureRequestId }) { + return this._makeRequest({ + path: `/signature-requests/${signatureRequestId}/context`, + envs: "", + }); + }, + getSignatureProof({ + documentId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/documents/${documentId}/signature-proof/PDF_WITH_SIGNATURES`, + envs: "", + ...opts, + }); + }, + getSignerProfileInputs({ signerProfileId }) { + return this._makeRequest({ + path: `/signer-profiles/${signerProfileId}/inputs-needed`, + }); + }, + getSignerDetails({ signerId }) { + return this._makeRequest({ + path: `/signers/${signerId}/details`, + }); + }, + uploadFile({ + documentId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/documents/${documentId}/file`, + envs: "", + ...opts, + }); + }, + publishSignatureRequest({ signatureRequestId }) { + return this._makeRequest({ + method: "POST", + path: `/signature-requests/${signatureRequestId}/publish`, + envs: "", + }); + }, + closeSignatureRequest({ signatureRequestId }) { + return this._makeRequest({ + method: "POST", + path: `/signature-requests/${signatureRequestId}/close`, + envs: "", + }); + }, + createWebhook(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/webhooks", + ...opts, + }); + }, + deleteWebhook(webhookId) { + return this._makeRequest({ + method: "DELETE", + path: `/webhooks/${webhookId}`, + envs: "", + }); + }, + disableWebhookEvents(webhookId) { + return this._makeRequest({ + method: "POST", + path: `/webhooks/${webhookId}/disabled-events`, + envs: "", + data: { + topics: { + "ALL": true, + "APP": true, + "SIGNATURE": true, + "SIGNATURE_REQUEST": true, + "SIGNER": true, + "SIGNATURE_PROFILE": true, + "SIGNATURE_SESSION": true, + "SIGNATURE_SIGNER_IMAGE": true, + "ID_PROOFING": true, + "SIGNER_AUTH": true, + }, + }, + }); + }, + }, +}; diff --git a/components/ignisign/package.json b/components/ignisign/package.json new file mode 100644 index 0000000000000..1b6342af79b25 --- /dev/null +++ b/components/ignisign/package.json @@ -0,0 +1,22 @@ +{ + "name": "@pipedream/ignisign", + "version": "0.1.0", + "description": "Pipedream IgniSign Components", + "main": "ignisign.app.mjs", + "keywords": [ + "pipedream", + "ignisign" + ], + "homepage": "https://pipedream.com/apps/ignisign", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3", + "form-data": "^4.0.1", + "fs": "^0.0.1-security", + "stream": "^0.0.3", + "util": "^0.12.5" + } +} diff --git a/components/ignisign/sources/new-signature-proof-instant/new-signature-proof-instant.mjs b/components/ignisign/sources/new-signature-proof-instant/new-signature-proof-instant.mjs new file mode 100644 index 0000000000000..013057fea24f7 --- /dev/null +++ b/components/ignisign/sources/new-signature-proof-instant/new-signature-proof-instant.mjs @@ -0,0 +1,54 @@ +import ignisign from "../../ignisign.app.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + key: "ignisign-new-signature-proof-instant", + name: "New Signature Proof Instant", + description: "Emit new event when a signature proof is generated. [See the documentation](https://ignisign.io/docs/webhooks/signatureproof)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ignisign, + http: { + type: "$.interface.http", + customResponse: true, + }, + db: "$.service.db", + }, + methods: { + _getHookId() { + return this.db.get("hookId"); + }, + _setHookId(hookId) { + this.db.set("hookId", hookId); + }, + }, + hooks: { + async activate() { + const [ + response, + ] = await this.ignisign.createWebhook({ + data: { + url: this.http.endpoint, + description: this.description, + }, + }); + await this.ignisign.disableWebhookEvents(response._id); + this._setHookId(response._id); + }, + async deactivate() { + const webhookId = this._getHookId(); + await this.ignisign.deleteWebhook(webhookId); + }, + }, + async run({ body }) { + const ts = Date.parse(new Date()); + this.$emit(body, { + id: `${body.signatureRequestId}-${ts}`, + summary: `New signature proof generated for signature request ID: ${body.content.signatureRequestId}`, + ts: ts, + }); + }, + sampleEmit, +}; diff --git a/components/ignisign/sources/new-signature-proof-instant/test-event.mjs b/components/ignisign/sources/new-signature-proof-instant/test-event.mjs new file mode 100644 index 0000000000000..6a55d69c5de4f --- /dev/null +++ b/components/ignisign/sources/new-signature-proof-instant/test-event.mjs @@ -0,0 +1,23 @@ +export default { + "appId": "appId_e5d6bcfb-e9f4-4858-8027-b65e1a170bd7", + "appEnv": "DEVELOPMENT", + "topic": "SIGNATURE_PROOF", + "action": "GENERATED", + "msgNature": "SUCCESS", + "content": { + "appEnv": "DEVELOPMENT", + "appId": "appId_e5d6bcfb-e9f4-4858-8027-b65e1a170bd7", + "signatureRequestId": "672d206d10e44e00125eac63", + "documents": [ + { + "documentId": "672d206d9902ba0012e9a7f0", + "documentExternalId": "123123123", + "token": "5wvyV2d0Q0SapHS0PcdgrZgUbTHbUEVVq3uaZd8CcLIAA", + "url": "https://sign.ignisign.io/signature-requests/672d206d10e44e00125eac63/proofs?t=5wvyV2d0Q0SapHS0PcdgrZgUbTHbUEVVq3uaZd8CcLIAA" + } + ], + "signatureProofToken": "9CdzHRTIQFZZlGSBciSUmnLXahOl4J0dZqV3wwl9XzYIAA", + "signatureProofUrl": "https://sign.ignisign.io/signature-requests/672d206d10e44e00125eac63/proofs?t=9CdzHRTIQFZZlGSBciSUmnLXahOl4J0dZqV3wwl9XzYIAA" + }, + "verificationToken": "N3gVBJB4TZZuE3MZSBeCqKo25ijHHFU2SvdiIMxm8znMAA" +} \ No newline at end of file diff --git a/components/insertchat/actions/create-lead/create-lead.mjs b/components/insertchat/actions/create-lead/create-lead.mjs new file mode 100644 index 0000000000000..1d1fce45231fd --- /dev/null +++ b/components/insertchat/actions/create-lead/create-lead.mjs @@ -0,0 +1,77 @@ +import insertchat from "../../insertchat.app.mjs"; + +export default { + key: "insertchat-create-lead", + name: "Create Lead", + description: "Creates a new lead within Insertchat. [See the documentation](https://www.postman.com/gold-star-239225/insertchat/request/uiugp1c/create-a-lead)", + version: "0.0.1", + type: "action", + props: { + insertchat, + chatbotId: { + propDefinition: [ + insertchat, + "chatbotId", + ], + }, + firstName: { + type: "string", + label: "First Name", + description: "First name of the lead", + optional: true, + }, + lastName: { + type: "string", + label: "Last Name", + description: "Last name of the lead", + optional: true, + }, + email: { + type: "string", + label: "Email", + description: "Email address of the lead", + optional: true, + }, + phone: { + type: "string", + label: "Phone", + description: "Phone number of the lead", + optional: true, + }, + address: { + type: "string", + label: "Address", + description: "Address of the lead", + optional: true, + }, + website: { + type: "string", + label: "Website", + description: "Website of the lead", + optional: true, + }, + company: { + type: "string", + label: "Company", + description: "Company of the lead", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.insertchat.createLead({ + $, + data: { + widget_uid: this.chatbotId, + first_name: this.firstName, + last_name: this.lastName, + email: this.email, + phone: this.phone, + address: this.address, + website: this.website, + company: this.company, + }, + }); + $.export("$summary", `Created lead with ID: ${response.uid}`); + return response; + }, +}; diff --git a/components/insertchat/actions/delete-lead/delete-lead.mjs b/components/insertchat/actions/delete-lead/delete-lead.mjs new file mode 100644 index 0000000000000..e908d3437073a --- /dev/null +++ b/components/insertchat/actions/delete-lead/delete-lead.mjs @@ -0,0 +1,26 @@ +import insertchat from "../../insertchat.app.mjs"; + +export default { + key: "insertchat-delete-lead", + name: "Delete Lead", + description: "Deletes an existing lead from InsertChat. [See the documentation](https://www.postman.com/gold-star-239225/insertchat/request/2vgc20j/delete-a-lead)", + version: "0.0.1", + type: "action", + props: { + insertchat, + leadId: { + propDefinition: [ + insertchat, + "leadId", + ], + }, + }, + async run({ $ }) { + const response = await this.insertchat.deleteLead({ + $, + leadId: this.leadId, + }); + $.export("$summary", `Successfully deleted lead with ID: ${this.leadId}`); + return response; + }, +}; diff --git a/components/insertchat/actions/push-message-existing-chat/push-message-existing-chat.mjs b/components/insertchat/actions/push-message-existing-chat/push-message-existing-chat.mjs new file mode 100644 index 0000000000000..996bc0a1fe873 --- /dev/null +++ b/components/insertchat/actions/push-message-existing-chat/push-message-existing-chat.mjs @@ -0,0 +1,55 @@ +import insertchat from "../../insertchat.app.mjs"; + +export default { + key: "insertchat-push-message-existing-chat", + name: "Push Message to Existing Chat", + description: "Pushes a new message into an existing chat session in InsertChat. [See the documentation](https://www.postman.com/gold-star-239225/insertchat/request/me7mcwa/push-a-message-into-a-chat-session)", + version: "0.0.1", + type: "action", + props: { + insertchat, + chatbotId: { + propDefinition: [ + insertchat, + "chatbotId", + ], + }, + chatSessionId: { + propDefinition: [ + insertchat, + "chatSessionId", + (c) => ({ + chatbotId: c.chatbotId, + }), + ], + }, + role: { + type: "string", + label: "Role", + description: "Role to send message as", + options: [ + "user", + "assistant", + ], + }, + message: { + type: "string", + label: "Message Content", + description: "The content of the message to be pushed into the chat session", + }, + }, + run({ $ }) { + // method works, but times out if we await the response + this.insertchat.pushMessage({ + $, + data: new URLSearchParams({ + widget_uid: this.chatbotId, + chat_uid: this.chatSessionId, + role: this.role, + input: this.message, + }), + }); + $.export("$summary", `Successfully pushed message to chat session ${this.chatSessionId}`); + // nothing to return + }, +}; diff --git a/components/insertchat/insertchat.app.mjs b/components/insertchat/insertchat.app.mjs new file mode 100644 index 0000000000000..45b944864f5c7 --- /dev/null +++ b/components/insertchat/insertchat.app.mjs @@ -0,0 +1,162 @@ +import { axios } from "@pipedream/platform"; + +export default { + type: "app", + app: "insertchat", + propDefinitions: { + chatbotId: { + type: "string", + label: "Chatbot ID", + description: "The unique identifier for the chatbot", + async options({ page }) { + const { data } = await this.listChatbots({ + params: { + page: page + 1, + }, + }); + return data?.map(({ + uid: value, label, + }) => ({ + value, + label, + })) || []; + }, + }, + leadId: { + type: "string", + label: "Lead ID", + description: "The unique identifier for the lead", + async options({ page }) { + const { data } = await this.listLeads({ + params: { + page: page + 1, + }, + }); + return data?.map(({ + uid: value, first_name: firstName, last_name: lastName, + }) => ({ + value, + label: firstName || lastName + ? (`${firstName} ${lastName}`).trim() + : value, + })) || []; + }, + }, + chatSessionId: { + type: "string", + label: "Chat Session ID", + description: "The unique identifier for the chat session", + async options({ + chatbotId, page, + }) { + const { data } = await this.listChatSessions({ + chatbotId, + page: page + 1, + }); + return data?.map(({ + uid: value, label, + }) => ({ + value, + label, + })) || []; + }, + }, + }, + methods: { + _appId() { + return this.$auth.app_uid; + }, + _baseUrl() { + return "https://api.insertchat.com/v1"; + }, + _makeRequest(opts = {}) { + const { + $ = this, + path, + headers, + ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + url: `${this._baseUrl()}${path}`, + headers: { + ...headers, + Authorization: `Bearer ${this.$auth.oauth_access_token}`, + }, + }); + }, + listChatbots(opts = {}) { + return this._makeRequest({ + path: `/${this._appId()}/widgets`, + ...opts, + }); + }, + listLeads(opts = {}) { + return this._makeRequest({ + path: `/${this._appId()}/contacts`, + ...opts, + }); + }, + listChatSessions({ + chatbotId, ...opts + }) { + return this._makeRequest({ + path: `/${this._appId()}/chats/history/${chatbotId}?expand[0]=messages`, + ...opts, + }); + }, + createLead(opts = {}) { + return this._makeRequest({ + method: "POST", + path: `/${this._appId()}/contacts`, + ...opts, + }); + }, + deleteLead({ + leadId, ...opts + }) { + return this._makeRequest({ + method: "DELETE", + path: `/${this._appId()}/contacts/${leadId}`, + ...opts, + }); + }, + pushMessage(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/embeds/messages", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + ...opts, + }); + }, + async *paginate({ + fn, + args, + max, + }) { + args = { + ...args, + params: { + ...args?.params, + page: 1, + }, + }; + let done, count = 0; + do { + const { + data, meta, + } = await fn(args); + for (const item of data) { + yield item; + if (max && ++count >= max) { + return; + } + done = args.params.page === meta.last_page; + args.params.page++; + } + } while (!done); + }, + }, +}; diff --git a/components/insertchat/package.json b/components/insertchat/package.json new file mode 100644 index 0000000000000..7b39139ab935f --- /dev/null +++ b/components/insertchat/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/insertchat", + "version": "0.1.0", + "description": "Pipedream InsertChat Components", + "main": "insertchat.app.mjs", + "keywords": [ + "pipedream", + "insertchat" + ], + "homepage": "https://pipedream.com/apps/insertchat", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" + } +} diff --git a/components/insertchat/sources/common/base.mjs b/components/insertchat/sources/common/base.mjs new file mode 100644 index 0000000000000..30c3e68d735a4 --- /dev/null +++ b/components/insertchat/sources/common/base.mjs @@ -0,0 +1,74 @@ +import insertchat from "../../insertchat.app.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + props: { + insertchat, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + hooks: { + async deploy() { + await this.processEvent(25); + }, + }, + methods: { + _getLastTs() { + return this.db.get("lastTs") || 0; + }, + _setLastTs(lastTs) { + this.db.set("lastTs", lastTs); + }, + getResourceFn() { + throw new Error("getResourceFn is not implemented"); + }, + getSummary() { + throw new Error("getSummary is not implemented"); + }, + getArgs() { + return {}; + }, + getTsField() { + return "created_at"; + }, + generateMeta(item) { + return { + id: item.uid, + summary: this.getSummary(item), + ts: Date.parse(item[this.getTsField()]), + }; + }, + async processEvent(max) { + const lastTs = this._getLastTs(); + let maxTs = lastTs; + const resourceFn = this.getResourceFn(); + const args = this.getArgs(); + const tsField = this.getTsField(); + + const items = this.insertchat.paginate({ + fn: resourceFn, + args, + max, + }); + + for await (const item of items) { + const ts = Date.parse(item[tsField]); + if (ts > lastTs) { + const meta = this.generateMeta(item); + this.$emit(item, meta); + maxTs = Math.max(ts, maxTs); + } + } + + this._setLastTs(maxTs); + }, + }, + async run() { + await this.processEvent(); + }, +}; diff --git a/components/insertchat/sources/new-ai-chatbot/new-ai-chatbot.mjs b/components/insertchat/sources/new-ai-chatbot/new-ai-chatbot.mjs new file mode 100644 index 0000000000000..3bd9c821f759d --- /dev/null +++ b/components/insertchat/sources/new-ai-chatbot/new-ai-chatbot.mjs @@ -0,0 +1,20 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "insertchat-new-ai-chatbot", + name: "New AI Chatbot", + description: "Emit new event when a new AI chatbot is created.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.insertchat.listChatbots; + }, + getSummary(item) { + return `New Chatbot ID: ${item.uid}`; + }, + }, +}; diff --git a/components/insertchat/sources/new-chat-session/new-chat-session.mjs b/components/insertchat/sources/new-chat-session/new-chat-session.mjs new file mode 100644 index 0000000000000..49d433e88e9c9 --- /dev/null +++ b/components/insertchat/sources/new-chat-session/new-chat-session.mjs @@ -0,0 +1,34 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "insertchat-new-chat-session", + name: "New Chat Session", + description: "Emit new event when a new chat session is initiated.", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + chatbotId: { + propDefinition: [ + common.props.insertchat, + "chatbotId", + ], + }, + }, + methods: { + ...common.methods, + getResourceFn() { + return this.insertchat.listChatSessions; + }, + getArgs() { + return { + chatbotId: this.chatbotId, + }; + }, + getSummary(item) { + return `New Chat Session ID: ${item.uid}`; + }, + }, +}; diff --git a/components/insertchat/sources/new-lead/new-lead.mjs b/components/insertchat/sources/new-lead/new-lead.mjs new file mode 100644 index 0000000000000..17ab6f9ceeffe --- /dev/null +++ b/components/insertchat/sources/new-lead/new-lead.mjs @@ -0,0 +1,20 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "insertchat-new-lead", + name: "New Lead Created", + description: "Emit new event when a new lead is created.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.insertchat.listLeads; + }, + getSummary(item) { + return `New Lead ID: ${item.uid}`; + }, + }, +}; diff --git a/components/intercom/actions/create-note/create-note.mjs b/components/intercom/actions/create-note/create-note.mjs index ba5e85e80a117..978dad18d5a79 100644 --- a/components/intercom/actions/create-note/create-note.mjs +++ b/components/intercom/actions/create-note/create-note.mjs @@ -4,7 +4,7 @@ export default { key: "intercom-create-note", name: "Create Note", description: "Creates a note for a specific user. [See the docs here](https://developers.intercom.com/intercom-api-reference/reference/create-note-for-contact)", - version: "0.0.3", + version: "0.0.4", type: "action", props: { intercom, diff --git a/components/intercom/actions/send-incoming-message/send-incoming-message.mjs b/components/intercom/actions/send-incoming-message/send-incoming-message.mjs index 75178c8f21849..516f3563c1e06 100644 --- a/components/intercom/actions/send-incoming-message/send-incoming-message.mjs +++ b/components/intercom/actions/send-incoming-message/send-incoming-message.mjs @@ -4,7 +4,7 @@ export default { key: "intercom-send-incoming-message", name: "Send Incoming Message", description: "Send a message from a user into your Intercom app. [See the docs here](https://developers.intercom.com/intercom-api-reference/reference/create-a-conversation)", - version: "0.0.3", + version: "0.0.4", type: "action", props: { intercom, diff --git a/components/intercom/actions/upsert-contact/upsert-contact.mjs b/components/intercom/actions/upsert-contact/upsert-contact.mjs new file mode 100644 index 0000000000000..673dbf8a5a567 --- /dev/null +++ b/components/intercom/actions/upsert-contact/upsert-contact.mjs @@ -0,0 +1,131 @@ +import { ROLE_OPTIONS } from "../../common/constants.mjs"; +import intercom from "../../intercom.app.mjs"; + +export default { + key: "intercom-upsert-contact", + name: "Upsert Contact", + description: "Create a new contact. If there is already a contact with the email provided, the existing contact will be updated. [See the docs here](https://developers.intercom.com/docs/references/rest-api/api.intercom.io/contacts/createcontact)", + version: "0.0.1", + type: "action", + props: { + intercom, + email: { + type: "string", + label: "Email", + description: "The contact's email.", + }, + role: { + type: "string", + label: "Role", + description: "The role of the contact.", + options: ROLE_OPTIONS, + optional: true, + }, + externalId: { + type: "string", + label: "External Id", + description: "A unique identifier for the contact which is given to Intercom.", + optional: true, + }, + phone: { + type: "string", + label: "Phone", + description: "The contact's phone number.", + optional: true, + }, + name: { + type: "string", + label: "Name", + description: "The contact's name.", + optional: true, + }, + avatar: { + type: "string", + label: "Avatar", + description: "An image URL containing the avatar of a contact.", + optional: true, + }, + unsubscribedFromEmails: { + type: "boolean", + label: "Unsubscribed From Emails", + description: "Whether the contact is unsubscribed from emails.", + optional: true, + }, + customAttributes: { + type: "object", + label: "Custom Attributes", + description: "The custom attributes which are set for the contact.", + optional: true, + }, + }, + async run({ $ }) { + let response = {}; + let requestType = "created"; + let data = { + email: this.email, + role: this.role, + externalId: this.externalId, + phone: this.phone, + name: this.name, + avatar: this.avatar, + unsubscribedFromEmails: this.unsubscribedFromEmails, + customAttributes: this.customAttributes, + }; + + data = Object.entries(data).filter(([ + , + value, + ]) => (value != "" && value != undefined)) + .reduce((obj, [ + key, + value, + ]) => Object.assign(obj, { + [key]: value, + }), {}); + + const { + data: contact, total_count: total, + } = await this.intercom.searchContact({ + data: { + query: { + operator: "AND", + value: [ + { + field: "email", + operator: "=", + value: this.email, + }, + ], + }, + pagination: { + per_page: 1, + }, + }, + }); + + if (total) { + const { + id: contactId, + // eslint-disable-next-line no-unused-vars + owner_id, + ...contactInfos + } = contact[0]; + response = await this.intercom.updateContact({ + $, + contactId, + data: { + ...contactInfos, + ...data, + }, + }); + requestType = "updated"; + } else { + response = await this.intercom.createContact({ + $, + data, + }); + } + $.export("$summary", `Successfully ${requestType} contact with ID ${response.id}`); + return response; + }, +}; diff --git a/components/intercom/common/constants.mjs b/components/intercom/common/constants.mjs new file mode 100644 index 0000000000000..cfa8b3e7d544e --- /dev/null +++ b/components/intercom/common/constants.mjs @@ -0,0 +1,10 @@ +export const ROLE_OPTIONS = [ + { + label: "User", + value: "user", + }, + { + label: "Lead", + value: "lead", + }, +]; diff --git a/components/intercom/intercom.app.mjs b/components/intercom/intercom.app.mjs index d0749fa1a6a7d..b432e2e3e4f33 100644 --- a/components/intercom/intercom.app.mjs +++ b/components/intercom/intercom.app.mjs @@ -45,14 +45,13 @@ export default { * @params {Object} [opts.data] - The request body * @returns {*} The response may vary depending on the specific API request. */ - async makeRequest(opts) { - const { - method, - url, - endpoint, - data, - $, - } = opts; + async makeRequest({ + method, + url, + endpoint, + $, + ...opts + }) { const config = { method, url: url ?? `https://api.intercom.io/${endpoint}`, @@ -60,7 +59,7 @@ export default { Authorization: `Bearer ${this.$auth.oauth_access_token}`, Accept: "application/json", }, - data, + ...opts, }; return axios($ || this, config); }, @@ -210,6 +209,29 @@ export default { $, }); }, + searchContact(opts = {}) { + return this.makeRequest({ + method: "POST", + endpoint: "contacts/search", + ...opts, + }); + }, + createContact(opts = {}) { + return this.makeRequest({ + method: "POST", + endpoint: "contacts", + ...opts, + }); + }, + updateContact({ + contactId, ...opts + }) { + return this.makeRequest({ + method: "PUT", + endpoint: `contacts/${contactId}`, + ...opts, + }); + }, /** * Create an incoming message from a user * @params {Object} data - The request body parameters including a `from` object and diff --git a/components/intercom/package.json b/components/intercom/package.json index 0cd9b5d28f76e..7f4c0cd330f9d 100644 --- a/components/intercom/package.json +++ b/components/intercom/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/intercom", - "version": "0.4.0", + "version": "0.5.0", "description": "Pipedream Intercom Components", "main": "intercom.app.mjs", "keywords": [ diff --git a/components/intercom/sources/conversation-closed/conversation-closed.mjs b/components/intercom/sources/conversation-closed/conversation-closed.mjs index 6215d45ab3c46..e5f7fcca44126 100644 --- a/components/intercom/sources/conversation-closed/conversation-closed.mjs +++ b/components/intercom/sources/conversation-closed/conversation-closed.mjs @@ -5,7 +5,7 @@ export default { key: "intercom-conversation-closed", name: "New Closed Conversation", description: "Emit new event each time a conversation is closed.", - version: "0.0.5", + version: "0.0.6", type: "source", dedupe: "unique", methods: { diff --git a/components/intercom/sources/lead-added-email/lead-added-email.mjs b/components/intercom/sources/lead-added-email/lead-added-email.mjs index 2e7137ead1547..50d4d45f846eb 100644 --- a/components/intercom/sources/lead-added-email/lead-added-email.mjs +++ b/components/intercom/sources/lead-added-email/lead-added-email.mjs @@ -5,7 +5,7 @@ export default { key: "intercom-lead-added-email", name: "Lead Added Email", description: "Emit new event each time a lead adds their email address.", - version: "0.0.5", + version: "0.0.6", type: "source", dedupe: "unique", methods: { diff --git a/components/intercom/sources/new-admin-reply/new-admin-reply.mjs b/components/intercom/sources/new-admin-reply/new-admin-reply.mjs index 907a1aaada9ac..3eef629455950 100644 --- a/components/intercom/sources/new-admin-reply/new-admin-reply.mjs +++ b/components/intercom/sources/new-admin-reply/new-admin-reply.mjs @@ -5,7 +5,7 @@ export default { key: "intercom-new-admin-reply", name: "New Reply From Admin", description: "Emit new event each time an admin replies to a conversation.", - version: "0.0.5", + version: "0.0.6", type: "source", dedupe: "unique", methods: { diff --git a/components/intercom/sources/new-company/new-company.mjs b/components/intercom/sources/new-company/new-company.mjs index fe46f463b84c1..b6de1a661f35c 100644 --- a/components/intercom/sources/new-company/new-company.mjs +++ b/components/intercom/sources/new-company/new-company.mjs @@ -5,7 +5,7 @@ export default { key: "intercom-new-company", name: "New Companies", description: "Emit new event each time a new company is added.", - version: "0.0.5", + version: "0.0.6", type: "source", dedupe: "unique", methods: { diff --git a/components/intercom/sources/new-conversation-rating-added/new-conversation-rating-added.mjs b/components/intercom/sources/new-conversation-rating-added/new-conversation-rating-added.mjs index c6c652cc6acf4..d4de8c5b9e826 100644 --- a/components/intercom/sources/new-conversation-rating-added/new-conversation-rating-added.mjs +++ b/components/intercom/sources/new-conversation-rating-added/new-conversation-rating-added.mjs @@ -5,7 +5,7 @@ export default { key: "intercom-new-conversation-rating-added", name: "New Conversation Rating Added", description: "Emit new event each time a new rating is added to a conversation.", - version: "0.0.1", + version: "0.0.2", type: "source", dedupe: "unique", methods: { diff --git a/components/intercom/sources/new-conversation/new-conversation.mjs b/components/intercom/sources/new-conversation/new-conversation.mjs index 547c6f3a0879d..6a1c0315b020c 100644 --- a/components/intercom/sources/new-conversation/new-conversation.mjs +++ b/components/intercom/sources/new-conversation/new-conversation.mjs @@ -5,7 +5,7 @@ export default { key: "intercom-new-conversation", name: "New Conversations", description: "Emit new event each time a new conversation is added.", - version: "0.0.5", + version: "0.0.6", type: "source", dedupe: "unique", methods: { diff --git a/components/intercom/sources/new-event/new-event.mjs b/components/intercom/sources/new-event/new-event.mjs index c4ee3fa096183..b3f7ba6c7c4a0 100644 --- a/components/intercom/sources/new-event/new-event.mjs +++ b/components/intercom/sources/new-event/new-event.mjs @@ -5,7 +5,7 @@ export default { key: "intercom-new-event", name: "New Event", description: "Emit new event for each new Intercom event for a user.", - version: "0.0.5", + version: "0.0.6", type: "source", dedupe: "unique", props: { diff --git a/components/intercom/sources/new-lead/new-lead.mjs b/components/intercom/sources/new-lead/new-lead.mjs index dae88ca980589..64a999cd8d86b 100644 --- a/components/intercom/sources/new-lead/new-lead.mjs +++ b/components/intercom/sources/new-lead/new-lead.mjs @@ -5,7 +5,7 @@ export default { key: "intercom-new-lead", name: "New Leads", description: "Emit new event each time a new lead is added.", - version: "0.0.5", + version: "0.0.6", type: "source", dedupe: "unique", methods: { diff --git a/components/intercom/sources/new-topic/new-topic.mjs b/components/intercom/sources/new-topic/new-topic.mjs index edd04e0c7407e..2c1a431e49b15 100644 --- a/components/intercom/sources/new-topic/new-topic.mjs +++ b/components/intercom/sources/new-topic/new-topic.mjs @@ -1,12 +1,12 @@ -import app from "../../intercom.app.mjs"; -import { v4 as uuid } from "uuid"; import crypto from "crypto"; +import { v4 as uuid } from "uuid"; +import app from "../../intercom.app.mjs"; export default { key: "intercom-new-topic", name: "New Topic (Instant)", description: "Emit new event for each new topic that you subscribed in your webhook. [See more here](https://developers.intercom.com/building-apps/docs/setting-up-webhooks).", - version: "0.0.2", + version: "0.0.3", type: "source", dedupe: "unique", props: { diff --git a/components/intercom/sources/new-unsubscription/new-unsubscription.mjs b/components/intercom/sources/new-unsubscription/new-unsubscription.mjs index 53e222a11c801..be8e1eef2fd32 100644 --- a/components/intercom/sources/new-unsubscription/new-unsubscription.mjs +++ b/components/intercom/sources/new-unsubscription/new-unsubscription.mjs @@ -5,7 +5,7 @@ export default { key: "intercom-new-unsubscription", name: "New Unsubscriptions", description: "Emit new event each time a user unsubscribes from receiving emails.", - version: "0.0.5", + version: "0.0.6", type: "source", dedupe: "unique", methods: { diff --git a/components/intercom/sources/new-user-reply/new-user-reply.mjs b/components/intercom/sources/new-user-reply/new-user-reply.mjs index a02030a437f3a..44be581762364 100644 --- a/components/intercom/sources/new-user-reply/new-user-reply.mjs +++ b/components/intercom/sources/new-user-reply/new-user-reply.mjs @@ -5,7 +5,7 @@ export default { key: "intercom-new-user-reply", name: "New Reply From User", description: "Emit new event each time a user replies to a conversation.", - version: "0.0.5", + version: "0.0.6", type: "source", dedupe: "unique", methods: { diff --git a/components/intercom/sources/new-user/new-user.mjs b/components/intercom/sources/new-user/new-user.mjs index 6f27c94dbda24..09fc3c0172a58 100644 --- a/components/intercom/sources/new-user/new-user.mjs +++ b/components/intercom/sources/new-user/new-user.mjs @@ -5,7 +5,7 @@ export default { key: "intercom-new-user", name: "New Users", description: "Emit new event each time a new user is added.", - version: "0.0.5", + version: "0.0.6", type: "source", dedupe: "unique", methods: { diff --git a/components/intercom/sources/tag-added-to-conversation/tag-added-to-conversation.mjs b/components/intercom/sources/tag-added-to-conversation/tag-added-to-conversation.mjs index 95efa65929810..bede6b1036d92 100644 --- a/components/intercom/sources/tag-added-to-conversation/tag-added-to-conversation.mjs +++ b/components/intercom/sources/tag-added-to-conversation/tag-added-to-conversation.mjs @@ -5,7 +5,7 @@ export default { key: "intercom-tag-added-to-conversation", name: "Tag Added To Conversation", description: "Emit new event each time a new tag is added to a conversation.", - version: "0.0.5", + version: "0.0.6", type: "source", dedupe: "unique", methods: { diff --git a/components/intercom/sources/tag-added-to-lead/tag-added-to-lead.mjs b/components/intercom/sources/tag-added-to-lead/tag-added-to-lead.mjs index 326b2c85f6504..31bf4134855ab 100644 --- a/components/intercom/sources/tag-added-to-lead/tag-added-to-lead.mjs +++ b/components/intercom/sources/tag-added-to-lead/tag-added-to-lead.mjs @@ -5,7 +5,7 @@ export default { key: "intercom-tag-added-to-lead", name: "Tag Added To Lead", description: "Emit new event each time a new tag is added to a lead.", - version: "0.0.5", + version: "0.0.6", type: "source", dedupe: "unique", methods: { diff --git a/components/intercom/sources/tag-added-to-user/tag-added-to-user.mjs b/components/intercom/sources/tag-added-to-user/tag-added-to-user.mjs index 2bb250640625a..648c2d1455870 100644 --- a/components/intercom/sources/tag-added-to-user/tag-added-to-user.mjs +++ b/components/intercom/sources/tag-added-to-user/tag-added-to-user.mjs @@ -5,7 +5,7 @@ export default { key: "intercom-tag-added-to-user", name: "Tag Added To User", description: "Emit new event each time a new tag is added to a user.", - version: "0.0.5", + version: "0.0.6", type: "source", dedupe: "unique", methods: { diff --git a/components/jira/package.json b/components/jira/package.json index e12af30ad40e6..a40a0d335e23c 100644 --- a/components/jira/package.json +++ b/components/jira/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/jira", - "version": "0.1.2", + "version": "0.1.3", "description": "Pipedream Jira Components", "main": "jira.app.mjs", "keywords": [ diff --git a/components/jira/sources/common/common.mjs b/components/jira/sources/common/common.mjs index 0532476f7f768..d25fc992239e4 100644 --- a/components/jira/sources/common/common.mjs +++ b/components/jira/sources/common/common.mjs @@ -21,6 +21,13 @@ export default { ], description: "The JQL filter that specifies which issues the webhook is sent for, only a subset of JQL can be used, e.g. `project = P1` [See supported JQL filters](https://developer.atlassian.com/cloud/jira/service-desk/webhooks/#supported-jql-queries)", }, + overrideExistingWebhooks: { + type: "boolean", + label: "Override Existing Webhooks", + description: "Override existing webhooks with this new Pipedream source's webhook. Recommend to set this to `true` if you have an existing Jira webhook that you no longer use and want to override with the new Pipedream source.", + default: false, + optional: true, + }, }, methods: { _getHookID() { @@ -91,9 +98,28 @@ export default { ts, }; }, + async deleteExistingWebhooks() { + const resourcesStream = await this.jira.getResourcesStream({ + cloudId: this.cloudId, + resourceFn: this.jira.getWebhook, + resourceFnArgs: { + params: {}, + }, + resourceFiltererFn: (resource) => resource.values, + }); + for await (const webhook of resourcesStream) { + await this.jira.deleteHook({ + hookId: webhook.id, + cloudId: this.cloudId, + }); + } + }, }, hooks: { async activate() { + if (this.overrideExistingWebhooks) { + await this.deleteExistingWebhooks(); + } const { hookId } = await this.jira.createHook({ url: this.http.endpoint, events: this.getEvents(), diff --git a/components/jira/sources/events/events.mjs b/components/jira/sources/events/events.mjs index 2331c35d109ed..1329faa50e701 100644 --- a/components/jira/sources/events/events.mjs +++ b/components/jira/sources/events/events.mjs @@ -5,7 +5,7 @@ export default { key: "jira-events", name: "New Event", description: "Emit new event when an event with subscribed event source triggered, [See the docs](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-webhooks/#api-rest-api-3-webhook-post)", - version: "0.0.9", + version: "0.0.10", type: "source", dedupe: "unique", ...common, diff --git a/components/jira/sources/issue-created/issue-created.mjs b/components/jira/sources/issue-created/issue-created.mjs index b696d3acd974a..1699d666fa308 100644 --- a/components/jira/sources/issue-created/issue-created.mjs +++ b/components/jira/sources/issue-created/issue-created.mjs @@ -2,9 +2,9 @@ import common from "../common/common.mjs"; export default { key: "jira-issue-created", - name: "New Issue Created Event", + name: "New Issue Created Event (Instant)", description: "Emit new event when an issue is created. Note that Jira supports only one webhook, if more sources are needed please use `New Event` source and select multiple events.", - version: "0.0.9", + version: "0.0.10", type: "source", dedupe: "unique", ...common, diff --git a/components/jira/sources/issue-deleted/issue-deleted.mjs b/components/jira/sources/issue-deleted/issue-deleted.mjs index b8e73b78554ae..90e34de11b911 100644 --- a/components/jira/sources/issue-deleted/issue-deleted.mjs +++ b/components/jira/sources/issue-deleted/issue-deleted.mjs @@ -2,9 +2,9 @@ import common from "../common/common.mjs"; export default { key: "jira-issue-deleted", - name: "New Issue Deleted Event", + name: "New Issue Deleted Event (Instant)", description: "Emit new event when an issue is deleted. Note that Jira supports only one webhook, if more sources are needed please use `New Event` source and select multiple events.", - version: "0.0.9", + version: "0.0.10", type: "source", dedupe: "unique", ...common, diff --git a/components/jira/sources/issue-updated/issue-updated.mjs b/components/jira/sources/issue-updated/issue-updated.mjs index 6ce2287dfafc8..71e7e82a74069 100644 --- a/components/jira/sources/issue-updated/issue-updated.mjs +++ b/components/jira/sources/issue-updated/issue-updated.mjs @@ -2,9 +2,9 @@ import common from "../common/common.mjs"; export default { key: "jira-issue-updated", - name: "New Issue Updated Event", + name: "New Issue Updated Event (Instant)", description: "Emit new event when an issue is updated. Note that Jira supports only one webhook, if more sources are needed please use `New Event` source and select multiple events.", - version: "0.0.9", + version: "0.0.10", type: "source", dedupe: "unique", ...common, diff --git a/components/klipfolio/app/klipfolio.app.ts b/components/klipfolio/app/klipfolio.app.ts index 688ab9a4688e9..d631c15b1dd8e 100644 --- a/components/klipfolio/app/klipfolio.app.ts +++ b/components/klipfolio/app/klipfolio.app.ts @@ -10,4 +10,4 @@ export default defineApp({ console.log(Object.keys(this.$auth)); }, }, -}); \ No newline at end of file +}); diff --git a/components/langbase/actions/create-memory/create-memory.mjs b/components/langbase/actions/create-memory/create-memory.mjs new file mode 100644 index 0000000000000..f72fead97f9b1 --- /dev/null +++ b/components/langbase/actions/create-memory/create-memory.mjs @@ -0,0 +1,38 @@ +import app from "../../langbase.app.mjs"; + +export default { + key: "langbase-create-memory", + name: "Create Memory", + description: "Create a new organization memory by sending the memory data. [See the documentation](https://langbase.com/docs/api-reference/memory/create)", + version: "0.0.1", + type: "action", + props: { + app, + name: { + propDefinition: [ + app, + "name" + ] + }, + description: { + propDefinition: [ + app, + "description" + ] + }, + }, + + async run({ $ }) { + const response = await this.app.createMemory({ + $, + data: { + name: this.name, + description: this.description + } + }); + + $.export("$summary", `Successfully created memory ${this.name}`); + + return response; + }, +}; diff --git a/components/langbase/actions/delete-memory/delete-memory.mjs b/components/langbase/actions/delete-memory/delete-memory.mjs new file mode 100644 index 0000000000000..a5bfb3268b6bd --- /dev/null +++ b/components/langbase/actions/delete-memory/delete-memory.mjs @@ -0,0 +1,29 @@ +import app from "../../langbase.app.mjs"; + +export default { + key: "langbase-delete-memory", + name: "Delete Memory", + description: "Delete an existing memory on Langbase. [See the documentation](https://langbase.com/docs/api-reference/memory/delete)", + version: "0.0.1", + type: "action", + props: { + app, + memoryName: { + propDefinition: [ + app, + "memoryName" + ] + }, + }, + + async run({ $ }) { + const response = await this.app.deleteMemory({ + $, + memoryName: this.memoryName, + }); + + $.export("$summary", `Successfully deleted memory named ${this.memoryName}`); + + return response; + }, +}; diff --git a/components/langbase/actions/list-memories/list-memories.mjs b/components/langbase/actions/list-memories/list-memories.mjs new file mode 100644 index 0000000000000..fc47e02888c14 --- /dev/null +++ b/components/langbase/actions/list-memories/list-memories.mjs @@ -0,0 +1,22 @@ +import app from "../../langbase.app.mjs"; + +export default { + key: "langbase-list-memories", + name: "List Memories", + description: "Get a list of memory sets on Langbase. [See the documentation](https://langbase.com/docs/api-reference/memory/list)", + version: "0.0.1", + type: "action", + props: { + app, + }, + + async run({ $ }) { + const response = await this.app.listMemories({ + $, + }); + + $.export("$summary", `Successfully retrieved ${response.memorySets.length} memorysets`); + + return response; + }, +}; diff --git a/components/langbase/langbase.app.mjs b/components/langbase/langbase.app.mjs index 4a66c23d22182..97bef7c02f3cc 100644 --- a/components/langbase/langbase.app.mjs +++ b/components/langbase/langbase.app.mjs @@ -1,11 +1,73 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "langbase", - propDefinitions: {}, + propDefinitions: { + memoryName: { + type: "string", + label: "Memory Name", + description: "The name of the memory", + async options() { + const response = await this.listMemories(); + const memoryNames = response.memorySets; + return memoryNames.map(({ name, description }) => ({ + label: description, + value: name, + })); + } + }, + name: { + type: "string", + label: "Name", + description: "Name of the memory", + }, + description: { + type: "string", + label: "Description", + description: "Short description of the memory", + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.langbase.com/beta"; + }, + async _makeRequest(opts = {}) { + const { + $ = this, + path, + headers, + ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + url: this._baseUrl() + path, + headers: { + ...headers, + Authorization: `Bearer ${this.$auth.org_api_key}`, + "Accept": `application/json`, + }, + }); + }, + async createMemory(args = {}) { + return this._makeRequest({ + path: `/org/${this.$auth.org}/memorysets`, + method: "post", + ...args, + }); + }, + async deleteMemory({ memoryName, ...args }) { + return this._makeRequest({ + path: `/memorysets/${this.$auth.org}/${memoryName}`, + method: "delete", + ...args, + }); + }, + async listMemories(args = {}) { + return this._makeRequest({ + path: `/org/${this.$auth.org}/memorysets`, + ...args, + }); }, }, }; diff --git a/components/langbase/package.json b/components/langbase/package.json index 879d82fe8225e..91c547399b30e 100644 --- a/components/langbase/package.json +++ b/components/langbase/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/langbase", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Langbase Components", "main": "langbase.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } -} \ No newline at end of file +} diff --git a/components/livekit/livekit.app.mjs b/components/livekit/livekit.app.mjs new file mode 100644 index 0000000000000..7fb5229a0b5b1 --- /dev/null +++ b/components/livekit/livekit.app.mjs @@ -0,0 +1,11 @@ +export default { + type: "app", + app: "livekit", + propDefinitions: {}, + methods: { + // this.$auth contains connected account data + authKeys() { + console.log(Object.keys(this.$auth)); + }, + }, +}; \ No newline at end of file diff --git a/components/livekit/package.json b/components/livekit/package.json new file mode 100644 index 0000000000000..66f578a3d3796 --- /dev/null +++ b/components/livekit/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pipedream/livekit", + "version": "0.0.1", + "description": "Pipedream LiveKit Components", + "main": "livekit.app.mjs", + "keywords": [ + "pipedream", + "livekit" + ], + "homepage": "https://pipedream.com/apps/livekit", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/components/lokalise/actions/create-project/create-project.mjs b/components/lokalise/actions/create-project/create-project.mjs new file mode 100644 index 0000000000000..59a040a866872 --- /dev/null +++ b/components/lokalise/actions/create-project/create-project.mjs @@ -0,0 +1,60 @@ +import lokalise from "../../lokalise.app.mjs"; + +export default { + key: "lokalise-create-project", + name: "Create Project", + description: "Initializes an empty project in Lokalise. [See the documentation](https://developers.lokalise.com/reference/create-a-project)", + version: "0.0.1", + type: "action", + props: { + lokalise, + name: { + type: "string", + label: "Name", + description: "Name of the project", + }, + description: { + type: "string", + label: "Description", + description: "Description of the project", + optional: true, + }, + language: { + propDefinition: [ + lokalise, + "language", + ], + optional: true, + }, + projectType: { + type: "string", + label: "Project Type", + description: "The type of project", + options: [ + "localization_files", + "paged_documents", + ], + optional: true, + }, + isSegmentationEnabled: { + type: "boolean", + label: "Is Segmentation Enabled", + description: "Whether to enable Segmentation feature for project", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.lokalise.createProject({ + $, + data: { + name: this.name, + description: this.description, + base_lang_iso: this.language, + project_type: this.projectType, + is_segmentation_enabled: this.isSegmentationEnabled, + }, + }); + $.export("$summary", `Successfully created project with ID: ${response.project_id}`); + return response; + }, +}; diff --git a/components/lokalise/actions/download-files/download-files.mjs b/components/lokalise/actions/download-files/download-files.mjs new file mode 100644 index 0000000000000..cd86bbe73df10 --- /dev/null +++ b/components/lokalise/actions/download-files/download-files.mjs @@ -0,0 +1,35 @@ +import lokalise from "../../lokalise.app.mjs"; + +export default { + key: "lokalise-download-files", + name: "Download Files", + description: "Retrieves and downloads files from a specified Lokalise project. [See the documentation](https://developers.lokalise.com/reference/download-files)", + version: "0.0.1", + type: "action", + props: { + lokalise, + projectId: { + propDefinition: [ + lokalise, + "projectId", + ], + }, + fileFormat: { + type: "string", + label: "File Format", + description: "File format (e.g. json, strings, xml). Must be file extension of any of the [supported file formats](https://docs.lokalise.com/en/collections/2909229-supported-file-formats). May also be `ios_sdk` or `android_sdk` for respective OTA SDK bundles.", + }, + }, + async run({ $ }) { + const response = await this.lokalise.downloadFiles({ + $, + projectId: this.projectId, + data: { + format: this.fileFormat, + }, + }); + + $.export("$summary", `Successfully downloaded files from project ${this.projectId}`); + return response; + }, +}; diff --git a/components/lokalise/actions/upload-file/upload-file.mjs b/components/lokalise/actions/upload-file/upload-file.mjs new file mode 100644 index 0000000000000..510bf37d2734a --- /dev/null +++ b/components/lokalise/actions/upload-file/upload-file.mjs @@ -0,0 +1,53 @@ +import lokalise from "../../lokalise.app.mjs"; +import fs from "fs"; + +export default { + key: "lokalise-upload-file", + name: "Upload File", + description: "Uploads a specified file to a Lokalise project. [See the documentation](https://developers.lokalise.com/reference/upload-a-file)", + version: "0.0.1", + type: "action", + props: { + lokalise, + projectId: { + propDefinition: [ + lokalise, + "projectId", + ], + }, + filePath: { + type: "string", + label: "File Path", + description: "The path to a file of a [supported file format](https://docs.lokalise.com/en/collections/2909229-supported-file-formats) in the `/tmp` directory. [See the documentation on working with files](https://pipedream.com/docs/code/nodejs/working-with-files/#writing-a-file-to-tmp).", + }, + language: { + propDefinition: [ + lokalise, + "language", + ], + }, + filename: { + type: "string", + label: "Filename", + description: "Set the filename. You may optionally use a relative path in the filename", + }, + }, + async run({ $ }) { + const fileData = fs.readFileSync(this.filePath.startsWith("/tmp") + ? this.filePath + : `/tmp/${this.filePath}`, { + encoding: "base64", + }); + const response = await this.lokalise.uploadFile({ + $, + projectId: this.projectId, + data: { + data: fileData, + filename: this.filename, + lang_iso: this.language, + }, + }); + $.export("$summary", "Successfully uploaded file"); + return response; + }, +}; diff --git a/components/lokalise/lokalise.app.mjs b/components/lokalise/lokalise.app.mjs new file mode 100644 index 0000000000000..6739459519378 --- /dev/null +++ b/components/lokalise/lokalise.app.mjs @@ -0,0 +1,118 @@ +import { axios } from "@pipedream/platform"; + +export default { + type: "app", + app: "lokalise", + propDefinitions: { + projectId: { + type: "string", + label: "Project ID", + description: "Identifier of a project", + async options({ page }) { + const { projects } = await this.listProjects({ + params: { + page: page + 1, + }, + }); + return projects?.map(({ + project_id: value, name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + language: { + type: "string", + label: "Language", + description: "Language/locale code of the project base language", + async options({ page }) { + const { languages } = await this.listLanguages({ + params: { + page: page + 1, + }, + }); + return languages?.map(({ + lang_iso: value, lang_name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + }, + methods: { + _baseUrl() { + return "https://api.lokalise.com/api2"; + }, + _makeRequest(opts = {}) { + const { + $ = this, + path, + ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + url: `${this._baseUrl()}${path}`, + headers: { + "Authorization": `Bearer ${this.$auth.oauth_access_token}`, + }, + }); + }, + createWebhook({ + projectId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/projects/${projectId}/webhooks`, + ...opts, + }); + }, + deleteWebhook({ + projectId, hookId, ...opts + }) { + return this._makeRequest({ + method: "DELETE", + path: `/projects/${projectId}/webhooks/${hookId}`, + ...opts, + }); + }, + listProjects(opts = {}) { + return this._makeRequest({ + path: "/projects", + ...opts, + }); + }, + listLanguages(opts = {}) { + return this._makeRequest({ + path: "/system/languages", + ...opts, + }); + }, + createProject(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/projects", + ...opts, + }); + }, + uploadFile({ + projectId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/projects/${projectId}/files/upload`, + ...opts, + }); + }, + downloadFiles({ + projectId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/projects/${projectId}/files/download`, + ...opts, + }); + }, + }, +}; diff --git a/components/lokalise/package.json b/components/lokalise/package.json new file mode 100644 index 0000000000000..c8853ae704b26 --- /dev/null +++ b/components/lokalise/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/lokalise", + "version": "0.1.0", + "description": "Pipedream Lokalise Components", + "main": "lokalise.app.mjs", + "keywords": [ + "pipedream", + "lokalise" + ], + "homepage": "https://pipedream.com/apps/lokalise", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" + } +} diff --git a/components/lokalise/sources/common/base.mjs b/components/lokalise/sources/common/base.mjs new file mode 100644 index 0000000000000..217cc694aa591 --- /dev/null +++ b/components/lokalise/sources/common/base.mjs @@ -0,0 +1,65 @@ +import lokalise from "../../lokalise.app.mjs"; + +export default { + props: { + lokalise, + http: "$.interface.http", + db: "$.service.db", + projectId: { + propDefinition: [ + lokalise, + "projectId", + ], + }, + }, + hooks: { + async activate() { + const { webhook } = await this.lokalise.createWebhook({ + projectId: this.projectId, + data: { + url: this.http.endpoint, + events: this.getEvents(), + }, + }); + this._setHookId(webhook.webhook_id); + }, + async deactivate() { + const hookId = this._getHookId(); + if (hookId) { + await this.lokalise.deleteWebhook({ + projectId: this.projectId, + hookId, + }); + } + }, + }, + methods: { + _getHookId() { + return this.db.get("hookId"); + }, + _setHookId(hookId) { + this.db.set("hookId", hookId); + }, + generateMeta(event) { + return { + id: `${event.event}-${event.created_at_timestamp}`, + summary: this.getSummary(event), + ts: event.created_at_timestamp, + }; + }, + getEvents() { + throw new Error("getEvents is not implemented"); + }, + getSummary() { + throw new Error("getSummary is not implemented"); + }, + }, + async run(event) { + const { body } = event; + if (!body.event) { + return; + } + const meta = this.generateMeta(body); + this.$emit(body, meta); + }, +}; diff --git a/components/lokalise/sources/new-task-closed-instant/new-task-closed-instant.mjs b/components/lokalise/sources/new-task-closed-instant/new-task-closed-instant.mjs new file mode 100644 index 0000000000000..3b9f209118fd3 --- /dev/null +++ b/components/lokalise/sources/new-task-closed-instant/new-task-closed-instant.mjs @@ -0,0 +1,24 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "lokalise-new-task-closed-instant", + name: "New Task Closed (Instant)", + description: "Emit new event when a task is closed in Lokalise", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEvents() { + return [ + "project.task.closed", + ]; + }, + getSummary({ task }) { + return `Task Closed with ID: ${task.id}`; + }, + }, + sampleEmit, +}; diff --git a/components/lokalise/sources/new-task-closed-instant/test-event.mjs b/components/lokalise/sources/new-task-closed-instant/test-event.mjs new file mode 100644 index 0000000000000..38e6458d8db97 --- /dev/null +++ b/components/lokalise/sources/new-task-closed-instant/test-event.mjs @@ -0,0 +1,21 @@ +export default { + "event": "project.task.closed", + "task": { + "id": 5022, + "type": "translation", + "title": "Headings translation", + "due_date": "2019-08-01 00:00:00", + "description": "Task description" + }, + "project": { + "id": "138c1ffa0ad94848f01f980e7f2f2af19d1bd553", + "name": "TheApp Project", + "branch": "master" + }, + "user": { + "email": "jdoe@mycompany.com", + "full_name": "John Doe" + }, + "created_at": "2019-07-29 12:18:31", + "created_at_timestamp": 1564395511 +} \ No newline at end of file diff --git a/components/lokalise/sources/new-task-created-instant/new-task-created-instant.mjs b/components/lokalise/sources/new-task-created-instant/new-task-created-instant.mjs new file mode 100644 index 0000000000000..a342548b70132 --- /dev/null +++ b/components/lokalise/sources/new-task-created-instant/new-task-created-instant.mjs @@ -0,0 +1,24 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "lokalise-new-task-created-instant", + name: "New Task Created (Instant)", + description: "Emit new event when a new task is created in Lokalise", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEvents() { + return [ + "project.task.created", + ]; + }, + getSummary({ task }) { + return `New Task with ID: ${task.id}`; + }, + }, + sampleEmit, +}; diff --git a/components/lokalise/sources/new-task-created-instant/test-event.mjs b/components/lokalise/sources/new-task-created-instant/test-event.mjs new file mode 100644 index 0000000000000..6aea90f65171d --- /dev/null +++ b/components/lokalise/sources/new-task-created-instant/test-event.mjs @@ -0,0 +1,21 @@ +export default { + "event": "project.task.created", + "task": { + "id": 5022, + "type": "translation", + "title": "Headings translation", + "due_date": "2019-08-01 00:00:00", + "description": "Task description" + }, + "project": { + "id": "138c1ffa0ad94848f01f980e7f2f2af19d1bd553", + "name": "TheApp Project", + "branch": "master" + }, + "user": { + "email": "jdoe@mycompany.com", + "full_name": "John Doe" + }, + "created_at": "2019-07-29 12:18:31", + "created_at_timestamp": 1564395511 +} \ No newline at end of file diff --git a/components/lokalise/sources/project-imported-instant/project-imported-instant.mjs b/components/lokalise/sources/project-imported-instant/project-imported-instant.mjs new file mode 100644 index 0000000000000..a61a9b69241ec --- /dev/null +++ b/components/lokalise/sources/project-imported-instant/project-imported-instant.mjs @@ -0,0 +1,24 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "lokalise-project-imported-instant", + name: "New Project Imported (Instant)", + description: "Emit new event when data is imported into a project", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEvents() { + return [ + "project.imported", + ]; + }, + getSummary() { + return "Data imported to project"; + }, + }, + sampleEmit, +}; diff --git a/components/lokalise/sources/project-imported-instant/test-event.mjs b/components/lokalise/sources/project-imported-instant/test-event.mjs new file mode 100644 index 0000000000000..ed85fc7c05d41 --- /dev/null +++ b/components/lokalise/sources/project-imported-instant/test-event.mjs @@ -0,0 +1,43 @@ +export default { + "event": "project.imported", + "import": { + "filename": "ru.yml", + "format": "yml", + "inserted": 231, + "updated": 0, + "skipped": 0 + }, + "import_options": { + "replace_line_breaks": false, + "convert_placeholders": true, + "replace_modified": false, + "key_tags": [ + "tag1", "tag2" + ], + "tag_keys_inserted": true, + "tag_keys_updated": true, + "tag_keys_skipped": false, + "detect_icu_plurals": true, + "fill_empty_with_keys": false, + "hide_from_contributors": false, + "diff_by_file": false, + "use_tm": false, + "cleanup": false + }, + "project": { + "id": "138c1ffa0ad94848f01f980e7f2f2af19d1bd553", + "name": "TheApp Project", + "branch": "develop" + }, + "language": { + "id": 597, + "iso": "ru", + "name": "Russian" + }, + "user": { + "email": "jdoe@mycompany.com", + "full_name": "John Doe" + }, + "created_at": "2019-07-29 12:18:31", + "created_at_timestamp": 1564395511 +} \ No newline at end of file diff --git a/components/loops_so/actions/common/common-create-update.mjs b/components/loops_so/actions/common/common-create-update.mjs index 2e591c4883cc3..e1f9a82c497d0 100644 --- a/components/loops_so/actions/common/common-create-update.mjs +++ b/components/loops_so/actions/common/common-create-update.mjs @@ -1,3 +1,6 @@ +/* eslint-disable no-unused-vars */ +import pickBy from "lodash.pickby"; +import { parseObject } from "../../common/utils.mjs"; import loops from "../../loops_so.app.mjs"; export default { @@ -31,12 +34,36 @@ export default { "lastName", ], }, + source: { + propDefinition: [ + loops, + "source", + ], + }, + subscribed: { + propDefinition: [ + loops, + "subscribed", + ], + }, userGroup: { propDefinition: [ loops, "userGroup", ], }, + userId: { + propDefinition: [ + loops, + "userId", + ], + }, + mailingLists: { + propDefinition: [ + loops, + "mailingLists", + ], + }, customFields: { propDefinition: [ loops, @@ -44,4 +71,24 @@ export default { ], }, }, + methods: { + prepareData() { + const { + loops, + customFields, + mailingLists, + ...data + } = this; + + const mailingListObject = {}; + for (const item of (parseObject(mailingLists) || [])) { + mailingListObject[item] = true; + } + + return pickBy({ + mailingLists: mailingListObject, + ...data, + }); + }, + }, }; diff --git a/components/loops_so/actions/create-contact/create-contact.mjs b/components/loops_so/actions/create-contact/create-contact.mjs index aa1af0b007fdf..d03452435b8f3 100644 --- a/components/loops_so/actions/create-contact/create-contact.mjs +++ b/components/loops_so/actions/create-contact/create-contact.mjs @@ -5,20 +5,11 @@ export default { key: "loops_so-create-contact", name: "Create Contact", description: "Creates a new contact. [See the Documentation](https://loops.so/docs/add-users/api-reference#add)", - version: "0.1.1", + version: "0.2.0", type: "action", async run({ $ }) { - const { // eslint-disable-next-line no-unused-vars - loops, email, firstName, lastName, userGroup, customFields, ...data - } = this; - const response = await loops.createContact({ - data: { - email, - firstName, - lastName, - userGroup, - ...data, - }, + const response = await this.loops.createContact({ + data: this.prepareData(), $, }); diff --git a/components/loops_so/actions/delete-contact/delete-contact.mjs b/components/loops_so/actions/delete-contact/delete-contact.mjs index 1d5e9a111f664..42e413253270f 100644 --- a/components/loops_so/actions/delete-contact/delete-contact.mjs +++ b/components/loops_so/actions/delete-contact/delete-contact.mjs @@ -5,7 +5,7 @@ export default { key: "loops_so-delete-contact", name: "Delete Contact", description: "Delete an existing contact. [See the documentation](https://loops.so/docs/api-reference/delete-contact)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { loops, diff --git a/components/loops_so/actions/find-contact/find-contact.mjs b/components/loops_so/actions/find-contact/find-contact.mjs index 2ef773e7bf0db..9fba12feeda25 100644 --- a/components/loops_so/actions/find-contact/find-contact.mjs +++ b/components/loops_so/actions/find-contact/find-contact.mjs @@ -4,7 +4,7 @@ export default { key: "loops_so-find-contact", name: "Find Contact", description: "Search for a contact by email address. [See the Documentation](https://loops.so/docs/add-users/api-reference#find)", - version: "0.0.3", + version: "0.0.4", type: "action", props: { loops, diff --git a/components/loops_so/actions/list-custom-fields/list-custom-fields.mjs b/components/loops_so/actions/list-custom-fields/list-custom-fields.mjs index 6f09402dd9de2..f622635091b91 100644 --- a/components/loops_so/actions/list-custom-fields/list-custom-fields.mjs +++ b/components/loops_so/actions/list-custom-fields/list-custom-fields.mjs @@ -4,7 +4,7 @@ export default { key: "loops_so-list-custom-fields", name: "List Custom Fields", description: "List your account's custom contact properties. [See the documentation](https://loops.so/docs/api-reference/list-custom-fields)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { loops, diff --git a/components/loops_so/actions/list-mailing-lists/list-mailing-lists.mjs b/components/loops_so/actions/list-mailing-lists/list-mailing-lists.mjs index 331f0d415ebb0..48530bddebf2d 100644 --- a/components/loops_so/actions/list-mailing-lists/list-mailing-lists.mjs +++ b/components/loops_so/actions/list-mailing-lists/list-mailing-lists.mjs @@ -4,7 +4,7 @@ export default { key: "loops_so-list-mailing-lists", name: "List Mailing Lists", description: "List your account's mailing lists. [See the documentation](https://loops.so/docs/api-reference/list-mailing-lists)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { loops, diff --git a/components/loops_so/actions/send-event/send-event.mjs b/components/loops_so/actions/send-event/send-event.mjs index bd05174c56e99..230148ba3a31d 100644 --- a/components/loops_so/actions/send-event/send-event.mjs +++ b/components/loops_so/actions/send-event/send-event.mjs @@ -4,7 +4,7 @@ export default { key: "loops_so-send-event", name: "Send Event", description: "Send an event to an email address. [See the Documentation](https://loops.so/docs/add-users/api-reference#send)", - version: "0.0.3", + version: "0.0.4", type: "action", props: { loops, diff --git a/components/loops_so/actions/send-transactional-email/send-transactional-email.mjs b/components/loops_so/actions/send-transactional-email/send-transactional-email.mjs index 73c4a65cfd9d3..d964d899749f3 100644 --- a/components/loops_so/actions/send-transactional-email/send-transactional-email.mjs +++ b/components/loops_so/actions/send-transactional-email/send-transactional-email.mjs @@ -4,7 +4,7 @@ export default { key: "loops_so-send-transactional-email", name: "Send Transactional Email", description: "Send a transactional email. [See the Documentation](https://loops.so/docs/transactional/guide#send-your-email)", - version: "0.0.3", + version: "0.0.4", type: "action", props: { loops, diff --git a/components/loops_so/actions/update-contact/update-contact.mjs b/components/loops_so/actions/update-contact/update-contact.mjs index 1de02233ac884..fee9d3bd85561 100644 --- a/components/loops_so/actions/update-contact/update-contact.mjs +++ b/components/loops_so/actions/update-contact/update-contact.mjs @@ -1,25 +1,15 @@ import common from "../common/common-create-update.mjs"; -import pickBy from "lodash.pickby"; export default { ...common, key: "loops_so-update-contact", name: "Update Contact", description: "Updates an existing contact by email. If email not found, a new contact will be created. [See the Documentation](https://loops.so/docs/add-users/api-reference#update)", - version: "0.1.1", + version: "0.2.0", type: "action", async run({ $ }) { - const { // eslint-disable-next-line no-unused-vars - loops, email, firstName, lastName, userGroup, customFields, ...data - } = this; - const response = await loops.updateContact({ - data: pickBy({ - email, - firstName, - lastName, - userGroup, - ...data, - }), + const response = await this.loops.updateContact({ + data: this.prepareData(), $, }); diff --git a/components/loops_so/common/utils.mjs b/components/loops_so/common/utils.mjs new file mode 100644 index 0000000000000..dcc9cc61f6f41 --- /dev/null +++ b/components/loops_so/common/utils.mjs @@ -0,0 +1,24 @@ +export const parseObject = (obj) => { + if (!obj) return undefined; + + if (Array.isArray(obj)) { + return obj.map((item) => { + if (typeof item === "string") { + try { + return JSON.parse(item); + } catch (e) { + return item; + } + } + return item; + }); + } + if (typeof obj === "string") { + try { + return JSON.parse(obj); + } catch (e) { + return obj; + } + } + return obj; +}; diff --git a/components/loops_so/loops_so.app.mjs b/components/loops_so/loops_so.app.mjs index 10d7ef3c3da17..be6e9edf07dd4 100644 --- a/components/loops_so/loops_so.app.mjs +++ b/components/loops_so/loops_so.app.mjs @@ -21,12 +21,46 @@ export default { description: "The last name of the contact", optional: true, }, + source: { + type: "string", + label: "Source", + description: "A custom source value to replace the default **API**. [Read more](https://loops.so/docs/contacts/properties#source).", + optional: true, + }, + subscribed: { + type: "boolean", + label: "Subscribed", + description: "Whether the contact will receive campaign and loops emails. [Read more](https://loops.so/docs/contacts/properties#subscribed).", + optional: true, + }, userGroup: { type: "string", label: "User Group", description: "User group of the contact", optional: true, }, + userId: { + type: "string", + label: "User Id", + description: "A unique user ID (for example, from an external application). [Read more](https://loops.so/docs/contacts/properties#user-id).", + optional: true, + }, + mailingLists: { + type: "string[]", + label: "Mailing Lists", + description: "A list of mailing list IDs", + async options() { + const data = await this.listMailingLists(); + return data.map(({ + id: value, + name: label, + }) => ({ + label, + value, + })); + }, + optional: true, + }, customFields: { type: "string[]", label: "Custom Fields", diff --git a/components/loops_so/package.json b/components/loops_so/package.json index ada57b44b3e17..8dc67ee930a14 100644 --- a/components/loops_so/package.json +++ b/components/loops_so/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/loops_so", - "version": "0.3.0", + "version": "0.3.1", "description": "Pipedream Loops.so Components", "main": "loops_so.app.mjs", "keywords": [ diff --git a/components/microsoft_azure_ai_translator/microsoft_azure_ai_translator.app.mjs b/components/microsoft_azure_ai_translator/microsoft_azure_ai_translator.app.mjs new file mode 100644 index 0000000000000..c0c4cf190a5a0 --- /dev/null +++ b/components/microsoft_azure_ai_translator/microsoft_azure_ai_translator.app.mjs @@ -0,0 +1,11 @@ +export default { + type: "app", + app: "microsoft_azure_ai_translator", + propDefinitions: {}, + methods: { + // this.$auth contains connected account data + authKeys() { + console.log(Object.keys(this.$auth)); + }, + }, +}; diff --git a/components/microsoft_azure_ai_translator/package.json b/components/microsoft_azure_ai_translator/package.json new file mode 100644 index 0000000000000..2c61ece4ea585 --- /dev/null +++ b/components/microsoft_azure_ai_translator/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pipedream/microsoft_azure_ai_translator", + "version": "0.0.1", + "description": "Pipedream Microsoft Azure AI Translator Components", + "main": "microsoft_azure_ai_translator.app.mjs", + "keywords": [ + "pipedream", + "microsoft_azure_ai_translator" + ], + "homepage": "https://pipedream.com/apps/microsoft_azure_ai_translator", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/components/microsoft_text_translate/microsoft_text_translate.app.mjs b/components/microsoft_text_translate/microsoft_text_translate.app.mjs new file mode 100644 index 0000000000000..5bd3c6fe11a9f --- /dev/null +++ b/components/microsoft_text_translate/microsoft_text_translate.app.mjs @@ -0,0 +1,11 @@ +export default { + type: "app", + app: "microsoft_text_translate", + propDefinitions: {}, + methods: { + // this.$auth contains connected account data + authKeys() { + console.log(Object.keys(this.$auth)); + }, + }, +}; diff --git a/components/microsoft_text_translate/package.json b/components/microsoft_text_translate/package.json new file mode 100644 index 0000000000000..3ff5a5362a5b1 --- /dev/null +++ b/components/microsoft_text_translate/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pipedream/microsoft_text_translate", + "version": "0.0.1", + "description": "Pipedream Microsoft Text Translate Components", + "main": "microsoft_text_translate.app.mjs", + "keywords": [ + "pipedream", + "microsoft_text_translate" + ], + "homepage": "https://pipedream.com/apps/microsoft_text_translate", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/components/mural/actions/create-mural/create-mural.mjs b/components/mural/actions/create-mural/create-mural.mjs new file mode 100644 index 0000000000000..f61afb50a29b2 --- /dev/null +++ b/components/mural/actions/create-mural/create-mural.mjs @@ -0,0 +1,95 @@ +import mural from "../../mural.app.mjs"; + +export default { + key: "mural-create-mural", + name: "Create Mural", + description: "Create a new mural within a specified workspace. [See the documentation](https://developers.mural.co/public/reference/createmural)", + version: "0.0.1", + type: "action", + props: { + mural, + workspaceId: { + propDefinition: [ + mural, + "workspaceId", + ], + }, + roomId: { + propDefinition: [ + mural, + "roomId", + (c) => ({ + workspaceId: c.workspaceId, + }), + ], + }, + title: { + type: "string", + label: "Title", + description: "The title of the Mural.", + }, + backgroundColor: { + type: "string", + label: "Background Color", + description: "The background color of the mural. Example: `#FAFAFAFF`", + optional: true, + }, + height: { + type: "integer", + label: "Height", + description: "The height of the mural in px", + optional: true, + }, + width: { + type: "integer", + label: "Width", + description: "The width of the mural in px", + optional: true, + }, + infinite: { + type: "boolean", + label: "Infinite", + description: "When `true`, this indicates that the mural canvas is borderless and grows as you add widgets to it.", + optional: true, + }, + timerSoundTheme: { + type: "string", + label: "Timer Sound Theme", + description: "The timer sound theme for the mural", + options: [ + "airplane", + "cello", + "cuckoo", + ], + optional: true, + }, + visitorAvatarTheme: { + type: "string", + label: "Visitor Avatar Theme", + description: "The visitor avatar theme for the mural", + options: [ + "animals", + "music", + "travel", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.mural.createMural({ + $, + data: { + roomId: this.roomId, + title: this.title, + backgroundColor: this.backgroundColor, + height: this.height, + width: this.width, + infinite: this.infinite, + timerSoundTheme: this.timerSoundTheme, + visitorAvatarTheme: this.visitorAvatarTheme, + }, + }); + $.export("$summary", `Successfully created mural "${this.title}"`); + return response; + }, +}; diff --git a/components/mural/actions/create-sticky/create-sticky.mjs b/components/mural/actions/create-sticky/create-sticky.mjs new file mode 100644 index 0000000000000..ae273cb02d290 --- /dev/null +++ b/components/mural/actions/create-sticky/create-sticky.mjs @@ -0,0 +1,117 @@ +import mural from "../../mural.app.mjs"; + +export default { + key: "mural-create-sticky", + name: "Create Sticky", + description: "Create a new sticky note within a given mural. [See the documentation](https://developers.mural.co/public/reference/createstickynote)", + version: "0.0.1", + type: "action", + props: { + mural, + workspaceId: { + propDefinition: [ + mural, + "workspaceId", + ], + }, + muralId: { + propDefinition: [ + mural, + "muralId", + (c) => ({ + workspaceId: c.workspaceId, + }), + ], + }, + shape: { + type: "string", + label: "Shape", + description: "The shape of the sticky note widget", + options: [ + "circle", + "rectangle", + ], + }, + xPosition: { + type: "integer", + label: "X Position", + description: "The horizontal position of the widget in px. This is the distance from the left of the parent widget, such as an area. If the widget has no parent widget, this is the distance from the left of the mural.", + }, + yPosition: { + type: "integer", + label: "Y Position", + description: "The vertical position of the widget in px. This is the distance from the top of the parent widget, such as an area. If the widget has no parent widget, this is the distance from the top of the mural.", + }, + text: { + type: "string", + label: "Text", + description: "The text in the widget", + }, + title: { + type: "string", + label: "Title", + description: "The title of the widget in the outline", + optional: true, + }, + height: { + type: "integer", + label: "Height", + description: "The height of the widget in px", + optional: true, + }, + width: { + type: "integer", + label: "Width", + description: "The width of the widget in px", + optional: true, + }, + hidden: { + type: "boolean", + label: "Hidden", + description: "If `true`, the widget is hidden from non-facilitators. Applies only when the widget is in the outline", + optional: true, + }, + tagIds: { + propDefinition: [ + mural, + "tagIds", + (c) => ({ + muralId: c.muralId, + }), + ], + }, + parentId: { + propDefinition: [ + mural, + "widgetId", + (c) => ({ + muralId: c.muralId, + type: "areas", + }), + ], + label: "Parent ID", + description: "The ID of the area widget that contains the widget", + }, + }, + async run({ $ }) { + const response = await this.mural.createSticky({ + $, + muralId: this.muralId, + data: [ + { + shape: this.shape, + x: this.xPosition, + y: this.yPosition, + text: this.text, + title: this.title, + height: this.height, + width: this.width, + hidden: this.hidden, + parentId: this.parentId, + }, + ], + }); + $.export("$summary", `Successfully created sticky note with ID: ${response.value[0].id}`); + return response; + }, +}; diff --git a/components/mural/mural.app.mjs b/components/mural/mural.app.mjs new file mode 100644 index 0000000000000..79bfc8c0a77e6 --- /dev/null +++ b/components/mural/mural.app.mjs @@ -0,0 +1,247 @@ +import { axios } from "@pipedream/platform"; + +export default { + type: "app", + app: "mural", + propDefinitions: { + workspaceId: { + type: "string", + label: "Workspace ID", + description: "The ID of the Workspace.", + async options({ prevContext }) { + const { + value, next: nextToken, + } = await this.listWorkspaces({ + params: { + next: prevContext?.next, + }, + }); + return { + options: value?.map(({ + id: value, name: label, + }) => ({ + value, + label, + })) || [], + context: { + next: nextToken, + }, + }; + }, + }, + muralId: { + type: "string", + label: "Mural ID", + description: "The ID of the Mural.", + async options({ + workspaceId, prevContext, + }) { + const { + value, next: nextToken, + } = await this.listMurals({ + workspaceId, + params: { + next: prevContext?.next, + }, + }); + return { + options: value?.map(({ + id: value, title: label, + }) => ({ + value, + label, + })) || [], + context: { + next: nextToken, + }, + }; + }, + }, + roomId: { + type: "string", + label: "Room ID", + description: "The ID of the Room.", + async options({ + workspaceId, prevContext, + }) { + const { + value, next: nextToken, + } = await this.listRooms({ + workspaceId, + params: { + next: prevContext?.next, + }, + }); + return { + options: value?.map(({ + id: value, name: label, + }) => ({ + value, + label, + })) || [], + context: { + next: nextToken, + }, + }; + }, + }, + tagIds: { + type: "string[]", + label: "Tag IDs", + description: "Unique identifiers of the tags in the widget", + optional: true, + async options({ + muralId, prevContext, + }) { + const { + value, next: nextToken, + } = await this.listTags({ + muralId, + params: { + next: prevContext?.next, + }, + }); + return { + options: value?.map(({ + id: value, text: label, + }) => ({ + value, + label, + })) || [], + context: { + next: nextToken, + }, + }; + }, + }, + widgetId: { + type: "string", + label: "Widget ID", + description: "Unique identifiers of the widget", + optional: true, + async options({ + muralId, type, prevContext, + }) { + const { + value, next: nextToken, + } = await this.listWidgets({ + muralId, + params: { + type, + next: prevContext?.next, + }, + }); + return { + options: value?.map(({ + id: value, title: label, + }) => ({ + value, + label, + })) || [], + context: { + next: nextToken, + }, + }; + }, + }, + }, + methods: { + _baseUrl() { + return "https://app.mural.co/api/public/v1"; + }, + _makeRequest(opts = {}) { + const { + $ = this, + path, + ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + url: `${this._baseUrl()}${path}`, + headers: { + Authorization: `Bearer ${this.$auth.oauth_access_token}`, + }, + }); + }, + listWorkspaces(opts = {}) { + return this._makeRequest({ + path: "/workspaces", + ...opts, + }); + }, + listMurals({ + workspaceId, ...opts + }) { + return this._makeRequest({ + path: `/workspaces/${workspaceId}/murals`, + ...opts, + }); + }, + listRooms({ + workspaceId, ...opts + }) { + return this._makeRequest({ + path: `/workspaces/${workspaceId}/rooms`, + ...opts, + }); + }, + listTags({ + muralId, ...opts + }) { + return this._makeRequest({ + path: `/murals/${muralId}/tags`, + ...opts, + }); + }, + listWidgets({ + muralId, ...opts + }) { + return this._makeRequest({ + path: `/murals/${muralId}/widgets`, + ...opts, + }); + }, + createMural(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/murals", + ...opts, + }); + }, + createSticky({ + muralId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/murals/${muralId}/widgets/sticky-note`, + ...opts, + }); + }, + async *paginate({ + fn, + args, + max, + }) { + args = { + ...args, + params: { + ...args?.params, + limit: 100, + }, + }; + let count = 0; + do { + const { + value, next, + } = await fn(args); + for (const item of value) { + yield item; + if (max && ++count >= max) { + return; + } + } + args.params.next = next; + } while (args.params.next); + }, + }, +}; diff --git a/components/mural/package.json b/components/mural/package.json new file mode 100644 index 0000000000000..227fca215ed5d --- /dev/null +++ b/components/mural/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/mural", + "version": "0.1.0", + "description": "Pipedream Mural Components", + "main": "mural.app.mjs", + "keywords": [ + "pipedream", + "mural" + ], + "homepage": "https://pipedream.com/apps/mural", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" + } +} diff --git a/components/mural/sources/common/base.mjs b/components/mural/sources/common/base.mjs new file mode 100644 index 0000000000000..7fada52e4d214 --- /dev/null +++ b/components/mural/sources/common/base.mjs @@ -0,0 +1,85 @@ +import mural from "../../mural.app.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + props: { + mural, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + workspaceId: { + propDefinition: [ + mural, + "workspaceId", + ], + }, + }, + hooks: { + async deploy() { + await this.processEvent(25); + }, + }, + methods: { + _getLastTs() { + return this.db.get("lastTs") || 0; + }, + _setLastTs(lastTs) { + this.db.set("lastTs", lastTs); + }, + getResourceFn() { + throw new Error("getResourceFn is not implemented"); + }, + getArgs() { + return {}; + }, + getTsField() { + return "createdOn"; + }, + generateMeta(item) { + return { + id: item.id, + summary: this.getSummary(item), + ts: item[this.getTsField()], + }; + }, + getSummary() { + throw new Error("getSummary is not implemented"); + }, + async processEvent(max) { + const lastTs = this._getLastTs(); + let maxTs = lastTs; + const fn = this.getResourceFn(); + const args = this.getArgs(); + const tsField = this.getTsField(); + + const results = this.mural.paginate({ + fn, + args, + max, + }); + + const items = []; + for await (const item of results) { + const ts = item[tsField]; + if (ts > lastTs) { + items.push(item); + maxTs = Math.max(ts, maxTs); + } + } + + this._setLastTs(maxTs); + + items.forEach((item) => { + const meta = this.generateMeta(item); + this.$emit(item, meta); + }); + }, + }, + async run() { + await this.processEvent(); + }, +}; diff --git a/components/mural/sources/new-area/new-area.mjs b/components/mural/sources/new-area/new-area.mjs new file mode 100644 index 0000000000000..d97417079f6f4 --- /dev/null +++ b/components/mural/sources/new-area/new-area.mjs @@ -0,0 +1,40 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "mural-new-area", + name: "New Area Created", + description: "Emit new event when a new area is created in the user's mural", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + muralId: { + propDefinition: [ + common.props.mural, + "muralId", + (c) => ({ + workspaceId: c.workspaceId, + }), + ], + }, + }, + methods: { + ...common.methods, + getResourceFn() { + return this.mural.listWidgets; + }, + getArgs() { + return { + muralId: this.muralId, + params: { + type: "areas", + }, + }; + }, + getSummary(item) { + return `New Area Widget ID: ${item.id}`; + }, + }, +}; diff --git a/components/mural/sources/new-mural/new-mural.mjs b/components/mural/sources/new-mural/new-mural.mjs new file mode 100644 index 0000000000000..49abc7fd03fda --- /dev/null +++ b/components/mural/sources/new-mural/new-mural.mjs @@ -0,0 +1,28 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "mural-new-mural", + name: "New Mural Created", + description: "Emit new event when a new mural is created.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.mural.listMurals; + }, + getArgs() { + return { + workspaceId: this.workspaceId, + params: { + sortBy: "lastCreated", + }, + }; + }, + getSummary(item) { + return `New Mural ID: ${item.id}`; + }, + }, +}; diff --git a/components/mural/sources/new-sticky/new-sticky.mjs b/components/mural/sources/new-sticky/new-sticky.mjs new file mode 100644 index 0000000000000..6316a2dc51261 --- /dev/null +++ b/components/mural/sources/new-sticky/new-sticky.mjs @@ -0,0 +1,40 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "mural-new-sticky", + name: "New Sticky Note Created", + description: "Emit new event each time a new sticky note is created in a specified mural", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + muralId: { + propDefinition: [ + common.props.mural, + "muralId", + (c) => ({ + workspaceId: c.workspaceId, + }), + ], + }, + }, + methods: { + ...common.methods, + getResourceFn() { + return this.mural.listWidgets; + }, + getArgs() { + return { + muralId: this.muralId, + params: { + type: "sticky notes", + }, + }; + }, + getSummary(item) { + return `New Sticky Note ID: ${item.id}`; + }, + }, +}; diff --git a/components/news_api/actions/search-everything/search-everything.mjs b/components/news_api/actions/search-everything/search-everything.mjs new file mode 100644 index 0000000000000..49e77517b6383 --- /dev/null +++ b/components/news_api/actions/search-everything/search-everything.mjs @@ -0,0 +1,100 @@ +import newsapi from "../../news_api.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "news_api-search-everything", + name: "Search Everything", + description: "Search through millions of articles from over 150,000 large and small news sources and blogs. [See the documentation](https://newsapi.org/docs/endpoints/everything)", + version: "0.0.1", + type: "action", + props: { + newsapi, + q: { + propDefinition: [ + newsapi, + "q", + ], + }, + searchin: { + propDefinition: [ + newsapi, + "searchin", + ], + }, + sourceIds: { + propDefinition: [ + newsapi, + "sourceIds", + ], + }, + domains: { + type: "string[]", + label: "Domains", + description: "An array of domains to restrict the search to", + optional: true, + }, + excludeDomains: { + type: "string[]", + label: "Exclude Domains", + description: "An array of domains to remove from the results", + optional: true, + }, + from: { + type: "string", + label: "From", + description: "A date and optional time for the oldest article allowed. This should be in ISO 8601 format (e.g. `2024-11-01` or `2024-11-01T17:27:47`)", + optional: true, + }, + to: { + type: "string", + label: "To", + description: "A date and optional time for the newest article allowed. This should be in ISO 8601 format (e.g. `2024-11-01` or `2024-11-01T17:27:47`)", + optional: true, + }, + language: { + propDefinition: [ + newsapi, + "language", + ], + }, + sortBy: { + propDefinition: [ + newsapi, + "sortBy", + ], + }, + maxResults: { + propDefinition: [ + newsapi, + "maxResults", + ], + }, + }, + async run({ $ }) { + const { + status, articles, + } = await this.newsapi.searchEverything({ + $, + params: { + q: this.q, + searchin: utils.joinArray(this.searchin), + sources: utils.joinArray(this.sourceIds), + domains: utils.joinArray(this.domains), + excludeDomains: utils.joinArray(this.excludeDomains), + from: this.from, + to: this.to, + language: this.language, + sortBy: this.sortBy, + pageSize: this.maxResults, + }, + }); + + if (status === "ok") { + $.export("$summary", `Successfully retrieved ${articles.length} article${articles.length === 1 + ? "" + : "s"}`); + } + + return articles; + }, +}; diff --git a/components/news_api/actions/search-top-headlines/search-top-headlines.mjs b/components/news_api/actions/search-top-headlines/search-top-headlines.mjs new file mode 100644 index 0000000000000..e520a2bd23a2d --- /dev/null +++ b/components/news_api/actions/search-top-headlines/search-top-headlines.mjs @@ -0,0 +1,73 @@ +import newsapi from "../../news_api.app.mjs"; +import utils from "../../common/utils.mjs"; +import { ConfigurationError } from "@pipedream/platform"; + +export default { + key: "news_api-search-top-headlines", + name: "Search Top Headlines", + description: "Retrieve live top and breaking headlines for a category, single source, multiple sources, or keywords. [See the documentation](https://newsapi.org/docs/endpoints/top-headlines)", + version: "0.0.1", + type: "action", + props: { + newsapi, + q: { + propDefinition: [ + newsapi, + "q", + ], + optional: true, + }, + category: { + type: "string", + label: "Category", + description: "The category you want to get headlines for. Possible options: `business` `entertainment` `general` `health` `science` `sports` `technology`. Note: you can't mix this param with the `sources` param.", + optional: true, + }, + sourceIds: { + propDefinition: [ + newsapi, + "sourceIds", + ], + }, + maxResults: { + propDefinition: [ + newsapi, + "maxResults", + ], + }, + }, + async run({ $ }) { + if (this.category && this.sourceIds) { + throw new ConfigurationError("Please specify only one of `Category` or `SourceIds`"); + } + + const params = { + q: this.q, + category: this.category, + sources: utils.joinArray(this.sourceIds), + pageSize: this.maxResults, + }; + + // The only available country is "us", but it can't be specified along with category or sources. + // At least one of q, category, sources, or country must be entered, so adding in country if + // none of the others are specified. + if (!this.q && !this.category && !this.sourceIds) { + params.country = "us"; + } + + const { + status, articles, + } = await this.newsapi.searchTopHeadlines({ + $, + params, + }); + + if (status === "ok") { + $.export("$summary", `Successfully retrieved ${articles.length} article${articles.length === 1 + ? "" + : "s"}`); + } + + return articles; + }, +}; diff --git a/components/news_api/common/constants.mjs b/components/news_api/common/constants.mjs new file mode 100644 index 0000000000000..527f5bb44c4f4 --- /dev/null +++ b/components/news_api/common/constants.mjs @@ -0,0 +1,76 @@ +const SEARCH_IN_OPTIONS = [ + "title", + "description", + "content", +]; + +const SORT_OPTIONS = [ + "relevancy", + "popularity", + "publishedAt", +]; + +const LANGUAGES = [ + { + value: "ar", + label: "Arabic", + }, + { + value: "de", + label: "German", + }, + { + value: "en", + label: "English", + }, + { + value: "es", + label: "Spanish", + }, + { + value: "fr", + label: "French", + }, + { + value: "he", + label: "Hebrew", + }, + { + value: "it", + label: "Italian", + }, + { + value: "nl", + label: "Dutch", + }, + { + value: "no", + label: "Norwegian", + }, + { + value: "pt", + label: "Portuguese", + }, + { + value: "ru", + label: "Russian", + }, + { + value: "sv", + label: "Swedish", + }, + { + value: "ud", + label: "Urdu", + }, + { + value: "zh", + label: "Chinese", + }, +]; + +export default { + SEARCH_IN_OPTIONS, + SORT_OPTIONS, + LANGUAGES, +}; diff --git a/components/news_api/common/utils.mjs b/components/news_api/common/utils.mjs new file mode 100644 index 0000000000000..1c5c52546a575 --- /dev/null +++ b/components/news_api/common/utils.mjs @@ -0,0 +1,10 @@ +function joinArray(arr) { + if (!arr) { + return undefined; + } + return arr.join(); +} + +export default { + joinArray, +}; diff --git a/components/news_api/news_api.app.mjs b/components/news_api/news_api.app.mjs new file mode 100644 index 0000000000000..138238ac9dd18 --- /dev/null +++ b/components/news_api/news_api.app.mjs @@ -0,0 +1,92 @@ +import { axios } from "@pipedream/platform"; +import constants from "./common/constants.mjs"; + +export default { + type: "app", + app: "news_api", + propDefinitions: { + sourceIds: { + type: "string[]", + label: "Source IDs", + description: "An array of source identifiers (maximum 20) for the news sources or blogs you want headlines from", + optional: true, + async options() { + const { sources } = await this.listSources(); + return sources.map(({ + id: value, name: label, + }) => ({ + value, + label, + })); + }, + }, + searchin: { + type: "string[]", + label: "Search In", + description: "The fields to restrict your q search to. Default: all fields are searched", + options: constants.SEARCH_IN_OPTIONS, + optional: true, + }, + q: { + type: "string", + label: "Query", + description: "Keywords or phrases to search for", + }, + language: { + type: "string", + label: "Language", + description: "The 2-letter ISO-639-1 code of the language you want to get headlines for", + options: constants.LANGUAGES, + optional: true, + }, + sortBy: { + type: "string", + label: "Sort By", + description: "The order to sort the articles in. Default: `publishedAt`", + options: constants.SORT_OPTIONS, + optional: true, + }, + maxResults: { + type: "integer", + label: "Max Results", + description: "The maximum number of results to return. Must be between 1 and 100.", + optional: true, + }, + }, + methods: { + _baseUrl() { + return "https://newsapi.org/v2"; + }, + _makeRequest({ + $ = this, + path, + ...opts + }) { + return axios($, { + url: `${this._baseUrl()}${path}`, + headers: { + "X-Api-Key": `${this.$auth.api_key}`, + }, + ...opts, + }); + }, + listSources(opts = {}) { + return this._makeRequest({ + path: "/top-headlines/sources", + ...opts, + }); + }, + searchEverything(opts = {}) { + return this._makeRequest({ + path: "/everything", + ...opts, + }); + }, + searchTopHeadlines(opts = {}) { + return this._makeRequest({ + path: "/top-headlines", + ...opts, + }); + }, + }, +}; diff --git a/components/news_api/package.json b/components/news_api/package.json new file mode 100644 index 0000000000000..542a42c5868a2 --- /dev/null +++ b/components/news_api/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/news_api", + "version": "0.1.0", + "description": "Pipedream News API Components", + "main": "news_api.app.mjs", + "keywords": [ + "pipedream", + "news_api" + ], + "homepage": "https://pipedream.com/apps/news_api", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" + } +} diff --git a/components/nocodb/actions/add-record/add-record.mjs b/components/nocodb/actions/add-record/add-record.mjs index 82b347bf0bf13..adbe8a8ab03fc 100644 --- a/components/nocodb/actions/add-record/add-record.mjs +++ b/components/nocodb/actions/add-record/add-record.mjs @@ -5,7 +5,7 @@ export default { key: "nocodb-add-record", name: "Add Record", description: "This action adds a record in a table. [See the documentation](https://data-apis-v2.nocodb.com/#tag/Table-Records/operation/db-data-table-row-create)", - version: "0.0.4", + version: "0.0.5", type: "action", props: { ...common.props, diff --git a/components/nocodb/actions/delete-record/delete-record.mjs b/components/nocodb/actions/delete-record/delete-record.mjs index 58de395793d2b..d9591b3c96ada 100644 --- a/components/nocodb/actions/delete-record/delete-record.mjs +++ b/components/nocodb/actions/delete-record/delete-record.mjs @@ -5,7 +5,7 @@ export default { key: "nocodb-delete-record", name: "Delete Record", description: "This action deletes a row in a table. [See the documentation](https://data-apis-v2.nocodb.com/#tag/Table-Records/operation/db-data-table-row-delete)", - version: "0.0.4", + version: "0.0.5", type: "action", props: { ...common.props, diff --git a/components/nocodb/actions/get-record/get-record.mjs b/components/nocodb/actions/get-record/get-record.mjs index 63c4f67494548..5ae8549178421 100644 --- a/components/nocodb/actions/get-record/get-record.mjs +++ b/components/nocodb/actions/get-record/get-record.mjs @@ -5,7 +5,7 @@ export default { key: "nocodb-get-record", name: "Get Record (from row number)", description: "This action gets a row by row Id. [See the documentation](https://data-apis-v2.nocodb.com/#tag/Table-Records/operation/db-data-table-row-read)", - version: "0.0.4", + version: "0.0.5", type: "action", props: { ...common.props, diff --git a/components/nocodb/actions/list-records-matching-criteria/list-records-matching-criteria.mjs b/components/nocodb/actions/list-records-matching-criteria/list-records-matching-criteria.mjs index b1df484d68558..14aabf15b1afa 100644 --- a/components/nocodb/actions/list-records-matching-criteria/list-records-matching-criteria.mjs +++ b/components/nocodb/actions/list-records-matching-criteria/list-records-matching-criteria.mjs @@ -5,7 +5,7 @@ export default { key: "nocodb-list-records-matching-criteria", name: "List Records in Table Matching Criteria", description: "This action lists all rows in a table. [See the documentation](https://data-apis-v2.nocodb.com/#tag/Table-Records/operation/db-data-table-row-list)", - version: "0.0.5", + version: "0.0.6", type: "action", props: { ...common.props, diff --git a/components/nocodb/actions/update-record/update-record.mjs b/components/nocodb/actions/update-record/update-record.mjs index adab950775c61..8756f4f0c64d7 100644 --- a/components/nocodb/actions/update-record/update-record.mjs +++ b/components/nocodb/actions/update-record/update-record.mjs @@ -5,7 +5,7 @@ export default { key: "nocodb-update-record", name: "Update Record", description: "This action updates a record in a table. [See the documentation](https://data-apis-v2.nocodb.com/#tag/Table-Records/operation/db-data-table-row-update)", - version: "0.0.4", + version: "0.0.5", type: "action", props: { ...common.props, diff --git a/components/nocodb/nocodb.app.mjs b/components/nocodb/nocodb.app.mjs index 97065fcb0e0ed..bc4326cafbb25 100644 --- a/components/nocodb/nocodb.app.mjs +++ b/components/nocodb/nocodb.app.mjs @@ -69,6 +69,27 @@ export default { return rows?.map(({ Id }) => `${Id}` ) || []; }, }, + viewId: { + type: "string", + label: "View ID", + description: "The ID of a view", + async options({ + tableId, page, + }) { + const { list } = await this.listViews({ + tableId, + params: { + offset: page * DEFAULT_LIMIT, + }, + }); + return list?.map(({ + id: value, title: label, + }) => ({ + label, + value, + })) || []; + }, + }, data: { type: "any", label: "data", @@ -233,5 +254,13 @@ export default { ...opts, }); }, + listViews({ + tableId, ...opts + }) { + return this._makeRequest({ + path: `/meta/tables/${tableId}/views`, + ...opts, + }); + }, }, }; diff --git a/components/nocodb/package.json b/components/nocodb/package.json index c53cdf7a13904..fd91c3e67f090 100644 --- a/components/nocodb/package.json +++ b/components/nocodb/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/nocodb", - "version": "0.0.7", + "version": "0.1.0", "description": "Pipedream Nocodb Components", "main": "nocodb.app.mjs", "keywords": [ @@ -9,11 +9,10 @@ ], "homepage": "https://pipedream.com/apps/nocodb", "author": "Pipedream (https://pipedream.com/)", - "dependencies": { - "@pipedream/platform": "^1.6.0", - "moment": "^2.29.4" - }, "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } } diff --git a/components/nocodb/sources/common/base.mjs b/components/nocodb/sources/common/base.mjs index 67a42071eb0a0..20b3f807d9d29 100644 --- a/components/nocodb/sources/common/base.mjs +++ b/components/nocodb/sources/common/base.mjs @@ -1,4 +1,3 @@ -import moment from "moment"; import nocodb from "../../nocodb.app.mjs"; import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; @@ -46,59 +45,53 @@ export default { _setLastTime(lastTime) { this.db.set("lastTime", lastTime); }, - async processEvent({ - params, lastTime, - }) { + getParams(timeField) { + return { + sort: `-${timeField}`, + }; + }, + async getRows(records, timeField, lastTime) { + const rows = []; + for await (const row of records) { + if (!lastTime || Date.parse(row[timeField]) >= Date.parse(lastTime)) { + rows.push(row); + } else { + break; + } + } + return rows.reverse(); + }, + async processEvent(max) { const timeField = this.getTimeField(); + const lastTime = this._getLastTime(); const records = this.nocodb.paginate({ fn: this.nocodb.listTableRow, args: { tableId: this.tableId.value, - params, + params: this.getParams(timeField), }, + max, }); - for await (const record of records) { - if (moment(record[timeField]).isAfter(lastTime)) this._setLastTime(record[timeField]); - this.$emit(record, this.getDataToEmit(record)); + const rows = await this.getRows(records, timeField, lastTime); + + if (!rows.length) { + return; } + + this._setLastTime(rows[rows.length - 1][timeField]); + + rows.forEach((row) => this.$emit(row, this.getDataToEmit(row))); }, }, hooks: { - async activate() { - const timeField = this.getTimeField(); - const lastTime = this._getLastTime(); - const { list } = await this.nocodb.listTableRow({ - tableId: this.tableId.value, - params: { - sort: `-${timeField}`, - }, - }); - - list.reverse(); - - for (const row of list) { - if (!lastTime || moment(lastTime).isAfter(row[timeField])) { - this._setLastTime(row[timeField]); - } - this.$emit(row, this.getDataToEmit(row)); - } + async deploy() { + await this.processEvent(25); }, }, async run() { - const timeField = this.getTimeField(); - const lastTime = this._getLastTime(); - const params = { - sort: timeField, - }; - // moment is necessary because nocodb query doesn't filter equal datetime in 'greater than' - if (lastTime) params.where = `(${timeField},gte,${moment(lastTime).add(1, "ms") - .toISOString()})`; - return this.processEvent({ - params, - lastTime, - }); + await this.processEvent(); }, }; diff --git a/components/nocodb/sources/new-record-in-view/new-record-in-view.mjs b/components/nocodb/sources/new-record-in-view/new-record-in-view.mjs new file mode 100644 index 0000000000000..b3f67c758ed62 --- /dev/null +++ b/components/nocodb/sources/new-record-in-view/new-record-in-view.mjs @@ -0,0 +1,51 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "nocodb-new-record-in-view", + name: "New Record in View", + description: "Emit new event for each new record in a view. [See the documentation](https://data-apis-v2.nocodb.com/#tag/Table-Records/operation/db-data-table-row-list)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + viewId: { + propDefinition: [ + common.props.nocodb, + "viewId", + (c) => ({ + tableId: c.tableId.value || c.tableId, + }), + ], + }, + }, + methods: { + ...common.methods, + getDataToEmit(record) { + return { + id: record.id, + summary: `New record created (${record.id})`, + ts: Date.parse(record[this.getTimeField()]), + }; + }, + getTimeField() { + return "created_at"; + }, + getParams(timeField) { + return { + viewId: this.viewId, + fields: timeField, + }; + }, + async getRows(records, timeField, lastTime) { + const rows = []; + for await (const row of records) { + if (!lastTime || Date.parse(row[timeField]) >= Date.parse(lastTime)) { + rows.push(row); + } + } + return rows; + }, + }, +}; diff --git a/components/nocodb/sources/new-record/new-record.mjs b/components/nocodb/sources/new-record/new-record.mjs index 238bf43ca4421..64d836fcfe7e1 100644 --- a/components/nocodb/sources/new-record/new-record.mjs +++ b/components/nocodb/sources/new-record/new-record.mjs @@ -6,7 +6,7 @@ export default { name: "New Record in Table", key: "nocodb-new-record", description: "Emit new event for each new record in table. [See the documentation](https://data-apis-v2.nocodb.com/#tag/Table-Records/operation/db-data-table-row-list)", - version: "0.0.5", + version: "0.0.6", dedupe: "unique", props: { ...common.props, diff --git a/components/nocodb/sources/updated-record/updated-record.mjs b/components/nocodb/sources/updated-record/updated-record.mjs index aa6daa498b211..18633ea71ead5 100644 --- a/components/nocodb/sources/updated-record/updated-record.mjs +++ b/components/nocodb/sources/updated-record/updated-record.mjs @@ -6,7 +6,7 @@ export default { name: "New Update in Table", key: "nocodb-updated-record", description: "Emit new event for each update in table. [See the documentation](https://data-apis-v2.nocodb.com/#tag/Table-Records/operation/db-data-table-row-list)", - version: "0.0.5", + version: "0.0.6", dedupe: "unique", props: { ...common.props, diff --git a/components/notion/actions/append-block/append-block.mjs b/components/notion/actions/append-block/append-block.mjs index 27c6165e5e0fe..9c1be436470c0 100644 --- a/components/notion/actions/append-block/append-block.mjs +++ b/components/notion/actions/append-block/append-block.mjs @@ -6,7 +6,7 @@ export default { key: "notion-append-block", name: "Append Block to Parent", description: "Creates and appends blocks to the specified parent. [See the documentation](https://developers.notion.com/reference/patch-block-children)", - version: "0.2.15", + version: "0.2.16", type: "action", props: { notion, diff --git a/components/notion/actions/create-page-from-database/create-page-from-database.mjs b/components/notion/actions/create-page-from-database/create-page-from-database.mjs index 13492dc5aab64..c0c71904fb112 100644 --- a/components/notion/actions/create-page-from-database/create-page-from-database.mjs +++ b/components/notion/actions/create-page-from-database/create-page-from-database.mjs @@ -7,7 +7,7 @@ export default { key: "notion-create-page-from-database", name: "Create Page from Database", description: "Creates a page from a database. [See the docs](https://developers.notion.com/reference/post-page)", - version: "0.1.13", + version: "0.1.14", type: "action", props: { notion, diff --git a/components/notion/actions/create-page/create-page.mjs b/components/notion/actions/create-page/create-page.mjs index 61d3f9cb7f095..70fca6573e189 100644 --- a/components/notion/actions/create-page/create-page.mjs +++ b/components/notion/actions/create-page/create-page.mjs @@ -7,7 +7,7 @@ export default { key: "notion-create-page", name: "Create Page", description: "Creates a page from a parent page. The only valid property is *title*. [See the documentation](https://developers.notion.com/reference/post-page)", - version: "0.2.11", + version: "0.2.12", type: "action", props: { notion, diff --git a/components/notion/actions/duplicate-page/duplicate-page.mjs b/components/notion/actions/duplicate-page/duplicate-page.mjs index 2bdf1b2626561..fa7b22d26eb7f 100644 --- a/components/notion/actions/duplicate-page/duplicate-page.mjs +++ b/components/notion/actions/duplicate-page/duplicate-page.mjs @@ -7,7 +7,7 @@ export default { key: "notion-duplicate-page", name: "Duplicate Page", description: "Creates a new page copied from an existing page block. [See the docs](https://developers.notion.com/reference/post-page)", - version: "0.0.7", + version: "0.0.8", type: "action", props: { notion, diff --git a/components/notion/actions/update-page/update-page.mjs b/components/notion/actions/update-page/update-page.mjs index e418140a5ecfe..df6c194eaa631 100644 --- a/components/notion/actions/update-page/update-page.mjs +++ b/components/notion/actions/update-page/update-page.mjs @@ -7,7 +7,7 @@ export default { key: "notion-update-page", name: "Update Page", description: "Updates page property values for the specified page. Properties that are not set will remain unchanged. To append page content, use the *append block* action. [See the docs](https://developers.notion.com/reference/patch-page)", - version: "1.1.1", + version: "1.1.2", type: "action", props: { notion, diff --git a/components/notion/common/notion-page-properties.mjs b/components/notion/common/notion-page-properties.mjs index b12dc2cf4df85..621e304c3e352 100644 --- a/components/notion/common/notion-page-properties.mjs +++ b/components/notion/common/notion-page-properties.mjs @@ -64,12 +64,16 @@ const NOTION_PAGE_PROPERTIES = { }, date: { type: "string", - example: "2022-05-15T18:47:00.000Z", + example: "2022-05-15T18:47:00.000Z or { \"start\": \"2022-05-15T18:47:00.000Z\", \"end\": \"2022-06-15T18:47:00.000Z\" }", options: () => undefined, convertToNotion: (property) => ({ - date: { - start: property.value, - }, + date: !(typeof (property.value) === "string") + ? property.value + : property.value.trim().startsWith("{") + ? JSON.parse(property.value) + : { + start: property.value, + }, }), }, people: { diff --git a/components/notion/package.json b/components/notion/package.json index 79139e3951f78..7fbb305fef49d 100644 --- a/components/notion/package.json +++ b/components/notion/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/notion", - "version": "0.2.4", + "version": "0.2.5", "description": "Pipedream Notion Components", "main": "notion.app.mjs", "keywords": [ diff --git a/components/ntfy/actions/send-notification/send-notification.mjs b/components/ntfy/actions/send-notification/send-notification.mjs new file mode 100644 index 0000000000000..4c23e89c5249e --- /dev/null +++ b/components/ntfy/actions/send-notification/send-notification.mjs @@ -0,0 +1,56 @@ +import app from "../../ntfy.app.mjs"; + +export default { + key: "ntfy-send-notification", + name: "Send Notification", + description: "Send a notification using Ntfy. [See the documentation](https://docs.ntfy.sh/publish/).", + version: "0.0.1", + type: "action", + props: { + app, + topic: { + type: "string", + label: "Topic", + description: "The topic to which the notification will be sent", + }, + data: { + type: "string", + label: "Message", + description: "The message content of the notification", + }, + headers: { + type: "object", + label: "Headers", + description: "Optional headers to include in the request. [See the documentation](https://docs.ntfy.sh/publish/).", + optional: true, + }, + }, + methods: { + sendNotification({ + topic, ...args + } = {}) { + return this.app.post({ + path: `/${topic}`, + ...args, + }); + }, + }, + async run({ $ }) { + const { + sendNotification, + topic, + data, + headers, + } = this; + + const response = await sendNotification({ + $, + headers, + topic, + data, + }); + + $.export("$summary", `Successfully sent notification with ID \`${response.id}\`.`); + return response; + }, +}; diff --git a/components/ntfy/ntfy.app.mjs b/components/ntfy/ntfy.app.mjs new file mode 100644 index 0000000000000..7933474f09d0d --- /dev/null +++ b/components/ntfy/ntfy.app.mjs @@ -0,0 +1,25 @@ +import { axios } from "@pipedream/platform"; + +export default { + type: "app", + app: "ntfy", + methods: { + getUrl(path) { + return `${this.$auth.server}${path}`; + }, + _makeRequest({ + $ = this, path, ...args + } = {}) { + return axios($, { + ...args, + url: this.getUrl(path), + }); + }, + post(args = {}) { + return this._makeRequest({ + method: "POST", + ...args, + }); + }, + }, +}; diff --git a/components/ntfy/package.json b/components/ntfy/package.json new file mode 100644 index 0000000000000..9b18e22fd40e3 --- /dev/null +++ b/components/ntfy/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/ntfy", + "version": "0.1.0", + "description": "Pipedream ntfy Components", + "main": "ntfy.app.mjs", + "keywords": [ + "pipedream", + "ntfy" + ], + "homepage": "https://pipedream.com/apps/ntfy", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "3.0.3" + } +} diff --git a/components/openai/actions/analyze-image-content/analyze-image-content.mjs b/components/openai/actions/analyze-image-content/analyze-image-content.mjs index 3062ec31322c9..f106bf656f01e 100644 --- a/components/openai/actions/analyze-image-content/analyze-image-content.mjs +++ b/components/openai/actions/analyze-image-content/analyze-image-content.mjs @@ -8,7 +8,7 @@ export default { key: "openai-analyze-image-content", name: "Analyze Image Content", description: "Send a message or question about an image and receive a response. [See the documentation](https://platform.openai.com/docs/api-reference/runs/createThreadAndRun)", - version: "0.1.0", + version: "0.1.2", type: "action", props: { openai, diff --git a/components/openai/actions/cancel-run/cancel-run.mjs b/components/openai/actions/cancel-run/cancel-run.mjs index f2fb2bc614054..40d0a6eaf4fc1 100644 --- a/components/openai/actions/cancel-run/cancel-run.mjs +++ b/components/openai/actions/cancel-run/cancel-run.mjs @@ -4,7 +4,7 @@ export default { key: "openai-cancel-run", name: "Cancel Run (Assistants)", description: "Cancels a run that is in progress. [See the documentation](https://platform.openai.com/docs/api-reference/runs/cancelRun)", - version: "0.0.10", + version: "0.0.11", type: "action", props: { openai, diff --git a/components/openai/actions/chat-with-assistant/chat-with-assistant.mjs b/components/openai/actions/chat-with-assistant/chat-with-assistant.mjs index e7d7d03496eb2..dc11b7ef09dcc 100644 --- a/components/openai/actions/chat-with-assistant/chat-with-assistant.mjs +++ b/components/openai/actions/chat-with-assistant/chat-with-assistant.mjs @@ -6,7 +6,7 @@ export default { key: "openai-chat-with-assistant", name: "Chat with Assistant", description: "Sends a message and generates a response, storing the message history for a continuous conversation. [See the documentation](https://platform.openai.com/docs/api-reference/runs/createThreadAndRun)", - version: "0.0.5", + version: "0.0.7", type: "action", props: { openai, diff --git a/components/openai/actions/chat/chat.mjs b/components/openai/actions/chat/chat.mjs index 8f741e50e9551..ce9c55b1cecab 100644 --- a/components/openai/actions/chat/chat.mjs +++ b/components/openai/actions/chat/chat.mjs @@ -6,7 +6,7 @@ import { ConfigurationError } from "@pipedream/platform"; export default { ...common, name: "Chat", - version: "0.2.1", + version: "0.2.3", key: "openai-chat", description: "The Chat API, using the `gpt-3.5-turbo` or `gpt-4` model. [See the documentation](https://platform.openai.com/docs/api-reference/chat)", type: "action", @@ -57,19 +57,87 @@ export default { optional: true, reloadProps: true, }, + toolTypes: { + type: "string[]", + label: "Tool Types", + description: "The types of tools to enable on the assistant", + options: constants.TOOL_TYPES.filter((toolType) => toolType === "function"), + optional: true, + reloadProps: true, + }, }, additionalProps() { - const { responseFormat } = this; - if (responseFormat !== constants.CHAT_RESPONSE_FORMAT.JSON_SCHEMA.value) { - return {}; - } - return { - jsonSchema: { + const { + responseFormat, + toolTypes, + numberOfFunctions, + } = this; + const props = {}; + + if (responseFormat === constants.CHAT_RESPONSE_FORMAT.JSON_SCHEMA.value) { + props.jsonSchema = { type: "string", label: "JSON Schema", description: "Define the schema that the model's output must adhere to. [See the documentation here](https://platform.openai.com/docs/guides/structured-outputs/supported-schemas).", - }, - }; + }; + } + + if (toolTypes?.includes("function")) { + props.numberOfFunctions = { + type: "integer", + label: "Number of Functions", + description: "The number of functions to define", + optional: true, + reloadProps: true, + default: 1, + }; + + for (let i = 0; i < (numberOfFunctions || 1); i++) { + props[`functionName_${i}`] = { + type: "string", + label: `Function Name ${i + 1}`, + description: "The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64.", + }; + props[`functionDescription_${i}`] = { + type: "string", + label: `Function Description ${i + 1}`, + description: "A description of what the function does, used by the model to choose when and how to call the function.", + optional: true, + }; + props[`functionParameters_${i}`] = { + type: "object", + label: `Function Parameters ${i + 1}`, + description: "The parameters the functions accepts, described as a JSON Schema object. See the [guide](https://platform.openai.com/docs/guides/text-generation/function-calling) for examples, and the [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for documentation about the format.", + optional: true, + }; + } + } + + return props; + }, + methods: { + ...common.methods, + _buildTools() { + const tools = this.toolTypes?.filter((toolType) => toolType !== "function")?.map((toolType) => ({ + type: toolType, + })) || []; + if (this.toolTypes?.includes("function")) { + const numberOfFunctions = this.numberOfFunctions || 1; + for (let i = 0; i < numberOfFunctions; i++) { + tools.push({ + type: "function", + function: { + name: this[`functionName_${i}`], + description: this[`functionDescription_${i}`], + parameters: this[`functionParameters_${i}`], + }, + }); + } + } + return tools.length + ? tools + : undefined; + }, }, async run({ $ }) { if (this.audio && !this.modelId.includes("gpt-4o-audio-preview")) { @@ -80,7 +148,10 @@ export default { const response = await this.openai.createChatCompletion({ $, - data: args, + data: { + ...args, + tools: this._buildTools(), + }, }); if (response) { diff --git a/components/openai/actions/classify-items-into-categories/classify-items-into-categories.mjs b/components/openai/actions/classify-items-into-categories/classify-items-into-categories.mjs index 7e30c012cffa5..bf10ee2669a47 100644 --- a/components/openai/actions/classify-items-into-categories/classify-items-into-categories.mjs +++ b/components/openai/actions/classify-items-into-categories/classify-items-into-categories.mjs @@ -3,7 +3,7 @@ import common from "../common/common-helper.mjs"; export default { ...common, name: "Classify Items into Categories", - version: "0.1.1", + version: "0.1.2", key: "openai-classify-items-into-categories", description: "Classify items into specific categories using the Chat API. [See the documentation](https://platform.openai.com/docs/api-reference/chat)", type: "action", diff --git a/components/openai/actions/common/common-assistants.mjs b/components/openai/actions/common/common-assistants.mjs index f0e19c1dc209e..f6c1e26684f22 100644 --- a/components/openai/actions/common/common-assistants.mjs +++ b/components/openai/actions/common/common-assistants.mjs @@ -16,7 +16,20 @@ export default { if (!this.toolTypes?.length) { return props; } - return this.getToolProps(); + if (this.toolTypes.includes("function")) { + props.numberOfFunctions = { + type: "integer", + label: "Number of Functions", + description: "The number of functions to define.", + optional: true, + reloadProps: true, + default: 1, + }; + } + return { + ...props, + ...(await this.getToolProps()), + }; }, methods: { async getToolProps() { @@ -58,23 +71,26 @@ export default { }; } if (this.toolTypes.includes("function")) { - props.functionName = { - type: "string", - label: "Function Name", - description: "The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64.", - }; - props.functionDescription = { - type: "string", - label: "Function Description", - description: "A description of what the function does, used by the model to choose when and how to call the function.", - optional: true, - }; - props.functionParameters = { - type: "object", - label: "Function Parameters", - description: "The parameters the functions accepts, described as a JSON Schema object. See the [guide](https://platform.openai.com/docs/guides/text-generation/function-calling) for examples, and the [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for documentation about the format.", - optional: true, - }; + const numberOfFunctions = this.numberOfFunctions || 1; + for (let i = 0; i < numberOfFunctions; i++) { + props[`functionName_${i}`] = { + type: "string", + label: `Function Name ${i + 1}`, + description: "The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64.", + }; + props[`functionDescription_${i}`] = { + type: "string", + label: `Function Description ${i + 1}`, + description: "A description of what the function does, used by the model to choose when and how to call the function.", + optional: true, + }; + props[`functionParameters_${i}`] = { + type: "object", + label: `Function Parameters ${i + 1}`, + description: "The parameters the functions accepts, described as a JSON Schema object. See the [guide](https://platform.openai.com/docs/guides/text-generation/function-calling) for examples, and the [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for documentation about the format.", + optional: true, + }; + } } return props; }, @@ -83,14 +99,17 @@ export default { type: toolType, })) || []; if (this.toolTypes?.includes("function")) { - tools.push({ - type: "function", - function: { - name: this.functionName, - description: this.functionDescription, - parameters: this.functionParameters, - }, - }); + const numberOfFunctions = this.numberOfFunctions || 1; + for (let i = 0; i < numberOfFunctions; i++) { + tools.push({ + type: "function", + function: { + name: this[`functionName_${i}`], + description: this[`functionDescription_${i}`], + parameters: this[`functionParameters_${i}`], + }, + }); + } } return tools.length ? tools diff --git a/components/openai/actions/convert-text-to-speech/convert-text-to-speech.mjs b/components/openai/actions/convert-text-to-speech/convert-text-to-speech.mjs index 890ca942945b6..aad60c68cd1d3 100644 --- a/components/openai/actions/convert-text-to-speech/convert-text-to-speech.mjs +++ b/components/openai/actions/convert-text-to-speech/convert-text-to-speech.mjs @@ -5,7 +5,7 @@ export default { key: "openai-convert-text-to-speech", name: "Convert Text to Speech (TTS)", description: "Generates audio from the input text. [See the documentation](https://platform.openai.com/docs/api-reference/audio/createSpeech)", - version: "0.0.9", + version: "0.0.10", type: "action", props: { openai, diff --git a/components/openai/actions/create-assistant/create-assistant.mjs b/components/openai/actions/create-assistant/create-assistant.mjs index 4b1a7c5fb872b..c88071a627ecf 100644 --- a/components/openai/actions/create-assistant/create-assistant.mjs +++ b/components/openai/actions/create-assistant/create-assistant.mjs @@ -6,7 +6,7 @@ export default { key: "openai-create-assistant", name: "Create Assistant", description: "Creates an assistant with a model and instructions. [See the documentation](https://platform.openai.com/docs/api-reference/assistants/createAssistant)", - version: "0.1.7", + version: "0.1.9", type: "action", props: { openai, diff --git a/components/openai/actions/create-batch/create-batch.mjs b/components/openai/actions/create-batch/create-batch.mjs index de83b3161f2a2..1c5b18f47f06c 100644 --- a/components/openai/actions/create-batch/create-batch.mjs +++ b/components/openai/actions/create-batch/create-batch.mjs @@ -8,7 +8,7 @@ export default { key: "openai-create-batch", name: "Create Batch", description: "Creates and executes a batch from an uploaded file of requests. [See the documentation](https://platform.openai.com/docs/api-reference/batch/create)", - version: "0.0.4", + version: "0.0.5", type: "action", props: { openai, diff --git a/components/openai/actions/create-embeddings/create-embeddings.mjs b/components/openai/actions/create-embeddings/create-embeddings.mjs index 20d400b6deb7b..66c0697a8d7be 100644 --- a/components/openai/actions/create-embeddings/create-embeddings.mjs +++ b/components/openai/actions/create-embeddings/create-embeddings.mjs @@ -4,7 +4,7 @@ import common from "../common/common.mjs"; export default { name: "Create Embeddings", - version: "0.0.13", + version: "0.0.14", key: "openai-create-embeddings", description: "Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms. [See the documentation](https://platform.openai.com/docs/api-reference/embeddings)", type: "action", diff --git a/components/openai/actions/create-fine-tuning-job/create-fine-tuning-job.mjs b/components/openai/actions/create-fine-tuning-job/create-fine-tuning-job.mjs index 611c9daddeb55..6dcc7a58b60e9 100644 --- a/components/openai/actions/create-fine-tuning-job/create-fine-tuning-job.mjs +++ b/components/openai/actions/create-fine-tuning-job/create-fine-tuning-job.mjs @@ -4,7 +4,7 @@ export default { key: "openai-create-fine-tuning-job", name: "Create Fine Tuning Job", description: "Creates a job that fine-tunes a specified model from a given dataset. [See the documentation](https://platform.openai.com/docs/api-reference/fine-tuning/create)", - version: "0.0.9", + version: "0.0.10", type: "action", props: { openai, diff --git a/components/openai/actions/create-image/create-image.mjs b/components/openai/actions/create-image/create-image.mjs index 01a8fb8a5f770..e103ff5c67e28 100644 --- a/components/openai/actions/create-image/create-image.mjs +++ b/components/openai/actions/create-image/create-image.mjs @@ -4,7 +4,7 @@ import fs from "fs"; export default { name: "Create Image (Dall-E)", - version: "0.1.17", + version: "0.1.18", key: "openai-create-image", description: "Creates an image given a prompt returning a URL to the image. [See the documentation](https://platform.openai.com/docs/api-reference/images)", type: "action", diff --git a/components/openai/actions/create-moderation/create-moderation.mjs b/components/openai/actions/create-moderation/create-moderation.mjs index fbb1000812945..4a2646f5cc611 100644 --- a/components/openai/actions/create-moderation/create-moderation.mjs +++ b/components/openai/actions/create-moderation/create-moderation.mjs @@ -5,7 +5,7 @@ export default { key: "openai-create-moderation", name: "Create Moderation", description: "Classifies if text is potentially harmful. [See the documentation](https://platform.openai.com/docs/api-reference/moderations/create)", - version: "0.0.4", + version: "0.0.5", type: "action", props: { openai, diff --git a/components/openai/actions/create-thread/create-thread.mjs b/components/openai/actions/create-thread/create-thread.mjs index f1581782c808e..885471417ce58 100644 --- a/components/openai/actions/create-thread/create-thread.mjs +++ b/components/openai/actions/create-thread/create-thread.mjs @@ -6,7 +6,7 @@ export default { key: "openai-create-thread", name: "Create Thread (Assistants)", description: "Creates a thread with optional messages and metadata, and optionally runs the thread using the specified assistant. [See the documentation](https://platform.openai.com/docs/api-reference/threads/createThread)", - version: "0.0.9", + version: "0.0.11", type: "action", props: { openai, diff --git a/components/openai/actions/create-transcription/create-transcription.mjs b/components/openai/actions/create-transcription/create-transcription.mjs index 6fcec2e43d091..8c977872fb8cd 100644 --- a/components/openai/actions/create-transcription/create-transcription.mjs +++ b/components/openai/actions/create-transcription/create-transcription.mjs @@ -24,7 +24,7 @@ const pipelineAsync = promisify(stream.pipeline); export default { name: "Create Transcription (Whisper)", - version: "0.1.13", + version: "0.1.14", key: "openai-create-transcription", description: "Transcribes audio into the input language. [See the documentation](https://platform.openai.com/docs/api-reference/audio/create).", type: "action", diff --git a/components/openai/actions/create-vector-store-file/create-vector-store-file.mjs b/components/openai/actions/create-vector-store-file/create-vector-store-file.mjs new file mode 100644 index 0000000000000..035e72411f8f9 --- /dev/null +++ b/components/openai/actions/create-vector-store-file/create-vector-store-file.mjs @@ -0,0 +1,77 @@ +import openai from "../../openai.app.mjs"; + +export default { + key: "openai-create-vector-store-file", + name: "Create Vector Store File", + description: "Create a vector store file. [See the documentation](https://platform.openai.com/docs/api-reference/vector-stores-files/createFile)", + version: "0.0.1", + type: "action", + props: { + openai, + vectorStoreId: { + propDefinition: [ + openai, + "vectorStoreId", + ], + }, + fileId: { + propDefinition: [ + openai, + "fileId", + ], + }, + chunkingStrategy: { + type: "string", + label: "Chunking Strategy", + description: "The chunking strategy used to chunk the file", + options: [ + "auto", + "static", + ], + optional: true, + reloadProps: true, + }, + }, + additionalProps() { + const props = {}; + + if (this?.chunkingStrategy === "static") { + props.maxChunkSizeTokens = { + type: "integer", + label: "Max Chunk Size Tokens", + description: "The maximum number of tokens in each chunk. The default value is `800`. The minimum value is `100` and the maximum value is `4096`.", + default: 800, + optional: true, + }; + props.chunkOverlapTokens = { + type: "integer", + label: "Chunk Overlap Tokens", + description: "The number of tokens that overlap between chunks. The default value is `400`. Note that the overlap must not exceed half of max_chunk_size_tokens.", + default: 400, + optional: true, + }; + } + + return props; + }, + async run({ $ }) { + const response = await this.openai.createVectorStoreFile({ + $, + vectorStoreId: this.vectorStoreId, + data: { + file_id: this.fileId, + chunking_strategy: this.chunkingStrategy && { + type: this.chunkingStrategy, + static: this.chunkingStrategy === "static" + ? { + max_chunk_size_tokens: this.maxChunkSizeTokens, + chunk_overlap_tokens: this.chunkOverlapTokens, + } + : undefined, + }, + }, + }); + $.export("$summary", `Successfully created vector store file with ID: ${response.id}`); + return response; + }, +}; diff --git a/components/openai/actions/create-vector-store/create-vector-store.mjs b/components/openai/actions/create-vector-store/create-vector-store.mjs new file mode 100644 index 0000000000000..3ee22b8216163 --- /dev/null +++ b/components/openai/actions/create-vector-store/create-vector-store.mjs @@ -0,0 +1,103 @@ +import openai from "../../openai.app.mjs"; + +export default { + key: "openai-create-vector-store", + name: "Create Vector Store", + description: "Create a vector store. [See the documentation](https://platform.openai.com/docs/api-reference/vector-stores/create)", + version: "0.0.1", + type: "action", + props: { + openai, + fileIds: { + propDefinition: [ + openai, + "fileId", + ], + type: "string[]", + label: "File IDs", + description: "An array of File IDs that the vector store should use", + optional: true, + reloadProps: true, + }, + name: { + type: "string", + label: "Name", + description: "Name of the vector store", + optional: true, + }, + expiryDays: { + type: "integer", + label: "Expiration Days", + description: "The number of days after the `last_active_at` time that the vector store will expire", + optional: true, + }, + metadata: { + type: "object", + label: "Metadata", + description: "Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a structured format", + optional: true, + }, + }, + additionalProps() { + const props = {}; + if (!this.fileIds?.length) { + return props; + } + + props.chunkingStrategy = { + type: "string", + label: "Chunking Strategy", + description: "The chunking strategy used to chunk the file(s)", + options: [ + "auto", + "static", + ], + optional: true, + reloadProps: true, + }; + + if (this?.chunkingStrategy === "static") { + props.maxChunkSizeTokens = { + type: "integer", + label: "Max Chunk Size Tokens", + description: "The maximum number of tokens in each chunk. The default value is `800`. The minimum value is `100` and the maximum value is `4096`.", + default: 800, + optional: true, + }; + props.chunkOverlapTokens = { + type: "integer", + label: "Chunk Overlap Tokens", + description: "The number of tokens that overlap between chunks. The default value is `400`. Note that the overlap must not exceed half of max_chunk_size_tokens.", + default: 400, + optional: true, + }; + } + + return props; + }, + async run({ $ }) { + const response = await this.openai.createVectorStore({ + $, + data: { + file_ids: this.fileIds, + name: this.name, + expires_after: this.expiryDays && { + anchor: "last_active_at", + days: this.expiryDays, + }, + chunking_strategy: this.chunkingStrategy && { + type: this.chunkingStrategy, + static: this.chunkingStrategy === "static" + ? { + max_chunk_size_tokens: this.maxChunkSizeTokens, + chunk_overlap_tokens: this.chunkOverlapTokens, + } + : undefined, + }, + metadata: this.metadata, + }, + }); + $.export("$summary", `Successfully created vector store with ID: ${response.id}`); + return response; + }, +}; diff --git a/components/openai/actions/delete-file/delete-file.mjs b/components/openai/actions/delete-file/delete-file.mjs index 9340375ab91d7..9aa7593802a3a 100644 --- a/components/openai/actions/delete-file/delete-file.mjs +++ b/components/openai/actions/delete-file/delete-file.mjs @@ -4,7 +4,7 @@ export default { key: "openai-delete-file", name: "Delete File", description: "Deletes a specified file from OpenAI. [See the documentation](https://platform.openai.com/docs/api-reference/files/delete)", - version: "0.0.10", + version: "0.0.11", type: "action", props: { openai, diff --git a/components/openai/actions/delete-vector-store-file/delete-vector-store-file.mjs b/components/openai/actions/delete-vector-store-file/delete-vector-store-file.mjs new file mode 100644 index 0000000000000..68ea78acde3dd --- /dev/null +++ b/components/openai/actions/delete-vector-store-file/delete-vector-store-file.mjs @@ -0,0 +1,36 @@ +import openai from "../../openai.app.mjs"; + +export default { + key: "openai-delete-vector-store-file", + name: "Delete Vector Store File", + description: "Deletes a vector store file. [See the documentation](https://platform.openai.com/docs/api-reference/vector-stores-files/deleteFile)", + version: "0.0.1", + type: "action", + props: { + openai, + vectorStoreId: { + propDefinition: [ + openai, + "vectorStoreId", + ], + }, + vectorStoreFileId: { + propDefinition: [ + openai, + "vectorStoreFileId", + (c) => ({ + vectorStoreId: c.vectorStoreId, + }), + ], + }, + }, + async run({ $ }) { + const response = await this.openai.deleteVectorStoreFile({ + $, + vectorStoreId: this.vectorStoreId, + vectorStoreFileId: this.vectorStoreFileId, + }); + $.export("$summary", `Successfully deleted vector store file with ID: ${this.vectorStoreFileId}`); + return response; + }, +}; diff --git a/components/openai/actions/delete-vector-store/delete-vector-store.mjs b/components/openai/actions/delete-vector-store/delete-vector-store.mjs new file mode 100644 index 0000000000000..27550a19a255b --- /dev/null +++ b/components/openai/actions/delete-vector-store/delete-vector-store.mjs @@ -0,0 +1,26 @@ +import openai from "../../openai.app.mjs"; + +export default { + key: "openai-delete-vector-store", + name: "Delete Vector Store", + description: "Delete a vector store. [See the documentation](https://platform.openai.com/docs/api-reference/vector-stores/delete)", + version: "0.0.1", + type: "action", + props: { + openai, + vectorStoreId: { + propDefinition: [ + openai, + "vectorStoreId", + ], + }, + }, + async run({ $ }) { + const response = await this.openai.deleteVectorStore({ + $, + vectorStoreId: this.vectorStoreId, + }); + $.export("$summary", `Successfully deleted vector store with ID: ${this.vectorStoreId}`); + return response; + }, +}; diff --git a/components/openai/actions/list-files/list-files.mjs b/components/openai/actions/list-files/list-files.mjs index 8c88b1991c2a7..92722d240fc5d 100644 --- a/components/openai/actions/list-files/list-files.mjs +++ b/components/openai/actions/list-files/list-files.mjs @@ -4,7 +4,7 @@ export default { key: "openai-list-files", name: "List Files", description: "Returns a list of files that belong to the user's organization. [See the documentation](https://platform.openai.com/docs/api-reference/files/list)", - version: "0.0.10", + version: "0.0.11", type: "action", props: { openai, diff --git a/components/openai/actions/list-messages/list-messages.mjs b/components/openai/actions/list-messages/list-messages.mjs index 37c2cda81f74e..acbf2087550a9 100644 --- a/components/openai/actions/list-messages/list-messages.mjs +++ b/components/openai/actions/list-messages/list-messages.mjs @@ -4,7 +4,7 @@ export default { key: "openai-list-messages", name: "List Messages (Assistants)", description: "Lists the messages for a given thread. [See the documentation](https://platform.openai.com/docs/api-reference/messages/listMessages)", - version: "0.0.11", + version: "0.0.12", type: "action", props: { openai, diff --git a/components/openai/actions/list-run-steps/list-run-steps.mjs b/components/openai/actions/list-run-steps/list-run-steps.mjs index 3f191a2bd8534..e9a84b3ccd361 100644 --- a/components/openai/actions/list-run-steps/list-run-steps.mjs +++ b/components/openai/actions/list-run-steps/list-run-steps.mjs @@ -4,7 +4,7 @@ export default { key: "openai-list-run-steps", name: "List Run Steps (Assistants)", description: "Returns a list of run steps belonging to a run. [See the documentation](https://platform.openai.com/docs/api-reference/runs/list-run-steps)", - version: "0.0.10", + version: "0.0.11", type: "action", props: { openai, diff --git a/components/openai/actions/list-runs/list-runs.mjs b/components/openai/actions/list-runs/list-runs.mjs index 1201a20374901..f9d9bd0e4f6b1 100644 --- a/components/openai/actions/list-runs/list-runs.mjs +++ b/components/openai/actions/list-runs/list-runs.mjs @@ -4,7 +4,7 @@ export default { key: "openai-list-runs", name: "List Runs (Assistants)", description: "Returns a list of runs belonging to a thread. [See the documentation](https://platform.openai.com/docs/api-reference/runs/list)", - version: "0.0.11", + version: "0.0.12", type: "action", props: { openai, diff --git a/components/openai/actions/list-vector-store-files/list-vector-store-files.mjs b/components/openai/actions/list-vector-store-files/list-vector-store-files.mjs new file mode 100644 index 0000000000000..c66f966ceb87c --- /dev/null +++ b/components/openai/actions/list-vector-store-files/list-vector-store-files.mjs @@ -0,0 +1,53 @@ +import openai from "../../openai.app.mjs"; + +export default { + key: "openai-list-vector-store-files", + name: "List Vector Store Files", + description: "Returns a list of vector store file. [See the documentation](https://platform.openai.com/docs/api-reference/vector-stores-files/listFiles)", + version: "0.0.1", + type: "action", + props: { + openai, + vectorStoreId: { + propDefinition: [ + openai, + "vectorStoreId", + ], + }, + limit: { + propDefinition: [ + openai, + "limit", + ], + }, + order: { + propDefinition: [ + openai, + "order", + ], + }, + }, + async run({ $ }) { + const response = this.openai.paginate({ + resourceFn: this.openai.listVectorStoreFiles, + args: { + $, + vectorStoreId: this.vectorStoreId, + params: { + order: this.order, + }, + }, + max: this.limit, + }); + + const vectorStoreFiles = []; + for await (const vectorStoreFile of response) { + vectorStoreFiles.push(vectorStoreFile); + } + + $.export("$summary", `Successfully retrieved ${vectorStoreFiles.length} vector store file${vectorStoreFiles.length === 1 + ? "" + : "s"}`); + return vectorStoreFiles; + }, +}; diff --git a/components/openai/actions/list-vector-stores/list-vector-stores.mjs b/components/openai/actions/list-vector-stores/list-vector-stores.mjs new file mode 100644 index 0000000000000..e8f123a78c4d9 --- /dev/null +++ b/components/openai/actions/list-vector-stores/list-vector-stores.mjs @@ -0,0 +1,46 @@ +import openai from "../../openai.app.mjs"; + +export default { + key: "openai-list-vector-stores", + name: "List Vector Stores", + description: "Returns a list of vector stores. [See the documentation](https://platform.openai.com/docs/api-reference/vector-stores/list)", + version: "0.0.1", + type: "action", + props: { + openai, + limit: { + propDefinition: [ + openai, + "limit", + ], + }, + order: { + propDefinition: [ + openai, + "order", + ], + }, + }, + async run({ $ }) { + const response = this.openai.paginate({ + resourceFn: this.openai.listVectorStores, + args: { + $, + params: { + order: this.order, + }, + }, + max: this.limit, + }); + + const vectorStores = []; + for await (const vectorStore of response) { + vectorStores.push(vectorStore); + } + + $.export("$summary", `Successfully retrieved ${vectorStores.length} vector store${vectorStores.length === 1 + ? "" + : "s"}`); + return vectorStores; + }, +}; diff --git a/components/openai/actions/modify-assistant/modify-assistant.mjs b/components/openai/actions/modify-assistant/modify-assistant.mjs index 37bd585a53c04..6333623d53c1e 100644 --- a/components/openai/actions/modify-assistant/modify-assistant.mjs +++ b/components/openai/actions/modify-assistant/modify-assistant.mjs @@ -6,7 +6,7 @@ export default { key: "openai-modify-assistant", name: "Modify an Assistant", description: "Modifies an existing OpenAI assistant. [See the documentation](https://platform.openai.com/docs/api-reference/assistants/modifyAssistant)", - version: "0.1.7", + version: "0.1.9", type: "action", props: { openai, diff --git a/components/openai/actions/retrieve-file-content/retrieve-file-content.mjs b/components/openai/actions/retrieve-file-content/retrieve-file-content.mjs index 952bba36ccb96..128841faa6ffa 100644 --- a/components/openai/actions/retrieve-file-content/retrieve-file-content.mjs +++ b/components/openai/actions/retrieve-file-content/retrieve-file-content.mjs @@ -5,7 +5,7 @@ export default { key: "openai-retrieve-file-content", name: "Retrieve File Content", description: "Retrieves the contents of the specified file. [See the documentation](https://platform.openai.com/docs/api-reference/files/retrieve-content)", - version: "0.0.10", + version: "0.0.11", type: "action", props: { openai, diff --git a/components/openai/actions/retrieve-file/retrieve-file.mjs b/components/openai/actions/retrieve-file/retrieve-file.mjs index 671a9bb3561b9..171fa29378e73 100644 --- a/components/openai/actions/retrieve-file/retrieve-file.mjs +++ b/components/openai/actions/retrieve-file/retrieve-file.mjs @@ -4,7 +4,7 @@ export default { key: "openai-retrieve-file", name: "Retrieve File", description: "Retrieves a specific file from OpenAI. [See the documentation](https://platform.openai.com/docs/api-reference/files/retrieve)", - version: "0.0.10", + version: "0.0.11", type: "action", props: { openai, diff --git a/components/openai/actions/retrieve-run-step/retrieve-run-step.mjs b/components/openai/actions/retrieve-run-step/retrieve-run-step.mjs index 4536673229e3b..0a44686bab787 100644 --- a/components/openai/actions/retrieve-run-step/retrieve-run-step.mjs +++ b/components/openai/actions/retrieve-run-step/retrieve-run-step.mjs @@ -4,7 +4,7 @@ export default { key: "openai-retrieve-run-step", name: "Retrieve Run Step (Assistants)", description: "Retrieve a specific run step in a thread. [See the documentation](https://platform.openai.com/docs/api-reference/runs/getRunStep)", - version: "0.0.10", + version: "0.0.11", type: "action", props: { openai, diff --git a/components/openai/actions/retrieve-run/retrieve-run.mjs b/components/openai/actions/retrieve-run/retrieve-run.mjs index e5dfc49cbcbb6..f3827345e7711 100644 --- a/components/openai/actions/retrieve-run/retrieve-run.mjs +++ b/components/openai/actions/retrieve-run/retrieve-run.mjs @@ -4,7 +4,7 @@ export default { key: "openai-retrieve-run", name: "Retrieve Run (Assistants)", description: "Retrieves a specific run within a thread. [See the documentation](https://platform.openai.com/docs/api-reference/runs/getRun)", - version: "0.0.10", + version: "0.0.11", type: "action", props: { openai, diff --git a/components/openai/actions/retrieve-vector-store-file/retrieve-vector-store-file.mjs b/components/openai/actions/retrieve-vector-store-file/retrieve-vector-store-file.mjs new file mode 100644 index 0000000000000..aa58ba1ae65fe --- /dev/null +++ b/components/openai/actions/retrieve-vector-store-file/retrieve-vector-store-file.mjs @@ -0,0 +1,36 @@ +import openai from "../../openai.app.mjs"; + +export default { + key: "openai-retrieve-vector-store-file", + name: "Retrieve Vector Store File", + description: "Retrieve a vector store file. [See the documentation](https://platform.openai.com/docs/api-reference/vector-stores-files/getFile)", + version: "0.0.1", + type: "action", + props: { + openai, + vectorStoreId: { + propDefinition: [ + openai, + "vectorStoreId", + ], + }, + vectorStoreFileId: { + propDefinition: [ + openai, + "vectorStoreFileId", + (c) => ({ + vectorStoreId: c.vectorStoreId, + }), + ], + }, + }, + async run({ $ }) { + const response = await this.openai.getVectorStoreFile({ + $, + vectorStoreId: this.vectorStoreId, + vectorStoreFileId: this.vectorStoreFileId, + }); + $.export("$summary", `Successfully retrieved vector store file with ID: ${this.vectorStoreFileId}`); + return response; + }, +}; diff --git a/components/openai/actions/retrieve-vector-store/retrieve-vector-store.mjs b/components/openai/actions/retrieve-vector-store/retrieve-vector-store.mjs new file mode 100644 index 0000000000000..858f00c494f66 --- /dev/null +++ b/components/openai/actions/retrieve-vector-store/retrieve-vector-store.mjs @@ -0,0 +1,26 @@ +import openai from "../../openai.app.mjs"; + +export default { + key: "openai-retrieve-vector-store", + name: "Retrieve Vector Store", + description: "Retrieve a vector store. [See the documentation](https://platform.openai.com/docs/api-reference/vector-stores/retrieve)", + version: "0.0.1", + type: "action", + props: { + openai, + vectorStoreId: { + propDefinition: [ + openai, + "vectorStoreId", + ], + }, + }, + async run({ $ }) { + const response = await this.openai.getVectorStore({ + $, + vectorStoreId: this.vectorStoreId, + }); + $.export("$summary", `Successfully retrieved vector store with ID: ${this.vectorStoreId}`); + return response; + }, +}; diff --git a/components/openai/actions/send-prompt/send-prompt.mjs b/components/openai/actions/send-prompt/send-prompt.mjs index fa1bf3abae291..c5c100847ecd9 100644 --- a/components/openai/actions/send-prompt/send-prompt.mjs +++ b/components/openai/actions/send-prompt/send-prompt.mjs @@ -4,7 +4,7 @@ import common from "../common/common.mjs"; export default { ...common, name: "Create Completion (Send Prompt)", - version: "0.1.12", + version: "0.1.13", key: "openai-send-prompt", description: "OpenAI recommends using the **Chat** action for the latest `gpt-3.5-turbo` API, since it's faster and 10x cheaper. This action creates a completion for the provided prompt and parameters using the older `/completions` API. [See the documentation](https://beta.openai.com/docs/api-reference/completions/create)", type: "action", diff --git a/components/openai/actions/submit-tool-outputs-to-run/submit-tool-outputs-to-run.mjs b/components/openai/actions/submit-tool-outputs-to-run/submit-tool-outputs-to-run.mjs index 72a3b47548369..0c7bb6c7b700c 100644 --- a/components/openai/actions/submit-tool-outputs-to-run/submit-tool-outputs-to-run.mjs +++ b/components/openai/actions/submit-tool-outputs-to-run/submit-tool-outputs-to-run.mjs @@ -5,7 +5,7 @@ export default { key: "openai-submit-tool-outputs-to-run", name: "Submit Tool Outputs to Run (Assistants)", description: "Submits tool outputs to a run that requires action. [See the documentation](https://platform.openai.com/docs/api-reference/runs/submitToolOutputs)", - version: "0.0.10", + version: "0.0.11", type: "action", props: { openai, diff --git a/components/openai/actions/summarize/summarize.mjs b/components/openai/actions/summarize/summarize.mjs index 36e43c22ff4f9..82ddfc6781511 100644 --- a/components/openai/actions/summarize/summarize.mjs +++ b/components/openai/actions/summarize/summarize.mjs @@ -4,7 +4,7 @@ import constants from "../../common/constants.mjs"; export default { ...common, name: "Summarize Text", - version: "0.1.1", + version: "0.1.2", key: "openai-summarize", description: "Summarizes text using the Chat API. [See the documentation](https://platform.openai.com/docs/api-reference/chat)", type: "action", diff --git a/components/openai/actions/translate-text/translate-text.mjs b/components/openai/actions/translate-text/translate-text.mjs index 9e2758532b2ad..73e5450963a28 100644 --- a/components/openai/actions/translate-text/translate-text.mjs +++ b/components/openai/actions/translate-text/translate-text.mjs @@ -9,7 +9,7 @@ const langOptions = lang.LANGUAGES.map((l) => ({ export default { ...common, name: "Translate Text (Whisper)", - version: "0.1.1", + version: "0.1.2", key: "openai-translate-text", description: "Translate text from one language to another using the Chat API. [See the documentation](https://platform.openai.com/docs/api-reference/chat)", type: "action", diff --git a/components/openai/actions/upload-file/upload-file.mjs b/components/openai/actions/upload-file/upload-file.mjs index 5b6aa9c981cac..13fa75a99ef69 100644 --- a/components/openai/actions/upload-file/upload-file.mjs +++ b/components/openai/actions/upload-file/upload-file.mjs @@ -6,7 +6,7 @@ export default { key: "openai-upload-file", name: "Upload File", description: "Upload a file that can be used across various endpoints/features. The size of individual files can be a maximum of 512mb. [See the documentation](https://platform.openai.com/docs/api-reference/files/create)", - version: "0.0.13", + version: "0.0.14", type: "action", props: { openai, diff --git a/components/openai/openai.app.mjs b/components/openai/openai.app.mjs index afc6469debd14..c8d28bbdc225b 100644 --- a/components/openai/openai.app.mjs +++ b/components/openai/openai.app.mjs @@ -67,6 +67,58 @@ export default { }; }, }, + vectorStoreId: { + type: "string", + label: "Vector Store ID", + description: "The identifier of a vector store", + async options({ prevContext }) { + const params = prevContext?.after + ? { + after: prevContext.after, + } + : {}; + const { + data: vectorStores, last_id: after, + } = await this.listVectorStores({ + params, + }); + return { + options: vectorStores.map((vectorStore) => ({ + label: vectorStore.name || vectorStore.id, + value: vectorStore.id, + })), + context: { + after, + }, + }; + }, + }, + vectorStoreFileId: { + type: "string", + label: "Vector Store File ID", + description: "The identifier of a vector store file", + async options({ + vectorStoreId, prevContext, + }) { + const params = prevContext?.after + ? { + after: prevContext.after, + } + : {}; + const { + data: vectorStoreFiles, last_id: after, + } = await this.listVectorStoreFiles({ + vectorStoreId, + params, + }); + return { + options: vectorStoreFiles.map((vectorStoreFile) => vectorStoreFile.id), + context: { + after, + }, + }; + }, + }, name: { type: "string", label: "Name", @@ -620,6 +672,15 @@ export default { ...args, }); }, + getVectorStore({ + vectorStoreId, ...args + }) { + return this._makeRequest({ + path: `/vector_stores/${vectorStoreId}`, + headers: this._betaHeaders("v2"), + ...args, + }); + }, listVectorStores(args = {}) { return this._makeRequest({ path: "/vector_stores", @@ -627,6 +688,62 @@ export default { ...args, }); }, + createVectorStore(args = {}) { + return this._makeRequest({ + method: "POST", + path: "/vector_stores", + headers: this._betaHeaders("v2"), + ...args, + }); + }, + deleteVectorStore({ + vectorStoreId, ...args + }) { + return this._makeRequest({ + method: "DELETE", + path: `/vector_stores/${vectorStoreId}`, + headers: this._betaHeaders("v2"), + ...args, + }); + }, + getVectorStoreFile({ + vectorStoreId, vectorStoreFileId, ...args + }) { + return this._makeRequest({ + path: `/vector_stores/${vectorStoreId}/files/${vectorStoreFileId}`, + headers: this._betaHeaders("v2"), + ...args, + }); + }, + listVectorStoreFiles({ + vectorStoreId, ...args + }) { + return this._makeRequest({ + path: `/vector_stores/${vectorStoreId}/files`, + headers: this._betaHeaders("v2"), + ...args, + }); + }, + createVectorStoreFile({ + vectorStoreId, ...args + }) { + return this._makeRequest({ + method: "POST", + path: `/vector_stores/${vectorStoreId}/files`, + headers: this._betaHeaders("v2"), + ...args, + }); + }, + deleteVectorStoreFile({ + vectorStoreId, vectorStoreFileId, ...args + }) { + return this._makeRequest({ + method: "DELETE", + path: `/vector_stores/${vectorStoreId}/files/${vectorStoreFileId}`, + headers: this._betaHeaders("v2"), + ...args, + }); + }, createModeration(args = {}) { return this._makeRequest({ method: "POST", diff --git a/components/openai/package.json b/components/openai/package.json index 79c6ebea67a8f..77591f70f54b0 100644 --- a/components/openai/package.json +++ b/components/openai/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/openai", - "version": "0.6.1", + "version": "0.7.1", "description": "Pipedream OpenAI Components", "main": "openai.app.mjs", "keywords": [ diff --git a/components/openai/sources/new-batch-completed/new-batch-completed.mjs b/components/openai/sources/new-batch-completed/new-batch-completed.mjs index 66904d43aabbb..d6729d664db88 100644 --- a/components/openai/sources/new-batch-completed/new-batch-completed.mjs +++ b/components/openai/sources/new-batch-completed/new-batch-completed.mjs @@ -6,7 +6,7 @@ export default { key: "openai-new-batch-completed", name: "New Batch Completed", description: "Emit new event when a new batch is completed in OpenAI. [See the documentation](https://platform.openai.com/docs/api-reference/batch/list)", - version: "0.0.4", + version: "0.0.5", type: "source", dedupe: "unique", methods: { diff --git a/components/openai/sources/new-file-created/new-file-created.mjs b/components/openai/sources/new-file-created/new-file-created.mjs index 90263faa0bc0b..dc92e4d5d3c79 100644 --- a/components/openai/sources/new-file-created/new-file-created.mjs +++ b/components/openai/sources/new-file-created/new-file-created.mjs @@ -6,7 +6,7 @@ export default { key: "openai-new-file-created", name: "New File Created", description: "Emit new event when a new file is created in OpenAI. [See the documentation](https://platform.openai.com/docs/api-reference/files/list)", - version: "0.0.9", + version: "0.0.10", type: "source", dedupe: "unique", props: { diff --git a/components/openai/sources/new-fine-tuning-job-created/new-fine-tuning-job-created.mjs b/components/openai/sources/new-fine-tuning-job-created/new-fine-tuning-job-created.mjs index 610022d0d259e..cdf35531b4f24 100644 --- a/components/openai/sources/new-fine-tuning-job-created/new-fine-tuning-job-created.mjs +++ b/components/openai/sources/new-fine-tuning-job-created/new-fine-tuning-job-created.mjs @@ -6,7 +6,7 @@ export default { key: "openai-new-fine-tuning-job-created", name: "New Fine Tuning Job Created", description: "Emit new event when a new fine-tuning job is created in OpenAI. [See the documentation](https://platform.openai.com/docs/api-reference/fine-tuning/list)", - version: "0.0.9", + version: "0.0.10", type: "source", dedupe: "unique", methods: { diff --git a/components/openai/sources/new-run-state-changed/new-run-state-changed.mjs b/components/openai/sources/new-run-state-changed/new-run-state-changed.mjs index 7536c757cdb4f..31ec1fe5265e2 100644 --- a/components/openai/sources/new-run-state-changed/new-run-state-changed.mjs +++ b/components/openai/sources/new-run-state-changed/new-run-state-changed.mjs @@ -6,7 +6,7 @@ export default { key: "openai-new-run-state-changed", name: "New Run State Changed", description: "Emit new event every time a run changes its status. [See the documentation](https://platform.openai.com/docs/api-reference/runs/listRuns)", - version: "0.0.5", + version: "0.0.6", type: "source", dedupe: "unique", props: { diff --git a/components/openphone/actions/create-contact/create-contact.mjs b/components/openphone/actions/create-contact/create-contact.mjs new file mode 100644 index 0000000000000..be3f6626c2ad4 --- /dev/null +++ b/components/openphone/actions/create-contact/create-contact.mjs @@ -0,0 +1,80 @@ +import { parseObject } from "../../common/utils.mjs"; +import openphone from "../../openphone.app.mjs"; + +export default { + key: "openphone-create-contact", + name: "Create Contact", + description: "Create a new contact in OpenPhone. [See the documentation](https://www.openphone.com/docs/api-reference/contacts/create-a-contact)", + version: "0.0.1", + type: "action", + props: { + openphone, + firstName: { + propDefinition: [ + openphone, + "firstName", + ], + }, + lastName: { + propDefinition: [ + openphone, + "lastName", + ], + optional: true, + }, + company: { + propDefinition: [ + openphone, + "company", + ], + optional: true, + }, + role: { + propDefinition: [ + openphone, + "role", + ], + optional: true, + }, + emails: { + propDefinition: [ + openphone, + "emails", + ], + optional: true, + }, + phoneNumbers: { + propDefinition: [ + openphone, + "phoneNumbers", + ], + optional: true, + }, + customFields: { + propDefinition: [ + openphone, + "customFields", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.openphone.createContact({ + $, + data: { + defaultFields: { + firstName: this.firstName, + lastName: this.lastName, + company: this.company, + role: this.role, + emails: parseObject(this.emails), + phoneNumbers: parseObject(this.phoneNumbers), + }, + customFields: parseObject(this.customFields), + }, + }); + + $.export("$summary", `Successfully created contact with ID: ${response.data.id}`); + return response; + }, +}; diff --git a/components/openphone/actions/send-message/send-message.mjs b/components/openphone/actions/send-message/send-message.mjs new file mode 100644 index 0000000000000..c44ccd8ea68ae --- /dev/null +++ b/components/openphone/actions/send-message/send-message.mjs @@ -0,0 +1,57 @@ +import { ConfigurationError } from "@pipedream/platform"; +import openphone from "../../openphone.app.mjs"; + +export default { + key: "openphone-send-message", + name: "Send a Text Message via OpenPhone", + description: "Send a text message from your OpenPhone number to a recipient. [See the documentation](https://www.openphone.com/docs/api-reference/messages/send-a-text-message)", + version: "0.0.1", + type: "action", + props: { + openphone, + from: { + propDefinition: [ + openphone, + "from", + ], + }, + to: { + type: "string", + label: "To", + description: "Recipient phone number in E.164 format.", + }, + content: { + type: "string", + label: "Content", + description: "The text content of the message to be sent.", + }, + }, + async run({ $ }) { + try { + const response = await this.openphone.sendTextMessage({ + $, + data: { + content: this.content, + from: this.from, + to: [ + this.to, + ], + setInboxStatus: "done", + }, + }); + $.export("$summary", `Successfully sent message to ${this.to}`); + return response; + + } catch ({ response }) { + let errorMessage = ""; + + if (response.data.errors) { + errorMessage = `Prop: ${response.data.errors[0].path} - ${response.data.errors[0].message}`; + } else { + errorMessage = response.data.message; + } + + throw new ConfigurationError(errorMessage); + } + }, +}; diff --git a/components/openphone/actions/update-contact/update-contact.mjs b/components/openphone/actions/update-contact/update-contact.mjs new file mode 100644 index 0000000000000..d685eb793b0d9 --- /dev/null +++ b/components/openphone/actions/update-contact/update-contact.mjs @@ -0,0 +1,87 @@ +import { parseObject } from "../../common/utils.mjs"; +import openphone from "../../openphone.app.mjs"; + +export default { + key: "openphone-update-contact", + name: "Update Contact", + description: "Update an existing contact on OpenPhone. [See the documentation](https://www.openphone.com/docs/api-reference/contacts/update-a-contact-by-id)", + version: "0.0.1", + type: "action", + props: { + openphone, + contactId: { + type: "string", + label: "Contact ID", + description: "The unique identifier of the contact.", + }, + firstName: { + propDefinition: [ + openphone, + "firstName", + ], + optional: true, + }, + lastName: { + propDefinition: [ + openphone, + "lastName", + ], + optional: true, + }, + company: { + propDefinition: [ + openphone, + "company", + ], + optional: true, + }, + role: { + propDefinition: [ + openphone, + "role", + ], + optional: true, + }, + emails: { + propDefinition: [ + openphone, + "emails", + ], + optional: true, + }, + phoneNumbers: { + propDefinition: [ + openphone, + "phoneNumbers", + ], + optional: true, + }, + customFields: { + propDefinition: [ + openphone, + "customFields", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.openphone.updateContact({ + $, + contactId: this.contactId, + data: { + defaultFields: { + firstName: this.firstName, + lastName: this.lastName, + company: this.company, + role: this.role, + emails: parseObject(this.emails), + phoneNumbers: parseObject(this.phoneNumbers), + }, + customFields: parseObject(this.customFields), + }, + }); + + $.export("$summary", `Successfully updated contact with ID ${this.contactId}`); + return response; + }, +}; diff --git a/components/openphone/common/utils.mjs b/components/openphone/common/utils.mjs new file mode 100644 index 0000000000000..e7c267cec0d5e --- /dev/null +++ b/components/openphone/common/utils.mjs @@ -0,0 +1,26 @@ +export const parseObject = (obj) => { + if (!obj) return undefined; + + let parsedObj = obj; + if (typeof obj === "string") { + try { + parsedObj = JSON.parse(obj); + } catch (e) { + return obj; + } + } + + if (Array.isArray(parsedObj)) { + return parsedObj.map((item) => parseObject(item)); + } + if (typeof parsedObj === "object") { + for (const [ + key, + value, + ] of Object.entries(parsedObj)) { + parsedObj[key] = parseObject(value); + } + } + + return parsedObj; +}; diff --git a/components/openphone/openphone.app.mjs b/components/openphone/openphone.app.mjs new file mode 100644 index 0000000000000..7d126dd084736 --- /dev/null +++ b/components/openphone/openphone.app.mjs @@ -0,0 +1,121 @@ +import { axios } from "@pipedream/platform"; + +export default { + type: "app", + app: "openphone", + propDefinitions: { + from: { + type: "string", + label: "From", + description: "The sender's phone number. Can be either your OpenPhone phone number ID or the full phone number in E.164 format.", + async options() { + const { data } = await this.listPhoneNumbers(); + return data.map(({ + id: value, name, formattedNumber, + }) => ({ + label: `${name} - ${formattedNumber}`, + value, + })); + }, + }, + firstName: { + type: "string", + label: "First Name", + description: "The contact's first name.", + }, + lastName: { + type: "string", + label: "Last Name", + description: "The contact's last name.", + optional: true, + }, + company: { + type: "string", + label: "Company", + description: "The contact's company name.", + optional: true, + }, + role: { + type: "string", + label: "Role", + description: "The contact's role.", + optional: true, + }, + emails: { + type: "string[]", + label: "Emails", + description: "Array of objects of contact's emails. **Example:** `{\"name\": \"Company Email\", \"value\": \"abc@example.com\"}`.", + }, + phoneNumbers: { + type: "string[]", + label: "Phone Numbers", + description: "Array of objects of contact's phone numbers. **Example:** `{\"name\": \"Company Phone\", \"value\": \"+12345678901\"}`.", + }, + customFields: { + type: "string[]", + label: "Custom Fields", + description: "Array of objects of custom fields for the contact. **Example:** `{\"key\": \"inbound-lead\", \"value\": \"[\"option1\", \"option2\"]\"}`.", + }, + }, + methods: { + _baseUrl() { + return "https://api.openphone.com/v1"; + }, + _headers() { + return { + Authorization: `${this.$auth.api_key}`, + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + headers: this._headers(), + ...opts, + }); + }, + listPhoneNumbers(opts = {}) { + return this._makeRequest({ + path: "/phone-numbers", + ...opts, + }); + }, + createWebhook(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/webhooks/calls", + ...opts, + }); + }, + deleteWebhook(webhookId) { + return this._makeRequest({ + method: "DELETE", + path: `/webhooks/${webhookId}`, + }); + }, + sendTextMessage(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/messages", + ...opts, + }); + }, + createContact(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/contacts", + ...opts, + }); + }, + updateContact({ + contactId, ...opts + }) { + return this._makeRequest({ + method: "PATCH", + path: `/contacts/${contactId}`, + ...opts, + }); + }, + }, +}; diff --git a/components/openphone/package.json b/components/openphone/package.json new file mode 100644 index 0000000000000..e7fae1c54b357 --- /dev/null +++ b/components/openphone/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/openphone", + "version": "0.1.0", + "description": "Pipedream OpenPhone Components", + "main": "openphone.app.mjs", + "keywords": [ + "pipedream", + "openphone" + ], + "homepage": "https://pipedream.com/apps/openphone", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" + } +} diff --git a/components/openphone/sources/common/base.mjs b/components/openphone/sources/common/base.mjs new file mode 100644 index 0000000000000..75921c5b6df7d --- /dev/null +++ b/components/openphone/sources/common/base.mjs @@ -0,0 +1,62 @@ +import openphone from "../../openphone.app.mjs"; + +export default { + props: { + openphone, + http: "$.interface.http", + db: "$.service.db", + resourceIds: { + propDefinition: [ + openphone, + "from", + ], + type: "string[]", + label: "Resource IDs", + description: "The unique identifiers of phone numbers associated with the webhook.", + optional: true, + }, + label: { + type: "string", + label: "Label", + description: "Webhook's label", + optional: true, + }, + }, + methods: { + _getHookId() { + return this.db.get("hookId"); + }, + _setHookId(hookId) { + this.db.set("hookId", hookId); + }, + getEventFilter() { + return true; + }, + }, + hooks: { + async activate() { + const response = await this.openphone.createWebhook({ + data: { + url: this.http.endpoint, + events: this.getEvent(), + resourceIds: this.resourceIds, + label: this.label, + }, + }); + this._setHookId(response.data.id); + }, + async deactivate() { + const webhookId = this._getHookId(); + await this.openphone.deleteWebhook(webhookId); + }, + }, + async run({ body }) { + if (this.getEventFilter(body)) { + this.$emit(body, { + id: body.id, + summary: this.getSummary(body), + ts: Date.parse(body.data.object.completedAt), + }); + } + }, +}; diff --git a/components/openphone/sources/new-call-recording-completed-instant/new-call-recording-completed-instant.mjs b/components/openphone/sources/new-call-recording-completed-instant/new-call-recording-completed-instant.mjs new file mode 100644 index 0000000000000..9a0098f0fbe39 --- /dev/null +++ b/components/openphone/sources/new-call-recording-completed-instant/new-call-recording-completed-instant.mjs @@ -0,0 +1,24 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "openphone-new-call-recording-completed-instant", + name: "New Call Recording Completed", + description: "Emit new event when a call recording has finished.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEvent() { + return [ + "call.recording.completed", + ]; + }, + getEmit(body) { + return `New call recording completed for call ID: ${body.data.object.id}`; + }, + }, + sampleEmit, +}; diff --git a/components/openphone/sources/new-call-recording-completed-instant/test-event.mjs b/components/openphone/sources/new-call-recording-completed-instant/test-event.mjs new file mode 100644 index 0000000000000..bd109bda4fb0f --- /dev/null +++ b/components/openphone/sources/new-call-recording-completed-instant/test-event.mjs @@ -0,0 +1,30 @@ +export default { + "apiVersion": "v3", + "createdAt": "2022-01-24T19:30:55.400Z", + "data": { + "object": { + "answeredAt": "2022-01-24T19:30:38.000Z", + "completedAt": "2022-01-24T19:30:48.000Z", + "conversationId": "CN78ba0373683c48fd8fd96bc836c51f79", + "createdAt": "2022-01-24T19:30:34.675Z", + "direction": "incoming", + "from": "+18005550100", + "media": [ + { + "duration":7, + "type": "audio/mpeg", + "url": "https://storage.googleapis.com/opstatics-dev/a5f839bc72a24b33a7fc032f78777146.mp3" + } + ], + "object": "call", + "phoneNumberId": "PNtoDbDhuz", + "status": "completed", + "to": "+18885550101", + "userId": "USu5AsEHuQ", + "voicemail":null + } + }, + "id": "EVda6e196255814311aaac1983005fa2d9", + "object": "event", + "type": "call.recording.completed" +} \ No newline at end of file diff --git a/components/openphone/sources/new-incoming-call-completed-instant/new-incoming-call-completed-instant.mjs b/components/openphone/sources/new-incoming-call-completed-instant/new-incoming-call-completed-instant.mjs new file mode 100644 index 0000000000000..dade14952b2a6 --- /dev/null +++ b/components/openphone/sources/new-incoming-call-completed-instant/new-incoming-call-completed-instant.mjs @@ -0,0 +1,27 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "openphone-new-incoming-call-completed-instant", + name: "New Incoming Call Completed (Instant)", + description: "Emit new event when an incoming call is completed, including calls not picked up or voicemails left.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEvent() { + return [ + "call.completed", + ]; + }, + getSummary() { + return "New Incoming Call Completed"; + }, + getEventFilter(body) { + return body.data.object.direction === "incoming"; + }, + }, + sampleEmit, +}; diff --git a/components/openphone/sources/new-incoming-call-completed-instant/test-event.mjs b/components/openphone/sources/new-incoming-call-completed-instant/test-event.mjs new file mode 100644 index 0000000000000..350b4db7cad36 --- /dev/null +++ b/components/openphone/sources/new-incoming-call-completed-instant/test-event.mjs @@ -0,0 +1,28 @@ +export default { + "apiVersion": "v3", + "createdAt": "2022-01-24T19:22:25.427Z", + "data": { + "object": { + "answeredAt": null, + "completedAt": "2022-01-24T19:22:19.000Z", + "conversationId": "CN78ba0373683c48fd8fd96bc836c51f79", + "createdAt": "2022-01-24T19:21:59.545Z", + "direction": "incoming", + "from": "+18005550100", + "media": [], + "object": "call", + "phoneNumberId": "PNtoDbDhuz", + "status": "completed", + "to": "+18885550101", + "userId": "USu5AsEHuQ", + "voicemail": { + "duration": 7, + "type": "audio/mpeg", + "url": "https://m.openph.one/static/15ad4740be6048e4a80efb268d347482.mp3" + } + } + }, + "id": "EVd39d3c8d6f244d21a9131de4fc9350d0", + "object": "event", + "type": "call.completed" +} \ No newline at end of file diff --git a/components/openphone/sources/new-outgoing-call-completed-instant/new-outgoing-call-completed-instant.mjs b/components/openphone/sources/new-outgoing-call-completed-instant/new-outgoing-call-completed-instant.mjs new file mode 100644 index 0000000000000..cdd01cfa2a1cd --- /dev/null +++ b/components/openphone/sources/new-outgoing-call-completed-instant/new-outgoing-call-completed-instant.mjs @@ -0,0 +1,27 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "openphone-new-outgoing-call-completed-instant", + name: "New Outgoing Call Completed (Instant)", + description: "Emit new event when an outgoing call has ended.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEvent() { + return [ + "call.completed", + ]; + }, + getSummary() { + return "New Outgoing Call Completed"; + }, + getEventFilter(body) { + return body.data.object.direction === "outgoing"; + }, + }, + sampleEmit, +}; diff --git a/components/openphone/sources/new-outgoing-call-completed-instant/test-event.mjs b/components/openphone/sources/new-outgoing-call-completed-instant/test-event.mjs new file mode 100644 index 0000000000000..3451381272c21 --- /dev/null +++ b/components/openphone/sources/new-outgoing-call-completed-instant/test-event.mjs @@ -0,0 +1,28 @@ +export default { + "apiVersion": "v3", + "createdAt": "2022-01-24T19:22:25.427Z", + "data": { + "object": { + "answeredAt": null, + "completedAt": "2022-01-24T19:22:19.000Z", + "conversationId": "CN78ba0373683c48fd8fd96bc836c51f79", + "createdAt": "2022-01-24T19:21:59.545Z", + "direction": "outgoing", + "from": "+18005550100", + "media": [], + "object": "call", + "phoneNumberId": "PNtoDbDhuz", + "status": "completed", + "to": "+18885550101", + "userId": "USu5AsEHuQ", + "voicemail": { + "duration": 7, + "type": "audio/mpeg", + "url": "https://m.openph.one/static/15ad4740be6048e4a80efb268d347482.mp3" + } + } + }, + "id": "EVd39d3c8d6f244d21a9131de4fc9350d0", + "object": "event", + "type": "call.completed" +} \ No newline at end of file diff --git a/components/papersign/actions/copy-document/copy-document.mjs b/components/papersign/actions/copy-document/copy-document.mjs new file mode 100644 index 0000000000000..19202f76c34a5 --- /dev/null +++ b/components/papersign/actions/copy-document/copy-document.mjs @@ -0,0 +1,65 @@ +import papersign from "../../papersign.app.mjs"; + +export default { + key: "papersign-copy-document", + name: "Copy Document", + description: "Duplicates a given document. [See the documentation](https://paperform.readme.io/reference/papersigncopydocument)", + version: "0.0.1", + type: "action", + props: { + papersign, + documentId: { + propDefinition: [ + papersign, + "documentId", + ], + }, + name: { + type: "string", + label: "Name", + description: "The new name of the copied document", + optional: true, + }, + spaceId: { + propDefinition: [ + papersign, + "spaceId", + ], + optional: true, + }, + path: { + type: "string", + label: "Path", + description: "The path to copy the document to. Maximum depth is 4 levels. Any missing folders will be created.", + optional: true, + }, + folderId: { + propDefinition: [ + papersign, + "folderId", + ], + optional: true, + }, + }, + async run({ $ }) { + const data = {}; + if (this.folderId) { + data.folder_id = this.folderId; + } else { + data.space_id = this.spaceId; + data.path = this.path; + } + + const response = await this.papersign.duplicateDocument({ + $, + documentId: this.documentId, + data: { + ...data, + name: this.name, + }, + }); + + $.export("$summary", `Successfully copied document: ${response.results.document.name}`); + return response; + }, +}; diff --git a/components/papersign/actions/get-document/get-document.mjs b/components/papersign/actions/get-document/get-document.mjs new file mode 100644 index 0000000000000..d566fdd390518 --- /dev/null +++ b/components/papersign/actions/get-document/get-document.mjs @@ -0,0 +1,27 @@ +import papersign from "../../papersign.app.mjs"; + +export default { + key: "papersign-get-document", + name: "Get Document", + description: "Retrieve a document using a specified ID. [See the documentation](https://paperform.readme.io/reference/getpapersigndocument)", + version: "0.0.1", + type: "action", + props: { + papersign, + documentId: { + propDefinition: [ + papersign, + "documentId", + ], + }, + }, + async run({ $ }) { + const response = await this.papersign.getDocument({ + $, + documentId: this.documentId, + }); + + $.export("$summary", `Successfully retrieved document with ID: ${this.documentId}`); + return response; + }, +}; diff --git a/components/papersign/actions/send-document/send-document.mjs b/components/papersign/actions/send-document/send-document.mjs new file mode 100644 index 0000000000000..1847319e6a907 --- /dev/null +++ b/components/papersign/actions/send-document/send-document.mjs @@ -0,0 +1,115 @@ +import { ConfigurationError } from "@pipedream/platform"; +import { parseObject } from "../../common/utils.mjs"; +import papersign from "../../papersign.app.mjs"; + +export default { + key: "papersign-send-document", + name: "Send Document", + description: "Dispatches a document to a specified recipient. [See the documentation](https://paperform.readme.io/reference/papersignsenddocument)", + version: "0.0.1", + type: "action", + props: { + papersign, + documentId: { + propDefinition: [ + papersign, + "documentId", + ], + }, + expiration: { + type: "string", + label: "Expiration", + description: "The expiration date of the document. Must be at least 30 minutes in the future. **Format: YYYY-MM-DDTHH:MM:SS.SSSZ**", + optional: true, + }, + inviteMessage: { + type: "string", + label: "Invite Message", + description: "The message to include in the invitation email, up to 1000 characters.", + optional: true, + }, + fromUserEmail: { + type: "string", + label: "From User Email", + description: "The email address of a User on your team's account to send the document from.", + optional: true, + }, + documentRecipientEmails: { + type: "string[]", + label: "Document Recipient Emails", + description: "An array of recipient emails for the document.", + optional: true, + }, + firstAfterDays: { + type: "integer", + label: "Automatic Reminder - First After Days", + description: "The number of days after the document is sent to send the reminder.", + optional: true, + }, + followUpEveryDays: { + type: "integer", + label: "Automatic Reminder - Follow Up Every Days", + description: "The number of days to wait between reminders.", + optional: true, + }, + signers: { + type: "string[]", + label: "Signers", + description: "An array of objects of signers. **Object format: {\"key\": \"123\",\"name\": \"Jack Smith\",\"email\": \"signer@example.com\",\"phone\": \"123 456 7899\",\"job_title\": \"Account Manager\",\"company\": \"Explosive Startup\",\"custom_attributes\": [{\"key\": \"Relationship\",\"label\": \"Relationship to the company\",\"value\": \"CEO\"}]}**", + optional: true, + }, + variables: { + type: "object", + label: "Variables", + description: "The key: value of the document variables.", + optional: true, + }, + copy: { + type: "boolean", + label: "Copy", + description: "Whether to copy before sending.", + optional: true, + }, + }, + async run({ $ }) { + if ( + (this.firstAfterDays && !this.followUpEveryDays) || + (!this.firstAfterDays && this.followUpEveryDays) + ) { + throw new ConfigurationError("You must fill in the fields 'First After Days' and 'Follow Up Every Days' or none of them"); + } + + const automaticReminders = {}; + if (this.firstAfterDays) { + automaticReminders.first_after_days = this.firstAfterDays; + automaticReminders.follow_up_every_days = this.followUpEveryDays; + } + + const variables = []; + if (this.variables) { + for (const key of Object.keys(parseObject(this.variables))) { + variables.push({ + key, + value: this.variables[key], + }); + } + } + + const response = await this.papersign.sendDocument({ + $, + documentId: this.documentId, + data: { + expiration: this.expiration, + inviteMessage: this.inviteMessage, + fromUserEmail: this.fromUserEmail, + documentRecipientEmails: parseObject(this.documentRecipientEmails), + automaticReminders, + signers: parseObject(this.signers), + variables, + copy: this.copy, + }, + }); + $.export("$summary", `Document sent successfully with ID ${this.documentId}`); + return response; + }, +}; diff --git a/components/papersign/common/constants.mjs b/components/papersign/common/constants.mjs new file mode 100644 index 0000000000000..28a6656d208ad --- /dev/null +++ b/components/papersign/common/constants.mjs @@ -0,0 +1,12 @@ +export const LIMIT = 100; + +export const SCOPE_OPTIONS = [ + { + label: "Direct Children", + value: "folder.direct_children", + }, + { + label: "All Descendants", + value: "folder.all_descendants", + }, +]; diff --git a/components/papersign/common/utils.mjs b/components/papersign/common/utils.mjs new file mode 100644 index 0000000000000..9050833bcda0b --- /dev/null +++ b/components/papersign/common/utils.mjs @@ -0,0 +1,32 @@ +export const parseObject = (obj) => { + if (!obj) return undefined; + + if (Array.isArray(obj)) { + return obj.map((item) => { + if (typeof item === "string") { + try { + return JSON.parse(item); + } catch (e) { + return parseObject(item); + } + } + return item; + }); + } + if (typeof obj === "string") { + try { + return JSON.parse(obj); + } catch (e) { + return obj; + } + } + if (typeof obj === "object") { + for (const [ + key, + value, + ] of Object.entries(obj)) { + obj[key] = parseObject(value); + } + } + return obj; +}; diff --git a/components/papersign/package.json b/components/papersign/package.json index 8e3df417d8ff5..656e518ce4d24 100644 --- a/components/papersign/package.json +++ b/components/papersign/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/papersign", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Papersign Components", "main": "papersign.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } -} \ No newline at end of file +} diff --git a/components/papersign/papersign.app.mjs b/components/papersign/papersign.app.mjs index d51851226db7e..2f3e59c9ec6f2 100644 --- a/components/papersign/papersign.app.mjs +++ b/components/papersign/papersign.app.mjs @@ -1,11 +1,122 @@ +import { axios } from "@pipedream/platform"; +import { LIMIT } from "./common/constants.mjs"; + export default { type: "app", app: "papersign", - propDefinitions: {}, + propDefinitions: { + documentId: { + type: "string", + label: "Document ID", + description: "Enter the ID of the Papersign document", + async options({ page }) { + return await this.list({ + module: "documents", + page, + }); + }, + }, + spaceId: { + type: "string", + label: "Space ID", + description: "Enter the ID of the Papersign space", + async options({ page }) { + return await this.list({ + module: "spaces", + page, + }); + }, + }, + folderId: { + type: "string", + label: "Folder ID", + description: "Enter the ID of the Papersign folder. `If folder id is present, Space Id and Path will be ignored`.", + async options({ page }) { + return await this.list({ + module: "folders", + page, + }); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.paperform.co/v1/papersign"; + }, + _headers() { + return { + "Authorization": `Bearer ${this.$auth.access_token}`, + "accept": "application/json", + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + headers: this._headers(), + ...opts, + }); + }, + async list({ + module, page, ...opts + }) { + const { results } = await this._makeRequest({ + path: `/${module}`, + params: { + limit: LIMIT, + skip: LIMIT * page, + }, + ...opts, + }); + + return results[module].map(({ + id: value, name: label, + }) => ({ + value, + label, + })); + }, + duplicateDocument({ + documentId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/documents/${documentId}/copy`, + ...opts, + }); + }, + getDocument({ + documentId, ...opts + }) { + return this._makeRequest({ + path: `/documents/${documentId}`, + ...opts, + }); + }, + sendDocument({ + documentId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/documents/${documentId}/send`, + ...opts, + }); + }, + createWebhook({ + folderId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/folders/${folderId}/webhooks`, + ...opts, + }); + }, + deleteWebhook(hookId) { + return this._makeRequest({ + method: "DELETE", + path: `/webhooks/${hookId}`, + }); }, }, -}; \ No newline at end of file +}; diff --git a/components/papersign/sources/common/base.mjs b/components/papersign/sources/common/base.mjs new file mode 100644 index 0000000000000..3fcd824eb6ed5 --- /dev/null +++ b/components/papersign/sources/common/base.mjs @@ -0,0 +1,64 @@ +import { SCOPE_OPTIONS } from "../../common/constants.mjs"; +import papersign from "../../papersign.app.mjs"; + +export default { + props: { + papersign, + http: { + type: "$.interface.http", + customResponse: false, + }, + db: "$.service.db", + folderId: { + propDefinition: [ + papersign, + "folderId", + ], + }, + name: { + type: "string", + label: "Name", + description: "The name of the webhook.", + optional: true, + }, + scope: { + type: "string", + label: "Scope", + description: "The scope of the webhook", + options: SCOPE_OPTIONS, + }, + }, + methods: { + _setHookId(hookId) { + this.db.set("hookId", hookId); + }, + _getHookId() { + return this.db.get("hookId"); + }, + }, + hooks: { + async activate() { + const { results: { webhook } } = await this.papersign.createWebhook({ + folderId: this.folderId, + data: { + name: this.name, + target_url: this.http.endpoint, + scope: this.scope, + triggers: this.getTriggers(), + }, + }); + this._setHookId(webhook.id); + }, + async deactivate() { + const webhookId = this._getHookId(); + await this.papersign.deleteWebhook(webhookId); + }, + }, + async run({ body }) { + this.$emit(body, { + id: body.id, + summary: this.getSummary(body), + ts: Date.parse(body.created_at), + }); + }, +}; diff --git a/components/papersign/sources/new-document-completed-instant/new-document-completed-instant.mjs b/components/papersign/sources/new-document-completed-instant/new-document-completed-instant.mjs new file mode 100644 index 0000000000000..40175d61ff4f1 --- /dev/null +++ b/components/papersign/sources/new-document-completed-instant/new-document-completed-instant.mjs @@ -0,0 +1,24 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "papersign-new-document-completed-instant", + name: "New Document Completed (Instant)", + description: "Emit new event when a document is completed, i.e., all signers have signed.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getTriggers() { + return [ + "document.completed", + ]; + }, + getSummary(body) { + return `Document '${body.document_name}' completed`; + }, + }, + sampleEmit, +}; diff --git a/components/papersign/sources/new-document-completed-instant/test-event.mjs b/components/papersign/sources/new-document-completed-instant/test-event.mjs new file mode 100644 index 0000000000000..568316a81e779 --- /dev/null +++ b/components/papersign/sources/new-document-completed-instant/test-event.mjs @@ -0,0 +1,10 @@ +export default { + "id": "671ba9030af55bff3b7a8c00", + "document_id": "671ba9030af55bff3b7a8c00", + "document_name": "Document Name", + "type": "document.completed", + "data": { + "document_url": "https://sign.papersign.com/sign/671ba9030af55bff3b7a8c00/completed-document?expires=1737659429&signature=671ba9030af55bff3b7a8c00671ba9030af55bff3b7a8c00" + }, + "created_at": "2024-10-25T19:10:29+00:00" +} \ No newline at end of file diff --git a/components/papersign/sources/new-event-instant/new-event-instant.mjs b/components/papersign/sources/new-event-instant/new-event-instant.mjs new file mode 100644 index 0000000000000..229f94dbabcf7 --- /dev/null +++ b/components/papersign/sources/new-event-instant/new-event-instant.mjs @@ -0,0 +1,33 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "papersign-new-event-instant", + name: "New Event in Papersign (Instant)", + description: "Emit new event when any document or signer action occurs.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getTriggers() { + return [ + "document.sent", + "document.completed", + "document.cancelled", + "document.rejected", + "document.expired", + "signer.notified", + "signer.viewed", + "signer.consent_accepted", + "signer.nominated", + "signer.signed", + ]; + }, + getSummary({ data }) { + return `New event: ${data.type}`; + }, + }, + sampleEmit, +}; diff --git a/components/papersign/sources/new-event-instant/test-event.mjs b/components/papersign/sources/new-event-instant/test-event.mjs new file mode 100644 index 0000000000000..568316a81e779 --- /dev/null +++ b/components/papersign/sources/new-event-instant/test-event.mjs @@ -0,0 +1,10 @@ +export default { + "id": "671ba9030af55bff3b7a8c00", + "document_id": "671ba9030af55bff3b7a8c00", + "document_name": "Document Name", + "type": "document.completed", + "data": { + "document_url": "https://sign.papersign.com/sign/671ba9030af55bff3b7a8c00/completed-document?expires=1737659429&signature=671ba9030af55bff3b7a8c00671ba9030af55bff3b7a8c00" + }, + "created_at": "2024-10-25T19:10:29+00:00" +} \ No newline at end of file diff --git a/components/papersign/sources/new-signer-signed-instant/new-signer-signed-instant.mjs b/components/papersign/sources/new-signer-signed-instant/new-signer-signed-instant.mjs new file mode 100644 index 0000000000000..16b08fa730384 --- /dev/null +++ b/components/papersign/sources/new-signer-signed-instant/new-signer-signed-instant.mjs @@ -0,0 +1,24 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "papersign-new-signer-signed-instant", + name: "New Signer Signed (Instant)", + description: "Emit new event when a signer signs a document.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getTriggers() { + return [ + "signer.signed", + ]; + }, + getSummary({ data }) { + return `Document signed by signer ${data.by.name}`; + }, + }, + sampleEmit, +}; diff --git a/components/papersign/sources/new-signer-signed-instant/test-event.mjs b/components/papersign/sources/new-signer-signed-instant/test-event.mjs new file mode 100644 index 0000000000000..912a48198cf59 --- /dev/null +++ b/components/papersign/sources/new-signer-signed-instant/test-event.mjs @@ -0,0 +1,7 @@ +export default { + "project": "project_slug", + "translated": 100, + "resource": "resource_slug", + "event": "translation_completed", + "language": "lang_code" +} \ No newline at end of file diff --git a/components/parallel/parallel.app.mjs b/components/parallel/parallel.app.mjs index bb7d6d051c17e..ddcc94b46e628 100644 --- a/components/parallel/parallel.app.mjs +++ b/components/parallel/parallel.app.mjs @@ -8,4 +8,4 @@ export default { console.log(Object.keys(this.$auth)); }, }, -}; \ No newline at end of file +}; diff --git a/components/quickbooks/actions/create-ap-aging-report/create-ap-aging-report.mjs b/components/quickbooks/actions/create-ap-aging-report/create-ap-aging-report.mjs new file mode 100644 index 0000000000000..a93338dc8b751 --- /dev/null +++ b/components/quickbooks/actions/create-ap-aging-report/create-ap-aging-report.mjs @@ -0,0 +1,102 @@ +import quickbooks from "../../quickbooks.app.mjs"; +import { AP_AGING_REPORT_COLUMNS } from "../../common/constants.mjs"; + +export default { + key: "quickbooks-create-ap-aging-report", + name: "Create AP Aging Detail Report", + description: "Creates an AP aging report in Quickbooks Online. [See the documentation](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/apagingdetail#query-a-report)", + version: "0.0.1", + type: "action", + props: { + quickbooks, + shipvia: { + type: "string", + label: "Ship Via", + description: "Filter by the shipping method", + optional: true, + }, + termIds: { + propDefinition: [ + quickbooks, + "termIds", + ], + }, + startDueDate: { + type: "string", + label: "Start Due Date", + description: "The range of dates over which receivables are due, in the format `YYYY-MM-DD`. `start_duedate` must be less than `end_duedate`. If not specified, all data is returned.", + optional: true, + }, + endDueDate: { + type: "string", + label: "End Due Date", + description: "The range of dates over which receivables are due, in the format `YYYY-MM-DD`. `start_duedate` must be less than `end_duedate`. If not specified, all data is returned.", + optional: true, + }, + accountingMethod: { + propDefinition: [ + quickbooks, + "accountingMethod", + ], + }, + reportDate: { + type: "string", + label: "Report Date", + description: "Start date to use for the report, in the format `YYYY-MM-DD`", + optional: true, + }, + numPeriods: { + type: "integer", + label: "Num Periods", + description: "The number of periods to be shown in the report", + optional: true, + }, + vendorIds: { + propDefinition: [ + quickbooks, + "vendorIds", + ], + }, + pastDue: { + type: "integer", + label: "Past Due", + description: "Filters report contents based on minimum days past due", + optional: true, + }, + agingPeriod: { + type: "integer", + label: "Aging Period", + description: "The number of days in the aging period", + optional: true, + }, + columns: { + propDefinition: [ + quickbooks, + "columns", + ], + options: AP_AGING_REPORT_COLUMNS, + }, + }, + async run({ $ }) { + const response = await this.quickbooks.getApAgingReport({ + $, + params: { + shipvia: this.shipvia, + term: this.termIds, + start_duedate: this.startDueDate, + end_duedate: this.endDueDate, + accounting_method: this.accountingMethod, + report_date: this.reportDate, + num_periods: this.numPeriods, + vendor: this.vendorIds, + past_due: this.pastDue, + aging_period: this.agingPeriod, + columns: this.columns, + }, + }); + if (response) { + $.export("summary", "Successfully created AP Aging Detail Report"); + } + return response; + }, +}; diff --git a/components/quickbooks/actions/create-bill/create-bill.mjs b/components/quickbooks/actions/create-bill/create-bill.mjs index 38a800d3ab5ec..bb2bb2fed3ae2 100644 --- a/components/quickbooks/actions/create-bill/create-bill.mjs +++ b/components/quickbooks/actions/create-bill/create-bill.mjs @@ -5,7 +5,7 @@ export default { key: "quickbooks-create-bill", name: "Create Bill", description: "Creates a bill. [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/bill#create-a-bill)", - version: "0.1.5", + version: "0.1.6", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/create-customer/create-customer.mjs b/components/quickbooks/actions/create-customer/create-customer.mjs index df11cc7297c22..a5d419c22de74 100644 --- a/components/quickbooks/actions/create-customer/create-customer.mjs +++ b/components/quickbooks/actions/create-customer/create-customer.mjs @@ -5,7 +5,7 @@ export default { key: "quickbooks-create-customer", name: "Create Customer", description: "Creates a customer. [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/customer#create-a-customer)", - version: "0.1.5", + version: "0.1.6", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/create-invoice/create-invoice.mjs b/components/quickbooks/actions/create-invoice/create-invoice.mjs index 408f0706437e7..408be0eeacc87 100644 --- a/components/quickbooks/actions/create-invoice/create-invoice.mjs +++ b/components/quickbooks/actions/create-invoice/create-invoice.mjs @@ -5,7 +5,7 @@ export default { key: "quickbooks-create-invoice", name: "Create Invoice", description: "Creates an invoice. [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/invoice#create-an-invoice)", - version: "0.1.5", + version: "0.1.6", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/create-payment/create-payment.mjs b/components/quickbooks/actions/create-payment/create-payment.mjs index ffd877c3221af..01758a39a6827 100644 --- a/components/quickbooks/actions/create-payment/create-payment.mjs +++ b/components/quickbooks/actions/create-payment/create-payment.mjs @@ -4,7 +4,7 @@ export default { key: "quickbooks-create-payment", name: "Create Payment", description: "Creates a payment. [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/payment#create-a-payment)", - version: "0.0.4", + version: "0.0.5", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/create-pl-report/create-pl-report.mjs b/components/quickbooks/actions/create-pl-report/create-pl-report.mjs new file mode 100644 index 0000000000000..54cf14deec88d --- /dev/null +++ b/components/quickbooks/actions/create-pl-report/create-pl-report.mjs @@ -0,0 +1,144 @@ +import quickbooks from "../../quickbooks.app.mjs"; +import { + DATE_MACRO_OPTIONS, PAYMENT_METHOD_OPTIONS, ACCOUNT_TYPE_OPTIONS, PROFIT_LOSS_REPORT_COLUMNS, +} from "../../common/constants.mjs"; + +export default { + key: "quickbooks-create-pl-report", + name: "Create Profit and Loss Detail Report", + description: "Creates a profit and loss report in Quickbooks Online. [See the documentation](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/profitandloss#query-a-report)", + version: "0.0.1", + type: "action", + props: { + quickbooks, + customerIds: { + propDefinition: [ + quickbooks, + "customer", + ], + type: "string[]", + label: "Customer Ids", + description: "Filters report contents to include information for specified customers", + optional: true, + }, + accountIds: { + propDefinition: [ + quickbooks, + "accountIds", + ], + }, + accountingMethod: { + propDefinition: [ + quickbooks, + "accountingMethod", + ], + }, + dateMacro: { + type: "string", + label: "Date Macro", + description: "Predefined date range. Use if you want the report to cover a standard report date range; otherwise, use the `start_date` and `end_date` to cover an explicit report date range", + options: DATE_MACRO_OPTIONS, + optional: true, + }, + startDate: { + type: "string", + label: "Start Date", + description: "The start date of the report, in the format `YYYY-MM-DD`. `start_date` must be less than `end_date`", + optional: true, + }, + endDate: { + type: "string", + label: "End Date", + description: "The end date of the report, in the format `YYYY-MM-DD`. `start_date` must be less than `end_date`", + optional: true, + }, + adjustedGainLoss: { + type: "string", + label: "Adjusted Gain Loss", + description: "Specifies whether unrealized gain and losses are included in the report", + options: [ + "true", + "false", + ], + optional: true, + }, + classIds: { + propDefinition: [ + quickbooks, + "classIds", + ], + }, + paymentMethod: { + type: "string", + label: "Payment Method", + description: "Filters report contents based on payment method", + options: PAYMENT_METHOD_OPTIONS, + optional: true, + }, + employeeIds: { + propDefinition: [ + quickbooks, + "employeeIds", + ], + }, + departmentIds: { + propDefinition: [ + quickbooks, + "departmentIds", + ], + }, + vendorIds: { + propDefinition: [ + quickbooks, + "vendorIds", + ], + }, + accountType: { + type: "string", + label: "Account Type", + description: "Account type from which transactions are included in the report", + options: ACCOUNT_TYPE_OPTIONS, + optional: true, + }, + sortBy: { + type: "string", + label: "Sort By", + description: "The column type used in sorting report rows", + options: PROFIT_LOSS_REPORT_COLUMNS, + optional: true, + }, + columns: { + propDefinition: [ + quickbooks, + "columns", + ], + options: PROFIT_LOSS_REPORT_COLUMNS, + }, + }, + async run({ $ }) { + const response = await this.quickbooks.getProfitLossReport({ + $, + params: { + customer: this.customerIds, + account: this.accountIds, + accounting_method: this.accountingMethod, + date_macro: this.dateMacro, + start_date: this.startDate, + end_date: this.endDate, + adjusted_gain_loss: this.adjustedGainLoss, + class: this.classIds, + payment_method: this.paymentMethod, + employee: this.employeeIds, + department: this.departmentIds, + vendor: this.vendorIds, + account_type: this.accountType, + sort_by: this.sortBy, + columns: this.columns, + }, + }); + if (response) { + $.export("summary", "Successfully created Profit and Loss Detail Report"); + } + return response; + }, +}; diff --git a/components/quickbooks/actions/create-purchase/create-purchase.mjs b/components/quickbooks/actions/create-purchase/create-purchase.mjs index be79a5bc3deec..fcf186c2cd465 100644 --- a/components/quickbooks/actions/create-purchase/create-purchase.mjs +++ b/components/quickbooks/actions/create-purchase/create-purchase.mjs @@ -5,7 +5,7 @@ export default { key: "quickbooks-create-purchase", name: "Create Purchase", description: "Creates a new purchase. [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/purchase#create-a-purchase)", - version: "0.0.3", + version: "0.0.4", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/create-sales-receipt/create-sales-receipt.mjs b/components/quickbooks/actions/create-sales-receipt/create-sales-receipt.mjs index c62a07bbcd777..02d9f84627e96 100644 --- a/components/quickbooks/actions/create-sales-receipt/create-sales-receipt.mjs +++ b/components/quickbooks/actions/create-sales-receipt/create-sales-receipt.mjs @@ -5,7 +5,7 @@ export default { key: "quickbooks-create-sales-receipt", name: "Create Sales Receipt", description: "Creates a sales receipt. [See the documentation](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/salesreceipt#create-a-salesreceipt)", - version: "0.0.4", + version: "0.0.5", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/delete-purchase/delete-purchase.mjs b/components/quickbooks/actions/delete-purchase/delete-purchase.mjs index 379e6d1d772a7..707d945323344 100644 --- a/components/quickbooks/actions/delete-purchase/delete-purchase.mjs +++ b/components/quickbooks/actions/delete-purchase/delete-purchase.mjs @@ -4,7 +4,7 @@ export default { key: "quickbooks-delete-purchase", name: "Delete Purchase", description: "Delete a specific purchase. [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/purchase#delete-a-purchase)", - version: "0.0.3", + version: "0.0.4", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/get-bill/get-bill.mjs b/components/quickbooks/actions/get-bill/get-bill.mjs index 1d56b0b24c2b5..afb7b60c592bd 100644 --- a/components/quickbooks/actions/get-bill/get-bill.mjs +++ b/components/quickbooks/actions/get-bill/get-bill.mjs @@ -5,7 +5,7 @@ export default { key: "quickbooks-get-bill", name: "Get Bill", description: "Returns info about a bill. [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/bill#read-a-bill)", - version: "0.1.5", + version: "0.1.6", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/get-customer/get-customer.mjs b/components/quickbooks/actions/get-customer/get-customer.mjs index 172efd7969298..6ea17e0340607 100644 --- a/components/quickbooks/actions/get-customer/get-customer.mjs +++ b/components/quickbooks/actions/get-customer/get-customer.mjs @@ -5,7 +5,7 @@ export default { key: "quickbooks-get-customer", name: "Get Customer", description: "Returns info about a customer. [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/customer#read-a-customer)", - version: "0.3.5", + version: "0.3.6", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/get-invoice/get-invoice.mjs b/components/quickbooks/actions/get-invoice/get-invoice.mjs index 5e2b23e99e5b8..898ee0495e57a 100644 --- a/components/quickbooks/actions/get-invoice/get-invoice.mjs +++ b/components/quickbooks/actions/get-invoice/get-invoice.mjs @@ -5,7 +5,7 @@ export default { key: "quickbooks-get-invoice", name: "Get Invoice", description: "Returns info about an invoice. [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/invoice#read-an-invoice)", - version: "0.2.6", + version: "0.2.7", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/get-my-company/get-my-company.mjs b/components/quickbooks/actions/get-my-company/get-my-company.mjs index 2e3c0f122d2d5..334150777e38a 100644 --- a/components/quickbooks/actions/get-my-company/get-my-company.mjs +++ b/components/quickbooks/actions/get-my-company/get-my-company.mjs @@ -4,7 +4,7 @@ export default { key: "quickbooks-get-my-company", name: "Get My Company", description: "Gets info about a company. [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/companyinfo)", - version: "0.1.5", + version: "0.1.6", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/get-purchase-order/get-purchase-order.mjs b/components/quickbooks/actions/get-purchase-order/get-purchase-order.mjs index 237b2f5534099..5bdfc45477ac4 100644 --- a/components/quickbooks/actions/get-purchase-order/get-purchase-order.mjs +++ b/components/quickbooks/actions/get-purchase-order/get-purchase-order.mjs @@ -5,7 +5,7 @@ export default { key: "quickbooks-get-purchase-order", name: "Get Purchase Order", description: "Returns details about a purchase order. [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/purchaseorder#read-a-purchase-order)", - version: "0.1.5", + version: "0.1.6", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/get-purchase/get-purchase.mjs b/components/quickbooks/actions/get-purchase/get-purchase.mjs index 1a733bad674a0..d10339ef55fba 100644 --- a/components/quickbooks/actions/get-purchase/get-purchase.mjs +++ b/components/quickbooks/actions/get-purchase/get-purchase.mjs @@ -5,7 +5,7 @@ export default { key: "quickbooks-get-purchase", name: "Get Purchase", description: "Returns info about a purchase. [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/purchase#read-a-purchase)", - version: "0.1.5", + version: "0.1.6", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/get-sales-receipt/get-sales-receipt.mjs b/components/quickbooks/actions/get-sales-receipt/get-sales-receipt.mjs index 622e5aa3d5584..c630ea1e62c6e 100644 --- a/components/quickbooks/actions/get-sales-receipt/get-sales-receipt.mjs +++ b/components/quickbooks/actions/get-sales-receipt/get-sales-receipt.mjs @@ -5,7 +5,7 @@ export default { key: "quickbooks-get-sales-receipt", name: "Get Sales Receipt", description: "Returns details about a sales receipt. [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/salesreceipt#read-a-salesreceipt)", - version: "0.1.5", + version: "0.1.6", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/get-time-activity/get-time-activity.mjs b/components/quickbooks/actions/get-time-activity/get-time-activity.mjs index 968cc3f4e266c..6b6ad07ffb380 100644 --- a/components/quickbooks/actions/get-time-activity/get-time-activity.mjs +++ b/components/quickbooks/actions/get-time-activity/get-time-activity.mjs @@ -5,7 +5,7 @@ export default { key: "quickbooks-get-time-activity", name: "Get Time Activity", description: "Returns info about an activity. [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/timeactivity#read-a-timeactivity-object)", - version: "0.1.5", + version: "0.1.6", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/sandbox-search-invoices/sandbox-search-invoices.mjs b/components/quickbooks/actions/sandbox-search-invoices/sandbox-search-invoices.mjs index 77ff78098a203..164006f4750d8 100644 --- a/components/quickbooks/actions/sandbox-search-invoices/sandbox-search-invoices.mjs +++ b/components/quickbooks/actions/sandbox-search-invoices/sandbox-search-invoices.mjs @@ -5,7 +5,7 @@ export default { key: "quickbooks-sandbox-search-invoices", name: "Search Invoices", description: "Searches for invoices. [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/invoice#query-an-invoice)", - version: "0.1.5", + version: "0.1.6", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/search-accounts/search-accounts.mjs b/components/quickbooks/actions/search-accounts/search-accounts.mjs index 9d48857a508ab..ebee75f17be3b 100644 --- a/components/quickbooks/actions/search-accounts/search-accounts.mjs +++ b/components/quickbooks/actions/search-accounts/search-accounts.mjs @@ -5,7 +5,7 @@ export default { key: "quickbooks-search-accounts", name: "Search Accounts", description: "Search for accounts. [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/account#query-an-account)", - version: "0.2.5", + version: "0.2.6", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/search-customers/search-customers.mjs b/components/quickbooks/actions/search-customers/search-customers.mjs index e537b75d1f604..2ec5f2752b675 100644 --- a/components/quickbooks/actions/search-customers/search-customers.mjs +++ b/components/quickbooks/actions/search-customers/search-customers.mjs @@ -5,7 +5,7 @@ export default { key: "quickbooks-search-customers", name: "Search Customers", description: "Searches for customers. [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/customer#query-a-customer)", - version: "0.1.5", + version: "0.1.6", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/search-items/search-items.mjs b/components/quickbooks/actions/search-items/search-items.mjs index 14392a3d32ef0..1913190a94d4d 100644 --- a/components/quickbooks/actions/search-items/search-items.mjs +++ b/components/quickbooks/actions/search-items/search-items.mjs @@ -5,7 +5,7 @@ export default { key: "quickbooks-search-items", name: "Search Items", description: "Searches for items. [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/item#query-an-item)", - version: "0.1.5", + version: "0.1.6", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/search-products/search-products.mjs b/components/quickbooks/actions/search-products/search-products.mjs index 1356711e07dc2..6abe33fefa886 100644 --- a/components/quickbooks/actions/search-products/search-products.mjs +++ b/components/quickbooks/actions/search-products/search-products.mjs @@ -5,7 +5,7 @@ export default { key: "quickbooks-search-products", name: "Search Products", description: "Search for products. [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/item#query-an-item)", - version: "0.1.5", + version: "0.1.6", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/search-purchases/search-purchases.mjs b/components/quickbooks/actions/search-purchases/search-purchases.mjs index b4a8851f46fd0..ac08e79571e04 100644 --- a/components/quickbooks/actions/search-purchases/search-purchases.mjs +++ b/components/quickbooks/actions/search-purchases/search-purchases.mjs @@ -4,7 +4,7 @@ export default { key: "quickbooks-search-purchases", name: "Search Purchases", description: "Searches for purchases. [See the documentation](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/purchase#query-a-purchase)", - version: "0.0.3", + version: "0.0.4", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/search-query/search-query.mjs b/components/quickbooks/actions/search-query/search-query.mjs index c92ee509128b6..5515f8038650b 100644 --- a/components/quickbooks/actions/search-query/search-query.mjs +++ b/components/quickbooks/actions/search-query/search-query.mjs @@ -5,7 +5,7 @@ export default { key: "quickbooks-search-query", name: "Search Query", description: "Performs a search query against a Quickbooks entity. [See docs here](https://developer.intuit.com/app/develophttps://developer.intuit.com/app/developer/qbo/docs/develop/explore-the-quickbooks-online-api/data-queries)", - version: "0.1.5", + version: "0.1.6", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/search-time-activities/search-time-activities.mjs b/components/quickbooks/actions/search-time-activities/search-time-activities.mjs index 2ff57a1484e4f..4f52319d3c300 100644 --- a/components/quickbooks/actions/search-time-activities/search-time-activities.mjs +++ b/components/quickbooks/actions/search-time-activities/search-time-activities.mjs @@ -5,7 +5,7 @@ export default { key: "quickbooks-search-time-activities", name: "Search Time Activities", description: "Searches for time activities. [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/timeactivity#query-a-timeactivity-object)", - version: "0.1.5", + version: "0.1.6", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/search-vendors/search-vendors.mjs b/components/quickbooks/actions/search-vendors/search-vendors.mjs index 578129bd80caf..20e4752e1d35c 100644 --- a/components/quickbooks/actions/search-vendors/search-vendors.mjs +++ b/components/quickbooks/actions/search-vendors/search-vendors.mjs @@ -5,7 +5,7 @@ export default { key: "quickbooks-search-vendors", name: "Search Vendors", description: "Searches for vendors. [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/vendor#query-a-vendor)", - version: "0.1.5", + version: "0.1.6", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/sparse-update-invoice/sparse-update-invoice.mjs b/components/quickbooks/actions/sparse-update-invoice/sparse-update-invoice.mjs index 85fcbb6927fe9..9495fc93a8d89 100644 --- a/components/quickbooks/actions/sparse-update-invoice/sparse-update-invoice.mjs +++ b/components/quickbooks/actions/sparse-update-invoice/sparse-update-invoice.mjs @@ -6,7 +6,7 @@ export default { key: "quickbooks-sparse-update-invoice", name: "Sparse Update Invoice", description: "Sparse updating provides the ability to update a subset of properties for a given object; only elements specified in the request are updated. Missing elements are left untouched. The ID of the object to update is specified in the request body.​ [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/invoice#sparse-update-an-invoice)", - version: "0.1.3", + version: "0.1.4", type: "action", props: { quickbooks, diff --git a/components/quickbooks/actions/update-customer/update-customer.mjs b/components/quickbooks/actions/update-customer/update-customer.mjs index 6b6c5bdc72b77..ab971febe78df 100644 --- a/components/quickbooks/actions/update-customer/update-customer.mjs +++ b/components/quickbooks/actions/update-customer/update-customer.mjs @@ -5,7 +5,7 @@ export default { key: "quickbooks-update-customer", name: "Update Customer", description: "Updates a customer. [See docs here](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/customer#full-update-a-customer)", - version: "0.1.5", + version: "0.1.6", type: "action", props: { quickbooks, diff --git a/components/quickbooks/common/constants.mjs b/components/quickbooks/common/constants.mjs index 02f549d432bf3..bf001cba36632 100644 --- a/components/quickbooks/common/constants.mjs +++ b/components/quickbooks/common/constants.mjs @@ -1,3 +1,125 @@ export const LIMIT = 100; export const MAX_RETRIES = 5; export const INITIAL_BACKOFF_MILLISECONDS = 1500; + +export const AP_AGING_REPORT_COLUMNS = [ + "create_by", + "create_date", + "doc_num", + "due_date", + "last_mod_by", + "last_mod_date", + "memo", + "past_due", + "term_name", + "tx_date", + "txn_type", + "vend_bill_addr", + "vend_comp_name", + "vend_name", + "vend_pri_cont", + "vend_pri_email", + "vend_pri_tel", + "dept_name", + "currency", + "exch_rate", + "subt_neg_open_bal", + "subt_neg_amount", + "neg_foreign_open_bal", + "subt_neg_home_open_bal", + "neg_foreign_amount", + "subt_neg_home_amount", +]; + +export const PROFIT_LOSS_REPORT_COLUMNS = [ + "create_by", + "create_date", + "doc_num", + "last_mod_by", + "last_mod_date", + "memo", + "name", + "pmt_mthd", + "split_acc", + "tx_date", + "txn_type", + "tax_code", + "klass_name", + "dept_name", + "debt_amt", + "credit_amt", + "nat_open_bal", + "subt_nat_amount", + "subt_nat_amount_nt", + "rbal_nat_amount", + "rbal_nat_amount_nt", + "tax_amount", + "net_amount", + "debt_home_amt", + "credit_home_amt", + "currency", + "exch_rate", + "nat_home_open_bal", + "nat_foreign_open_bal", + "subt_nat_home_amount", + "subt_nat_amount_home_nt", + "rbal_nat_home_amount", + "rbal_nat_amount_home_nt", + "home_tax_amount", + "home_net_amount", +]; + +export const DATE_MACRO_OPTIONS = [ + "Today", + "Yesterday", + "This Week", + "Last Week", + "This Week-to-date", + "Last Week-to-date", + "Next Week", + "Next 4 Weeks", + "This Month", + "Last Month", + "This Month-to-date", + "Last Month-to-date", + "Next Month", + "This Fiscal Quarter", + "Last Fiscal Quarter", + "This Fiscal Quarter-to-date", + "Last Fiscal Quarter-to-date", + "Next Fiscal Quarter", + "This Fiscal Year", + "Last Fiscal Year", + "This Fiscal Year-to-date", + "Last Fiscal Year-to-date", + "Next Fiscal Year", +]; + +export const PAYMENT_METHOD_OPTIONS = [ + "Cash", + "Check", + "Dinners Club", + "American Express", + "Discover", + "MasterCard", + "Visa", +]; + +export const ACCOUNT_TYPE_OPTIONS = [ + "AccountsPayable", + "AccountsReceivable", + "Bank", + "CostOfGoodsSold", + "CreditCard", + "Equity", + "Expense", + "FixedAsset", + "Income", + "LongTermLiability", + "NonPosting", + "OtherAsset", + "OtherCurrentAsset", + "OtherCurrentLiability", + "OtherExpense", + "OtherIncome", +]; diff --git a/components/quickbooks/package.json b/components/quickbooks/package.json index d674ade55f074..b53bc5fa84355 100644 --- a/components/quickbooks/package.json +++ b/components/quickbooks/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/quickbooks", - "version": "0.2.1", + "version": "0.3.0", "description": "Pipedream Quickbooks Components", "main": "quickbooks.app.mjs", "keywords": [ diff --git a/components/quickbooks/quickbooks.app.mjs b/components/quickbooks/quickbooks.app.mjs index 14dfb8c6596ac..9f5c38b071291 100644 --- a/components/quickbooks/quickbooks.app.mjs +++ b/components/quickbooks/quickbooks.app.mjs @@ -195,6 +195,154 @@ export default { description: "Suffix of the name. For example, `Jr`. The `DisplayName` attribute or at least one of `Title`, `GivenName`, `MiddleName`, `FamilyName`, or `Suffix` attributes is required for object create.", optional: true, }, + accountingMethod: { + type: "string", + label: "Accounting Method", + description: "The accounting method used in the report", + options: [ + "Cash", + "Accrual", + ], + optional: true, + }, + columns: { + type: "string[]", + label: "Columns", + description: "Column types to be shown in the report", + optional: true, + }, + termIds: { + type: "string[]", + label: "Term Ids", + description: "Filters report contents based on term or terms supplied", + optional: true, + async options({ page }) { + const position = 1 + (page * 10); + const { QueryResponse: { Term: terms } } = await this.query({ + params: { + query: `select * from term maxresults 10 ${page + ? `startposition ${position}` + : ""} `, + }, + }); + return terms?.map(({ + Id: value, Name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + vendorIds: { + type: "string[]", + label: "Vendor Ids", + description: "Filters report contents to include information for specified vendors", + optional: true, + async options({ page }) { + const position = 1 + (page * 10); + const { QueryResponse: { Vendor: vendors } } = await this.query({ + params: { + query: `select * from vendor maxresults 10 ${page + ? `startposition ${position}` + : ""} `, + }, + }); + return vendors?.map(({ + Id: value, DisplayName: label, + }) => ({ + value, + label, + })) || []; + }, + }, + accountIds: { + type: "string[]", + label: "Account Ids", + description: "Filters report contents to include information for specified accounts", + optional: true, + async options({ page }) { + const position = 1 + (page * 10); + const { QueryResponse: { Account: accounts } } = await this.query({ + params: { + query: `select * from account maxresults 10 ${page + ? `startposition ${position}` + : ""} `, + }, + }); + return accounts?.map(({ + Id: value, Name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + classIds: { + type: "string[]", + label: "Class Ids", + description: "Filters report contents to include information for specified classes if so configured in the company file", + optional: true, + async options({ page }) { + const position = 1 + (page * 10); + const { QueryResponse: { Class: classes } } = await this.query({ + params: { + query: `select * from class maxresults 10 ${page + ? `startposition ${position}` + : ""} `, + }, + }); + return classes?.map(({ + Id: value, Name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + employeeIds: { + type: "string[]", + label: "Employee Ids", + description: "Filters report contents to include information for specified employees", + optional: true, + async options({ page }) { + const position = 1 + (page * 10); + const { QueryResponse: { Employee: employees } } = await this.query({ + params: { + query: `select * from employee maxresults 10 ${page + ? `startposition ${position}` + : ""} `, + }, + }); + return employees?.map(({ + Id: value, DisplayName: label, + }) => ({ + value, + label, + })) || []; + }, + }, + departmentIds: { + type: "string[]", + label: "Department Ids", + description: "Filters report contents to include information for specified departments if so configured in the company file", + optional: true, + async options({ page }) { + const position = 1 + (page * 10); + const { QueryResponse: { Department: departments } } = await this.query({ + params: { + query: `select * from department maxresults 10 ${page + ? `startposition ${position}` + : ""} `, + }, + }); + return departments?.map(({ + Id: value, Name: label, + }) => ({ + value, + label, + })) || []; + }, + }, }, methods: { _companyId() { @@ -354,6 +502,20 @@ export default { params, }, $); }, + getApAgingReport({ + $, params, + }) { + return this._makeRequest(`company/${this._companyId()}/reports/AgedPayableDetail`, { + params, + }, $); + }, + getProfitLossReport({ + $, params, + }) { + return this._makeRequest(`company/${this._companyId()}/reports/ProfitAndLoss`, { + params, + }, $); + }, async *paginate({ fn, params = {}, fieldList, query, maxResults = null, ...opts }) { diff --git a/components/quickbooks/sources/new-customer-created/new-customer-created.mjs b/components/quickbooks/sources/new-customer-created/new-customer-created.mjs index 4e4672910f85f..1e4d533c14d38 100644 --- a/components/quickbooks/sources/new-customer-created/new-customer-created.mjs +++ b/components/quickbooks/sources/new-customer-created/new-customer-created.mjs @@ -6,7 +6,7 @@ export default { key: "quickbooks-new-customer-created", name: "New Customer Created", description: "Emit new event when a new customer is created.", - version: "0.0.2", + version: "0.0.3", type: "source", dedupe: "unique", methods: { diff --git a/components/quickbooks/sources/new-customer-updated/new-customer-updated.mjs b/components/quickbooks/sources/new-customer-updated/new-customer-updated.mjs index cc65e923c5350..9525dbdd22ca2 100644 --- a/components/quickbooks/sources/new-customer-updated/new-customer-updated.mjs +++ b/components/quickbooks/sources/new-customer-updated/new-customer-updated.mjs @@ -6,7 +6,7 @@ export default { key: "quickbooks-new-customer-updated", name: "New Customer Updated", description: "Emit new event when a customer is updated.", - version: "0.0.2", + version: "0.0.3", type: "source", dedupe: "unique", methods: { diff --git a/components/quickbooks/sources/new-invoice-created/new-invoice-created.mjs b/components/quickbooks/sources/new-invoice-created/new-invoice-created.mjs index 78981811cc033..b59049a23143b 100644 --- a/components/quickbooks/sources/new-invoice-created/new-invoice-created.mjs +++ b/components/quickbooks/sources/new-invoice-created/new-invoice-created.mjs @@ -6,7 +6,7 @@ export default { key: "quickbooks-new-invoice-created", name: "New Invoice Created", description: "Emit new event when a new invoice is created.", - version: "0.0.2", + version: "0.0.3", type: "source", dedupe: "unique", methods: { diff --git a/components/quickbooks/sources/new-invoice-updated/new-invoice-updated.mjs b/components/quickbooks/sources/new-invoice-updated/new-invoice-updated.mjs index 79f19f1965357..f9d2f68c20417 100644 --- a/components/quickbooks/sources/new-invoice-updated/new-invoice-updated.mjs +++ b/components/quickbooks/sources/new-invoice-updated/new-invoice-updated.mjs @@ -6,7 +6,7 @@ export default { key: "quickbooks-new-invoice-updated", name: "New Invoice Updated", description: "Emit new event when an invoice is updated.", - version: "0.0.2", + version: "0.0.3", type: "source", dedupe: "unique", methods: { diff --git a/components/quickbooks/sources/new-item-created/new-item-created.mjs b/components/quickbooks/sources/new-item-created/new-item-created.mjs index 6a19ced4a2e01..e0f890385281e 100644 --- a/components/quickbooks/sources/new-item-created/new-item-created.mjs +++ b/components/quickbooks/sources/new-item-created/new-item-created.mjs @@ -6,7 +6,7 @@ export default { key: "quickbooks-new-item-created", name: "New Item Created", description: "Emit new event when a new item is created.", - version: "0.0.2", + version: "0.0.3", type: "source", dedupe: "unique", methods: { diff --git a/components/quickbooks/sources/new-item-updated/new-item-updated.mjs b/components/quickbooks/sources/new-item-updated/new-item-updated.mjs index 01d5b38c12b23..02a64206cb400 100644 --- a/components/quickbooks/sources/new-item-updated/new-item-updated.mjs +++ b/components/quickbooks/sources/new-item-updated/new-item-updated.mjs @@ -6,7 +6,7 @@ export default { key: "quickbooks-new-item-updated", name: "New Item Updated", description: "Emit new event when an item is updated.", - version: "0.0.2", + version: "0.0.3", type: "source", dedupe: "unique", methods: { 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); + }, +}; diff --git a/components/runware/actions/image-background-removal/image-background-removal.mjs b/components/runware/actions/image-background-removal/image-background-removal.mjs new file mode 100644 index 0000000000000..abd486ebeaf65 --- /dev/null +++ b/components/runware/actions/image-background-removal/image-background-removal.mjs @@ -0,0 +1,141 @@ +import { ConfigurationError } from "@pipedream/platform"; +import { v4 as uuid } from "uuid"; +import app from "../../runware.app.mjs"; +import constants from "../../common/constants.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "runware-image-background-removal", + name: "Image Background Removal", + description: "Request an image background removal task to be processed by the Runware API. [See the documentation](https://docs.runware.ai/en/image-editing/background-removal).", + version: "0.0.1", + type: "action", + props: { + app, + inputImage: { + propDefinition: [ + app, + "inputImage", + ], + }, + outputType: { + propDefinition: [ + app, + "outputType", + ], + }, + outputFormat: { + propDefinition: [ + app, + "outputFormat", + ], + }, + includeCost: { + propDefinition: [ + app, + "includeCost", + ], + }, + rgb: { + type: "string[]", + label: "RGB", + description: "An array representing the `[red, green, blue]` values that define the color of the removed background. Eg. `[255, 255, 255]`.", + optional: true, + }, + postProcessMask: { + type: "boolean", + label: "Post-Process Mask", + description: "Flag indicating whether to post-process the mask. Controls whether the mask should undergo additional post-processing. This step can improve the accuracy and quality of the background removal mask.", + optional: true, + }, + returnOnlyMask: { + type: "boolean", + label: "Return Only Mask", + description: "Flag indicating whether to return only the mask. The mask is the opposite of the image background removal.", + optional: true, + }, + alphaMatting: { + type: "boolean", + label: "Alpha Matting", + description: "Flag indicating whether to use alpha matting. Alpha matting is a post-processing technique that enhances the quality of the output by refining the edges of the foreground object.", + optional: true, + }, + alphaMattingForegroundThreshold: { + type: "integer", + label: "Alpha Matting Foreground Threshold", + description: "Threshold value used in alpha matting to distinguish the foreground from the background. Adjusting this parameter affects the sharpness and accuracy of the foreground object edges. Eg. `240`.", + optional: true, + min: 1, + max: 255, + }, + alphaMattingBackgroundThreshold: { + type: "integer", + label: "Alpha Matting Background Threshold", + description: "Threshold value used in alpha matting to refine the background areas. It influences how aggressively the algorithm removes the background while preserving image details. The higher the value, the more computation is needed and therefore the more expensive the operation is. Eg. `10`.", + optional: true, + min: 1, + max: 255, + }, + alphaMattingErodeSize: { + type: "integer", + label: "Alpha Matting Erode Size", + description: "Specifies the size of the erosion operation used in alpha matting. Erosion helps in smoothing the edges of the foreground object for a cleaner removal of the background. Eg. `10`.", + optional: true, + min: 1, + max: 255, + }, + }, + methods: { + getRGBA(rgb, alpha = 0) { + if (rgb) { + const parsed = utils.parseArray(rgb).map((value) => parseInt(value, 10)); + return parsed.concat(alpha); + } + }, + }, + async run({ $ }) { + const { + app, + getRGBA, + inputImage, + outputType, + outputFormat, + includeCost, + rgb, + postProcessMask, + returnOnlyMask, + alphaMatting, + alphaMattingForegroundThreshold, + alphaMattingBackgroundThreshold, + alphaMattingErodeSize, + } = this; + + if (rgb && utils.parseArray(rgb).length !== 3) { + throw new ConfigurationError("The **RGB** array must contain exactly 3 integer numbers. Eg. `[255, 255, 255]`."); + } + + const response = await app.post({ + $, + data: [ + { + taskType: constants.TASK_TYPE.IMAGE_BACKGROUND_REMOVAL.value, + taskUUID: uuid(), + inputImage, + outputType, + outputFormat, + includeCost, + rgba: getRGBA(rgb), + postProcessMask, + returnOnlyMask, + alphaMatting, + alphaMattingForegroundThreshold, + alphaMattingBackgroundThreshold, + alphaMattingErodeSize, + }, + ], + }); + + $.export("$summary", `Successfully requested image background removal task with UUID \`${response.data[0].taskUUID}\`.`); + return response; + }, +}; diff --git a/components/runware/actions/image-caption/image-caption.mjs b/components/runware/actions/image-caption/image-caption.mjs new file mode 100644 index 0000000000000..dd3f12c1322d4 --- /dev/null +++ b/components/runware/actions/image-caption/image-caption.mjs @@ -0,0 +1,48 @@ +import { v4 as uuid } from "uuid"; +import app from "../../runware.app.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + key: "runware-image-caption", + name: "Image Caption", + description: "Request an image caption task to be processed by the Runware API. [See the documentation](https://docs.runware.ai/en/utilities/image-to-text).", + version: "0.0.1", + type: "action", + props: { + app, + inputImage: { + propDefinition: [ + app, + "inputImage", + ], + }, + includeCost: { + propDefinition: [ + app, + "includeCost", + ], + }, + }, + async run({ $ }) { + const { + app, + inputImage, + includeCost, + } = this; + + const response = await app.post({ + $, + data: [ + { + taskType: constants.TASK_TYPE.IMAGE_CAPTION.value, + taskUUID: uuid(), + inputImage, + includeCost, + }, + ], + }); + + $.export("$summary", `Successfully requested image caption task with UUID \`${response.data[0].taskUUID}\`.`); + return response; + }, +}; diff --git a/components/runware/actions/image-control-net-preprocess/image-control-net-preprocess.mjs b/components/runware/actions/image-control-net-preprocess/image-control-net-preprocess.mjs new file mode 100644 index 0000000000000..fe5a1958b53be --- /dev/null +++ b/components/runware/actions/image-control-net-preprocess/image-control-net-preprocess.mjs @@ -0,0 +1,126 @@ +import { v4 as uuid } from "uuid"; +import app from "../../runware.app.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + key: "runware-image-control-net-preprocess", + name: "Image Control Net Preprocess", + description: "Request an image control net preprocess task to be processed by the Runware API. [See the documentation](https://docs.runware.ai/en/image-editing/controlnet-tools).", + version: "0.0.1", + type: "action", + props: { + app, + inputImage: { + propDefinition: [ + app, + "inputImage", + ], + }, + outputType: { + propDefinition: [ + app, + "outputType", + ], + }, + outputFormat: { + propDefinition: [ + app, + "outputFormat", + ], + }, + includeCost: { + propDefinition: [ + app, + "includeCost", + ], + }, + preProcessorType: { + type: "string", + label: "Preprocessor Type", + description: "The preprocessor to be used.", + optional: true, + options: [ + "canny", + "depth", + "mlsd", + "normalbae", + "openpose", + "tile", + "seg", + "lineart", + "lineart_anime", + "shuffle", + "scribble", + "softedge", + ], + }, + height: { + propDefinition: [ + app, + "height", + ], + }, + width: { + propDefinition: [ + app, + "width", + ], + }, + lowThresholdCanny: { + type: "integer", + label: "Low Threshold Canny", + description: "Defines the lower threshold when using the Canny edge detection preprocessor. The recommended value is `100`.", + optional: true, + }, + highThresholdCanny: { + type: "integer", + label: "High Threshold Canny", + description: "Defines the high threshold when using the Canny edge detection preprocessor. The recommended value is `200`.", + optional: true, + }, + includeHandsAndFaceOpenPose: { + type: "boolean", + label: "Include Hands and Face OpenPose", + description: "Include the hands and face in the pose outline when using the OpenPose preprocessor.", + optional: true, + }, + }, + async run({ $ }) { + const { + app, + outputType, + outputFormat, + includeCost, + inputImage, + preProcessorType, + height, + width, + lowThresholdCanny, + highThresholdCanny, + includeHandsAndFaceOpenPose, + } = this; + + const response = await app.post({ + $, + data: [ + { + taskType: constants.TASK_TYPE.IMAGE_CONTROL_NET_PREPROCESS.value, + taskUUID: uuid(), + outputType, + outputFormat, + inputImage, + includeCost, + height, + width, + preProcessorType, + lowThresholdCanny, + highThresholdCanny, + includeHandsAndFaceOpenPose, + }, + ], + }); + + $.export("$summary", `Successfully requested image control net preprocess task with UUID \`${response.data[0].taskUUID}\`.`); + return response; + }, +}; diff --git a/components/runware/actions/image-inference/image-inference.mjs b/components/runware/actions/image-inference/image-inference.mjs new file mode 100644 index 0000000000000..f1daafceace2a --- /dev/null +++ b/components/runware/actions/image-inference/image-inference.mjs @@ -0,0 +1,435 @@ +import { v4 as uuid } from "uuid"; +import app from "../../runware.app.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + key: "runware-image-inference", + name: "Image Inference", + description: "Request an image inference task to be processed by the Runware API. [See the documentation](https://docs.runware.ai/en/image-inference/api-reference).", + version: "0.0.1", + type: "action", + props: { + app, + structure: { + type: "string", + label: "Structure", + description: "The structure of the task to be processed.", + options: Object.values(constants.IMAGE_INFERENCE_STRUCTURE), + reloadProps: true, + }, + model: { + type: "string", + label: "Model", + description: "This identifier is a unique string that represents a specific model. You can find the AIR identifier of the model you want to use in our [Model Explorer](https://docs.runware.ai/en/image-inference/models#model-explorer), which is a tool that allows you to search for models based on their characteristics. More information about the AIR system can be found in the [Models page](https://docs.runware.ai/en/image-inference/models). Eg. `civitai:78605@83390`.", + }, + positivePrompt: { + type: "string", + label: "Positive Prompt", + description: "A positive prompt is a text instruction to guide the model on generating the image. It is usually a sentence or a paragraph that provides positive guidance for the task. This parameter is essential to shape the desired results. For example, if the positive prompt is `dragon drinking coffee`, the model will generate an image of a dragon drinking coffee. The more detailed the prompt, the more accurate the results. The length of the prompt must be between 4 and 2000 characters.", + }, + height: { + propDefinition: [ + app, + "height", + ], + }, + width: { + propDefinition: [ + app, + "width", + ], + }, + uploadEndpoint: { + type: "string", + label: "Upload Endpoint", + description: "This parameter allows you to specify a URL to which the generated image will be uploaded as binary image data using the HTTP PUT method. For example, an S3 bucket URL can be used as the upload endpoint. When the image is ready, it will be uploaded to the specified URL.", + optional: true, + }, + checkNSFW: { + type: "boolean", + label: "Check NSFW", + description: "This parameter is used to enable or disable the NSFW check. When enabled, the API will check if the image contains NSFW (not safe for work) content. This check is done using a pre-trained model that detects adult content in images. When the check is enabled, the API will return `NSFWContent: true` in the response object if the image is flagged as potentially sensitive content. If the image is not flagged, the API will return `NSFWContent: false`. If this parameter is not used, the parameter `NSFWContent` will not be included in the response object. Adds `0.1` seconds to image inference time and incurs additional costs. The NSFW filter occasionally returns false positives and very rarely false negatives.", + optional: true, + }, + includeCost: { + propDefinition: [ + app, + "includeCost", + ], + }, + scheduler: { + type: "string", + label: "Scheduler", + description: "An scheduler is a component that manages the inference process. Different schedulers can be used to achieve different results like more detailed images, faster inference, or more accurate results. The default scheduler is the one that the model was trained with, but you can choose a different one to get different results. Schedulers are explained in more detail in the [Schedulers page](https://docs.runware.ai/en/image-inference/schedulers).", + optional: true, + }, + seed: { + type: "string", + label: "Seed", + description: "A seed is a value used to randomize the image generation. If you want to make images reproducible (generate the same image multiple times), you can use the same seed value. When requesting multiple images with the same seed, the seed will be incremented by 1 (+1) for each image generated. Min: `0` Max: `9223372036854776000`. Defaults to `Random`.", + optional: true, + }, + numberResults: { + type: "integer", + label: "Number Of Results", + description: "The number of images to generate from the specified prompt. If **Seed** is set, it will be incremented by 1 (+1) for each image generated.", + optional: true, + }, + }, + additionalProps() { + const { structure } = this; + + const seedImage = { + type: "string", + label: "Seed Image", + description: "When doing Image-to-Image, Inpainting or Outpainting, this parameter is **required**. Specifies the seed image to be used for the diffusion process. The image can be specified in one of the following formats:\n - An UUID v4 string of a [previously uploaded image](https://docs.runware.ai/en/getting-started/image-upload) or a [generated image](https://docs.runware.ai/en/image-inference/api-reference).\n - A data URI string representing the image. The data URI must be in the format `data:;base64,` followed by the base64-encoded image. For example: `data:image/png;base64,iVBORw0KGgo...`.\n - A base64 encoded image without the data URI prefix. For example: `iVBORw0KGgo...`.\n - A URL pointing to the image. The image must be accessible publicly. Supported formats are: PNG, JPG and WEBP.", + }; + + const maskImage = { + type: "string", + label: "Mask Image", + description: "When doing Inpainting or Outpainting, this parameter is **required**. Specifies the mask image to be used for the inpainting process. The image can be specified in one of the following formats:\n - An UUID v4 string of a [previously uploaded image](https://docs.runware.ai/en/getting-started/image-upload) or a [generated image](https://docs.runware.ai/en/image-inference/api-reference).\n - A data URI string representing the image. The data URI must be in the format `data:;base64,` followed by the base64-encoded image. For example: `data:image/png;base64,iVBORw0KGgo...`.\n - A base64 encoded image without the data URI prefix. For example: `iVBORw0KGgo...`.\n - A URL pointing to the image. The image must be accessible publicly. Supported formats are: PNG, JPG and WEBP.", + }; + + const strength = { + type: "string", + label: "Strength", + description: "When doing Image-to-Image, Inpainting or Outpainting, this parameter is used to determine the influence of the **Seed Image** image in the generated output. A higher value results in more influence from the original image, while a lower value allows more creative deviation. Min: `0` Max: `1` and Default: `0.8`.", + optional: true, + }; + + const controlNetModel = { + type: "string", + label: "ControlNet Model 0", + description: "For basic/common ControlNet models, you can check the list of available models [here](https://docs.runware.ai/en/image-inference/models#basic-controlnet-models). For custom or specific ControlNet models, we make use of the [AIR system](https://github.com/civitai/civitai/wiki/AIR-%E2%80%90-Uniform-Resource-Names-for-AI) to identify ControlNet models. This identifier is a unique string that represents a specific model. You can find the AIR identifier of the ControlNet model you want to use in our [Model Explorer](https://docs.runware.ai/en/image-inference/models#model-explorer), which is a tool that allows you to search for models based on their characteristics. More information about the AIR system can be found in the [Models page](https://docs.runware.ai/en/image-inference/models).", + }; + + const controlNetGuideImage = { + type: "string", + label: "ControlNet Guide Image 0", + description: "The guide image for ControlNet.", + }; + + const controlNetWeight = { + type: "integer", + label: "ControlNet Weight 0", + description: "The weight for ControlNet.", + }; + + const controlNetStartStep = { + type: "integer", + label: "ControlNet Start Step 0", + description: "The start step for ControlNet.", + }; + + const controlNetEndStep = { + type: "integer", + label: "ControlNet End Step 0", + description: "The end step for ControlNet.", + }; + + const controlNetControlMode = { + type: "string", + label: "ControlNet Control Mode 0", + description: "The control mode for ControlNet.", + }; + + const loraModel = { + type: "string", + label: "LoRA Model 0", + description: "We make use of the [AIR system](https://github.com/civitai/civitai/wiki/AIR-%E2%80%90-Uniform-Resource-Names-for-AI) to identify LoRA models. This identifier is a unique string that represents a specific model. You can find the AIR identifier of the LoRA model you want to use in our [Model Explorer](https://docs.runware.ai/en/image-inference/models#model-explorer), which is a tool that allows you to search for models based on their characteristics. More information about the AIR system can be found in the [Models page](https://docs.runware.ai/en/image-inference/models).", + }; + + const loraWeight = { + type: "integer", + label: "LoRA Weight 0", + description: "It is possible to use multiple LoRAs at the same time. With the `weight` parameter you can assign the importance of the LoRA with respect to the others. The sum of all `weight` parameters must always be `1`. If needed, we will increase the values proportionally to achieve it.", + optional: true, + }; + + if (structure === constants.IMAGE_INFERENCE_STRUCTURE.TEXT_TO_IMAGE.value) { + return { + outputType: { + type: "string", + label: "Output Type", + description: "Specifies the output type in which the image is returned.", + optional: true, + options: [ + "base64Data", + "dataURI", + "URL", + ], + }, + outputFormat: { + type: "string", + label: "Output Format", + description: "Specifies the format of the output image.", + optional: true, + options: [ + "PNG", + "JPG", + "WEBP", + ], + }, + negativePrompt: { + type: "string", + label: "Negative Prompt", + description: "A negative prompt is a text instruction to guide the model on generating the image. It is usually a sentence or a paragraph that provides negative guidance for the task. This parameter helps to avoid certain undesired results. For example, if the negative prompt is `red dragon, cup`, the model will follow the positive prompt but will avoid generating an image of a red dragon or including a cup. The more detailed the prompt, the more accurate the results. The length of the prompt must be between 4 and 2000 characters.", + optional: true, + }, + steps: { + type: "integer", + label: "Steps", + description: "The number of steps is the number of iterations the model will perform to generate the image. The higher the number of steps, the more detailed the image will be. However, increasing the number of steps will also increase the time it takes to generate the image and may not always result in a better image (some [schedulers](https://docs.runware.ai/en/image-inference/api-reference#request-scheduler) work differently). When using your own models you can specify a new default value for the number of steps. Defaults to `20`.", + min: 1, + max: 100, + optional: true, + }, + CFGScale: { + type: "string", + label: "CFG Scale", + description: "Guidance scale represents how closely the images will resemble the prompt or how much freedom the AI model has. Higher values are closer to the prompt. Low values may reduce the quality of the results. Min: `0`, Max: `30` Default: `7`.", + optional: true, + }, + }; + } + + if (structure === constants.IMAGE_INFERENCE_STRUCTURE.IMAGE_TO_IMAGE.value) { + return { + seedImage, + strength, + }; + } + + if (structure === constants.IMAGE_INFERENCE_STRUCTURE.IN_OUT_PAINTING.value) { + return { + seedImage, + maskImage, + strength, + }; + } + + if (structure === constants.IMAGE_INFERENCE_STRUCTURE.REFINER.value) { + return { + refinerModel: { + type: "string", + label: "Refiner Model", + description: "We make use of the [AIR system](https://github.com/civitai/civitai/wiki/AIR-%E2%80%90-Uniform-Resource-Names-for-AI) to identify refinement models. This identifier is a unique string that represents a specific model. Note that refiner models are only SDXL based. You can find the AIR identifier of the refinement model you want to use in our [Model Explorer](https://docs.runware.ai/en/image-inference/models#model-explorer), which is a tool that allows you to search for models based on their characteristics. More information about the AIR system can be found in the [Models page](https://docs.runware.ai/en/image-inference/models).", + }, + refinerStartStep: { + type: "integer", + label: "Refiner Start Step", + description: "Represents the step number at which the refinement process begins. The initial model will generate the image up to this step, after which the refiner model takes over to enhance the result. It can take values from `0` (first step) to the number of [steps](https://docs.runware.ai/en/image-inference/api-reference#request-steps) specified.", + optional: true, + }, + }; + } + + if (structure === constants.IMAGE_INFERENCE_STRUCTURE.CONTROL_NET.value) { + return { + controlNetModel1: { + ...controlNetModel, + label: "Control Net Model 1", + }, + controlNetGuideImage1: { + ...controlNetGuideImage, + label: "Control Net Guide Image 1", + }, + controlNetWeight1: { + ...controlNetWeight, + label: "Control Net Weight 1", + }, + controlNetStartStep1: { + ...controlNetStartStep, + label: "Control Net Start Step 1", + }, + controlNetEndStep1: { + label: "Control Net End Step 1", + ...controlNetEndStep, + }, + controlNetControlMode1: { + ...controlNetControlMode, + label: "Control Net Control Mode 1", + }, + controlNetModel2: { + ...controlNetModel, + label: "Control Net Model 2", + optional: true, + }, + controlNetGuideImage2: { + ...controlNetGuideImage, + label: "Control Net Guide Image 2", + optional: true, + }, + controlNetWeight2: { + ...controlNetWeight, + label: "Control Net Weight 2", + optional: true, + }, + controlNetStartStep2: { + ...controlNetStartStep, + label: "Control Net Start Step 2", + optional: true, + }, + controlNetEndStep2: { + ...controlNetEndStep, + label: "Control Net End Step 2", + optional: true, + }, + controlNetControlMode2: { + ...controlNetControlMode, + label: "Control Net Control Mode 2", + optional: true, + }, + }; + } + + if (structure === constants.IMAGE_INFERENCE_STRUCTURE.LORA.value) { + return { + loraModel1: { + ...loraModel, + label: "LoRA Model 1", + }, + loraWeight1: { + label: "LoRA Weight 1", + ...loraWeight, + }, + loraModel2: { + label: "LoRA Model 2", + ...loraModel, + optional: true, + }, + loraWeight2: { + label: "LoRA Weight 2", + ...loraWeight, + }, + }; + } + + return {}; + }, + async run({ $ }) { + const { + app, + outputType, + outputFormat, + uploadEndpoint, + checkNSFW, + includeCost, + positivePrompt, + negativePrompt, + seedImage, + maskImage, + strength, + height, + width, + model, + steps, + scheduler, + seed, + numberResults, + CFGScale, + refinerModel, + refinerStartStep, + controlNetModel1, + controlNetGuideImage1, + controlNetWeight1, + controlNetStartStep1, + controlNetEndStep1, + controlNetControlMode1, + controlNetModel2, + controlNetGuideImage2, + controlNetWeight2, + controlNetStartStep2, + controlNetEndStep2, + controlNetControlMode2, + loraModel1, + loraWeight1, + loraModel2, + loraWeight2, + } = this; + + const data = { + taskType: constants.TASK_TYPE.IMAGE_INFERENCE.value, + taskUUID: uuid(), + positivePrompt, + outputType, + outputFormat, + uploadEndpoint, + checkNSFW, + includeCost, + negativePrompt, + seedImage, + maskImage, + strength, + height, + width, + model, + steps, + scheduler, + seed: seed + ? parseInt(seed) + : undefined, + numberResults, + CFGScale, + refiner: refinerModel + ? { + model: refinerModel, + startStep: refinerStartStep, + } + : undefined, + controlNet: controlNetModel1 + ? [ + { + model: controlNetModel1, + guideImage: controlNetGuideImage1, + weight: controlNetWeight1, + startStep: controlNetStartStep1, + endStep: controlNetEndStep1, + controlMode: controlNetControlMode1, + }, + ...(controlNetModel2 + ? [ + { + model: controlNetModel2, + guideImage: controlNetGuideImage2, + weight: controlNetWeight2, + startStep: controlNetStartStep2, + endStep: controlNetEndStep2, + controlMode: controlNetControlMode2, + }, + ] + : [] + ), + ] + : undefined, + lora: loraModel1 + ? [ + { + model: loraModel1, + weight: loraWeight1, + }, + ...(loraModel2 + ? [ + { + model: loraModel2, + weight: loraWeight2, + }, + ] + : [] + ), + ] + : undefined, + }; + + const response = await app.post({ + $, + data: [ + data, + ], + }); + + $.export("$summary", `Successfully requested image inference task with UUID \`${response.data[0].taskUUID}\`.`); + return response; + }, +}; diff --git a/components/runware/actions/image-upscale/image-upscale.mjs b/components/runware/actions/image-upscale/image-upscale.mjs new file mode 100644 index 0000000000000..49879c4573a71 --- /dev/null +++ b/components/runware/actions/image-upscale/image-upscale.mjs @@ -0,0 +1,73 @@ +import { v4 as uuid } from "uuid"; +import app from "../../runware.app.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + key: "runware-image-upscale", + name: "Image Upscale", + description: "Request an image upscale task to be processed by the Runware API. [See the documentation](https://docs.runware.ai/en/image-editing/upscaling).", + version: "0.0.1", + type: "action", + props: { + app, + inputImage: { + propDefinition: [ + app, + "inputImage", + ], + }, + outputType: { + propDefinition: [ + app, + "outputType", + ], + }, + outputFormat: { + propDefinition: [ + app, + "outputFormat", + ], + }, + upscaleFactor: { + type: "integer", + label: "Upscale Factor", + description: "The level of upscaling performed. Each will increase the size of the image by the corresponding factor. Eg. `2`.", + min: 2, + max: 4, + }, + includeCost: { + propDefinition: [ + app, + "includeCost", + ], + }, + }, + async run({ $ }) { + const { + app, + inputImage, + outputType, + outputFormat, + upscaleFactor, + includeCost, + } = this; + + const response = await app.post({ + $, + data: [ + { + taskType: constants.TASK_TYPE.IMAGE_UPSCALE.value, + taskUUID: uuid(), + inputImage, + outputType, + outputFormat, + upscaleFactor, + includeCost, + }, + ], + }); + + $.export("$summary", `Successfully requested image upscale task with UUID \`${response.data[0].taskUUID}\`.`); + return response; + }, +}; diff --git a/components/runware/actions/prompt-enhance/prompt-enhance.mjs b/components/runware/actions/prompt-enhance/prompt-enhance.mjs new file mode 100644 index 0000000000000..0d21956a34469 --- /dev/null +++ b/components/runware/actions/prompt-enhance/prompt-enhance.mjs @@ -0,0 +1,65 @@ +import { v4 as uuid } from "uuid"; +import app from "../../runware.app.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + key: "runware-prompt-enhance", + name: "Prompt Enhance", + description: "Request a prompt enhance task to be processed by the Runware API. [See the documentation](https://docs.runware.ai/en/utilities/prompt-enhancer).", + version: "0.0.1", + type: "action", + props: { + app, + prompt: { + type: "string", + label: "Prompt", + description: "The prompt that you intend to enhance.", + }, + promptMaxLength: { + type: "integer", + label: "Prompt Max Length", + description: "Represents the maximum length of the enhanced prompt that you intend to receive. Min `12`, Max `400`.", + min: 12, + max: 400, + }, + promptVersions: { + type: "integer", + label: "Prompt Versions", + description: "The number of prompt versions that will be received. Min `1`, Max `5`.", + min: 1, + max: 5, + }, + includeCost: { + propDefinition: [ + app, + "includeCost", + ], + }, + }, + async run({ $ }) { + const { + app, + prompt, + promptMaxLength, + promptVersions, + includeCost, + } = this; + + const response = await app.post({ + $, + data: [ + { + taskUUID: uuid(), + taskType: constants.TASK_TYPE.PROMPT_ENHANCE.value, + prompt, + promptMaxLength, + promptVersions, + includeCost, + }, + ], + }); + + $.export("$summary", `Successfully requested prompt enhance task with UUID \`${response.data[0].taskUUID}\`.`); + return response; + }, +}; diff --git a/components/runware/actions/request-task/request-task.mjs b/components/runware/actions/request-task/request-task.mjs deleted file mode 100644 index 509660bd5ad75..0000000000000 --- a/components/runware/actions/request-task/request-task.mjs +++ /dev/null @@ -1,176 +0,0 @@ -import { v4 as uuid } from "uuid"; -import app from "../../runware.app.mjs"; - -export default { - key: "runware-request-task", - name: "Request Task", - description: "Request one task to be processed by the Runware API. [See the documentation](https://docs.runware.ai/en/image-inference/api-reference).", - version: "0.0.1", - type: "action", - props: { - app, - taskType: { - propDefinition: [ - app, - "taskType", - ], - }, - outputType: { - propDefinition: [ - app, - "outputType", - ], - }, - outputFormat: { - propDefinition: [ - app, - "outputFormat", - ], - }, - uploadEndpoint: { - propDefinition: [ - app, - "uploadEndpoint", - ], - }, - checkNSFW: { - propDefinition: [ - app, - "checkNSFW", - ], - }, - includeCost: { - propDefinition: [ - app, - "includeCost", - ], - }, - positivePrompt: { - propDefinition: [ - app, - "positivePrompt", - ], - }, - negativePrompt: { - propDefinition: [ - app, - "negativePrompt", - ], - }, - seedImage: { - propDefinition: [ - app, - "seedImage", - ], - }, - maskImage: { - propDefinition: [ - app, - "maskImage", - ], - }, - strength: { - propDefinition: [ - app, - "strength", - ], - }, - height: { - propDefinition: [ - app, - "height", - ], - }, - width: { - propDefinition: [ - app, - "width", - ], - }, - model: { - propDefinition: [ - app, - "model", - ], - }, - steps: { - propDefinition: [ - app, - "steps", - ], - }, - scheduler: { - propDefinition: [ - app, - "scheduler", - ], - }, - seed: { - propDefinition: [ - app, - "seed", - ], - }, - numberResults: { - propDefinition: [ - app, - "numberResults", - ], - }, - }, - async run({ $ }) { - const { - app, - taskType, - outputType, - outputFormat, - uploadEndpoint, - checkNSFW, - includeCost, - positivePrompt, - negativePrompt, - seedImage, - maskImage, - strength, - height, - width, - model, - steps, - scheduler, - seed, - numberResults, - } = this; - - const response = await app.post({ - $, - data: [ - { - taskUUID: uuid(), - taskType, - outputType, - outputFormat, - uploadEndpoint, - checkNSFW, - includeCost, - positivePrompt, - negativePrompt, - seedImage, - maskImage, - strength, - height, - width, - model, - steps, - scheduler, - seed: seed - ? parseInt(seed) - : undefined, - numberResults, - }, - ], - }); - - $.export("$summary", `Successfully requested task with UUID \`${response.data[0].taskUUID}\`.`); - return response; - }, -}; diff --git a/components/runware/common/constants.mjs b/components/runware/common/constants.mjs index 01df0f6f95e74..c7e2902406234 100644 --- a/components/runware/common/constants.mjs +++ b/components/runware/common/constants.mjs @@ -28,8 +28,36 @@ const TASK_TYPE = { }, }; +const IMAGE_INFERENCE_STRUCTURE = { + TEXT_TO_IMAGE: { + value: "textToImage", + label: "Text to Image", + }, + IMAGE_TO_IMAGE: { + value: "imageToImage", + label: "Image to Image", + }, + IN_OUT_PAINTING: { + value: "inOutpainting", + label: "In/Outpainting", + }, + REFINER: { + value: "refiner", + label: "Refiner", + }, + CONTROL_NET: { + value: "controlNet", + label: "Control Net", + }, + LORA: { + value: "lora", + label: "LoRA", + }, +}; + export default { BASE_URL, VERSION_PATH, + IMAGE_INFERENCE_STRUCTURE, TASK_TYPE, }; diff --git a/components/runware/common/utils.mjs b/components/runware/common/utils.mjs new file mode 100644 index 0000000000000..2e10f7e6444f5 --- /dev/null +++ b/components/runware/common/utils.mjs @@ -0,0 +1,28 @@ +import { ConfigurationError } from "@pipedream/platform"; + +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"); + } +} + +export default { + parseArray, +}; diff --git a/components/runware/package.json b/components/runware/package.json index 02fb5aff6d31d..345513e700ef6 100644 --- a/components/runware/package.json +++ b/components/runware/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/runware", - "version": "0.1.0", + "version": "0.2.0", "description": "Pipedream Runware Components", "main": "runware.app.mjs", "keywords": [ diff --git a/components/runware/runware.app.mjs b/components/runware/runware.app.mjs index eda79b83d54b0..8fea1cf1c8ff4 100644 --- a/components/runware/runware.app.mjs +++ b/components/runware/runware.app.mjs @@ -33,53 +33,12 @@ export default { "WEBP", ], }, - uploadEndpoint: { - type: "string", - label: "Upload Endpoint", - description: "This parameter allows you to specify a URL to which the generated image will be uploaded as binary image data using the HTTP PUT method. For example, an S3 bucket URL can be used as the upload endpoint. When the image is ready, it will be uploaded to the specified URL.", - optional: true, - }, - checkNSFW: { - type: "boolean", - label: "Check NSFW", - description: "This parameter is used to enable or disable the NSFW check. When enabled, the API will check if the image contains NSFW (not safe for work) content. This check is done using a pre-trained model that detects adult content in images. When the check is enabled, the API will return `NSFWContent: true` in the response object if the image is flagged as potentially sensitive content. If the image is not flagged, the API will return `NSFWContent: false`. If this parameter is not used, the parameter `NSFWContent` will not be included in the response object. Adds `0.1` seconds to image inference time and incurs additional costs. The NSFW filter occasionally returns false positives and very rarely false negatives.", - optional: true, - }, includeCost: { type: "boolean", label: "Include Cost", description: "If set to `true`, the cost to perform the task will be included in the response object. Defaults to `false`.", optional: true, }, - positivePrompt: { - type: "string", - label: "Positive Prompt", - description: "A positive prompt is a text instruction to guide the model on generating the image. It is usually a sentence or a paragraph that provides positive guidance for the task. This parameter is essential to shape the desired results. For example, if the positive prompt is `dragon drinking coffee`, the model will generate an image of a dragon drinking coffee. The more detailed the prompt, the more accurate the results. The length of the prompt must be between 4 and 2000 characters.", - }, - negativePrompt: { - type: "string", - label: "Negative Prompt", - description: "A negative prompt is a text instruction to guide the model on generating the image. It is usually a sentence or a paragraph that provides negative guidance for the task. This parameter helps to avoid certain undesired results. For example, if the negative prompt is `red dragon, cup`, the model will follow the positive prompt but will avoid generating an image of a red dragon or including a cup. The more detailed the prompt, the more accurate the results. The length of the prompt must be between 4 and 2000 characters.", - optional: true, - }, - seedImage: { - type: "string", - label: "Seed Image", - description: "When doing Image-to-Image, Inpainting or Outpainting, this parameter is **required**. Specifies the seed image to be used for the diffusion process. The image can be specified in one of the following formats:\n - An UUID v4 string of a [previously uploaded image](https://docs.runware.ai/en/getting-started/image-upload) or a [generated image](https://docs.runware.ai/en/image-inference/api-reference).\n - A data URI string representing the image. The data URI must be in the format `data:;base64,` followed by the base64-encoded image. For example: `data:image/png;base64,iVBORw0KGgo...`.\n - A base64 encoded image without the data URI prefix. For example: `iVBORw0KGgo...`.\n - A URL pointing to the image. The image must be accessible publicly. Supported formats are: PNG, JPG and WEBP.", - optional: true, - }, - maskImage: { - type: "string", - label: "Mask Image", - description: "When doing Inpainting or Outpainting, this parameter is **required**. Specifies the mask image to be used for the inpainting process. The image can be specified in one of the following formats:\n - An UUID v4 string of a [previously uploaded image](https://docs.runware.ai/en/getting-started/image-upload) or a [generated image](https://docs.runware.ai/en/image-inference/api-reference).\n - A data URI string representing the image. The data URI must be in the format `data:;base64,` followed by the base64-encoded image. For example: `data:image/png;base64,iVBORw0KGgo...`.\n - A base64 encoded image without the data URI prefix. For example: `iVBORw0KGgo...`.\n - A URL pointing to the image. The image must be accessible publicly. Supported formats are: PNG, JPG and WEBP.", - optional: true, - }, - strength: { - type: "string", - label: "Strength", - description: "When doing Image-to-Image, Inpainting or Outpainting, this parameter is used to determine the influence of the **Seed Image** image in the generated output. A higher value results in more influence from the original image, while a lower value allows more creative deviation. Min: `0` Max: `1` and Default: `0.8`.", - optional: true, - }, height: { type: "integer", label: "Height", @@ -94,36 +53,10 @@ export default { min: 512, max: 2048, }, - model: { - type: "string", - label: "Model", - description: "This identifier is a unique string that represents a specific model. You can find the AIR identifier of the model you want to use in our [Model Explorer](https://docs.runware.ai/en/image-inference/models#model-explorer), which is a tool that allows you to search for models based on their characteristics. More information about the AIR system can be found in the [Models page](https://docs.runware.ai/en/image-inference/models). Eg. `civitai:78605@83390`.", - }, - steps: { - type: "integer", - label: "Steps", - description: "The number of steps is the number of iterations the model will perform to generate the image. The higher the number of steps, the more detailed the image will be. However, increasing the number of steps will also increase the time it takes to generate the image and may not always result in a better image (some [schedulers](https://docs.runware.ai/en/image-inference/api-reference#request-scheduler) work differently). When using your own models you can specify a new default value for the number of steps. Defaults to `20`.", - min: 1, - max: 100, - optional: true, - }, - scheduler: { - type: "string", - label: "Scheduler", - description: "An scheduler is a component that manages the inference process. Different schedulers can be used to achieve different results like more detailed images, faster inference, or more accurate results. The default scheduler is the one that the model was trained with, but you can choose a different one to get different results. Schedulers are explained in more detail in the [Schedulers page](https://docs.runware.ai/en/image-inference/schedulers).", - optional: true, - }, - seed: { + inputImage: { type: "string", - label: "Seed", - description: "A seed is a value used to randomize the image generation. If you want to make images reproducible (generate the same image multiple times), you can use the same seed value. When requesting multiple images with the same seed, the seed will be incremented by 1 (+1) for each image generated. Min: `0` Max: `9223372036854776000`. Defaults to `Random`.", - optional: true, - }, - numberResults: { - type: "integer", - label: "Number Of Results", - description: "The number of images to generate from the specified prompt. If **Seed** is set, it will be incremented by 1 (+1) for each image generated.", - optional: true, + label: "Input Image", + description: "Specifies the input image to be processed. The image can be specified in one of the following formats:\n - An UUID v4 string of a [previously uploaded image](https://docs.runware.ai/en/getting-started/image-upload) or a [generated image](https://docs.runware.ai/en/image-inference/api-reference).\n - A data URI string representing the image. The data URI must be in the format `data:;base64,` followed by the base64-encoded image. For example: `data:image/png;base64,iVBORw0KGgo...`.\n - A base64 encoded image without the data URI prefix. For example: `iVBORw0KGgo...`.\n - A URL pointing to the image. The image must be accessible publicly.\nSupported formats are: PNG, JPG and WEBP.", }, }, methods: { diff --git a/components/salesforce_rest_api/package.json b/components/salesforce_rest_api/package.json index ccbf2505e532a..c45dc0f43c0be 100644 --- a/components/salesforce_rest_api/package.json +++ b/components/salesforce_rest_api/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/salesforce_rest_api", - "version": "1.3.0", + "version": "1.3.1", "description": "Pipedream Salesforce (REST API) Components", "main": "salesforce_rest_api.app.mjs", "keywords": [ diff --git a/components/salesforce_rest_api/sources/new-record-instant/new-record-instant.mjs b/components/salesforce_rest_api/sources/new-record-instant/new-record-instant.mjs index b512834263831..382ba6a826ba5 100644 --- a/components/salesforce_rest_api/sources/new-record-instant/new-record-instant.mjs +++ b/components/salesforce_rest_api/sources/new-record-instant/new-record-instant.mjs @@ -7,7 +7,21 @@ export default { name: "New Record (Instant, of Selectable Type)", key: "salesforce_rest_api-new-record-instant", description: "Emit new event when a record of the selected object type is created. [See the documentation](https://sforce.co/3yPSJZy)", - version: "0.1.0", + version: "0.2.0", + props: { + ...common.props, + fieldsToObtain: { + propDefinition: [ + common.props.salesforce, + "fieldsToObtain", + (c) => ({ + objType: c.objectType, + }), + ], + optional: true, + description: "Select the field(s) to be retrieved for the records. Only applicable if the source is running on a timer. If running on a webhook, or if not specified, all fields will be retrieved.", + }, + }, hooks: { ...common.hooks, async deploy() { @@ -40,7 +54,7 @@ export default { Id: id, } = item; const entityType = startCase(objectType); - const summary = `New ${entityType} created: ${name}`; + const summary = `New ${entityType} created: ${name ?? id}`; const ts = Date.parse(createdDate); return { id, @@ -57,7 +71,7 @@ export default { [nameField]: name, } = newObject; const entityType = startCase(this.objectType).toLowerCase(); - const summary = `New ${entityType} created: ${name}`; + const summary = `New ${entityType} created: ${name ?? id}`; const ts = Date.parse(createdDate); return { id, @@ -118,8 +132,11 @@ export default { setObjectTypeColumns, } = this; - const { fields } = await getObjectTypeDescription(objectType); - const columns = fields.map(({ name }) => name); + let columns = this.fieldsToObtain; + if (!columns?.length) { + const { fields } = await getObjectTypeDescription(objectType); + columns = fields.map(({ name }) => name); + } setObjectTypeColumns(columns); }, diff --git a/components/sdk/package.json b/components/sdk/package.json new file mode 100644 index 0000000000000..f8ba24fbcd236 --- /dev/null +++ b/components/sdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pipedream/sdk", + "version": "0.1.2", + "description": "Pipedream SDK Components", + "main": "sdk.app.mjs", + "keywords": [ + "pipedream", + "sdk", + "integrations", + "api" + ], + "homepage": "https://pipedream.com/docs/connect/workflows", + "author": "Pipedream (https://pipedream.com/)", + "private": true +} diff --git a/components/sdk/sdk.app.mjs b/components/sdk/sdk.app.mjs new file mode 100644 index 0000000000000..5673b6dba3d83 --- /dev/null +++ b/components/sdk/sdk.app.mjs @@ -0,0 +1,5 @@ +export default { + type: "app", + app: "sdk", + propDefinitions: {}, +}; diff --git a/components/sdk/sources/nextjs-event-received/nextjs-event-received.mjs b/components/sdk/sources/nextjs-event-received/nextjs-event-received.mjs new file mode 100644 index 0000000000000..132f58233ecc3 --- /dev/null +++ b/components/sdk/sources/nextjs-event-received/nextjs-event-received.mjs @@ -0,0 +1,16 @@ +/* eslint-disable pipedream/source-description */ +/* eslint-disable pipedream/source-name */ +import sdk from "../../sdk.app.mjs"; + +export default { + name: "Next.js", + version: "0.0.2", + key: "sdk-nextjs-event-received", + description: "Emit a new event via the Next.js Pipedream SDK.", + props: { + sdk, + }, + type: "source", + methods: {}, + async run() {}, +}; diff --git a/components/sdk/sources/nodejs-event-received/nodejs-event-received.mjs b/components/sdk/sources/nodejs-event-received/nodejs-event-received.mjs new file mode 100644 index 0000000000000..623d18cfa4ebb --- /dev/null +++ b/components/sdk/sources/nodejs-event-received/nodejs-event-received.mjs @@ -0,0 +1,16 @@ +/* eslint-disable pipedream/source-description */ +/* eslint-disable pipedream/source-name */ +import sdk from "../../sdk.app.mjs"; + +export default { + name: "Node.js", + version: "0.0.2", + key: "sdk-nodejs-event-received", + description: "Emit a new event via the Node.js Pipedream SDK.", + props: { + sdk, + }, + type: "source", + methods: {}, + async run() {}, +}; diff --git a/components/sdk/sources/nuxtjs-event-received/nuxtjs-event-received.mjs b/components/sdk/sources/nuxtjs-event-received/nuxtjs-event-received.mjs new file mode 100644 index 0000000000000..a9283baff04d7 --- /dev/null +++ b/components/sdk/sources/nuxtjs-event-received/nuxtjs-event-received.mjs @@ -0,0 +1,16 @@ +/* eslint-disable pipedream/source-description */ +/* eslint-disable pipedream/source-name */ +import sdk from "../../sdk.app.mjs"; + +export default { + name: "NuxtJS", + version: "0.0.2", + key: "sdk-nuxtjs-event-received", + description: "Emit a new event via the Nuxt.js Pipedream SDK.", + props: { + sdk, + }, + type: "source", + methods: {}, + async run() {}, +}; diff --git a/components/sdk/sources/python-event-received/python-event-received.mjs b/components/sdk/sources/python-event-received/python-event-received.mjs new file mode 100644 index 0000000000000..b1e168bbde536 --- /dev/null +++ b/components/sdk/sources/python-event-received/python-event-received.mjs @@ -0,0 +1,16 @@ +/* eslint-disable pipedream/source-description */ +/* eslint-disable pipedream/source-name */ +import sdk from "../../sdk.app.mjs"; + +export default { + name: "Python", + version: "0.0.2", + key: "sdk-python-event-received", + description: "Emit a new event via the Python Pipedream SDK.", + props: { + sdk, + }, + type: "source", + methods: {}, + async run() {}, +}; diff --git a/components/sdk/sources/reactjs-event-received/reactjs-event-received.mjs b/components/sdk/sources/reactjs-event-received/reactjs-event-received.mjs new file mode 100644 index 0000000000000..1aa3d1b69b4dc --- /dev/null +++ b/components/sdk/sources/reactjs-event-received/reactjs-event-received.mjs @@ -0,0 +1,16 @@ +/* eslint-disable pipedream/source-description */ +/* eslint-disable pipedream/source-name */ +import sdk from "../../sdk.app.mjs"; + +export default { + name: "React.js", + version: "0.0.2", + key: "sdk-reactjs-event-received", + description: "Emit a new event via the React.js Pipedream SDK.", + props: { + sdk, + }, + type: "source", + methods: {}, + async run() {}, +}; diff --git a/components/sdk/sources/vue-event-received/vue-event-received.mjs b/components/sdk/sources/vue-event-received/vue-event-received.mjs new file mode 100644 index 0000000000000..005e51f6e87ed --- /dev/null +++ b/components/sdk/sources/vue-event-received/vue-event-received.mjs @@ -0,0 +1,16 @@ +/* eslint-disable pipedream/source-description */ +/* eslint-disable pipedream/source-name */ +import sdk from "../../sdk.app.mjs"; + +export default { + name: "Vue", + version: "0.0.2", + key: "sdk-vue-event-received", + description: "Emit a new event via the Vue Pipedream SDK.", + props: { + sdk, + }, + type: "source", + methods: {}, + async run() {}, +}; diff --git a/components/seqera/actions/create-action/create-action.mjs b/components/seqera/actions/create-action/create-action.mjs index e87320d3ec6ec..c28c9f15a7876 100644 --- a/components/seqera/actions/create-action/create-action.mjs +++ b/components/seqera/actions/create-action/create-action.mjs @@ -6,7 +6,7 @@ export default { key: "seqera-create-action", name: "Create Pipeline Action", description: "Creates a new pipeline action in Seqera. [See the documentation](https://docs.seqera.io/platform/23.3.0/api/overview)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { app, diff --git a/components/seqera/actions/create-compute-environment/create-compute-environment.mjs b/components/seqera/actions/create-compute-environment/create-compute-environment.mjs index 66de51b4a7c74..7947f43f6b98f 100644 --- a/components/seqera/actions/create-compute-environment/create-compute-environment.mjs +++ b/components/seqera/actions/create-compute-environment/create-compute-environment.mjs @@ -5,7 +5,7 @@ export default { key: "seqera-create-compute-environment", name: "Create Compute Environment", description: "Creates a new compute environment in Seqera Tower. [See the documentation](https://docs.seqera.io/platform/23.3.0/api/overview)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { app, diff --git a/components/seqera/actions/create-pipeline/create-pipeline.mjs b/components/seqera/actions/create-pipeline/create-pipeline.mjs index bc9acb85fa5e5..b9dc787f2308e 100644 --- a/components/seqera/actions/create-pipeline/create-pipeline.mjs +++ b/components/seqera/actions/create-pipeline/create-pipeline.mjs @@ -5,7 +5,7 @@ export default { key: "seqera-create-pipeline", name: "Create Pipeline", description: "Creates a new pipeline in a user context. [See the documentation](https://docs.seqera.io/platform/23.3.0/api/overview)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { app, diff --git a/components/seqera/package.json b/components/seqera/package.json index adf038d81abe6..361247492f40b 100644 --- a/components/seqera/package.json +++ b/components/seqera/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/seqera", - "version": "0.1.0", + "version": "0.1.1", "description": "Pipedream Seqera Components", "main": "seqera.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } } diff --git a/components/seqera/seqera.app.mjs b/components/seqera/seqera.app.mjs index 7146bb522b90c..03f2781fa6901 100644 --- a/components/seqera/seqera.app.mjs +++ b/components/seqera/seqera.app.mjs @@ -165,9 +165,11 @@ export default { ...args, }); }, - listRuns(args = {}) { + listWorkflows({ + workspaceId, ...args + }) { return this._makeRequest({ - path: "/ga4gh/wes/v1/runs", + path: `/workflow?workspaceId=${workspaceId}`, ...args, }); }, @@ -177,18 +179,18 @@ export default { resourceName, max = constants.DEFAULT_MAX, }) { - let nextPageToken; + const params = { + ...resourcesFnArgs?.params, + max: constants.DEFAULT_LIMIT, + offset: 0, + }; let resourcesCount = 0; while (true) { const response = await resourcesFn({ ...resourcesFnArgs, - params: { - ...resourcesFnArgs?.params, - page_size: constants.DEFAULT_LIMIT, - page_token: nextPageToken, - }, + params, }); const nextResources = resourceName && response[resourceName] || response; @@ -207,12 +209,12 @@ export default { } } - if (Number(response.next_page_token) === 0) { + if (resourcesCount >= response.totalSize) { console.log("No more pages found"); return; } - nextPageToken = response.next_page_token; + params.offset += params.max; } }, paginate(args = {}) { diff --git a/components/seqera/sources/new-run-created/new-run-created.mjs b/components/seqera/sources/new-run-created/new-run-created.mjs index 8e95d55f3a6de..97b55910edce7 100644 --- a/components/seqera/sources/new-run-created/new-run-created.mjs +++ b/components/seqera/sources/new-run-created/new-run-created.mjs @@ -5,7 +5,7 @@ export default { key: "seqera-new-run-created", name: "New Run Created", description: "Emit new event when a new run is created in Seqera. [See the documentation](https://docs.seqera.io/platform/23.3.0/api/overview)", - version: "0.0.1", + version: "0.0.2", type: "source", dedupe: "unique", props: { @@ -18,22 +18,31 @@ export default { intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, }, }, + workspaceId: { + propDefinition: [ + app, + "workspaceId", + ], + optional: false, + }, }, methods: { getResourceName() { - return "runs"; + return "workflows"; }, getResourcesFn() { - return this.app.listRuns; + return this.app.listWorkflows; }, getResourcesFnArgs() { - return; + return { + workspaceId: this.workspaceId, + }; }, - generateMeta(resource) { + generateMeta({ workflow }) { return { - id: resource.run_id, - summary: `New Run: ${resource.run_id}`, - ts: Date.now(), + id: workflow.id, + summary: `New Run: ${workflow.id}`, + ts: Date.parse(workflow.dateCreated), }; }, processResource(resource) { diff --git a/components/skyvern/actions/create-run-task/create-run-task.mjs b/components/skyvern/actions/create-run-task/create-run-task.mjs new file mode 100644 index 0000000000000..c8618f6846393 --- /dev/null +++ b/components/skyvern/actions/create-run-task/create-run-task.mjs @@ -0,0 +1,80 @@ +import { parseObject } from "../../common/utils.mjs"; +import skyvern from "../../skyvern.app.mjs"; + +export default { + key: "skyvern-create-run-task", + name: "Create and Run Task", + description: "Create a new task and run it instantly in Skyvern. Useful for one-off automations. [See the documentation](https://docs.skyvern.com/)", + version: "0.0.1", + type: "action", + props: { + skyvern, + url: { + type: "string", + label: "URL", + description: "It must be a http or https URL.", + }, + navigationGoal: { + type: "string", + label: "Navigation Goal", + description: "The prompt that tells the agent what the user-facing goal is. This is the guiding light for the LLM as it navigates a particular website / sitemap to achieve this specified goal.", + optional: true, + }, + dataExtractionGoal: { + type: "string", + label: "Data Extraction Goal", + description: "The prompt that instructs the agent to extract information once the agent has achieved its **User Goal**.", + optional: true, + }, + navigationPayload: { + type: "object", + label: "Navigation Payload", + description: "JSON-formatted payload with any \"facts\" or information that would help the agent perform its job. In the case of navigating an insurance quote, this payload would include any user information to help fill out the insurance flow such as date of birth, or age they got their license, and so on. This can include nested information, and the formatting isn't validated.", + optional: true, + }, + webhookCallbackUrl: { + propDefinition: [ + skyvern, + "webhookCallbackUrl", + ], + description: "The callback URL once our system is finished processing this async task.", + optional: true, + }, + extractedInformationSchema: { + type: "object", + label: "Extracted Information Schema", + description: "Used to enforce a JSON schema spec to be enforced in the data_extraction_goal. Similar to [https://json-schema.org/](https://json-schema.org/) definition.", + optional: true, + }, + totpVerificationUrl: { + type: "string", + label: "TOTP Verification URL", + description: "The URL of your TOTP endpoint. If this field is provided, Skyvern will call the URL to fetch the TOTP/2FA/MFA code when needed.", + optional: true, + }, + totpIdentifier: { + type: "string", + label: "TOTP Identifier", + description: "The email address or the phone number which receives the TOTP/2FA/MFA code. If this field is provided, Skyvern will fetch the code that is pushed to [Skyvern's TOTP API](https://docs.skyvern.com/running-tasks/advanced-features#push-code-to-skyvern).", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.skyvern.createAndRunTask({ + $, + data: { + url: this.url, + navigation_goal: this.navigationGoal, + data_extraction_goal: this.dataExtractionGoal, + navigation_payload: parseObject(this.navigationPayload), + webhook_callback_url: this.webhookCallbackUrl, + proxyLocation: "RESIDENTIAL", + extracted_information_schema: parseObject(this.extractedInformationSchema), + totp_verification_url: this.totpVerificationUrl, + totp_identifier: this.totpIdentifier, + }, + }); + $.export("$summary", `Created and ran task with ID ${response.task_id}`); + return response; + }, +}; diff --git a/components/skyvern/actions/get-workflow/get-workflow.mjs b/components/skyvern/actions/get-workflow/get-workflow.mjs new file mode 100644 index 0000000000000..530fde4c96ed6 --- /dev/null +++ b/components/skyvern/actions/get-workflow/get-workflow.mjs @@ -0,0 +1,26 @@ +import skyvern from "../../skyvern.app.mjs"; + +export default { + key: "skyvern-get-workflow", + name: "Get Workflow Run Details", + description: "Retrieve details of runs of a specific Skyvern workflow. Useful for checking the status and result of a run. [See the documentation](https://docs.skyvern.com/workflows/getting-workflows)", + version: "0.0.1", + type: "action", + props: { + skyvern, + workflowId: { + propDefinition: [ + skyvern, + "workflowId", + ], + }, + }, + async run({ $ }) { + const response = await this.skyvern.getWorkflowRunDetails({ + $, + workflowId: this.workflowId, + }); + $.export("$summary", `Successfully retrieved run details for workflow: ${this.workflowId}`); + return response; + }, +}; diff --git a/components/skyvern/actions/run-workflow/run-workflow.mjs b/components/skyvern/actions/run-workflow/run-workflow.mjs new file mode 100644 index 0000000000000..f1bfe4fbc26bd --- /dev/null +++ b/components/skyvern/actions/run-workflow/run-workflow.mjs @@ -0,0 +1,46 @@ +import { parseObject } from "../../common/utils.mjs"; +import skyvern from "../../skyvern.app.mjs"; + +export default { + key: "skyvern-run-workflow", + name: "Run Workflow", + description: "Trigger a predefined workflow in Skyvern, allowing the execution of complex routines from Pipedream. [See the documentation](https://docs.skyvern.com/workflows/running-workflows)", + version: "0.0.1", + type: "action", + props: { + skyvern, + workflowId: { + propDefinition: [ + skyvern, + "workflowId", + ], + }, + data: { + type: "object", + label: "Data", + description: "The data field is used to pass in required and optional parameters that a workflow accepts. [See the documentation](https://docs.skyvern.com/workflows/running-workflows) for further information.", + optional: true, + }, + webhookCallbackUrl: { + propDefinition: [ + skyvern, + "webhookCallbackUrl", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.skyvern.triggerWorkflow({ + $, + workflowId: this.workflowId, + data: { + data: parseObject(this.data), + proxyLocation: "RESIDENTIAL", + webhookCallbackUrl: this.webhookCallbackUrl, + }, + }); + + $.export("$summary", `Successfully triggered workflow with ID ${response.workflow_id}`); + return response; + }, +}; diff --git a/components/skyvern/common/utils.mjs b/components/skyvern/common/utils.mjs new file mode 100644 index 0000000000000..dcc9cc61f6f41 --- /dev/null +++ b/components/skyvern/common/utils.mjs @@ -0,0 +1,24 @@ +export const parseObject = (obj) => { + if (!obj) return undefined; + + if (Array.isArray(obj)) { + return obj.map((item) => { + if (typeof item === "string") { + try { + return JSON.parse(item); + } catch (e) { + return item; + } + } + return item; + }); + } + if (typeof obj === "string") { + try { + return JSON.parse(obj); + } catch (e) { + return obj; + } + } + return obj; +}; diff --git a/components/skyvern/package.json b/components/skyvern/package.json new file mode 100644 index 0000000000000..62d24d3d820c7 --- /dev/null +++ b/components/skyvern/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/skyvern", + "version": "0.1.0", + "description": "Pipedream Skyvern Components", + "main": "skyvern.app.mjs", + "keywords": [ + "pipedream", + "skyvern" + ], + "homepage": "https://pipedream.com/apps/skyvern", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" + } +} diff --git a/components/skyvern/skyvern.app.mjs b/components/skyvern/skyvern.app.mjs new file mode 100644 index 0000000000000..98db90739188d --- /dev/null +++ b/components/skyvern/skyvern.app.mjs @@ -0,0 +1,112 @@ +import { axios } from "@pipedream/platform"; + +export default { + type: "app", + app: "skyvern", + propDefinitions: { + workflowId: { + type: "string", + label: "Workflow ID", + description: "The unique identifier for a workflow", + async options({ page }) { + const workflows = await this.listWorkflows({ + params: { + page: page + 1, + }, + }); + return workflows.map(({ + workflow_permanent_id: value, title: label, + }) => ({ + label, + value, + })); + }, + }, + webhookCallbackUrl: { + type: "string", + label: "Webhook Callback URL", + description: "URL where system will send callback once it finishes executing the workflow run.", + }, + }, + methods: { + _baseUrl() { + return "https://api.skyvern.com/api/v1"; + }, + _headers() { + return { + "x-api-key": this.$auth.api_key, + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + headers: this._headers(), + ...opts, + }); + }, + listWorkflows({ + params, ...opts + }) { + return this._makeRequest({ + path: "/workflows", + params: { + ...params, + only_workflows: true, + }, + ...opts, + }); + }, + getWorkflowRunDetails({ + workflowId, ...opts + }) { + const path = `/workflows/${workflowId}/runs`; + return this._makeRequest({ + path, + ...opts, + }); + }, + triggerWorkflow({ + workflowId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/workflows/${workflowId}/run`, + ...opts, + }); + }, + createAndRunTask(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/tasks", + ...opts, + }); + }, + async *paginate({ + fn, params = {}, maxResults = null, ...opts + }) { + let hasMore = false; + let count = 0; + let page = 0; + + do { + params.page = ++page; + const data = await fn({ + params, + ...opts, + }); + for (const d of data) { + yield d; + + if (maxResults && ++count === maxResults) { + return count; + } + } + + hasMore = data.length; + + } while (hasMore); + }, + }, +}; diff --git a/components/skyvern/sources/new-or-updated-workflow/new-or-updated-workflow.mjs b/components/skyvern/sources/new-or-updated-workflow/new-or-updated-workflow.mjs new file mode 100644 index 0000000000000..686b81f5ebebb --- /dev/null +++ b/components/skyvern/sources/new-or-updated-workflow/new-or-updated-workflow.mjs @@ -0,0 +1,67 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import skyvern from "../../skyvern.app.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + key: "skyvern-new-or-updated-workflow", + name: "New or Updated Workflow", + description: "Emit new event when a workflow is created or updated in Skyvern.", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + skyvern, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + _getLastDate() { + return this.db.get("lastDate") || 0; + }, + _setLastDate(lastDate) { + this.db.set("lastDate", lastDate); + }, + async emitEvent(maxResults = false) { + const lastDate = this._getLastDate(); + + const response = this.skyvern.paginate({ + fn: this.skyvern.listWorkflows, + maxResults, + }); + + let responseArray = []; + for await (const item of response) { + if (Date.parse(item.modified_at) <= lastDate) break; + responseArray.push(item); + } + + if (responseArray.length) { + this._setLastDate(responseArray[0].modified_at); + } + + for (const item of responseArray.reverse()) { + this.$emit(item, { + id: `${item.modified_at}-${item.workflow_permanent_id}`, + summary: `New Workflow ${item.version === 1 + ? "Created" + : "Updated"}: ${item.title}`, + ts: Date.parse(item.modified_at), + }); + } + }, + }, + hooks: { + async deploy() { + await this.emitEvent(25); + }, + }, + async run() { + await this.emitEvent(); + }, + sampleEmit, +}; diff --git a/components/skyvern/sources/new-or-updated-workflow/test-event.mjs b/components/skyvern/sources/new-or-updated-workflow/test-event.mjs new file mode 100644 index 0000000000000..ef4fdef8db798 --- /dev/null +++ b/components/skyvern/sources/new-or-updated-workflow/test-event.mjs @@ -0,0 +1,27 @@ +export default { + "workflow_id": "string", + "organization_id": "string", + "title": "string", + "workflow_permanent_id": "string", + "version": "integer", + "is_saved_task": "boolean", + "description": "string", + "proxy_location": "string | null", + "webhook_callback_url": "string | null", + "totp_verification_url": "string | null", + "workflow_definition": { + "parameters": [ + { + "parameter_type": "string", + "key": "string", + "description": "string | null" + } + ], + "blocks": [ + { + "label": "string", + "block_type": "string" + } + ] + } +} \ No newline at end of file diff --git a/components/slack/actions/add-emoji-reaction/add-emoji-reaction.mjs b/components/slack/actions/add-emoji-reaction/add-emoji-reaction.mjs index ef972dec1009f..d0f3d393663fd 100644 --- a/components/slack/actions/add-emoji-reaction/add-emoji-reaction.mjs +++ b/components/slack/actions/add-emoji-reaction/add-emoji-reaction.mjs @@ -4,7 +4,7 @@ export default { key: "slack-add-emoji-reaction", name: "Add Emoji Reaction", description: "Add an emoji reaction to a message. [See the documentation](https://api.slack.com/methods/reactions.add)", - version: "0.0.11", + version: "0.0.12", type: "action", props: { slack, diff --git a/components/slack/actions/archive-channel/archive-channel.mjs b/components/slack/actions/archive-channel/archive-channel.mjs index 42a532d6806e3..29b742d2c0f5b 100644 --- a/components/slack/actions/archive-channel/archive-channel.mjs +++ b/components/slack/actions/archive-channel/archive-channel.mjs @@ -5,7 +5,7 @@ export default { key: "slack-archive-channel", name: "Archive Channel", description: "Archive a channel. [See the documentation](https://api.slack.com/methods/conversations.archive)", - version: "0.0.19", + version: "0.0.20", type: "action", props: { slack, diff --git a/components/slack/actions/common/send-message.mjs b/components/slack/actions/common/send-message.mjs index c4783844dbcae..7d5cb5d45cd94 100644 --- a/components/slack/actions/common/send-message.mjs +++ b/components/slack/actions/common/send-message.mjs @@ -170,6 +170,9 @@ export default { }, }; }, + getChannelId() { + return this.conversation ?? this.reply_channel; + }, }, async run({ $ }) { let blocks = this.blocks; @@ -207,7 +210,7 @@ export default { const obj = { text: this.text, - channel: this.conversation ?? this.reply_channel, + channel: await this.getChannelId(), attachments: this.attachments, unfurl_links: this.unfurl_links, unfurl_media: this.unfurl_media, diff --git a/components/slack/actions/create-channel/create-channel.mjs b/components/slack/actions/create-channel/create-channel.mjs index dac0324e91166..fbf6a8c7fd586 100644 --- a/components/slack/actions/create-channel/create-channel.mjs +++ b/components/slack/actions/create-channel/create-channel.mjs @@ -4,7 +4,7 @@ export default { key: "slack-create-channel", name: "Create a Channel", description: "Create a new channel. [See the documentation](https://api.slack.com/methods/conversations.create)", - version: "0.0.20", + version: "0.0.21", type: "action", props: { slack, diff --git a/components/slack/actions/create-reminder/create-reminder.mjs b/components/slack/actions/create-reminder/create-reminder.mjs index fe6a0c43d43ef..4b8ae6379cfc1 100644 --- a/components/slack/actions/create-reminder/create-reminder.mjs +++ b/components/slack/actions/create-reminder/create-reminder.mjs @@ -4,7 +4,7 @@ export default { key: "slack-create-reminder", name: "Create Reminder", description: "Create a reminder. [See the documentation](https://api.slack.com/methods/reminders.add)", - version: "0.0.20", + version: "0.0.21", type: "action", props: { slack, diff --git a/components/slack/actions/delete-file/delete-file.mjs b/components/slack/actions/delete-file/delete-file.mjs index 5941f5b3eeaaa..591abf608f17c 100644 --- a/components/slack/actions/delete-file/delete-file.mjs +++ b/components/slack/actions/delete-file/delete-file.mjs @@ -4,7 +4,7 @@ export default { key: "slack-delete-file", name: "Delete File", description: "Delete a file. [See the documentation](https://api.slack.com/methods/files.delete)", - version: "0.0.19", + version: "0.0.20", type: "action", props: { slack, diff --git a/components/slack/actions/delete-message/delete-message.mjs b/components/slack/actions/delete-message/delete-message.mjs index cb186c044c644..192788f295765 100644 --- a/components/slack/actions/delete-message/delete-message.mjs +++ b/components/slack/actions/delete-message/delete-message.mjs @@ -4,7 +4,7 @@ export default { key: "slack-delete-message", name: "Delete Message", description: "Delete a message. [See the documentation](https://api.slack.com/methods/chat.delete)", - version: "0.0.19", + version: "0.0.20", type: "action", props: { slack, diff --git a/components/slack/actions/find-message/find-message.mjs b/components/slack/actions/find-message/find-message.mjs index 81a592114f643..6d9998b414a35 100644 --- a/components/slack/actions/find-message/find-message.mjs +++ b/components/slack/actions/find-message/find-message.mjs @@ -4,7 +4,7 @@ export default { key: "slack-find-message", name: "Find Message", description: "Find a Slack message. [See the documentation](https://api.slack.com/methods/search.messages)", - version: "0.0.19", + version: "0.0.20", type: "action", props: { slack, diff --git a/components/slack/actions/find-user-by-email/find-user-by-email.mjs b/components/slack/actions/find-user-by-email/find-user-by-email.mjs index 82faefe5497f2..fdef842f377d5 100644 --- a/components/slack/actions/find-user-by-email/find-user-by-email.mjs +++ b/components/slack/actions/find-user-by-email/find-user-by-email.mjs @@ -4,7 +4,7 @@ export default { key: "slack-find-user-by-email", name: "Find User by Email", description: "Find a user by matching against their email. [See the documentation](https://api.slack.com/methods/users.lookupByEmail)", - version: "0.0.19", + version: "0.0.20", type: "action", props: { slack, diff --git a/components/slack/actions/get-file/get-file.mjs b/components/slack/actions/get-file/get-file.mjs index 904968c5069ec..9cd5b9275e017 100644 --- a/components/slack/actions/get-file/get-file.mjs +++ b/components/slack/actions/get-file/get-file.mjs @@ -4,7 +4,7 @@ export default { key: "slack-get-file", name: "Get File", description: "Return information about a file. [See the documentation](https://api.slack.com/methods/files.info)", - version: "0.0.19", + version: "0.0.20", type: "action", props: { slack, diff --git a/components/slack/actions/invite-user-to-channel/invite-user-to-channel.mjs b/components/slack/actions/invite-user-to-channel/invite-user-to-channel.mjs index 8110eced16883..c6a86099107a8 100644 --- a/components/slack/actions/invite-user-to-channel/invite-user-to-channel.mjs +++ b/components/slack/actions/invite-user-to-channel/invite-user-to-channel.mjs @@ -4,7 +4,7 @@ export default { key: "slack-invite-user-to-channel", name: "Invite User to Channel", description: "Invite a user to an existing channel. [See the documentation](https://api.slack.com/methods/conversations.invite)", - version: "0.0.19", + version: "0.0.20", type: "action", props: { slack, diff --git a/components/slack/actions/kick-user/kick-user.mjs b/components/slack/actions/kick-user/kick-user.mjs index f6f7c35584b94..c4397ad8dd0c4 100644 --- a/components/slack/actions/kick-user/kick-user.mjs +++ b/components/slack/actions/kick-user/kick-user.mjs @@ -5,7 +5,7 @@ export default { key: "slack-kick-user", name: "Kick User", description: "Remove a user from a conversation. [See the documentation](https://api.slack.com/methods/conversations.kick)", - version: "0.0.19", + version: "0.0.20", type: "action", props: { slack, diff --git a/components/slack/actions/list-channels/list-channels.mjs b/components/slack/actions/list-channels/list-channels.mjs index 6566a6a91ed4a..1e47f2d3b8ea9 100644 --- a/components/slack/actions/list-channels/list-channels.mjs +++ b/components/slack/actions/list-channels/list-channels.mjs @@ -4,7 +4,7 @@ export default { key: "slack-list-channels", name: "List Channels", description: "Return a list of all channels in a workspace. [See the documentation](https://api.slack.com/methods/conversations.list)", - version: "0.0.19", + version: "0.0.20", type: "action", props: { slack, diff --git a/components/slack/actions/list-files/list-files.mjs b/components/slack/actions/list-files/list-files.mjs index b83ba79d90beb..c66cc0ea1bbb1 100644 --- a/components/slack/actions/list-files/list-files.mjs +++ b/components/slack/actions/list-files/list-files.mjs @@ -4,7 +4,7 @@ export default { key: "slack-list-files", name: "List Files", description: "Return a list of files within a team. [See the documentation](https://api.slack.com/methods/files.list)", - version: "0.0.47", + version: "0.0.48", type: "action", props: { slack, diff --git a/components/slack/actions/list-group-members/list-group-members.mjs b/components/slack/actions/list-group-members/list-group-members.mjs index de8f9a37dc65b..4eb7c72cc6a39 100644 --- a/components/slack/actions/list-group-members/list-group-members.mjs +++ b/components/slack/actions/list-group-members/list-group-members.mjs @@ -4,7 +4,7 @@ export default { key: "slack-list-group-members", name: "List Group Members", description: "List all users in a User Group. [See the documentation](https://api.slack.com/methods/usergroups.users.list)", - version: "0.0.4", + version: "0.0.5", type: "action", props: { slack, diff --git a/components/slack/actions/list-members-in-channel/list-members-in-channel.mjs b/components/slack/actions/list-members-in-channel/list-members-in-channel.mjs index 2b32c457a8fbb..87075618c8eb4 100644 --- a/components/slack/actions/list-members-in-channel/list-members-in-channel.mjs +++ b/components/slack/actions/list-members-in-channel/list-members-in-channel.mjs @@ -4,7 +4,7 @@ export default { key: "slack-list-members-in-channel", name: "List Members in Channel", description: "Retrieve members of a channel. [See the documentation](https://api.slack.com/methods/conversations.members)", - version: "0.0.19", + version: "0.0.20", type: "action", props: { slack, diff --git a/components/slack/actions/list-replies/list-replies.mjs b/components/slack/actions/list-replies/list-replies.mjs index 0663f8066984d..82824e922b423 100644 --- a/components/slack/actions/list-replies/list-replies.mjs +++ b/components/slack/actions/list-replies/list-replies.mjs @@ -4,7 +4,7 @@ export default { key: "slack-list-replies", name: "List Replies", description: "Retrieve a thread of messages posted to a conversation. [See the documentation](https://api.slack.com/methods/conversations.replies)", - version: "0.0.19", + version: "0.0.20", type: "action", props: { slack, diff --git a/components/slack/actions/list-users/list-users.mjs b/components/slack/actions/list-users/list-users.mjs index 539cd89942a71..ee58b07aad8a8 100644 --- a/components/slack/actions/list-users/list-users.mjs +++ b/components/slack/actions/list-users/list-users.mjs @@ -4,7 +4,7 @@ export default { key: "slack-list-users", name: "List Users", description: "Return a list of all users in a workspace. [See the documentation](https://api.slack.com/methods/users.list)", - version: "0.0.19", + version: "0.0.20", type: "action", props: { slack, diff --git a/components/slack/actions/reply-to-a-message/reply-to-a-message.mjs b/components/slack/actions/reply-to-a-message/reply-to-a-message.mjs index 0cacf21c7f9eb..5964057c0bc8c 100644 --- a/components/slack/actions/reply-to-a-message/reply-to-a-message.mjs +++ b/components/slack/actions/reply-to-a-message/reply-to-a-message.mjs @@ -6,7 +6,7 @@ export default { key: "slack-reply-to-a-message", name: "Reply to a Message Thread", description: "Send a message as a threaded reply. See [postMessage](https://api.slack.com/methods/chat.postMessage) or [scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) docs here", - version: "0.1.24", + version: "0.1.25", type: "action", props: { slack: common.props.slack, diff --git a/components/slack/actions/send-block-kit-message/send-block-kit-message.mjs b/components/slack/actions/send-block-kit-message/send-block-kit-message.mjs new file mode 100644 index 0000000000000..f4d114f786e6c --- /dev/null +++ b/components/slack/actions/send-block-kit-message/send-block-kit-message.mjs @@ -0,0 +1,43 @@ +import buildBlocks from "../common/build-blocks.mjs"; +import common from "../common/send-message.mjs"; + +export default { + ...common, + ...buildBlocks, + key: "slack-send-block-kit-message", + name: "Build and Send a Block Kit Message", + description: "Configure custom blocks and send to a channel, group, or user. [See the documentation](https://api.slack.com/tools/block-kit-builder).", + version: "0.4.1", + type: "action", + props: { + slack: common.props.slack, + conversation: { + propDefinition: [ + common.props.slack, + "conversation", + ], + }, + text: { + type: "string", + label: "Notification Text", + description: "Optionally provide a string for Slack to display as the new message notification (if you do not provide this, notification will be blank).", + optional: true, + }, + ...common.props, + ...buildBlocks.props, + }, + methods: { + ...common.methods, + ...buildBlocks.methods, + async getGeneratedBlocks() { + return await buildBlocks.run.call(this); // call buildBlocks.run with the current context + }, + }, + async run({ $ }) { + this.blocks = await this.getGeneratedBlocks(); // set the blocks prop for common.run to use + const resp = await common.run.call(this, { + $, + }); // call common.run with the current context + return resp; + }, +}; diff --git a/components/slack/actions/send-large-message/send-large-message.mjs b/components/slack/actions/send-large-message/send-large-message.mjs index cb50844645c21..3ccfaac51e912 100644 --- a/components/slack/actions/send-large-message/send-large-message.mjs +++ b/components/slack/actions/send-large-message/send-large-message.mjs @@ -5,7 +5,7 @@ export default { key: "slack-send-large-message", name: "Send a Large Message (3000+ characters)", description: "Send a large message (more than 3000 characters) to a channel, group or user. See [postMessage](https://api.slack.com/methods/chat.postMessage) or [scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) docs here", - version: "0.0.19", + version: "0.0.20", type: "action", props: { slack: common.props.slack, diff --git a/components/slack/actions/send-message-advanced/send-message-advanced.mjs b/components/slack/actions/send-message-advanced/send-message-advanced.mjs index af140f26bd9db..3118834c4b98b 100644 --- a/components/slack/actions/send-message-advanced/send-message-advanced.mjs +++ b/components/slack/actions/send-message-advanced/send-message-advanced.mjs @@ -7,7 +7,7 @@ export default { key: "slack-send-message-advanced", name: "Send Message (Advanced)", description: "Customize advanced setttings and send a message to a channel, group or user. See [postMessage](https://api.slack.com/methods/chat.postMessage) or [scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) docs here", - version: "0.0.2", + version: "0.0.3", type: "action", props: { slack: common.props.slack, diff --git a/components/slack/actions/send-message-to-channel/send-message-to-channel.mjs b/components/slack/actions/send-message-to-channel/send-message-to-channel.mjs new file mode 100644 index 0000000000000..7e5b24086396a --- /dev/null +++ b/components/slack/actions/send-message-to-channel/send-message-to-channel.mjs @@ -0,0 +1,40 @@ +import common from "../common/send-message.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + ...common, + key: "slack-send-message-to-channel", + name: "Send Message to Channel", + description: "Send a message to a public or private channel. [See the documentation](https://api.slack.com/methods/chat.postMessage)", + version: "0.0.1", + type: "action", + props: { + slack: common.props.slack, + conversation: { + propDefinition: [ + common.props.slack, + "conversation", + () => ({ + types: [ + constants.CHANNEL_TYPE.PUBLIC, + constants.CHANNEL_TYPE.PRIVATE, + ], + }), + ], + description: "Select a public or private channel", + }, + text: { + propDefinition: [ + common.props.slack, + "text", + ], + }, + mrkdwn: { + propDefinition: [ + common.props.slack, + "mrkdwn", + ], + }, + ...common.props, + }, +}; diff --git a/components/slack/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs b/components/slack/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs new file mode 100644 index 0000000000000..dc07a2a46316f --- /dev/null +++ b/components/slack/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs @@ -0,0 +1,73 @@ +import common from "../common/send-message.mjs"; +import constants from "../../common/constants.mjs"; +import { ConfigurationError } from "@pipedream/platform"; + +export default { + ...common, + key: "slack-send-message-to-user-or-group", + name: "Send Message to User or Group", + description: "Send a message to a user or group. [See the documentation](https://api.slack.com/methods/chat.postMessage)", + version: "0.0.1", + type: "action", + props: { + slack: common.props.slack, + users: { + propDefinition: [ + common.props.slack, + "user", + ], + type: "string[]", + label: "Users", + description: "Select the user(s) to message", + optional: true, + }, + conversation: { + propDefinition: [ + common.props.slack, + "conversation", + () => ({ + types: [ + constants.CHANNEL_TYPE.MPIM, + ], + }), + ], + description: "Select the group to message", + optional: true, + }, + text: { + propDefinition: [ + common.props.slack, + "text", + ], + }, + mrkdwn: { + propDefinition: [ + common.props.slack, + "mrkdwn", + ], + }, + ...common.props, + }, + methods: { + ...common.methods, + openConversation(args = {}) { + return this.slack.makeRequest({ + method: "conversations.open", + ...args, + }); + }, + async getChannelId() { + if (!this.conversation && !this.users?.length) { + throw new ConfigurationError("Must select a group or user(s) to message"); + } + + if (this.conversation) { + return this.conversation; + } + const { channel: { id } } = await this.openConversation({ + users: this.users.join(), + }); + return id; + }, + }, +}; diff --git a/components/slack/actions/send-message/send-message.mjs b/components/slack/actions/send-message/send-message.mjs index 04b721b2ab2c9..e18801e728e68 100644 --- a/components/slack/actions/send-message/send-message.mjs +++ b/components/slack/actions/send-message/send-message.mjs @@ -6,7 +6,7 @@ export default { key: "slack-send-message", name: "Send Message", description: "Send a message to a user, group, private channel or public channel. [See the documentation](https://api.slack.com/methods/chat.postMessage)", - version: "0.0.15", + version: "0.0.16", type: "action", props: { slack: common.props.slack, diff --git a/components/slack/actions/set-channel-description/set-channel-description.mjs b/components/slack/actions/set-channel-description/set-channel-description.mjs index 79fad11b17321..2e8426682f3a5 100644 --- a/components/slack/actions/set-channel-description/set-channel-description.mjs +++ b/components/slack/actions/set-channel-description/set-channel-description.mjs @@ -4,7 +4,7 @@ export default { key: "slack-set-channel-description", name: "Set Channel Description", description: "Change the description or purpose of a channel. [See the documentation](https://api.slack.com/methods/conversations.setPurpose)", - version: "0.0.4", + version: "0.0.5", type: "action", props: { slack, diff --git a/components/slack/actions/set-channel-topic/set-channel-topic.mjs b/components/slack/actions/set-channel-topic/set-channel-topic.mjs index a09f3bf487100..5fbef5e0d483f 100644 --- a/components/slack/actions/set-channel-topic/set-channel-topic.mjs +++ b/components/slack/actions/set-channel-topic/set-channel-topic.mjs @@ -4,7 +4,7 @@ export default { key: "slack-set-channel-topic", name: "Set Channel Topic", description: "Set the topic on a selected channel. [See the documentation](https://api.slack.com/methods/conversations.setTopic)", - version: "0.0.19", + version: "0.0.20", type: "action", props: { slack, diff --git a/components/slack/actions/set-status/set-status.mjs b/components/slack/actions/set-status/set-status.mjs index 26a9f0225ab2c..00791f64fbc8e 100644 --- a/components/slack/actions/set-status/set-status.mjs +++ b/components/slack/actions/set-status/set-status.mjs @@ -4,7 +4,7 @@ export default { key: "slack-set-status", name: "Set Status", description: "Set the current status for a user. [See the documentation](https://api.slack.com/methods/users.profile.set)", - version: "0.0.4", + version: "0.0.5", type: "action", props: { slack, diff --git a/components/slack/actions/update-group-members/update-group-members.mjs b/components/slack/actions/update-group-members/update-group-members.mjs index 53c0e3adf8966..7b6d612e55ded 100644 --- a/components/slack/actions/update-group-members/update-group-members.mjs +++ b/components/slack/actions/update-group-members/update-group-members.mjs @@ -4,7 +4,7 @@ export default { key: "slack-update-group-members", name: "Update Groups Members", description: "Update the list of users for a User Group. [See the documentation](https://api.slack.com/methods/usergroups.users.update)", - version: "0.0.4", + version: "0.0.5", type: "action", props: { slack, diff --git a/components/slack/actions/update-message/update-message.mjs b/components/slack/actions/update-message/update-message.mjs index f687533cb670b..025b024c94fc7 100644 --- a/components/slack/actions/update-message/update-message.mjs +++ b/components/slack/actions/update-message/update-message.mjs @@ -4,7 +4,7 @@ export default { key: "slack-update-message", name: "Update Message", description: "Update a message. [See the documentation](https://api.slack.com/methods/chat.update)", - version: "0.1.19", + version: "0.1.20", type: "action", props: { slack, diff --git a/components/slack/actions/update-profile/update-profile.mjs b/components/slack/actions/update-profile/update-profile.mjs index 93c58bcb70e1d..88982335d2bf4 100644 --- a/components/slack/actions/update-profile/update-profile.mjs +++ b/components/slack/actions/update-profile/update-profile.mjs @@ -5,7 +5,7 @@ export default { key: "slack-update-profile", name: "Update Profile", description: "Update basic profile field such as name or title. [See the documentation](https://api.slack.com/methods/users.profile.set)", - version: "0.0.19", + version: "0.0.20", type: "action", props: { slack, diff --git a/components/slack/actions/upload-file/upload-file.mjs b/components/slack/actions/upload-file/upload-file.mjs index c35c415db3554..36dafd3651c3e 100644 --- a/components/slack/actions/upload-file/upload-file.mjs +++ b/components/slack/actions/upload-file/upload-file.mjs @@ -6,7 +6,7 @@ export default { key: "slack-upload-file", name: "Upload File", description: "Upload a file. [See the documentation](https://api.slack.com/methods/files.upload)", - version: "0.0.23", + version: "0.0.24", type: "action", props: { slack, diff --git a/components/slack/actions/verify-slack-signature/verify-slack-signature.mjs b/components/slack/actions/verify-slack-signature/verify-slack-signature.mjs index cc1f5cff2b3d1..4e658d7358809 100644 --- a/components/slack/actions/verify-slack-signature/verify-slack-signature.mjs +++ b/components/slack/actions/verify-slack-signature/verify-slack-signature.mjs @@ -5,7 +5,7 @@ export default { key: "slack-verify-slack-signature", name: "Verify Slack Signature", description: "Verifying requests from Slack, slack signs its requests using a secret that's unique to your app. [See the documentation](https://api.slack.com/authentication/verifying-requests-from-slack)", - version: "0.0.12", + version: "0.0.13", type: "action", props: { slack, diff --git a/components/slack/package.json b/components/slack/package.json index 6cd894efe2f81..a75ac01e1e2d9 100644 --- a/components/slack/package.json +++ b/components/slack/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/slack", - "version": "0.6.7", + "version": "0.8.0", "description": "Pipedream Slack Components", "main": "slack.app.mjs", "keywords": [ diff --git a/components/slack/slack.app.mjs b/components/slack/slack.app.mjs index 7d7964c3eb026..443a99e60dbeb 100644 --- a/components/slack/slack.app.mjs +++ b/components/slack/slack.app.mjs @@ -119,7 +119,7 @@ export default { } } const [ - userNames, + userNames = await this.userNames(), conversationsResp, ] = await Promise.all([ userNamesOrPromise, diff --git a/components/slack/sources/new-channel-created/new-channel-created.mjs b/components/slack/sources/new-channel-created/new-channel-created.mjs index a0c1f5bee9d83..ee4807bd945b3 100644 --- a/components/slack/sources/new-channel-created/new-channel-created.mjs +++ b/components/slack/sources/new-channel-created/new-channel-created.mjs @@ -5,7 +5,7 @@ export default { ...common, key: "slack-new-channel-created", name: "New Channel Created (Instant)", - version: "0.0.6", + version: "0.0.7", description: "Emit new event when a new channel is created.", type: "source", dedupe: "unique", diff --git a/components/slack/sources/new-direct-message/new-direct-message.mjs b/components/slack/sources/new-direct-message/new-direct-message.mjs index 3d418e6f8acb5..7be679e119e0b 100644 --- a/components/slack/sources/new-direct-message/new-direct-message.mjs +++ b/components/slack/sources/new-direct-message/new-direct-message.mjs @@ -5,7 +5,7 @@ export default { ...common, key: "slack-new-direct-message", name: "New Direct Message (Instant)", - version: "1.0.19", + version: "1.0.20", description: "Emit new event when a message was posted in a direct message channel", type: "source", dedupe: "unique", diff --git a/components/slack/sources/new-interaction-event-received/new-interaction-event-received.mjs b/components/slack/sources/new-interaction-event-received/new-interaction-event-received.mjs index 7f7db12455810..cbdec6f441c20 100644 --- a/components/slack/sources/new-interaction-event-received/new-interaction-event-received.mjs +++ b/components/slack/sources/new-interaction-event-received/new-interaction-event-received.mjs @@ -3,7 +3,7 @@ import sampleEmit from "./test-event.mjs"; export default { name: "New Interaction Events (Instant)", - version: "0.0.16", + version: "0.0.17", key: "slack-new-interaction-event-received", description: "Emit new events on new Slack [interactivity events](https://api.slack.com/interactivity) sourced from [Block Kit interactive elements](https://api.slack.com/interactivity/components), [Slash commands](https://api.slack.com/interactivity/slash-commands), or [Shortcuts](https://api.slack.com/interactivity/shortcuts).", type: "source", diff --git a/components/slack/sources/new-keyword-mention/new-keyword-mention.mjs b/components/slack/sources/new-keyword-mention/new-keyword-mention.mjs index 1a1509e28ce38..277d887082d2f 100644 --- a/components/slack/sources/new-keyword-mention/new-keyword-mention.mjs +++ b/components/slack/sources/new-keyword-mention/new-keyword-mention.mjs @@ -6,7 +6,7 @@ export default { ...common, key: "slack-new-keyword-mention", name: "New Keyword Mention (Instant)", - version: "0.0.4", + version: "0.0.5", description: "Emit new event when a specific keyword is mentioned in a channel", type: "source", dedupe: "unique", diff --git a/components/slack/sources/new-message-in-channels/new-message-in-channels.mjs b/components/slack/sources/new-message-in-channels/new-message-in-channels.mjs index 87cdb6ce298d3..648a69dc41f63 100644 --- a/components/slack/sources/new-message-in-channels/new-message-in-channels.mjs +++ b/components/slack/sources/new-message-in-channels/new-message-in-channels.mjs @@ -6,7 +6,7 @@ export default { ...common, key: "slack-new-message-in-channels", name: "New Message In Channels (Instant)", - version: "1.0.21", + version: "1.0.22", description: "Emit new event when a new message is posted to one or more channels", type: "source", dedupe: "unique", diff --git a/components/slack/sources/new-reaction-added/new-reaction-added.mjs b/components/slack/sources/new-reaction-added/new-reaction-added.mjs index da9a25e961d80..18a5096f621e9 100644 --- a/components/slack/sources/new-reaction-added/new-reaction-added.mjs +++ b/components/slack/sources/new-reaction-added/new-reaction-added.mjs @@ -5,7 +5,7 @@ export default { ...common, key: "slack-new-reaction-added", name: "New Reaction Added (Instant)", - version: "1.1.22", + version: "1.1.23", description: "Emit new event when a member has added an emoji reaction to a message", type: "source", dedupe: "unique", diff --git a/components/slack/sources/new-saved-message/new-saved-message.mjs b/components/slack/sources/new-saved-message/new-saved-message.mjs index 908076d930963..1ad9ba6444640 100644 --- a/components/slack/sources/new-saved-message/new-saved-message.mjs +++ b/components/slack/sources/new-saved-message/new-saved-message.mjs @@ -5,7 +5,7 @@ export default { ...common, key: "slack-new-saved-message", name: "New Saved Message (Instant)", - version: "0.0.2", + version: "0.0.3", description: "Emit new event when a message is saved. Note: The endpoint is marked as deprecated, and Slack might shut this off at some point down the line.", type: "source", dedupe: "unique", diff --git a/components/slack/sources/new-user-added/new-user-added.mjs b/components/slack/sources/new-user-added/new-user-added.mjs new file mode 100644 index 0000000000000..207ddf9fd276e --- /dev/null +++ b/components/slack/sources/new-user-added/new-user-added.mjs @@ -0,0 +1,32 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "slack-new-user-added", + name: "New User Added (Instant)", + version: "0.0.1", + description: "Emit new event when a new member joins a workspace.", + type: "source", + dedupe: "unique", + props: { + ...common.props, + // eslint-disable-next-line pipedream/props-description,pipedream/props-label + slackApphook: { + type: "$.interface.apphook", + appProp: "slack", + async eventNames() { + return [ + "team_join", + ]; + }, + }, + }, + methods: { + ...common.methods, + getSummary({ user: { name } }) { + return `New User: ${name}`; + }, + }, + sampleEmit, +}; diff --git a/components/slack/sources/new-user-added/test-event.mjs b/components/slack/sources/new-user-added/test-event.mjs new file mode 100644 index 0000000000000..3bcd19da42f6a --- /dev/null +++ b/components/slack/sources/new-user-added/test-event.mjs @@ -0,0 +1,48 @@ +export default { + "type": "team_join", + "user": { + "id": "U080GULP8SD", + "team_id": "TS8319547", + "name": "", + "deleted": false, + "color": "9b3b45", + "real_name": "", + "tz": "America/New_York", + "tz_label": "Eastern Standard Time", + "tz_offset": -18000, + "profile": { + "title": "", + "phone": "", + "skype": "", + "real_name": "", + "real_name_normalized": "", + "display_name": "", + "display_name_normalized": "", + "fields": {}, + "status_text": "", + "status_emoji": "", + "status_emoji_display_info": [], + "status_expiration": 0, + "avatar_hash": "g96b6e8b38c2", + "email": "", + "first_name": "", + "last_name": "", + "status_text_canonical": "", + "team": "TS8319547" + }, + "is_admin": false, + "is_owner": false, + "is_primary_owner": false, + "is_restricted": false, + "is_ultra_restricted": false, + "is_bot": false, + "is_app_user": false, + "updated": 1731094476, + "is_email_confirmed": true, + "who_can_share_contact_card": "EVERYONE", + "presence": "away" + }, + "cache_ts": 1731094476, + "event_ts": "1731094477.001400", + "pipedream_msg_id": "pd_1731094479305_v1ic236by8" +} \ No newline at end of file diff --git a/components/slack/sources/new-user-mention/new-user-mention.mjs b/components/slack/sources/new-user-mention/new-user-mention.mjs index ee3f673720d84..d4e35230721ab 100644 --- a/components/slack/sources/new-user-mention/new-user-mention.mjs +++ b/components/slack/sources/new-user-mention/new-user-mention.mjs @@ -6,7 +6,7 @@ export default { ...common, key: "slack-new-user-mention", name: "New User Mention (Instant)", - version: "0.0.4", + version: "0.0.5", description: "Emit new event when a username or specific keyword is mentioned in a channel", type: "source", dedupe: "unique", diff --git a/components/spotify/actions/get-currently-playing-track/get-currently-playing-track.mjs b/components/spotify/actions/get-currently-playing-track/get-currently-playing-track.mjs new file mode 100644 index 0000000000000..3103ecaafbbf2 --- /dev/null +++ b/components/spotify/actions/get-currently-playing-track/get-currently-playing-track.mjs @@ -0,0 +1,56 @@ +import { axios } from "@pipedream/platform"; +import spotify from "../../spotify.app.mjs"; +import { ITEM_TYPES } from "../../consts.mjs"; + +export default { + name: "Get currently playing track", + description: + "Get the object currently being played on the user's Spotify account. [See the documentation](https://developer.spotify.com/documentation/web-api/reference/get-the-users-currently-playing-track)", + key: "spotify-get-currently-playing-track", + version: "0.0.1", + type: "action", + props: { + spotify, + market: { + propDefinition: [ + spotify, + "market", + ], + optional: true, + }, + }, + async run({ $ }) { + const { market } = this; + + try { + const res = await axios( + $, + this.spotify._getAxiosParams({ + method: "GET", + path: "/me/player/currently-playing", + params: { + market, + additional_types: [ + ITEM_TYPES.TRACK, + ITEM_TYPES.EPISODE, + ].join(","), + }, + }), + ); + + const itemType = res?.currently_playing_type || "track"; + const itemName = res?.item?.name || "Nothing"; + $.export("$summary", `Currently playing ${itemType}: ${itemName}`); + + return { + playing: !!res, + type: res?.currently_playing_type, + item: res?.item, + progress_ms: res?.progress_ms, + is_playing: res?.is_playing, + }; + } catch (error) { + throw new Error(`Failed to get currently playing track for user: ${error.message}`); + } + }, +}; diff --git a/components/spotify/package.json b/components/spotify/package.json index 714e27cdff439..021dc4ea3c1c0 100644 --- a/components/spotify/package.json +++ b/components/spotify/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/spotify", - "version": "0.7.0", + "version": "0.7.1", "description": "Pipedream Spotify Components", "main": "spotify.app.js", "keywords": [ @@ -14,6 +14,7 @@ "access": "public" }, "dependencies": { - "@pipedream/platform": "^1.4.0" + "@pipedream/platform": "^3.0.3", + "lodash": "^4.17.21" } } diff --git a/components/supabase/actions/delete-row/delete-row.mjs b/components/supabase/actions/delete-row/delete-row.mjs index 391c12f1d3c72..4ca2757f1155c 100644 --- a/components/supabase/actions/delete-row/delete-row.mjs +++ b/components/supabase/actions/delete-row/delete-row.mjs @@ -4,7 +4,7 @@ export default { key: "supabase-delete-row", name: "Delete Row", type: "action", - version: "0.1.1", + version: "0.1.2", description: "Deletes row(s) in a database. [See the docs here](https://supabase.com/docs/reference/javascript/delete)", props: { supabase, diff --git a/components/supabase/actions/insert-row/insert-row.mjs b/components/supabase/actions/insert-row/insert-row.mjs index 1b9e2353246bd..223bd8124382e 100644 --- a/components/supabase/actions/insert-row/insert-row.mjs +++ b/components/supabase/actions/insert-row/insert-row.mjs @@ -4,7 +4,7 @@ export default { key: "supabase-insert-row", name: "Insert Row", type: "action", - version: "0.1.1", + version: "0.1.2", description: "Inserts a new row into a database. [See the docs here](https://supabase.com/docs/reference/javascript/insert)", props: { supabase, diff --git a/components/supabase/actions/remote-procedure-call/remote-procedure-call.mjs b/components/supabase/actions/remote-procedure-call/remote-procedure-call.mjs index ad56972ea4909..eba974b7bf4a9 100644 --- a/components/supabase/actions/remote-procedure-call/remote-procedure-call.mjs +++ b/components/supabase/actions/remote-procedure-call/remote-procedure-call.mjs @@ -4,7 +4,7 @@ export default { key: "supabase-remote-procedure-call", name: "Remote Procedure Call", type: "action", - version: "0.1.1", + version: "0.1.2", description: "Call a Postgres function in a database. [See the docs here](https://supabase.com/docs/reference/javascript/rpc)", props: { supabase, diff --git a/components/supabase/actions/select-row/select-row.mjs b/components/supabase/actions/select-row/select-row.mjs index fcae3d8290ea1..11b47389ee3e6 100644 --- a/components/supabase/actions/select-row/select-row.mjs +++ b/components/supabase/actions/select-row/select-row.mjs @@ -5,7 +5,7 @@ export default { key: "supabase-select-row", name: "Select Row", type: "action", - version: "0.1.1", + version: "0.1.2", description: "Selects row(s) in a database. [See the docs here](https://supabase.com/docs/reference/javascript/select)", props: { supabase, diff --git a/components/supabase/actions/update-row/update-row.mjs b/components/supabase/actions/update-row/update-row.mjs index ab38061dddbea..ff48a6852c1d1 100644 --- a/components/supabase/actions/update-row/update-row.mjs +++ b/components/supabase/actions/update-row/update-row.mjs @@ -4,7 +4,7 @@ export default { key: "supabase-update-row", name: "Update Row", type: "action", - version: "0.1.1", + version: "0.1.2", description: "Updates row(s) in a database. [See the docs here](https://supabase.com/docs/reference/javascript/update)", props: { supabase, diff --git a/components/supabase/actions/upsert-row/upsert-row.mjs b/components/supabase/actions/upsert-row/upsert-row.mjs index 12e1dea34b87f..a6a5cbdc8cba3 100644 --- a/components/supabase/actions/upsert-row/upsert-row.mjs +++ b/components/supabase/actions/upsert-row/upsert-row.mjs @@ -4,7 +4,7 @@ export default { key: "supabase-upsert-row", name: "Upsert Row", type: "action", - version: "0.1.1", + version: "0.1.2", description: "Updates a row in a database or inserts new row if not found. [See the docs here](https://supabase.com/docs/reference/javascript/upsert)", props: { supabase, diff --git a/components/supabase/package.json b/components/supabase/package.json index a0f16c5d6441e..96792f41447f8 100644 --- a/components/supabase/package.json +++ b/components/supabase/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/supabase", - "version": "0.2.1", + "version": "0.2.2", "description": "Pipedream Supabase Components", "main": "supabase.app.mjs", "keywords": [ @@ -14,6 +14,6 @@ }, "dependencies": { "@pipedream/platform": "^1.2.1", - "@supabase/supabase-js": "^2.39.0" + "@supabase/supabase-js": "^2.45.6" } } diff --git a/components/supabase/sources/new-row-added/new-row-added.mjs b/components/supabase/sources/new-row-added/new-row-added.mjs index eaf4bb135f683..7eb470aa3bf85 100644 --- a/components/supabase/sources/new-row-added/new-row-added.mjs +++ b/components/supabase/sources/new-row-added/new-row-added.mjs @@ -9,7 +9,7 @@ export default { key: "supabase-new-row-added", name: "New Row Added", description: "Emit new event for every new row added in a table. [See documentation here](https://supabase.com/docs/reference/javascript/select)", - version: "0.0.3", + version: "0.0.4", type: "source", props: { ...base.props, diff --git a/components/supabase/sources/new-webhook-event/new-webhook-event.mjs b/components/supabase/sources/new-webhook-event/new-webhook-event.mjs index f64bbb3c23b76..07d0331dcf7ec 100644 --- a/components/supabase/sources/new-webhook-event/new-webhook-event.mjs +++ b/components/supabase/sources/new-webhook-event/new-webhook-event.mjs @@ -5,7 +5,7 @@ export default { key: "supabase-new-webhook-event", name: "New Webhook Event (Instant)", description: "Emit new event for every `insert`, `update`, or `delete` operation in a table. This source requires user configuration using the Supabase website. More information in the README. [Also see documentation here](https://supabase.com/docs/guides/database/webhooks#creating-a-webhook)", - version: "0.0.4", + version: "0.0.5", type: "source", props: { ...base.props, diff --git a/components/supabase/supabase.app.mjs b/components/supabase/supabase.app.mjs index 0c1a86e417a92..30fd3156dafdd 100644 --- a/components/supabase/supabase.app.mjs +++ b/components/supabase/supabase.app.mjs @@ -1,4 +1,4 @@ -import { createClient } from "@supabase/supabase-js@2.39.8"; +import { createClient } from "@supabase/supabase-js"; import constants from "./common/constants.mjs"; export default { diff --git a/components/tess_ai_by_pareto/actions/execute-agent/execute-agent.mjs b/components/tess_ai_by_pareto/actions/execute-agent/execute-agent.mjs new file mode 100644 index 0000000000000..6f0805e8eeebc --- /dev/null +++ b/components/tess_ai_by_pareto/actions/execute-agent/execute-agent.mjs @@ -0,0 +1,45 @@ +import { getQuestionProps } from "../../common/utils.mjs"; +import app from "../../tess_ai_by_pareto.app.mjs"; + +export default { + key: "tess_ai_by_pareto-execute-agent", + name: "Execute AI Agent", + description: + "Executes an AI Agent (template) with the given input. [See the documentation](https://tess.pareto.io/api/swagger#/default/f13b3be7386ce63d99fa4bdee0cf6c95)", + version: "0.0.1", + type: "action", + props: { + app, + templateId: { + propDefinition: [ + app, + "templateId", + ], + reloadProps: true, + }, + }, + methods: { + getQuestionProps, + }, + async additionalProps() { + const { questions } = await this.app.getTemplate(this.templateId); + return this.getQuestionProps(questions); + + }, + async run({ $ }) { + /* eslint-disable no-unused-vars */ + const { + app, templateId, getQuestionProps, ...data + } = this; + const response = await this.app.executeTemplate({ + $, + templateId, + data, + }); + $.export( + "$summary", + `Executed AI agent ${response.id}`, + ); + return response; + }, +}; diff --git a/components/tess_ai_by_pareto/actions/get-execution-response/get-execution-response.mjs b/components/tess_ai_by_pareto/actions/get-execution-response/get-execution-response.mjs new file mode 100644 index 0000000000000..79546f275d361 --- /dev/null +++ b/components/tess_ai_by_pareto/actions/get-execution-response/get-execution-response.mjs @@ -0,0 +1,30 @@ +import app from "../../tess_ai_by_pareto.app.mjs"; + +export default { + key: "tess_ai_by_pareto-get-execution-response", + name: "Get Agent Execution Response", + description: + "Retrieves the result of a previously executed AI Agent (template). [See the documentation](https://tess.pareto.io/api/swagger#/default/370b6709c5d9e8c17a76e1abb288e7ad)", + version: "0.0.1", + type: "action", + props: { + app, + executionId: { + type: "string", + label: "Agent Execution ID", + description: + "The ID of the AI Agent (template) execution to retrieve the result for.", + }, + }, + async run({ $ }) { + const result = await this.app.getTemplateResponse({ + $, + executionId: this.executionId, + }); + $.export( + "$summary", + `Retrieved response for execution ID ${this.executionId}`, + ); + return result; + }, +}; diff --git a/components/tess_ai_by_pareto/actions/search-ai-agents/search-ai-agents.mjs b/components/tess_ai_by_pareto/actions/search-ai-agents/search-ai-agents.mjs new file mode 100644 index 0000000000000..0d418cd82bee0 --- /dev/null +++ b/components/tess_ai_by_pareto/actions/search-ai-agents/search-ai-agents.mjs @@ -0,0 +1,47 @@ +import app from "../../tess_ai_by_pareto.app.mjs"; + +export default { + key: "tess_ai_by_pareto-search-ai-agents", + name: "Search AI Agents", + description: + "Retrieve AI Agents (templates) that match the specified criteria. [See the documentation](https://tess.pareto.io/api/swagger#/default/201046139d07458d530ad3526e0b3c2f)", + version: "0.0.1", + type: "action", + props: { + app, + query: { + type: "string", + label: "Search Query", + description: + "Search agents (templates) by title, description and long description.", + optional: true, + }, + type: { + type: "string", + label: "Type Filter", + description: "Filter by template type", + optional: true, + }, + maxResults: { + type: "integer", + label: "Max Results", + description: "The maximum number of results to return", + optional: true, + default: 15, + min: 1, + max: 1000, + }, + }, + async run({ $ }) { + const response = await this.app.searchTemplates({ + $, + params: { + q: this.query, + type: this.type, + per_page: this.maxResults, + }, + }); + $.export("$summary", `Retrieved ${response.data?.length} templates`); + return response; + }, +}; diff --git a/components/tess_ai_by_pareto/common/utils.mjs b/components/tess_ai_by_pareto/common/utils.mjs new file mode 100644 index 0000000000000..b21ac571c51cd --- /dev/null +++ b/components/tess_ai_by_pareto/common/utils.mjs @@ -0,0 +1,21 @@ +export function getQuestionProps(questions) { + function getQuestionPropType(type) { + switch (type) { + case "number": + return "integer"; + default: + return "string"; + } + } + + return (questions ?? []).reduce((obj, question) => { + obj[question.name] = { + type: getQuestionPropType(question.type), + label: `Field: "${question.name}"`, + description: `Type: \`${question.type}\`. Description: "${question.description}"`, + options: question.options, + optional: !question.required, + }; + return obj; + }, {}); +} diff --git a/components/tess_ai_by_pareto/package.json b/components/tess_ai_by_pareto/package.json index 16ca35b5f25dc..8060e0938e483 100644 --- a/components/tess_ai_by_pareto/package.json +++ b/components/tess_ai_by_pareto/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/tess_ai_by_pareto", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Tess AI by Pareto Components", "main": "tess_ai_by_pareto.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } -} \ No newline at end of file +} diff --git a/components/tess_ai_by_pareto/tess_ai_by_pareto.app.mjs b/components/tess_ai_by_pareto/tess_ai_by_pareto.app.mjs index a07f40f61533f..3769360ea2446 100644 --- a/components/tess_ai_by_pareto/tess_ai_by_pareto.app.mjs +++ b/components/tess_ai_by_pareto/tess_ai_by_pareto.app.mjs @@ -1,11 +1,73 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "tess_ai_by_pareto", - propDefinitions: {}, + propDefinitions: { + templateId: { + type: "string", + label: "AI Agent ID", + description: "The ID of the AI Agent (template) to execute.", + useQuery: true, + async options({ + page = 0, query, + }) { + const response = await this.searchTemplates({ + params: { + page: page + 1, + q: query || undefined, + }, + }); + return response?.data?.map((template) => ({ + label: template.title, + value: template.id, + })); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://tess.pareto.io/api"; + }, + async _makeRequest({ + $ = this, path = "/", headers, ...otherOpts + } = {}) { + return axios($, { + url: this._baseUrl() + path, + headers: { + ...headers, + "Authorization": `Bearer ${this.$auth.api_token}`, + }, + ...otherOpts, + }); + }, + async executeTemplate({ + templateId, ...args + }) { + return this._makeRequest({ + method: "POST", + path: `/templates/${templateId}/execute`, + ...args, + }); + }, + async getTemplate(templateId) { + return this._makeRequest({ + path: `/templates/${templateId}`, + }); + }, + async searchTemplates(args) { + return this._makeRequest({ + path: "/templates", + ...args, + }); + }, + async getTemplateResponse({ + executionId, ...args + }) { + return this._makeRequest({ + path: `/template-responses/${executionId}`, + ...args, + }); }, }, }; diff --git a/components/tldr/actions/summarize-text/summarize-text.mjs b/components/tldr/actions/summarize-text/summarize-text.mjs new file mode 100644 index 0000000000000..618b2ea3c80d2 --- /dev/null +++ b/components/tldr/actions/summarize-text/summarize-text.mjs @@ -0,0 +1,40 @@ +import tldr from "../../tldr.app.mjs"; + +export default { + key: "tldr-summarize-text", + name: "Summarize Text", + description: "Reads in a piece of text and distills the main points. [See the documentation](https://runtldr.com/documentation)", + version: "0.0.1", + type: "action", + props: { + tldr, + inputText: { + type: "string", + label: "Text to Summarize", + description: "The text that needs to be summarized.", + }, + responseStyle: { + type: "string", + label: "Response Style", + description: "Style of the response (e.g., Funny, Serious).", + }, + responseLength: { + type: "integer", + label: "Response Length", + description: "Length of the response summary.", + }, + }, + async run({ $ }) { + const response = await this.tldr.summarize({ + $, + data: { + inputText: this.inputText, + responseLength: this.responseLength, + responseStyle: this.responseStyle, + }, + }); + + $.export("$summary", `Successfully summarized the text with the following input: "${this.inputText}"`); + return response; + }, +}; diff --git a/components/tldr/package.json b/components/tldr/package.json new file mode 100644 index 0000000000000..a8353a1194f10 --- /dev/null +++ b/components/tldr/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/tldr", + "version": "0.1.0", + "description": "Pipedream TLDR Components", + "main": "tldr.app.mjs", + "keywords": [ + "pipedream", + "tldr" + ], + "homepage": "https://pipedream.com/apps/tldr", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" + } +} diff --git a/components/tldr/tldr.app.mjs b/components/tldr/tldr.app.mjs new file mode 100644 index 0000000000000..9cec238fcb7cf --- /dev/null +++ b/components/tldr/tldr.app.mjs @@ -0,0 +1,33 @@ +import { axios } from "@pipedream/platform"; + +export default { + type: "app", + app: "tldr", + methods: { + _headers() { + return { + "Authorization": `Bearer ${this.$auth.api_key}`, + "Content-Type": "application/json", + }; + }, + _baseUrl() { + return "https://runtldr.com/apis/v1"; + }, + _makeRequest({ + $ = this, path, ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + headers: this._headers(), + ...opts, + }); + }, + summarize(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/summarize", + ...opts, + }); + }, + }, +}; diff --git a/components/tremendous/actions/create-order-email-reward/create-order-email-reward.mjs b/components/tremendous/actions/create-order-email-reward/create-order-email-reward.mjs new file mode 100644 index 0000000000000..692a06c6b46ca --- /dev/null +++ b/components/tremendous/actions/create-order-email-reward/create-order-email-reward.mjs @@ -0,0 +1,107 @@ +import app from "../../tremendous.app.mjs"; +import { DELIVERY_METHOD_OPTIONS } from "../../common/constants.mjs"; + +export default { + name: "Create Order Email Reward", + version: "0.0.1", + key: "tremendous-create-order-email-reward", + description: "Create an order to send out a reward. [See the documentation](https://developers.tremendous.com/reference/create-order)", + type: "action", + props: { + app, + campaignId: { + propDefinition: [ + app, + "campaignId", + ], + optional: true, + }, + products: { + propDefinition: [ + app, + "products", + ], + optional: true, + }, + infoBox: { + type: "alert", + alertType: "info", + content: "Either `Products` or `Campaign ID` must be specified. [See the documentation](https://developers.tremendous.com/reference/create-order) for more information.", + }, + fundingSourceId: { + propDefinition: [ + app, + "fundingSourceId", + ], + default: "balance", + }, + externalId: { + type: "string", + label: "External ID", + description: "Reference for this order. If set, any subsequent requests with the same `External ID` will not create any further orders, and simply return the initially created order.", + optional: true, + }, + valueAmount: { + type: "string", + label: "Value Amount", + description: "Amount of the reward.", + }, + valueCurrencyCode: { + type: "string", + label: "Value Currency Code", + description: "Currency of the reward.", + }, + recipientName: { + type: "string", + label: "Recipient Name", + description: "Name of the recipient.", + }, + recipientEmail: { + type: "string", + label: "Recipient Email", + description: "Email address of the recipient.", + }, + recipientPhone: { + type: "string", + label: "Recipient Phone", + description: "Phone number of the recipient. For non-US phone numbers, specify the country code (prefixed with `+`).", + }, + deliveryMethod: { + type: "string", + label: "Delivery Method", + description: "How to deliver the reward to the recipient.", + options: DELIVERY_METHOD_OPTIONS, + }, + }, + async run({ $ }) { + const response = await this.app.createOrder({ + $, + data: { + external_id: this.externalId, + payment: { + funding_source_id: this.fundingSourceId, + }, + reward: { + campaign_id: this.campaignId, + products: this.products, + value: { + denomination: this.valueAmount, + currency_code: this.valueCurrencyCode, + }, + recipient: { + name: this.recipientName, + email: this.recipientEmail, + phone: this.recipientPhone, + }, + delivery: { + method: this.deliveryMethod, + }, + }, + }, + }); + + $.export("$summary", `Successfully created order (ID: ${response?.order?.id})`); + + return response; + }, +}; diff --git a/components/tremendous/common/constants.mjs b/components/tremendous/common/constants.mjs new file mode 100644 index 0000000000000..8fa569a6180b0 --- /dev/null +++ b/components/tremendous/common/constants.mjs @@ -0,0 +1,15 @@ +export const DELIVERY_METHOD_OPTIONS = [ + { + value: "EMAIL", + label: "Deliver the reward to the recipient by email", + }, + { + value: "LINK", + label: "Deliver the reward to the recipient via a link.", + }, + + { + value: "PHONE", + label: "Deliver the reward to the recipient by SMS", + }, +]; diff --git a/components/tremendous/package.json b/components/tremendous/package.json new file mode 100644 index 0000000000000..e502f2a48a695 --- /dev/null +++ b/components/tremendous/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/tremendous", + "version": "0.0.1", + "description": "Pipedream Tremendous Components", + "main": "tremendous.app.mjs", + "keywords": [ + "pipedream", + "tremendous" + ], + "homepage": "https://pipedream.com/apps/tremendous", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" + } +} diff --git a/components/tremendous/tremendous.app.mjs b/components/tremendous/tremendous.app.mjs index 6d0c587c8b121..6318f95bef4cc 100644 --- a/components/tremendous/tremendous.app.mjs +++ b/components/tremendous/tremendous.app.mjs @@ -1,11 +1,89 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "tremendous", - propDefinitions: {}, + propDefinitions: { + campaignId: { + type: "string", + label: "Campaign ID", + description: "ID of the campaign in your account, that defines the available products (different gift cards, charity, etc.) that the recipient can choose from.", + async options() { + const { campaigns } = await this.listCampaigns(); + return campaigns?.map(({ + id, name, + }) => ({ + label: name, + value: id, + })); + }, + }, + products: { + type: "string[]", + label: "Products", + description: "IDs of products (different gift cards, charity, etc.) that will be available to the recipient to choose from. If this and `Campaign ID` are specified, this will override the products made available by the campaign. It will not override other campaign attributes, like the message and customization of the look and feel.", + async options() { + const { products } = await this.listProducts(); + return products?.map(({ + id, name, + }) => ({ + label: name, + value: id, + })); + }, + }, + fundingSourceId: { + type: "string", + label: "Funding Source ID", + description: "Tremendous ID of the funding source that will be used to pay for the order. Use `balance` to use your Tremendous's balance.", + async options() { + const response = await this.listFundingSources(); + return response.funding_sources?.map(({ + id, method, + }) => ({ + label: `${id} - ${method}`, + value: id, + })); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseRequest({ + $, headers, ...args + }) { + return axios($, { + headers: { + ...headers, + Authorization: `Bearer ${this.$auth.api_key}`, + }, + baseURL: "https://testflight.tremendous.com/api/v2", + ...args, + }); + }, + createOrder(args) { + return this._baseRequest({ + method: "POST", + url: "/orders", + ...args, + }); + }, + listCampaigns() { + return this._baseRequest({ + method: "GET", + url: "/campaigns", + }); + }, + listProducts() { + return this._baseRequest({ + method: "GET", + url: "/products", + }); + }, + listFundingSources() { + return this._baseRequest({ + method: "GET", + url: "/funding_sources", + }); }, }, }; diff --git a/components/verifone/package.json b/components/verifone/package.json new file mode 100644 index 0000000000000..c712ba1f18346 --- /dev/null +++ b/components/verifone/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pipedream/verifone", + "version": "0.0.1", + "description": "Pipedream Verifone Components", + "main": "verifone.app.mjs", + "keywords": [ + "pipedream", + "verifone" + ], + "homepage": "https://pipedream.com/apps/verifone", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/components/verifone/verifone.app.mjs b/components/verifone/verifone.app.mjs new file mode 100644 index 0000000000000..c7e4ad1b4a029 --- /dev/null +++ b/components/verifone/verifone.app.mjs @@ -0,0 +1,11 @@ +export default { + type: "app", + app: "verifone", + propDefinitions: {}, + methods: { + // this.$auth contains connected account data + authKeys() { + console.log(Object.keys(this.$auth)); + }, + }, +}; diff --git a/components/workflow_max/package.json b/components/workflow_max/package.json new file mode 100644 index 0000000000000..58044fbd07804 --- /dev/null +++ b/components/workflow_max/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pipedream/workflow_max", + "version": "0.0.1", + "description": "Pipedream Workflow Max Components", + "main": "workflow_max.app.mjs", + "keywords": [ + "pipedream", + "workflow_max" + ], + "homepage": "https://pipedream.com/apps/workflow_max", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/components/workflow_max/workflow_max.app.mjs b/components/workflow_max/workflow_max.app.mjs new file mode 100644 index 0000000000000..803f6ffc43364 --- /dev/null +++ b/components/workflow_max/workflow_max.app.mjs @@ -0,0 +1,11 @@ +export default { + type: "app", + app: "workflow_max", + propDefinitions: {}, + methods: { + // this.$auth contains connected account data + authKeys() { + console.log(Object.keys(this.$auth)); + }, + }, +}; \ No newline at end of file diff --git a/components/x_ai/package.json b/components/x_ai/package.json new file mode 100644 index 0000000000000..ebeff5c995aab --- /dev/null +++ b/components/x_ai/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pipedream/x_ai", + "version": "0.0.1", + "description": "Pipedream X AI Components", + "main": "x_ai.app.mjs", + "keywords": [ + "pipedream", + "x_ai" + ], + "homepage": "https://pipedream.com/apps/x_ai", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/components/x_ai/x_ai.app.mjs b/components/x_ai/x_ai.app.mjs new file mode 100644 index 0000000000000..43e6d5153b9c0 --- /dev/null +++ b/components/x_ai/x_ai.app.mjs @@ -0,0 +1,11 @@ +export default { + type: "app", + app: "x_ai", + propDefinitions: {}, + methods: { + // this.$auth contains connected account data + authKeys() { + console.log(Object.keys(this.$auth)); + }, + }, +}; \ No newline at end of file diff --git a/components/zendesk_sell/actions/create-contact/create-contact.mjs b/components/zendesk_sell/actions/create-contact/create-contact.mjs new file mode 100644 index 0000000000000..d4e2c8fd37a18 --- /dev/null +++ b/components/zendesk_sell/actions/create-contact/create-contact.mjs @@ -0,0 +1,91 @@ +import zendeskSell from "../../zendesk_sell.app.mjs"; + +export default { + key: "zendesk_sell-create-contact", + name: "Create Contact", + description: "Creates a new contact. [See the documentation](https://developer.zendesk.com/api-reference/sales-crm/resources/contacts/#create-a-contact).", + type: "action", + version: "0.0.1", + props: { + zendeskSell, + isOrganization: { + propDefinition: [ + zendeskSell, + "isOrganization", + ], + reloadProps: true, + }, + status: { + propDefinition: [ + zendeskSell, + "status", + ], + }, + title: { + propDefinition: [ + zendeskSell, + "title", + ], + }, + description: { + propDefinition: [ + zendeskSell, + "description", + ], + }, + email: { + propDefinition: [ + zendeskSell, + "email", + ], + }, + phone: { + propDefinition: [ + zendeskSell, + "phone", + ], + }, + }, + async additionalProps() { + const props = {}; + if (this.isOrganization) { + props.name = { + type: "string", + label: "Name", + description: "Name of the contact", + }; + } else { + props.firstName = { + type: "string", + label: "First Name", + description: "First name of the contact", + }; + props.lastName = { + type: "string", + label: "Last Name", + description: "Last name of the contact", + }; + } + return props; + }, + async run({ $ }) { + const response = await this.zendeskSell.createContact({ + $, + data: { + data: { + is_organization: this.isOrganization, + name: this.name, + first_name: this.firstName, + last_name: this.lastName, + customer_status: this.status, + title: this.title, + description: this.description, + email: this.email, + phone: this.phone, + }, + }, + }); + $.export("$summary", `Successfully created contact with ID ${response.data.id}`); + return response; + }, +}; diff --git a/components/zendesk_sell/actions/create-lead/create-lead.mjs b/components/zendesk_sell/actions/create-lead/create-lead.mjs new file mode 100644 index 0000000000000..42a855752168f --- /dev/null +++ b/components/zendesk_sell/actions/create-lead/create-lead.mjs @@ -0,0 +1,96 @@ +import zendeskSell from "../../zendesk_sell.app.mjs"; + +export default { + key: "zendesk_sell-create-lead", + name: "Create Lead", + description: "Creates a new lead. [See the documentation](https://developer.zendesk.com/api-reference/sales-crm/resources/leads/#create-a-lead).", + type: "action", + version: "0.0.1", + props: { + zendeskSell, + isOrganization: { + propDefinition: [ + zendeskSell, + "isOrganization", + ], + description: "Indicator of whether or not this lead refers to an organization or an individual", + reloadProps: true, + }, + status: { + propDefinition: [ + zendeskSell, + "status", + ], + description: "The status of the lead", + }, + title: { + propDefinition: [ + zendeskSell, + "title", + ], + description: "The lead’s job title", + }, + description: { + propDefinition: [ + zendeskSell, + "description", + ], + description: "The lead’s description", + }, + email: { + propDefinition: [ + zendeskSell, + "email", + ], + description: "The lead’s email address", + }, + phone: { + propDefinition: [ + zendeskSell, + "phone", + ], + description: "The lead’s phone number", + }, + }, + async additionalProps() { + const props = {}; + if (this.isOrganization) { + props.name = { + type: "string", + label: "Name", + description: "Name of the lead", + }; + } else { + props.firstName = { + type: "string", + label: "First Name", + description: "First name of the lead", + }; + props.lastName = { + type: "string", + label: "Last Name", + description: "Last name of the lead", + }; + } + return props; + }, + async run({ $ }) { + const response = await this.zendeskSell.createLead({ + $, + data: { + data: { + first_name: this.firstName, + last_name: this.lastName, + organization_name: this.name, + status: this.status, + title: this.title, + description: this.description, + email: this.email, + phone: this.phone, + }, + }, + }); + $.export("$summary", `Successfully created lead with ID ${response.data.id}`); + return response; + }, +}; diff --git a/components/zendesk_sell/actions/create-task/create-task.mjs b/components/zendesk_sell/actions/create-task/create-task.mjs new file mode 100644 index 0000000000000..dac6db39968b2 --- /dev/null +++ b/components/zendesk_sell/actions/create-task/create-task.mjs @@ -0,0 +1,86 @@ +import zendeskSell from "../../zendesk_sell.app.mjs"; + +export default { + key: "zendesk_sell-create-task", + name: "Create Task", + description: "Creates a new task. [See the documentation](https://developer.zendesk.com/api-reference/sales-crm/resources/tasks/#create-a-task).", + type: "action", + version: "0.0.1", + props: { + zendeskSell, + resourceType: { + type: "string", + label: "Resource Type", + description: "Name of the resource type the task is attached to", + options: [ + "contact", + "lead", + "deal", + ], + reloadProps: true, + }, + contactId: { + propDefinition: [ + zendeskSell, + "contactId", + ], + hidden: true, + }, + leadId: { + propDefinition: [ + zendeskSell, + "leadId", + ], + hidden: true, + }, + dealId: { + propDefinition: [ + zendeskSell, + "dealId", + ], + hidden: true, + }, + content: { + type: "string", + label: "Content", + description: "Content of the task", + }, + completed: { + type: "boolean", + label: "Completed", + description: "Indicator of whether the task is completed or not", + optional: true, + }, + dueDate: { + type: "string", + label: "Due Date", + description: "Date and time the task is due in UTC (ISO8601 format)", + optional: true, + }, + }, + async additionalProps(props) { + props.contactId.hidden = this.resourceType !== "contact"; + props.leadId.hidden = this.resourceType !== "lead"; + props.dealId.hidden = this.resourceType !== "deal"; + return {}; + }, + async run({ $ }) { + const response = await this.zendeskSell.createTask({ + $, + data: { + data: { + resource_type: this.resourceType, + resource_id: this.resourceType === "contact" + ? this.contactId + : this.resourceType === "lead" + ? this.leadId + : this.dealId, + content: this.content, + completed: this.completed, + due_date: this.dueDate, + }, + }, + }); + return response; + }, +}; diff --git a/components/zendesk_sell/package.json b/components/zendesk_sell/package.json new file mode 100644 index 0000000000000..c445f120a18a7 --- /dev/null +++ b/components/zendesk_sell/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/zendesk_sell", + "version": "0.1.0", + "description": "Pipedream Zendesk Sell Components", + "main": "zendesk_sell.app.mjs", + "keywords": [ + "pipedream", + "zendesk_sell" + ], + "homepage": "https://pipedream.com/apps/zendesk_sell", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" + } +} diff --git a/components/zendesk_sell/sources/common/base.mjs b/components/zendesk_sell/sources/common/base.mjs new file mode 100644 index 0000000000000..97b9a92952d86 --- /dev/null +++ b/components/zendesk_sell/sources/common/base.mjs @@ -0,0 +1,86 @@ +import zendeskSell from "../../zendesk_sell.app.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + props: { + zendeskSell, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + hooks: { + async deploy() { + await this.processEvent(25); + }, + }, + methods: { + _getLastTs() { + return this.db.get("lastTs") || 0; + }, + _setLastTs(lastTs) { + this.db.set("lastTs", lastTs); + }, + getTsField() { + return "created_at"; + }, + getParams() { + return { + sort_by: `${this.getTsField()}:desc`, + }; + }, + generateMeta(item) { + return { + id: item.id, + summary: this.getSummary(item), + ts: Date.parse(item[this.getTsField()]), + }; + }, + getResourceFn() { + throw new Error("getResourceFn is not implemented"); + }, + getSummary() { + throw new Error("getSummary is not implemented"); + }, + async processEvent(max) { + const lastTs = this._getLastTs(); + const fn = this.getResourceFn(); + const params = this.getParams(); + const tsField = this.getTsField(); + + const results = this.zendeskSell.paginate({ + fn, + params, + max, + }); + + const items = []; + for await (const result of results) { + const { data: item } = result; + const ts = Date.parse(item[tsField]); + if (ts >= lastTs) { + items.push(item); + } else { + break; + } + } + + if (!items?.length) { + return; + } + + this._setLastTs(Date.parse(items[0][tsField])); + + items.forEach((item) => { + const meta = this.generateMeta(item); + this.$emit(item, meta); + }); + }, + }, + async run() { + await this.processEvent(); + }, +}; diff --git a/components/zendesk_sell/sources/new-contact-created/new-contact-created.mjs b/components/zendesk_sell/sources/new-contact-created/new-contact-created.mjs new file mode 100644 index 0000000000000..f9b978d665624 --- /dev/null +++ b/components/zendesk_sell/sources/new-contact-created/new-contact-created.mjs @@ -0,0 +1,20 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "zendesk_sell-new-contact-created", + name: "New Contact Created", + description: "Emit new event when a new contact is created in Zendesk Sell.", + type: "source", + version: "0.0.1", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.zendeskSell.listContacts; + }, + getSummary(contact) { + return `New Contact ID: ${contact.id}`; + }, + }, +}; diff --git a/components/zendesk_sell/sources/new-deal-created/new-deal-created.mjs b/components/zendesk_sell/sources/new-deal-created/new-deal-created.mjs new file mode 100644 index 0000000000000..317231798499c --- /dev/null +++ b/components/zendesk_sell/sources/new-deal-created/new-deal-created.mjs @@ -0,0 +1,20 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "zendesk_sell-new-deal-created", + name: "New Deal Created", + description: "Emit new event when a new deal is created in Zendesk Sell.", + type: "source", + version: "0.0.1", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.zendeskSell.listDeals; + }, + getSummary(deal) { + return `New Deal ID: ${deal.id}`; + }, + }, +}; diff --git a/components/zendesk_sell/sources/new-lead-created/new-lead-created.mjs b/components/zendesk_sell/sources/new-lead-created/new-lead-created.mjs new file mode 100644 index 0000000000000..7af498231679a --- /dev/null +++ b/components/zendesk_sell/sources/new-lead-created/new-lead-created.mjs @@ -0,0 +1,20 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "zendesk_sell-new-lead-created", + name: "New Lead Created", + description: "Emit new event when a new lead is created in Zendesk Sell.", + type: "source", + version: "0.0.1", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.zendeskSell.listLeads; + }, + getSummary(lead) { + return `New Lead ID: ${lead.id}`; + }, + }, +}; diff --git a/components/zendesk_sell/zendesk_sell.app.mjs b/components/zendesk_sell/zendesk_sell.app.mjs new file mode 100644 index 0000000000000..09c30bf5a7820 --- /dev/null +++ b/components/zendesk_sell/zendesk_sell.app.mjs @@ -0,0 +1,173 @@ +import { axios } from "@pipedream/platform"; + +export default { + type: "app", + app: "zendesk_sell", + propDefinitions: { + contactId: { + type: "string", + label: "Contact ID", + description: "Identifier of a contact", + async options({ page }) { + const { items } = await this.listContacts({ + page: page + 1, + }); + return items?.map(({ data }) => ({ + value: data.id, + label: data.name || (`${data.first_name} ${data.last_name}`).trim(), + })) || []; + }, + }, + leadId: { + type: "string", + label: "Lead ID", + description: "Identifier of a lead", + async options({ page }) { + const { items } = await this.listLeads({ + page: page + 1, + }); + return items?.map(({ data }) => ({ + value: data.id, + label: data.organization_name || (`${data.first_name} ${data.last_name}`).trim(), + })) || []; + }, + }, + dealId: { + type: "string", + label: "Deal ID", + description: "Identifier of a deal", + async options({ page }) { + const { items } = await this.listDeals({ + page: page + 1, + }); + return items?.map(({ data }) => ({ + value: data.id, + label: data.name, + })) || []; + }, + }, + isOrganization: { + type: "boolean", + label: "Is Organization", + description: "Indicator of whether or not this contact refers to an organization or an individual", + }, + status: { + type: "string", + label: "Status", + description: "The customer status of the contact", + options: [ + "none", + "current", + "past", + ], + optional: true, + }, + title: { + type: "string", + label: "Title", + description: "The contact’s job title", + optional: true, + }, + description: { + type: "string", + label: "Description", + description: "The contact’s description", + optional: true, + }, + email: { + type: "string", + label: "Email", + description: "The contact’s email address", + optional: true, + }, + phone: { + type: "string", + label: "Phone", + description: "The contact’s phone number", + optional: true, + }, + }, + methods: { + _baseUrl() { + return "https://api.getbase.com/v2"; + }, + _makeRequest({ + $ = this, + path, + ...opts + }) { + return axios($, { + url: `${this._baseUrl()}${path}`, + headers: { + Authorization: `Bearer ${this.$auth.oauth_access_token}`, + accept: "application/json", + }, + ...opts, + }); + }, + listContacts(opts = {}) { + return this._makeRequest({ + path: "/contacts", + ...opts, + }); + }, + listLeads(opts = {}) { + return this._makeRequest({ + path: "/leads", + ...opts, + }); + }, + listDeals(opts = {}) { + return this._makeRequest({ + path: "/deals", + ...opts, + }); + }, + createContact(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/contacts", + ...opts, + }); + }, + createLead(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/leads", + ...opts, + }); + }, + createTask(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/tasks", + ...opts, + }); + }, + async *paginate({ + fn, + params, + max, + }) { + params = { + ...params, + per_page: 100, + page: 1, + }; + let total, count = 0; + do { + const { items } = await fn({ + params, + }); + for (const item of items) { + yield item; + if (max && ++count >= max) { + return; + } + } + total = items?.length; + params.page++; + } while (total); + }, + }, +}; diff --git a/components/zoho_sheet/.gitignore b/components/zoho_sheet/.gitignore deleted file mode 100644 index ec761ccab7595..0000000000000 --- a/components/zoho_sheet/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.js -*.mjs -dist \ No newline at end of file diff --git a/components/zoho_sheet/actions/create-row/create-row.mjs b/components/zoho_sheet/actions/create-row/create-row.mjs new file mode 100644 index 0000000000000..882a067ba3c02 --- /dev/null +++ b/components/zoho_sheet/actions/create-row/create-row.mjs @@ -0,0 +1,54 @@ +import { parseObject } from "../../common/utils.mjs"; +import zohoSheet from "../../zoho_sheet.app.mjs"; + +export default { + key: "zoho_sheet-create-row", + name: "Create Row", + description: "Creates a new row in the specified worksheet. [See the documentation](https://www.zoho.com/sheet/help/api/v2/)", + version: "0.0.1", + type: "action", + props: { + zohoSheet, + workbookId: { + propDefinition: [ + zohoSheet, + "workbookId", + ], + }, + worksheet: { + propDefinition: [ + zohoSheet, + "worksheet", + ({ workbookId }) => ({ + workbookId, + }), + ], + }, + headerRow: { + type: "integer", + label: "Header Row", + description: "Default value is 1. This can be mentioned if the table header is not in the first row of the worksheet.", + optional: true, + }, + data: { + propDefinition: [ + zohoSheet, + "data", + ], + }, + }, + async run({ $ }) { + const response = await this.zohoSheet.createRow({ + $, + workbookId: this.workbookId, + data: { + worksheet_id: this.worksheet, + header_row: this.headerRow || 1, + json_data: JSON.stringify(parseObject(this.data)), + }, + }); + + $.export("$summary", `Successfully created a row in the worksheet: ${response.sheet_name}`); + return response; + }, +}; diff --git a/components/zoho_sheet/actions/search-delete-row/search-delete-row.mjs b/components/zoho_sheet/actions/search-delete-row/search-delete-row.mjs new file mode 100644 index 0000000000000..da2705b497491 --- /dev/null +++ b/components/zoho_sheet/actions/search-delete-row/search-delete-row.mjs @@ -0,0 +1,88 @@ +import { ConfigurationError } from "@pipedream/platform"; +import { parseObject } from "../../common/utils.mjs"; +import zohoSheet from "../../zoho_sheet.app.mjs"; + +export default { + key: "zoho_sheet-search-delete-row", + name: "Search and Delete Row", + description: "Searches for a row based on provided criteria and deletes it. [See the documentation](https://www.zoho.com/sheet/help/api/v2/)", + version: "0.0.1", + type: "action", + props: { + zohoSheet, + workbookId: { + propDefinition: [ + zohoSheet, + "workbookId", + ], + }, + worksheet: { + propDefinition: [ + zohoSheet, + "worksheet", + ({ workbookId }) => ({ + workbookId, + }), + ], + }, + headerRow: { + propDefinition: [ + zohoSheet, + "headerRow", + ], + optional: true, + }, + criteria: { + propDefinition: [ + zohoSheet, + "criteria", + ], + optional: true, + }, + rowArray: { + type: "integer[]", + label: "Row Array", + description: "Array of row indexs, which needs to be deleted.", + optional: true, + }, + firstMatchOnly: { + propDefinition: [ + zohoSheet, + "firstMatchOnly", + ], + }, + isCaseSensitive: { + propDefinition: [ + zohoSheet, + "isCaseSensitive", + ], + }, + deleteRows: { + type: "boolean", + label: "Delete Rows", + description: "If true it will delete the rows completely, otherwise the records are only erased by default.", + default: false, + }, + }, + async run({ $ }) { + if (!this.criteria && !this.rowArray) { + throw new ConfigurationError("You must provide at least **Criteria** or **Row Array** to process this request."); + } + const response = await this.zohoSheet.deleteRow({ + $, + workbookId: this.workbookId, + data: { + worksheet_id: this.worksheet, + header_row: this.headerRow, + criteria: this.criteria, + row_array: JSON.stringify(parseObject(this.rowArray)), + first_match_only: this.firstMatchOnly, + is_case_sensitive: this.isCaseSensitive, + delete_rows: this.deleteRows, + }, + }); + + $.export("$summary", `Row matching criteria deleted successfully from worksheet ${this.worksheet}`); + return response; + }, +}; diff --git a/components/zoho_sheet/actions/update-row/update-row.mjs b/components/zoho_sheet/actions/update-row/update-row.mjs new file mode 100644 index 0000000000000..4dfd8bdf696e6 --- /dev/null +++ b/components/zoho_sheet/actions/update-row/update-row.mjs @@ -0,0 +1,81 @@ +import { parseObject } from "../../common/utils.mjs"; +import zohoSheet from "../../zoho_sheet.app.mjs"; + +export default { + key: "zoho_sheet-update-row", + name: "Update Row", + description: "Finds a specific row by its index and updates its content. [See the documentation](https://www.zoho.com/sheet/help/api/v2/)", + version: "0.0.1", + type: "action", + props: { + zohoSheet, + workbookId: { + propDefinition: [ + zohoSheet, + "workbookId", + ], + }, + worksheet: { + propDefinition: [ + zohoSheet, + "worksheet", + ({ workbookId }) => ({ + workbookId, + }), + ], + }, + headerRow: { + propDefinition: [ + zohoSheet, + "headerRow", + ], + optional: true, + }, + criteria: { + propDefinition: [ + zohoSheet, + "criteria", + ], + description: "If criteria is not set all available rows will get updated. Mention the criteria as described above.", + optional: true, + }, + firstMatchOnly: { + propDefinition: [ + zohoSheet, + "firstMatchOnly", + ], + description: "If true and if there are multiple records on the specified criteria, records will be updated for first match alone. Otherwise, all the matched records will be updated.", + }, + isCaseSensitive: { + propDefinition: [ + zohoSheet, + "isCaseSensitive", + ], + }, + data: { + propDefinition: [ + zohoSheet, + "data", + ], + type: "object", + description: "The JSON data that needs to be updated. Example:{\"Month\":\"May\",\"Amount\":50}", + }, + }, + async run({ $ }) { + const response = await this.zohoSheet.updateRow({ + $, + workbookId: this.workbookId, + data: { + worksheet_id: this.worksheet, + header_row: this.headerRow, + criteria: this.criteria, + first_match_only: this.firstMatchOnly, + is_case_sensitive: this.isCaseSensitive, + data: JSON.stringify(parseObject(this.data)), + }, + }); + + $.export("$summary", `Successfully updated ${response.no_of_affected_rows} row(s) in worksheet ${this.worksheet}`); + return response; + }, +}; diff --git a/components/zoho_sheet/app/zoho_sheet.app.ts b/components/zoho_sheet/app/zoho_sheet.app.ts deleted file mode 100644 index 7b57f52e4f210..0000000000000 --- a/components/zoho_sheet/app/zoho_sheet.app.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineApp } from "@pipedream/types"; - -export default defineApp({ - type: "app", - app: "zoho_sheet", - propDefinitions: {}, - methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); - }, - }, -}); diff --git a/components/zoho_sheet/common/utils.mjs b/components/zoho_sheet/common/utils.mjs new file mode 100644 index 0000000000000..dcc9cc61f6f41 --- /dev/null +++ b/components/zoho_sheet/common/utils.mjs @@ -0,0 +1,24 @@ +export const parseObject = (obj) => { + if (!obj) return undefined; + + if (Array.isArray(obj)) { + return obj.map((item) => { + if (typeof item === "string") { + try { + return JSON.parse(item); + } catch (e) { + return item; + } + } + return item; + }); + } + if (typeof obj === "string") { + try { + return JSON.parse(obj); + } catch (e) { + return obj; + } + } + return obj; +}; diff --git a/components/zoho_sheet/package.json b/components/zoho_sheet/package.json index dd257a1f16d70..7c6ae868bb86f 100644 --- a/components/zoho_sheet/package.json +++ b/components/zoho_sheet/package.json @@ -1,18 +1,18 @@ { "name": "@pipedream/zoho_sheet", - "version": "0.0.3", + "version": "0.1.0", "description": "Pipedream Zoho Sheet Components", - "main": "dist/app/zoho_sheet.app.mjs", + "main": "zoho_sheet.app.mjs", "keywords": [ "pipedream", "zoho_sheet" ], - "files": [ - "dist" - ], "homepage": "https://pipedream.com/apps/zoho_sheet", "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } } diff --git a/components/zoho_sheet/sources/common/base.mjs b/components/zoho_sheet/sources/common/base.mjs new file mode 100644 index 0000000000000..68b830a1fb559 --- /dev/null +++ b/components/zoho_sheet/sources/common/base.mjs @@ -0,0 +1,47 @@ +import zohoSheet from "../../zoho_sheet.app.mjs"; + +export default { + props: { + zohoSheet, + http: "$.interface.http", + db: "$.service.db", + serviceName: { + type: "string", + label: "Service Name", + description: "The name of the webhook.", + }, + }, + methods: { + getExtraData() { + return {}; + }, + }, + hooks: { + async activate() { + await this.zohoSheet.createWebhook({ + data: { + service_name: this.serviceName.replace(/\s/g, ""), + target_url: this.http.endpoint, + event: this.getEvent(), + ...this.getExtraData(), + }, + }); + }, + async deactivate() { + await this.zohoSheet.deleteWebhook({ + data: { + target_url: this.http.endpoint, + ...this.getExtraData(), + }, + }); + }, + }, + async run({ body }) { + const ts = Date.parse(new Date()); + this.$emit(body, { + id: `${ts}`, + summary: this.getSummary(body), + ts: ts, + }); + }, +}; diff --git a/components/zoho_sheet/sources/new-or-updated-row-instant/new-or-updated-row-instant.mjs b/components/zoho_sheet/sources/new-or-updated-row-instant/new-or-updated-row-instant.mjs new file mode 100644 index 0000000000000..645345c7f31e3 --- /dev/null +++ b/components/zoho_sheet/sources/new-or-updated-row-instant/new-or-updated-row-instant.mjs @@ -0,0 +1,54 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "zoho_sheet-new-or-updated-row-instant", + name: "New or Updated Row (Instant)", + description: "Emit new event whenever a row is added or modified.", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + workbookId: { + propDefinition: [ + common.props.zohoSheet, + "workbookId", + ], + }, + worksheetId: { + propDefinition: [ + common.props.zohoSheet, + "worksheet", + ({ workbookId }) => ({ + workbookId, + }), + ], + withLabel: true, + }, + alert: { + type: "alert", + alertType: "info", + content: "**New row** will be triggered only after the entire row is completed.", + }, + }, + methods: { + ...common.methods, + getEvent() { + return "update_worksheet"; + }, + getExtraData() { + return { + resource_id: this.workbookId, + worksheet_id: this.worksheetId.value, + }; + }, + getSummary({ updated_rows }) { + return `Row ${updated_rows[0].row_type === "NEW" + ? "created" + : "updated"} in worksheet ${this.worksheetId.label}`; + }, + }, + sampleEmit, +}; diff --git a/components/zoho_sheet/sources/new-or-updated-row-instant/test-event.mjs b/components/zoho_sheet/sources/new-or-updated-row-instant/test-event.mjs new file mode 100644 index 0000000000000..23b39924bf5be --- /dev/null +++ b/components/zoho_sheet/sources/new-or-updated-row-instant/test-event.mjs @@ -0,0 +1,17 @@ +export default { + "updated_rows" : [ + { + "Name": "John", + "Age": 24, + "Marks": 34, + "row_index": 3, + "row_type": "update" + } + ], + "header_row_index": 1, + "start_column": 1, + "end_column": 3, + "webhook_id": "", + "service_name": "", + "event": "update_worksheet" +} \ No newline at end of file diff --git a/components/zoho_sheet/sources/new-row-instant/new-row-instant.mjs b/components/zoho_sheet/sources/new-row-instant/new-row-instant.mjs new file mode 100644 index 0000000000000..7502ed121f841 --- /dev/null +++ b/components/zoho_sheet/sources/new-row-instant/new-row-instant.mjs @@ -0,0 +1,47 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "zoho_sheet-new-row-instant", + name: "New Row Created (Instant)", + description: "Emit new event each time a new row is created in a Zoho Sheet worksheet.", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + workbookId: { + propDefinition: [ + common.props.zohoSheet, + "workbookId", + ], + }, + worksheetId: { + propDefinition: [ + common.props.zohoSheet, + "worksheet", + ({ workbookId }) => ({ + workbookId, + }), + ], + withLabel: true, + }, + }, + methods: { + ...common.methods, + getEvent() { + return "new_row"; + }, + getExtraData() { + return { + resource_id: this.workbookId, + worksheet_id: this.worksheetId.value, + }; + }, + getSummary() { + return `New row in worksheet ${this.worksheetId.label}`; + }, + }, + sampleEmit, +}; diff --git a/components/zoho_sheet/sources/new-row-instant/test-event.mjs b/components/zoho_sheet/sources/new-row-instant/test-event.mjs new file mode 100644 index 0000000000000..42946d13d2528 --- /dev/null +++ b/components/zoho_sheet/sources/new-row-instant/test-event.mjs @@ -0,0 +1,17 @@ +export default { + "updated_rows" : [ + { + "Name": "John", + "Age": 24, + "Marks": 34, + "row_index": 3, + "row_type": "update" + } + ], + "header_row_index": 1, + "start_column": 1, + "end_column": 3, + "webhook_id": "", + "service_name": "", + "event": "new_row" +} \ No newline at end of file diff --git a/components/zoho_sheet/sources/new-workbook-instant/new-workbook-instant.mjs b/components/zoho_sheet/sources/new-workbook-instant/new-workbook-instant.mjs new file mode 100644 index 0000000000000..7de4a80469a0d --- /dev/null +++ b/components/zoho_sheet/sources/new-workbook-instant/new-workbook-instant.mjs @@ -0,0 +1,22 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "zoho_sheet-new-workbook-instant", + name: "New Workbook Created (Instant)", + description: "Emit new event whenever a new workbook is created.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEvent() { + return "new_workbook"; + }, + getSummary(event) { + return `New workbook: ${event.workbook_name} (${event.resource_id})`; + }, + }, + sampleEmit, +}; diff --git a/components/zoho_sheet/sources/new-workbook-instant/test-event.mjs b/components/zoho_sheet/sources/new-workbook-instant/test-event.mjs new file mode 100644 index 0000000000000..f591adf0eb33b --- /dev/null +++ b/components/zoho_sheet/sources/new-workbook-instant/test-event.mjs @@ -0,0 +1,8 @@ +export default { + "workbook_url": "", + "resource_id": "", + "workbook_name": "", + "webhook_id": "", + "service_name": "", + "event": "new_workbook" +} \ No newline at end of file diff --git a/components/zoho_sheet/zoho_sheet.app.mjs b/components/zoho_sheet/zoho_sheet.app.mjs new file mode 100644 index 0000000000000..12e2617345c12 --- /dev/null +++ b/components/zoho_sheet/zoho_sheet.app.mjs @@ -0,0 +1,147 @@ +import { axios } from "@pipedream/platform"; + +export default { + type: "app", + app: "zoho_sheet", + propDefinitions: { + workbookId: { + type: "string", + label: "Worksheet", + description: "The ID of the workbook (Spreadsheet)", + async options() { + const { workbooks } = await this.listWorkbooks({}); + return workbooks.map(({ + resource_id: value, workbook_name: label, + }) => ({ + label, + value, + })); + }, + }, + worksheet: { + type: "string", + label: "Worksheet", + description: "The ID of the worksheet (Sheet of the Spreadsheet)", + async options({ workbookId }) { + const { worksheet_names: worksheets } = await this.listWorksheets({ + workbookId, + }); + return worksheets.map(({ + worksheet_id: value, worksheet_name: label, + }) => ({ + label, + value, + })); + }, + }, + data: { + type: "string[]", + label: "JSON Data", + description: "An array of objects of the data for the row content, including the column headers as keys. **Example: {\"Name\":\"Joe\",\"Region\":\"South\",\"Units\":284}**", + }, + headerRow: { + type: "integer", + label: "Header Row", + description: "By default, first row of the worksheet is considered as header row. This can be used if tabular data starts from any row other than the first row..", + }, + criteria: { + type: "string", + label: "Criteria", + description: "Conditions to locate the row to delete. **Example: \"Month\"=\"March\" and \"Amount\">50**", + }, + firstMatchOnly: { + type: "boolean", + label: "First Match Only", + description: "If true and if there are multiple records on the specified criteria, records will be deleted for first match alone. Otherwise, all the matched records will be deleted. This parameter will be ignored if criteria is not mentioned.", + default: false, + }, + isCaseSensitive: { + type: "boolean", + label: "Is Case Sensitive", + description: "Can be set as false for case insensitive search.", + default: true, + }, + }, + methods: { + _baseUrl() { + return `https://sheet.${this.$auth.base_api_uri}/api/v2`; + }, + _headers() { + return { + "Authorization": `Zoho-oauthtoken ${this.$auth.oauth_access_token}`, + "Content-Type": "application/x-www-form-urlencoded", + }; + }, + _makeRequest({ + $ = this, path, data, method, ...opts + }) { + return axios($, { + method: "POST", + url: this._baseUrl() + path, + headers: this._headers(), + data: { + ...data, + method, + }, + ...opts, + }); + }, + listWorkbooks(opts = {}) { + return this._makeRequest({ + path: "/workbooks", + method: "workbook.list", + ...opts, + }); + }, + listWorksheets({ + workbookId, ...opts + }) { + return this._makeRequest({ + path: `/${workbookId}`, + method: "worksheet.list", + ...opts, + }); + }, + createRow({ + workbookId, ...opts + }) { + return this._makeRequest({ + path: `/${workbookId}`, + method: "worksheet.records.add", + ...opts, + }); + }, + deleteRow({ + workbookId, ...opts + }) { + return this._makeRequest({ + path: `/${workbookId}`, + method: "worksheet.records.delete", + ...opts, + }); + }, + updateRow({ + workbookId, ...opts + }) { + return this._makeRequest({ + path: `/${workbookId}`, + method: "worksheet.records.update", + ...opts, + }); + }, + createWebhook(opts = {}) { + return this._makeRequest({ + path: "/webhook", + method: "webhook.subscribe", + ...opts, + }); + }, + deleteWebhook(opts = {}) { + return this._makeRequest({ + path: "/webhook", + method: "webhook.unsubscribe", + ...opts, + }); + }, + }, +}; diff --git a/docs-v2/next.config.js b/docs-v2/next.config.js index 063cce71dd76f..4c466a512f454 100644 --- a/docs-v2/next.config.js +++ b/docs-v2/next.config.js @@ -48,7 +48,7 @@ module.exports = withNextra({ TMP_SIZE_LIMIT: "2GB", DELAY_MIN_MAX_TIME: "You can pause your workflow for as little as one millisecond, or as long as one year", - PUBLIC_APPS: "2,200", + PUBLIC_APPS: "2,400", FREE_INSPECTOR_EVENT_LIMIT: "7 days of events", WARM_WORKERS_INTERVAL: "10 minutes", WARM_WORKERS_CREDITS_PER_INTERVAL: "5", diff --git a/docs-v2/package.json b/docs-v2/package.json index b65fc47bc3da0..5e615985f3062 100644 --- a/docs-v2/package.json +++ b/docs-v2/package.json @@ -12,6 +12,7 @@ "homepage": "https://pipedream.com", "dependencies": { "@docsearch/react": "^3.6.1", + "@pipedream/sdk": "^0.1.9", "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", "@vercel/analytics": "^1.3.1", diff --git a/docs-v2/pages/_meta.json b/docs-v2/pages/_meta.json index b3b881ae820c2..0b3d4a0d22382 100644 --- a/docs-v2/pages/_meta.json +++ b/docs-v2/pages/_meta.json @@ -17,7 +17,7 @@ "event-history": "Event History", "http": "HTTP", "environment-variables": "Environment Variables", - "rest-api": "API Reference", + "rest-api": "REST API", "cli": "CLI", "destinations": "Destinations", "user-settings": "User and Billing Settings", diff --git a/docs-v2/pages/components/index.mdx b/docs-v2/pages/components/index.mdx index 9007652c2fcc8..bb61ed3273806 100644 --- a/docs-v2/pages/components/index.mdx +++ b/docs-v2/pages/components/index.mdx @@ -94,8 +94,8 @@ Finally, the target app must be integrated with Pipedream. You can explore all a ### Quickstart Guides -- [Sources](quickstart/nodejs/sources/) -- [Actions](quickstart/nodejs/actions/) +- [Sources](/components/sources-quickstart/) +- [Actions](/components/actions-quickstart/) ### Component API Reference diff --git a/docs-v2/pages/connect/_meta.json b/docs-v2/pages/connect/_meta.json index 3fdc2240f308f..6673166a10cb2 100644 --- a/docs-v2/pages/connect/_meta.json +++ b/docs-v2/pages/connect/_meta.json @@ -3,12 +3,33 @@ "title": "Overview" }, "use-cases": { - "title": "Use cases" + "title": "Use Cases" }, "quickstart": { "title": "Quickstart" }, "api": { "title": "API & SDK Reference" + }, + "tokens": { + "title": "Connect Tokens" + }, + "environments": { + "title": "Environments" + }, + "oauth-clients": { + "title": "OAuth Clients" + }, + "webhooks": { + "title": "Webhooks" + }, + "connect-link": { + "title": "Connect Link" + }, + "customize-your-app": { + "title": "Customize Your App" + }, + "migrating-from-project-keys-to-oauth": { + "display": "hidden" } } diff --git a/docs-v2/pages/connect/api.mdx b/docs-v2/pages/connect/api.mdx index 86de64dcf24cb..408c477dd7333 100644 --- a/docs-v2/pages/connect/api.mdx +++ b/docs-v2/pages/connect/api.mdx @@ -5,14 +5,12 @@ import { Tabs } from 'nextra/components' Pipedream provides a TypeScript SDK and a REST API to interact with the Connect service. You'll find examples using the SDK and the REST API in multiple languages below. - -Note that both the Base URL and authentication method for the Connect REST API are different from the rest of Pipedream's REST API. - - ## REST API Base URL +Pipedream Connect resources are scoped to [projects](/projects), so you'll need to pass [the project's ID](/projects#finding-your-projects-id) as a part of the base URL: + ``` -https://api.pipedream.com/v1/connect +https://api.pipedream.com/v1/connect/{project_id} ``` ## Installing the TypeScript SDK @@ -43,19 +41,23 @@ or a specific version: ## Authentication +See the [REST API Authentication docs](/rest-api/auth). + ### TypeScript SDK (Server) Most of your interactions with the Connect API will happen on the server, to protect API requests and user credentials. You'll use the SDK in [your frontend](#typescript-sdk-browser) to let users connect accounts. Once connected, you'll use the SDK on the server to retrieve credentials, invoke workflows on their behalf, and more. -Find your [project keys](/connect/quickstart#get-your-project-keys) and instantiate the SDK client: +[Create a Pipedream OAuth client](/rest-api/auth#oauth) and instantiate the SDK with your client ID and secret: ```typescript -import { createClient } from "@pipedream/sdk"; +import { createBackendClient } from "@pipedream/sdk"; // These secrets should be saved securely and passed to your environment -const pd = createClient({ - publicKey: "YOUR_PUBLIC_KEY", - secretKey: "YOUR_SECRET_KEY", +const pd = createBackendClient({ + credentials: { + clientId: "your-oauth-client-id", + clientSecret: "your-oauth-client-secret", + } }); // The `pd` client provides methods to interact with the Connect API — see below @@ -71,8 +73,7 @@ You'll primarily use the browser SDK to let your users securely connect apps fro Here's a Next.js example [from our quickstart](/connect/quickstart): ```typescript -// Note that we import the browser-specific SDK client here -import { createClient } from "@pipedream/sdk/browser" +import { createFrontendClient } from "@pipedream/sdk" // Example from our Next.js app import { serverConnectTokenCreate } from "./server" @@ -81,10 +82,11 @@ const { token, expires_at } = await serverConnectTokenCreate({ }); export default function Home() { - const pd = createClient({}) + const pd = createFrontendClient() function connectAccount() { pd.connectAccount({ - app: appSlug, + app: appSlug, // Pass the app name slug of the app you want to integrate + oauthAppId: appId, // For OAuth apps, pass the OAuth app ID; omit this param to use Pipedream's OAuth client or for key-based apps token, // The token you received from your server above onSuccess: ({ id: accountId }) => { console.log(`Account successfully connected: ${accountId}`) @@ -94,480 +96,236 @@ export default function Home() { return (
- +
) } ``` -### REST API +## Environment -You authenticate to the Connect API using **Basic Auth**. Send your project public key as the username and the project secret key as the password. When you make API requests, pass an -`Authorization` header of the following format: +Some API endpoints accept an [environment](/connect/environments) parameter. This lets you specify the environment (`production` or `development`) where resources will live in your project. -```shell -Authorization: Basic base_64(public_key:secret_key) +You can set the environment when you create the SDK client: + +```typescript +import { createBackendClient } from "@pipedream/sdk"; + +const pd = createBackendClient({ + environment: "development", // change to production if running for a test production account, or in production + credentials: { + clientId: "your-oauth-client-id", + clientSecret: "your-oauth-client-secret", + } +}); ``` -Clients like `cURL` will often make this easy. For example, here's how you list all accounts on a project: +or pass the `X-PD-Environment` header in HTTP requests: -```shell -curl 'https://api.pipedream.com/v1/connect/accounts' -u public_key:secret_key +```bash +curl -X POST https://api.pipedream.com/v1/connect/{project_id}/tokens \ + -H "Content-Type: application/json" \ + -H "X-PD-Environment: development" \ + -H "Authorization: Bearer {access_token}" \ + -d '{ + "external_user_id": "your-external-user-id" + }' ``` ## External users -When you use the Connect API, you'll pass an `external_id` parameter when initiating account connections and retrieving credentials. This is your user's ID, in your system — whatever you use to uniquely identify them. +When you use the Connect API, you'll pass an `external_user_id` parameter when initiating account connections and retrieving credentials. This is your user's ID, in your system — whatever you use to uniquely identify them. Pipedream associates this ID with user accounts, so you can retrieve credentials for a specific user, and invoke workflows on their behalf. -External IDs are limited to 250 characters. +External User IDs are limited to 250 characters. ## Rate limits | API Endpoint | Rate Limit | |----------------------------|------------------------------------------------------| -| `POST /tokens` | 100 requests per minute per `external_id` | +| `POST /tokens` | 100 requests per minute per `external_user_id` | | `GET */accounts/*`| The sum of requests across all `*/accounts/*` endpoints must not exceed 100 requests per minute. This includes requests to `/accounts`, `/apps/:app_id/accounts`, `/accounts/:account_id`, and more — any request for account metadata and credentials is counted towards this total. | If you need higher rate limits, please [reach out](https://pipedream.com/support). ## API Reference -### Tokens - -Your app will initiate the account connection flow for your end users in your frontend. But you can't expose your project keys in the client, since they'd be accessible to anyone. Instead, on your server, **you exchange your project keys for a short-lived token that allows a specific user to connect a specific app**, and return that token to your frontend. - -#### Create a new token - -``` -POST /tokens -``` - -##### Parameters - -- `external_id` - [The external user ID](#external-users) in your system -- `success_redirect_uri` — _Optional_. The URL to redirect the user to after they successfully connect an account -- `error_redirect_uri` — _Optional_. The URL to redirect the user to if they encounter an error during the connection flow +### Invoke workflows -##### Examples - -To create a short-lived token via TypeScript / JavaScript SDK, you'll need to create a Pipedream API client and call the `connectTokenCreate` method. In our example app, this code is in `app/server.ts`. - -In other languages, you'll need to make an HTTP POST request to the `/tokens` endpoint to create a token, then return the token to your frontend. Click into other tabs to see examples in additional languages. +You can use the SDK to invoke workflows on behalf of any end user. **Write one workflow, run it for all of your users**. - + ```typescript -"use server"; - -import { - createClient, - type ConnectAPIResponse, - type ConnectTokenCreateOpts, - type ConnectTokenResponse, -} from "@pipedream/sdk"; - -const pd = createClient({ - publicKey: process.env.PIPEDREAM_PROJECT_PUBLIC_KEY, - secretKey: process.env.PIPEDREAM_PROJECT_SECRET_KEY, +import { createBackendClient, HTTPAuthType } from "@pipedream/sdk"; + +// These secrets should be saved securely and passed to your environment +const pd = createBackendClient({ + credentials: { + clientId: "YOUR_CLIENT_ID", + clientSecret: "YOUR_CLIENT_SECRET", + }, }); -export async function serverConnectTokenCreate(opts: ConnectTokenCreateOpts): Promise> { - return pd.connectTokenCreate(opts); -} - -const { token, expires_at } = await serverConnectTokenCreate({ - external_user_id: externalUserId // The end user's ID in your system -}); +await pd.invokeWorkflowForExternalUser( + "enabc123", // pass the endpoint ID or full URL here + "external_user_id", // The end user's ID in your system + { + method: "POST", + body: { + key: "value", + } + }, + HTTPAuthType.OAuth // Will automatically send the Authorization header with a fresh token +) ``` ```javascript -const fetch = require('node-fetch'); - -class Client { - constructor(opts) { - this.secretKey = opts.secretKey; - this.publicKey = opts.publicKey; - - const apiHost = 'api.pipedream.com'; - this.baseURL = `https://${apiHost}`; - } - - _authorizationHeader() { - const encoded = Buffer.from(`${this.publicKey}:${this.secretKey}`).toString('base64'); - return `Basic ${encoded}`; - } +import { createBackendClient } from "@pipedream/sdk"; - async connectTokenCreate(opts) { - const auth = this._authorizationHeader(); - const response = await fetch(`${this.baseURL}/v1/connect/tokens`, { - method: 'POST', - headers: { - 'Authorization': auth, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(opts), - }); - - return response.json(); - } -} - -const client = new Client({ - publicKey: 'YOUR_PUBLIC_KEY', - secretKey: 'YOUR_SECRET_KEY', +// These secrets should be saved securely and passed to your environment +const pd = createBackendClient({ + credentials: { + clientId: "YOUR_CLIENT_ID", + clientSecret: "YOUR_CLIENT_SECRET", + }, }); -const connectTokenOpts = { - external_id: "USER_ID" // The end user's ID in your system -} - -// Expose this code as an API endpoint in your server to fetch the token from the frontend -client.connectTokenCreate(connectTokenOpts) - .then(response => { - // return the token to the frontend - }) - .catch(error => { - // handle errors - }); -``` - - -```python -import base64 -import json -import requests - -class Client: - def __init__(self, opts): - self.secret_key = opts['secret_key'] - self.public_key = opts['public_key'] - - api_host = 'api.pipedream.com' - self.base_url = f"https://{api_host}" - - def _authorization_header(self): - encoded = base64.b64encode(f"{self.public_key}:{self.secret_key}".encode()).decode() - return f"Basic {encoded}" - - def connect_token_create(self, opts): - auth = self._authorization_header() - response = requests.post( - f"{self.base_url}/v1/connect/tokens", - headers={ - "Authorization": auth, - "Content-Type": "application/json", - }, - data=json.dumps(opts) - ) - return response.json() - -# Usage example -client = Client({ - 'public_key': 'YOUR_PUBLIC_KEY', - 'secret_key': 'YOUR_SECRET_KEY', -}) - -connect_token_opts = { - 'external_id': "USER_ID" -} - -# Expose this code as an API endpoint in your server to fetch the token from the frontend -response = client.connect_token_create(connect_token_opts) - +await pd.invokeWorkflowForExternalUser( + "enabc123", // pass the endpoint ID or full URL here + "external_user_id", // The end user's ID in your system + { + method: "POST", + body: { + key: "value", + } + }, + "oauth" // Will automatically send the Authorization header with a fresh token +) ``` - -```java -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Base64; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; - -public class Client { - private String secretKey; - private String publicKey; - private String baseURL; - - public Client(String secretKey, String publicKey) { - this.publicKey = publicKey; - this.secretKey = secretKey; - - String apiHost = "api.pipedream.com"; - this.baseURL = "https://" + apiHost; - } + - private String authorizationHeader() { - String encoded = Base64.getEncoder().encodeToString((publicKey + ":" + secretKey).getBytes(StandardCharsets.UTF_8)); - return "Basic " + encoded; - } +See the [workflow invocation docs](/workflows/triggers#oauth) for more details. - public String connectTokenCreate(String externalId) throws Exception { - String auth = authorizationHeader(); - URL url = new URL(baseURL + "/v1/connect/tokens"); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("POST"); - conn.setRequestProperty("Authorization", auth); - conn.setRequestProperty("Content-Type", "application/json"); - conn.setDoOutput(true); +### Tokens - String jsonInputString = String.format("{\"external_id\":\"%s\"}", externalId); +Your app will initiate the account connection flow for your end users in your frontend. To securely scope connection to a specific end user, on your server, **you retrieve a short-lived token for that user**, and return that token to your frontend. - try (OutputStream os = conn.getOutputStream()) { - byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8); - os.write(input, 0, input.length); - } +See [the Connect tokens docs](/connect/tokens) for more information. - return new String(conn.getInputStream().readAllBytes(), StandardCharsets.UTF_8); - } +#### Create a new token - public static void main(String[] args) throws Exception { - Client client = new Client("YOUR_SECRET_KEY", "YOUR_PUBLIC_KEY"); +``` +POST /{project_id}/tokens +``` - // Expose this code as an API endpoint in your server to fetch the token from the frontend - String response = client.connectTokenCreate("USER_ID"); - } -} +##### Path parameters -``` - - -```csharp -using System; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; - -public class Client { - private string secretKey; - private string publicKey; - private string baseURL; - - public Client(string secretKey, string publicKey) { - this.publicKey = publicKey; - this.secretKey = secretKey; - - string apiHost = "api.pipedream.com"; - this.baseURL = $"https://{apiHost}"; - } +`project_id` **string** - private string AuthorizationHeader() { - string encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{publicKey}:{secretKey}")); - return $"Basic {encoded}"; - } +[The project's ID](/projects#finding-your-projects-id) - public async Task ConnectTokenCreate(string externalId) { - string auth = AuthorizationHeader(); - using (HttpClient client = new HttpClient()) { - client.DefaultRequestHeaders.Add("Authorization", auth); - client.DefaultRequestHeaders.Add("Content-Type", "application/json"); +##### Body parameters - var content = new StringContent($"{{\"external_id\":\"{externalId}\"}}", Encoding.UTF8, "application/json"); - var response = await client.PostAsync($"{baseURL}/v1/connect/tokens", content); +`external_user_id` **string** - return await response.Content.ReadAsStringAsync(); - } - } +The ID of your end user. Use whatever ID uniquely identifies the user in your system. - public static async Task Main(string[] args) { - var client = new Client("YOUR_SECRET_KEY", "YOUR_PUBLIC_KEY"); +--- - // Expose this code as an API endpoint in your server to fetch the token from the frontend - string response = await client.ConnectTokenCreate("USER_ID"); - } -} +`success_redirect_uri` **string** (_optional_) -``` - - -```go -package main - -import ( - "bytes" - "encoding/base64" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" -) +When using [Connect Link](/connect/connect-link), you can optionally redirect your end user to the `success_redirect_uri` on successful completion of the auth flow. -type Client struct { - SecretKey string - PublicKey string - BaseURL string -} +--- -func NewClient(secretKey, publicKey string) *Client { - apiHost := "api.pipedream.com" - baseURL := fmt.Sprintf("https://%s", apiHost) +`error_redirect_uri` **string** (_optional_) - return &Client{ - PublicKey: publicKey, - SecretKey: secretKey, - } -} +When using [Connect Link](/connect/connect-link), you can optionally redirect your end user to the `error_redirect_uri` on any errors in the auth flow. This lets you handle errors in whatever way you want in your own app. -func (c *Client) authorizationHeader() string { - encoded := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", c.PublicKey, c.SecretKey))) - return fmt.Sprintf("Basic %s", encoded) -} +--- -func (c *Client) ConnectTokenCreate(externalId string) (map[string]interface{}, error) { - auth := c.authorizationHeader() - url := fmt.Sprintf("%s/v1/connect/tokens", c.BaseURL) +`webhook_uri` **string** (_optional_) - opts := map[string]string{ - "external_id": externalId, - } +Pipedream will send events on successful auth, or any errors, to this URL via webhook. [See the webhooks docs](/connect/webhooks) for more information. - jsonData, err := json.Marshal(opts) - if err != nil { - return nil, err - } +--- - req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) - if err != nil { - return nil, err - } +`environment_name` **string** (_optional_) - req.Header.Set("Authorization", auth) - req.Header.Set("Content-Type", "application/json") +Specify the environment (`production` or `development`) to use for the account connection flow. Defaults to `production`. - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() +##### Examples - body, _ := ioutil.ReadAll(resp.Body) +To create a short-lived token via TypeScript / JavaScript SDK, you'll need to create a Pipedream API client and call the `createConnectToken` method. In our example app, this code is in `app/server.ts`. - var result map[string]interface{} - json.Unmarshal(body, &result) - return result, nil -} +In other languages, you'll need to make an HTTP POST request to the `/tokens` endpoint to create a token, then return the token to your frontend. Click into other tabs to see examples in additional languages. -func main() { - client := NewClient("YOUR_SECRET_KEY", "YOUR_PUBLIC_KEY") + + +```typescript +import { + createBackendClient, + type ConnectAPIResponse, + type ConnectTokenCreateOpts, + type ConnectTokenResponse, +} from "@pipedream/sdk"; - // Expose this code as an API endpoint in your server to fetch the token from the frontend - response, err := client.ConnectTokenCreate( "USER_ID") - if err != nil { - fmt.Println("Error:", err) - return - } -} +const pd = createBackendClient({ + environment: "development", // change to production if running for a test production account, or in production + credentials: { + clientId: "your-oauth-client-id", + clientSecret: "your-oauth-client-secret", + } +}); +const { token, expires_at } = await pd.createConnectToken({ + project_id: "your-project-id", + external_user_id: "your-external-user-id" // The end user's ID in your system +}); ``` -```php -publicKey = $publicKey; - $this->secretKey = $secretKey; - - $apiHost = 'api.pipedream.com'; - $this->baseURL = "https://$apiHost"; - } - - private function authorizationHeader() { - $encoded = base64_encode("$this->publicKey:$this->secretKey"); - return "Basic $encoded"; - } - - public function connectTokenCreate($externalId) { - $auth = $this->authorizationHeader(); - $url = "$this->baseURL/v1/connect/tokens"; - - $data = json_encode([ - 'external_id' => $externalId - ]); - - $options = [ - 'http' => [ - 'header' => [ - "Authorization: $auth", - "Content-Type: application/json", - ], - 'method' => 'POST', - 'content' => $data, - ], - ]; - - $context = stream_context_create($options); - $result = file_get_contents($url, false, $context); - - return json_decode($result, true); - } -} - -// Usage example -$client = new Client('YOUR_SECRET_KEY', 'YOUR_PUBLIC_KEY'); +```javascript +import { createBackendClient } from "@pipedream/sdk"; -$connectTokenOpts = [ - 'external_id' => "USER_ID" -]; +const pd = createBackendClient({ + environment: "development", // change to production if running for a test production account, or in production + credentials: { + clientId: "your-oauth-client-id", + clientSecret: "your-oauth-client-secret", + }, +}); -// Expose this code as an API endpoint in your server to fetch the token from the frontend -$response = $client->connectTokenCreate($connectTokenOpts['external_id']); -?> +const { token, expires_at } = await pd.createConnectToken({ + project_id: "your-project-id", + external_user_id: "your-external-user-id", // The end user's ID in your system +}); ``` -```ruby -require 'base64' -require 'json' -require 'net/http' -require 'uri' - -class Client - def initialize(secret_key, public_key) - @public_key = public_key - @secret_key = secret_key - - api_host = 'api.pipedream.com' - @base_url = "https://#{api_host}" - end - - def authorization_header - encoded = Base64.strict_encode64("#{@public_key}:#{@secret_key}") - "Basic #{encoded}" - end - - def connect_token_create(app_slug, oauth_app_id, external_id) - uri = URI("#{@base_url}/v1/connect/tokens") - req = Net::HTTP::Post.new(uri) - req['Authorization'] = authorization_header - req['Content-Type'] = 'application/json' - req.body = { external_id: external_id }.to_json - - res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| - http.request(req) - end - - JSON.parse(res.body) - end -end - -client = Client.new('YOUR_SECRET_KEY', 'YOUR_PUBLIC_KEY') - -connect_token_opts = { - external_id: "USER_ID" -} - -# Expose this code as an API endpoint in your server to fetch the token from the frontend -response = client.connect_token_create(connect_token_opts[:external_id]) +```bash +# First, obtain an OAuth access token +curl -X POST https://api.pipedream.com/v1/oauth/token \ + -H "Content-Type: application/json" \ + -d '{ + "grant_type": "client_credentials", + "client_id": "{oauth_client_id}", + "client_secret": "{oauth_client_secret}" + }' + +# The response will include an access_token. Use it in the Authorization header below. + +curl -X POST https://api.pipedream.com/v1/connect/{project_id}/tokens \ + -H "Content-Type: application/json" \ + -H "X-PD-Environment: development" \ + -H "Authorization: Bearer {access_token}" \ + -d '{ + "external_user_id": "your-external-user-id" + }' ``` @@ -576,410 +334,102 @@ response = client.connect_token_create(connect_token_opts[:external_id]) #### List all accounts -List all connected accounts for all end users within your project - - -This endpoint is not currently paginated, so we'll attempt to return all connected accounts for all users within your project. We intend to add pagination soon. - +List all connected accounts for all end users within a project. ``` -GET /accounts/ +GET /{project_id}/accounts/ ``` -##### Parameters - -- `include_credentials` — _Optional_. Pass `include_credentials=1` as a query-string parameter to include the account credentials in the response - -##### Examples +##### Path parameters - - -```typescript -import { - createClient, -} from "@pipedream/sdk"; +`project_id` **string** -const pd = createClient({ - publicKey: "YOUR_PUBLIC_KEY", - secretKey: "YOUR_SECRET_KEY", -}); +[The project's ID](/projects#finding-your-projects-id) -export async function getAccounts(include_credentials: number = 0) { - const accounts = await pd.getAccounts({ - include_credentials, // set to 1 to include credentials - }); +##### Query parameters - // Parse and return the data you need. These may contain credentials, - // which you should never return to the client - return accounts; -} +`app` **string** (_optional_) -``` - - -```javascript -const fetch = require('node-fetch'); +The ID or name slug the app you'd like to retrieve. For example, Slack's unique app ID is `app_OkrhR1`, and its name slug is `slack`. -class Client { - constructor({ publicKey, secretKey }) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = 'https://api.pipedream.com/v1'; - } +You can find the app's ID in the response from the [List apps](#list-apps) endpoint, and the name slug under the **Authentication** section of any [app page](https://pipedream.com/apps). - _authorizationHeader() { - const encoded = Buffer.from(`${this.publicKey}:${this.secretKey}`).toString('base64'); - return `Basic ${encoded}`; - } +--- - async getAccounts() { - const auth = this._authorizationHeader(); - const url = `${this.baseURL}/connect/accounts?include_credentials=1`; +`oauth_app_id` **string** (_optional_) - const response = await fetch(url, { - method: 'GET', - headers: { - 'Authorization': auth, - 'Content-Type': 'application/json', - }, - }); +The ID of the custom [OAuth app](/connect/quickstart#creating-a-custom-oauth-client) you'd like to retrieve accounts for. - const data = await response.json(); +--- - if (!data?.accounts?.length) { - return null; - } +`external_user_id` **string** (_optional_) - // Parse and return data.accounts. Ensure to handle sensitive data appropriately. - return data.accounts; - } -} +[The external user ID](/connect/api/#external-users) in your system that you want to retrieve accounts for. -// Usage example -const client = new Client({ - publicKey: 'YOUR_PUBLIC_KEY', - secretKey: 'YOUR_SECRET_KEY', -}); +--- -client.getAccounts() - .then(accounts => { - console.log(accounts); - }) - .catch(error => { - console.error('Error:', error); - }); -``` - - -```python -import base64 -import requests - -class Client: - def __init__(self, public_key, secret_key): - self.public_key = public_key - self.secret_key = secret_key - self.base_url = "https://api.pipedream.com/v1" - - def _authorization_header(self): - encoded = base64.b64encode(f"{self.public_key}:{self.secret_key}".encode()).decode() - return f"Basic {encoded}" - - def get_accounts(self): - auth = self._authorization_header() - url = f"{self.base_url}/connect/accounts?include_credentials=1" - response = requests.get(url, headers={"Authorization": auth}) - data = response.json() - - if not data.get('accounts'): - return None - - # Parse and return data['accounts']. Ensure to handle sensitive data appropriately. - return data['accounts'] - -# Usage example -client = Client('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY') -accounts = client.get_accounts() -print(accounts) -``` - - -```java -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Base64; -import java.io.InputStreamReader; -import java.io.BufferedReader; -import java.nio.charset.StandardCharsets; - -public class Client { - private String publicKey; - private String secretKey; - private String baseURL; - - public Client(String publicKey, String secretKey) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = "https://api.pipedream.com/v1"; - } +`include_credentials` **boolean** (_optional_) - private String authorizationHeader() { - String encoded = Base64.getEncoder().encodeToString((publicKey + ":" + secretKey).getBytes(StandardCharsets.UTF_8)); - return "Basic " + encoded; - } +Pass `include_credentials=true` as a query-string parameter to include the account credentials in the response - public String getAccounts() throws Exception { - String auth = authorizationHeader(); - URL url = new URL(baseURL + "/connect/accounts?include_credentials=1"); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("GET"); - conn.setRequestProperty("Authorization", auth); - - BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); - String inputLine; - StringBuilder content = new StringBuilder(); - while ((inputLine = in.readLine()) != null) { - content.append(inputLine); - } - in.close(); - - String response = content.toString(); - - // Parse response and return accounts data. Ensure to handle sensitive data appropriately. - if (!response.contains("accounts")) { - return null; - } - - return response; // Modify to parse and handle accounts as needed. - } +##### Examples - public static void main(String[] args) throws Exception { - Client client = new Client("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY"); - String response = client.getAccounts(); - System.out.println(response); - } -} -``` - + -```csharp -using System; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Threading.Tasks; - -public class Client { - private string publicKey; - private string secretKey; - private string baseURL; - - public Client(string publicKey, string secretKey) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = "https://api.pipedream.com/v1"; - } - - private string AuthorizationHeader() { - string encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{publicKey}:{secretKey}")); - return $"Basic {encoded}"; - } - - public async Task GetAccounts() { - string auth = AuthorizationHeader(); - using (HttpClient client = new HttpClient()) { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", auth); - HttpResponseMessage response = await client.GetAsync($"{baseURL}/connect/accounts?include_credentials=1"); - - string result = await response.Content.ReadAsStringAsync(); +```typescript +import { + createBackendClient, +} from "@pipedream/sdk"; - // Parse and return accounts data. Ensure to handle sensitive data appropriately. - if (!result.Contains("accounts")) { - return null; - } +const pd = createBackendClient({ + credentials: { + clientId: "your-oauth-client-id", + clientSecret: "your-oauth-client-secret", + } +}); - return result; // Modify to parse and handle accounts as needed. - } - } +const accounts = await pd.getAccounts({ + include_credentials: true, // set to true to include credentials +}); - public static async Task Main(string[] args) { - var client = new Client("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY"); - string accounts = await client.GetAccounts(); - Console.WriteLine(accounts); - } -} +// Parse and return the data you need. These may contain credentials, +// which you should never return to the client ``` -```go -package main - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" -) - -type Client struct { - PublicKey string - SecretKey string - BaseURL string -} - -func NewClient(publicKey, secretKey string) *Client { - return &Client{ - PublicKey: publicKey, - SecretKey: secretKey, - BaseURL: "https://api.pipedream.com/v1", - } -} - -func (c *Client) authorizationHeader() string { - encoded := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", c.PublicKey, c.SecretKey))) - return fmt.Sprintf("Basic %s", encoded) -} - -func (c *Client) GetAccounts() (map[string]interface{}, error) { - url := fmt.Sprintf("%s/connect/accounts?include_credentials=1", c.BaseURL) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, err - } - - req.Header.Set("Authorization", c.authorizationHeader()) - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - var result map[string]interface{} - err = json.Unmarshal(body, &result) - if err != nil { - return nil, err - } - - if accounts, exists := result["accounts"]; exists { - return accounts.([]interface{}), nil - } - - return nil, nil -} +```javascript +import { createBackendClient } from "@pipedream/sdk"; -func main() { - client := NewClient("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY") +const pd = createBackendClient({ + credentials: { + clientId: "your-oauth-client-id", + clientSecret: "your-oauth-client-secret", + } +}); - accounts, err := client.GetAccounts() - if err != nil { - fmt.Println("Error:", err) - return - } +const accounts = await pd.getAccounts({ + include_credentials: true, // set to true to include credentials +}); - fmt.Println(accounts) -} +// Parse and return the data you need. These may contain credentials, +// which you should never return to the client ``` -```php -publicKey = $publicKey; - $this->secretKey = $secretKey; - $this->baseURL = "https://api.pipedream.com/v1"; - } - - private function authorizationHeader() { - return "Basic " . base64_encode("$this->publicKey:$this->secretKey"); - } - - public function getAccounts() { - $url = "$this->baseURL/connect/accounts?include_credentials=1"; - $opts = [ - 'http' => [ - 'header' => "Authorization: " . $this->authorizationHeader(), - 'method' => 'GET', - ], - ]; - - $context = stream_context_create($opts); - $result = file_get_contents($url, false, $context); - - if ($result === FALSE) { - return null; - } - - $data = json_decode($result, true); - - if (empty($data['accounts'])) { - return null; - } +```bash +# First, obtain an OAuth access token +curl -X POST https://api.pipedream.com/v1/oauth/token \ + -H "Content-Type: application/json" \ + -d '{ + "grant_type": "client_credentials", + "client_id": "{oauth_client_id}", + "client_secret": "{oauth_client_secret}" + }' - return $data['accounts']; // Modify to parse and handle accounts as needed. - } -} +# The response will include an access_token. Use it in the Authorization header below. -// Usage example -$client = new Client('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY'); -$accounts = $client->getAccounts(); -print_r($accounts); -?> -``` - - -```ruby -require 'base64' -require 'json' -require 'net/http' -require 'uri' - -class Client - def initialize(public_key, secret_key) - @public_key = public_key - @secret_key = secret_key - @base_url = "https://api.pipedream.com/v1" - end - - def authorization_header - encoded = Base64.strict_encode64("#{@public_key}:#{@secret_key}") - "Basic #{encoded}" - end - - def get_accounts - uri = URI("#{@base_url}/connect/accounts?include_credentials=1") - req = Net::HTTP::Get.new(uri) - req['Authorization'] = authorization_header - - res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| - http.request(req) - end - - data = JSON.parse(res.body) - - # Parse and return accounts data. Ensure to handle sensitive data appropriately. - return nil if data['accounts'].nil? || data['accounts'].empty? - - data['accounts'] - end -end - -# Usage example -client = Client.new('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY') -accounts = client.get_accounts -puts accounts.inspect +curl -X GET "https://api.pipedream.com/v1/connect/{project_id}/accounts/?include_credentials=true" \ + -H "Authorization: Bearer {access_token}" ``` @@ -1060,1304 +510,102 @@ puts accounts.inspect } ``` -#### List all accounts for an app +#### Retrieve account details by ID + +Retrieve the account details for a specific account based on the account ID ``` -GET /apps/:app_id/accounts -``` +GET /{project_id}/accounts/{account_id} +``` + +##### Path parameters + +`project_id` **string** + +[The project's ID](/projects#finding-your-projects-id) + +--- + +`account_id` **string** + +The ID of the account you want to retrieve + ##### Parameters -- `app_id` — the `oauth_app_id` for [OAuth apps](/connect/quickstart#creating-a-custom-oauth-client) or [name slug](/connect/quickstart/#find-your-apps-name-slug) for key-based apps -- `include_credentials` — _Optional_. Pass `include_credentials=1` as a query-string parameter to include the account credentials in the response +`include_credentials` **boolean** (_optional_) + +Pass `include_credentials=true` as a query-string parameter to include the account credentials in the response. ##### Examples - + ```typescript import { - createClient, + createBackendClient, } from "@pipedream/sdk"; -const pd = createClient({ - publicKey: "YOUR_PUBLIC_KEY", - secretKey: "YOUR_SECRET_KEY", +const pd = createBackendClient({ + credentials: { + clientId: "your-oauth-client-id", + clientSecret: "your-oauth-client-secret", + } }); -export async function getAccountsByApp(appId: string, include_credentials: number = 0) { - const accounts = await pd.getAccountsByApp(appId, { - include_credentials, // set to 1 to include credentials - }); - - // Parse and return the data you need. These may contain credentials, - // which you should never return to the client - return accounts; -} +const account = await pd.getAccount(accountId, { + include_credentials: true, // set to true to include credentials +}); +// Parse and return the data you need. These may contain credentials, +// which you should never return to the client ``` ```javascript -const fetch = require('node-fetch'); - -class Client { - constructor({ publicKey, secretKey }) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = 'https://api.pipedream.com/v1'; - } +import { createBackendClient } from "@pipedream/sdk"; - _authorizationHeader() { - const encoded = Buffer.from(`${this.publicKey}:${this.secretKey}`).toString('base64'); - return `Basic ${encoded}`; +const pd = createBackendClient({ + credentials: { + clientId: "your-oauth-client-id", + clientSecret: "your-oauth-client-secret", } +}); - async getAccountsByApp(appId) { - const auth = this._authorizationHeader(); - const url = `${this.baseURL}/connect/accounts/app/${appId}?include_credentials=1`; - - const response = await fetch(url, { - method: 'GET', - headers: { - 'Authorization': auth, - 'Content-Type': 'application/json', - }, - }); - - const data = await response.json(); - - if (!data?.accounts?.length) { - return null; - } - - // Parse and return data.accounts. Ensure to handle sensitive data appropriately. - return data.accounts; - } -} +const accountId = "{account_id}"; // Replace with your account ID -// Usage example -const client = new Client({ - publicKey: 'YOUR_PUBLIC_KEY', - secretKey: 'YOUR_SECRET_KEY', +const account = await pd.getAccount(accountId, { + include_credentials: true, // set to true to include credentials }); -client.getAccountsByApp('APP_ID') - .then(accounts => { - console.log(accounts); - }) - .catch(error => { - console.error('Error:', error); - }); -``` - - -```python -import base64 -import requests - -class Client: - def __init__(self, public_key, secret_key): - self.public_key = public_key - self.secret_key = secret_key - self.base_url = "https://api.pipedream.com/v1" - - def _authorization_header(self): - encoded = base64.b64encode(f"{self.public_key}:{self.secret_key}".encode()).decode() - return f"Basic {encoded}" - - def get_accounts_by_app(self, app_id): - auth = self._authorization_header() - url = f"{self.base_url}/connect/accounts/app/{app_id}?include_credentials=1" - response = requests.get(url, headers={"Authorization": auth}) - data = response.json() - - if not data.get('accounts'): - return None - - # Parse and return data['accounts']. Ensure to handle sensitive data appropriately. - return data['accounts'] - -# Usage example -client = Client('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY') -accounts = client.get_accounts_by_app('APP_ID') -print(accounts) +// Parse and return the data you need. These may contain credentials, +// which you should never return to the client ``` -```java -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Base64; -import java.io.InputStreamReader; -import java.io.BufferedReader; -import java.nio.charset.StandardCharsets; - -public class Client { - private String publicKey; - private String secretKey; - private String baseURL; - - public Client(String publicKey, String secretKey) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = "https://api.pipedream.com/v1"; - } - - private String authorizationHeader() { - String encoded = Base64.getEncoder().encodeToString((publicKey + ":" + secretKey).getBytes(StandardCharsets.UTF_8)); - return "Basic " + encoded; - } +```bash +# First, obtain an OAuth access token +curl -X POST https://api.pipedream.com/v1/oauth/token \ + -H "Content-Type: application/json" \ + -d '{ + "grant_type": "client_credentials", + "client_id": "{oauth_client_id}", + "client_secret": "{oauth_client_secret}" + }' - public String getAccountsByApp(String appId) throws Exception { - String auth = authorizationHeader(); - URL url = new URL(baseURL + "/connect/accounts/app/" + appId + "?include_credentials=1"); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("GET"); - conn.setRequestProperty("Authorization", auth); - - BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); - String inputLine; - StringBuilder content = new StringBuilder(); - while ((inputLine = in.readLine()) != null) { - content.append(inputLine); - } - in.close(); - - String response = content.toString(); - - // Parse response and return accounts data. Ensure to handle sensitive data appropriately. - if (!response.contains("accounts")) { - return null; - } - - return response; // Modify to parse and handle accounts as needed. - } +# The response will include an access_token. Use it in the Authorization header below. - public static void main(String[] args) throws Exception { - Client client = new Client("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY"); - String accounts = client.getAccountsByApp("APP_ID"); - System.out.println(accounts); - } -} +curl -X GET "https://api.pipedream.com/v1/connect/{project_id}/accounts/{account_id}?include_credentials=true" \ + -H "Authorization: Bearer {access_token}" ``` - -```csharp -using System; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Threading.Tasks; - -public class Client { - private string publicKey; - private string secretKey; - private string baseURL; - - public Client(string publicKey, string secretKey) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = "https://api.pipedream.com/v1"; - } - - private string AuthorizationHeader() { - string encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{publicKey}:{secretKey}")); - return $"Basic {encoded}"; - } - - public async Task GetAccountsByApp(string appId) { - string auth = AuthorizationHeader(); - using (HttpClient client = new HttpClient()) { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", auth); - HttpResponseMessage response = await client.GetAsync($"{baseURL}/connect/accounts/app/{appId}?include_credentials=1"); - - string result = await response.Content.ReadAsStringAsync(); - - // Parse and return accounts data. Ensure to handle sensitive data appropriately. - if (!result.Contains("accounts")) { - return null; - } + - return result; // Modify to parse and handle accounts as needed. - } - } - - public static async Task Main(string[] args) { - var client = new Client("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY"); - string accounts = await client.GetAccountsByApp("APP_ID"); - Console.WriteLine(accounts); - } -} -``` - - -```go -package main - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" -) - -type Client struct { - PublicKey string - SecretKey string - BaseURL string -} - -func NewClient(publicKey, secretKey string) *Client { - return &Client{ - PublicKey: publicKey, - SecretKey: secretKey, - BaseURL: "https://api.pipedream.com/v1", - } -} - -func (c *Client) authorizationHeader() string { - encoded := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", c.PublicKey, c.SecretKey))) - return fmt.Sprintf("Basic %s", encoded) -} - -func (c *Client) GetAccountsByApp(appId string) (map[string]interface{}, error) { - url := fmt.Sprintf("%s/connect/accounts/app/%s?include_credentials=1", c.BaseURL, appId) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, err - } - - req.Header.Set("Authorization", c.authorizationHeader()) - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - var result map[string]interface{} - err = json.Unmarshal(body, &result) - if err != nil { - return nil, err - } - - if accounts, exists := result["accounts"]; exists { - return accounts.([]interface{}), nil - } - - return nil, nil -} - -func main() { - client := NewClient("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY") - - accounts, err := client.GetAccountsByApp("APP_ID") - if err != nil { - fmt.Println("Error:", err) - return - } - - fmt.Println(accounts) -} -``` - - -```php -publicKey = $publicKey; - $this->secretKey = $secretKey; - $this->baseURL = "https://api.pipedream.com/v1"; - } - - private function authorizationHeader() { - return "Basic " . base64_encode("$this->publicKey:$this->secretKey"); - } - - public function getAccountsByApp($appId) { - $url = "$this->baseURL/connect/accounts/app/$appId?include_credentials=1"; - $opts = [ - 'http' => [ - 'header' => "Authorization: " . $this->authorizationHeader(), - 'method' => 'GET', - ], - ]; - - $context = stream_context_create($opts); - $result = file_get_contents($url, false, $context); - - if ($result === FALSE) { - return null; - } - - $data = json_decode($result, true); - - if (empty($data['accounts'])) { - return null; - } - - return $data['accounts']; // Modify to parse and handle accounts as needed. - } -} - -// Usage example -$client = new Client('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY'); -$accounts = $client->getAccountsByApp('APP_ID'); -print_r($accounts); -?> -``` - - -```ruby -require 'base64' -require 'json' -require 'net/http' -require 'uri' - -class Client - def initialize(public_key, secret_key) - @public_key = public_key - @secret_key = secret_key - @base_url = "https://api.pipedream.com/v1" - end - - def authorization_header - encoded = Base64.strict_encode64("#{@public_key}:#{@secret_key}") - "Basic #{encoded}" - end - - def get_accounts_by_app(app_id) - uri = URI("#{@base_url}/connect/accounts/app/#{app_id}?include_credentials=1") - req = Net::HTTP::Get.new(uri) - req['Authorization'] = authorization_header - - res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| - http.request(req) - end - - data = JSON.parse(res.body) - - # Parse and return accounts data. Ensure to handle sensitive data appropriately. - return nil if data['accounts'].nil? || data['accounts'].empty? - - data['accounts'] - end -end - -# Usage example -client = Client.new('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY') -accounts = client.get_accounts_by_app('APP_ID') -puts accounts.inspect -``` - - - -##### Example response - -```json -[ - { - "id": "apn_WYhMlrz", - "name": null, - "external_id": "user-abc-123", - "healthy": true, - "dead": false, - "app": { - "id": "oa_aw4ib2", - "name_slug": "airtable_oauth", - "name": "Airtable", - "auth_type": "oauth", - "description": "Airtable is a low-code platform to build next-gen apps. Move beyond rigid tools, operationalize your critical data, and reimagine workflows with AI.", - "img_src": "https://assets.pipedream.net/s.v0/app_XBxhAl/logo/orig", - "custom_fields_json": "[]", - "categories": ["Productivity"] - }, - "created_at": "2024-08-01T04:04:03.000Z", - "updated_at": "2024-08-01T04:04:03.000Z" - } -] -``` - -#### Retrieve account details by ID - -Retrieve the account details for a specific account based on the account ID - -``` -GET /accounts/:account_id -``` - -##### Parameters - -- `account_id` — the ID of the account you want to retrieve -- `include_credentials` — _Optional_. Pass `include_credentials=1` as a query-string parameter to include the account credentials in the response - -##### Examples - - - -```typescript -import { - createClient, -} from "@pipedream/sdk"; - -const pd = createClient({ - publicKey: "YOUR_PUBLIC_KEY", - secretKey: "YOUR_SECRET_KEY", -}); - -export async function getAccount(accountId: string, include_credentials: number = 0) { - const account = await pd.getAccount(accountId, { - include_credentials, // set to 1 to include credentials - }); - - // Parse and return the data you need. These may contain credentials, - // which you should never return to the client - return account; -} -``` - - -```javascript -const fetch = require('node-fetch'); - -class Client { - constructor({ publicKey, secretKey }) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = 'https://api.pipedream.com/v1'; - } - - _authorizationHeader() { - const encoded = Buffer.from(`${this.publicKey}:${this.secretKey}`).toString('base64'); - return `Basic ${encoded}`; - } - - async getAccount(accountId) { - const auth = this._authorizationHeader(); - const url = `${this.baseURL}/connect/accounts/${accountId}?include_credentials=1`; - - const response = await fetch(url, { - method: 'GET', - headers: { - 'Authorization': auth, - 'Content-Type': 'application/json', - }, - }); - - const data = await response.json(); - - if (!data?.id) { - return null; - } - - // Parse and return data.account. Ensure to handle sensitive data appropriately. - return data; - } -} - -// Usage example -const client = new Client({ - publicKey: 'YOUR_PUBLIC_KEY', - secretKey: 'YOUR_SECRET_KEY', -}); - -client.getAccount('ACCOUNT_ID') - .then(account => { - console.log(account); - }) - .catch(error => { - console.error('Error:', error); - }); -``` - - -```python -import base64 -import requests - -class Client: - def __init__(self, public_key, secret_key): - self.public_key = public_key - self.secret_key = secret_key - self.base_url = "https://api.pipedream.com/v1" - - def _authorization_header(self): - encoded = base64.b64encode(f"{self.public_key}:{self.secret_key}".encode()).decode() - return f"Basic {encoded}" - - def get_account(self, account_id): - auth = self._authorization_header() - url = f"{self.base_url}/connect/accounts/{account_id}?include_credentials=1" - response = requests.get(url, headers={"Authorization": auth}) - data = response.json() - - if not data.get('id'): - return None - - # Parse and return data. Ensure to handle sensitive data appropriately. - return data - -# Usage example -client = Client('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY') -account = client.get_account('ACCOUNT_ID') -print(account) -``` - - -```java -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Base64; -import java.io.InputStreamReader; -import java.io.BufferedReader; -import java.nio.charset.StandardCharsets; - -public class Client { - private String publicKey; - private String secretKey; - private String baseURL; - - public Client(String publicKey, String secretKey) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = "https://api.pipedream.com/v1"; - } - - private String authorizationHeader() { - String encoded = Base64.getEncoder().encodeToString((publicKey + ":" + secretKey).getBytes(StandardCharsets.UTF_8)); - return "Basic " + encoded; - } - - public String getAccount(String accountId) throws Exception { - String auth = authorizationHeader(); - URL url = new URL(baseURL + "/connect/accounts/" + accountId + "?include_credentials=1"); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("GET"); - conn.setRequestProperty("Authorization", auth); - - BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); - String inputLine; - StringBuilder content = new StringBuilder(); - while ((inputLine = in.readLine()) != null) { - content.append(inputLine); - } - in.close(); - - String response = content.toString(); - - // Parse response and return account data. Ensure to handle sensitive data appropriately. - if (!response.contains("id")) { - return null; - } - - return response; // Modify to parse and handle account as needed. - } - - public static void main(String[] args) throws Exception { - Client client = new Client("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY"); - String account = client.getAccount("ACCOUNT_ID"); - System.out.println(account); - } -} -``` - - -```csharp -using System; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Threading.Tasks; - -public class Client { - private string publicKey; - private string secretKey; - private string baseURL; - - public Client(string publicKey, string secretKey) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = "https://api.pipedream.com/v1"; - } - - private string AuthorizationHeader() { - string encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{publicKey}:{secretKey}")); - return $"Basic {encoded}"; - } - - public async Task GetAccount(string accountId) { - string auth = AuthorizationHeader(); - using (HttpClient client = new HttpClient()) { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", auth); - HttpResponseMessage response = await client.GetAsync($"{baseURL}/connect/accounts/{accountId}?include_credentials=1"); - - string result = await response.Content.ReadAsStringAsync(); - - // Parse and return account data. Ensure to handle sensitive data appropriately. - if (!result.Contains("id")) { - return null; - } - - return result; // Modify to parse and handle account as needed. - } - } - - public static async Task Main(string[] args) { - var client = new Client("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY"); - string account = await client.GetAccount("ACCOUNT_ID"); - Console.WriteLine(account); - } -} -``` - - -```go -package main - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" -) - -type Client struct { - PublicKey string - SecretKey string - BaseURL string -} - -func NewClient(publicKey, secretKey string) *Client { - return &Client{ - PublicKey: publicKey, - SecretKey: secretKey, - BaseURL: "https://api.pipedream.com/v1", - } -} - -func (c *Client) authorizationHeader() string { - encoded := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", c.PublicKey, c.SecretKey))) - return fmt.Sprintf("Basic %s", encoded) -} - -func (c *Client) GetAccount(accountId string) (map[string]interface{}, error) { - url := fmt.Sprintf("%s/connect/accounts/%s?include_credentials=1", c.BaseURL, accountId) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, err - } - - req.Header.Set("Authorization", c.authorizationHeader()) - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - var result map[string]interface{} - err = json.Unmarshal(body, &result) - if err != nil { - return nil, err - } - - if id, exists := result["id"]; exists { - return id.(map[string]interface{}), nil - } - - return nil, nil -} - -func main() { - client := NewClient("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY") - - account, err := client.GetAccount("ACCOUNT_ID") - if err != nil { - fmt.Println("Error:", err) - return - } - - fmt.Println(account) -} -``` - - -```php -publicKey = $publicKey; - $this->secretKey = $secretKey; - $this->baseURL = "https://api.pipedream.com/v1"; - } - - private function authorizationHeader() { - return "Basic " . base64_encode("$this->publicKey:$this->secretKey"); - } - - public function getAccount($accountId) { - $url = "$this->baseURL/connect/accounts/$accountId?include_credentials=1"; - $opts = [ - 'http' => [ - 'header' => "Authorization: " . $this->authorizationHeader(), - 'method' => 'GET', - ], - ]; - - $context = stream_context_create($opts); - $result = file_get_contents($url, false, $context); - - if ($result === FALSE) { - return null; - } - - $data = json_decode($result, true); - - if (empty($data['id'])) { - return null; - } - - return $data; // Modify to parse and handle account as needed. - } -} - -// Usage example -$client = new Client('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY'); -$account = $client->getAccount('ACCOUNT_ID'); -print_r($account); -?> -``` - - -```ruby -require 'base64' -require 'json' -require 'net/http' -require 'uri' - -class Client - def initialize(public_key, secret_key) - @public_key = public_key - @secret_key = secret_key - @base_url = "https://api.pipedream.com/v1" - end - - def authorization_header - encoded = Base64.strict_encode64("#{@public_key}:#{@secret_key}") - "Basic #{encoded}" - end - - def get_account(account_id) - uri = URI("#{@base_url}/connect/accounts/#{account_id}?include_credentials=1") - req = Net::HTTP::Get.new(uri) - req['Authorization'] = authorization_header - - res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| - http.request(req) - end - - data = JSON.parse(res.body) - - # Parse and return account data. Ensure to handle sensitive data appropriately. - return nil if data['id'].nil? - - data - end -end - -# Usage example -client = Client.new('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY') -account = client.get_account('ACCOUNT_ID') -puts account.inspect -``` - - - -##### Example response (without account credentials) +##### Example response (without account credentials) ```json { "data": { - "id": "apn_WYhMlrz", - "name": null, - "external_id": "user-abc-123", - "healthy": true, - "dead": false, - "app": { - "id": "oa_aw4ib2", - "name_slug": "airtable_oauth", - "name": "Airtable", - "auth_type": "oauth", - "description": "Airtable is a low-code platform to build next-gen apps. Move beyond rigid tools, operationalize your critical data, and reimagine workflows with AI.", - "img_src": "https://assets.pipedream.net/s.v0/app_XBxhAl/logo/orig", - "custom_fields_json": "[]", - "categories": ["Productivity"] - }, - "created_at": "2024-08-01T04:04:03.000Z", - "updated_at": "2024-08-01T04:04:03.000Z" - } -} -``` - -##### Example Response (with `include_credentials=1`) - -```json -{ - "data": { - "id": "apn_WYhMlrz", - "name": null, - "external_id": "user-abc-123", - "healthy": true, - "dead": false, - "app": { - "id": "app_XBxhAl", - "name": "Airtable" - }, - "created_at": "2024-08-01T04:04:03.000Z", - "updated_at": "2024-08-01T04:04:03.000Z", - "credentials": { - "oauth_client_id": "dd7a26ca-ba11-4f80-8667-xxxxxxxx", - "oauth_access_token": "oaaLa2Ob1umiregWa.v1.xxxxxxxx.xxxxxxxx", - "oauth_refresh_token": "oaaLa2Ob1umiregWa.v1.refresh.xxxxxxxx", - "oauth_uid": "usrnbIhrxxxxxxxx" - }, - "expires_at": "2024-08-01T05:04:03.000Z", - "project_id": 279440, - "user_id": "danny", - "error": null, - "last_refreshed_at": null, - "next_refresh_at": "2024-08-01T04:17:33.000Z" - } -} -``` - -#### Retrieve accounts for an external user - -Retrieve the account details for a specific account based on the external user ID - -``` -GET /users/:external_id/accounts -``` - -##### Parameters - -- `external_id` — [The external user ID](#external-users) in your system -- `include_credentials` — _Optional_. Pass `?include_credentials=1` as a query-string parameter to include the account credentials in the response - -##### Examples - - - -```typescript -import { - createClient, -} from "@pipedream/sdk"; - -const pd = createClient({ - publicKey: PIPEDREAM_PROJECT_PUBLIC_KEY, - secretKey: PIPEDREAM_PROJECT_SECRET_KEY, -}); - -export async function getUserAccounts(externalId: string, include_credentials: number = 0) { - await pd.getAccountsByExternalId(externalId, { - include_credentials, // set to 1 to include credentials - }) - - // Parse and return the data you need. These may contain credentials, - // which you should never return to the client -} -``` - - -```javascript -const fetch = require('node-fetch'); - -class Client { - constructor({ publicKey, secretKey }) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = 'https://api.pipedream.com'; - } - - _authorizationHeader() { - const encoded = Buffer.from(`${this.publicKey}:${this.secretKey}`).toString('base64'); - return `Basic ${encoded}`; - } - - async getUserAccounts(externalId) { - const auth = this._authorizationHeader(); - const url = `${this.baseURL}/v1/connect/users/${externalId}/accounts`; - - const response = await fetch(url, { - method: 'GET', - headers: { - 'Authorization': auth, - 'Content-Type': 'application/json', - }, - }); - - const data = await response.json(); - - if (!data?.accounts?.length) { - return null; - } - - // Parse and return data.accounts. Ensure to handle sensitive data appropriately. - return data.accounts; - } -} - -// Usage example -const client = new Client({ - publicKey: 'YOUR_PUBLIC_KEY', - secretKey: 'YOUR_SECRET_KEY', -}); - -client.getUserAccounts('USER_ID') - .then(response => { - // handle response - }) - .catch(error => { - console.error('Error:', error); - }); -``` - - -```python -import base64 -import requests - -class Client: - def __init__(self, public_key, secret_key): - self.public_key = public_key - self.secret_key = secret_key - self.base_url = "https://api.pipedream.com" - - def _authorization_header(self): - encoded = base64.b64encode(f"{self.public_key}:{self.secret_key}".encode()).decode() - return f"Basic {encoded}" - - def get_user_accounts(self, external_id): - auth = self._authorization_header() - url = f"{self.base_url}/v1/connect/users/{external_id}/accounts" - response = requests.get(url, headers={"Authorization": auth}) - data = response.json() - - if not data.get('accounts'): - return None - - # Parse and return data['accounts']. Ensure to handle sensitive data appropriately. - return data['accounts'] - -# Usage example -client = Client('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY') -response = client.get_user_accounts('USER_ID') -``` - - -```java -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Base64; -import java.io.InputStreamReader; -import java.io.BufferedReader; -import java.nio.charset.StandardCharsets; - -public class Client { - private String publicKey; - private String secretKey; - private String baseURL; - - public Client(String publicKey, String secretKey) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = "https://api.pipedream.com"; - } - - private String authorizationHeader() { - String encoded = Base64.getEncoder().encodeToString((publicKey + ":" + secretKey).getBytes(StandardCharsets.UTF_8)); - return "Basic " + encoded; - } - - public String getUserAccounts(String externalId) throws Exception { - String auth = authorizationHeader(); - URL url = new URL(baseURL + "/v1/connect/users/" + externalId + "/accounts"); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("GET"); - conn.setRequestProperty("Authorization", auth); - - BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); - String inputLine; - StringBuilder content = new StringBuilder(); - while ((inputLine = in.readLine()) != null) { - content.append(inputLine); - } - in.close(); - - String response = content.toString(); - - // Parse response and return accounts data. Ensure to handle sensitive data appropriately. - if (!response.contains("accounts")) { - return null; - } - - return response; // Modify to parse and handle accounts as needed. - } - - public static void main(String[] args) throws Exception { - Client client = new Client("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY"); - String response = client.getUserAccounts("USER_ID"); - } -} -``` - - -```csharp -using System; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Threading.Tasks; - -public class Client { - private string publicKey; - private string secretKey; - private string baseURL; - - public Client(string publicKey, string secretKey) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = "https://api.pipedream.com"; - } - - private string AuthorizationHeader() { - string encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{publicKey}:{secretKey}")); - return $"Basic {encoded}"; - } - - public async Task GetUserAccounts(string externalId) { - string auth = AuthorizationHeader(); - using (HttpClient client = new HttpClient()) { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", auth); - HttpResponseMessage response = await client.GetAsync($"{baseURL}/v1/connect/users/{externalId}/accounts"); - - string result = await response.Content.ReadAsStringAsync(); - - // Parse and return accounts data. Ensure to handle sensitive data appropriately. - if (!result.Contains("accounts")) { - return null; - } - - return result; // Modify to parse and handle accounts as needed. - } - } - - public static async Task Main(string[] args) { - var client = new Client("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY"); - string response = await client.GetUserAccounts("USER_ID"); - } -} -``` - - -```go -package main - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" -) - -type Client struct { - PublicKey string - SecretKey string - BaseURL string -} - -func NewClient(publicKey, secretKey string) *Client { - return &Client{ - PublicKey: publicKey, - SecretKey: secretKey, - BaseURL: "https://api.pipedream.com", - } -} - -func (c *Client) authorizationHeader() string { - encoded := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", c.PublicKey, c.SecretKey))) - return fmt.Sprintf("Basic %s", encoded) -} - -func (c *Client) GetUserAccounts(externalId string) (map[string]interface{}, error) { - url := fmt.Sprintf("%s/v1/connect/users/%s/accounts", c.BaseURL, externalId) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, err - } - - req.Header.Set("Authorization", c.authorizationHeader()) - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - var result map[string]interface{} - err = json.Unmarshal(body, &result) - if err != nil { - return nil, err - } - - if accounts, exists := result["accounts"]; exists { - return accounts.([]interface{}), nil - } - - return nil, nil -} - -func main() { - client := NewClient("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY") - - accounts, err := client.GetUserAccounts("USER_ID") - if err != nil { - fmt.Println("Error:", err) - return - } -} -``` - - -```php -publicKey = $publicKey; - $this->secretKey = $secretKey; - $this->baseURL = "https://api.pipedream.com"; - } - - private function authorizationHeader() { - return "Basic " . base64_encode("$this->publicKey:$this->secretKey"); - } - - public function getUserAccounts($externalId) { - $url = "$this->baseURL/v1/connect/users/$externalId/accounts"; - $opts = [ - 'http' => [ - 'header' => "Authorization: " . $this->authorizationHeader(), - 'method' => 'GET', - ], - ]; - - $context = stream_context_create($opts); - $result = file_get_contents($url, false, $context); - - if ($result === FALSE) { - return null; - } - - $data = json_decode($result, true); - - if (empty($data['accounts'])) { - return null; - } - - return $data['accounts']; // Modify to parse and handle accounts as needed. - } -} - -// Usage example -$client = new Client('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY'); -$response = $client->getUserAccounts('USER_ID'); -?> -``` - - -```ruby -require 'base64' -require 'json' -require 'net/http' -require 'uri' - -class Client - def initialize(public_key, secret_key) - @public_key = public_key - @secret_key = secret_key - @base_url = "https://api.pipedream.com" - end - - def authorization_header - encoded = Base64.strict_encode64("#{@public_key}:#{@secret_key}") - "Basic #{encoded}" - end - - def get_user_accounts(external_id) - uri = URI("#{@base_url}/v1/connect/users/#{external_id}/accounts") - req = Net::HTTP::Get.new(uri) - req['Authorization'] = authorization_header - - res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| - http.request(req) - end - - data = JSON.parse(res.body) - - # Parse and return accounts data. Ensure to handle sensitive data appropriately. - return nil if data['accounts'].nil? || data['accounts'].empty? - - data['accounts'] - end -end - -# Usage example -client = Client.new('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY') -response = client.get_user_accounts('USER_ID') -``` - - - -##### Example response (without account credentials) - -```json -[ - { - "id": "apn_WYhM5ov", + "id": "apn_WYhMlrz", "name": null, "external_id": "user-abc-123", "healthy": true, @@ -2370,459 +618,116 @@ response = client.get_user_accounts('USER_ID') "description": "Airtable is a low-code platform to build next-gen apps. Move beyond rigid tools, operationalize your critical data, and reimagine workflows with AI.", "img_src": "https://assets.pipedream.net/s.v0/app_XBxhAl/logo/orig", "custom_fields_json": "[]", - "categories": [ - "Productivity" - ] - }, - "created_at": "2024-08-06T21:51:30.000Z", - "updated_at": "2024-08-06T21:51:30.000Z", - "expires_at": "2024-08-06T22:51:30.000Z", - "error": null, - "last_refreshed_at": null, - "next_refresh_at": "2024-08-06T22:04:41.000Z" - }, - { - "id": "apn_KAh7JwW", - "name": null, - "external_id": "user-abc-123", - "healthy": true, - "dead": false, - "app": { - "id": "oa_aPXiQd", - "name_slug": "github", - "name": "GitHub", - "auth_type": "oauth", - "description": "Where the world builds software. Millions of developers and companies build, ship, and maintain their software on GitHub—the largest and most advanced development platform in the world.", - "img_src": "https://assets.pipedream.net/s.v0/app_OrZhaO/logo/orig", - "custom_fields_json": "[]", - "categories": [ - "Developer Tools" - ] + "categories": ["Productivity"] }, - "created_at": "2024-08-06T21:53:05.000Z", - "updated_at": "2024-08-06T21:53:05.000Z", - "expires_at": null, - "error": null, - "last_refreshed_at": null, - "next_refresh_at": "2024-08-06T22:50:01.000Z" + "created_at": "2024-08-01T04:04:03.000Z", + "updated_at": "2024-08-01T04:04:03.000Z" } -] +} ``` -##### Example Response (with `include_credentials=1`) +##### Example Response (with `include_credentials=true`) ```json -[ - { - "id": "apn_WYhM5ov", +{ + "data": { + "id": "apn_WYhMlrz", "name": null, "external_id": "user-abc-123", "healthy": true, "dead": false, "app": { - "id": "oa_aw4ib2", - "name_slug": "airtable_oauth", - "name": "Airtable", - "auth_type": "oauth", - "description": "Airtable is a low-code platform to build next-gen apps. Move beyond rigid tools, operationalize your critical data, and reimagine workflows with AI.", - "img_src": "https://assets.pipedream.net/s.v0/app_XBxhAl/logo/orig", - "custom_fields_json": "[]", - "categories": [ - "Productivity" - ] + "id": "app_XBxhAl", + "name": "Airtable" }, - "created_at": "2024-08-06T21:51:30.000Z", - "updated_at": "2024-08-06T21:51:30.000Z", + "created_at": "2024-08-01T04:04:03.000Z", + "updated_at": "2024-08-01T04:04:03.000Z", "credentials": { "oauth_client_id": "dd7a26ca-ba11-4f80-8667-xxxxxxxx", "oauth_access_token": "oaaLa2Ob1umiregWa.v1.xxxxxxxx.xxxxxxxx", "oauth_refresh_token": "oaaLa2Ob1umiregWa.v1.refresh.xxxxxxxx", "oauth_uid": "usrnbIhrxxxxxxxx" }, - "expires_at": "2024-08-06T22:51:30.000Z", - "error": null, - "last_refreshed_at": null, - "next_refresh_at": "2024-08-06T22:04:41.000Z" - }, - { - "id": "apn_KAh7JwW", - "name": null, - "external_id": "user-abc-123", - "healthy": true, - "dead": false, - "app": { - "id": "oa_aPXiQd", - "name_slug": "github", - "name": "GitHub", - "auth_type": "oauth", - "description": "Where the world builds software. Millions of developers and companies build, ship, and maintain their software on GitHub—the largest and most advanced development platform in the world.", - "img_src": "https://assets.pipedream.net/s.v0/app_OrZhaO/logo/orig", - "custom_fields_json": "[]", - "categories": [ - "Developer Tools" - ] - }, - "created_at": "2024-08-06T21:53:05.000Z", - "updated_at": "2024-08-06T21:53:05.000Z", - "credentials": { - "oauth_client_id": "57dc28xxxxxxxxxxxxx", - "oauth_access_token": "gho_xxxxxxxxxxxxxxxxxx", - "oauth_uid": "104484339" - }, - "expires_at": null, + "expires_at": "2024-08-01T05:04:03.000Z", + "project_id": 279440, + "user_id": "danny", "error": null, "last_refreshed_at": null, - "next_refresh_at": "2024-08-06T22:50:01.000Z" + "next_refresh_at": "2024-08-01T04:17:33.000Z" } -] +} ``` #### Delete connected account Delete a specific connected account for an end user -```shell -DELETE /v1/connect/accounts/:account_id +``` +DELETE /{project_id}/accounts/{account_id} ``` -##### Parameters +##### Path parameters + +`project_id` **string** -- `account_id` is the ID of the account you want to retrieve +[The project's ID](/projects#finding-your-projects-id) + +--- + +`account_id` **string** ##### Examples - + ```typescript import { - createClient, + createBackendClient, } from "@pipedream/sdk"; -const pd = createClient({ - publicKey: "YOUR_PUBLIC_KEY", - secretKey: "YOUR_SECRET_KEY", +const pd = createBackendClient({ + credentials: { + clientId: "your-oauth-client-id", + clientSecret: "your-oauth-client-secret", + } }); -export async function deleteAccount(accountId: string) { - await pd.deleteAccount(accountId); - - // You can return a message or handle any post-deletion logic here. -} +await pd.deleteAccount(accountId); +// You can return a message or handle any post-deletion logic here. ``` ```javascript -const fetch = require('node-fetch'); - -class Client { - constructor({ publicKey, secretKey }) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = 'https://api.pipedream.com/v1'; - } - - _authorizationHeader() { - const encoded = Buffer.from(`${this.publicKey}:${this.secretKey}`).toString('base64'); - return `Basic ${encoded}`; - } - - async deleteAccount(accountId) { - const auth = this._authorizationHeader(); - const url = `${this.baseURL}/connect/accounts/${accountId}`; - - const response = await fetch(url, { - method: 'DELETE', - headers: { - 'Authorization': auth, - }, - }); +import { createBackendClient } from "@pipedream/sdk"; - if (response.status === 204) { - console.log("Account deleted successfully."); - } else { - console.log("Failed to delete account."); - } +const pd = createBackendClient({ + credentials: { + clientId: "your-oauth-client-id", + clientSecret: "your-oauth-client-secret", } -} - -// Usage example -const client = new Client({ - publicKey: 'YOUR_PUBLIC_KEY', - secretKey: 'YOUR_SECRET_KEY', }); -client.deleteAccount('ACCOUNT_ID') - .catch(error => { - console.error('Error:', error); - }); -``` - - -```python -import base64 -import requests - -class Client: - def __init__(self, public_key, secret_key): - self.public_key = public_key - self.secret_key = secret_key - self.base_url = "https://api.pipedream.com/v1" - - def _authorization_header(self): - encoded = base64.b64encode(f"{self.public_key}:{self.secret_key}".encode()).decode() - return f"Basic {encoded}" - - def delete_account(self, account_id): - auth = self._authorization_header() - url = f"{self.base_url}/connect/accounts/{account_id}" - response = requests.delete(url, headers={"Authorization": auth}) - - if response.status_code == 204: - print("Account deleted successfully.") - else: - print("Failed to delete account.") - -# Usage example -client = Client('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY') -client.delete_account('ACCOUNT_ID') -``` - - -```java -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Base64; -import java.nio.charset.StandardCharsets; - -public class Client { - private String publicKey; - private String secretKey; - private String baseURL; - - public Client(String publicKey, String secretKey) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = "https://api.pipedream.com/v1"; - } - - private String authorizationHeader() { - String encoded = Base64.getEncoder().encodeToString((publicKey + ":" + secretKey).getBytes(StandardCharsets.UTF_8)); - return "Basic " + encoded; - } - - public void deleteAccount(String accountId) throws Exception { - String auth = authorizationHeader(); - URL url = new URL(baseURL + "/connect/accounts/" + accountId); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("DELETE"); - conn.setRequestProperty("Authorization", auth); - - int responseCode = conn.getResponseCode(); - if (responseCode == 204) { - System.out.println("Account deleted successfully."); - } else { - System.out.println("Failed to delete account. Response code: " + responseCode); - } - } - - public static void main(String[] args) throws Exception { - Client client = new Client("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY"); - client.deleteAccount("ACCOUNT_ID"); - } -} -``` - - -```csharp -using System; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading.Tasks; - -public class Client { - private string publicKey; - private string secretKey; - private string baseURL; - - public Client(string publicKey, string secretKey) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = "https://api.pipedream.com/v1"; - } - - private string AuthorizationHeader() { - string encoded = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes($"{publicKey}:{secretKey}")); - return $"Basic {encoded}"; - } - - public async Task DeleteAccount(string accountId) { - string auth = AuthorizationHeader(); - using (HttpClient client = new HttpClient()) { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", auth); - HttpResponseMessage response = await client.DeleteAsync($"{baseURL}/connect/accounts/{accountId}"); - - if (response.StatusCode == System.Net.HttpStatusCode.NoContent) { - Console.WriteLine("Account deleted successfully."); - } else { - Console.WriteLine("Failed to delete account."); - } - } - } - - public static async Task Main(string[] args) { - var client = new Client("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY"); - await client.DeleteAccount("ACCOUNT_ID"); - } -} -``` - - -```go -package main - -import ( - "encoding/base64" - "fmt" - "net/http" -) - -type Client struct { - PublicKey string - SecretKey string - BaseURL string -} - -func NewClient(publicKey, secretKey string) *Client { - return &Client{ - PublicKey: publicKey, - SecretKey: secretKey, - BaseURL: "https://api.pipedream.com/v1", - } -} - -func (c *Client) authorizationHeader() string { - encoded := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", c.PublicKey, c.SecretKey))) - return fmt.Sprintf("Basic %s", encoded) -} - -func (c *Client) DeleteAccount(accountId string) error { - url := fmt.Sprintf("%s/connect/accounts/%s", c.BaseURL, accountId) - req, err := http.NewRequest("DELETE", url, nil) - if err != nil { - return err - } - - req.Header.Set("Authorization", c.authorizationHeader()) - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode == http.StatusNoContent { - fmt.Println("Account deleted successfully.") - } else { - fmt.Printf("Failed to delete account. Status code: %d\n", resp.StatusCode) - } - - return nil -} - -func main() { - client := NewClient("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY") +await pd.deleteAccount(accountId); - err := client.DeleteAccount("ACCOUNT_ID") - if err != nil { - fmt.Println("Error:", err) - } -} +// You can return a message or handle any post-deletion logic here. ``` -```php -publicKey = $publicKey; - $this->secretKey = $secretKey; - $this->baseURL = "https://api.pipedream.com/v1"; - } - - private function authorizationHeader() { - return "Basic " . base64_encode("$this->publicKey:$this->secretKey"); - } +```bash +# First, obtain an OAuth access token +curl -X POST https://api.pipedream.com/v1/oauth/token \ + -H "Content-Type: application/json" \ + -d '{ + "grant_type": "client_credentials", + "client_id": "{oauth_client_id}", + "client_secret": "{oauth_client_secret}" + }' - public function deleteAccount($accountId) { - $url = "$this->baseURL/connect/accounts/$accountId"; - $opts = [ - 'http' => [ - 'header' => "Authorization: " . $this->authorizationHeader(), - 'method' => 'DELETE', - ], - ]; - - $context = stream_context_create($opts); - $result = file_get_contents($url, false, $context); - - if ($http_response_header[0] == 'HTTP/1.1 204 No Content') { - echo "Account deleted successfully."; - } else { - echo "Failed to delete account."; - } - } -} +# The response will include an access_token. Use it in the Authorization header below. -// Usage example -$client = new Client('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY'); -$client->deleteAccount('ACCOUNT_ID'); -?> -``` - - -```ruby -require 'base64' -require 'net/http' -require 'uri' - -class Client - def initialize(public_key, secret_key) - @public_key = public_key - @secret_key = secret_key - @base_url = "https://api.pipedream.com/v1" - end - - def authorization_header - encoded = Base64.strict_encode64("#{@public_key}:#{@secret_key}") - "Basic #{encoded}" - end - - def delete_account(account_id) - uri = URI("#{@base_url}/connect/accounts/#{account_id}") - req = Net::HTTP::Delete.new(uri) - req['Authorization'] = authorization_header - - res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| - http.request(req) - end - - if res.code.to_i == 204 - puts "Account deleted successfully." - else - puts "Failed to delete account. Status code: #{res.code}" - end - end -end - -# Usage example -client = Client.new('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY') -client.delete_account('ACCOUNT_ID') +curl -X DELETE "https://api.pipedream.com/v1/connect/{project_id}/accounts/{account_id}" \ + -H "Authorization: Bearer {access_token}" ``` @@ -2835,347 +740,74 @@ Pipedream returns a `204 No Content` response on successful account deletion Delete all connected accounts for a specific app -```shell -DELETE /apps/:app_id/accounts +``` +DELETE /{project_id}/apps/{app_id}/accounts ``` -##### Parameters +##### Path parameters + +`project_id` **string** + +[The project's ID](/projects#finding-your-projects-id) -- `app_id` can be `oauth_app_id` for [OAuth apps](/connect/quickstart#creating-a-custom-oauth-client) or [name slug](/connect/quickstart/#find-your-apps-name-slug) for key-based apps +--- + +`app_id` **string** + +The app ID for which you want to delete all connected accounts. `app_id` can be `oauth_app_id` for [OAuth apps](/connect/quickstart#creating-a-custom-oauth-client) or [name slug](/connect/quickstart/#find-your-apps-name-slug) for key-based apps. ##### Examples - + ```typescript import { - createClient, + createBackendClient, } from "@pipedream/sdk"; -const pd = createClient({ - publicKey: "YOUR_PUBLIC_KEY", - secretKey: "YOUR_SECRET_KEY", +const pd = createBackendClient({ + credentials: { + clientId: "your-oauth-client-id", + clientSecret: "your-oauth-client-secret", + } }); -export async function deleteAccountsByApp(appId: string) { - await pd.deleteAccountsByApp(appId); +await pd.deleteAccountsByApp(appId); - // You can return a message or handle any post-deletion logic here. -} +// You can return a message or handle any post-deletion logic here. ``` ```javascript -const fetch = require('node-fetch'); +import { createBackendClient } from "@pipedream/sdk"; -class Client { - constructor({ publicKey, secretKey }) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = 'https://api.pipedream.com/v1'; +const pd = createBackendClient({ + credentials: { + clientId: "your-oauth-client-id", + clientSecret: "your-oauth-client-secret", } - - _authorizationHeader() { - const encoded = Buffer.from(`${this.publicKey}:${this.secretKey}`).toString('base64'); - return `Basic ${encoded}`; - } - - async deleteAccountsByApp(appId) { - const auth = this._authorizationHeader(); - const url = `${this.baseURL}/connect/accounts/app/${appId}`; - - const response = await fetch(url, { - method: 'DELETE', - headers: { - 'Authorization': auth, - }, - }); - - if (response.status === 204) { - console.log("Accounts deleted successfully."); - } else { - console.log("Failed to delete accounts."); - } - } -} - -// Usage example -const client = new Client({ - publicKey: 'YOUR_PUBLIC_KEY', - secretKey: 'YOUR_SECRET_KEY', }); -client.deleteAccountsByApp('APP_ID') - .catch(error => { - console.error('Error:', error); - }); -``` - - -```python -import base64 -import requests - -class Client: - def __init__(self, public_key, secret_key): - self.public_key = public_key - self.secret_key = secret_key - self.base_url = "https://api.pipedream.com/v1" - - def _authorization_header(self): - encoded = base64.b64encode(f"{self.public_key}:{self.secret_key}".encode()).decode() - return f"Basic {encoded}" - - def delete_accounts_by_app(self, app_id): - auth = self._authorization_header() - url = f"{self.base_url}/connect/accounts/app/{app_id}" - response = requests.delete(url, headers={"Authorization": auth}) - - if response.status_code == 204: - print("Accounts deleted successfully.") - else: - print("Failed to delete accounts.") - -# Usage example -client = Client('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY') -client.delete_accounts_by_app('APP_ID') -``` - - -```java -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Base64; -import java.nio.charset.StandardCharsets; - -public class Client { - private String publicKey; - private String secretKey; - private String baseURL; - - public Client(String publicKey, String secretKey) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = "https://api.pipedream.com/v1"; - } - - private String authorizationHeader() { - String encoded = Base64.getEncoder().encodeToString((publicKey + ":" + secretKey).getBytes(StandardCharsets.UTF_8)); - return "Basic " + encoded; - } - - public void deleteAccountsByApp(String appId) throws Exception { - String auth = authorizationHeader(); - URL url = new URL(baseURL + "/connect/accounts/app/" + appId); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("DELETE"); - conn.setRequestProperty("Authorization", auth); - - int responseCode = conn.getResponseCode(); - if (responseCode == 204) { - System.out.println("Accounts deleted successfully."); - } else { - System.out.println("Failed to delete accounts. Response code: " + responseCode); - } - } - - public static void main(String[] args) throws Exception { - Client client = new Client("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY"); - client.deleteAccountsByApp("APP_ID"); - } -} -``` - - -```csharp -using System; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading.Tasks; - -public class Client { - private string publicKey; - private string secretKey; - private string baseURL; - - public Client(string publicKey, string secretKey) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = "https://api.pipedream.com/v1"; - } - - private string AuthorizationHeader() { - string encoded = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes($"{publicKey}:{secretKey}")); - return $"Basic {encoded}"; - } - - public async Task DeleteAccountsByApp(string appId) { - string auth = AuthorizationHeader(); - using (HttpClient client = new HttpClient()) { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", auth); - HttpResponseMessage response = await client.DeleteAsync($"{baseURL}/connect/accounts/app/{appId}"); - - if (response.StatusCode == System.Net.HttpStatusCode.NoContent) { - Console.WriteLine("Accounts deleted successfully."); - } else { - Console.WriteLine("Failed to delete accounts."); - } - } - } - - public static async Task Main(string[] args) { - var client = new Client("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY"); - await client.DeleteAccountsByApp("APP_ID"); - } -} -``` - - -```go -package main - -import ( - "encoding/base64" - "fmt" - "net/http" -) - -type Client struct { - PublicKey string - SecretKey string - BaseURL string -} - -func NewClient(publicKey, secretKey string) *Client { - return &Client{ - PublicKey: publicKey, - SecretKey: secretKey, - BaseURL: "https://api.pipedream.com/v1", - } -} - -func (c *Client) authorizationHeader() string { - encoded := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", c.PublicKey, c.SecretKey))) - return fmt.Sprintf("Basic %s", encoded) -} - -func (c *Client) DeleteAccountsByApp(appId string) error { - url := fmt.Sprintf("%s/connect/accounts/app/%s", c.BaseURL, appId) - req, err := http.NewRequest("DELETE", url, nil) - if err != nil { - return err - } - - req.Header.Set("Authorization", c.authorizationHeader()) - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode == http.StatusNoContent { - fmt.Println("Accounts deleted successfully.") - } else { - fmt.Printf("Failed to delete accounts. Status code: %d\n", resp.StatusCode) - } - - return nil -} - -func main() { - client := NewClient("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY") +await pd.deleteAccount(accountId); - err := client.DeleteAccountsByApp("APP_ID") - if err != nil { - fmt.Println("Error:", err) - } -} +// You can return a message or handle any post-deletion logic here. ``` -```php -publicKey = $publicKey; - $this->secretKey = $secretKey; - $this->baseURL = "https://api.pipedream.com/v1"; - } - - private function authorizationHeader() { - return "Basic " . base64_encode("$this->publicKey:$this->secretKey"); - } +```bash +# First, obtain an OAuth access token +curl -X POST https://api.pipedream.com/v1/oauth/token \ + -H "Content-Type: application/json" \ + -d '{ + "grant_type": "client_credentials", + "client_id": "{oauth_client_id}", + "client_secret": "{oauth_client_secret}" + }' - public function deleteAccountsByApp($appId) { - $url = "$this->baseURL/connect/accounts/app/$appId"; - $opts = [ - 'http' => [ - 'header' => "Authorization: " . $this->authorizationHeader(), - 'method' => 'DELETE', - ], - ]; - - $context = stream_context_create($opts); - $result = file_get_contents($url, false, $context); - - if ($http_response_header[0] == 'HTTP/1.1 204 No Content') { - echo "Accounts deleted successfully."; - } else { - echo "Failed to delete accounts."; - } - } -} +# The response will include an access_token. Use it in the Authorization header below. -// Usage example -$client = new Client('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY'); -$client->deleteAccountsByApp('APP_ID'); -?> -``` - - -```ruby -require 'base64' -require 'net/http' -require 'uri' - -class Client - def initialize(public_key, secret_key) - @public_key = public_key - @secret_key = secret_key - @base_url = "https://api.pipedream.com/v1" - end - - def authorization_header - encoded = Base64.strict_encode64("#{@public_key}:#{@secret_key}") - "Basic #{encoded}" - end - - def delete_accounts_by_app(app_id) - uri = URI("#{@base_url}/connect/accounts/app/#{app_id}") - req = Net::HTTP::Delete.new(uri) - req['Authorization'] = authorization_header - - res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| - http.request(req) - end - - if res.code.to_i == 204 - puts "Accounts deleted successfully." - else - puts "Failed to delete accounts. Status code: #{res.code}" - end - end -end - -# Usage example -client = Client.new('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY') -client.delete_accounts_by_app('APP_ID') +curl -X DELETE "https://api.pipedream.com/v1/connect/{project_id}/accounts/{account_id}" \ + -H "Authorization: Bearer {access_token}" ``` @@ -3188,352 +820,150 @@ Pipedream returns a `204 No Content` response on successful account deletion Delete an end user and all their connected accounts -```shell -DELETE /users/:external_id +``` +DELETE /{project_id}/users/{external_user_id} ``` -##### Parameters +##### Path parameters + +`project_id` **string** + +[The project's ID](/projects#finding-your-projects-id) + +--- -- `external_id` — [The external user ID](#external-users) in your system +`external_user_id` **string** + +[The external user ID](#external-users) in your system ##### Examples - + ```typescript import { - createClient, + createBackendClient, } from "@pipedream/sdk"; -const pd = createClient({ - publicKey: "YOUR_PUBLIC_KEY", - secretKey: "YOUR_SECRET_KEY", +const pd = createBackendClient({ + credentials: { + clientId: "your-oauth-client-id", + clientSecret: "your-oauth-client-secret", + } }); -export async function deleteExternalUser(externalId: string) { - await pd.deleteExternalUser(externalId); - - console.log("All accounts associated with the external ID have been deleted."); -} +await pd.deleteExternalUser(externalId); +console.log("All accounts associated with the external ID have been deleted."); ``` ```javascript -const fetch = require('node-fetch'); +import { createBackendClient } from "@pipedream/sdk"; -class Client { - constructor({ publicKey, secretKey }) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = 'https://api.pipedream.com/v1'; +const pd = createBackendClient({ + credentials: { + clientId: "your-oauth-client-id", + clientSecret: "your-oauth-client-secret", } +}); - _authorizationHeader() { - const encoded = Buffer.from(`${this.publicKey}:${this.secretKey}`).toString('base64'); - return `Basic ${encoded}`; - } - - async deleteExternalUser(externalId) { - const auth = this._authorizationHeader(); - const url = `${this.baseURL}/users/${externalId}`; - - const response = await fetch(url, { - method: 'DELETE', - headers: { - 'Authorization': auth, - }, - }); - - if (response.status === 204) { - console.log("All accounts associated with the external ID have been deleted."); - } else { - console.log("Failed to delete accounts."); - } - } -} +const externalId = "{external_user_id}"; // Replace with your external user ID -// Usage example -const client = new Client({ - publicKey: 'YOUR_PUBLIC_KEY', - secretKey: 'YOUR_SECRET_KEY', -}); +await pd.deleteExternalUser(externalId); -client.deleteExternalUser('EXTERNAL_ID') - .catch(error => { - console.error('Error:', error); - }); +console.log("All accounts associated with the external ID have been deleted."); ``` -```python -import base64 -import requests - -class Client: - def __init__(self, public_key, secret_key): - self.public_key = public_key - self.secret_key = secret_key - self.base_url = "https://api.pipedream.com/v1" - - def _authorization_header(self): - encoded = base64.b64encode(f"{self.public_key}:{self.secret_key}".encode()).decode() - return f"Basic {encoded}" - - def delete_external_user(self, external_id): - auth = self._authorization_header() - url = f"{self.base_url}/users/{external_id}" - response = requests.delete(url, headers={"Authorization": auth}) - - if response.status_code == 204: - print("All accounts associated with the external ID have been deleted.") - else: - print("Failed to delete accounts.") - -# Usage example -client = Client('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY') -client.delete_external_user('EXTERNAL_ID') +```bash +# First, obtain an OAuth access token +curl -X POST https://api.pipedream.com/v1/oauth/token \ + -H "Content-Type: application/json" \ + -d '{ + "grant_type": "client_credentials", + "client_id": "{oauth_client_id}", + "client_secret": "{oauth_client_secret}" + }' + +# The response will include an access_token. Use it in the Authorization header below. + +curl -X DELETE "https://api.pipedream.com/v1/connect/{project_id}/users/{external_user_id}" \ + -H "Authorization: Bearer {access_token}" ``` - -```java -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Base64; -import java.nio.charset.StandardCharsets; - -public class Client { - private String publicKey; - private String secretKey; - private String baseURL; - - public Client(String publicKey, String secretKey) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = "https://api.pipedream.com/v1"; - } + - private String authorizationHeader() { - String encoded = Base64.getEncoder().encodeToString((publicKey + ":" + secretKey).getBytes(StandardCharsets.UTF_8)); - return "Basic " + encoded; - } +##### Response - public void deleteExternalUser(String externalId) throws Exception { - String auth = authorizationHeader(); - URL url = new URL(baseURL + "/users/" + externalId); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("DELETE"); - conn.setRequestProperty("Authorization", auth); - - int responseCode = conn.getResponseCode(); - if (responseCode == 204) { - System.out.println("All accounts associated with the external ID have been deleted."); - } else { - System.out.println("Failed to delete accounts. Response code: " + responseCode); - } - } +Pipedream returns a `204 No Content` response on successful account deletion - public static void main(String[] args) throws Exception { - Client client = new Client("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY"); - client.deleteExternalUser("EXTERNAL_ID"); - } -} -``` - - -```csharp -using System; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading.Tasks; - -public class Client { - private string publicKey; - private string secretKey; - private string baseURL; - - public Client(string publicKey, string secretKey) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = "https://api.pipedream.com/v1"; - } +### Projects - private string AuthorizationHeader() { - string encoded = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes($"{publicKey}:{secretKey}")); - return $"Basic {encoded}"; - } +#### Retrieve project info - public async Task DeleteExternalUser(string externalId) { - string auth = AuthorizationHeader(); - using (HttpClient client = new HttpClient()) { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", auth); - HttpResponseMessage response = await client.DeleteAsync($"{baseURL}/users/{externalId}"); - - if (response.StatusCode == System.Net.HttpStatusCode.NoContent) { - Console.WriteLine("All accounts associated with the external ID have been deleted."); - } else { - Console.WriteLine("Failed to delete accounts."); - } - } - } +Retrieve the configuration for a specific project - public static async Task Main(string[] args) { - var client = new Client("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY"); - await client.DeleteExternalUser("EXTERNAL_ID"); - } -} ``` - - -```go -package main +GET /{project_id}/info +``` -import ( - "encoding/base64" - "fmt" - "net/http" -) +##### Path parameters -type Client struct { - PublicKey string - SecretKey string - BaseURL string -} +`project_id` **string** -func NewClient(publicKey, secretKey string) *Client { - return &Client{ - PublicKey: publicKey, - SecretKey: secretKey, - BaseURL: "https://api.pipedream.com/v1", - } -} +[The project's ID](/projects#finding-your-projects-id) -func (c *Client) authorizationHeader() string { - encoded := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", c.PublicKey, c.SecretKey))) - return fmt.Sprintf("Basic %s", encoded) -} +##### Examples -func (c *Client) DeleteExternalUser(externalId string) error { - url := fmt.Sprintf("%s/users/%s", c.BaseURL, externalId) - req, err := http.NewRequest("DELETE", url, nil) - if err != nil { - return err - } - - req.Header.Set("Authorization", c.authorizationHeader()) - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode == http.StatusNoContent { - fmt.Println("All accounts associated with the external ID have been deleted.") - } else { - fmt.Printf("Failed to delete accounts. Status code: %d\n", resp.StatusCode) - } - - return nil -} + + +```typescript +import { createBackendClient } from "@pipedream/sdk"; -func main() { - client := NewClient("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY") +const pd = createBackendClient({ + credentials: { + clientId: "your-oauth-client-id", + clientSecret: "your-oauth-client-secret", + } +}); - err := client.DeleteExternalUser("EXTERNAL_ID") - if err != nil { - fmt.Println("Error:", err) - } -} +const info = await pd.getProjectInfo({ + project_id: "your-project-id", +}); ``` -```php -publicKey = $publicKey; - $this->secretKey = $secretKey; - $this->baseURL = "https://api.pipedream.com/v1"; - } - - private function authorizationHeader() { - return "Basic " . base64_encode("$this->publicKey:$this->secretKey"); - } +```javascript +import { createBackendClient } from "@pipedream/sdk"; - public function deleteExternalUser($externalId) { - $url = "$this->baseURL/users/$externalId"; - $opts = [ - 'http' => [ - 'header' => "Authorization: " . $this->authorizationHeader(), - 'method' => 'DELETE', - ], - ]; - - $context = stream_context_create($opts); - $result = file_get_contents($url, false, $context); - - if ($http_response_header[0] == 'HTTP/1.1 204 No Content') { - echo "All accounts associated with the external ID have been deleted."; - } else { - echo "Failed to delete accounts."; - } - } -} +const pd = createBackendClient({ + credentials: { + clientId: "your-oauth-client-id", + clientSecret: "your-oauth-client-secret", + } +}); -// Usage example -$client = new Client('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY'); -$client->deleteExternalUser('EXTERNAL_ID'); -?> +const info = await pd.getProjectInfo({ + project_id: "your-project-id", +}); ``` -```ruby -require 'base64' -require 'net/http' -require 'uri' - -class Client - def initialize(public_key, secret_key) - @public_key = public_key - @secret_key = secret_key - @base_url = "https://api.pipedream.com/v1" - end - - def authorization_header - encoded = Base64.strict_encode64("#{@public_key}:#{@secret_key}") - "Basic #{encoded}" - end - - def delete_external_user(external_id) - uri = URI("#{@base_url}/users/#{external_id}") - req = Net::HTTP::Delete.new(uri) - req['Authorization'] = authorization_header - - res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| - http.request(req) - end - - if res.code.to_i == 204 - puts "All accounts associated with the external ID have been deleted." - else - puts "Failed to delete accounts. Status code: #{res.code}" - end - end -end - -# Usage example -client = Client.new('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY') -client.delete_external_user('EXTERNAL_ID') +```bash +# First, obtain an OAuth access token +curl -X POST https://api.pipedream.com/v1/oauth/token \ + -H "Content-Type: application/json" \ + -d '{ + "grant_type": "client_credentials", + "client_id": "{oauth_client_id}", + "client_secret": "{oauth_client_secret}" + }' + +# The response will include an access_token. Use it in the Authorization header below. + +curl -X POST https://api.pipedream.com/v1/connect/{project_id}/info \ + -H "Authorization: Bearer {access_token}" \ ``` - -##### Response - -Pipedream returns a `204 No Content` response on successful account deletion \ No newline at end of file diff --git a/docs-v2/pages/connect/connect-link.mdx b/docs-v2/pages/connect/connect-link.mdx new file mode 100644 index 0000000000000..7f5f4cb097843 --- /dev/null +++ b/docs-v2/pages/connect/connect-link.mdx @@ -0,0 +1,32 @@ +# Connect Link + +Connect Link provides a Pipedream-hosted link that lets you connect third-party accounts without any frontend implementation in your app. + +## When to use Connect Link + +If you aren't able to execute JavaScript or open an iFrame in your frontend, or you want to send users a URL to connect accounts via email or SMS, use Connect Link. That URL opens a Pipedream-hosted page that guides the user through the account connection flow. The URL is scoped to the specific end user, and expires after 4 hours. + +## How to generate a link + +See [the Connect quickstart](/connect/quickstart) for a full tutorial for getting Connect up and running. + +Here's a quick overview of how to generate a Connect Link URL: + +1. First, [generate a token](/connect/quickstart/#generate-a-token) for your users. +2. Extract the `connect_link_url` from the token response. +3. Before returning the URL to your user, add an `app` parameter to the end of the query string: + +``` +https://pipedream.com/_static/connect.html?token={token}&connectLink=true&app={appSlug} +``` + +4. Redirect your users to this URL, or send it to them via email, SMS, and more. + +**To test this code, check out this workflow:** +[https://pipedream.com/new?h=tch_EvfbvQ](https://pipedream.com/new?h=tch_EvfbvQ) + +## Success and error redirect URLs + +When you [generate a Connect link](/connect/quickstart/#how-to-generate-a-link), you can specify `success_redirect_url` and `error_redirect_url` parameters. Pipedream will redirect the end user to these URLs after they complete the connection flow, or if an error occurs. [See the API docs](/connect/api#create-a-new-token) for details. + +In the absence of these URLs, Pipedream will redirect the user to a Pipedream-hosted success or error page at the end of the connection flow. \ No newline at end of file diff --git a/docs-v2/pages/connect/customize-your-app.mdx b/docs-v2/pages/connect/customize-your-app.mdx new file mode 100644 index 0000000000000..4443ad14f1cb8 --- /dev/null +++ b/docs-v2/pages/connect/customize-your-app.mdx @@ -0,0 +1,39 @@ +import ArcadeEmbed from '@/components/ArcadeEmbed' +import Callout from '@/components/Callout' + +# Customizing Your Application + + + + + +By default, your end users will see a primarly Pipedream branded experience when they connect their account. To customize this screen to highlight your application, you can configure your [app's name](#application-name), [support email](#support-email), and [logo](#logo) in the Pipedream UI. + + +## Customizing your application details + +Open your project in the Pipedream UI: [https://pipedream.com/projects](https://pipedream.com/projects) + +1. Once you've opened your project, click the **Connect** tab in the left sidebar +2. From there, select the **Configuration** tab + +![Pipedream Connect Configuration](https://res.cloudinary.com/pipedreamin/image/upload/v1731045468/connect-app-config_th9fqo.png) + +### Application name +By default, your end users will see: +>We use Pipedream to connect your account + +Enter the name of your application that you'd like to show instead, so it reads: +>\{Application Name\} uses Pipedream to connect your account + +### Support email +In the case of any errors during the account connection flow, by default your users will see: +>Connection failed. Please retry or contact support. + +To give your end users an email address to seek support, enter your support email. We'll display it: +>Connection failed. Please retry or contact support [help@example.com](mailto:help@example.com). + +### Logo +By default we'll show Pipedream's logo alongside the app your user is connecting to. If you'd like to show your own logo instead, upload it here. \ No newline at end of file diff --git a/docs-v2/pages/connect/environments.mdx b/docs-v2/pages/connect/environments.mdx new file mode 100644 index 0000000000000..a312c35c1ac54 --- /dev/null +++ b/docs-v2/pages/connect/environments.mdx @@ -0,0 +1,36 @@ +# Environments + +Pipedream Connect projects support two environments: `development` and `production`. + +1. Connected accounts and credentials stored in `development` remain separate from `production`. +2. In `development`, you can use the official Pipedream OAuth apps, so you can test integrations without creating your own OAuth client. + +## How to specify environment + +You specify the environment when [creating a new Connect token](/connect/api/#create-a-new-token) with the Pipedream SDK or API. By default, the enviromment is set to `production`. When users succesfully connect their account, Pipedream saves it for that `external_user_id` in the specified environment. + +You can set the environment when you create the SDK client: + +```typescript +import { createBackendClient } from "@pipedream/sdk"; + +const pd = createBackendClient({ + environment: "development", // change to production if running for a test production account, or in production + credentials: { + clientId: "your-oauth-client-id", + clientSecret: "your-oauth-client-secret", + } +}); +``` + +or pass the `X-PD-Environment` header in HTTP requests: + +```bash +curl -X POST https://api.pipedream.com/v1/connect/{project_id}/tokens \ + -H "Content-Type: application/json" \ + -H "X-PD-Environment: development" \ + -H "Authorization: Bearer {access_token}" \ + -d '{ + "external_user_id": "your-external-user-id" + }' +``` diff --git a/docs-v2/pages/connect/index.mdx b/docs-v2/pages/connect/index.mdx index fbac92d72b0cf..526f4f06f2935 100644 --- a/docs-v2/pages/connect/index.mdx +++ b/docs-v2/pages/connect/index.mdx @@ -5,23 +5,16 @@ import VideoPlayer from "@/components/VideoPlayer"; # Pipedream Connect - -**Pipedream Connect is currently in preview, and we're looking for feedback!** +Pipedream Connect is the easiest way for your users to connect to [over {process.env.PUBLIC_APPS}+ APIs](https://pipedream.com/apps), **right in your product**. You can build in-app messaging, CRM syncs, AI agents, [and much more](/connect/use-cases), all in a few minutes. Visit [the quickstart](/connect/quickstart) to build your first integration. -**During the preview phase, Connect is free to use for any workspace. The API may change without notice, which may cause breaking changes**. We'll do our best to communicate these changes. +You have full, code-level control over how these integrations work in your app. You handle your product, Pipedream simplifies the integration. -Please reach out at `connect@pipedream.com` or our [Slack community](https://pipedream.com/support) to let us know how you're using it, what's not working, and what else you'd like to see. - +Connect lets you: -Pipedream Connect is the easiest way for your users to connect to [over {process.env.PUBLIC_APPS}+ APIs](https://pipedream.com/apps), **right in your product**. You can build in-app messaging, CRM syncs, AI-driven products, [and much more](/connect/use-cases), all in a few minutes. Visit [the quickstart](/connect/quickstart) to build your first integration. - -Connect lets your users authorize access to any API, directly in your app. You can then retrieve fresh credentials for any account, making requests on their behalf. Pipedream handles the security of credentials and the whole OAuth flow — **no need to manage authorization grants or token refresh yourself.** - -You have full, code-level control over how these integrations work. You handle the product, Pipedream takes care of the auth. Connect provides: - -1. A [Client SDK](https://github.com/PipedreamHQ/pipedream/tree/master/packages/sdk) or [Connect Link](/connect/quickstart#use-connect-link) to initiate authorization or accept API keys on behalf of your users, for any of the [{process.env.PUBLIC_APPS}+ APIs](https://pipedream.com/apps) available on Pipedream -2. A [REST API](/connect/api) to retrieve credentials for your end users — OAuth access tokens, API keys, and other credentials stored for them -3. The Pipedream platform and its [workflow builder](/workflows), [serverless runtime](/), and thousands of no-code [triggers](/workflows/triggers) and [actions](/workflows/actions) +1. Handle authorization or accept API keys on behalf of your users, for any of Pipedream's [{process.env.PUBLIC_APPS}+ APIs](https://pipedream.com/apps). Use the [Client SDK](https://github.com/PipedreamHQ/pipedream/tree/master/packages/sdk) or [Connect Link](/connect/quickstart#use-connect-link) to accept auth in minutes. +2. Securely retrieve OAuth access tokens, API keys, and other credentials for your end users with Pipedream's [REST API](/connect/api) +3. Run workflows for your end users with Pipedream's [workflow builder](/workflows), [serverless runtime](/), and thousands of no-code [triggers](/workflows/triggers) and [actions](/workflows/actions). Build complex integrations in minutes, writing code when you need it and using no-code components when you don't. Pipedream workflows are easy to modify, debug, and scale. +4. Run [any Pipedream action](https://pipedream.com/explore) or [deploy any Pipedream trigger](https://pipedream.com/explore) on behalf of your users, directly from within your application.
@@ -42,15 +35,31 @@ Pipedream Connect lets you build any API integration into your product in minute Watch [the demo](https://www.youtube.com/embed/xhUagMsogkQ) or visit [the quickstart](/connect/quickstart) to build your first integration. -## Plans and pricing +## App configuration for OAuth apps + +Pipedream has more than {process.env.PUBLIC_APPS} apps available for you to integrate via Connect. Getting started is easy — just follow the [quickstart](/connect/quickstart) to get up and running. + +By default, apps that use OAuth to authenticate will use Pipedream's official OAuth client. To deploy your integrations to production, you'll need to configure your own OAuth client. Read more about OAuth clients in Pipedream [here](/connected-accounts/oauth-clients). + +[Let us know](https://pipedream.com/support) if the app you're looking for isn't listed [here](https://pipedream.com/apps). -During the preview phase, **Connect is free to use for any workspace**. +## Users -Please let us know if you have any feedback on the value of Connect and how you'd like to see it priced. +To view or delete your users' connected accounts: + +1. Open your project +2. Click the **Connect** tab +3. In the **Apps** section, add a new app. + +You'll see a list of all users, their connected accounts, and the option to delete any accounts from the UI. You can also retrieve and delete all your users via the API ([see the docs for reference](/connect/api)). + +## Plans and pricing + +**Connect is free to use for any workspace**. ## Security -Pipedream takes the security of our products seriously. Please [review our security docs](/privacy-and-security) and send us any questions or [suspected vulnerabilities](/privacy-and-security#reporting-a-vulnerability). You can also get a copy of our [SOC 2 Type 2 report](/privacy-and-security#soc-2), [sign HIPAA BAAs](/privacy-and-security#hipaa), and get information on other practices and controls. +Pipedream takes the security of our products seriously. See [details on Connect security](/privacy-and-security#pipedream-connect) and [our general security docs](/privacy-and-security). Please send us any questions or [suspected vulnerabilities](/privacy-and-security#reporting-a-vulnerability). You can also get a copy of our [SOC 2 Type 2 report](/privacy-and-security#soc-2), [sign HIPAA BAAs](/privacy-and-security#hipaa), and get information on other practices and controls. ### Storing user credentials, token refresh @@ -58,23 +67,15 @@ All credentials and tokens are sent to Pipedream securely over HTTPS, and encryp ### How to secure your Connect apps -- **Secure all secrets** — Secure your Pipedream project's secret key and user credentials. Never expose secrets in your client-side code. Make all requests to Pipedream's API and third-party APIs from your server-side code. +- **Secure all secrets** — Secure your Pipedream OAuth client credentials, and especially any user credentials. Never expose secrets in your client-side code. Make all requests to Pipedream's API and third-party APIs from your server-side code. - **Use HTTPS** — Always use HTTPS to secure your connections between your client and server. Requests to Pipedream's API will be automatically redirected to HTTPS. - **Use secure, session-based auth between your client and server** — authorize all requests from your client to your server using a secure, session-based auth mechanism. Use well-known identity providers with services like [Clerk](https://clerk.com/), [Firebase](https://firebase.google.com/), or [Auth0](https://auth0.com/) to securely generate and validate authentication tokens. The same follows for Pipedream workflows — if you trigger Pipedream workflows from your client or server, validate all requests in the workflow before executing workflow code. - **Secure your workflows** — See our [standard security practices](/privacy-and-security/best-practices) for recommendations on securing your Pipedream workflows. -## Product roadmap for Connect - -- Address bugs and feedback during the preview phase -- Use Pipedream OAuth clients while in development, to make it easier to get started -- Invoke Pipedream workflows on behalf of your end users -- Improve error handling for Connect developers and end users -- And more! - ## Glossary of terms - **App**: GitHub, Notion, Slack, Google Sheets, and more. The app is the API you want your users to connect to in your product. See the [full list here](https://pipedream.com/apps). - **Developer**: This is probably you, the Pipedream customer who's developing an app and wants to use Connect to make API requests on behalf of your end users. -- **End User**: Your customer or user, whose data you want to access on their behalf. End users are identifed via the `external_id` param in the Connect SDK and API. +- **End User**: Your customer or user, whose data you want to access on their behalf. End users are identifed via the `external_user_id` param in the Connect SDK and API. - **Connected Account**: The account your end user connects. [Read more about connected accounts](/connected-accounts). - **OAuth Client**: Custom OAuth clients you create in Pipedream. [Read more about OAuth clients](/connected-accounts/oauth-clients). diff --git a/docs-v2/pages/connect/migrating-from-project-keys-to-oauth.mdx b/docs-v2/pages/connect/migrating-from-project-keys-to-oauth.mdx new file mode 100644 index 0000000000000..77d25cde30f8a --- /dev/null +++ b/docs-v2/pages/connect/migrating-from-project-keys-to-oauth.mdx @@ -0,0 +1,103 @@ +import Callout from '@/components/Callout' +import { Steps, Tabs } from 'nextra/components' + +# Migrating to the 1.0 SDK + + +This guide is only relevant if: + +- You used the `0.x` version of the JavaScript SDK +- You authenticated with the Pipedream API using project keys + + +## What changed + +- In the `0.x` version of the SDK and the original Connect API, you could authenticate with keys scoped to a specific project. In the `1.x` version of the SDK, you need to authenticate with [OAuth clients](/rest-api/auth#oauth). +- The `createClient` method from both the browser and Node.js SDKs has been replaced with separate methods: `createFrontendClient` and `createBackendClient`, respectively. +- The `connectTokenCreate` method has been renamed `createConnectToken` +- New SDK methods: `projectInfo`, `invokeWorkflow`, and more + +## How to migrate + + + +### Create an OAuth client + +Follow the instructions [here](/rest-api/auth#oauth) to create an OAuth client. + +### Update your SDK version + +Change the `@pipedream/sdk` version in your `package.json`: + +```json +{ + "dependencies": { + "@pipedream/sdk": "1.0.0" + } +} +``` + +Then run + +```bash +npm install +``` + +### Update your project key references to use your OAuth client + +You may have been referencing project keys in environment variables or other config. Replace these with references to the OAuth client you created in step 1. + +For example, `process.env.PIPEDREAM_PROJECT_KEY` should be replaced with `process.env.PIPEDREAM_OAUTH_CLIENT_ID`, and `process.env.PIPEDREAM_PROJECT_SECRET` should be replaced with `process.env.PIPEDREAM_OAUTH_CLIENT_SECRET`. + +### Update your SDK code + +#### Frontend client + +You'll need to make two changes to your frontend client code: + +1. Replace references to `@pipedream/sdk/browser`. Instead, import directly from `@pipedream/sdk`. +2. Change the `createClient` method to `createFrontendClient`. + +```typescript +import { createFrontendClient } from "@pipedream/sdk" +const pd = createFrontendClient() +``` + +#### Backend client + +You'll need to make three changes to your backend code: + +1. Change the `createClient` method to `createBackendClient`. +2. Pass your OAuth client ID and secret to the `createBackendClient` method, removing references to project keys. +3. Change any token create method references from `connectTokenCreate` to `createConnectToken`. + + + +```typescript +import { createBackendClient, HTTPAuthType } from "@pipedream/sdk"; + +// These secrets should be saved securely and passed to your environment +const pd = createBackendClient({ + credentials: { + clientId: "YOUR_CLIENT_ID", + clientSecret: "YOUR_CLIENT_SECRET", + }, +}); +``` + + +```javascript +import { createBackendClient } from "@pipedream/sdk"; + +// These secrets should be saved securely and passed to your environment +const pd = createBackendClient({ + credentials: { + clientId: "YOUR_CLIENT_ID", + clientSecret: "YOUR_CLIENT_SECRET", + }, +}); +``` + + + + \ No newline at end of file diff --git a/docs-v2/pages/connect/oauth-clients.mdx b/docs-v2/pages/connect/oauth-clients.mdx new file mode 100644 index 0000000000000..b60e8f9d3e5dd --- /dev/null +++ b/docs-v2/pages/connect/oauth-clients.mdx @@ -0,0 +1,35 @@ +import Image from 'next/image' +import Callout from '@/components/Callout' + +# OAuth Clients + +When connecting an account for any OAuth app via Pipedream Connect, we'll default to using Pipedream's official OAuth client, which enables you to quickly get up and running. [Read more about OAuth clients in Pipedream here](/connected-accounts/oauth-clients). + +## Using Pipedream OAuth + +There are two types of apps in Pipedream: + +1. **Key-based**: These apps require static credentials, like API keys. Pipedream stores these credentials securely and exposes them via API. +2. **OAuth**: These apps require OAuth authorization. Pipedream manages the OAuth flow for these apps, ensuring you always have a fresh access token for requests. + +**OAuth apps require you create your own OAuth client to [deploy Connect to production](/connect/environments):** + + +To get started in [development mode](/connect/environments), you can skip these steps. To deploy your app to production, you'll need to [create an OAuth client](#using-a-custom-oauth-client) for the app you're integrating. + + +## Using a custom OAuth client + +1. Follow the steps [here](/connected-accounts/oauth-clients#configuring-custom-oauth-clients) to create an OAuth client in Pipedream. +2. Open the **Apps** tab within your Pipedream project and select **Add app**. +3. Select the app you're looking for, then select your OAuth client from the dropdown menu. +4. When connecting an account either via the [frontend SDK](/connect/quickstart#use-the-pipedream-sdk-in-your-frontend), make sure to include the `oauth_app_id` in `pd.connectAccount()`. +5. If using [Connect Link](/connect/quickstart#use-connect-link), make sure to include the `oauth_app_id` in the URL. + +### Finding your OAuth app ID + +[Create your OAuth client in Pipedream](https://pipedream.com/@/accounts/oauth-clients) then click the arrow to the left of the client name to expand the details. + +
+ +Copy OAuth App ID \ No newline at end of file diff --git a/docs-v2/pages/connect/quickstart.mdx b/docs-v2/pages/connect/quickstart.mdx index cf857e61cef56..eb305fdd118fa 100644 --- a/docs-v2/pages/connect/quickstart.mdx +++ b/docs-v2/pages/connect/quickstart.mdx @@ -5,7 +5,7 @@ import VideoPlayer from "@/components/VideoPlayer"; # Quickstart -Pipedream Connect is the easiest way for your users to connect to [over {process.env.PUBLIC_APPS}+ APIs](https://pipedream.com/apps), **right in your product**. You can build in-app messaging, CRM syncs, AI-driven products, [and much more](/connect/use-cases), all in a few minutes. +Pipedream Connect is the easiest way for your users to connect to [over {process.env.PUBLIC_APPS}+ APIs](https://pipedream.com/apps), **right in your product**. You can build in-app messaging, CRM syncs, AI agents, [and much more](/connect/use-cases), all in a few minutes. ## Visual overview @@ -14,48 +14,29 @@ Here's a high-level overview of how Connect works with your app:
Pipedream Connect overview -And here's the technical flow between your frontend, backend, and Pipedream's API, described step-by-step below: +Here's how Connect sits in your frontend and backend, and communicates with Pipedream's API:
Connect developer flow - - -### Add the apps you want to integrate - -1. Open an existing Pipedream project or create a new one at [https://pipedream.com/projects](https://pipedream.com/projects). -2. Click the **Connect** tab, then select **Apps**. -3. From here, you can add any app to your project that you want to integrate with your application. - -There are two types of apps in Pipedream: - -1. **Key-based**: These apps require static credentials, like API keys. Pipedream stores these credentials securely and exposes them via API. -2. **OAuth**: These apps require OAuth authorization. Pipedream manages the OAuth flow for these apps, ensuring you always have a fresh access token for requests. -**OAuth apps require you [create your own OAuth client](#creating-a-custom-oauth-client):** -1. First, [create an OAuth client](/connected-accounts/oauth-clients#configuring-custom-oauth-clients) for the app you'd like to integrate. -2. Now when selecting an OAuth app in the **Apps** tab, you'll be prompted to select the OAuth client you've created. +## Getting started -### Get your project keys - -1. From within the the **Connect** tab in your project, select **Configuration**. -3. Find your project's **Public Key** and **Secret Key**. - -You'll need these when configuring the SDK and making API requests. - -
- -Project keys in the Connect tab + -### Run the Pipedream demo app or configure your own +### Run the Pipedream example app or configure your own -You'll need to do two things to integrate Pipedream Connect into your app: +You'll need to do two things to add Pipedream Connect to your app: -1. [Connect to the Pipedream API from your server](#connect-to-the-pipedream-api-from-your-server-and-create-a-token). This lets you make secure calls to the Pipedream API to initiate the account connection flow and retrieve account credentials. If you're running a JavaScript framework like Node.js on the server, you can use the Pipedream SDK. -2. [Add the Pipedream SDK to your frontend](#connect-your-account-from-the-frontend) or deliver a URL to your users to start the account connection flow. +1. [Connect to the Pipedream API from your server](#connect-to-the-pipedream-api-from-your-server-and-create-a-token). This lets you make secure calls to the Pipedream API to initiate the account connection flow and retrieve account credentials. If you're running a JavaScript framework like Node.js on your server, you can use the Pipedream SDK. +2. [Add the Pipedream SDK to your frontend](#connect-your-account-from-the-frontend) or redirect your users to [a Pipedream-hosted URL](/connect/connect-link) to start the account connection flow. We'll walk through these steps below, using [an example Next.js app](https://github.com/PipedreamHQ/pipedream-connect-examples/tree/master/next-app/). To follow along, clone [the repo](https://github.com/PipedreamHQ/pipedream-connect-examples/) and follow the instructions in [the app's `README`](https://github.com/PipedreamHQ/pipedream-connect-examples/tree/master/next-app/). That will run the app on `localhost:3000`. + +The Next.js app is just an example. You can build Connect apps in any framework, using any language. We've provided examples in Python, Ruby, and others below. + + First, copy the `.env.example` file to `.env.local`: ```bash @@ -66,450 +47,36 @@ and fill the `.env.local` file with your project and app details: ```bash # Used by `app/server.ts` to authorize requests to the Pipedream API — see below -PIPEDREAM_PROJECT_PUBLIC_KEY=pub_abc123 -PIPEDREAM_PROJECT_SECRET_KEY=sec_abc123 +PIPEDREAM_CLIENT_ID=your_client_id +PIPEDREAM_CLIENT_SECRET=your_client_secret +PIPEDREAM_PROJECT_ENVIRONMENT=development +PIPEDREAM_PROJECT_ID=your_project_id ``` If you're building your own app, you'll need to provide these values to the environment, or retrieve them from your secrets store. -### Connect to the Pipedream API from your server and create a token - - -**Why do I need to talk to the Pipedream API from my server?** - -You need to secure specific operations, for example: - -- You need to initiate the account connection flow for your end users. In Pipedream Connect, **you exchange your project keys for a short-lived token that allows a specific user to connect a specific app**, and return either that token or the `connect_link_url` to your frontend. -- If you expose your Pipedream project keys directly to the frontend, anyone could initiate the account connection flow for any user. -- You need to retrieve account credentials for your end users. Again, this should happen securely on your server, not in the frontend, to protect your users' data. - - - -In this Next.js example, we're running Node.js code on our server, via [Next server components](https://nextjs.org/docs/app/building-your-application/rendering/server-components), so we'll use the Pipedream SDK. Additional examples in Python, Ruby, and other languages are noted below. - -To install the [Pipedream SDK](https://www.npmjs.com/package/@pipedream/sdk) in your own project, run: - -```bash -npm i --save @pipedream/sdk -``` - -To create a short-lived token via TypeScript / JavaScript SDK, you'll need to create a Pipedream API client and call the `connectTokenCreate` method. In our example app, this code is in `app/server.ts`. - -In other languages, you'll need to make an HTTP POST request to the `/v1/connect/tokens` endpoint to create a token, then return the token to your frontend. Click into other tabs to see examples in additional languages. - - - -```typescript -"use server"; - -import { - createClient, - type ConnectAPIResponse, - type ConnectTokenCreateOpts, - type ConnectTokenResponse, -} from "@pipedream/sdk"; - -const pd = createClient({ - publicKey: process.env.PIPEDREAM_PROJECT_PUBLIC_KEY, - secretKey: process.env.PIPEDREAM_PROJECT_SECRET_KEY, -}); - -export async function serverConnectTokenCreate(opts: ConnectTokenCreateOpts): Promise> { - return pd.connectTokenCreate(opts); -} -``` - - -```javascript -const fetch = require('node-fetch'); - -class Client { - constructor(opts) { - this.secretKey = opts.secretKey; - this.publicKey = opts.publicKey; - - const apiHost = 'api.pipedream.com'; - this.baseURL = `https://${apiHost}`; - } - - _authorizationHeader() { - const encoded = Buffer.from(`${this.publicKey}:${this.secretKey}`).toString('base64'); - return `Basic ${encoded}`; - } - - async connectTokenCreate(opts) { - const auth = this._authorizationHeader(); - const response = await fetch(`${this.baseURL}/v1/connect/tokens`, { - method: 'POST', - headers: { - 'Authorization': auth, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(opts), - }); - - return response.json(); - } -} - -const client = new Client({ - publicKey: 'YOUR_PUBLIC_KEY', - secretKey: 'YOUR_SECRET_KEY', -}); - -const connectTokenOpts = { - external_id: "USER_ID" // The end user's ID in your system -} - -// Expose this code as an API endpoint in your server to fetch the token from the frontend -client.connectTokenCreate(connectTokenOpts) - .then(response => { - // return the token to the frontend - }) - .catch(error => { - // handle errors - }); -``` - - -```python -import base64 -import json -import requests - -class Client: - def __init__(self, opts): - self.secret_key = opts['secret_key'] - self.public_key = opts['public_key'] - - api_host = 'api.pipedream.com' - self.base_url = f"https://{api_host}" - - def _authorization_header(self): - encoded = base64.b64encode(f"{self.public_key}:{self.secret_key}".encode()).decode() - return f"Basic {encoded}" - - def connect_token_create(self, opts): - auth = self._authorization_header() - response = requests.post( - f"{self.base_url}/v1/connect/tokens", - headers={ - "Authorization": auth, - "Content-Type": "application/json", - }, - data=json.dumps(opts) - ) - return response.json() - -# Usage example -client = Client({ - 'public_key': 'YOUR_PUBLIC_KEY', - 'secret_key': 'YOUR_SECRET_KEY', -}) - -connect_token_opts = { - 'external_id': "USER_ID" -} - -# Expose this code as an API endpoint in your server to fetch the token from the frontend -response = client.connect_token_create(connect_token_opts) - -``` - - -```java -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Base64; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; - -public class Client { - private String secretKey; - private String publicKey; - private String baseURL; - - public Client(String secretKey, String publicKey) { - this.publicKey = publicKey; - this.secretKey = secretKey; - - String apiHost = "api.pipedream.com"; - this.baseURL = "https://" + apiHost; - } - - private String authorizationHeader() { - String encoded = Base64.getEncoder().encodeToString((publicKey + ":" + secretKey).getBytes(StandardCharsets.UTF_8)); - return "Basic " + encoded; - } - - public String connectTokenCreate(String externalId) throws Exception { - String auth = authorizationHeader(); - URL url = new URL(baseURL + "/v1/connect/tokens"); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("POST"); - conn.setRequestProperty("Authorization", auth); - conn.setRequestProperty("Content-Type", "application/json"); - conn.setDoOutput(true); - - String jsonInputString = String.format("{\"external_id\":\"%s\"}", externalId); - - try (OutputStream os = conn.getOutputStream()) { - byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8); - os.write(input, 0, input.length); - } - - return new String(conn.getInputStream().readAllBytes(), StandardCharsets.UTF_8); - } - - public static void main(String[] args) throws Exception { - Client client = new Client("YOUR_SECRET_KEY", "YOUR_PUBLIC_KEY"); - - // Expose this code as an API endpoint in your server to fetch the token from the frontend - String response = client.connectTokenCreate("USER_ID"); - } -} - -``` - - -```csharp -using System; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; - -public class Client { - private string secretKey; - private string publicKey; - private string baseURL; - - public Client(string secretKey, string publicKey) { - this.publicKey = publicKey; - this.secretKey = secretKey; - - string apiHost = "api.pipedream.com"; - this.baseURL = $"https://{apiHost}"; - } - - private string AuthorizationHeader() { - string encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{publicKey}:{secretKey}")); - return $"Basic {encoded}"; - } - - public async Task ConnectTokenCreate(string externalId) { - string auth = AuthorizationHeader(); - using (HttpClient client = new HttpClient()) { - client.DefaultRequestHeaders.Add("Authorization", auth); - client.DefaultRequestHeaders.Add("Content-Type", "application/json"); - - var content = new StringContent($"{{\"external_id\":\"{externalId}\"}}", Encoding.UTF8, "application/json"); - var response = await client.PostAsync($"{baseURL}/v1/connect/tokens", content); - - return await response.Content.ReadAsStringAsync(); - } - } - - public static async Task Main(string[] args) { - var client = new Client("YOUR_SECRET_KEY", "YOUR_PUBLIC_KEY"); - - // Expose this code as an API endpoint in your server to fetch the token from the frontend - string response = await client.ConnectTokenCreate("USER_ID"); - } -} - -``` - - -```go -package main - -import ( - "bytes" - "encoding/base64" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" -) - -type Client struct { - SecretKey string - PublicKey string - BaseURL string -} - -func NewClient(secretKey, publicKey string) *Client { - apiHost := "api.pipedream.com" - baseURL := fmt.Sprintf("https://%s", apiHost) - - return &Client{ - PublicKey: publicKey, - SecretKey: secretKey, - } -} +### Create a project in Pipedream -func (c *Client) authorizationHeader() string { - encoded := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", c.PublicKey, c.SecretKey))) - return fmt.Sprintf("Basic %s", encoded) -} - -func (c *Client) ConnectTokenCreate(externalId string) (map[string]interface{}, error) { - auth := c.authorizationHeader() - url := fmt.Sprintf("%s/v1/connect/tokens", c.BaseURL) - - opts := map[string]string{ - "external_id": externalId, - } - - jsonData, err := json.Marshal(opts) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) - if err != nil { - return nil, err - } - - req.Header.Set("Authorization", auth) - req.Header.Set("Content-Type", "application/json") - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - body, _ := ioutil.ReadAll(resp.Body) - - var result map[string]interface{} - json.Unmarshal(body, &result) - return result, nil -} - -func main() { - client := NewClient("YOUR_SECRET_KEY", "YOUR_PUBLIC_KEY") +1. Open an existing Pipedream project or create a new one at [https://pipedream.com/projects](https://pipedream.com/projects). +2. Click the **Settings** tab, then copy your **Project ID**. - // Expose this code as an API endpoint in your server to fetch the token from the frontend - response, err := client.ConnectTokenCreate("USER_ID") - if err != nil { - fmt.Println("Error:", err) - return - } -} +### Create a Pipedream OAuth client -``` - - -```php -publicKey = $publicKey; - $this->secretKey = $secretKey; - - $apiHost = 'api.pipedream.com'; - $this->baseURL = "https://$apiHost"; - } - - private function authorizationHeader() { - $encoded = base64_encode("$this->publicKey:$this->secretKey"); - return "Basic $encoded"; - } - - public function connectTokenCreate($externalId) { - $auth = $this->authorizationHeader(); - $url = "$this->baseURL/v1/connect/tokens"; - - $data = json_encode([ - 'external_id' => $externalId - ]); - - $options = [ - 'http' => [ - 'header' => [ - "Authorization: $auth", - "Content-Type: application/json", - ], - 'method' => 'POST', - 'content' => $data, - ], - ]; - - $context = stream_context_create($options); - $result = file_get_contents($url, false, $context); - - return json_decode($result, true); - } -} +Pipedream uses OAuth to authorize requests to the REST API. To create an OAuth client: -// Usage example -$client = new Client('YOUR_SECRET_KEY', 'YOUR_PUBLIC_KEY'); +1. Visit the [API settings](https://pipedream.com/settings/api) for your workspace. +2. Click the **New OAuth Client** button. +3. Name your client and click **Create**. +4. Copy the client secret. **It will not be accessible again**. Click **Close**. +5. Copy the client ID from the list. -$connectTokenOpts = [ - 'external_id' => "USER_ID" -]; +You'll need these when configuring the SDK and making API requests. -// Expose this code as an API endpoint in your server to fetch the token from the frontend -$response = $client->connectTokenCreate($connectTokenOpts['external_id']); -?> -``` - - -```ruby -require 'base64' -require 'json' -require 'net/http' -require 'uri' - -class Client - def initialize(secret_key, public_key) - @public_key = public_key - @secret_key = secret_key - - api_host = 'api.pipedream.com' - @base_url = "https://#{api_host}" - end - - def authorization_header - encoded = Base64.strict_encode64("#{@public_key}:#{@secret_key}") - "Basic #{encoded}" - end - - def connect_token_create(external_id) - uri = URI("#{@base_url}/v1/connect/tokens") - req = Net::HTTP::Post.new(uri) - req['Authorization'] = authorization_header - req['Content-Type'] = 'application/json' - req.body = { external_id: external_id }.to_json - - res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| - http.request(req) - end - - JSON.parse(res.body) - end -end - -client = Client.new('YOUR_SECRET_KEY', 'YOUR_PUBLIC_KEY') - -connect_token_opts = { - external_id: "USER_ID" -} +### Generate a short-lived token -# Expose this code as an API endpoint in your server to fetch the token from the frontend -response = client.connect_token_create(connect_token_opts[:external_id]) -``` - - +To securely initiate account connections for your users, you'll need generate a short-lived token for your users and use that in the [account connection flow](#connect-a-users-account). See [the docs on Connect tokens](/connect/tokens) for a general overview of why we need to create tokens and scope them to end users. -In our Next.js app, we call the `serverConnectTokenCreate` method from the frontend to retrieve a token **for a specific user**. +In the Next.js example here, we're running [Next server components](https://nextjs.org/docs/app/building-your-application/rendering/server-components) in `app/server.ts`. We call the `serverConnectTokenCreate` method from the frontend to retrieve a token **for a specific user**. ```typescript import { serverConnectTokenCreate } from "./server" @@ -519,22 +86,32 @@ const { token, expires_at } = await serverConnectTokenCreate({ }); ``` -If you're using a different server / API framework, you'll need to make secure calls to that API to create a new token for your users. For example, you might validate a user's session, then call the Pipedream API to create a new token for that user. +If you're using a different server / API framework, you'll need to make secure calls to that API to create a new token for your users. + +Once you have a token, return it to your frontend to start the account connection flow for the user, or redirect them to a Pipedream-hosted URL with [Connect Link](#use-connect-link). + + +Refer to the API docs for [full set of parameters you can pass](/connect/api#create-a-new-token) in the `ConnectTokenCreate` call. + + +### Connect a user's account -### Connect your account +To connect a third-party account for a user, you have two options: -To actually connect an account, you have two options: 1. [Use the Pipedream SDK](#use-the-pipedream-sdk-in-your-frontend) in your frontend 2. [Use Connect Link](#use-connect-link) to deliver a hosted URL to your user #### Use the Pipedream SDK in your frontend + +Use this method when you want to handle the account connection flow yourself, in your app. For example, you might want to show a **Connect Slack** button in your app that triggers the account connection flow. + First, install the [Pipedream SDK](https://www.npmjs.com/package/@pipedream/sdk) in your frontend: ```bash npm i --save @pipedream/sdk ``` -When the user connects an account in your product, [fetch a token from your backend](#connect-to-the-pipedream-api-from-your-server-and-create-a-token) and call `connectAccount`. This opens a Pipedream iFrame that guides the user through the account connection. +When the user connects an account in your product, [pass the token from your backend](#connect-to-the-pipedream-api-from-your-server-and-create-a-token) and call `connectAccount`. This opens a Pipedream iFrame that guides the user through the account connection. In our example, `app/page.tsx` calls the `connectAccount` method from the Pipedream SDK when the user clicks the **Connect your account** button. @@ -542,14 +119,14 @@ In our example, `app/page.tsx` calls the `connectAccount` method from the Pipedr Connect your account button ```typescript -// Note that we import the browser-specific SDK client here -import { createClient } from "@pipedream/sdk/browser" +import { createFrontendClient } from "@pipedream/sdk" export default function Home() { - const pd = createClient() + const pd = createFrontendClient() function connectAccount() { pd.connectAccount({ - app: process.env.NEXT_PUBLIC_PIPEDREAM_APP_SLUG, // From the Next.js example — adjust to pass your own app name slug + app: appSlug, // Pass the app name slug of the app you want to integrate + oauthAppId: appId, // For OAuth apps, pass the OAuth app ID; omit this param to use Pipedream's OAuth client or for key-based apps token: "YOUR_TOKEN", // The token you received from your server above onSuccess: ({ id: accountId }) => { console.log(`Account successfully connected: ${accountId}`) @@ -559,7 +136,7 @@ export default function Home() { return (
- +
) } @@ -568,39 +145,45 @@ export default function Home() { Press that button to connect an account for the app you configured. #### Use Connect Link -- Avoid any frontend implementation -- If you aren't able to execute JavaScript or open an iFrame in your frontend, you can use the `connect_link_url` that's returned from the `connectTokenCreate` method to deliver a URL to your users. -- Before returning the URL to your user, you'll need to include the `app` on the URL. For example, see below. -- Users can open this URL in a browser window to connect their account. + +Use this option when you can't execute JavaScript or open an iFrame in your environment (e.g. mobile apps), or when you want to offload the account connection flow to Pipedream and avoid frontend work. You can also send these links via email or SMS. + +The Connect Link opens a Pipedream-hosted page, guiding users through the account connection process. The URL is specific to the user and expires after 4 hours. + +1. First, [generate a token](#generate-a-token) for your users. +2. Extract the `connect_link_url` from the token response. +3. Before returning the URL to your user, add an `app` parameter to the end of the query string: ``` -https://pipedream.com/_static/connect.html?token={token}&connectLink=true&app={appSlug} +https://pipedream.com/_static/connect.html?token={token}&connectLink=true&app={appSlug}&oauthAppId={oauthAppId} ``` -**To test this code, check out this workflow:** -[https://pipedream.com/new?h=tch_EvfbvQ](https://pipedream.com/new?h=tch_EvfbvQ) +4. Redirect your users to this URL, or send it to them via email, SMS, and more. ### Retrieve the credentials from the backend -Once the user connects an account, you can retrieve their credentials from your backend with your project keys. +Once your user connects an account, you can retrieve their credentials from your backend. -This example shows you how to fetch credentials by your end user's `external_id` and the app's name slug. You can also fetch all connected accounts for a specific app, or a specific user — see the [Connect API reference](/connect/api). +This example shows you how to fetch credentials by your end user's `external_user_id`. You can also fetch all connected accounts for a specific app, or a specific user — see the [Connect API reference](/connect/api). - + ```typescript import { - createClient, + createBackendClient, } from "@pipedream/sdk"; -const pd = createClient({ - publicKey: PIPEDREAM_PROJECT_PUBLIC_KEY, - secretKey: PIPEDREAM_PROJECT_SECRET_KEY, +const pd = createBackendClient({ + credentials: { + clientId: "your-oauth-client-id", + clientSecret: "your-oauth-client-secret", + } }); -export async function getUserAccounts(externalId: string, include_credentials: number = 0) { - await pd.getAccountsByExternalId(externalId, { - include_credentials, // set to 1 to include credentials +async function getUserAccounts(external_user_id: string, include_credentials: boolean = false) { + await pd.getAccounts({ + external_user_id, + include_credentials, // set to true to include credentials }) // Parse and return the data you need. These may contain credentials, @@ -610,367 +193,33 @@ export async function getUserAccounts(externalId: string, include_credentials: n ```javascript -const fetch = require('node-fetch'); - -class Client { - constructor({ publicKey, secretKey }) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = 'https://api.pipedream.com'; - } - - _authorizationHeader() { - const encoded = Buffer.from(`${this.publicKey}:${this.secretKey}`).toString('base64'); - return `Basic ${encoded}`; - } - - async getUserAccounts(externalId) { - const auth = this._authorizationHeader(); - const url = `${this.baseURL}/v1/connect/users/${externalId}/accounts`; - - const response = await fetch(url, { - method: 'GET', - headers: { - 'Authorization': auth, - 'Content-Type': 'application/json', - }, - }); - - const data = await response.json(); - - if (!data?.accounts?.length) { - return null; - } +import { + createBackendClient, +} from "@pipedream/sdk"; - // Parse and return data.accounts. Ensure to handle sensitive data appropriately. - return data.accounts; +const pd = createBackendClient({ + credentials: { + clientId: "your-oauth-client-id", + clientSecret: "your-oauth-client-secret", } -} - -// Usage example -const client = new Client({ - publicKey: 'YOUR_PUBLIC_KEY', - secretKey: 'YOUR_SECRET_KEY', }); -client.getUserAccounts('USER_ID') - .then(response => { - // handle response +async function getUserAccounts(external_user_id, include_credentials = false) { + await pd.getAccounts({ + external_user_id, + include_credentials, // set to true to include credentials }) - .catch(error => { - console.error('Error:', error); - }); -``` - - -```python -import base64 -import requests - -class Client: - def __init__(self, public_key, secret_key): - self.public_key = public_key - self.secret_key = secret_key - self.base_url = "https://api.pipedream.com" - - def _authorization_header(self): - encoded = base64.b64encode(f"{self.public_key}:{self.secret_key}".encode()).decode() - return f"Basic {encoded}" - - def get_user_accounts(self, external_id): - auth = self._authorization_header() - url = f"{self.base_url}/v1/connect/users/{external_id}/accounts" - response = requests.get(url, headers={"Authorization": auth}) - data = response.json() - - if not data.get('accounts'): - return None - - # Parse and return data['accounts']. Ensure to handle sensitive data appropriately. - return data['accounts'] - -# Usage example -client = Client('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY') -response = client.get_user_accounts('USER_ID') -``` - - -```java -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Base64; -import java.io.InputStreamReader; -import java.io.BufferedReader; -import java.nio.charset.StandardCharsets; - -public class Client { - private String publicKey; - private String secretKey; - private String baseURL; - - public Client(String publicKey, String secretKey) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = "https://api.pipedream.com"; - } - - private String authorizationHeader() { - String encoded = Base64.getEncoder().encodeToString((publicKey + ":" + secretKey).getBytes(StandardCharsets.UTF_8)); - return "Basic " + encoded; - } - - public String getUserAccounts(String externalId) throws Exception { - String auth = authorizationHeader(); - URL url = new URL(baseURL + "/v1/connect/users/" + externalId + "/accounts"); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("GET"); - conn.setRequestProperty("Authorization", auth); - - BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); - String inputLine; - StringBuilder content = new StringBuilder(); - while ((inputLine = in.readLine()) != null) { - content.append(inputLine); - } - in.close(); - - String response = content.toString(); - - // Parse response and return accounts data. Ensure to handle sensitive data appropriately. - if (!response.contains("accounts")) { - return null; - } - - return response; // Modify to parse and handle accounts as needed. - } - - public static void main(String[] args) throws Exception { - Client client = new Client("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY"); - String response = client.getUserAccounts("USER_ID"); - } -} -``` - - -```csharp -using System; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Threading.Tasks; - -public class Client { - private string publicKey; - private string secretKey; - private string baseURL; - - public Client(string publicKey, string secretKey) { - this.publicKey = publicKey; - this.secretKey = secretKey; - this.baseURL = "https://api.pipedream.com"; - } - - private string AuthorizationHeader() { - string encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{publicKey}:{secretKey}")); - return $"Basic {encoded}"; - } - - public async Task GetUserAccounts(string externalId) { - string auth = AuthorizationHeader(); - using (HttpClient client = new HttpClient()) { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", auth); - HttpResponseMessage response = await client.GetAsync($"{baseURL}/v1/connect/users/{externalId}/accounts"); - - string result = await response.Content.ReadAsStringAsync(); - - // Parse and return accounts data. Ensure to handle sensitive data appropriately. - if (!result.Contains("accounts")) { - return null; - } - - return result; // Modify to parse and handle accounts as needed. - } - } - - public static async Task Main(string[] args) { - var client = new Client("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY"); - string response = await client.GetUserAccounts("USER_ID"); - } -} -``` - - -```go -package main - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" -) - -type Client struct { - PublicKey string - SecretKey string - BaseURL string -} - -func NewClient(publicKey, secretKey string) *Client { - return &Client{ - PublicKey: publicKey, - SecretKey: secretKey, - BaseURL: "https://api.pipedream.com", - } -} - -func (c *Client) authorizationHeader() string { - encoded := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", c.PublicKey, c.SecretKey))) - return fmt.Sprintf("Basic %s", encoded) -} - -func (c *Client) GetUserAccounts(externalId string) (map[string]interface{}, error) { - url := fmt.Sprintf("%s/v1/connect/users/%s/accounts", c.BaseURL, externalId) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, err - } - - req.Header.Set("Authorization", c.authorizationHeader()) - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - var result map[string]interface{} - err = json.Unmarshal(body, &result) - if err != nil { - return nil, err - } - - if accounts, exists := result["accounts"]; exists { - return accounts.([]interface{}), nil - } - - return nil, nil -} - -func main() { - client := NewClient("YOUR_PUBLIC_KEY", "YOUR_SECRET_KEY") - accounts, err := client.GetUserAccounts("USER_ID") - if err != nil { - fmt.Println("Error:", err) - return - } -} -``` - - -```php -publicKey = $publicKey; - $this->secretKey = $secretKey; - $this->baseURL = "https://api.pipedream.com"; - } - - private function authorizationHeader() { - return "Basic " . base64_encode("$this->publicKey:$this->secretKey"); - } - - public function getUserAccounts($externalId) { - $url = "$this->baseURL/v1/connect/users/$externalId/accounts"; - $opts = [ - 'http' => [ - 'header' => "Authorization: " . $this->authorizationHeader(), - 'method' => 'GET', - ], - ]; - - $context = stream_context_create($opts); - $result = file_get_contents($url, false, $context); - - if ($result === FALSE) { - return null; - } - - $data = json_decode($result, true); - - if (empty($data['accounts'])) { - return null; - } - - return $data['accounts']; // Modify to parse and handle accounts as needed. - } + // Parse and return the data you need. These may contain credentials, + // which you should never return to the client } - -// Usage example -$client = new Client('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY'); -$response = $client->getUserAccounts('USER_ID'); -?> -``` - - -```ruby -require 'base64' -require 'json' -require 'net/http' -require 'uri' - -class Client - def initialize(public_key, secret_key) - @public_key = public_key - @secret_key = secret_key - @base_url = "https://api.pipedream.com" - end - - def authorization_header - encoded = Base64.strict_encode64("#{@public_key}:#{@secret_key}") - "Basic #{encoded}" - end - - def get_user_accounts(external_id) - uri = URI("#{@base_url}/v1/connect/users/#{external_id}/accounts") - req = Net::HTTP::Get.new(uri) - req['Authorization'] = authorization_header - - res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| - http.request(req) - end - - data = JSON.parse(res.body) - - # Parse and return accounts data. Ensure to handle sensitive data appropriately. - return nil if data['accounts'].nil? || data['accounts'].empty? - - data['accounts'] - end -end - -# Usage example -client = Client.new('YOUR_PUBLIC_KEY', 'YOUR_SECRET_KEY') -response = client.get_user_accounts('USER_ID') ``` ### Deploy your app to production -Now that you've successfully connected an account and retrieved the credentials, you're ready to deploy your app to production! +- Now that you've successfully connected an account and retrieved the credentials, you're almost done! +- Learn about [development environments](/connect/environments) and [using OAuth clients](/connect/oauth-clients) to deploy your app to production.
diff --git a/docs-v2/pages/connect/tokens.mdx b/docs-v2/pages/connect/tokens.mdx new file mode 100644 index 0000000000000..b4dc1c03c3d33 --- /dev/null +++ b/docs-v2/pages/connect/tokens.mdx @@ -0,0 +1,26 @@ +# Connect Tokens + +When you initiate account connection for your end users, you must either: + +1. Generate a secure, short-lived token scoped to the end user, or +2. Use the [Connect Link](/connect/connect-link) feature to generate a URL that guides the user through the account connection flow without any frontend work on your side. + +Here, we'll show you how to generate tokens for your users and return that to your frontend, passing that to the account connection flow. + +Use tokens when you want to handle the account connection flow yourself, in your app's UI. For example, you might want to show a **Connect Slack** button in your app that triggers the account connection flow for Slack, or launch the flow in a modal. + +## Creating a token + +See docs on [the `/tokens` endpoint](/connect/api/#create-a-new-token) to create new tokens. + +## Webhooks + +When you generate a token, you can specify a `webhook_uri` where Pipedream will deliver updates on the account connection flow. This is useful if you want to update your UI based on the status of the account connection flow, get a log of errors, and more. + +[See the webhooks docs](/connect/webhooks) for more information. + +## Tokens are scoped to end users and project environments + +When you [create a new Connect token](/connect/api/#create-a-new-token), you pass an `external_user_id` and an optional `environment_name` parameter. By default, the environment is set to `production`. See the docs on [environments](/connect/environments) for more information. + +Tokens are scoped to this user and environment. When the user successfully connects an account with that token, it will be saved for that `external_user_id` in the specified `environment_name`. diff --git a/docs-v2/pages/connect/use-cases.mdx b/docs-v2/pages/connect/use-cases.mdx index 9bdcb3a7c55d7..5d1867920331b 100644 --- a/docs-v2/pages/connect/use-cases.mdx +++ b/docs-v2/pages/connect/use-cases.mdx @@ -1,4 +1,4 @@ -# Pipedream Connect use cases +# Pipedream Connect Use Cases Developers use Pipedream Connect to build customer-facing API integrations into their products. It lets you build [in-app messaging](#in-app-messaging), [CRM syncs](#crm-syncs), [AI-driven products](#ai-products), and much more, all in a few minutes. diff --git a/docs-v2/pages/connect/webhooks.mdx b/docs-v2/pages/connect/webhooks.mdx new file mode 100644 index 0000000000000..f0ee3a0172d57 --- /dev/null +++ b/docs-v2/pages/connect/webhooks.mdx @@ -0,0 +1,54 @@ +# Connect Webhooks + +When you [generate a Connect token](/connect/quickstart/#generate-a-token), you can pass a `webhook_uri` parameter. Pipedream will send a POST request to this URL when the user completes the connection flow, or if an error occurs at any point. [See the API docs](/connect/api#create-a-new-token) for details. + +## Webhook events + +- `CONNECTION_SUCCESS` - Sent when the user successfully connects their account +- `CONNECTION_ERROR` - Sent when an error occurs during the connection flow + +## Webhook payload + +### Successful connection + +Please note that user credentials are not sent in the webhook request. To retrieve credentials, use the [Connect API to fetch the account](/connect/api#retrieve-account-details-by-id) using the `account.id` provided in the webhook payload. + +```json +{ + "event": "CONNECTION_SUCCESS", + "connect_token": "abc123", + "environment": "production", + "connect_session_id": 123, + "account": { + "id": "apn_abc123", + "name": "My Slack workspace", + "external_id": "U123456", + "healthy": true, + "dead": false, + "app": { + "id": "app_abc123", + "name_slug": "slack", + "name": "Slack", + "auth_type": "oauth", + "description": "Slack is a channel-based messaging platform", + "img_src": "https://assets.pipedream.net/icons/slack.svg", + "custom_fields_json": [], + "categories": "Communication", + }, + "created_at": "2021-09-01T00:00:00Z", + "updated_at": "2021-09-01T00:00:00Z", + } +} +``` + +### Error + +```json +{ + "event": "CONNECTION_ERROR", + "connect_token": "abc123", + "environment": "production", + "connect_session_id": 123, + "error": "You've hit your limit on the number of external users you can connect." +} +``` \ No newline at end of file diff --git a/docs-v2/pages/connected-accounts/index.mdx b/docs-v2/pages/connected-accounts/index.mdx index edb4aff970777..a3f6c814259d4 100644 --- a/docs-v2/pages/connected-accounts/index.mdx +++ b/docs-v2/pages/connected-accounts/index.mdx @@ -237,7 +237,11 @@ If you use a secrets store like [Pipedream Connect](/connect) or [HashiCorp Vaul ## Connecting to apps with IP restrictions -If you're connecting to an app that enforces IP restrictions, you may need to whitelist Pipedream's IP addresses: + +These IP addresses are tied to **app connections only**, not workflows or other Pipedream services. To whitelist requests from Pipedream workflows, [use VPCs](/workflows/vpc). + + +If you're connecting to an app that enforces IP restrictions, you may need to whitelist the Pipedream API's IP addresses:
   {process.env.PD_EGRESS_IP_RANGE}
diff --git a/docs-v2/pages/destinations/http.mdx b/docs-v2/pages/destinations/http.mdx
index 70b0c2679d81c..c5ef2307c5c05 100644
--- a/docs-v2/pages/destinations/http.mdx
+++ b/docs-v2/pages/destinations/http.mdx
@@ -112,13 +112,12 @@ Currently, Pipedream will not retry any failed request. If your HTTP destination
 
 ## IP addresses for Pipedream HTTP requests
 
+
+These IP addresses are tied to **requests sent with `$.send.http` only, not other HTTP requests made from workflows**. To whitelist standard HTTP requests from Pipedream workflows, [use VPCs](/workflows/vpc).
+
+
 When you make an HTTP request using `$.send.http()`, the traffic will come from one of the following IP addresses:
 
 
 
-This list may change over time. If you've previously whitelisted these IP addresses and are having trouble sending HTTP requests to your target service, please check to ensure this list matches your firewall rules.
-
-
-These IP addresses are tied specifically to the `$.send.http()` service. If you send traffic directly from a workflow, it will be sent from one of Pipedream's general range of IP addresses. [See our hosting docs for more information](/privacy-and-security/#hosting-details).
-
-
+This list may change over time. If you've previously whitelisted these IP addresses and are having trouble sending HTTP requests to your target service, please check to ensure this list matches your firewall rules.
\ No newline at end of file
diff --git a/docs-v2/pages/environment-variables.mdx b/docs-v2/pages/environment-variables.mdx
index 5ab87eb184222..d7280377117c7 100644
--- a/docs-v2/pages/environment-variables.mdx
+++ b/docs-v2/pages/environment-variables.mdx
@@ -114,6 +114,10 @@ You can reference the value of environment variables using `{{process.env.YOUR_E
   src="https://res.cloudinary.com/pipedreamin/image/upload/v1708551738/env-vars-feb-2024/env-vars-object-explorer-v2_x9afzl.png"
 />
 
+
+  [Private components](https://pipedream.com/docs/components#using-components) (actions or triggers) do not have direct access to workspace or project variables as public components or code steps. Add a prop specifically for the variable you need. For sensitive data like API keys, [configure the prop as a secret](https://pipedream.com/docs/components/api#props). In your prop configuration, set the value to `{{process.env.YOUR_ENV_VAR}}` to securely reference the environment variable.
+
+
 ## Frequently Asked Questions
 
 ### What if I define the same variable key in my workspace env vars and project env vars?
diff --git a/docs-v2/pages/privacy-and-security/best-practices.mdx b/docs-v2/pages/privacy-and-security/best-practices.mdx
index 77846fa98647c..dac77a201837a 100644
--- a/docs-v2/pages/privacy-and-security/best-practices.mdx
+++ b/docs-v2/pages/privacy-and-security/best-practices.mdx
@@ -1,10 +1,10 @@
 # Security Best Practices
 
-Pipedream implements a range of [privacy and security measures](/privacy-and-security/) meant to protect your data from unauthorized access. Since Pipedream [workflows](/workflows/), [event sources](/sources/), and other resources can run any Node.js code and process any event data, you also have a responsibility to ensure you handle that code and data securely. We've outlined a handful of best practices for that below.
+Pipedream implements a range of [privacy and security measures](/privacy-and-security/) meant to protect your data from unauthorized access. Since Pipedream [workflows](/workflows/), [event sources](/sources/), and other resources can run any code and process any event data, you also have a responsibility to ensure you handle that code and data securely. We've outlined a handful of best practices for that below.
 
 ## Store secrets as Pipedream connected accounts or environment variables
 
-Even if your workflow code is private, you should never store secrets like API keys in code. These secrets should be stored in one of two ways:
+Never store secrets like API keys directly in code. These secrets should be stored in one of two ways:
 
 - [If Pipedream integrates with the app](https://pipedream.com/apps), use [connected accounts](/connected-accounts/) to link your apps / APIs.
 - If you need to store credentials for an app Pipedream doesn't support, or you need to store arbitrary configuration data, use [environment variables](/environment-variables/).
@@ -13,21 +13,19 @@ Read more about how Pipedream secures connected accounts / environment variables
 
 ## Deliver data to Pipedream securely
 
-Whenever possible, encrypt data in transit to Pipedream. For example, use HTTPS endpoints when sending HTTP traffic to a workflow.
+Always send data over HTTPS to Pipedream endpoints.
 
 ## Send data out of Pipedream securely
 
 When you connect to APIs in a workflow, or deliver data to third-party destinations, encrypt that data in transit. For example, use HTTPS endpoints when sending HTTP traffic to third parties.
 
-## Add authentication to incoming event data
+## Require authorization for HTTP triggers
 
-You can add many public-facing triggers to your workflows. For example, when you add an HTTP trigger to your workflow, anyone with the associated trigger URL can invoke it. You should protect your workflow with an authentication mechanism like [Basic Auth](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication), JWT, or others.
+HTTP triggers are public by default, and require no authorization or token to invoke.
 
-The easiest way to do this is to use the [Validate Webhook Auth action](https://pipedream.com/apps/http/actions/validate-webhook-auth). This supports common auth options, and you don't have to write any code to configure it.
+For many workflows, you should [configure authorization](/workflows/triggers#authorizing-http-requests) to ensure that only authorized parties can invoke your HTTP trigger.
 
-If you need to implement custom logic in code, see [this workflow](https://pipedream.com/new?h=tch_OaJfNv) for a shared API key example. This reads the header `x-api-key` and compares it to the [environment variable](/environment-variables/) called `YOUR_WEBHOOK_API_KEY`. If the `x-api-key` header does not match this variable, it returns a `401 Unauthorized` error and exits the workflow early.
-
-This pattern is typical for protecting workflows: add the authentication logic in a step at the top of your workflow, ending early if auth fails. If auth succeeds, Pipedream runs the remaining steps of your workflow.
+For third-party services like webhooks, that authorize requests using their own mechanism, use the [Validate Webhook Auth action](https://pipedream.com/apps/http/actions/validate-webhook-auth). This supports common auth options, and you don't have to write any code to configure it.
 
 ## Validate signatures for incoming events, where available
 
diff --git a/docs-v2/pages/privacy-and-security/index.mdx b/docs-v2/pages/privacy-and-security/index.mdx
index b0a3736f9cd9c..9564dea0a0773 100644
--- a/docs-v2/pages/privacy-and-security/index.mdx
+++ b/docs-v2/pages/privacy-and-security/index.mdx
@@ -22,7 +22,7 @@ If you suspect Pipedream resources are being used for illegal purposes, or other
 
 ### SOC 2
 
-Pipedream undergoes regular third-party audits. We have demonstrated SOC 2 compliance and can provide a SOC 2 Type 2 report upon request. Please reach out to support@pipedream.com to request the latest report.
+Pipedream undergoes annual third-party audits. We have demonstrated SOC 2 compliance and can provide a SOC 2 Type 2 report upon request. Please reach out to support@pipedream.com to request the latest report.
 
 We use [Drata](https://drata.com) to continuosly monitor our infrastructure's compliance with standards like SOC 2, and you can visit our [Security Report](https://app.drata.com/security-report/b45c2f79-1968-496b-8a10-321115b55845/27f61ebf-57e1-4917-9536-780faed1f236) to see a list of policies and processes we implement and track within Drata.
 
@@ -86,6 +86,61 @@ No credentials are logged in your source or workflow by default. If you log thei
 
 You can delete your OAuth grants or key-based credentials at any time by visiting [https://pipedream.com/accounts](https://pipedream.com/accounts). Deleting OAuth grants within Pipedream **do not** revoke Pipedream's access to your account. You must revoke that access wherever you manage OAuth grants in your third party application.
 
+## Pipedream REST API security, OAuth clients
+
+The Pipedream API supports two methods of authentication: [OAuth](/rest-api/auth#oauth) and [User API keys](/rest-api/auth#user-api-keys). **We recommend using OAuth clients** for a few reasons:
+
+✅ OAuth clients are tied to the workspace, administered by workspace admins 
+✅ Tokens are short-lived
+✅ OAuth clients support scopes, limiting access to specific operations
+ +When testing the API or using the CLI, you can use your user API key. This key is tied to your user account and provides full access to any resources your user has access to, across workspaces. + +### OAuth clients + +Pipedream supports client credentials OAuth clients, which exchange a client ID and client secret for a short-lived access token. These clients are not tied to individual end users, and are meant to be used server-side. You must store these credentials securely on your server, never allowing them to be exposed in client-side code. + +Client secrets are salted and hashed before being saved to the database. The hashed secret is encrypted at rest. Pipedream does not store the client secret in plaintext. + +You can revoke a specific client secret at any time by visiting [https://pipedream.com/settings/api](https://pipedream.com/settings/api). + +### OAuth tokens + +Since Pipedream uses client credentials grants, access tokens must not be shared with end users or stored anywhere outside of your server environment. + +Access tokens are issued as JWTs, signed with an ED25519 private key. The public key used to verify these tokens is available at [https:/api.pipedream.com/.well-known/jwks.json](https://pipedream.com/.well-known/jwks.json). See [this workflow template](https://pipedream.com/new?h=tch_rBf76M) for example code you can use to validate these tokens. + +Access tokens are hashed before being saved in the Pipedream database, and are encrypted at rest. + +Access tokens expire after 1 hour. Tokens can be revoked at any time. + +## Pipedream Connect + +[Pipedream Connect](/connect) is the easiest way for your users to connect to [over {process.env.PUBLIC_APPS}+ APIs](https://pipedream.com/apps), **right in your product**. + +### Client-side SDK + +Pipedream provides a [client-side SDK](/connect/api#typescript-sdk-browser) to initiate authorization or accept API keys on behalf of your users in environments that can run JavaScript. You can see the code for that SDK [here](https://github.com/PipedreamHQ/pipedream/tree/master/packages/sdk). + +When you initiate authorization, you must: + +1. [Create a server-side token for a specific end user](/connect/api#create-a-new-token) +2. Initiate auth with that token, connecting an account for a specific user + +These tokens can only initiate the auth connection flow. They have no permissions to access credentials or perform other operations against the REST API. They are meant to be scoped to a specific user, for use in clients that need to initiate auth flows. + +Tokens expire after 4 hours, at which point you must create a new token for that specific user. + +### Connect Link + +You can also use [Connect Link](/connect/quickstart#use-connect-link) to generate a URL that initiates the authorization flow for a specific user. This is useful when you want to initiate the auth flow from a client-side environment that can't run JavaScript, or include the link in an email, chat message, etc. + +Like tokens, Connect Links are coupled to specific users, and expire after 4 hours. + +### REST API + +The Pipedream Connect API is a subset of the [Pipedream REST API](/rest-api/). See the [REST API Security](#pipedream-rest-api-security-oauth-clients) section for more information on how we secure the API. + ## Execution environment The **execution environment** refers to the environment in which your sources, workflows, and other Pipedream code is executed. diff --git a/docs-v2/pages/projects/index.mdx b/docs-v2/pages/projects/index.mdx index 3ddd71ed76e6c..86b7709da1e6d 100644 --- a/docs-v2/pages/projects/index.mdx +++ b/docs-v2/pages/projects/index.mdx @@ -75,6 +75,10 @@ At this time it's not possible to move workflows out of GitHub Synchronized Proj +## Finding your project's ID + +Visit your project's **Settings** and copy the project ID. + ## Access Controls The [projects list view](https://pipedream.com/projects) contains **Owner** and **Access** columns. diff --git a/docs-v2/pages/rest-api/auth.mdx b/docs-v2/pages/rest-api/auth.mdx index 4447e67c0712d..d6d6a995fd987 100644 --- a/docs-v2/pages/rest-api/auth.mdx +++ b/docs-v2/pages/rest-api/auth.mdx @@ -1,16 +1,105 @@ # Authentication -## Pipedream API Key +The Pipedream API supports two methods of authentication: [OAuth](#oauth) and [User API keys](#user-api-keys). -When you sign up for Pipedream, an API key is automatically generated for your account. You can use this key to authorize requests to the API. +**We recommend OAuth** for a few reasons: + +✅ OAuth clients are tied to the workspace, administered by workspace admins
+✅ Tokens are short-lived
+✅ OAuth clients support scopes, limiting access to specific operations (coming soon!)
+✅ Limit access to specific Pipedream projects (coming soon!)
+ +When testing the API or using the CLI, you can use your user API key. This key is tied to your user account and provides full access to any resources your user has access to, across workspaces. + +## OAuth + +Workspace administrators can create OAuth clients in your workspace's [API settings](https://pipedream.com/settings/api). + +Since API requests are meant to be made server-side, and since grants are not tied to individual end users, all OAuth clients are [**Client Credentials** applications](https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/). + +### Creating an OAuth client + +1. Visit the [API settings](https://pipedream.com/settings/api) for your workspace. +2. Click the **New OAuth Client** button. +3. Name your client and click **Create**. +4. Copy the client secret. **It will not be accessible again**. Click **Close**. +5. Copy the client ID from the list. + +### How to get an access token + +In the client credentials model, you exchange your OAuth client ID and secret for an access token. Then you use the access token to make API requests. + +If you're running a server that executes JavaScript, we recommend using [the Pipedream SDK](/connect/api#installing-the-typescript-sdk), which automatically refreshes tokens for you. + +```javascript +import { createBackendClient } from "@pipedream/sdk"; + +// These secrets should be saved securely and passed to your environment +const pd = createBackendClient({ + credentials: { + clientId: "YOUR_CLIENT_ID", + clientSecret: "YOUR_CLIENT_SECRET", + }, +}); + +// Use the SDK's helper methods to make requests +const accounts = await pd.getAccounts({ include_credentials: 1 }); + +// Or make any Pipedream API request with the fresh token +const accounts = await pd.makeRequest("/accounts", { + method: "GET" + headers: { + "Authorization": await this.oauthAuthorizationHeader(), // Automatically uses a fresh token + }, + params: { + include_credentials: 1, + } +}); +``` + +You can also manage this token refresh process yourself, using the `/oauth/token` API endpoint: + +```bash +curl https://api.pipedream.com/v1/oauth/token \ + -H 'Content-Type: application/json' \ + -d '{ "grant_type": "client_credentials", "client_id": "", "client_secret": "" }' +``` + +Access tokens expire after 1 hour. Store access tokens securely, server-side. + +### Revoking a client secret + +1. Visit your workspace's [API settings](https://pipedream.com/settings/api). +2. Click the **...** button to the right of the OAuth client whose secret you want to revoke, then click **Rotate client secret**. +3. Copy the new client secret. **It will not be accessible again**. + +### OAuth security + +See [the OAuth section of the security docs](/privacy-and-security#pipedream-rest-api-security-oauth-clients) for more information on how Pipedream secures OAuth credentials. + +## User API keys + +When you sign up for Pipedream, an API key is automatically generated for your user account. You can use this key to authorize requests to the API. You'll find this API key in your [User Settings](https://pipedream.com/user) (**My Account** -> **API Key**). +**Use user API keys when testing the API or using the CLI**. This key is tied to your user account and provides full access to any resources your user has access to, across workspaces. + +### Revoking your API key + +You can revoke your API key in your [Account Settings](https://pipedream.com/settings/account) (**Settings** -> **Account**). Click on the **REVOKE** button directly to the right of your API key. + +This will revoke your original API key, generating a new one. Any API requests made with the original token will yield a `401 Unauthorized` error. + ## Authorizing API requests -Pipedream uses [Bearer Authentication](https://oauth.net/2/bearer-tokens/) to authorize your access to the API or SSE event streams. When you make API requests, pass an `Authorization` header of the following format: +Whether you use OAuth access tokens or user API keys, Pipedream uses [Bearer Authentication](https://oauth.net/2/bearer-tokens/) to authorize your access to the API or SSE event streams. When you make API requests, pass an `Authorization` header of the following format: ``` +# OAuth access token +Authorization: Bearer + +# User API key Authorization: Bearer ``` @@ -24,9 +113,3 @@ curl 'https://api.pipedream.com/v1/users/me' \ ## Using the Pipedream CLI You can [link the CLI to your Pipedream account](/cli/login/), which will automatically pass your API key in the `Authorization` header with every API request. - -## Revoking your API key - -You can revoke your API key in your [Account Settings](https://pipedream.com/settings/account) (**Settings** -> **Account**). Click on the **REVOKE** button directly to the right of your API key. - -This will revoke your original API key, generating a new one. Any API requests made with the original token will yield a `401 Unauthorized` error. diff --git a/docs-v2/pages/rest-api/index.mdx b/docs-v2/pages/rest-api/index.mdx index c0a22ee4feed1..6337444fde7b0 100644 --- a/docs-v2/pages/rest-api/index.mdx +++ b/docs-v2/pages/rest-api/index.mdx @@ -4,7 +4,7 @@ import Callout from "@/components/Callout"; ## Overview -Use the REST API to create and manage sources, workflows, subscriptions, and more. +Use the REST API to create workflows, manage event sources, handle subscriptions, and more. ## Base URL @@ -12,34 +12,33 @@ The base URL for all requests is [https://api.pipedream.com/v1](https://api.pipe ## Authentication -You authenticate to the REST API using your [Pipedream API -key](/rest-api/auth/#pipedream-api-key). When you make API requests, pass an -`Authorization` header of the following format: +The Pipedream API supports two methods of authentication: [OAuth](/rest-api/auth#oauth) and [User API keys](/rest-api/auth#user-api-keys). **Pipedream recommends using OAuth for most use cases**. -``` -Authorization: Bearer -``` - -For example, here's how you can use `cURL` to fetch profile information for the -authenticated user: +All credentials are passed as a Bearer token in the `Authorization` header. For example: ```shell -curl 'https://api.pipedream.com/v1/users/me' \ - -H 'Authorization: Bearer ' +curl https://api.pipedream.com/v1/accounts \ + -H "Authorization Bearer " ``` -Learn more about [API authentication](/rest-api/auth/) +Learn more in the [Authentication docs](/rest-api/auth/). + +### Authenticating as a workspace vs. a user + +Pipedream recommends using [OAuth](/rest-api/auth#oauth) to auth against the Pipedream API. OAuth tokens are associated with a workspace, and the API will automatically use the workspace associated with the token. + +When you authenticate with a user API key, you must [specify the workspace ID in the `org_id` parameter](#common-parameters) when making requests to specific endpoints. ## Required headers -The `Authorization` header is required on all endpoints for authentication. +The `Authorization` header is required on all endpoints, to authenticate API requests. `POST` or `PUT` requests that accept JSON payloads also require a `Content-Type` header set to `application/json`. For example: ```shell curl https://api.pipedream.com/v1/components \ - -H "Authorization: Bearer " \ + -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"component_url": "https://github.com/PipedreamHQ/pipedream/blob/master/components/rss/sources/new-item-in-feed/new-item-in-feed.js"}' ``` @@ -70,25 +69,18 @@ including all fields). Pass as a string of comma-separated values: --- -`workspace_id` **string** - -Some endpoints require you to specify [your workspace ID](/workspaces/#finding-your-workspace-s-id) you want the operation to take effect in. For example, if you're creating a new event source in a specific workspace, you'll want to pass the workspace ID in the `workspace_id` query string parameter. - -[Find your workspace's ID here](/workspaces/#finding-your-workspace-s-id). +`org_id` **string** - - If your organization is on one of our legacy plans like the Free Teams or - Teams plan, the `workspace_id` is synonymous with your `org_id`. Just pass - your organization ID as the same parameter. + +The `org_id` parameter is only required when using [User API keys](/rest-api/auth#user-api-keys). When authenticating with OAuth tokens, the API will automatically use the workspace associated with the token. -## Working with resources owned by a workspace +When using [User API keys](/rest-api/auth#user-api-keys), some endpoints require you to specify [your workspace ID](/workspaces/#finding-your-workspace-s-id) you want the operation to take effect in: -If you're interacting with resources owned by a [workspace](/workspaces/), you may need to specify the workspace ID as a part of the request's query string parameter or route: +- When _fetching_ specific resources (for example, when you [retrieve events for a specific source](#get-source-events)), **you should not need to pass `org_id`**. If your user is a part of the workspace, and you have access to that resource, and the API will return the details of the resource. +- When _creating_ new resources, you'll need to specify the `org_id` in which you want to create the resource. -- When fetching specific resources (for example, when you [retrieve events for a specific source](#get-source-events)), you should not need to pass your workspace's ID. If your user is a part of the workspace, you should have access to that resource, and the API will return the details of the resource. -- When _creating_ new resources, you'll need to specify the `org_id` where you want the resource to live as a query string parameter (`?org_id=o_abc123`). Read more about the `org_id` parameter in the [Common Parameters section](#common-parameters). -- When _listing_ resources, use [the workspace-specific endpoints here](#workspaces). +[Find your workspace / org ID here](/workspaces/#finding-your-workspace-s-id). ## Pagination @@ -124,8 +116,6 @@ A cursor, specifying you'd like to retrieve items _before_ this cursor. Cursor strings are returned with all paginated responses. ---- - ### Example Paginated Request This request fetches a page of 5 sources in the authenticated account, after a @@ -165,19 +155,81 @@ The response from the request above will have a shape that looks like: ## Errors Pipedream uses conventional HTTP response codes to indicate the success or -failure of an API request. Codes in the **2xx** range indicate success. Codes in -the **4xx** range indicate an error that failed (e.g., a required parameter was -omitted). Codes in the **5xx** range indicate an error with Pipedream’s server. +failure of an API request: + +- Codes in the `2xx` range indicate success. +- Codes in the `4xx` range indicate an error that failed (e.g., a required parameter was omitted). +- Codes in the `5xx` range indicate an error with Pipedream's server. ## Accounts [Connected accounts](/connected-accounts/) let you manage credentials for integrated APIs. -### Get account +### List accounts + +List connected accounts accessible by the authenticated user or workspace. + +``` +GET /accounts/ +``` + +#### Parameters + +`app` **string** (_optional_) + +The ID or name slug the app you'd like to retrieve. For example, Slack's unique app ID is `app_OkrhR1`, and its name slug is `slack`. + +You can find the app's ID in the response from the [List apps](#list-apps) endpoint, and the name slug under the **Authentication** section of any [app page](https://pipedream.com/apps). + +--- + +`oauth_app_id` **string** (_optional_) + +The ID of the custom [OAuth app](/connect/quickstart#creating-a-custom-oauth-client) you'd like to retrieve accounts for. --- -By default, this route returns metadata for a specific connected account. Set `include_credentials=1` to return credentials that you can use in any app where you need auth. [See this guide](/connected-accounts/api/) to learn more. +`external_user_id` **string** (_optional_) + +[The external user ID](/connect/api/#external-users) in your system that you want to retrieve accounts for. + +--- + +`include_credentials` **boolean** (_optional_) + +Pass `include_credentials=true` as a query-string parameter to include the account credentials in the response + +#### Example Request — Get account metadata + +```bash +curl 'https://api.pipedream.com/v1/accounts' \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" +``` + +#### Example Response — Get account metadata + +```json +{ + "data": [ + { + "id": "apn_abc123", + "created_at": "2022-07-27T20:37:52.000Z", + "updated_at": "2024-02-11T04:18:46.000Z", + "name": "Google Sheets — pipedream.com", // account nickname, if set + "app": { + "id": "app_abc123", + "name": "Google Sheets" + }, + "healthy": true // true if Pipedream can make a successful test request + } + ] +} +``` + +### Get account + +By default, this route returns metadata for a specific connected account. Set `include_credentials=true` to return credentials that you can use in any app where you need auth. [See this guide](/connected-accounts/api/) to learn more. #### Endpoint @@ -187,8 +239,6 @@ GET /accounts/{account_id} #### Parameters ---- - `account_id` **string** To retrieve your account ID: @@ -201,21 +251,21 @@ To retrieve your account ID:
-`include_credentials` **number** +--- -Default `0`: Response only contains account metadata, no credentials. Pass `1` to include `credentials`. +`include_credentials` **boolean** (_optional_) ---- +Pass `include_credentials=true` as a query-string parameter to include the account credentials in the response -#### Example Request — Get account metadata +#### Example Request — Get account metadata ```bash curl 'https://api.pipedream.com/v1/accounts/' \ - -H "Authorization: Bearer " \ + -H "Authorization: Bearer " \ -H "Content-Type: application/json" ``` -#### Example Response — Get account metadata +#### Example Response — Get account metadata ```json { @@ -223,7 +273,7 @@ curl 'https://api.pipedream.com/v1/accounts/' \ "id": "apn_abc123", "created_at": "2022-07-27T20:37:52.000Z", "updated_at": "2024-02-11T04:18:46.000Z", - "name": "Google Sheets — pipedream.com", // account nickname, if set + "name": "Google Sheets — pipedream.com", // account nickname, if set "app": { "id": "app_abc123", "name": "Google Sheets" @@ -236,8 +286,8 @@ curl 'https://api.pipedream.com/v1/accounts/' \ #### Example Request — Get account credentials ```bash -curl 'https://api.pipedream.com/v1/accounts/?include_credentials=1' \ - -H "Authorization: Bearer " \ +curl 'https://api.pipedream.com/v1/accounts/?include_credentials=true' \ + -H "Authorization: Bearer " \ -H "Content-Type: application/json" ``` @@ -250,7 +300,7 @@ curl 'https://api.pipedream.com/v1/accounts/?include_credentials=1' "created_at": "2022-07-27T20:37:52.000Z", "updated_at": "2024-02-11T04:18:46.000Z", "expires_at": "2024-02-11T05:18:46.000Z", - "name": "Google Sheets — pipedream.com", // account nickname, if set + "name": "Google Sheets — pipedream.com", // account nickname, if set "app": { "id": "app_abc123", "name": "Google Sheets" @@ -268,20 +318,134 @@ The properties of the `credentials` object are specific to the app. All OAuth apps expose the following properties: -- `oauth_access_token` — A fresh OAuth access token -- `oauth_client_id` — The client ID of the OAuth app -- `oauth_refresh_token` — The latest OAuth refresh token for your grant -- `oauth_uid` — A unique identifier in the third party API's system, typically a user ID or email address +- `oauth_access_token` — A fresh OAuth access token +- `oauth_client_id` — The client ID of the OAuth app +- `oauth_refresh_token` — The latest OAuth refresh token for your grant +- `oauth_uid` — A unique identifier in the third party API's system, typically a user ID or email address Apps with static credentials expose fields specific to the API, e.g. `api_key`. Review the response for specific apps to see the app-specific response. +## Apps + +### List Apps + +--- + +Retrieve a list of all apps available on Pipedream. + +#### Endpoint + +``` +GET /apps +``` + +#### Parameters + +`q` **string** (_optional_) + +A query string to filter the list of apps. For example, to search for apps that **contain** the string "Slack", pass `q=Slack`. + +#### Example Request + +```shell +curl https://api.pipedream.com/v1/apps + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" +``` + +#### Example Response + +```json +{ + "page_info": { + "total_count": 2, + "count": 2, + "start_cursor": "c2xhY2s", + "end_cursor": "c2xhY2tfYm90" + }, + "data": [ + { + "id": "app_OkrhR1", + "name_slug": "slack", + "name": "Slack", + "auth_type": "oauth", + "description": "Slack is a channel-based messaging platform. With Slack, people can work together more effectively, connect all their software tools and services, and find the information they need to do their best work — all within a secure, enterprise-grade environment.", + "img_src": "https://assets.pipedream.net/s.v0/app_OkrhR1/logo/orig", + "custom_fields_json": "[]", + "categories": [ + "Communication" + ] + }, + { + "id": "app_mWnheL", + "name_slug": "slack_bot", + "name": "Slack Bot", + "auth_type": "keys", + "description": "Interact with Slack with your own bot user", + "img_src": "https://assets.pipedream.net/s.v0/app_mWnheL/logo/orig", + "custom_fields_json": "[{\"name\":\"bot_token\",\"label\":\"Bot Token\",\"description\":null,\"default\":null,\"optional\":null,\"type\":\"password\"}]", + "categories": [ + "Communication" + ] + } + ] +} +``` + +### Get an App + +Retrieve metadata for a specific app. + +#### Endpoint + +``` +GET /apps/{app_id} +``` + +#### Path Parameters + +`app_id` **string** + +The ID or name slug the app you'd like to retrieve. For example, Slack's unique app ID is `app_OkrhR1`, and its name slug is `slack`. + +You can find the app's ID in the response from the [List apps](#list-apps) endpoint, and the name slug under the **Authentication** section of any [app page](https://pipedream.com/apps). + +#### Example Request + +```bash +curl https://api.pipedream.com/v1/apps/app_OkrhR1 \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" +``` + +#### Example Response + +```json +"data": [ + { + "id": "app_OkrhR1", + "name_slug": "slack", + "name": "Slack", + "auth_type": "oauth", + "description": "Slack is a channel-based messaging platform. With Slack, people can work together more effectively, connect all their software tools and services, and find the information they need to do their best work — all within a secure, enterprise-grade environment.", + "img_src": "https://assets.pipedream.net/s.v0/app_OkrhR1/logo/orig", + "custom_fields_json": "[]", + "categories": [ + "Communication" + ] + } +] +``` + ## Components Components are objects that represent the code for an [event source](#sources). ### Create a component ---- + +`/components` endpoints are only available when using [user API keys](/rest-api/auth#user-api-keys), not yet for workspace [OAuth tokens](/rest-api/auth#oauth). + Before you can create a source using the REST API, you must first create a **component** - the code for the source. @@ -297,8 +461,6 @@ POST /components #### Parameters ---- - `component_code` **string** (_optional_) The full code for a [Pipedream component](/components/api/). @@ -324,7 +486,7 @@ Here's an example of how to create an RSS component from a Github URL: ```shell curl https://api.pipedream.com/v1/components \ - -H "Authorization: Bearer " \ + -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"component_url": "https://github.com/PipedreamHQ/pipedream/blob/master/components/rss/sources/new-item-in-feed/new-item-in-feed.js"}' ``` @@ -375,8 +537,6 @@ GET /components/{key|id} #### Parameters ---- - `key` **string** The component key (identified by the `key` property within the component's @@ -388,13 +548,11 @@ source code) you'd like to fetch metadata for (example: `my-component`) The saved component ID you'd like to fetch metadata for (example: `sc_JDi8EB`) ---- - #### Example Request ```shell curl https://api.pipedream.com/v1/components/my-component \ - -H "Authorization: Bearer " + -H "Authorization: Bearer " ``` #### Example Response @@ -444,20 +602,16 @@ GET /components/registry/{key} #### Parameters ---- - `key` **string** The component key (identified by the `key` property within the component's source code) you'd like to fetch metadata for (example: `my-component`) ---- - #### Example Request ```shell curl https://api.pipedream.com/v1/components/registry/github-new-repository \ - -H "Authorization: Bearer " + -H "Authorization: Bearer " ``` #### Example Response @@ -491,12 +645,77 @@ curl https://api.pipedream.com/v1/components/registry/github-new-repository \ } ``` -## Events +### Search for registry components -### Get Source Events +Search for components in the global registry with natural language. Pipedream will use AI to match your query to the most relevant components. + +#### Endpoint + +``` +GET /components/search +``` + +#### Parameters + +`query` **string** + +The query string to search for components in the global registry, e.g. "Send a message to Slack on new Hubspot contacts" + +--- + +`app` **string** (_optional_) + +The name slug the app you'd like to filter results for. For example, Slack's name slug is `slack`. Returned sources and actions are filtered to only those tied to the specified app. + +You can find the name slug under the **Authentication** section of any [app page](https://pipedream.com/apps). + +--- + +`similarity_threshold` **number** (_optional_) + +The minimum similarity score required for a component to be returned. The similarity score is a number between 0 and 1, where 1 is a perfect match. Similarity here is computed as the cosine distance between the embedding of the user query and the embedding of the component's metadata. --- +`debug` **boolean** (_optional_) + +Pass `debug=true` to return additional data in the response, useful for inspecting the results. + +#### Example Request + +```shell +curl https://api.pipedream.com/v1/components/search\?query\="When someone sends a tweet mentioning my brand, send me an SMS" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" +``` + +### Example Response + +```json +{ + "sources": [ + "twitter-new-mention-received-by-user", + "twitter-new-tweet-posted-by-user", + "twitter-new-tweet-posted-matching-query" + ], + "actions": [ + "sms-send-sms", + "sms_fusion-send-sms", + "sms_alert-send-sms" + ] +} +``` + +## Connect + +[Pipedream Connect](/connect) is the easiest way for your users to connect to [over {process.env.PUBLIC_APPS}+ APIs](https://pipedream.com/apps), **right in your product**. You can build in-app messaging, CRM syncs, AI-driven products, [and much more](/connect/use-cases), all in a few minutes. Visit [the quickstart](/connect/quickstart) to build your first integration. + +Read more about the Connect API in the [Connect API docs](/connect/api/). + +## Events + +### Get Source Events + Retrieve up to the last 100 events emitted by a source. #### Endpoint @@ -523,8 +742,6 @@ GET /sources/{id}/event_summaries?limit=10 ### Delete source events ---- - Deletes all events, or a specific set of events, tied to a source. By default, making a `DELETE` request to this endpoint deletes **all** events @@ -544,8 +761,6 @@ DELETE /sources/{id}/events #### Parameters ---- - `start_id` **string** The event ID from which you'd like to start deleting events. @@ -591,8 +806,6 @@ curl -X DELETE \ The request will delete the **first two events**. ---- - #### Example Request You can delete a single event by passing its event ID in both the value of the @@ -609,100 +822,112 @@ curl -X DELETE \ Deletion happens asynchronously, so you'll receive a `202 Accepted` HTTP status code in response to any deletion requests. -### List Projects +## OAuth + +### Get a new access token -Programmatically list the workspace's projects. +Exchanges a client ID and client secret for a new access token. #### Endpoint ``` -GET /v1/workspaces//projects +POST /oauth/token ``` -#### Path Parameters +#### Parameters -`workspaces_id` **string** +`grant_type` **string** -[Switch to your workspace's context](/workspaces/#switching-between-workspaces) and [find your org's ID](/workspaces/#finding-your-workspace-s-id). +The OAuth grant type. For Pipedream, this is always `client_credentials`. -#### Example Responses +--- -``` - "data": [ - { - "id": "proj_kYRs18", - "hid": "proj_kYRs18", - "name": "Sample Project", - "repository_id": null, - "repository_path": null, - "created_at": "2024-02-05T21:47:24.000Z", - "updated_at": "2024-02-06T16:13:07.000Z", - "org_id": 1, - "exceptions": null, - "allow_support": false - } - ] +`client_id` **string** + +The client ID of the OAuth app. + +--- + +`client_secret` **string** + +The client secret of the OAuth app. + +#### Example Request + +```bash +curl https://api.pipedream.com/v1/oauth/token \ + -H 'Content-Type: application/json' \ + -d '{ "grant_type": "client_credentials", "client_id": "", "client_secret": "" }' ``` -## Sources +#### Example Response -Event sources run code to collect events from an API, or receive events via -webhooks, emitting those events for use on Pipedream. Event sources can function -as workflow triggers. [Read more here](/sources/). +```json +{ + "access_token": "", + "token_type": "Bearer", + "expires_in": 3600, + "created_at": 1645142400 +} +``` -### List Current User Sources +### Revoke an access token ---- +Revokes an access token, rendering it invalid for future requests. #### Endpoint ``` -GET /users/me/sources/ +POST /oauth/revoke ``` #### Parameters -_No parameters_ +`token` **string** + +The access token to revoke. + +--- + +`client_id` **string** + +The client ID of the OAuth app. + +--- + +`client_secret` **string** + +The client secret of the OAuth app. + +--- #### Example Request -```shell -curl 'https://api.pipedream.com/v1/users/me/sources' \ - -H 'Authorization: Bearer ' +```bash +curl https://api.pipedream.com/v1/oauth/revoke \ + -H 'Content-Type: application/json' \ + -d '{ "token": "", "client_id": "", "client_secret": "" }' ``` #### Example Response +This endpoint will return a `200 OK` response with an empty body if the token was successfully revoked: + ```json -{ - "page_info": { - "total_count": 19, - "count": 10, - "start_cursor": "ZGNfSzB1QWVl", - "end_cursor": "ZGNfeUx1alJx" - }, - "data": [ - { - "id": "dc_abc123", - "component_id": "sc_def456", - "configured_props": { - "http": { - "endpoint_url": "https://myendpoint.m.pipedream.net" - } - }, - "active": true, - "created_at": 1587679599, - "updated_at": 1587764467, - "name": "test", - "name_slug": "test" - } - ] -} +{} ``` +## Sources + +Event sources run code to collect events from an API, or receive events via +webhooks, emitting those events for use on Pipedream. Event sources can function +as workflow triggers. [Read more here](/sources/). + ### Create a Source ---- + +This endpoint is only available when using [user API keys](/rest-api/auth#user-api-keys), not yet for workspace [OAuth tokens](/rest-api/auth#oauth). + #### Endpoint @@ -712,8 +937,6 @@ POST /sources/ #### Parameters ---- - `component_id` **string** (_optional_) The ID of a component previously created in your account. [See the component @@ -754,7 +977,7 @@ of the component used to create the source. ```shell curl https://api.pipedream.com/v1/sources \ - -H "Authorization: Bearer " \ + -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"component_url": "https://github.com/PipedreamHQ/pipedream/blob/master/components/rss/sources/new-item-in-feed/new-item-in-feed.js", "name": "your-name-here", "configured_props": { "url": "https://rss.m.pipedream.net", "timer": { "intervalSeconds": 60 }}}' ``` @@ -785,12 +1008,8 @@ Example response from creating an RSS source that runs once a minute: } ``` ---- - ### Update a source ---- - #### Endpoint ``` @@ -799,8 +1018,6 @@ PUT /sources/{id} #### Parameters ---- - `component_id` **string** (_optional_) The ID of a component previously created in your account. [See the component @@ -848,8 +1065,6 @@ Default: `true`. ### Delete a source ---- - #### Endpoint ``` @@ -858,17 +1073,17 @@ DELETE /sources/{id} ## Subscriptions -### Listen for events from another source or workflow + + The Subscriptions API is currently incompatible with projects that have [GitHub Sync](https://pipedream.com/docs/projects/git) enabled. To [trigger another workflow](https://pipedream.com/docs/code/nodejs#invoke-another-workflow), use `$.flow.trigger` instead. + ---- +### Listen for events from another source or workflow You can configure a source or workflow to receive events from any number of other workflows or sources. For example, if you want a single workflow to run on 10 different RSS sources, you can configure the workflow to _listen_ for events from those 10 sources. ---- - #### Endpoint ``` @@ -877,8 +1092,6 @@ POST /subscriptions?emitter_id={emitting_component_id}&event_name={event_name}&l #### Parameters ---- - `emitter_id` **string** The ID of the workflow or component emitting events. Events from this component @@ -924,8 +1137,6 @@ retrieve the ID of existing components. You can retrieve the ID of your workflow in your workflow's URL - it's the string `p_2gCPml` in `https://pipedream.com/@dylan/example-rss-sql-workflow-p_2gCPml/edit`. ---- - #### Example Request You can configure workflow `p_abc123` to listen to events from the source @@ -934,14 +1145,12 @@ You can configure workflow `p_abc123` to listen to events from the source ```shell curl "https://api.pipedream.com/v1/subscriptions?emitter_id=dc_def456&listener_id=p_abc123" \ -X POST \ - -H "Authorization: Bearer " \ + -H "Authorization: Bearer " \ -H "Content-Type: application/json" ``` ### Automatically subscribe a listener to events from new workflows / sources ---- - You can use this endpoint to automatically receive events, like workflow errors, in another listening workflow or event source. Once you setup the auto-subscription, any new workflows or event sources you create will @@ -955,8 +1164,6 @@ endpoint](#listen-for-events-from-another-source-or-workflow). **Currently, this feature is enabled only on the API. The Pipedream UI will not display the sources configured as listeners using this API**. ---- - #### Endpoint ``` @@ -965,8 +1172,6 @@ POST /auto_subscriptions?event_name={event_name}&listener_id={receiving_source_i #### Parameters ---- - `event_name` **string** The name of the event stream whose events you'd like to receive: @@ -986,8 +1191,6 @@ retrieve the ID of existing components. You can retrieve the ID of your workflow in your workflow's URL - it's the string `p_2gCPml` in `https://pipedream.com/@dylan/example-rss-sql-workflow-p_2gCPml/edit`. ---- - #### Example Request You can configure workflow `p_abc123` to listen to events from the source @@ -996,21 +1199,17 @@ You can configure workflow `p_abc123` to listen to events from the source ```shell curl "https://api.pipedream.com/v1/auto_subscriptions?event_name=$errors&listener_id=p_abc123" \ -X POST \ - -H "Authorization: Bearer " \ + -H "Authorization: Bearer " \ -H "Content-Type: application/json" ``` ### Delete a subscription ---- - Use this endpoint to delete an existing subscription. This endpoint accepts the same parameters as the [`POST /subscriptions` endpoint](#listen-for-events-from-another-source-or-workflow) for creating subscriptions. ---- - #### Endpoint ``` @@ -1019,8 +1218,6 @@ DELETE /subscriptions?emitter_id={emitting_component_id}&listener_id={receiving_ #### Parameters ---- - `emitter_id` **string** The ID of the workflow or component emitting events. Events from this component @@ -1073,8 +1270,6 @@ subscriptions](#get-current-user-s-subscriptions): } ``` ---- - #### Example Request You can delete a subscription you configured for workflow `p_abc123` to listen @@ -1083,15 +1278,17 @@ to events from the source `dc_def456` using the following command: ```shell curl "https://api.pipedream.com/v1/subscriptions?emitter_id=dc_def456&listener_id=p_abc123" \ -X DELETE \ - -H "Authorization: Bearer " \ + -H "Authorization: Bearer " \ -H "Content-Type: application/json" ``` ## Users -### Get Current User Info + +These endpoints only work when using [user API keys](/rest-api/auth#user-api-keys), and will not work with workspace-level OAuth clients. + ---- +### Get Current User Info Retrieve information on the authenticated user. @@ -1109,7 +1306,7 @@ _No parameters_ ```bash curl 'https://api.pipedream.com/v1/users/me' \ - -H 'Authorization: Bearer ' + -H 'Authorization: Bearer ' ``` #### Example Response @@ -1168,107 +1365,6 @@ Paid user: } ``` -### Get Current User's Subscriptions - ---- - -Retrieve all the [subscriptions](#subscriptions) configured for the -authenticated user. - -#### Endpoint - -``` -GET /users/me/subscriptions -``` - -#### Parameters - -_No parameters_ - -#### Example Request - -```shell -curl 'https://api.pipedream.com/v1/users/me/subscriptions' \ - -H 'Authorization: Bearer ' -``` - -#### Example Response - -```json -{ - "data": [ - { - "id": "sub_abc123", - "emitter_id": "dc_abc123", - "listener_id": "p_abc123", - "event_name": "" - }, - { - "id": "sub_def456", - "emitter_id": "dc_def456", - "listener_id": "p_def456", - "event_name": "" - } - ] -} -``` - -### Get Current User's Webhooks - ---- - -Retrieve all the [webhooks](#webhooks) configured for the authenticated user. - -#### Endpoint - -``` -GET /users/me/webhooks -``` - -#### Parameters - -_No parameters_ - -#### Example Request - -```shell -curl 'https://api.pipedream.com/v1/users/me/webhooks' \ - -H 'Authorization: Bearer ' -``` - -#### Example Response - -```json -{ - "page_info": { - "total_count": 2, - "count": 2, - "start_cursor": "d2hfMjlsdUd6", - "end_cursor": "d2hfb3dHdWVv" - }, - "data": [ - { - "id": "wh_abc123", - "name": null, - "description": null, - "url": "https://endpoint.m.pipedream.net", - "active": true, - "created_at": 1611964025, - "updated_at": 1611964025 - }, - { - "id": "wh_def456", - "name": "Test webhook", - "description": "just a test", - "url": "https://endpoint2.m.pipedream.net", - "active": true, - "created_at": 1605835136, - "updated_at": 1605835136 - } - ] -} -``` - ## Webhooks Pipedream supports webhooks as a way to deliver events to a endpoint you own. @@ -1295,8 +1391,6 @@ POST /webhooks?url={your_endpoint_url}&name={name}&description={description} #### Parameters ---- - `url` **string** The endpoint URL where you'd like to deliver events. Any events sent to this @@ -1343,7 +1437,7 @@ You can create a webhook that delivers events to ```shell curl "https://api.pipedream.com/v1/webhooks?url=https://endpoint.m.pipedream.net&name=name&description=description" \ -X POST \ - -H "Authorization: Bearer " \ + -H "Authorization: Bearer " \ -H "Content-Type: application/json" ``` @@ -1375,8 +1469,6 @@ You can list webhooks you've created in your account using the ### Delete a webhook ---- - Use this endpoint to delete a webhook in your account. #### Endpoint @@ -1387,8 +1479,6 @@ DELETE /webhooks/{id} #### Path Parameters ---- - `id` **string** The ID of a webhook in your account. @@ -1400,15 +1490,21 @@ The ID of a webhook in your account. ```shell curl "https://api.pipedream.com/v1/webhooks/wh_abc123" \ -X DELETE \ - -H "Authorization: Bearer " \ + -H "Authorization: Bearer " \ -H "Content-Type: application/json" ``` ## Workflows +### Invoke workflow + +You can invoke workflows by making an HTTP request to a workflow's HTTP trigger. [See the docs on authorizing requests and invoking workflows](/workflows/triggers#authorizing-http-requests) for more detail. + ### Create a Workflow ---- + +This endpoint is only available when using [user API keys](/rest-api/auth#user-api-keys), not yet for workspace [OAuth tokens](/rest-api/auth#oauth). + Creates a new workflow within an organization's project. This endpoint allows defining workflow steps, triggers, and settings, based on a supplied template. @@ -1674,7 +1770,9 @@ The ID of the workflow template to base the workflow on. To find a workflow's `t ### Update a Workflow ---- + +This endpoint is only available when using [user API keys](/rest-api/auth#user-api-keys), not yet for workspace [OAuth tokens](/rest-api/auth#oauth). + Updates the workflow's activation status. If you need to modify the workflow's steps, triggers, or connected accounts [consider making a new workflow](#create-a-workflow). @@ -1705,15 +1803,13 @@ The activation status of a workflow. Set to `true` to activate the workflow, or ```bash curl -X PUT 'https://api.pipedream.com/v1/workflows/p_abc123' \ - -H 'Authorization: Bearer ' \ + -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{"active": false, "org_id": "o_BYDI5y"}' ``` ### Get a Workflow's details ---- - Retrieves the details of a specific workflow within an organization's project. #### Endpoint @@ -1730,7 +1826,7 @@ GET /workflows/{workflow_id} ```bash curl 'https://api.pipedream.com/v1/workflows/p_abc123?org_id=o_abc123' \ - -H 'Authorization: Bearer ' + -H 'Authorization: Bearer ' ``` #### Example Response @@ -1783,8 +1879,6 @@ curl 'https://api.pipedream.com/v1/workflows/p_abc123?org_id=o_abc123' \ ### Get Workflow Emits ---- - Retrieve up to the last 100 events emitted from a workflow using [`$send.emit()`](/destinations/emit/#emit-events). @@ -1814,7 +1908,7 @@ GET /v1/workflows/{workflow_id}/event_summaries?expand=event&limit=1 ```shell curl 'https://api.pipedream.com/v1/workflows/p_abc123/event_summaries?expand=event&limit=1' \ - -H 'Authorization: Bearer ' + -H 'Authorization: Bearer ' ``` #### Example Response @@ -1847,12 +1941,8 @@ curl 'https://api.pipedream.com/v1/workflows/p_abc123/event_summaries?expand=eve } ``` ---- - ### Get Workflow Errors ---- - Retrieve up to the last 100 events for a workflow that threw an error. The details of the error, along with the original event data, will be included @@ -1882,7 +1972,7 @@ GET /v1/workflows/{workflow_id}/$errors/event_summaries?expand=event&limit=1 ```shell curl 'https://api.pipedream.com/v1/workflows/p_abc123/$errors/event_summaries?expand=event&limit=1' \ - -H 'Authorization: Bearer ' + -H 'Authorization: Bearer ' ``` #### Example Response @@ -1942,7 +2032,7 @@ Programmatically view your workspace's current credit usage for the billing peri #### Endpoint ``` -GET /v1/workspaces/ +GET /v1/workspaces/ ``` #### Path Parameters @@ -1968,19 +2058,17 @@ GET /v1/workspaces/ ### Get Workspaces's Connected Accounts ---- - Retrieve all the connected accounts for a specific workspace. #### Endpoint ``` -GET /workspaces//accounts +GET /workspaces//accounts ``` #### Path Parameters -`workspace_id` **string** +`org_id` **string** [Switch to your workspace's context](/workspaces/#switching-between-workspaces) and [find your org's ID](/workspaces/#finding-your-workspace-s-id). @@ -1994,7 +2082,7 @@ To look up the connected account information for a specific app, use the `query` ```shell curl 'https://api.pipedream.com/v1/workspaces/o_abc123/accounts?query=google_sheets' \ - -H 'Authorization: Bearer ' + -H 'Authorization: Bearer ' ``` #### Example Response @@ -2018,14 +2106,12 @@ curl 'https://api.pipedream.com/v1/workspaces/o_abc123/accounts?query=google_she ### Get Workspaces's Subscriptions ---- - Retrieve all the [subscriptions](#subscriptions) configured for a specific workspace. #### Endpoint ``` -GET /workspaces//subscriptions +GET /workspaces//subscriptions ``` #### Path Parameters @@ -2038,7 +2124,7 @@ GET /workspaces//subscriptions ```shell curl 'https://api.pipedream.com/v1/workspaces/o_abc123/subscriptions' \ - -H 'Authorization: Bearer ' + -H 'Authorization: Bearer ' ``` #### Example Response @@ -2064,14 +2150,12 @@ curl 'https://api.pipedream.com/v1/workspaces/o_abc123/subscriptions' \ ### Get Workspaces's Sources ---- - Retrieve all the [event sources](#sources) configured for a specific workspace. #### Endpoint ``` -GET /orgs//sources +GET /orgs//sources ``` #### Path Parameters @@ -2084,7 +2168,7 @@ GET /orgs//sources ```shell curl 'https://api.pipedream.com/v1/orgs/o_abc123/sources' \ - -H 'Authorization: Bearer ' + -H 'Authorization: Bearer ' ``` #### Example Response diff --git a/docs-v2/pages/workflows/triggers.mdx b/docs-v2/pages/workflows/triggers.mdx index 160dfd853d5b4..7b97886f6993d 100644 --- a/docs-v2/pages/workflows/triggers.mdx +++ b/docs-v2/pages/workflows/triggers.mdx @@ -1,5 +1,6 @@ import Image from 'next/image' import Callout from '@/components/Callout' +import { Tabs } from 'nextra/components' import PipedreamImg from '@/components/PipedreamImg' import VideoPlayer from "@/components/VideoPlayer"; @@ -100,9 +101,109 @@ You can send data of any [Media Type](https://www.iana.org/assignments/media-typ The primary limit we impose is on the size of the request body: we'll issue a `413 Payload Too Large` status when the body [exceeds our specified limit](#request-entity-too-large). +### Authorizing HTTP requests + +By default, HTTP triggers are public and require no authorization to invoke. Anyone with the endpoint URL can trigger your workflow. When possible, we recommend adding authorization. + +HTTP triggers support two built-in authorization types in the **Authorization** section of the HTTP trigger configuration: a [static, custom token](#custom-token) and [OAuth](#oauth). + +#### Custom token + +To configure a static, custom token for HTTP auth: + +1. Open the **Configure** section of the HTTP trigger +2. Select **Custom token**. +3. Enter whatever secret you'd like and click **Save and Continue**. + +![Custom token auth](https://res.cloudinary.com/pipedreamin/image/upload/v1729791152/Google_Chrome_-_Untitled_Workflow_-_10-24-2024_10-30_AM_-_Build_-_Pipedream_2024-10-24_at_10.31.01_AM_pkh8dk.png) + +When making HTTP requests, pass the custom token as a `Bearer` token in the `Authorization` header: + +```bash +curl -H 'Authorization: Bearer ' https://myendpoint.m.pipedream.net +``` + +#### OAuth + +You can also authorize requests using [Pipedream OAuth clients](/rest-api/auth#oauth): + +1. Open the **Configure** section of the HTTP trigger. +2. Select **OAuth**. +3. If you don't have an existing OAuth client, [create one in your workspace's API settings](/rest-api/auth#creating-an-oauth-application). + +![OAuth authorization](https://res.cloudinary.com/pipedreamin/image/upload/v1729791415/Google_Chrome_-_Untitled_Workflow_-_10-24-2024_10-30_AM_-_Build_-_Pipedream_2024-10-24_at_10.36.04_AM_ujx34e.png) + +Next, you'll need to [generate an OAuth access token](/rest-api/auth#how-to-get-an-access-token). + +When making HTTP requests, pass the OAuth access token as a `Bearer` token in the `Authorization` header: + +```bash +curl -H 'Authorization: Bearer ' https://myendpoint.m.pipedream.net +``` + +You can use the Pipedream SDK to automatically refresh access tokens and invoke workflows: + + + +```typescript +import { createBackendClient, HTTPAuthType } from "@pipedream/sdk"; + +// These secrets should be saved securely and passed to your environment +const pd = createBackendClient({ + credentials: { + clientId: "YOUR_CLIENT_ID", + clientSecret: "YOUR_CLIENT_SECRET", + }, +}); + +await pd.invokeWorkflow( + "enabc123", // pass the endpoint ID or full URL here + { + method: "POST", + body: { + key: "value", + } + }, + HTTPAuthType.OAuth // Will automatically send the Authorization header with a fresh token +) +``` + + +```javascript +import { createBackendClient } from "@pipedream/sdk"; + +// These secrets should be saved securely and passed to your environment +const pd = createBackendClient({ + credentials: { + clientId: "YOUR_CLIENT_ID", + clientSecret: "YOUR_CLIENT_SECRET", + }, +}); + +await pd.invokeWorkflow( + "enabc123", // pass the endpoint ID or full URL here + { + method: "POST", + body: { + key: "value", + } + }, + "oauth" // Will automatically send the Authorization header with a fresh token +) +``` + + + + +#### Implement your own authorization logic + +Since you have access to the entire request object, and can issue any HTTP response from a workflow, you can implement custom logic to validate requests. + +For example, you could require JWT tokens and validate those tokens using the [`jsonwebtoken` package](https://www.npmjs.com/package/jsonwebtoken) at the start of your workflow. + ### Custom domains -To configure endpoints on your own domain, e.g. `endpoint.yourdomain.com` instead of the default `{process.env.ENDPOINT_BASE_URL}` domain, see the [custom domains](/workflows/domains) docs. +To configure endpoints on your own domain, e.g. `endpoint.yourdomain.com` instead of the default `*.m.pipedream.net` domain, see the [custom domains](/workflows/domains) docs. ### How Pipedream handles JSON payloads @@ -441,20 +542,6 @@ You can also [reach out](https://pipedream.com/support/) to inquire about raisin If you control the application sending requests, you should implement [a backoff strategy](https://medium.com/clover-platform-blog/conquering-api-rate-limiting-dcac5552714d) to temporarily slow the rate of events. -### Validating requests - -Since you have access to the entire request object, and can issue any HTTP response from a workflow, you can implement custom logic to validate requests using any [Node.js code](/code/nodejs/). - -For example, you can [require requests pass a specific secret in a header](https://pipedream.com/@dylburger/end-a-workflow-early-on-invalid-secret-p_YyCmmK/edit). Just copy the workflow and add your secret as the value of the the **Secret** param. Add the rest of your code in steps below this initial one. Requests must contain the secret: - -```bash -curl -H 'X-Pipedream-Secret: abc123' https://myendpoint.m.pipedream.net -``` - -Otherwise, the workflow will [end early](/code/nodejs/#ending-a-workflow-early). - -Since you can [run any code](/code/) in a workflow, you can implement more complex validation. For example, you could require JWT tokens and validate those tokens using the [`jsonwebtoken` package](https://www.npmjs.com/package/jsonwebtoken) at the start of your workflow. - ## Schedule Pipedream allows you to run hosted scheduled jobs — commonly-referred to as a "cron job" — [for free](/pricing/). You can think of workflows like scripts that run on a schedule. @@ -599,26 +686,26 @@ myemailaddr+unsubscribe@pipedream.net This allows you implement conditional logic in your workflow based on the data in that string. -## RSS +### Troubleshooting -Choose the RSS trigger to watch an RSS feed for new items: +#### I'm receiving an `Expired Token` error when trying to read an email attachment -![Selecting the RSS feed trigger](pages/workflows/images/triggers/select-rss-trigger.png) +Email attachments are saved to S3, and are accessible in your workflows over [pre-signed URLs](https://docs.aws.amazon.com/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html). -This will create an RSS [event source](/sources/) that polls the feed for new items on the schedule you select. Every time a new item is found, your workflow will run. +If the presigned URL for the attachment has expired, then you'll need to send another email to create a brand new pre-signed URL. -## Don't see a trigger you need? +If you're using email attachments in combination with [`$.flow.delay`](/code/nodejs/delay/) or [`$.flow.rerun`](/code/nodejs/rerun/) which introduces a gap of time between steps in your workflow, then there's a chance the email attachment's URL will expire. -If you don't see a trigger you'd like us to support, please [let us know](https://pipedream.com/support/). +To overcome this, we suggest uploading your email attachments to your Project's [File Store](/projects/file-stores/) for persistent storage. -## Troubleshooting +## RSS -### I'm receiving an `Expired Token` error when trying to read an email attachment +Choose the RSS trigger to watch an RSS feed for new items: -Email attachments are saved to S3, and are accessible in your workflows over [pre-signed URLs](https://docs.aws.amazon.com/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html). +![Selecting the RSS feed trigger](pages/workflows/images/triggers/select-rss-trigger.png) -If the presigned URL for the attachment has expired, then you'll need to send another email to create a brand new pre-signed URL. +This will create an RSS [event source](/sources/) that polls the feed for new items on the schedule you select. Every time a new item is found, your workflow will run. -If you're using email attachments in combination with [`$.flow.delay`](/code/nodejs/delay/) or [`$.flow.rerun`](/code/nodejs/rerun/) which introduces a gap of time between steps in your workflow, then there's a chance the email attachment's URL will expire. +## Don't see a trigger you need? -To overcome this, we suggest uploading your email attachments to your Project's [File Store](/projects/file-stores/) for persistent storage. +If you don't see a trigger you'd like us to support, please [let us know](https://pipedream.com/support/). diff --git a/docs-v2/yarn.lock b/docs-v2/yarn.lock index 4b34a2150ffe0..202df7966c3b0 100644 --- a/docs-v2/yarn.lock +++ b/docs-v2/yarn.lock @@ -89,7 +89,7 @@ "@algolia/requester-common" "4.22.1" "@algolia/transporter" "4.22.1" -"@algolia/client-search@>= 4.9.1 < 6", "@algolia/client-search@4.22.1": +"@algolia/client-search@4.22.1": version "4.22.1" resolved "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.22.1.tgz" integrity sha512-yb05NA4tNaOgx3+rOxAmFztgMTtGBi97X7PC3jyNeGiwkAjOZc2QrdZBYyIdcDLoI09N0gjtpClcackoTN0gPA== @@ -177,7 +177,7 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-8.0.0-alpha.6.tgz" integrity sha512-Oam3YCPXzA5nII8+uqO1nur3PxJF3CcQpFJXHPAXJHhjIKvABCodYacDk8x/FQKBmBiUaMqGlEQ04m3H3rWqlA== -"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.8.0": +"@babel/core@^7.11.6", "@babel/core@^7.12.3": version "7.23.9" resolved "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz" integrity sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw== @@ -198,7 +198,7 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/core@^8.0.0-alpha.1", "@babel/core@^8.0.0-alpha.6": +"@babel/core@^8.0.0-alpha.1": version "8.0.0-alpha.6" resolved "https://registry.npmjs.org/@babel/core/-/core-8.0.0-alpha.6.tgz" integrity sha512-7wfyFNMnB3VkjKrhgLKEbKHs776ZMzaZmtCI9JlmuvoYje17DPxbbWE+1OR2Yzq0K5wK0KtrNUKUCptpH3UMwg== @@ -638,18 +638,6 @@ resolved "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz" integrity sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A== -"@csstools/css-parser-algorithms@^2.6.3": - version "2.6.3" - -"@csstools/css-tokenizer@^2.3.1": - version "2.3.1" - -"@csstools/media-query-list-parser@^2.1.11": - version "2.1.11" - -"@csstools/selector-specificity@^3.1.1": - version "3.1.1" - "@docsearch/css@3.6.1": version "3.6.1" resolved "https://registry.npmjs.org/@docsearch/css/-/css-3.6.1.tgz" @@ -665,8 +653,12 @@ "@docsearch/css" "3.6.1" algoliasearch "^4.19.1" -"@dual-bundle/import-meta-resolve@^4.1.0": - version "4.1.0" +"@emnapi/runtime@^1.1.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.3.1.tgz#0fcaa575afc31f455fd33534c19381cfce6c6f60" + integrity sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw== + dependencies: + tslib "^2.4.0" "@eslint-community/eslint-utils@^4.1.2", "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" @@ -710,15 +702,53 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@eslint/js@8.57.0": + version "8.57.0" + resolved "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz" + integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== + "@eslint/js@^9.2.0": version "9.8.0" resolved "https://registry.npmjs.org/@eslint/js/-/js-9.8.0.tgz" integrity sha512-MfluB7EUfxXtv3i/++oh89uzAr4PDI4nn201hsp+qaXqsjAWzinlZEHEfPgAX4doIlKvPG/i0A9dpKxOLII8yA== -"@eslint/js@8.57.0": - version "8.57.0" - resolved "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz" - integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== +"@hapi/boom@^10.0.1": + version "10.0.1" + resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-10.0.1.tgz#ebb14688275ae150aa6af788dbe482e6a6062685" + integrity sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA== + dependencies: + "@hapi/hoek" "^11.0.2" + +"@hapi/bourne@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-3.0.0.tgz#f11fdf7dda62fe8e336fa7c6642d9041f30356d7" + integrity sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w== + +"@hapi/hoek@^11.0.2", "@hapi/hoek@^11.0.4": + version "11.0.4" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-11.0.4.tgz#42a7f244fd3dd777792bfb74b8c6340ae9182f37" + integrity sha512-PnsP5d4q7289pS2T2EgGz147BFJ2Jpb4yrEdkpz2IhgEUzos1S7HTl7ezWh1yfYzYlj89KzLdCRkqsP6SIryeQ== + +"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" + integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== + +"@hapi/topo@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@hapi/wreck@^18.0.0": + version "18.1.0" + resolved "https://registry.yarnpkg.com/@hapi/wreck/-/wreck-18.1.0.tgz#68e631fc7568ebefc6252d5b86cb804466c8dbe6" + integrity sha512-0z6ZRCmFEfV/MQqkQomJ7sl/hyxvcZM7LtuVqN3vdAO4vM9eBbowl0kaqQj9EJJQab+3Uuh1GxbGIBFy4NfJ4w== + dependencies: + "@hapi/boom" "^10.0.1" + "@hapi/bourne" "^3.0.0" + "@hapi/hoek" "^11.0.2" "@headlessui/react@^1.7.17": version "1.7.18" @@ -747,18 +777,119 @@ resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz" integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== +"@img/sharp-darwin-arm64@0.33.4": + version "0.33.4" + resolved "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.4.tgz" + integrity sha512-p0suNqXufJs9t3RqLBO6vvrgr5OhgbWp76s5gTRvdmxmuv9E1rcaqGUsl3l4mKVmXPkTkTErXediAui4x+8PSA== + optionalDependencies: + "@img/sharp-libvips-darwin-arm64" "1.0.2" + "@img/sharp-darwin-x64@0.33.4": version "0.33.4" - resolved "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.4.tgz" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.4.tgz#f77be2d7c3609d3e77cd337b199a772e07b87bd2" integrity sha512-0l7yRObwtTi82Z6ebVI2PnHT8EB2NxBgpK2MiKJZJ7cz32R4lxd001ecMhzzsZig3Yv9oclvqqdV93jo9hy+Dw== optionalDependencies: "@img/sharp-libvips-darwin-x64" "1.0.2" +"@img/sharp-libvips-darwin-arm64@1.0.2": + version "1.0.2" + resolved "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.2.tgz" + integrity sha512-tcK/41Rq8IKlSaKRCCAuuY3lDJjQnYIW1UXU1kxcEKrfL8WR7N6+rzNoOxoQRJWTAECuKwgAHnPvqXGN8XfkHA== + "@img/sharp-libvips-darwin-x64@1.0.2": version "1.0.2" - resolved "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.2.tgz#5665da7360d8e5ed7bee314491c8fe736b6a3c39" integrity sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw== +"@img/sharp-libvips-linux-arm64@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.2.tgz#8a05e5e9e9b760ff46561e32f19bd5e035fa881c" + integrity sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw== + +"@img/sharp-libvips-linux-arm@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.2.tgz#0fd33b9bf3221948ce0ca7a5a725942626577a03" + integrity sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw== + +"@img/sharp-libvips-linux-s390x@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.2.tgz#4b89150ec91b256ee2cbb5bb125321bf029a4770" + integrity sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog== + +"@img/sharp-libvips-linux-x64@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.2.tgz#947ccc22ca5bc8c8cfe921b39a5fdaebc5e39f3f" + integrity sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ== + +"@img/sharp-libvips-linuxmusl-arm64@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.2.tgz#821d58ce774f0f8bed065b69913a62f65d512f2f" + integrity sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ== + +"@img/sharp-libvips-linuxmusl-x64@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.2.tgz#4309474bd8b728a61af0b3b4fad0c476b5f3ccbe" + integrity sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw== + +"@img/sharp-linux-arm64@0.33.4": + version "0.33.4" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.4.tgz#bd390113e256487041411b988ded13a26cfc5f95" + integrity sha512-2800clwVg1ZQtxwSoTlHvtm9ObgAax7V6MTAB/hDT945Tfyy3hVkmiHpeLPCKYqYR1Gcmv1uDZ3a4OFwkdBL7Q== + optionalDependencies: + "@img/sharp-libvips-linux-arm64" "1.0.2" + +"@img/sharp-linux-arm@0.33.4": + version "0.33.4" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.4.tgz#14ecc81f38f75fb4cd7571bc83311746d6745fca" + integrity sha512-RUgBD1c0+gCYZGCCe6mMdTiOFS0Zc/XrN0fYd6hISIKcDUbAW5NtSQW9g/powkrXYm6Vzwd6y+fqmExDuCdHNQ== + optionalDependencies: + "@img/sharp-libvips-linux-arm" "1.0.2" + +"@img/sharp-linux-s390x@0.33.4": + version "0.33.4" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.4.tgz#119e8081e2c6741b5ac908fe02244e4c559e525f" + integrity sha512-h3RAL3siQoyzSoH36tUeS0PDmb5wINKGYzcLB5C6DIiAn2F3udeFAum+gj8IbA/82+8RGCTn7XW8WTFnqag4tQ== + optionalDependencies: + "@img/sharp-libvips-linux-s390x" "1.0.2" + +"@img/sharp-linux-x64@0.33.4": + version "0.33.4" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.4.tgz#21d4c137b8da9a313b069ff5c920ded709f853d7" + integrity sha512-GoR++s0XW9DGVi8SUGQ/U4AeIzLdNjHka6jidVwapQ/JebGVQIpi52OdyxCNVRE++n1FCLzjDovJNozif7w/Aw== + optionalDependencies: + "@img/sharp-libvips-linux-x64" "1.0.2" + +"@img/sharp-linuxmusl-arm64@0.33.4": + version "0.33.4" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.4.tgz#f3fde68fd67b85a32da6f1155818c3b58b8e7ae0" + integrity sha512-nhr1yC3BlVrKDTl6cO12gTpXMl4ITBUZieehFvMntlCXFzH2bvKG76tBL2Y/OqhupZt81pR7R+Q5YhJxW0rGgQ== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-arm64" "1.0.2" + +"@img/sharp-linuxmusl-x64@0.33.4": + version "0.33.4" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.4.tgz#44373724aecd7b69900e0578228144e181db7892" + integrity sha512-uCPTku0zwqDmZEOi4ILyGdmW76tH7dm8kKlOIV1XC5cLyJ71ENAAqarOHQh0RLfpIpbV5KOpXzdU6XkJtS0daw== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64" "1.0.2" + +"@img/sharp-wasm32@0.33.4": + version "0.33.4" + resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.4.tgz#88e3f18d7e7cd8cfe1af98e9963db4d7b6491435" + integrity sha512-Bmmauh4sXUsUqkleQahpdNXKvo+wa1V9KhT2pDA4VJGKwnKMJXiSTGphn0gnJrlooda0QxCtXc6RX1XAU6hMnQ== + dependencies: + "@emnapi/runtime" "^1.1.1" + +"@img/sharp-win32-ia32@0.33.4": + version "0.33.4" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.4.tgz#b1c772dd2952e983980b1eb85808fa8129484d46" + integrity sha512-99SJ91XzUhYHbx7uhK3+9Lf7+LjwMGQZMDlO/E/YVJ7Nc3lyDFZPGhjwiYdctoH2BOzW9+TnfqcaMKt0jHLdqw== + +"@img/sharp-win32-x64@0.33.4": + version "0.33.4" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.4.tgz#106f911134035b4157ec92a0c154a6b6f88fa4c1" + integrity sha512-3QLocdTRVIrFNye5YocZl+KKpYKP+fksi1QhmOArgx7GyhIbQp/WrJRu176jm8IxromS7RIkzMiMINVdBtC8Aw== + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz" @@ -1042,11 +1173,61 @@ "@types/mdx" "^2.0.0" "@types/react" ">=16" +"@napi-rs/simple-git-android-arm-eabi@0.1.16": + version "0.1.16" + resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-android-arm-eabi/-/simple-git-android-arm-eabi-0.1.16.tgz#36b752f84a7e75a9dada3d8b307817f0b015a57d" + integrity sha512-dbrCL0Pl5KZG7x7tXdtVsA5CO6At5ohDX3myf5xIYn9kN4jDFxsocl8bNt6Vb/hZQoJd8fI+k5VlJt+rFhbdVw== + +"@napi-rs/simple-git-android-arm64@0.1.16": + version "0.1.16" + resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-android-arm64/-/simple-git-android-arm64-0.1.16.tgz#f84d9e2fdae91bb810b55ffc30a42ce5fe020c76" + integrity sha512-xYz+TW5J09iK8SuTAKK2D5MMIsBUXVSs8nYp7HcMi8q6FCRO7yJj96YfP9PvKsc/k64hOyqGmL5DhCzY9Cu1FQ== + +"@napi-rs/simple-git-darwin-arm64@0.1.16": + version "0.1.16" + resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-darwin-arm64/-/simple-git-darwin-arm64-0.1.16.tgz#8d995a920146c320bf13b32d1b1654f44beaa16b" + integrity sha512-XfgsYqxhUE022MJobeiX563TJqyQyX4FmYCnqrtJwAfivESVeAJiH6bQIum8dDEYMHXCsG7nL8Ok0Dp8k2m42g== + "@napi-rs/simple-git-darwin-x64@0.1.16": version "0.1.16" - resolved "https://registry.npmjs.org/@napi-rs/simple-git-darwin-x64/-/simple-git-darwin-x64-0.1.16.tgz" + resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-darwin-x64/-/simple-git-darwin-x64-0.1.16.tgz#7cc7155392c62f885d248af5f720e108d0aad2b5" integrity sha512-tkEVBhD6vgRCbeWsaAQqM3bTfpIVGeitamPPRVSbsq8qgzJ5Dx6ZedH27R7KSsA/uao7mZ3dsrNLXbu1Wy5MzA== +"@napi-rs/simple-git-linux-arm-gnueabihf@0.1.16": + version "0.1.16" + resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-linux-arm-gnueabihf/-/simple-git-linux-arm-gnueabihf-0.1.16.tgz#d5135543d372e0571d7c19928e75751eb407d7dd" + integrity sha512-R6VAyNnp/yRaT7DV1Ao3r67SqTWDa+fNq2LrNy0Z8gXk2wB9ZKlrxFtLPE1WSpWknWtyRDLpRlsorh7Evk7+7w== + +"@napi-rs/simple-git-linux-arm64-gnu@0.1.16": + version "0.1.16" + resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-linux-arm64-gnu/-/simple-git-linux-arm64-gnu-0.1.16.tgz#4e293005b2fd62d1eb399b50e53d983378c19fb7" + integrity sha512-LAGI0opFKw/HBMCV2qIBK3uWSEW9h4xd2ireZKLJy8DBPymX6NrWIamuxYNyCuACnFdPRxR4LaRFy4J5ZwuMdw== + +"@napi-rs/simple-git-linux-arm64-musl@0.1.16": + version "0.1.16" + resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-linux-arm64-musl/-/simple-git-linux-arm64-musl-0.1.16.tgz#679edd2c6d88de6aa35993401722ade04595869b" + integrity sha512-I57Ph0F0Yn2KW93ep+V1EzKhACqX0x49vvSiapqIsdDA2PifdEWLc1LJarBolmK7NKoPqKmf6lAKKO9lhiZzkg== + +"@napi-rs/simple-git-linux-x64-gnu@0.1.16": + version "0.1.16" + resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-linux-x64-gnu/-/simple-git-linux-x64-gnu-0.1.16.tgz#b33054b14a88335f19261b812f65f8d567e7d199" + integrity sha512-AZYYFY2V7hlcQASPEOWyOa3e1skzTct9QPzz0LiDM3f/hCFY/wBaU2M6NC5iG3d2Kr38heuyFS/+JqxLm5WaKA== + +"@napi-rs/simple-git-linux-x64-musl@0.1.16": + version "0.1.16" + resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-linux-x64-musl/-/simple-git-linux-x64-musl-0.1.16.tgz#8cfc8f5f35951dacae86e72b5535ea401f868b7a" + integrity sha512-9TyMcYSBJwjT8jwjY9m24BZbu7ozyWTjsmYBYNtK3B0Um1Ov6jthSNneLVvouQ6x+k3Ow+00TiFh6bvmT00r8g== + +"@napi-rs/simple-git-win32-arm64-msvc@0.1.16": + version "0.1.16" + resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-win32-arm64-msvc/-/simple-git-win32-arm64-msvc-0.1.16.tgz#e6b220574421695f4c05be4e065b1fd46ffb7007" + integrity sha512-uslJ1WuAHCYJWui6xjsyT47SjX6KOHDtClmNO8hqKz1pmDSNY7AjyUY8HxvD1lK9bDnWwc4JYhikS9cxCqHybw== + +"@napi-rs/simple-git-win32-x64-msvc@0.1.16": + version "0.1.16" + resolved "https://registry.yarnpkg.com/@napi-rs/simple-git-win32-x64-msvc/-/simple-git-win32-x64-msvc-0.1.16.tgz#4ec44d57fc2c069544ffb923a2871d81d5db7cfc" + integrity sha512-SoEaVeCZCDF1MP+M9bMSXsZWgEjk4On9GWADO5JOulvzR1bKjk0s9PMHwe/YztR9F0sJzrCxwtvBZowhSJsQPg== + "@napi-rs/simple-git@^0.1.9": version "0.1.16" resolved "https://registry.npmjs.org/@napi-rs/simple-git/-/simple-git-0.1.16.tgz" @@ -1069,18 +1250,58 @@ resolved "https://registry.npmjs.org/@next/env/-/env-14.2.5.tgz" integrity sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA== -"@next/eslint-plugin-next@^14.2.5", "@next/eslint-plugin-next@14.2.5": +"@next/eslint-plugin-next@14.2.5", "@next/eslint-plugin-next@^14.2.5": version "14.2.5" resolved "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.5.tgz" integrity sha512-LY3btOpPh+OTIpviNojDpUdIbHW9j0JBYBjsIp8IxtDFfYFyORvw3yNq6N231FVqQA7n7lwaf7xHbVJlA1ED7g== dependencies: glob "10.3.10" +"@next/swc-darwin-arm64@14.2.5": + version "14.2.5" + resolved "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz" + integrity sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ== + "@next/swc-darwin-x64@14.2.5": version "14.2.5" - resolved "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz#eb832a992407f6e6352eed05a073379f1ce0589c" integrity sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA== +"@next/swc-linux-arm64-gnu@14.2.5": + version "14.2.5" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz#098fdab57a4664969bc905f5801ef5a89582c689" + integrity sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA== + +"@next/swc-linux-arm64-musl@14.2.5": + version "14.2.5" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz#243a1cc1087fb75481726dd289c7b219fa01f2b5" + integrity sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA== + +"@next/swc-linux-x64-gnu@14.2.5": + version "14.2.5" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz#b8a2e436387ee4a52aa9719b718992e0330c4953" + integrity sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ== + +"@next/swc-linux-x64-musl@14.2.5": + version "14.2.5" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz#cb8a9adad5fb8df86112cfbd363aab5c6d32757b" + integrity sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ== + +"@next/swc-win32-arm64-msvc@14.2.5": + version "14.2.5" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz#81f996c1c38ea0900d4e7719cc8814be8a835da0" + integrity sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw== + +"@next/swc-win32-ia32-msvc@14.2.5": + version "14.2.5" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz#f61c74ce823e10b2bc150e648fc192a7056422e0" + integrity sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg== + +"@next/swc-win32-x64-msvc@14.2.5": + version "14.2.5" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz#ed199a920efb510cfe941cd75ed38a7be21e756f" + integrity sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" @@ -1089,7 +1310,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -1102,6 +1323,13 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@pipedream/sdk@^0.1.9": + version "0.1.9" + resolved "https://registry.yarnpkg.com/@pipedream/sdk/-/sdk-0.1.9.tgz#1d857e10482623c86c3ef8a4bb1b28a9614e44a6" + integrity sha512-f8FXEaoBqIOQpI4vVOO8OrHRAjwQYO4pKIwMUcGRkzS5KJ6cZ/7JYYGyXEu7NcS+y1VfyegtbJWu4tWacDjQZg== + dependencies: + simple-oauth2 "^5.1.0" + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" @@ -1117,67 +1345,11 @@ resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz" integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== -"@putout/babel@^2.0.0", "@putout/babel@^2.4.0": +"@putout/babel@^2.0.0": version "2.8.0" resolved "https://registry.npmjs.org/@putout/babel/-/babel-2.8.0.tgz" integrity sha512-Vq4DgAR6Zfc0VXyspQndmgT4T7sTgJBm8kwigN2zPxtyTtz8R199qjxSrypY1P2d+iAGatG2imksrzlPOlombg== -"@putout/cli-cache@^3.0.0": - version "3.1.0" - dependencies: - file-entry-cache "^9.0.0" - find-cache-dir "^5.0.0" - find-up "^7.0.0" - imurmurhash "^0.1.4" - json-stable-stringify-without-jsonify "^1.0.1" - try-to-catch "^3.0.0" - -"@putout/cli-choose-formatter@^4.0.0": - version "4.0.0" - dependencies: - "@putout/cli-choose" "^2.0.0" - find-up "^7.0.0" - -"@putout/cli-choose@^2.0.0": - version "2.0.0" - dependencies: - enquirer "^2.4.1" - try-to-catch "^3.0.1" - -"@putout/cli-filesystem@^2.0.1": - version "2.0.1" - -"@putout/cli-keypress@^2.0.0": - version "2.0.0" - dependencies: - ci-info "^4.0.0" - fullstore "^3.0.0" - -"@putout/cli-match@^2.0.0": - version "2.2.0" - dependencies: - try-catch "^3.0.0" - try-to-catch "^3.0.0" - -"@putout/cli-ruler@^3.0.0": - version "3.1.0" - dependencies: - try-to-catch "^3.0.0" - -"@putout/cli-staged@^1.0.0": - version "1.1.0" - dependencies: - "@putout/git-status-porcelain" "^3.0.0" - fullstore "^3.0.0" - once "^1.4.0" - try-to-catch "^3.0.1" - -"@putout/cli-validate-args@^1.0.0": - version "1.1.1" - dependencies: - fastest-levenshtein "^1.0.12" - just-kebab-case "^1.1.0" - "@putout/compare@^14.0.0": version "14.1.0" resolved "https://registry.npmjs.org/@putout/compare/-/compare-14.1.0.tgz" @@ -1190,17 +1362,7 @@ jessy "^3.0.0" nessy "^4.0.0" -"@putout/engine-loader@^13.0.0": - version "13.1.0" - dependencies: - "@putout/engine-parser" "^10.0.0" - diff-match-patch "^1.0.4" - nano-memoize "^3.0.11" - once "^1.4.0" - try-catch "^3.0.0" - try-to-catch "^3.0.1" - -"@putout/engine-parser@^10.0.0", "@putout/engine-parser@^10.0.2": +"@putout/engine-parser@^10.0.0": version "10.1.0" resolved "https://registry.npmjs.org/@putout/engine-parser/-/engine-parser-10.1.0.tgz" integrity sha512-5zyOJrbdFMwCJHd38nY4KV/Ttb5jVhDZZ+xedZROPLeJIRCY+KiZnt+qK4l0O6EugHnlhyKhe24VJOp48Xs9bA== @@ -1213,43 +1375,6 @@ once "^1.4.0" try-catch "^3.0.0" -"@putout/engine-processor@^11.0.0": - version "11.2.0" - dependencies: - "@putout/engine-loader" "^13.0.0" - once "^1.4.0" - picomatch "^4.0.1" - try-to-catch "^3.0.1" - -"@putout/engine-reporter@^1.0.0": - version "1.0.2" - dependencies: - "@putout/cli-choose-formatter" "^4.0.0" - "@putout/cli-keypress" "^2.0.0" - "@putout/engine-loader" "^13.0.0" - fullstore "^3.0.0" - try-catch "^3.0.1" - try-to-catch "^3.0.1" - -"@putout/engine-runner@^21.0.0": - version "21.0.3" - dependencies: - "@putout/babel" "^2.0.0" - "@putout/compare" "^14.0.0" - "@putout/engine-parser" "^10.0.0" - "@putout/operate" "^12.0.0" - "@putout/operator-declare" "^9.0.0" - "@putout/operator-filesystem" "^4.0.0" - "@putout/operator-json" "^2.0.0" - "@putout/plugin-filesystem" "^5.0.0" - debug "^4.1.1" - fullstore "^3.0.0" - jessy "^3.0.0" - nessy "^4.0.0" - once "^1.4.0" - try-catch "^3.0.0" - wraptile "^3.0.0" - "@putout/eslint-config@^9.0.0": version "9.1.0" resolved "https://registry.npmjs.org/@putout/eslint-config/-/eslint-config-9.1.0.tgz" @@ -1266,111 +1391,13 @@ find-up "^7.0.0" try-to-catch "^3.0.1" -"@putout/formatter-codeframe@^7.0.0": - version "7.0.0" - dependencies: - "@putout/babel" "^2.0.0" - "@putout/formatter-json" "^2.0.0" - chalk "^5.3.0" - table "^6.0.1" - -"@putout/formatter-dump@^5.0.0": - version "5.0.0" - dependencies: - "@putout/formatter-json" "^2.0.0" - chalk "^5.3.0" - table "^6.0.1" - -"@putout/formatter-frame@^6.0.0": - version "6.0.0" - dependencies: - "@putout/formatter-codeframe" "^7.0.0" - -"@putout/formatter-json-lines@^3.0.0": - version "3.0.0" - -"@putout/formatter-json@^2.0.0": - version "2.0.0" - -"@putout/formatter-memory@^4.0.0": - version "4.0.1" - dependencies: - "@putout/formatter-dump" "^5.0.0" - chalk "^5.3.0" - cli-progress "^3.8.2" - format-io "^2.0.0" - montag "^1.1.0" - once "^1.4.0" - -"@putout/formatter-progress-bar@^4.0.0": - version "4.0.1" - dependencies: - "@putout/formatter-dump" "^5.0.0" - chalk "^5.3.0" - cli-progress "^3.8.2" - once "^1.4.0" - -"@putout/formatter-progress@^5.0.0": - version "5.0.0" - dependencies: - "@putout/formatter-dump" "^5.0.0" - -"@putout/formatter-stream@^5.0.0": - version "5.0.0" - dependencies: - chalk "^5.3.0" - table "^6.0.1" - -"@putout/formatter-time@^3.0.0": - version "3.0.1" - dependencies: - "@putout/formatter-dump" "^5.0.0" - chalk "^5.3.0" - cli-progress "^3.8.2" - format-io "^2.0.0" - montag "^1.1.0" - once "^1.4.0" - timer-node "^5.0.7" - -"@putout/git-status-porcelain@^3.0.0": - version "3.0.0" - -"@putout/operate@^12.0.0", "@putout/operate@^12.5.0": +"@putout/operate@^12.0.0": version "12.9.2" resolved "https://registry.npmjs.org/@putout/operate/-/operate-12.9.2.tgz" integrity sha512-Z1KIlW9iFR5NBrzI60EqkJiC/k2fkqp6WK5+rrEokzvGox8DYJPAE5Dztc5IVMJRUjGB46fh7EXyA2vsY2Hj/Q== dependencies: "@putout/babel" "^2.0.0" -"@putout/operator-add-args@^8.0.0": - version "8.0.1" - dependencies: - "@putout/babel" "^2.0.0" - "@putout/compare" "^14.0.0" - "@putout/engine-parser" "^10.0.2" - -"@putout/operator-declare@^9.0.0": - version "9.1.0" - dependencies: - "@putout/babel" "^2.0.0" - "@putout/compare" "^14.0.0" - "@putout/engine-parser" "^10.0.2" - "@putout/operate" "^12.0.0" - -"@putout/operator-filesystem@^4.0.0": - version "4.1.0" - dependencies: - "@putout/babel" "^2.0.0" - "@putout/operate" "^12.0.0" - fullstore "^3.0.0" - try-catch "^3.0.1" - -"@putout/operator-ignore@^1.0.0": - version "1.2.0" - dependencies: - "@putout/babel" "^2.4.0" - "@putout/operate" "^12.5.0" - "@putout/operator-json@^2.0.0": version "2.1.0" resolved "https://registry.npmjs.org/@putout/operator-json/-/operator-json-2.1.0.tgz" @@ -1378,409 +1405,19 @@ dependencies: remove-blank-lines "^1.4.1" -"@putout/operator-match-files@^3.0.0": - version "3.5.0" - dependencies: - "@putout/babel" "^2.0.0" - "@putout/engine-parser" "^10.0.2" - "@putout/operator-filesystem" "^4.0.0" - "@putout/operator-json" "^2.0.0" - -"@putout/operator-regexp@^1.0.0": - version "1.0.0" - dependencies: - regexp-tree "^0.1.24" - -"@putout/operator-rename-files@^1.0.0": - version "1.0.0" - dependencies: - "@putout/operator-filesystem" "^4.0.0" - -"@putout/plugin-apply-at@^2.0.0": - version "2.0.0" - -"@putout/plugin-apply-destructuring@^7.0.0": - version "7.1.0" - -"@putout/plugin-apply-dot-notation@^2.0.0": - version "2.0.0" - -"@putout/plugin-apply-early-return@^3.0.0": - version "3.0.0" - -"@putout/plugin-apply-flat-map@^2.0.0": - version "2.0.0" - -"@putout/plugin-apply-optional-chaining@^5.0.0": - version "5.0.1" - -"@putout/plugin-apply-overrides@^1.0.0": - version "1.3.0" - -"@putout/plugin-apply-starts-with@^1.0.0": - version "1.1.0" - -"@putout/plugin-apply-template-literals@^3.0.0": - version "3.0.0" - -"@putout/plugin-browserlist@^2.0.0": - version "2.0.0" - -"@putout/plugin-conditions@^4.0.0": - version "4.4.0" - -"@putout/plugin-convert-apply-to-spread@^4.0.0": - version "4.0.0" - -"@putout/plugin-convert-arguments-to-rest@^2.0.0": - version "2.0.0" - -"@putout/plugin-convert-array-copy-to-slice@^3.0.0": - version "3.0.0" - -"@putout/plugin-convert-assignment-to-arrow-function@^1.0.0": - version "1.2.0" - -"@putout/plugin-convert-assignment-to-comparison@^2.0.0": - version "2.0.0" - -"@putout/plugin-convert-concat-to-flat@^1.0.0": - version "1.0.0" - -"@putout/plugin-convert-const-to-let@^1.0.0": - version "1.2.0" - -"@putout/plugin-convert-index-of-to-includes@^2.0.0": - version "2.0.1" - -"@putout/plugin-convert-label-to-object@^1.0.0": - version "1.0.2" - -"@putout/plugin-convert-object-assign-to-merge-spread@^6.0.0": - version "6.0.0" - -"@putout/plugin-convert-object-entries-to-array-entries@^3.0.0": - version "3.0.1" - -"@putout/plugin-convert-optional-to-logical@^3.0.0": - version "3.1.0" - -"@putout/plugin-convert-quotes-to-backticks@^3.0.0": - version "3.0.0" - -"@putout/plugin-convert-template-to-string@^2.0.0": - version "2.0.0" - -"@putout/plugin-convert-to-arrow-function@^4.0.0": - version "4.0.0" - -"@putout/plugin-coverage@^1.0.0": - version "1.0.0" - -"@putout/plugin-declare-before-reference@^4.0.0": - version "4.0.0" - -"@putout/plugin-declare-imports-first@^2.0.0": - version "2.1.0" - -"@putout/plugin-declare@^4.0.0": - version "4.0.0" - -"@putout/plugin-eslint@^8.0.0": - version "8.12.1" - -"@putout/plugin-extract-object-properties@^9.0.0": - version "9.0.0" - -"@putout/plugin-extract-sequence-expressions@^3.0.0": - version "3.5.0" - -"@putout/plugin-filesystem@^5.0.0": - version "5.3.0" - dependencies: - "@putout/babel" "^2.0.0" - "@putout/operate" "^12.0.0" - "@putout/operator-filesystem" "^4.0.0" - "@putout/operator-json" "^2.0.0" - -"@putout/plugin-for-of@^6.0.0": - version "6.1.0" - -"@putout/plugin-generators@^1.0.0": - version "1.0.0" - dependencies: - fullstore "^3.0.0" - -"@putout/plugin-github@^12.0.0": - version "12.3.0" - -"@putout/plugin-gitignore@^6.0.0": - version "6.0.0" - -"@putout/plugin-logical-expressions@^6.0.0": - version "6.0.0" - -"@putout/plugin-madrun@^18.0.0": - version "18.0.0" - -"@putout/plugin-math@^2.0.0": - version "2.1.0" - -"@putout/plugin-maybe@^2.0.0": - version "2.0.1" - -"@putout/plugin-merge-destructuring-properties@^8.0.0": - version "8.0.0" - -"@putout/plugin-merge-duplicate-functions@^2.0.0": - version "2.0.0" - -"@putout/plugin-merge-duplicate-imports@^11.0.0": - version "11.0.0" - -"@putout/plugin-montag@^2.0.0": - version "2.0.0" - -"@putout/plugin-new@^3.0.1": - version "3.0.1" - -"@putout/plugin-nodejs@^11.0.0": - version "11.11.0" - dependencies: - just-camel-case "^6.2.0" - -"@putout/plugin-npmignore@^5.0.0": - version "5.0.0" - -"@putout/plugin-package-json@^7.0.0": - version "7.2.0" - -"@putout/plugin-promises@^15.0.0": - version "15.0.0" - dependencies: - fullstore "^3.0.0" - -"@putout/plugin-putout-config@^5.0.0": - version "5.0.0" - -"@putout/plugin-putout@^20.0.0": - version "20.3.0" - dependencies: - fullstore "^3.0.0" - just-camel-case "^6.0.1" - parse-import-specifiers "^1.0.2" - try-catch "^3.0.0" - -"@putout/plugin-regexp@^8.0.0": - version "8.0.0" - dependencies: - regexp-tree "^0.1.21" - try-catch "^3.0.0" - -"@putout/plugin-remove-console@^6.0.0": - version "6.0.0" - -"@putout/plugin-remove-constant-conditions@^4.0.0": - version "4.0.2" - -"@putout/plugin-remove-debugger@^7.0.0": - version "7.0.0" - -"@putout/plugin-remove-duplicate-case@^3.0.0": - version "3.0.0" - -"@putout/plugin-remove-duplicate-keys@^5.0.0": - version "5.1.0" - dependencies: - fullstore "^3.0.0" - -"@putout/plugin-remove-empty@^12.0.0": - version "12.1.0" - -"@putout/plugin-remove-iife@^4.0.0": - version "4.1.0" - -"@putout/plugin-remove-nested-blocks@^6.0.0": - version "6.3.0" - -"@putout/plugin-remove-unreachable-code@^1.0.0": - version "1.2.0" - -"@putout/plugin-remove-unreferenced-variables@^3.0.0": - version "3.1.0" - -"@putout/plugin-remove-unused-expressions@^8.0.0": - version "8.0.0" - -"@putout/plugin-remove-unused-for-of-variables@^3.0.0": - version "3.0.1" - -"@putout/plugin-remove-unused-private-fields@^2.0.0": - version "2.1.0" - -"@putout/plugin-remove-unused-variables@^9.0.0": - version "9.0.0" - -"@putout/plugin-remove-useless-arguments@^8.0.0": - version "8.0.0" - -"@putout/plugin-remove-useless-array-constructor@^2.0.0": - version "2.0.0" - -"@putout/plugin-remove-useless-array-entries@^1.0.0": - version "1.0.0" - -"@putout/plugin-remove-useless-array@^1.0.0": - version "1.1.0" - -"@putout/plugin-remove-useless-assign@^1.0.0": - version "1.1.0" - -"@putout/plugin-remove-useless-constructor@^1.0.0": - version "1.0.0" - -"@putout/plugin-remove-useless-continue@^2.0.0": - version "2.0.0" - -"@putout/plugin-remove-useless-escape@^6.0.0": - version "6.0.0" - dependencies: - emoji-regex "^10.0.1" - -"@putout/plugin-remove-useless-functions@^3.0.0": - version "3.0.0" - -"@putout/plugin-remove-useless-map@^1.0.0": - version "1.1.0" - -"@putout/plugin-remove-useless-operand@^2.0.0": - version "2.1.0" - -"@putout/plugin-remove-useless-replace@^1.0.1": - version "1.0.4" - -"@putout/plugin-remove-useless-return@^6.0.0": - version "6.0.0" - -"@putout/plugin-remove-useless-spread@^11.0.0": - version "11.0.0" - -"@putout/plugin-remove-useless-template-expressions@^2.0.0": - version "2.0.0" - -"@putout/plugin-remove-useless-variables@^11.0.0": - version "11.1.1" - -"@putout/plugin-reuse-duplicate-init@^5.0.0": - version "5.0.0" - -"@putout/plugin-simplify-assignment@^3.0.0": - version "3.1.0" - -"@putout/plugin-simplify-boolean-return@^1.0.0": - version "1.1.0" - -"@putout/plugin-simplify-ternary@^7.0.0": - version "7.0.0" - -"@putout/plugin-sort-imports-by-specifiers@^1.0.0": - version "1.1.0" - dependencies: - parse-import-specifiers "^1.0.3" - -"@putout/plugin-split-assignment-expressions@^1.0.0": - version "1.0.0" - -"@putout/plugin-split-nested-destructuring@^3.0.0": - version "3.0.0" - -"@putout/plugin-split-variable-declarations@^3.0.0": - version "3.0.0" - -"@putout/plugin-tape@^14.0.0": - version "14.2.0" - -"@putout/plugin-try-catch@^3.0.0": - version "3.0.0" - -"@putout/plugin-types@^4.0.0": - version "4.1.0" - -"@putout/plugin-typescript@^7.0.0": - version "7.4.0" - -"@putout/plugin-webpack@^3.0.0": - version "3.0.0" - -"@putout/printer@^8.0.0", "@putout/printer@^8.0.1": +"@putout/printer@^8.0.0": version "8.5.0" resolved "https://registry.npmjs.org/@putout/printer/-/printer-8.5.0.tgz" integrity sha512-YHJgvZLIA4YI3ypeyg1vxvs+HzKI1sXQ2fzGoQe2XT0dYuwLFlMG72rJQRo2Yi1u3ws/cMsq3Pcqeju7hDXhsA== dependencies: "@putout/babel" "^2.0.0" - "@putout/compare" "^14.0.0" - "@putout/operate" "^12.0.0" - "@putout/operator-json" "^2.0.0" - fullstore "^3.0.0" - just-snake-case "^3.2.0" - parse-import-specifiers "^1.0.1" - rendy "^4.0.0" - -"@putout/processor-css@^9.0.0": - version "9.0.0" - dependencies: - align-spaces "^1.0.4" - cosmiconfig "^9.0.0" - deepmerge "^4.2.2" - prettier "^3.1.0" - stylelint "^16.0.1" - stylelint-config-standard "^36.0.0" - stylelint-prettier "^5.0.0" - -"@putout/processor-filesystem@^4.0.0": - version "4.0.0" - dependencies: - "@putout/cli-filesystem" "^2.0.1" - "@putout/operator-filesystem" "^4.0.0" - "@putout/operator-json" "^2.0.0" - -"@putout/processor-ignore@^6.0.0": - version "6.0.1" - dependencies: - "@putout/operator-json" "^2.0.0" - -"@putout/processor-javascript@^5.0.0": - version "5.0.0" - -"@putout/processor-json@^9.0.0": - version "9.0.0" - dependencies: - "@putout/operator-json" "^2.0.0" - -"@putout/processor-markdown@^12.0.0": - version "12.1.0" - dependencies: - "@putout/operator-json" "^2.0.0" - once "^1.4.0" - remark-parse "^11.0.0" - remark-preset-lint-consistent "^6.0.0" - remark-stringify "^11.0.0" - unified "^11.0.3" - unified-lint-rule "^3.0.0" - unist-util-visit "^5.0.0" - -"@putout/processor-yaml@^8.0.0": - version "8.1.0" - dependencies: - "@putout/operator-json" "^2.0.0" - just-kebab-case "^4.0.2" - try-catch "^3.0.0" - yaml "^2.2.2" - -"@putout/quick-lint@^1.2.0": - version "1.2.0" - dependencies: - once "^1.4.0" + "@putout/compare" "^14.0.0" + "@putout/operate" "^12.0.0" + "@putout/operator-json" "^2.0.0" + fullstore "^3.0.0" + just-snake-case "^3.2.0" + parse-import-specifiers "^1.0.1" + rendy "^4.0.0" "@putout/recast@^1.12.1": version "1.13.0" @@ -1793,17 +1430,28 @@ source-map "~0.6.1" tslib "^2.0.1" -"@putout/traverse@^10.0.0": - version "10.0.1" - dependencies: - "@putout/babel" "^2.0.0" - "@putout/compare" "^14.0.0" - "@rushstack/eslint-patch@^1.3.3": version "1.7.2" resolved "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.7.2.tgz" integrity sha512-RbhOOTCNoCrbfkRyoXODZp75MlpiHMgbE5MEBZAnnnLyQNgrigEj4p0lzsMDyc1zVsJDLrivB58tgg3emX0eEA== +"@sideway/address@^4.1.5": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" + integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" + integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + "@sinclair/typebox@^0.27.8": version "0.27.8" resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz" @@ -1823,7 +1471,7 @@ dependencies: "@sinonjs/commons" "^3.0.0" -"@stylistic/eslint-plugin-js@^2.1.0", "@stylistic/eslint-plugin-js@^2.6.1", "@stylistic/eslint-plugin-js@2.6.1": +"@stylistic/eslint-plugin-js@2.6.1", "@stylistic/eslint-plugin-js@^2.1.0", "@stylistic/eslint-plugin-js@^2.6.1": version "2.6.1" resolved "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.6.1.tgz" integrity sha512-iLOiVzcvqzDGD9U0EuVOX680v+XOPiPAjkxWj+Q6iV2GLOM5NB27tKVOpJY7AzBhidwpRbaLTgg3T4UzYx09jw== @@ -2078,7 +1726,7 @@ resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz" integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng== -"@types/react@>= 16.8.0 < 19.0.0", "@types/react@>=16", "@types/react@>=18": +"@types/react@>=16": version "18.2.55" resolved "https://registry.npmjs.org/@types/react/-/react-18.2.55.tgz" integrity sha512-Y2Tz5P4yz23brwm2d7jNon39qoAtMMmalOQv6+fEFt1mT+FcM3D841wDpoUvFXhaYenuROCy3FZYqdTjM7qVyA== @@ -2124,7 +1772,7 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.0.0 || ^6.0.0 || ^7.0.0", "@typescript-eslint/eslint-plugin@^7.0.1", "@typescript-eslint/eslint-plugin@^7.18.0", "@typescript-eslint/eslint-plugin@7.18.0": +"@typescript-eslint/eslint-plugin@7.18.0", "@typescript-eslint/eslint-plugin@^7.0.1", "@typescript-eslint/eslint-plugin@^7.18.0": version "7.18.0" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz" integrity sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw== @@ -2139,6 +1787,17 @@ natural-compare "^1.4.0" ts-api-utils "^1.3.0" +"@typescript-eslint/parser@7.18.0", "@typescript-eslint/parser@^7.0.1", "@typescript-eslint/parser@^7.18.0": + version "7.18.0" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz" + integrity sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg== + dependencies: + "@typescript-eslint/scope-manager" "7.18.0" + "@typescript-eslint/types" "7.18.0" + "@typescript-eslint/typescript-estree" "7.18.0" + "@typescript-eslint/visitor-keys" "7.18.0" + debug "^4.3.4" + "@typescript-eslint/parser@^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0": version "6.21.0" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz" @@ -2150,17 +1809,6 @@ "@typescript-eslint/visitor-keys" "6.21.0" debug "^4.3.4" -"@typescript-eslint/parser@^7.0.0", "@typescript-eslint/parser@^7.0.1", "@typescript-eslint/parser@^7.18.0", "@typescript-eslint/parser@7.18.0": - version "7.18.0" - resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz" - integrity sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg== - dependencies: - "@typescript-eslint/scope-manager" "7.18.0" - "@typescript-eslint/types" "7.18.0" - "@typescript-eslint/typescript-estree" "7.18.0" - "@typescript-eslint/visitor-keys" "7.18.0" - debug "^4.3.4" - "@typescript-eslint/scope-manager@5.62.0": version "5.62.0" resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz" @@ -2278,6 +1926,16 @@ semver "^7.6.0" ts-api-utils "^1.3.0" +"@typescript-eslint/utils@7.18.0": + version "7.18.0" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz" + integrity sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "7.18.0" + "@typescript-eslint/types" "7.18.0" + "@typescript-eslint/typescript-estree" "7.18.0" + "@typescript-eslint/utils@^5.10.0": version "5.62.0" resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz" @@ -2302,16 +1960,6 @@ "@typescript-eslint/types" "8.0.0" "@typescript-eslint/typescript-estree" "8.0.0" -"@typescript-eslint/utils@7.18.0": - version "7.18.0" - resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz" - integrity sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw== - dependencies: - "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "7.18.0" - "@typescript-eslint/types" "7.18.0" - "@typescript-eslint/typescript-estree" "7.18.0" - "@typescript-eslint/visitor-keys@5.62.0": version "5.62.0" resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz" @@ -2361,10 +2009,7 @@ acorn-jsx@^5.0.0, acorn-jsx@^5.3.2: resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-typescript@^1.4.13: - version "1.4.13" - -"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.0.0, acorn@^8.11.3, acorn@^8.12.0, acorn@^8.12.1, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.9.0, acorn@>=8.9.0: +acorn@^8.0.0, acorn@^8.11.3, acorn@^8.12.0, acorn@^8.12.1, acorn@^8.5.0, acorn@^8.9.0: version "8.12.1" resolved "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz" integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== @@ -2379,23 +2024,7 @@ ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.1: - version "8.14.0" - dependencies: - fast-deep-equal "^3.1.3" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.4.1" - -ajv@^8.8.2: - version "8.14.0" - dependencies: - fast-deep-equal "^3.1.3" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.4.1" - -algoliasearch@^4.19.1, "algoliasearch@>= 4.9.1 < 6": +algoliasearch@^4.19.1: version "4.22.1" resolved "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.22.1.tgz" integrity sha512-jwydKFQJKIx9kIZ8Jm44SdpigFwRGPESaxZBaHSV0XWN2yBJAOT4mT7ppvlrpA4UGzz92pqFnVKr/kaZXrcreg== @@ -2415,14 +2044,11 @@ algoliasearch@^4.19.1, "algoliasearch@>= 4.9.1 < 6": "@algolia/requester-node-http" "4.22.1" "@algolia/transporter" "4.22.1" -align-spaces@^1.0.0, align-spaces@^1.0.4: +align-spaces@^1.0.0: version "1.0.4" resolved "https://registry.npmjs.org/align-spaces/-/align-spaces-1.0.4.tgz" integrity sha512-JPl93xFbsX4OY7VFKjerJ9cjaelmKo1wt1EP0ScrKI578vro1WhGy+w9C0nAFup8qYADgAS2FvMb7uLPStTB6g== -ansi-colors@^4.1.1: - version "4.1.3" - ansi-escapes@^4.2.1: version "4.3.2" resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz" @@ -2452,28 +2078,14 @@ ansi-sequence-parser@^1.1.0: resolved "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz" integrity sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg== -ansi-styles@^3.1.0: - version "3.2.1" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^3.2.1: +ansi-styles@^3.1.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" -ansi-styles@^4.0.0: - version "4.3.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^4.1.0: +ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== @@ -2508,16 +2120,16 @@ arch@^2.1.0: resolved "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz" integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== -arg@^5.0.2: - version "5.0.2" - resolved "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz" - integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== - arg@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/arg/-/arg-1.0.0.tgz" integrity sha512-Wk7TEzl1KqvTGs/uyhmHO/3XLd3t1UeU4IstvPXVzGPM522cTjqjNZ99esCkcL52sjqjo8e8CTBcWhkxvGzoAw== +arg@^5.0.2: + version "5.0.2" + resolved "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" @@ -2651,9 +2263,6 @@ ast-types@^0.16.1: dependencies: tslib "^2.0.1" -astral-regex@^2.0.0: - version "2.0.0" - astring@^1.8.0: version "1.8.6" resolved "https://registry.npmjs.org/astring/-/astring-1.8.6.tgz" @@ -2765,9 +2374,6 @@ balanced-match@^1.0.0: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -balanced-match@^2.0.0: - version "2.0.0" - binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" @@ -2795,7 +2401,7 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" -browserslist@^4.22.2, browserslist@^4.23.0, "browserslist@>= 4.21.0": +browserslist@^4.22.2, browserslist@^4.23.0: version "4.23.0" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz" integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== @@ -2865,6 +2471,15 @@ ccount@^2.0.0: resolved "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz" integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== +chalk@2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz" + integrity sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q== + dependencies: + ansi-styles "^3.1.0" + escape-string-regexp "^1.0.5" + supports-color "^4.0.0" + chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" @@ -2882,25 +2497,11 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^5.3.0: - version "5.3.0" - resolved "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz" - integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== - -chalk@~5.3.0: +chalk@^5.3.0, chalk@~5.3.0: version "5.3.0" resolved "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz" integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== -chalk@2.3.0: - version "2.3.0" - resolved "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz" - integrity sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q== - dependencies: - ansi-styles "^3.1.0" - escape-string-regexp "^1.0.5" - supports-color "^4.0.0" - char-regex@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz" @@ -2946,9 +2547,6 @@ ci-info@^3.2.0: resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== -ci-info@^4.0.0: - version "4.0.0" - cjs-module-lexer@^1.0.0: version "1.2.3" resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz" @@ -2961,11 +2559,6 @@ cli-cursor@^5.0.0: dependencies: restore-cursor "^5.0.0" -cli-progress@^3.8.2: - version "3.12.0" - dependencies: - string-width "^4.2.3" - cli-truncate@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz" @@ -2974,7 +2567,7 @@ cli-truncate@^4.0.0: slice-ansi "^5.0.0" string-width "^7.0.0" -client-only@^0.0.1, client-only@0.0.1: +client-only@0.0.1, client-only@^0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz" integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== @@ -3025,16 +2618,16 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@^1.0.0, color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - color-name@1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + color-string@^1.9.0: version "1.9.1" resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz" @@ -3051,9 +2644,6 @@ color@^4.2.3: color-convert "^2.0.1" color-string "^1.9.0" -colord@^2.9.3: - version "2.9.3" - colorette@^2.0.20: version "2.0.20" resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz" @@ -3064,6 +2654,11 @@ comma-separated-tokens@^2.0.0: resolved "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz" integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== +commander@7: + version "7.2.0" + resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + commander@^4.0.0: version "4.1.1" resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz" @@ -3079,14 +2674,6 @@ commander@~12.1.0: resolved "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz" integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== -commander@7: - version "7.2.0" - resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - -common-path-prefix@^3.0.0: - version "3.0.0" - compute-scroll-into-view@^3.0.2: version "3.1.0" resolved "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz" @@ -3109,14 +2696,6 @@ cose-base@^1.0.0: dependencies: layout-base "^1.0.0" -cosmiconfig@^9.0.0: - version "9.0.0" - dependencies: - env-paths "^2.2.1" - import-fresh "^3.3.0" - js-yaml "^4.1.0" - parse-json "^5.2.0" - create-jest@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz" @@ -3148,15 +2727,6 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -css-functions-list@^3.2.2: - version "3.2.2" - -css-tree@^2.3.1: - version "2.3.1" - dependencies: - mdn-data "2.0.30" - source-map-js "^1.0.1" - cssesc@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz" @@ -3167,9 +2737,6 @@ csstype@^3.0.2: resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== -currify@^4.0.0: - version "4.0.0" - cytoscape-cose-bilkent@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz" @@ -3177,7 +2744,7 @@ cytoscape-cose-bilkent@^4.1.0: dependencies: cose-base "^1.0.0" -cytoscape@^3.2.0, cytoscape@^3.28.1: +cytoscape@^3.28.1: version "3.28.1" resolved "https://registry.npmjs.org/cytoscape/-/cytoscape-3.28.1.tgz" integrity sha512-xyItz4O/4zp9/239wCcH8ZcFuuZooEeF8KHRmzjDfGdXsj3OG9MFSMA0pJE0uX3uCN/ygof6hHf4L7lst+JaDg== @@ -3185,13 +2752,6 @@ cytoscape@^3.2.0, cytoscape@^3.28.1: heap "^0.2.6" lodash "^4.17.21" -d3-array@^3.2.0, "d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3: - version "3.2.4" - resolved "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz" - integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== - dependencies: - internmap "1 - 2" - "d3-array@1 - 2": version "2.12.1" resolved "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz" @@ -3199,6 +2759,13 @@ d3-array@^3.2.0, "d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", dependencies: internmap "^1.0.0" +"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0: + version "3.2.4" + resolved "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz" + integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== + dependencies: + internmap "1 - 2" + d3-axis@3: version "3.0.0" resolved "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz" @@ -3308,16 +2875,16 @@ d3-hierarchy@3: dependencies: d3-color "1 - 3" -d3-path@^3.1.0, "d3-path@1 - 3", d3-path@3: - version "3.1.0" - resolved "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz" - integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== - d3-path@1: version "1.0.9" resolved "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz" integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg== +"d3-path@1 - 3", d3-path@3, d3-path@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz" + integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== + d3-polygon@3: version "3.0.1" resolved "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz" @@ -3365,13 +2932,6 @@ d3-scale@4: resolved "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz" integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== -d3-shape@^1.2.0: - version "1.3.7" - resolved "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz" - integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw== - dependencies: - d3-path "1" - d3-shape@3: version "3.2.0" resolved "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz" @@ -3379,6 +2939,13 @@ d3-shape@3: dependencies: d3-path "^3.1.0" +d3-shape@^1.2.0: + version "1.3.7" + resolved "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz" + integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw== + dependencies: + d3-path "1" + "d3-time-format@2 - 4", d3-time-format@4: version "4.1.0" resolved "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz" @@ -3505,7 +3072,7 @@ deep-is@^0.1.3: resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -deepmerge@^4.0.0, deepmerge@^4.2.2: +deepmerge@^4.2.2: version "4.3.1" resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== @@ -3562,9 +3129,6 @@ didyoumean@^1.2.2: resolved "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz" integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== -diff-match-patch@^1.0.4: - version "1.0.5" - diff-sequences@^29.6.3: version "29.6.3" resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz" @@ -3626,9 +3190,6 @@ emittery@^0.13.1: resolved "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz" integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== -emoji-regex@^10.0.1: - version "10.3.0" - emoji-regex@^10.3.0: version "10.3.0" resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz" @@ -3652,20 +3213,11 @@ enhanced-resolve@^5.12.0, enhanced-resolve@^5.17.0: graceful-fs "^4.2.4" tapable "^2.2.0" -enquirer@^2.4.1: - version "2.4.1" - dependencies: - ansi-colors "^4.1.1" - strip-ansi "^6.0.1" - entities@^4.4.0: version "4.5.0" resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== -env-paths@^2.2.1: - version "2.2.1" - environment@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz" @@ -3873,7 +3425,7 @@ eslint-plugin-es-x@^7.5.0: "@eslint-community/regexpp" "^4.11.0" eslint-compat-utils "^0.5.1" -eslint-plugin-import@*, eslint-plugin-import@^2.28.1: +eslint-plugin-import@^2.28.1: version "2.29.1" resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz" integrity sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw== @@ -4034,7 +3586,7 @@ eslint-visitor-keys@^4.0.0: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz" integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw== -eslint@*, "eslint@^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8", "eslint@^3 || ^4 || ^5 || ^6 || ^7 || ^8", "eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "eslint@^6.0.0 || ^7.0.0 || ^8.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^7.0.0 || ^8.0.0", "eslint@^7.23.0 || ^8.0.0", eslint@^8.56.0, eslint@^8.57.0, "eslint@^8.57.0 || ^9.0.0", eslint@^8.9.0, eslint@>=6.0.0, eslint@>=8, eslint@>=8.0.0, eslint@>=8.23.0, eslint@>=8.40.0: +eslint@^8.57.0: version "8.57.0" resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz" integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== @@ -4148,11 +3700,6 @@ estree-util-attach-comments@^2.0.0: dependencies: "@types/estree" "^1.0.0" -estree-util-attach-comments@^3.0.0: - version "3.0.0" - dependencies: - "@types/estree" "^1.0.0" - estree-util-build-jsx@^2.0.0: version "2.2.2" resolved "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-2.2.2.tgz" @@ -4289,10 +3836,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-diff@^1.1.2: - version "1.3.0" - -fast-glob@^3.2.2, fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.1, fast-glob@^3.3.2: +fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.1: version "3.3.2" resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== @@ -4313,9 +3857,6 @@ fast-levenshtein@^2.0.6: resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== -fastest-levenshtein@^1.0.12, fastest-levenshtein@^1.0.16: - version "1.0.16" - fastq@^1.6.0: version "1.17.1" resolved "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz" @@ -4337,11 +3878,6 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -file-entry-cache@^9.0.0: - version "9.0.0" - dependencies: - flat-cache "^5.0.0" - fill-range@^7.1.1: version "7.1.1" resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz" @@ -4349,12 +3885,6 @@ fill-range@^7.1.1: dependencies: to-regex-range "^5.0.1" -find-cache-dir@^5.0.0: - version "5.0.0" - dependencies: - common-path-prefix "^3.0.0" - pkg-dir "^7.0.0" - find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" @@ -4371,12 +3901,6 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -find-up@^6.3.0: - version "6.3.0" - dependencies: - locate-path "^7.1.0" - path-exists "^5.0.0" - find-up@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz" @@ -4395,13 +3919,7 @@ flat-cache@^3.0.4: keyv "^4.5.3" rimraf "^3.0.2" -flat-cache@^5.0.0: - version "5.0.0" - dependencies: - flatted "^3.3.1" - keyv "^4.5.4" - -flatted@^3.2.9, flatted@^3.3.1: +flatted@^3.2.9: version "3.3.1" resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz" integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== @@ -4431,11 +3949,6 @@ foreground-child@^3.1.0: cross-spawn "^7.0.0" signal-exit "^4.0.1" -format-io@^2.0.0: - version "2.0.0" - dependencies: - currify "^4.0.0" - fraction.js@^4.3.7: version "4.3.7" resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz" @@ -4572,7 +4085,7 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob@^10.3.10: +glob@10.3.10, glob@^10.3.10: version "10.3.10" resolved "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz" integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g== @@ -4595,29 +4108,6 @@ glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -glob@10.3.10: - version "10.3.10" - resolved "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz" - integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g== - dependencies: - foreground-child "^3.1.0" - jackspeak "^2.3.5" - minimatch "^9.0.1" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - path-scurry "^1.10.1" - -global-modules@^2.0.0: - version "2.0.0" - dependencies: - global-prefix "^3.0.0" - -global-prefix@^3.0.0: - version "3.0.0" - dependencies: - ini "^1.3.5" - kind-of "^6.0.2" - which "^1.3.1" - globals@^11.1.0: version "11.12.0" resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" @@ -4654,23 +4144,6 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -globjoin@^0.1.4: - version "0.1.4" - -goldstein@^5.11.0: - version "5.11.0" - dependencies: - "@putout/plugin-declare" "^4.0.0" - "@putout/plugin-logical-expressions" "^6.0.0" - "@putout/plugin-try-catch" "^3.0.0" - "@putout/printer" "^8.0.1" - acorn "^8.7.1" - acorn-typescript "^1.4.13" - estree-to-babel "^9.0.0" - estree-util-attach-comments "^3.0.0" - putout "^35.0.0" - try-catch "^3.0.1" - gopd@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz" @@ -4934,9 +4407,6 @@ html-escaper@^2.0.0: resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -html-tags@^3.3.1: - version "3.3.1" - html-url-attributes@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.0.tgz" @@ -4964,12 +4434,12 @@ iconv-lite@0.6: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -ignore@^5.0.4, ignore@^5.2.0, ignore@^5.2.4, ignore@^5.3.1: +ignore@^5.2.0, ignore@^5.2.4, ignore@^5.3.1: version "5.3.1" resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz" integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== -import-fresh@^3.2.1, import-fresh@^3.3.0: +import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -4998,14 +4468,11 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@^2.0.3, inherits@2: +inherits@2, inherits@^2.0.3: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@^1.3.5: - version "1.3.8" - inline-style-parser@0.1.1: version "0.1.1" resolved "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz" @@ -5025,16 +4492,16 @@ internal-slot@^1.0.5, internal-slot@^1.0.7: hasown "^2.0.0" side-channel "^1.0.4" -internmap@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz" - integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw== - "internmap@1 - 2": version "2.0.3" resolved "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz" integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== +internmap@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz" + integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw== + intersection-observer@^0.12.2: version "0.12.2" resolved "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.12.2.tgz" @@ -5245,9 +4712,6 @@ is-plain-obj@^4.0.0: resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz" integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== -is-plain-object@^5.0.0: - version "5.0.0" - is-reference@^3.0.0: version "3.0.2" resolved "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz" @@ -5263,11 +4727,6 @@ is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-relative@^1.0.0: - version "1.0.0" - dependencies: - is-unc-path "^1.0.0" - is-set@^2.0.1: version "2.0.2" resolved "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz" @@ -5323,11 +4782,6 @@ is-typed-array@^1.1.10, is-typed-array@^1.1.13, is-typed-array@^1.1.3, is-typed- dependencies: which-typed-array "^1.1.14" -is-unc-path@^1.0.0: - version "1.0.0" - dependencies: - unc-path-regex "^0.1.2" - is-weakmap@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz" @@ -5640,7 +5094,7 @@ jest-resolve-dependencies@^29.7.0: jest-regex-util "^29.6.3" jest-snapshot "^29.7.0" -jest-resolve@*, jest-resolve@^29.7.0: +jest-resolve@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz" integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== @@ -5784,7 +5238,7 @@ jest-worker@^29.7.0: merge-stream "^2.0.0" supports-color "^8.0.0" -jest@*, jest@^29.7.0: +jest@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz" integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== @@ -5799,6 +5253,17 @@ jiti@^1.21.0: resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz" integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== +joi@^17.6.4: + version "17.13.3" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.13.3.tgz#0f5cc1169c999b30d344366d384b12d92558bcec" + integrity sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA== + dependencies: + "@hapi/hoek" "^9.3.0" + "@hapi/topo" "^5.1.0" + "@sideway/address" "^4.1.5" + "@sideway/formula" "^3.0.1" + "@sideway/pinpoint" "^2.0.0" + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" @@ -5809,9 +5274,6 @@ js-tokens@^8.0.0: resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.3.tgz" integrity sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw== -js-tokens@^9.0.0: - version "9.0.0" - js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" @@ -5852,9 +5314,6 @@ json-schema-traverse@^0.4.1: resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== -json-schema-traverse@^1.0.0: - version "1.0.0" - json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" @@ -5897,15 +5356,6 @@ jsonc-parser@^3.2.0: object.assign "^4.1.4" object.values "^1.1.6" -just-camel-case@^6.0.1, just-camel-case@^6.2.0: - version "6.2.0" - -just-kebab-case@^1.1.0: - version "1.1.0" - -just-kebab-case@^4.0.2: - version "4.2.0" - just-snake-case@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/just-snake-case/-/just-snake-case-3.2.0.tgz" @@ -5918,7 +5368,7 @@ katex@^0.16.0, katex@^0.16.9: dependencies: commander "^8.3.0" -keyv@^4.5.3, keyv@^4.5.4: +keyv@^4.5.3: version "4.5.4" resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz" integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== @@ -5945,9 +5395,6 @@ kleur@^4.0.3: resolved "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -known-css-properties@^0.31.0: - version "0.31.0" - language-subtag-registry@^0.3.20: version "0.3.22" resolved "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz" @@ -6035,11 +5482,6 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -locate-path@^7.1.0: - version "7.2.0" - dependencies: - p-locate "^6.0.0" - locate-path@^7.2.0: version "7.2.0" resolved "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz" @@ -6062,9 +5504,6 @@ lodash.merge@^4.6.2: resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash.truncate@^4.4.2: - version "4.4.2" - lodash@^4.17.21: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" @@ -6157,15 +5596,6 @@ match-sorter@^6.3.1: "@babel/runtime" "^7.23.8" remove-accents "0.5.0" -mathml-tag-names@^2.1.3: - version "2.1.3" - -mdast-comment-marker@^3.0.0: - version "3.0.0" - dependencies: - "@types/mdast" "^4.0.0" - mdast-util-mdx-expression "^2.0.0" - mdast-util-definitions@^5.0.0: version "5.1.2" resolved "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz" @@ -6279,11 +5709,6 @@ mdast-util-gfm@^2.0.0: mdast-util-gfm-task-list-item "^1.0.0" mdast-util-to-markdown "^1.0.0" -mdast-util-heading-style@^3.0.0: - version "3.0.0" - dependencies: - "@types/mdast" "^4.0.0" - mdast-util-math@^2.0.0: version "2.0.2" resolved "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-2.0.2.tgz" @@ -6474,12 +5899,6 @@ mdast-util-to-string@^4.0.0: dependencies: "@types/mdast" "^4.0.0" -mdn-data@2.0.30: - version "2.0.30" - -meow@^13.2.0: - version "13.2.0" - merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" @@ -7089,7 +6508,7 @@ micromark@^4.0.0: micromark-util-symbol "^2.0.0" micromark-util-types "^2.0.0" -micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.7, micromatch@~4.0.7: +micromatch@^4.0.4, micromatch@^4.0.5, micromatch@~4.0.7: version "4.0.7" resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz" integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== @@ -7112,41 +6531,27 @@ mimic-function@^5.0.0: resolved "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz" integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== -minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^9.0.1: +minimatch@9.0.3, minimatch@^9.0.1: version "9.0.3" resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz" integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.4: - version "9.0.5" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: - brace-expansion "^2.0.1" + brace-expansion "^1.1.7" -minimatch@^9.0.5: +minimatch@^9.0.4, minimatch@^9.0.5: version "9.0.5" resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz" integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== dependencies: brace-expansion "^2.0.1" -minimatch@9.0.3: - version "9.0.3" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz" - integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== - dependencies: - brace-expansion "^2.0.1" - minimist@^1.2.0, minimist@^1.2.6: version "1.2.8" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" @@ -7157,24 +6562,21 @@ minimist@^1.2.0, minimist@^1.2.6: resolved "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz" integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== -montag@^1.1.0: - version "1.2.1" - mri@^1.1.0: version "1.2.0" resolved "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz" integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== -ms@^2.1.1: - version "2.1.3" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - ms@2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + mz@^2.7.0: version "2.7.0" resolved "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz" @@ -7224,7 +6626,7 @@ next-themes@^0.2.1: resolved "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz" integrity sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A== -next@*, next@^14.2.5, "next@^8.1.1-canary.54 || >=9.0.0", "next@>= 13", next@>=9.5.3: +next@^14.2.5: version "14.2.5" resolved "https://registry.npmjs.org/next/-/next-14.2.5.tgz" integrity sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA== @@ -7266,7 +6668,7 @@ nextra-theme-docs@latest: scroll-into-view-if-needed "^3.1.0" zod "^3.22.3" -nextra@2.13.4, nextra@latest: +nextra@latest: version "2.13.4" resolved "https://registry.npmjs.org/nextra/-/nextra-2.13.4.tgz" integrity sha512-7of2rSBxuUa3+lbMmZwG9cqgftcoNOVQLTT6Rxf3EhBR9t1EI7b43dted8YoqSNaigdE3j1CoyNkX8N/ZzlEpw== @@ -7546,7 +6948,7 @@ parse-entities@^4.0.0: is-decimal "^2.0.0" is-hexadecimal "^2.0.0" -parse-import-specifiers@^1.0.1, parse-import-specifiers@^1.0.2, parse-import-specifiers@^1.0.3: +parse-import-specifiers@^1.0.1: version "1.0.3" resolved "https://registry.npmjs.org/parse-import-specifiers/-/parse-import-specifiers-1.0.3.tgz" integrity sha512-jNtWL2DinOHUGnFEzeAyCJhacxwFkLzPnR3Foy3t2mOTIEgzZ3aaOakPw0PvoLaPZUy64CWYuhVFa/QkEMLJhA== @@ -7654,9 +7056,6 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -picomatch@^4.0.1: - version "4.0.2" - picomatch@^4.0.2: version "4.0.2" resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz" @@ -7684,14 +7083,6 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -pkg-dir@^7.0.0: - version "7.0.0" - dependencies: - find-up "^6.3.0" - -pluralize@^8.0.0: - version "8.0.0" - postcss-import@^15.1.0: version "15.1.0" resolved "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz" @@ -7723,13 +7114,7 @@ postcss-nested@^6.0.1: dependencies: postcss-selector-parser "^6.0.11" -postcss-resolve-nested-selector@^0.1.1: - version "0.1.1" - -postcss-safe-parser@^7.0.0: - version "7.0.0" - -postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.13, postcss-selector-parser@^6.1.0: +postcss-selector-parser@^6.0.11: version "6.1.1" resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz" integrity sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg== @@ -7742,15 +7127,6 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.0.0, postcss@^8.1.0, postcss@^8.2.14, postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.31, postcss@^8.4.38, postcss@^8.4.40, postcss@>=8.0.9: - version "8.4.40" - resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz" - integrity sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q== - dependencies: - nanoid "^3.3.7" - picocolors "^1.0.1" - source-map-js "^1.2.0" - postcss@8.4.31: version "8.4.31" resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz" @@ -7760,17 +7136,21 @@ postcss@8.4.31: picocolors "^1.0.0" source-map-js "^1.0.2" +postcss@^8.4.23, postcss@^8.4.40: + version "8.4.40" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz" + integrity sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.1" + source-map-js "^1.2.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prettier-linter-helpers@^1.0.0: - version "1.0.0" - dependencies: - fast-diff "^1.1.2" - -prettier@^3.1.0, prettier@^3.3.3, prettier@>=3.0.0: +prettier@^3.3.3: version "3.3.3" resolved "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz" integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== @@ -7826,174 +7206,12 @@ pure-rand@^6.0.0: resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz" integrity sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA== -putout@*, putout@^35.0.0, putout@>=15, putout@>=16, putout@>=18, putout@>=20, putout@>=22.5, putout@>=23, putout@>=25, putout@>=26, putout@>=27, putout@>=28, putout@>=29, putout@>=30, putout@>=31, putout@>=32, putout@>=33, putout@>=34, putout@>=35, putout@>=4.31: - version "35.32.0" - dependencies: - "@putout/babel" "^2.0.0" - "@putout/cli-cache" "^3.0.0" - "@putout/cli-choose-formatter" "^4.0.0" - "@putout/cli-keypress" "^2.0.0" - "@putout/cli-match" "^2.0.0" - "@putout/cli-ruler" "^3.0.0" - "@putout/cli-staged" "^1.0.0" - "@putout/cli-validate-args" "^1.0.0" - "@putout/compare" "^14.0.0" - "@putout/engine-loader" "^13.0.0" - "@putout/engine-parser" "^10.0.0" - "@putout/engine-processor" "^11.0.0" - "@putout/engine-reporter" "^1.0.0" - "@putout/engine-runner" "^21.0.0" - "@putout/eslint" "^3.0.0" - "@putout/formatter-codeframe" "^7.0.0" - "@putout/formatter-dump" "^5.0.0" - "@putout/formatter-frame" "^6.0.0" - "@putout/formatter-json" "^2.0.0" - "@putout/formatter-json-lines" "^3.0.0" - "@putout/formatter-memory" "^4.0.0" - "@putout/formatter-progress" "^5.0.0" - "@putout/formatter-progress-bar" "^4.0.0" - "@putout/formatter-stream" "^5.0.0" - "@putout/formatter-time" "^3.0.0" - "@putout/operate" "^12.0.0" - "@putout/operator-add-args" "^8.0.0" - "@putout/operator-declare" "^9.0.0" - "@putout/operator-filesystem" "^4.0.0" - "@putout/operator-ignore" "^1.0.0" - "@putout/operator-json" "^2.0.0" - "@putout/operator-match-files" "^3.0.0" - "@putout/operator-regexp" "^1.0.0" - "@putout/operator-rename-files" "^1.0.0" - "@putout/plugin-apply-at" "^2.0.0" - "@putout/plugin-apply-destructuring" "^7.0.0" - "@putout/plugin-apply-dot-notation" "^2.0.0" - "@putout/plugin-apply-early-return" "^3.0.0" - "@putout/plugin-apply-flat-map" "^2.0.0" - "@putout/plugin-apply-optional-chaining" "^5.0.0" - "@putout/plugin-apply-overrides" "^1.0.0" - "@putout/plugin-apply-starts-with" "^1.0.0" - "@putout/plugin-apply-template-literals" "^3.0.0" - "@putout/plugin-browserlist" "^2.0.0" - "@putout/plugin-conditions" "^4.0.0" - "@putout/plugin-convert-apply-to-spread" "^4.0.0" - "@putout/plugin-convert-arguments-to-rest" "^2.0.0" - "@putout/plugin-convert-array-copy-to-slice" "^3.0.0" - "@putout/plugin-convert-assignment-to-arrow-function" "^1.0.0" - "@putout/plugin-convert-assignment-to-comparison" "^2.0.0" - "@putout/plugin-convert-concat-to-flat" "^1.0.0" - "@putout/plugin-convert-const-to-let" "^1.0.0" - "@putout/plugin-convert-index-of-to-includes" "^2.0.0" - "@putout/plugin-convert-label-to-object" "^1.0.0" - "@putout/plugin-convert-object-assign-to-merge-spread" "^6.0.0" - "@putout/plugin-convert-object-entries-to-array-entries" "^3.0.0" - "@putout/plugin-convert-optional-to-logical" "^3.0.0" - "@putout/plugin-convert-quotes-to-backticks" "^3.0.0" - "@putout/plugin-convert-template-to-string" "^2.0.0" - "@putout/plugin-convert-to-arrow-function" "^4.0.0" - "@putout/plugin-coverage" "^1.0.0" - "@putout/plugin-declare" "^4.0.0" - "@putout/plugin-declare-before-reference" "^4.0.0" - "@putout/plugin-declare-imports-first" "^2.0.0" - "@putout/plugin-eslint" "^8.0.0" - "@putout/plugin-extract-object-properties" "^9.0.0" - "@putout/plugin-extract-sequence-expressions" "^3.0.0" - "@putout/plugin-filesystem" "^5.0.0" - "@putout/plugin-for-of" "^6.0.0" - "@putout/plugin-generators" "^1.0.0" - "@putout/plugin-github" "^12.0.0" - "@putout/plugin-gitignore" "^6.0.0" - "@putout/plugin-logical-expressions" "^6.0.0" - "@putout/plugin-madrun" "^18.0.0" - "@putout/plugin-math" "^2.0.0" - "@putout/plugin-maybe" "^2.0.0" - "@putout/plugin-merge-destructuring-properties" "^8.0.0" - "@putout/plugin-merge-duplicate-functions" "^2.0.0" - "@putout/plugin-merge-duplicate-imports" "^11.0.0" - "@putout/plugin-montag" "^2.0.0" - "@putout/plugin-new" "^3.0.1" - "@putout/plugin-nodejs" "^11.0.0" - "@putout/plugin-npmignore" "^5.0.0" - "@putout/plugin-package-json" "^7.0.0" - "@putout/plugin-promises" "^15.0.0" - "@putout/plugin-putout" "^20.0.0" - "@putout/plugin-putout-config" "^5.0.0" - "@putout/plugin-regexp" "^8.0.0" - "@putout/plugin-remove-console" "^6.0.0" - "@putout/plugin-remove-constant-conditions" "^4.0.0" - "@putout/plugin-remove-debugger" "^7.0.0" - "@putout/plugin-remove-duplicate-case" "^3.0.0" - "@putout/plugin-remove-duplicate-keys" "^5.0.0" - "@putout/plugin-remove-empty" "^12.0.0" - "@putout/plugin-remove-iife" "^4.0.0" - "@putout/plugin-remove-nested-blocks" "^6.0.0" - "@putout/plugin-remove-unreachable-code" "^1.0.0" - "@putout/plugin-remove-unreferenced-variables" "^3.0.0" - "@putout/plugin-remove-unused-expressions" "^8.0.0" - "@putout/plugin-remove-unused-for-of-variables" "^3.0.0" - "@putout/plugin-remove-unused-private-fields" "^2.0.0" - "@putout/plugin-remove-unused-variables" "^9.0.0" - "@putout/plugin-remove-useless-arguments" "^8.0.0" - "@putout/plugin-remove-useless-array" "^1.0.0" - "@putout/plugin-remove-useless-array-constructor" "^2.0.0" - "@putout/plugin-remove-useless-array-entries" "^1.0.0" - "@putout/plugin-remove-useless-assign" "^1.0.0" - "@putout/plugin-remove-useless-constructor" "^1.0.0" - "@putout/plugin-remove-useless-continue" "^2.0.0" - "@putout/plugin-remove-useless-escape" "^6.0.0" - "@putout/plugin-remove-useless-functions" "^3.0.0" - "@putout/plugin-remove-useless-map" "^1.0.0" - "@putout/plugin-remove-useless-operand" "^2.0.0" - "@putout/plugin-remove-useless-replace" "^1.0.1" - "@putout/plugin-remove-useless-return" "^6.0.0" - "@putout/plugin-remove-useless-spread" "^11.0.0" - "@putout/plugin-remove-useless-template-expressions" "^2.0.0" - "@putout/plugin-remove-useless-variables" "^11.0.0" - "@putout/plugin-reuse-duplicate-init" "^5.0.0" - "@putout/plugin-simplify-assignment" "^3.0.0" - "@putout/plugin-simplify-boolean-return" "^1.0.0" - "@putout/plugin-simplify-ternary" "^7.0.0" - "@putout/plugin-sort-imports-by-specifiers" "^1.0.0" - "@putout/plugin-split-assignment-expressions" "^1.0.0" - "@putout/plugin-split-nested-destructuring" "^3.0.0" - "@putout/plugin-split-variable-declarations" "^3.0.0" - "@putout/plugin-tape" "^14.0.0" - "@putout/plugin-try-catch" "^3.0.0" - "@putout/plugin-types" "^4.0.0" - "@putout/plugin-typescript" "^7.0.0" - "@putout/plugin-webpack" "^3.0.0" - "@putout/processor-css" "^9.0.0" - "@putout/processor-filesystem" "^4.0.0" - "@putout/processor-ignore" "^6.0.0" - "@putout/processor-javascript" "^5.0.0" - "@putout/processor-json" "^9.0.0" - "@putout/processor-markdown" "^12.0.0" - "@putout/processor-yaml" "^8.0.0" - "@putout/traverse" "^10.0.0" - ajv "^8.8.2" - chalk "^5.3.0" - ci-info "^4.0.0" - debug "^4.1.1" - deepmerge "^4.0.0" - escalade "^3.1.1" - fast-glob "^3.2.2" - find-up "^7.0.0" - fullstore "^3.0.0" - ignore "^5.0.4" - is-relative "^1.0.0" - nano-memoize "^3.0.11" - once "^1.4.0" - picomatch "^4.0.1" - samadhi "^2.0.0" - try-catch "^3.0.0" - try-to-catch "^3.0.0" - wraptile "^3.0.0" - yargs-parser "^21.0.0" - queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -react-dom@*, "react-dom@^16 || ^17 || ^18", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", react-dom@^18.2.0, react-dom@^18.3.1, "react-dom@>= 16.8.0 < 19.0.0", react-dom@>=16.0.0, react-dom@>=16.13.1, "react-dom@>=16.x <=18.x": +react-dom@^18.3.1: version "18.3.1" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz" integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== @@ -8027,7 +7245,7 @@ react-markdown@^9.0.1: unist-util-visit "^5.0.0" vfile "^6.0.0" -react@*, "react@^16 || ^17 || ^18", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^18 || ^19", react@^18.2.0, react@^18.3.1, "react@>= 16.8.0 < 19.0.0", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", react@>=16, react@>=16.0.0, react@>=16.13.1, "react@>=16.x <=18.x", react@>=18: +react@^18.3.1: version "18.3.1" resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz" integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== @@ -8039,233 +7257,88 @@ read-cache@^1.0.0: resolved "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz" integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== dependencies: - pify "^2.3.0" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -reading-time@^1.3.0: - version "1.5.0" - resolved "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz" - integrity sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg== - -reflect.getprototypeof@^1.0.4: - version "1.0.5" - resolved "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.5.tgz" - integrity sha512-62wgfC8dJWrmxv44CA36pLDnP6KKl3Vhxb7PL+8+qrrFMMoJij4vgiMP8zV4O8+CBMXY1mHxI5fITGHXFHVmQQ== - dependencies: - call-bind "^1.0.5" - define-properties "^1.2.1" - es-abstract "^1.22.3" - es-errors "^1.0.0" - get-intrinsic "^1.2.3" - globalthis "^1.0.3" - which-builtin-type "^1.1.3" - -regenerator-runtime@^0.14.0: - version "0.14.1" - resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz" - integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== - -regexp-tree@^0.1.21, regexp-tree@^0.1.24: - version "0.1.27" - -regexp.prototype.flags@^1.5.0, regexp.prototype.flags@^1.5.2: - version "1.5.2" - resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz" - integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== - dependencies: - call-bind "^1.0.6" - define-properties "^1.2.1" - es-errors "^1.3.0" - set-function-name "^2.0.1" - -rehype-katex@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.0.tgz" - integrity sha512-h8FPkGE00r2XKU+/acgqwWUlyzve1IiOKwsEkg4pDL3k48PiE0Pt+/uLtVHDVkN1yA4iurZN6UES8ivHVEQV6Q== - dependencies: - "@types/hast" "^3.0.0" - "@types/katex" "^0.16.0" - hast-util-from-html-isomorphic "^2.0.0" - hast-util-to-text "^4.0.0" - katex "^0.16.0" - unist-util-visit-parents "^6.0.0" - vfile "^6.0.0" - -rehype-pretty-code@0.9.11: - version "0.9.11" - resolved "https://registry.npmjs.org/rehype-pretty-code/-/rehype-pretty-code-0.9.11.tgz" - integrity sha512-Eq90eCYXQJISktfRZ8PPtwc5SUyH6fJcxS8XOMnHPUQZBtC6RYo67gGlley9X2nR8vlniPj0/7oCDEYHKQa/oA== - dependencies: - "@types/hast" "^2.0.0" - hash-obj "^4.0.0" - parse-numeric-range "^1.3.0" - -rehype-raw@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz" - integrity sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww== - dependencies: - "@types/hast" "^3.0.0" - hast-util-raw "^9.0.0" - vfile "^6.0.0" - -remark-gfm@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz" - integrity sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig== - dependencies: - "@types/mdast" "^3.0.0" - mdast-util-gfm "^2.0.0" - micromark-extension-gfm "^2.0.0" - unified "^10.0.0" - -remark-lint-blockquote-indentation@^4.0.0: - version "4.0.0" - dependencies: - "@types/mdast" "^4.0.0" - mdast-util-phrasing "^4.0.0" - pluralize "^8.0.0" - unified-lint-rule "^3.0.0" - unist-util-position "^5.0.0" - unist-util-visit-parents "^6.0.0" - -remark-lint-checkbox-character-style@^5.0.0: - version "5.0.0" - dependencies: - "@types/mdast" "^4.0.0" - mdast-util-phrasing "^4.0.0" - unified-lint-rule "^3.0.0" - unist-util-position "^5.0.0" - unist-util-visit-parents "^6.0.0" - vfile-message "^4.0.0" - -remark-lint-code-block-style@^4.0.0: - version "4.0.0" - dependencies: - "@types/mdast" "^4.0.0" - mdast-util-phrasing "^4.0.0" - unified-lint-rule "^3.0.0" - unist-util-position "^5.0.0" - unist-util-visit-parents "^6.0.0" - vfile-message "^4.0.0" - -remark-lint-emphasis-marker@^4.0.0: - version "4.0.0" - dependencies: - "@types/mdast" "^4.0.0" - unified-lint-rule "^3.0.0" - unist-util-position "^5.0.0" - unist-util-visit-parents "^6.0.0" - vfile-message "^4.0.0" - -remark-lint-fenced-code-marker@^4.0.0: - version "4.0.0" - dependencies: - "@types/mdast" "^4.0.0" - mdast-util-phrasing "^4.0.0" - unified-lint-rule "^3.0.0" - unist-util-position "^5.0.0" - unist-util-visit-parents "^6.0.0" - vfile-message "^4.0.0" - -remark-lint-heading-style@^4.0.0: - version "4.0.0" - dependencies: - "@types/mdast" "^4.0.0" - mdast-util-heading-style "^3.0.0" - mdast-util-phrasing "^4.0.0" - unified-lint-rule "^3.0.0" - unist-util-position "^5.0.0" - unist-util-visit-parents "^6.0.0" - vfile-message "^4.0.0" - -remark-lint-link-title-style@^4.0.0: - version "4.0.0" - dependencies: - "@types/mdast" "^4.0.0" - unified-lint-rule "^3.0.0" - unist-util-position "^5.0.0" - unist-util-visit-parents "^6.0.0" - vfile-message "^4.0.0" + pify "^2.3.0" -remark-lint-list-item-content-indent@^4.0.0: - version "4.0.0" +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== dependencies: - "@types/mdast" "^4.0.0" - mdast-util-phrasing "^4.0.0" - pluralize "^8.0.0" - unified-lint-rule "^3.0.0" - unist-util-position "^5.0.0" - unist-util-visit-parents "^6.0.0" - vfile-message "^4.0.0" + picomatch "^2.2.1" -remark-lint-ordered-list-marker-style@^4.0.0: - version "4.0.0" +reading-time@^1.3.0: + version "1.5.0" + resolved "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz" + integrity sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg== + +reflect.getprototypeof@^1.0.4: + version "1.0.5" + resolved "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.5.tgz" + integrity sha512-62wgfC8dJWrmxv44CA36pLDnP6KKl3Vhxb7PL+8+qrrFMMoJij4vgiMP8zV4O8+CBMXY1mHxI5fITGHXFHVmQQ== dependencies: - "@types/mdast" "^4.0.0" - mdast-util-phrasing "^4.0.0" - micromark-util-character "^2.0.0" - unified-lint-rule "^3.0.0" - unist-util-position "^5.0.0" - unist-util-visit-parents "^6.0.0" - vfile-message "^4.0.0" + call-bind "^1.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.3" + es-errors "^1.0.0" + get-intrinsic "^1.2.3" + globalthis "^1.0.3" + which-builtin-type "^1.1.3" -remark-lint-ordered-list-marker-value@^4.0.0: - version "4.0.0" +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + +regexp.prototype.flags@^1.5.0, regexp.prototype.flags@^1.5.2: + version "1.5.2" + resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz" + integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== dependencies: - "@types/mdast" "^4.0.0" - devlop "^1.0.0" - mdast-util-phrasing "^4.0.0" - micromark-util-character "^2.0.0" - unified-lint-rule "^3.0.0" - unist-util-position "^5.0.0" - unist-util-visit-parents "^6.0.0" - vfile-message "^4.0.0" + call-bind "^1.0.6" + define-properties "^1.2.1" + es-errors "^1.3.0" + set-function-name "^2.0.1" -remark-lint-rule-style@^4.0.0: - version "4.0.0" +rehype-katex@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.0.tgz" + integrity sha512-h8FPkGE00r2XKU+/acgqwWUlyzve1IiOKwsEkg4pDL3k48PiE0Pt+/uLtVHDVkN1yA4iurZN6UES8ivHVEQV6Q== dependencies: - "@types/mdast" "^4.0.0" - mdast-util-phrasing "^4.0.0" - unified-lint-rule "^3.0.0" - unist-util-position "^5.0.0" + "@types/hast" "^3.0.0" + "@types/katex" "^0.16.0" + hast-util-from-html-isomorphic "^2.0.0" + hast-util-to-text "^4.0.0" + katex "^0.16.0" unist-util-visit-parents "^6.0.0" - vfile-message "^4.0.0" + vfile "^6.0.0" -remark-lint-strong-marker@^4.0.0: - version "4.0.0" +rehype-pretty-code@0.9.11: + version "0.9.11" + resolved "https://registry.npmjs.org/rehype-pretty-code/-/rehype-pretty-code-0.9.11.tgz" + integrity sha512-Eq90eCYXQJISktfRZ8PPtwc5SUyH6fJcxS8XOMnHPUQZBtC6RYo67gGlley9X2nR8vlniPj0/7oCDEYHKQa/oA== dependencies: - "@types/mdast" "^4.0.0" - unified-lint-rule "^3.0.0" - unist-util-position "^5.0.0" - unist-util-visit-parents "^6.0.0" - vfile-message "^4.0.0" + "@types/hast" "^2.0.0" + hash-obj "^4.0.0" + parse-numeric-range "^1.3.0" -remark-lint-table-cell-padding@^5.0.0: - version "5.0.0" +rehype-raw@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz" + integrity sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww== dependencies: - "@types/mdast" "^4.0.0" - "@types/unist" "^3.0.0" - devlop "^1.0.0" - mdast-util-phrasing "^4.0.0" - pluralize "^8.0.0" - unified-lint-rule "^3.0.0" - unist-util-position "^5.0.0" - unist-util-visit-parents "^6.0.0" - vfile-message "^4.0.0" + "@types/hast" "^3.0.0" + hast-util-raw "^9.0.0" + vfile "^6.0.0" -remark-lint@^10.0.0: - version "10.0.0" +remark-gfm@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz" + integrity sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig== dependencies: - "@types/mdast" "^4.0.0" - remark-message-control "^8.0.0" - unified "^11.0.0" + "@types/mdast" "^3.0.0" + mdast-util-gfm "^2.0.0" + micromark-extension-gfm "^2.0.0" + unified "^10.0.0" remark-math@^5.1.1: version "5.1.1" @@ -8285,14 +7358,6 @@ remark-mdx@^2.0.0: mdast-util-mdx "^2.0.0" micromark-extension-mdxjs "^1.0.0" -remark-message-control@^8.0.0: - version "8.0.0" - dependencies: - "@types/mdast" "^4.0.0" - mdast-comment-marker "^3.0.0" - unified-message-control "^5.0.0" - vfile "^6.0.0" - remark-parse@^10.0.0: version "10.0.2" resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz" @@ -8312,25 +7377,6 @@ remark-parse@^11.0.0: micromark-util-types "^2.0.0" unified "^11.0.0" -remark-preset-lint-consistent@^6.0.0: - version "6.0.0" - dependencies: - remark-lint "^10.0.0" - remark-lint-blockquote-indentation "^4.0.0" - remark-lint-checkbox-character-style "^5.0.0" - remark-lint-code-block-style "^4.0.0" - remark-lint-emphasis-marker "^4.0.0" - remark-lint-fenced-code-marker "^4.0.0" - remark-lint-heading-style "^4.0.0" - remark-lint-link-title-style "^4.0.0" - remark-lint-list-item-content-indent "^4.0.0" - remark-lint-ordered-list-marker-style "^4.0.0" - remark-lint-ordered-list-marker-value "^4.0.0" - remark-lint-rule-style "^4.0.0" - remark-lint-strong-marker "^4.0.0" - remark-lint-table-cell-padding "^5.0.0" - unified "^11.0.0" - remark-reading-time@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/remark-reading-time/-/remark-reading-time-2.0.1.tgz" @@ -8362,13 +7408,6 @@ remark-rehype@^11.0.0: unified "^11.0.0" vfile "^6.0.0" -remark-stringify@^11.0.0: - version "11.0.0" - dependencies: - "@types/mdast" "^4.0.0" - mdast-util-to-markdown "^2.0.0" - unified "^11.0.0" - remove-accents@0.5.0: version "0.5.0" resolved "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz" @@ -8389,9 +7428,6 @@ require-directory@^2.1.1: resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== -require-from-string@^2.0.2: - version "2.0.2" - resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz" @@ -8510,14 +7546,6 @@ safe-regex-test@^1.0.3: resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -samadhi@^2.0.0: - version "2.2.0" - dependencies: - "@putout/quick-lint" "^1.2.0" - goldstein "^5.11.0" - js-tokens "^9.0.0" - try-catch "^3.0.1" - scheduler@^0.23.2: version "0.23.2" resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz" @@ -8532,9 +7560,6 @@ scroll-into-view-if-needed@^3.1.0: dependencies: compute-scroll-into-view "^3.0.2" -"search-insights@>= 1 < 3": - version "2.14.0" - section-matter@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz" @@ -8543,12 +7568,7 @@ section-matter@^1.0.0: extend-shallow "^2.0.1" kind-of "^6.0.0" -semver@^6.3.0: - version "6.3.1" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^6.3.1: +semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== @@ -8639,7 +7659,7 @@ shebang-regex@^3.0.0: resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shiki@*, shiki@^0.14.3: +shiki@^0.14.3: version "0.14.7" resolved "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz" integrity sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg== @@ -8664,15 +7684,20 @@ signal-exit@^3.0.0, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -signal-exit@^4.0.1: +signal-exit@^4.0.1, signal-exit@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== -signal-exit@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== +simple-oauth2@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/simple-oauth2/-/simple-oauth2-5.1.0.tgz#1398fe2b8f4b4066298d63c155501b31b42238f2" + integrity sha512-gWDa38Ccm4MwlG5U7AlcJxPv3lvr80dU7ARJWrGdgvOKyzSj1gr3GBPN1rABTedAYvC/LsGYoFuFxwDBPtGEbw== + dependencies: + "@hapi/hoek" "^11.0.4" + "@hapi/wreck" "^18.0.0" + debug "^4.3.4" + joi "^17.6.4" simple-swizzle@^0.2.2: version "0.2.2" @@ -8691,13 +7716,6 @@ slash@^3.0.0: resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -slice-ansi@^4.0.0: - version "4.0.0" - dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" - slice-ansi@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz" @@ -8721,7 +7739,7 @@ sort-keys@^5.0.0: dependencies: is-plain-obj "^4.0.0" -source-map-js@^1.0.1, source-map-js@^1.0.2, source-map-js@^1.2.0: +source-map-js@^1.0.2, source-map-js@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz" integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== @@ -8779,16 +7797,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -8865,28 +7874,14 @@ stringify-entities@^4.0.0: character-entities-html4 "^2.0.0" character-entities-legacy "^3.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^7.0.1: - version "7.1.0" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz" - integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== - dependencies: - ansi-regex "^6.0.1" - -strip-ansi@^7.1.0: +strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz" integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== @@ -8949,62 +7944,6 @@ styled-jsx@5.1.1: dependencies: client-only "0.0.1" -stylelint-config-recommended@^14.0.0: - version "14.0.0" - -stylelint-config-standard@^36.0.0: - version "36.0.0" - dependencies: - stylelint-config-recommended "^14.0.0" - -stylelint-prettier@^5.0.0: - version "5.0.0" - dependencies: - prettier-linter-helpers "^1.0.0" - -stylelint@^16.0.0, stylelint@^16.0.1, stylelint@^16.1.0, stylelint@>=16.0.0: - version "16.6.1" - dependencies: - "@csstools/css-parser-algorithms" "^2.6.3" - "@csstools/css-tokenizer" "^2.3.1" - "@csstools/media-query-list-parser" "^2.1.11" - "@csstools/selector-specificity" "^3.1.1" - "@dual-bundle/import-meta-resolve" "^4.1.0" - balanced-match "^2.0.0" - colord "^2.9.3" - cosmiconfig "^9.0.0" - css-functions-list "^3.2.2" - css-tree "^2.3.1" - debug "^4.3.4" - fast-glob "^3.3.2" - fastest-levenshtein "^1.0.16" - file-entry-cache "^9.0.0" - global-modules "^2.0.0" - globby "^11.1.0" - globjoin "^0.1.4" - html-tags "^3.3.1" - ignore "^5.3.1" - imurmurhash "^0.1.4" - is-plain-object "^5.0.0" - known-css-properties "^0.31.0" - mathml-tag-names "^2.1.3" - meow "^13.2.0" - micromatch "^4.0.7" - normalize-path "^3.0.0" - picocolors "^1.0.1" - postcss "^8.4.38" - postcss-resolve-nested-selector "^0.1.1" - postcss-safe-parser "^7.0.0" - postcss-selector-parser "^6.1.0" - postcss-value-parser "^4.2.0" - resolve-from "^5.0.0" - string-width "^4.2.3" - strip-ansi "^7.1.0" - supports-hyperlinks "^3.0.0" - svg-tags "^1.0.0" - table "^6.8.2" - write-file-atomic "^5.0.1" - stylis@^4.1.3: version "4.3.1" resolved "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz" @@ -9037,7 +7976,7 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^7.0.0, supports-color@^7.1.0: +supports-color@^7.1.0: version "7.2.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -9051,20 +7990,11 @@ supports-color@^8.0.0: dependencies: has-flag "^4.0.0" -supports-hyperlinks@^3.0.0: - version "3.0.0" - dependencies: - has-flag "^4.0.0" - supports-color "^7.0.0" - supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -svg-tags@^1.0.0: - version "1.0.0" - synckit@^0.6.0: version "0.6.2" resolved "https://registry.npmjs.org/synckit/-/synckit-0.6.2.tgz" @@ -9080,15 +8010,6 @@ synckit@^0.9.0: "@pkgr/core" "^0.1.0" tslib "^2.6.2" -table@^6.0.1, table@^6.8.2: - version "6.8.2" - dependencies: - ajv "^8.0.1" - lodash.truncate "^4.4.2" - slice-ansi "^4.0.0" - string-width "^4.2.3" - strip-ansi "^6.0.1" - tailwindcss@^3.4.7: version "3.4.7" resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.7.tgz" @@ -9150,9 +8071,6 @@ thenify-all@^1.0.0: dependencies: any-promise "^1.0.0" -timer-node@^5.0.7: - version "5.0.7" - title@^3.5.3: version "3.5.3" resolved "https://registry.npmjs.org/title/-/title-3.5.3.tgz" @@ -9200,12 +8118,12 @@ trough@^2.0.0: resolved "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz" integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw== -try-catch@^3.0.0, try-catch@^3.0.1: +try-catch@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/try-catch/-/try-catch-3.0.1.tgz" integrity sha512-91yfXw1rr/P6oLpHSyHDOHm0vloVvUoo9FVdw8YwY05QjJQG9OT0LUxe2VRAzmHG+0CUOmI3nhxDUMLxDN/NEQ== -try-to-catch@^3.0.0, try-to-catch@^3.0.1: +try-to-catch@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/try-to-catch/-/try-to-catch-3.0.1.tgz" integrity sha512-hOY83V84Hx/1sCzDSaJA+Xz2IIQOHRvjxzt+F0OjbQGPZ6yLPLArMA0gw/484MlfUkQbCpKYMLX3VDCAjWKfzQ== @@ -9327,7 +8245,7 @@ typescript-eslint@^7.18.0: "@typescript-eslint/parser" "7.18.0" "@typescript-eslint/utils" "7.18.0" -typescript@^5.0.4, typescript@^5.5.4, "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta", typescript@>=3.3.1, typescript@>=4.2.0, typescript@>=4.9.5: +typescript@^5.0.4, typescript@^5.5.4: version "5.5.4" resolved "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz" integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== @@ -9342,9 +8260,6 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" -unc-path-regex@^0.1.2: - version "0.1.2" - undici-types@~5.26.4: version "5.26.5" resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz" @@ -9355,26 +8270,6 @@ unicorn-magic@^0.1.0: resolved "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz" integrity sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ== -unified-lint-rule@^3.0.0: - version "3.0.0" - dependencies: - "@types/unist" "^3.0.0" - trough "^2.0.0" - unified "^11.0.0" - vfile "^6.0.0" - -unified-message-control@^5.0.0: - version "5.0.0" - dependencies: - "@types/unist" "^3.0.0" - devlop "^1.0.0" - space-separated-tokens "^2.0.0" - unist-util-is "^6.0.0" - unist-util-visit "^5.0.0" - vfile "^6.0.0" - vfile-location "^5.0.0" - vfile-message "^4.0.0" - unified@^10.0.0: version "10.1.2" resolved "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz" @@ -9401,17 +8296,6 @@ unified@^11.0.0: trough "^2.0.0" vfile "^6.0.0" -unified@^11.0.3: - version "11.0.4" - dependencies: - "@types/unist" "^3.0.0" - bail "^2.0.0" - devlop "^1.0.0" - extend "^3.0.0" - is-plain-obj "^4.0.0" - trough "^2.0.0" - vfile "^6.0.0" - unist-util-find-after@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz" @@ -9507,15 +8391,7 @@ unist-util-visit-parents@^4.0.0: "@types/unist" "^2.0.0" unist-util-is "^5.0.0" -unist-util-visit-parents@^5.0.0: - version "5.1.3" - resolved "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz" - integrity sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg== - dependencies: - "@types/unist" "^2.0.0" - unist-util-is "^5.0.0" - -unist-util-visit-parents@^5.1.1: +unist-util-visit-parents@^5.0.0, unist-util-visit-parents@^5.1.1: version "5.1.3" resolved "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz" integrity sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg== @@ -9566,7 +8442,7 @@ update-browserslist-db@^1.0.13: escalade "^3.1.1" picocolors "^1.0.0" -uri-js@^4.2.2, uri-js@^4.4.1: +uri-js@^4.2.2: version "4.4.1" resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== @@ -9646,17 +8522,7 @@ vfile-message@^4.0.0: "@types/unist" "^3.0.0" unist-util-stringify-position "^4.0.0" -vfile@^5.0.0: - version "5.3.7" - resolved "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz" - integrity sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g== - dependencies: - "@types/unist" "^2.0.0" - is-buffer "^2.0.0" - unist-util-stringify-position "^3.0.0" - vfile-message "^3.0.0" - -vfile@^5.3.0: +vfile@^5.0.0, vfile@^5.3.0: version "5.3.7" resolved "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz" integrity sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g== @@ -9759,11 +8625,6 @@ which@^1.2.9: dependencies: isexe "^2.0.0" -which@^1.3.1: - version "1.3.1" - dependencies: - isexe "^2.0.0" - which@^2.0.1: version "2.0.2" resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" @@ -9771,16 +8632,7 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -9812,9 +8664,6 @@ wrappy@1: resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -wraptile@^3.0.0: - version "3.0.0" - write-file-atomic@^4.0.2: version "4.0.2" resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz" @@ -9823,12 +8672,6 @@ write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" -write-file-atomic@^5.0.1: - version "5.0.1" - dependencies: - imurmurhash "^0.1.4" - signal-exit "^4.0.1" - y18n@^5.0.5: version "5.0.8" resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" @@ -9849,12 +8692,12 @@ yallist@^4.0.0: resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^2.2.2, yaml@^2.3.4, yaml@~2.4.2: +yaml@^2.3.4, yaml@~2.4.2: version "2.4.5" resolved "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz" integrity sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg== -yargs-parser@^21.0.0, yargs-parser@^21.1.1: +yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index 60ce88d46d8b1..0000000000000 --- a/docs/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -yarn-error.log -node_modules -.DS_Store -docs/.vuepress/dist diff --git a/docs/.tool-versions b/docs/.tool-versions deleted file mode 100644 index eedff58c83cc6..0000000000000 --- a/docs/.tool-versions +++ /dev/null @@ -1 +0,0 @@ -nodejs 16.16.0 diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index aacbc08ddb26c..0000000000000 --- a/docs/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# Pipedream Docs - -## Modifying docs and testing locally - -First, install the dependencies for the repo: - -```bash -yarn install -``` - -Then, run the Vuepress app locally: - -```bash -yarn docs:dev -``` - -This should run a local development server on `http://localhost:8080/`. When you make changes to the Markdown files in the repo, the app should hot reload and refresh the browser automatically. - -And that's it! You're ready to develop Pipedream documentation 🏄 - -## The Pull Request process - -When contributing new code to this repo, please do so on a new Git branch, and submit a pull request to merge changes on that branch into `master`. - -A member of the Pipedream team will automatically be notified when you open a pull request. You can also reach out to us on [our community](https://pipedream.com/community/c/dev/11) or [Slack](https://pipedream.com/support). - -### Spellchecking - -A spellchecker is automatically run on Markdown files in PRs. If you see this check fail when you open a PR, check the output for misspelled words: - -```text -Misspelled words: - README.md: html>body>p --------------------------------------------------------------------------------- -lkjsdflkjsdflkjsdflk --------------------------------------------------------------------------------- - -!!!Spelling check failed!!! -Files in repository contain spelling errors -``` - -Some technical words (like Pipedream or JavaScript) aren't in an English dictionary. If the spellchecker fails on a real word, please add it to the `.wordlist.txt` file. - -The spellchecker configuration can be found in `.spellcheck.yml`. The spellchecking is handled by [this GitHub action](https://github.com/rojopolis/spellcheck-github-actions), which uses [PySpelling](https://facelessuser.github.io/pyspelling/). diff --git a/docs/docs/.vuepress/components/AlphaFeatureNotice.vue b/docs/docs/.vuepress/components/AlphaFeatureNotice.vue deleted file mode 100644 index 79c9c6603365f..0000000000000 --- a/docs/docs/.vuepress/components/AlphaFeatureNotice.vue +++ /dev/null @@ -1,31 +0,0 @@ - - - - - diff --git a/docs/docs/.vuepress/components/BetaFeatureNotice.vue b/docs/docs/.vuepress/components/BetaFeatureNotice.vue deleted file mode 100644 index a394518b39a91..0000000000000 --- a/docs/docs/.vuepress/components/BetaFeatureNotice.vue +++ /dev/null @@ -1,31 +0,0 @@ - - - - - diff --git a/docs/docs/.vuepress/components/Footer.vue b/docs/docs/.vuepress/components/Footer.vue deleted file mode 100644 index 96530a2a235d3..0000000000000 --- a/docs/docs/.vuepress/components/Footer.vue +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/docs/docs/.vuepress/components/GuideLink.vue b/docs/docs/.vuepress/components/GuideLink.vue deleted file mode 100644 index 22870a58bd47a..0000000000000 --- a/docs/docs/.vuepress/components/GuideLink.vue +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - diff --git a/docs/docs/.vuepress/components/LanguageLink.vue b/docs/docs/.vuepress/components/LanguageLink.vue deleted file mode 100644 index bec36d76156ca..0000000000000 --- a/docs/docs/.vuepress/components/LanguageLink.vue +++ /dev/null @@ -1,17 +0,0 @@ - - diff --git a/docs/docs/.vuepress/components/PythonMappings.vue b/docs/docs/.vuepress/components/PythonMappings.vue deleted file mode 100644 index eb29e15d9cc5e..0000000000000 --- a/docs/docs/.vuepress/components/PythonMappings.vue +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - diff --git a/docs/docs/.vuepress/components/VideoPlayer.vue b/docs/docs/.vuepress/components/VideoPlayer.vue deleted file mode 100644 index 507ba84ac5bcb..0000000000000 --- a/docs/docs/.vuepress/components/VideoPlayer.vue +++ /dev/null @@ -1,37 +0,0 @@ - - - - - diff --git a/docs/docs/.vuepress/components/python-mappings.json b/docs/docs/.vuepress/components/python-mappings.json deleted file mode 100644 index 0c8f9591ebc0d..0000000000000 --- a/docs/docs/.vuepress/components/python-mappings.json +++ /dev/null @@ -1,1078 +0,0 @@ -{ - "ShopifyAPI": "shopify", - "google-cloud-bigquery": "bigquery", - "carbon3d-client": "carbon3d", - "python-telegram-bot": "telegram", - "pyAFQ": "AFQ", - "agpy": "AG_fft_tools", - "pexpect": "screen", - "Adafruit_Libraries": "Adafruit", - "Zope2": "webdav", - "py_Asterisk": "Asterisk", - "bitbucket_jekyll_hook": "BB_jekyll_hook", - "Banzai_NGS": "Banzai", - "BeautifulSoup": "BeautifulSoupTests", - "biopython": "BioSQL", - "BuildbotEightStatusShields": "BuildbotStatusShields", - "ExtensionClass": "MethodObject", - "pycryptodome": "Crypto", - "pycryptodomex": "Cryptodome", - "51degrees_mobile_detector_v3_wrapper": "FiftyOneDegrees", - "pyfunctional": "functional", - "GeoBasesDev": "GeoBases", - "ipython": "IPython", - "astro_kittens": "Kittens", - "python_Levenshtein": "Levenshtein", - "MySQL-python": "MySQLdb", - "PyOpenGL": "OpenGL", - "pyOpenSSL": "OpenSSL", - "Pillow": "PIL", - "astLib": "PyWCSTools", - "astro_pyxis": "Pyxides", - "PySide": "pysideuic", - "s3cmd": "S3", - "pystick": "SCons", - "PyStemmer": "Stemmer", - "topzootools": "TopZooTools", - "DocumentTemplate": "TreeDisplay", - "aspose_pdf_java_for_python": "WorkingWithDocumentConversion", - "auto_adjust_display_brightness": "aadb", - "abakaffe_cli": "abakaffe", - "abiosgaming.py": "abiosgaming", - "abiquo_api": "abiquo", - "abl.cssprocessor": "abl", - "abl.robot": "abl", - "abl.util": "abl", - "abl.vpath": "abl", - "abo_generator": "abo", - "abris": "abris_transform", - "abstract.jwrotator": "abstract", - "abu.admin": "abu", - "AC_Flask_HipChat": "ac_flask", - "anikom15": "acg", - "acme.dchat": "acme", - "acme.hello": "acme", - "acted.projects": "acted", - "ActionServer": "action", - "actionbar.panel": "actionbar", - "afn": "activehomed", - "ActivePapers.Py": "activepapers", - "address_book_lansry": "address_book", - "adi.commons": "adi", - "adi.devgen": "adi", - "adi.fullscreen": "adi", - "adi.init": "adi", - "adi.playlist": "adi", - "adi.samplecontent": "adi", - "adi.slickstyle": "adi", - "adi.suite": "adi", - "adi.trash": "adi", - "aDict2": "adict", - "aditam.agent": "aditam", - "aditam.core": "aditam", - "adium_sh": "adiumsh", - "AdjectorClient": "adjector", - "AdjectorTracPlugin": "adjector", - "Banner_Ad_Toolkit": "adkit", - "django_admin_tools": "admin_tools", - "adminish_categories": "adminishcategories", - "django_admin_sortable": "adminsortable", - "adspygoogle.adwords": "adspygoogle", - "agtl": "advancedcaching", - "Adytum_PyMonitor": "adytum", - "affinitic.docpyflakes": "affinitic", - "affinitic.recipe.fakezope2eggs": "affinitic", - "affinitic.simplecookiecuttr": "affinitic", - "affinitic.verifyinterface": "affinitic", - "affinitic.zamqp": "affinitic", - "afpy.xap": "afpy", - "agate_sql": "agatesql", - "ageliaco.recipe.csvconfig": "ageliaco", - "agent.http": "agent_http", - "Agora_Client": "agora", - "Agora_Fountain": "agora", - "Agora_Fragment": "agora", - "Agora_Planner": "agora", - "Agora_Service_Provider": "agora", - "agoraplex.themes.sphinx": "agoraplex", - "agsci.blognewsletter": "agsci", - "agx.core": "agx", - "agx.dev": "agx", - "agx.generator.buildout": "agx", - "agx.generator.dexterity": "agx", - "agx.generator.generator": "agx", - "agx.generator.plone": "agx", - "agx.generator.pyegg": "agx", - "agx.generator.sql": "agx", - "agx.generator.uml": "agx", - "agx.generator.zca": "agx", - "agx.transform.uml2fs": "agx", - "agx.transform.xmi2uml": "agx", - "aimes.bundle": "aimes", - "aimes.skeleton": "aimes", - "aio.app": "aio", - "aio.config": "aio", - "aio.core": "aio", - "aio.signals": "aio", - "aio_hs2": "aiohs2", - "aio_routes": "aioroutes", - "aio_s3": "aios3", - "airbrake_flask": "airbrake", - "airship_icloud": "airship", - "airship_steamcloud": "airship", - "edgegrid_python": "akamai", - "alation_api": "alation", - "alba_client_python": "alba_client", - "alburnum_maas_client": "alburnum", - "alchemist.audit": "alchemist", - "alchemist.security": "alchemist", - "alchemist.traversal": "alchemist", - "alchemist.ui": "alchemist", - "alchemyapi_python": "alchemyapi", - "alerta_server": "alerta", - "Alexandria_Upload_Utils": "alexandria_upload", - "alibaba_python_sdk": "alibaba", - "aliyun_python_sdk": "aliyun", - "alicloudcli": "aliyuncli", - "aliyun_python_sdk_acs": "aliyunsdkacs", - "aliyun_python_sdk_batchcompute": "aliyunsdkbatchcompute", - "aliyun_python_sdk_bsn": "aliyunsdkbsn", - "aliyun_python_sdk_bss": "aliyunsdkbss", - "aliyun_python_sdk_cdn": "aliyunsdkcdn", - "aliyun_python_sdk_cms": "aliyunsdkcms", - "aliyun_python_sdk_core": "aliyunsdkcore", - "aliyun_python_sdk_crm": "aliyunsdkcrm", - "aliyun_python_sdk_cs": "aliyunsdkcs", - "aliyun_python_sdk_drds": "aliyunsdkdrds", - "aliyun_python_sdk_ecs": "aliyunsdkecs", - "aliyun_python_sdk_ess": "aliyunsdkess", - "aliyun_python_sdk_ft": "aliyunsdkft", - "aliyun_python_sdk_mts": "aliyunsdkmts", - "aliyun_python_sdk_ocs": "aliyunsdkocs", - "aliyun_python_sdk_oms": "aliyunsdkoms", - "aliyun_python_sdk_ossadmin": "aliyunsdkossadmin", - "aliyun_python_sdk_r_kvstore": "aliyunsdkr-kvstore", - "aliyun_python_sdk_ram": "aliyunsdkram", - "aliyun_python_sdk_rds": "aliyunsdkrds", - "aliyun_python_sdk_risk": "aliyunsdkrisk", - "aliyun_python_sdk_ros": "aliyunsdkros", - "aliyun_python_sdk_slb": "aliyunsdkslb", - "aliyun_python_sdk_sts": "aliyunsdksts", - "aliyun_python_sdk_ubsms": "aliyunsdkubsms", - "aliyun_python_sdk_yundun": "aliyunsdkyundun", - "AllAttachmentsMacro": "allattachments", - "allocine_wrapper": "allocine", - "django_allowedsites": "allowedsites", - "alm.solrindex": "alm", - "aloft.py": "aloft", - "alpaca": "alpacalib", - "alphabetic_simple": "alphabetic", - "alphasms_client": "alphasms", - "altered.states": "altered", - "alterootheme.busycity": "alterootheme", - "alterootheme.intensesimplicity": "alterootheme", - "alterootheme.lazydays": "alterootheme", - "alurinium_image_processing": "alurinium", - "alx": "alxlib", - "amara3_iri": "amara3", - "amara3_xml": "amara3", - "AmazonAPIWrapper": "amazon", - "python_amazon_simple_product_api": "amazon", - "ambikesh1349_1": "ambikesh1349-1", - "AmbilightParty": "ambilight", - "amifs_core": "amifs", - "ami_organizer": "amiorganizer", - "amitu.lipy": "amitu", - "amitu_putils": "amitu", - "amitu_websocket_client": "amitu", - "amitu_zutils": "amitu", - "AMLT_learn": "amltlearn", - "amocrm_api": "amocrm", - "amqp_dispatcher": "amqpdispatcher", - "AMQP_Storm": "amqpstorm", - "analytics_python": "analytics", - "AnalyzeDirectory": "analyzedir", - "ancientsolutions_crypttools": "ancientsolutions", - "anderson.paginator": "anderson_paginator", - "android_resource_remover": "android_clean_app", - "AnelPowerControl": "anel_power_control", - "angus_sdk_python": "angus", - "Annalist": "annalist_root", - "ANNOgesic": "annogesiclib", - "ansible_role_apply": "ansible-role-apply", - "ansible_playbook_debugger": "ansibledebugger", - "ansible_docgen": "ansibledocgen", - "ansible_flow": "ansibleflow", - "ansible_inventory_grapher": "ansibleinventorygrapher", - "ansible_lint": "ansiblelint", - "ansible_roles_graph": "ansiblerolesgraph", - "ansible_tools": "ansibletools", - "anthill.exampletheme": "anthill", - "anthill.skinner": "anthill", - "anthill.tal.macrorenderer": "anthill", - "AnthraxDojoFrontend": "anthrax", - "AnthraxHTMLInput": "anthrax", - "AnthraxImage": "anthrax", - "antiweb": "antisphinx", - "antispoofing.evaluation": "antispoofing", - "antlr4_python2_runtime": "antlr4", - "antlr4_python3_runtime": "antlr4", - "antlr4_python_alt": "antlr4", - "anybox.buildbot.openerp": "anybox", - "anybox.nose.odoo": "anybox", - "anybox.paster.odoo": "anybox", - "anybox.paster.openerp": "anybox", - "anybox.recipe.sysdeps": "anybox", - "anybox.scripts.odoo": "anybox", - "google_api_python_client": "googleapiclient", - "google_apitools": "apitools", - "arpm": "apm", - "django_appdata": "app_data", - "django_appconf": "appconf", - "AppDynamicsDownloader": "appd", - "AppDynamicsREST": "appd", - "appdynamics_bindeps_linux_x64": "appdynamics_bindeps", - "appdynamics_bindeps_linux_x86": "appdynamics_bindeps", - "appdynamics_bindeps_osx_x64": "appdynamics_bindeps", - "appdynamics_proxysupport_linux_x64": "appdynamics_proxysupport", - "appdynamics_proxysupport_linux_x86": "appdynamics_proxysupport", - "appdynamics_proxysupport_osx_x64": "appdynamics_proxysupport", - "Appium_Python_Client": "appium", - "applibase": "appliapps", - "broadwick": "appserver", - "archetypes.kss": "archetypes", - "archetypes.multilingual": "archetypes", - "archetypes.schemaextender": "archetypes", - "ansible_role_manager": "arm", - "armor_api": "armor", - "armstrong.apps.related_content": "armstrong", - "armstrong.apps.series": "armstrong", - "armstrong.cli": "armstrong", - "armstrong.core.arm_access": "armstrong", - "armstrong.core.arm_layout": "armstrong", - "armstrong.core.arm_sections": "armstrong", - "armstrong.core.arm_wells": "armstrong", - "armstrong.dev": "armstrong", - "armstrong.esi": "armstrong", - "armstrong.hatband": "armstrong", - "armstrong.templates.standard": "armstrong", - "armstrong.utils.backends": "armstrong", - "armstrong.utils.celery": "armstrong", - "arstecnica.raccoon.autobahn": "arstecnica", - "arstecnica.sqlalchemy.async": "arstecnica", - "article_downloader": "article-downloader", - "artifact_cli": "artifactcli", - "arvados_python_client": "arvados", - "arvados_cwl_runner": "arvados_cwl", - "arvados_node_manager": "arvnodeman", - "AsanaToGithub": "asana_to_github", - "AsciiBinaryConverter": "asciibinary", - "AdvancedSearchDiscovery": "asd", - "askbot_tuan": "askbot", - "askbot_tuanpa": "askbot", - "asnhistory_redis": "asnhistory", - "aspen_jinja2": "aspen_jinja2_renderer", - "aspen_tornado": "aspen_tornado_engine", - "asprise_ocr_sdk_python_api": "asprise_ocr_api", - "aspy.refactor_imports": "aspy", - "aspy.yaml": "aspy", - "asterisk_ami": "asterisk", - "add_asts": "asts", - "asymmetricbase.enum": "asymmetricbase", - "asymmetricbase.fields": "asymmetricbase", - "asymmetricbase.logging": "asymmetricbase", - "asymmetricbase.utils": "asymmetricbase", - "asyncio_irc": "asyncirc", - "asyncmongoorm_je": "asyncmongoorm", - "asyncssh_unofficial": "asyncssh", - "athletelistyy": "athletelist", - "automium": "atm", - "atmosphere_python_client": "atmosphere", - "gdata": "atom", - "AtomicWrite": "atomic", - "atomisator.db": "atomisator", - "atomisator.enhancers": "atomisator", - "atomisator.feed": "atomisator", - "atomisator.indexer": "atomisator", - "atomisator.outputs": "atomisator", - "atomisator.parser": "atomisator", - "atomisator.readers": "atomisator", - "atreal.cmfeditions.unlocker": "atreal", - "atreal.filestorage.common": "atreal", - "atreal.layouts": "atreal", - "atreal.mailservices": "atreal", - "atreal.massloader": "atreal", - "atreal.monkeyplone": "atreal", - "atreal.override.albumview": "atreal", - "atreal.richfile.preview": "atreal", - "atreal.richfile.qualifier": "atreal", - "atreal.usersinout": "atreal", - "atsim.potentials": "atsim", - "attract_sdk": "attractsdk", - "audio.bitstream": "audio", - "audio.coders": "audio", - "audio.filters": "audio", - "audio.fourier": "audio", - "audio.frames": "audio", - "audio.lp": "audio", - "audio.psychoacoustics": "audio", - "audio.quantizers": "audio", - "audio.shrink": "audio", - "audio.wave": "audio", - "auf_refer": "aufrefer", - "auslfe.formonline.content": "auslfe", - "auspost_apis": "auspost", - "auth0_python": "auth0", - "AuthServerClient": "auth_server_client", - "AuthorizeSauce": "authorize", - "AuthzPolicyPlugin": "authzpolicy", - "autobahn_rce": "autobahn", - "geonode_avatar": "avatar", - "android_webview": "awebview", - "azure_common": "azure", - "azure_mgmt_common": "azure", - "azure_mgmt_compute": "azure", - "azure_mgmt_network": "azure", - "azure_mgmt_nspkg": "azure", - "azure_mgmt_resource": "azure", - "azure_mgmt_storage": "azure", - "azure_nspkg": "azure", - "azure_servicebus": "azure", - "azure_servicemanagement_legacy": "azure", - "azure_storage": "azure", - "b2g_commands": "b2gcommands", - "b2gperf_v1.3": "b2gperf", - "b2gperf_v1.4": "b2gperf", - "b2gperf_v2.0": "b2gperf", - "b2gperf_v2.1": "b2gperf", - "b2gperf_v2.2": "b2gperf", - "b2gpopulate_v1.3": "b2gpopulate", - "b2gpopulate_v1.4": "b2gpopulate", - "b2gpopulate_v2.0": "b2gpopulate", - "b2gpopulate_v2.1": "b2gpopulate", - "b2gpopulate_v2.2": "b2gpopulate", - "b3j0f.annotation": "b3j0f", - "b3j0f.aop": "b3j0f", - "b3j0f.conf": "b3j0f", - "b3j0f.sync": "b3j0f", - "b3j0f.utils": "b3j0f", - "Babel": "babel", - "BabelGladeExtractor": "babelglade", - "backplane2_pyclient": "backplane", - "backport_collections": "backport_abcoll", - "backports.functools_lru_cache": "backports", - "backports.inspect": "backports", - "backports.pbkdf2": "backports", - "backports.shutil_get_terminal_size": "backports", - "backports.socketpair": "backports", - "backports.ssl": "backports", - "backports.ssl_match_hostname": "backports", - "backports.statistics": "backports", - "badgekit_api_client": "badgekit", - "BadLinksPlugin": "badlinks", - "bael.project": "bael", - "baidupy": "baidu", - "buildtools": "balrog", - "baluhn_redux": "baluhn", - "bamboo.pantrybell": "bamboo", - "bamboo.scaffold": "bamboo", - "bamboo.setuptools_version": "bamboo", - "bamboo_data": "bamboo", - "bamboo_server": "bamboo", - "bambu_codemirror": "bambu", - "bambu_dataportability": "bambu", - "bambu_enqueue": "bambu", - "bambu_faq": "bambu", - "bambu_ffmpeg": "bambu", - "bambu_grids": "bambu", - "bambu_international": "bambu", - "bambu_jwplayer": "bambu", - "bambu_minidetect": "bambu", - "bambu_navigation": "bambu", - "bambu_notifications": "bambu", - "bambu_payments": "bambu", - "bambu_pusher": "bambu", - "bambu_saas": "bambu", - "bambu_sites": "bambu", - "Bananas": "banana", - "banana.maya": "banana", - "bangtext": "bang", - "barcode_generator": "barcode", - "bark_ssg": "bark", - "BarkingOwl": "barking_owl", - "bart_py": "bart", - "basalt_tasks": "basalt", - "base_62": "base62", - "basemap_Jim": "basemap", - "bash_toolbelt": "bash", - "Python_Bash_Utils": "bashutils", - "BasicHttp": "basic_http", - "basil_daq": "basil", - "azure_batch_apps": "batchapps", - "python_bcrypt": "bcrypt", - "Beaker": "beaker", - "beets": "beetsplug", - "begins": "begin", - "bench_it": "benchit", - "beproud.utils": "beproud", - "burrito_fillings": "bfillings", - "BigJob": "pilot", - "billboard.py": "billboard", - "anaconda_build": "binstar_build_client", - "anaconda_client": "binstar_client", - "biocommons.dev": "biocommons", - "birdhousebuilder.recipe.conda": "birdhousebuilder", - "birdhousebuilder.recipe.docker": "birdhousebuilder", - "birdhousebuilder.recipe.redis": "birdhousebuilder", - "birdhousebuilder.recipe.supervisor": "birdhousebuilder", - "pymeshio": "blender26-meshio", - "borg.localrole": "borg", - "bagofwords": "bow", - "bpython": "bpdb", - "bisque_api": "bqapi", - "django_braces": "braces", - "briefs_caster": "briefscaster", - "brisa_media_server_plugins": "brisa_media_server/plugins", - "brkt_sdk": "brkt_requests", - "broadcast_logging": "broadcastlogging", - "brocade_tool": "brocadetool", - "bronto_python": "bronto", - "Brownie": "brownie", - "browsermob_proxy": "browsermobproxy", - "brubeck_mysql": "brubeckmysql", - "brubeck_oauth": "brubeckoauth", - "brubeck_service": "brubeckservice", - "brubeck_uploader": "brubeckuploader", - "beautifulsoup4": "bs4", - "pymongo": "gridfs", - "bst.pygasus.core": "bst", - "bst.pygasus.datamanager": "bst", - "bst.pygasus.demo": "bst", - "bst.pygasus.i18n": "bst", - "bst.pygasus.resources": "bst", - "bst.pygasus.scaffolding": "bst", - "bst.pygasus.security": "bst", - "bst.pygasus.session": "bst", - "bst.pygasus.wsgi": "bst", - "btable_py": "btable", - "bananatag_api": "btapi", - "btce_api": "btceapi", - "btce_bot": "btcebot", - "btsync.py": "btsync", - "buck.pprint": "buck", - "bud.nospam": "bud", - "budy_api": "budy", - "buffer_alpaca": "buffer", - "bug.gd": "buggd", - "bugle_sites": "bugle", - "bug_spots": "bugspots", - "python_bugzilla": "bugzilla", - "bugzscout_py": "bugzscout", - "ajk_ios_buildTools": "buildTools", - "BuildNotify": "buildnotifylib", - "buildout.bootstrap": "buildout", - "buildout.disablessl": "buildout", - "buildout.dumppickedversions": "buildout", - "buildout.dumppickedversions2": "buildout", - "buildout.dumprequirements": "buildout", - "buildout.eggnest": "buildout", - "buildout.eggscleaner": "buildout", - "buildout.eggsdirectories": "buildout", - "buildout.eggtractor": "buildout", - "buildout.extensionscripts": "buildout", - "buildout.locallib": "buildout", - "buildout.packagename": "buildout", - "buildout.recipe.isolation": "buildout", - "buildout.removeaddledeggs": "buildout", - "buildout.requirements": "buildout", - "buildout.sanitycheck": "buildout", - "buildout.sendpickedversions": "buildout", - "buildout.threatlevel": "buildout", - "buildout.umask": "buildout", - "buildout.variables": "buildout", - "buildbot_slave": "buildslave", - "pies2overrides": "xmlrpc", - "bumper_lib": "bumper", - "bumple_downloader": "bumple", - "bundesliga_cli": "bundesliga", - "bundlemanager": "bundlemaker", - "burp_ui": "burpui", - "busyflow.pivotal": "busyflow", - "buttercms_django": "buttercms-django", - "buzz_python_client": "buzz", - "buildout_versions_checker": "bvc", - "bvg_grabber": "bvggrabber", - "BYONDTools": "byond", - "Bugzilla_ETL": "bzETL", - "bugzillatools": "bzlib", - "bzr": "bzrlib", - "bzr_automirror": "bzrlib", - "bzr_bash_completion": "bzrlib", - "bzr_colo": "bzrlib", - "bzr_killtrailing": "bzrlib", - "bzr_pqm": "bzrlib", - "c2c.cssmin": "c2c", - "c2c.recipe.closurecompile": "c2c", - "c2c.recipe.cssmin": "c2c", - "c2c.recipe.jarfile": "c2c", - "c2c.recipe.msgfmt": "c2c", - "c2c.recipe.pkgversions": "c2c", - "c2c.sqlalchemy.rest": "c2c", - "c2c.versions": "c2c", - "c2c.recipe.facts": "c2c_recipe_facts", - "cabalgata_silla_de_montar": "cabalgata", - "cabalgata_zookeeper": "cabalgata", - "django_cache_utils": "cache_utils", - "django_recaptcha": "captcha", - "Cartridge": "cartridge", - "cassandra_driver": "cassandra", - "CassandraLauncher": "cassandralauncher", - "42qucc": "cc42", - "Cerberus": "cerberus", - "cfn-lint": "cfnlint", - "Chameleon": "chameleon", - "charm_tools": "charmtools", - "PyChef": "chef", - "c8d": "chip8", - "python_cjson": "cjson", - "django_classy_tags": "classytags", - "ConcurrentLogHandler": "cloghandler", - "virtualenv_clone": "clonevirtualenv", - "al_cloudinsight": "cloud-insight", - "adminapi": "cloud_admin", - "python_cloudservers": "cloudservers", - "cerebrod": "tasksitter", - "django_cms": "cms", - "ba_colander": "colander", - "ansicolors": "colors", - "bf_lc3": "compile", - "docker_compose": "compose", - "django_compressor": "compressor", - "futures": "concurrent", - "ConfigArgParse": "configargparse", - "PyContracts": "contracts", - "weblogo": "weblogolib", - "Couchapp": "couchapp", - "CouchDB": "couchdb", - "couchdb_python_curl": "couchdbcurl", - "coursera_dl": "courseradownloader", - "cow_framework": "cow", - "python_creole": "creole", - "Creoleparser": "creoleparser", - "django_crispy_forms": "crispy_forms", - "python_crontab": "crontab", - "tff": "ctff", - "pycups": "cups", - "elasticsearch_curator": "curator", - "pycurl": "curl", - "python_daemon": "daemon", - "DARE": "dare", - "python_dateutil": "dateutil", - "DAWG": "dawg", - "python_debian": "debian", - "python-decouple": "decouple", - "webunit": "demo", - "PySynth": "pysynth_samp", - "juju_deployer": "deployer", - "filedepot": "depot", - "tg.devtools": "devtools", - "2gis": "dgis", - "pyDHTMLParser": "dhtmlparser", - "python_digitalocean": "digitalocean", - "discord.py": "discord", - "ez_setup": "distribute_setup", - "Distutils2": "distutils2", - "Django": "django", - "amitu_hstore": "django_hstore", - "django_bower": "djangobower", - "django_celery": "djcelery", - "django_kombu": "djkombu", - "djorm_ext_pgarray": "djorm_pgarray", - "dnspython": "dns", - "ansible_docgenerator": "docgen", - "docker_py": "docker", - "dogpile.cache": "dogpile", - "dogpile.core": "dogpile", - "dogapi": "dogshell", - "pydot": "dot_parser", - "pydot2": "dot_parser", - "pydot3k": "dot_parser", - "python-dotenv": "dotenv", - "dpkt_fix": "dpkt", - "python_ldap": "ldif", - "django_durationfield": "durationfield", - "datazilla": "dzclient", - "easybuild_framework": "easybuild", - "python_editor": "editor", - "azure_elasticluster": "elasticluster", - "azure_elasticluster_current": "elasticluster", - "pyelftools": "elftools", - "Elixir": "elixir", - "empy": "emlib", - "pyenchant": "enchant", - "cssutils": "encutils", - "python_engineio": "engineio", - "enum34": "enum", - "pyephem": "ephem", - "abl.errorreporter": "errorreporter", - "beaker_es_plot": "esplot", - "adrest": "example", - "tweepy": "examples", - "pycassa": "ez_setup", - "Fabric": "fabric", - "Faker": "faker", - "python_fedora": "fedora", - "ailove_django_fias": "fias", - "51degrees_mobile_detector": "fiftyone_degrees", - "five.customerize": "five", - "five.globalrequest": "five", - "five.intid": "five", - "five.localsitemanager": "five", - "five.pt": "five", - "android_flasher": "flasher", - "Flask": "flask", - "Frozen_Flask": "flask_frozen", - "Flask_And_Redis": "flask_redis", - "Flask_Bcrypt": "flaskext", - "vnc2flv": "flvscreen", - "django_followit": "followit", - "pyforge": "forge", - "FormEncode": "formencode", - "django_formtools": "formtools", - "4ch": "fourch", - "allegrordf": "franz", - "freetype_py": "freetype", - "python_frontmatter": "frontmatter", - "ftp_cloudfs": "ftpcloudfs", - "librabbitmq": "funtests", - "fusepy": "fuse", - "Fuzzy": "fuzzy", - "tiddlyweb": "gabbi", - "3d_wallet_generator": "gen_3dwallet", - "android_gendimen": "gendimen", - "Genshi": "genshi", - "python_geohash": "quadtree", - "GeoNode": "geonode", - "gsconfig": "geoserver", - "Geraldo": "geraldo", - "django_getenv": "getenv", - "gevent_websocket": "geventwebsocket", - "python_gflags": "gflags", - "GitPython": "git", - "PyGithub": "github", - "github3.py": "github3", - "git_py": "gitpy", - "globusonline_transfer_api_client": "globusonline", - "protobuf": "google", - "grace_dizmo": "grace-dizmo", - "anovelmous_grammar": "grammar", - "graphenelib": "grapheneapi", - "scales": "greplin", - "grokcore.component": "grokcore", - "gsutil": "gslib", - "PyHamcrest": "hamcrest", - "HARPy": "harpy", - "PyHawk_with_a_single_extra_commit": "hawk", - "django_haystack": "haystack", - "mercurial": "hgext", - "hg_git": "hggit", - "python_hglib": "hglib", - "pisa": "sx", - "amarokHola": "hola", - "Hoover": "hoover", - "python_hostlist": "hostlist", - "nosehtmloutput": "htmloutput", - "django_hvad": "hvad", - "hydra-core": "hydra", - "199Fix": "i99fix", - "python_igraph": "igraph", - "IMDbPY": "imdb", - "impyla": "impala", - "ambition_inmemorystorage": "inmemorystorage", - "backport_ipaddress": "ipaddress", - "jaraco.timing": "jaraco", - "jaraco.util": "jaraco", - "Jinja2": "jinja2", - "jira_cli": "jiracli", - "johnny_cache": "johnny", - "JPype1": "jpypex", - "django_jsonfield": "jsonfield", - "aino_jstools": "jstools", - "jupyter_pip": "jupyterpip", - "PyJWT": "jwt", - "asana_kazoo": "kazoo", - "line_profiler": "kernprof", - "python_keyczar": "keyczar", - "django_keyedcache": "keyedcache", - "python_keystoneclient": "keystoneclient", - "kickstart": "kickstarter", - "krbV": "krbv", - "kss.core": "kss", - "Kuyruk": "kuyruk", - "AdvancedLangConv": "langconv", - "lava_utils_interface": "lava", - "lazr.authentication": "lazr", - "lazr.restfulclient": "lazr", - "lazr.uri": "lazr", - "adpasswd": "ldaplib", - "2or3": "lib2or3", - "3to2": "lib3to2", - "Aito": "libaito", - "bugs_everywhere": "libbe", - "bucket": "libbucket", - "apache_libcloud": "libcloud", - "future": "winreg", - "generateDS": "libgenerateDS", - "mitmproxy": "libmproxy", - "7lk_ocr_deploy": "libsvm", - "lisa_server": "lisa", - "aspose_words_java_for_python": "loadingandsaving", - "locustio": "locust", - "Logbook": "logbook", - "buildbot_status_logentries": "logentries", - "logilab_mtconverter": "logilab", - "python_magic": "magic", - "Mako": "mako", - "ManifestDestiny": "manifestparser", - "marionette_client": "marionette", - "Markdown": "markdown", - "pytest_marks": "marks", - "MarkupSafe": "markupsafe", - "pymavlink": "mavnative", - "python_memcached": "memcache", - "AllPairs": "metacomm", - "Metafone": "metaphone", - "metlog_py": "metlog", - "Mezzanine": "mezzanine", - "sqlalchemy_migrate": "migrate", - "python_mimeparse": "mimeparse", - "minitage.paste": "minitage", - "minitage.recipe.common": "minitage", - "android_missingdrawables": "missingdrawables", - "2lazy2rest": "mkrst_themes", - "mockredispy": "mockredis", - "python_modargs": "modargs", - "django_model_utils": "model_utils", - "asposebarcode": "models", - "asposestorage": "models", - "moksha.common": "moksha", - "moksha.hub": "moksha", - "moksha.wsgi": "moksha", - "py_moneyed": "moneyed", - "MongoAlchemy": "mongoalchemy", - "MonthDelta": "monthdelta", - "Mopidy": "mopidy", - "MoPyTools": "mopytools", - "django_mptt": "mptt", - "python-mpv": "mpv", - "mr.bob": "mrbob", - "msgpack_python": "msgpack", - "aino_mutations": "mutations", - "amazon_mws": "mws", - "mysql_connector_repackaged": "mysql", - "django_native_tags": "native_tags", - "ndg_httpsclient": "ndg", - "trytond_nereid": "nereid", - "baojinhuan": "nested", - "Amauri": "nester", - "abofly": "nester", - "bssm_pythonSig": "nester", - "python_novaclient": "novaclient", - "alauda_django_oauth": "oauth2_provider", - "oauth2client": "oauth2client", - "odfpy": "odf", - "Parsley": "ometa", - "python_openid": "openid", - "ali_opensearch": "opensearchsdk", - "oslo.i18n": "oslo_i18n", - "oslo.serialization": "oslo_serialization", - "oslo.utils": "oslo_utils", - "alioss": "oss", - "aliyun_python_sdk_oss": "oss", - "aliyunoss": "oss", - "cashew": "output", - "OWSLib": "owslib", - "nwdiag": "rackdiag", - "paho_mqtt": "paho", - "django_paintstore": "paintstore", - "django_parler": "parler", - "PasteScript": "paste", - "forked_path": "path", - "path.py": "path", - "patricia-trie": "patricia", - "Paver": "paver", - "ProxyTypes": "peak", - "anderson.picasso": "picasso", - "django-picklefield": "picklefield", - "pivotal_py": "pivotal", - "peewee": "pwiz", - "plivo": "plivoxml", - "plone.alterego": "plone", - "plone.api": "plone", - "plone.app.blob": "plone", - "plone.app.collection": "plone", - "plone.app.content": "plone", - "plone.app.contentlisting": "plone", - "plone.app.contentmenu": "plone", - "plone.app.contentrules": "plone", - "plone.app.contenttypes": "plone", - "plone.app.controlpanel": "plone", - "plone.app.customerize": "plone", - "plone.app.dexterity": "plone", - "plone.app.discussion": "plone", - "plone.app.event": "plone", - "plone.app.folder": "plone", - "plone.app.i18n": "plone", - "plone.app.imaging": "plone", - "plone.app.intid": "plone", - "plone.app.layout": "plone", - "plone.app.linkintegrity": "plone", - "plone.app.locales": "plone", - "plone.app.lockingbehavior": "plone", - "plone.app.multilingual": "plone", - "plone.app.portlets": "plone", - "plone.app.querystring": "plone", - "plone.app.redirector": "plone", - "plone.app.registry": "plone", - "plone.app.relationfield": "plone", - "plone.app.textfield": "plone", - "plone.app.theming": "plone", - "plone.app.users": "plone", - "plone.app.uuid": "plone", - "plone.app.versioningbehavior": "plone", - "plone.app.viewletmanager": "plone", - "plone.app.vocabularies": "plone", - "plone.app.widgets": "plone", - "plone.app.workflow": "plone", - "plone.app.z3cform": "plone", - "plone.autoform": "plone", - "plone.batching": "plone", - "plone.behavior": "plone", - "plone.browserlayer": "plone", - "plone.caching": "plone", - "plone.contentrules": "plone", - "plone.dexterity": "plone", - "plone.event": "plone", - "plone.folder": "plone", - "plone.formwidget.namedfile": "plone", - "plone.formwidget.recurrence": "plone", - "plone.i18n": "plone", - "plone.indexer": "plone", - "plone.intelligenttext": "plone", - "plone.keyring": "plone", - "plone.locking": "plone", - "plone.memoize": "plone", - "plone.namedfile": "plone", - "plone.outputfilters": "plone", - "plone.portlet.collection": "plone", - "plone.portlet.static": "plone", - "plone.portlets": "plone", - "plone.protect": "plone", - "plone.recipe.zope2install": "plone", - "plone.registry": "plone", - "plone.resource": "plone", - "plone.resourceeditor": "plone", - "plone.rfc822": "plone", - "plone.scale": "plone", - "plone.schema": "plone", - "plone.schemaeditor": "plone", - "plone.session": "plone", - "plone.stringinterp": "plone", - "plone.subrequest": "plone", - "plone.supermodel": "plone", - "plone.synchronize": "plone", - "plone.theme": "plone", - "plone.transformchain": "plone", - "plone.uuid": "plone", - "plone.z3cform": "plone", - "plonetheme.barceloneta": "plonetheme", - "pypng": "png", - "django_polymorphic": "polymorphic", - "python_postmark": "postmark", - "bash_powerprompt": "powerprompt", - "django-prefetch": "prefetch", - "AndrewList": "printList", - "progressbar2": "progressbar", - "progressbar33": "progressbar", - "django_oauth2_provider": "provider", - "pure_sasl": "puresasl", - "pylzma": "py7zlib", - "pyAMI_core": "pyAMI", - "arsespyder": "pyarsespyder", - "asdf": "pyasdf", - "aspell_python_ctypes": "pyaspell", - "pybbm": "pybb", - "pybloomfiltermmap": "pybloomfilter", - "Pyccuracy": "pyccuracy", - "PyCK": "pyck", - "python_crfsuite": "pycrfsuite", - "PyDispatcher": "pydispatch", - "pygeocoder": "pygeolib", - "Pygments": "pygments", - "python_graph_core": "pygraph", - "pyjon.utils": "pyjon", - "python_jsonrpc": "pyjsonrpc", - "Pykka": "pykka", - "PyLogo": "pylogo", - "adhocracy_Pylons": "pylons", - "libmagic": "pymagic", - "Amalwebcrawler": "pymycraawler", - "AbakaffeNotifier": "pynma", - "Pyphen": "pyphen", - "AEI": "pyrimaa", - "adhocracy_pysqlite": "pysqlite2", - "pysqlite": "pysqlite2", - "python_gettext": "pythongettext", - "python_json_logger": "pythonjsonlogger", - "PyUtilib": "pyutilib", - "Cython": "pyximport", - "qserve": "qs", - "django_quickapi": "quickapi", - "nose_quickunit": "quickunit", - "radical.pilot": "radical", - "radical.utils": "radical", - "readability_lxml": "readability", - "gnureadline": "readline", - "django_recaptcha_works": "recaptcha_works", - "RelStorage": "relstorage", - "django_reportapi": "reportapi", - "Requests": "requests", - "requirements_parser": "requirements", - "djangorestframework": "rest_framework", - "py_restclient": "restclient", - "async_retrial": "retrial", - "django_reversion": "reversion", - "rhaptos2.common": "rhaptos2", - "robotframework": "robot", - "django_robots": "robots", - "rosdep": "rosdep2", - "RSFile": "rsbackends", - "ruamel.base": "ruamel", - "pysaml2": "xmlenc", - "saga_python": "saga", - "aws-sam-translator": "samtranslator", - "libsass": "sassutils", - "alex_sayhi": "sayhi", - "scalr": "scalrtools", - "scikits.talkbox": "scikits", - "scratchpy": "scratch", - "pyScss": "scss", - "dict.sorted": "sdict", - "android_sdk_updater": "sdk_updater", - "django_sekizai": "sekizai", - "pysendfile": "sendfile", - "pyserial": "serial", - "astor": "setuputils", - "pyshp": "shapefile", - "Shapely": "shapely", - "ahonya_sika": "sika", - "pysingleton": "singleton", - "scikit_bio": "skbio", - "scikit_learn": "sklearn", - "slackclient": "slack", - "unicode_slugify": "slugify", - "smk_python_sdk": "smarkets", - "ctypes_snappy": "snappy", - "gevent_socketio": "socketio", - "sockjs_tornado": "sockjs", - "SocksiPy_branch": "socks", - "solrpy": "solr", - "Solution": "solution", - "sorl_thumbnail": "sorl", - "South": "south", - "Sphinx": "sphinx", - "ATD_document": "sphinx_pypi_upload", - "sphinxcontrib_programoutput": "sphinxcontrib", - "SQLAlchemy": "sqlalchemy", - "atlas": "src", - "auto_mix_prep": "src", - "bw_stats_toolkit": "stats_toolkit", - "dogstatsd_python": "statsd", - "python_stdnum": "stdnum", - "StoneageHTML": "stoneagehtml", - "django_storages": "storages", - "mox": "stubout", - "suds_jurko": "suds", - "python_swiftclient": "swiftclient", - "pytabix": "test", - "django_taggit": "taggit", - "django_tastypie": "tastypie", - "teamcity_messages": "teamcity", - "pyTelegramBotAPI": "telebot", - "Tempita": "tempita", - "Tenjin": "tenjin", - "python_termstyle": "termstyle", - "treeherder_client": "thclient", - "django_threaded_multihost": "threaded_multihost", - "3color_Press": "threecolor", - "pytidylib": "tidylib", - "3lwg": "tlw", - "toredis_fork": "toredis", - "tornado_redis": "tornadoredis", - "ansible_tower_cli": "tower_cli", - "Trac": "tracopt", - "android_localization_helper": "translation_helper", - "django_treebeard": "treebeard", - "trytond_stock": "trytond", - "tsuru_circus": "tsuru", - "python_tvrage": "tvrage", - "tw2.core": "tw2", - "tw2.d3": "tw2", - "tw2.dynforms": "tw2", - "tw2.excanvas": "tw2", - "tw2.forms": "tw2", - "tw2.jit": "tw2", - "tw2.jqplugins.flot": "tw2", - "tw2.jqplugins.gritter": "tw2", - "tw2.jqplugins.ui": "tw2", - "tw2.jquery": "tw2", - "tw2.sqla": "tw2", - "Twisted": "twisted", - "python_twitter": "twitter", - "transifex_client": "txclib", - "115wangpan": "u115", - "Unidecode": "unidecode", - "ansible_universe": "universe", - "pyusb": "usb", - "useless.pipes": "useless", - "auth_userpass": "userpass", - "automakesetup.py": "utilities", - "aino_utkik": "utkik", - "uWSGI": "uwsgidecorators", - "ab": "valentine", - "configobj": "validate", - "chartio": "version", - "ar_virtualenv_api": "virtualenvapi", - "brocade_plugins": "vyatta", - "WebOb": "webob", - "websocket_client": "websocket", - "WebTest": "webtest", - "Werkzeug": "werkzeug", - "wheezy.caching": "wheezy", - "wheezy.core": "wheezy", - "wheezy.http": "wheezy", - "tiddlywebwiki": "wikklytext", - "pywinrm": "winrm", - "Alfred_Workflow": "workflow", - "WSME": "wsmeext", - "WTForms": "wtforms", - "wtf_peewee": "wtfpeewee", - "pyxdg": "xdg", - "pytest_xdist": "xdist", - "xmpppy": "xmpp", - "XStatic_Font_Awesome": "xstatic", - "XStatic_jQuery": "xstatic", - "XStatic_jquery_ui": "xstatic", - "PyYAML": "yaml", - "z3c.autoinclude": "z3c", - "z3c.caching": "z3c", - "z3c.form": "z3c", - "z3c.formwidget.query": "z3c", - "z3c.objpath": "z3c", - "z3c.pt": "z3c", - "z3c.relationfield": "z3c", - "z3c.traverser": "z3c", - "z3c.zcmlhook": "z3c", - "pyzmq": "zmq", - "zopyx.textindexng3": "zopyx" -} \ No newline at end of file diff --git a/docs/docs/.vuepress/config.js b/docs/docs/.vuepress/config.js deleted file mode 100644 index 857506085e678..0000000000000 --- a/docs/docs/.vuepress/config.js +++ /dev/null @@ -1,34 +0,0 @@ -const path = require("path"); -const webpack = require("webpack"); -const themeConfig = require("./configs/themeConfig"); - -module.exports = { - title: "", - head: [ - ["link", { rel: "icon", href: "/favicon.ico" }], - ['meta', { name: 'version', content: 'latest' }] - ], - description: "Pipedream Documentation - Connect APIs, remarkably fast", - base: "/docs/", - plugins: [ - [ - "vuepress-plugin-canonical", - { - baseURL: "https://pipedream.com/docs", // base url for your canonical link, optional, default: '' - stripExtension: true, - }, - ], - "check-md", - "tabs", - ['vuepress-plugin-code-copy', { - color: '#34d28b', - backgroundColor: '#34d28b', - backgroundTransition: false, - successText: 'Copied' - }] - ], - themeConfig, - postcss: { - plugins: [require("autoprefixer"), require("tailwindcss")], - } -}; diff --git a/docs/docs/.vuepress/configs/envVars.js b/docs/docs/.vuepress/configs/envVars.js deleted file mode 100644 index 76bf902f86d62..0000000000000 --- a/docs/docs/.vuepress/configs/envVars.js +++ /dev/null @@ -1,41 +0,0 @@ -module.exports = { - PIPEDREAM_BASE_URL: "https://pipedream.com", - API_BASE_URL: "https://api.pipedream.com/v1", - SQL_API_BASE_URL: "https://rt.pipedream.com/sql", - ENDPOINT_BASE_URL: "*.m.pipedream.net", - PAYLOAD_SIZE_LIMIT: "512KB", - MEMORY_LIMIT: "256MB", - MEMORY_ABSOLUTE_LIMIT: "10GB", - EMAIL_PAYLOAD_SIZE_LIMIT: "30MB", - MAX_WORKFLOW_EXECUTION_LIMIT: "750", - base_credits_price: { - memory: 256, - seconds: 30, - }, - DATA_STORES_MAX_KEYS: "1,024", - DAILY_TESTING_LIMIT: "30 minutes", - INSPECTOR_EVENT_EXPIRY_DAYS: "365", - FUNCTION_PAYLOAD_LIMIT: "6MB", - DAILY_INVOCATIONS_LIMIT: "333", - FREE_ORG_DAILY_INVOCATIONS_LIMIT: "66", - PRICE_PER_INVOCATION: "0.0002", - FREE_MONTHLY_INVOCATIONS: "10,000", - PRO_MONTHLY_INVOCATIONS: "20,000", - TEAM_MONTHLY_INVOCATIONS: "20,000", - TEAM_MEMBER_LIMIT: "5", - PRO_MONTHLY_PRICE: "$19", - TEAM_MONTHLY_PRICE: "$19", - DEFAULT_WORKFLOW_QUEUE_SIZE: "100", - MAX_WORKFLOW_QUEUE_SIZE: "10,000", - NODE_VERSION: "18", - PYTHON_VERSION: "3.9", - GO_LANG_VERSION: "1.21.5", - CONFIGURED_PROPS_SIZE_LIMIT: "64KB", - SERVICE_DB_SIZE_LIMIT: "60KB", - TMP_SIZE_LIMIT: "2GB", - DELAY_MIN_MAX_TIME: - "You can pause your workflow for as little as one millisecond, or as long as one year", - PUBLIC_APPS: "1,700", - WARM_WORKERS_INTERVAL: "10 minutes", - WARM_WORKERS_CREDITS_PER_INTERVAL: 5, -}; diff --git a/docs/docs/.vuepress/configs/navConfig.js b/docs/docs/.vuepress/configs/navConfig.js deleted file mode 100644 index df827632c2390..0000000000000 --- a/docs/docs/.vuepress/configs/navConfig.js +++ /dev/null @@ -1,58 +0,0 @@ -module.exports = { - left: [ - { - text: "Documentation", - link: "/", - }, - { - text: "Quickstart", - link: "/quickstart/", - variant: "primary", - }, - { - text: "Guides", - link: "/guides/", - }, - { - text: "Reference", - items: [ - { text: "Components API", link: "/components/api/" }, - { text: "CLI", link: "/cli/install/" }, - { text: "REST API", link: "/api/" }, - { text: "Limits", link: "/limits/" }, - { text: "Security & Privacy", link: "/privacy-and-security/"}, - { text: "Handling Cold Starts", link: "/workflows/settings/#eliminate-cold-starts"}, - ], - }, - ], - right: [ - { - text: "Support", - link: "https://pipedream.com/support", - internal: true, - }, - { - text: "Pricing", - link: "/pricing/", - }, - { - text: "v2", - className: "docs-version", - ariaLabel: "Docs Version Menu", - items: [ - { - text: "v2", - link: "https://pipedream.com/docs", - internal: true, - }, - { - text: "v1", - link: "https://pipedream.com/docs/v1", - internal: true, - badge: "Deprecated", - badgeVariation: "danger", - }, - ], - }, - ] -}; diff --git a/docs/docs/.vuepress/configs/sidebarConfig.js b/docs/docs/.vuepress/configs/sidebarConfig.js deleted file mode 100644 index c430e8813c498..0000000000000 --- a/docs/docs/.vuepress/configs/sidebarConfig.js +++ /dev/null @@ -1,216 +0,0 @@ -// NEW NAV - -const docsNav = [ - { - title: "Quickstart", - children: [ - { - title: "Develop Workflows", - path: "/quickstart/", - }, - { - title: "Use GitHub Sync", - path: "/quickstart/github-sync/", - }, - ] - }, - "/workspaces/", - { - title: "Projects", - children: [ - "/projects/", - "/projects/git/", - { - title: "File Stores", - type: "group", - children: [{ title: "File Stores", path: "/projects/file-stores/" }, { title: "Node.js Reference", path: "/projects/file-stores/reference/" }], - }, - ] - }, - { - title: "Workflows", - children: [ - "/workflows/", - "/workflows/steps/", - "/workflows/steps/triggers/", - "/workflows/steps/actions/", - "/workflows/steps/using-props/", - "/workflows/events/", - "/workflows/events/inspect/", - "/workflows/flow-control/", - "/workflows/errors/", - "/workflows/concurrency-and-throttling/", - "/workflows/settings/", - "/workflows/vpc/", - "/workflows/domains/", - "/workflows/sharing/", - "/migrate-from-v1/", - ], - }, - "/event-history/", - "/sources/", - { - title: "Connected Accounts", - children: [ - "/connected-accounts/", - "/connected-accounts/api/", - "/connected-accounts/external-auth/", - ], - }, - ["/data-stores/", "Data Stores"], - { - title: "Code", - children: [ - "/code/", - { - title: "Node.js", - type: "group", - collapsable: false, - sidebarDepth: 2, - children: [ - "/code/nodejs/", - "/code/nodejs/ai-code-generation/", - "/code/nodejs/auth/", - "/code/nodejs/http-requests/", - "/code/nodejs/working-with-files/", - "/code/nodejs/using-data-stores/", - "/code/nodejs/delay/", - "/code/nodejs/rerun/", - "/environment-variables/", - "/code/nodejs/async/", - "/code/nodejs/sharing-code/", - "/code/nodejs/browser-automation/", - { - title: "Reference", - path: "/components/api/#run" - } - ], - }, - { - title: "Python", - type: "group", - collapsable: false, - sidebarDepth: 2, - children: [ - "/code/python/", - "/code/python/auth/", - "/code/python/http-requests/", - "/code/python/working-with-files/", - "/code/python/using-data-stores/", - "/code/python/delay/", - "/code/python/rerun/", - "/code/python/import-mappings/", - "/code/python/faqs/", - ], - }, - "/code/go/", - { - title: "Bash", - type: "group", - children: ["/code/bash/", "/code/bash/http-requests/"], - }, - "/destinations/", - "/environment-variables/", - ], - }, - "/http/", - { - title: "Integrations", - type: "group", - children: [ - "/apps/", - "/apps/contributing/", - { - title: "Components", - type: "group", - collapsable: false, - children: [ - "/components/", - "/components/quickstart/nodejs/actions/", - "/components/quickstart/nodejs/sources/", - "/pipedream-axios/", - "/components/typescript/", - "/components/guidelines/", - ], - }, - ], - }, - ["/troubleshooting/", "Troubleshooting"], - ["/user-settings/", "Settings"], - { - title: "Single-Sign On (SSO)", - children: [ - "/workspaces/sso/", - "/workspaces/sso/okta/", - "/workspaces/sso/google/", - "/workspaces/sso/saml/", - ], - }, -]; - -const referenceNav = [ - { - title: "Components API", - children: ["/components/api/"], - }, - { - title: "CLI", - type: "group", - children: ["/cli/install/", "/cli/login/", "/cli/reference/"], - }, - { - title: "API", - type: "group", - children: [ - "/api/", - "/api/auth/", - "/api/rest/", - "/api/rest/webhooks/", - "/api/rest/rss/", - "/api/sse/", - ], - }, - "/limits/", - { - title: "Security and Privacy", - children: [ - "/privacy-and-security/", - "/privacy-and-security/best-practices/", - "/abuse/", - "/privacy-and-security/pgp-key/", - "/subprocessors/", - ], - }, - "/workflows/settings/#eliminate-cold-starts", -]; - -const pricingNav = ["/pricing/"]; - -module.exports = { - // reference nav - "/components/api/": referenceNav, - "/pipedream-axios/": referenceNav, - "/api/": referenceNav, - "/api/auth/": referenceNav, - "/api/rest/": referenceNav, - "/api/rest/webhooks/": referenceNav, - "/api/rest/rss/": referenceNav, - "/api/rest/workflow-errors/": referenceNav, - "/api/sse/": referenceNav, - "/scheduling-future-tasks/": referenceNav, - "/cli/install/": referenceNav, - "/cli/login/": referenceNav, - "/cli/reference/": referenceNav, - "/limits/": referenceNav, - // security nav - "/privacy-and-security/": referenceNav, - "/privacy-and-reference/pgp-key/": referenceNav, - "/privacy-and-reference/best-practices/": referenceNav, - "/subprocessors/": referenceNav, - "/workflows/settings/#eliminate-cold-starts": referenceNav, - "/abuse/": referenceNav, - // pricing nav - "/pricing/": pricingNav, - // main nav - "/": docsNav, -}; diff --git a/docs/docs/.vuepress/configs/themeConfig.js b/docs/docs/.vuepress/configs/themeConfig.js deleted file mode 100644 index f858191059a0c..0000000000000 --- a/docs/docs/.vuepress/configs/themeConfig.js +++ /dev/null @@ -1,59 +0,0 @@ -const navConfig = require("./navConfig"); -const sidebarConfig = require("./sidebarConfig"); -const envVars = require("./envVars"); - -module.exports = { - algolia: { - appId: "XY28M447C5", - apiKey: "9d9169458128b3d60c22bb04da4431c7", - indexName: "pipedream", - algoliaOptions: { - facetFilters: ["version:latest"], - }, - }, - searchPlaceholder: "Search...", - logo: "https://res.cloudinary.com/pipedreamin/image/upload/t_logo48x48/v1597038956/docs/HzP2Yhq8_400x400_1_sqhs70.jpg", - repo: "PipedreamHQ/pipedream", - nav: navConfig, - - // if your docs are not at the root of the repo: - docsDir: "docs/docs", - editLinks: true, - // custom text for edit link. Defaults to "Edit this page" - editLinkText: "Improve this page", - sidebarDepth: 3, - sidebar: sidebarConfig, - // languages - icons: { - nodejs: { - only_icon: - "https://res.cloudinary.com/pipedreamin/image/upload/v1646761316/docs/icons/icons8-nodejs_aax6wn.svg", - with_title: - "https://res.cloudinary.com/pipedreamin/image/upload/v1646761316/docs/icons/icons8-nodejs_aax6wn.svg", - }, - python: { - only_icon: - "https://res.cloudinary.com/pipedreamin/image/upload/v1646763734/docs/icons/icons8-python_ypgmya.svg", - with_title: - "https://res.cloudinary.com/pipedreamin/image/upload/v1647356607/docs/icons/python-logo-generic_k3o5w2.svg", - }, - go: { - only_icon: - "https://res.cloudinary.com/pipedreamin/image/upload/v1646763751/docs/icons/Go-Logo_Blue_zhkchv.svg", - with_title: - "https://res.cloudinary.com/pipedreamin/image/upload/v1646763751/docs/icons/Go-Logo_Blue_zhkchv.svg", - }, - bash: { - only_icon: - "https://res.cloudinary.com/pipedreamin/image/upload/v1646763756/docs/icons/full_colored_dark_nllzkl.svg", - with_title: - "https://res.cloudinary.com/pipedreamin/image/upload/v1647356698/docs/icons/full_colored_dark_1_-svg_vyfnv7.svg", - }, - native: { - only_icon: "https://res.cloudinary.com/pipedreamin/image/upload/v1569526159/icons/pipedream_x6plab.svg", - with_title: "https://res.cloudinary.com/pipedreamin/image/upload/v1569526159/icons/pipedream_x6plab.svg" - } - }, - // environment variables - ...envVars, -}; diff --git a/docs/docs/.vuepress/enhanceApp.js b/docs/docs/.vuepress/enhanceApp.js deleted file mode 100644 index e20ecef17fc82..0000000000000 --- a/docs/docs/.vuepress/enhanceApp.js +++ /dev/null @@ -1,114 +0,0 @@ -import VueGtm from "vue-gtm"; - -// fork from vue-router@3.0.2 -// src/util/scroll.js -function getElementPosition(el) { - const docEl = document.documentElement - const docRect = docEl.getBoundingClientRect() - const elRect = el.getBoundingClientRect() - return { - x: elRect.left - docRect.left, - y: elRect.top - docRect.top, - } -} - -/** - * Fix broken Vuepress scrolling to internal #links - * - * @param {String} to - * @returns void - */ -function scrollToAnchor(to) { - const targetAnchor = to.hash.slice(1) - const targetElement = document.getElementById(targetAnchor) || document.querySelector(`[name='${targetAnchor}']`) - - if (targetElement) { - return window.scrollTo({ - top: getElementPosition(targetElement).y, - behavior: 'smooth', - }) - } else { - return false - } -} - -export default ({ - Vue, // the version of Vue being used in the VuePress app - options, // the options for the root Vue instance - router, // the router instance for the app - siteData, // site metadata -}) => { - if (typeof window !== "undefined") { - Vue.use(VueGtm, { - id: "GTM-KBDH3DB", - enabled: true, - debug: false, - vueRouter: router, - }); - } - - // Adapted from https://github.com/vuepress/vuepress-community/blob/7feb5c514090b6901cd7d9998f4dd858e0055b7a/packages/vuepress-plugin-smooth-scroll/src/enhanceApp.ts#L21 - // With a bug fix for handling long pages - router.options.scrollBehavior = (to, from, savedPosition) => { - if (typeof window === "undefined") { - return; - } - - if (savedPosition) { - return window.scrollTo({ - top: savedPosition.y, - behavior: 'smooth', - }) - } else if (to.hash) { - if (Vue.$vuepress.$get('disableScrollBehavior')) { - return false - } - const scrollResult = scrollToAnchor(to) - - if (scrollResult) { - return scrollResult - } else { - window.onload = () => { - scrollToAnchor(to) - } - } - } else { - return window.scrollTo({ - top: 0, - behavior: 'smooth', - }) - } - } - - router.addRoutes([ - { path: "/cron", redirect: "/workflows/steps/triggers" }, - { path: "/notebook", redirect: "/workflows/steps" }, - { path: "/workflows/fork", redirect: "/workflows/copy" }, - { path: "/notebook/fork", redirect: "/workflows/copy" }, - { path: "/notebook/inspector/", redirect: "/workflows/events/inspect/" }, - { path: "/notebook/destinations/s3/", redirect: "/destinations/s3/" }, - { path: "/notebook/destinations/sse/", redirect: "/destinations/sse/" }, - { - path: "/notebook/destinations/snowflake/", - redirect: "/destinations/snowflake/", - }, - { path: "/notebook/destinations/http/", redirect: "/destinations/http/" }, - { path: "/notebook/destinations/email/", redirect: "/destinations/email/" }, - { path: "/notebook/destinations/", redirect: "/destinations/" }, - { path: "/notebook/code/", redirect: "/workflows/steps/code/" }, - { - path: "/notebook/observability/", - redirect: "/workflows/events/inspect/", - }, - { path: "/notebook/actions/", redirect: "/workflows/steps/actions/" }, - { path: "/notebook/sources/", redirect: "/workflows/steps/triggers/" }, - { path: "/notebook/sql/", redirect: "/destinations/triggers/" }, - { path: "/what-is-pipedream/", redirect: "/" }, - { - path: "/docs/apps/all-apps", - redirect: "https://pipedream.com/apps", - }, - { path: "/workflows/steps/code/", redirect: '/code/nodejs/'} - - ]); -}; diff --git a/docs/docs/.vuepress/public/favicon.ico b/docs/docs/.vuepress/public/favicon.ico deleted file mode 100644 index b854b21bb03af..0000000000000 Binary files a/docs/docs/.vuepress/public/favicon.ico and /dev/null differ diff --git a/docs/docs/.vuepress/public/pipedream.svg b/docs/docs/.vuepress/public/pipedream.svg deleted file mode 100644 index ca61e0c2d013e..0000000000000 --- a/docs/docs/.vuepress/public/pipedream.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/docs/.vuepress/styles/index.styl b/docs/docs/.vuepress/styles/index.styl deleted file mode 100644 index a06ac76a0b8bc..0000000000000 --- a/docs/docs/.vuepress/styles/index.styl +++ /dev/null @@ -1,98 +0,0 @@ -@require '~vuepress-plugin-tabs/dist/themes/default.styl' - -body - font-family system-ui,BlinkMacSystemFont,-apple-system, sans-serif, Helvetica, Roboto, "Helvetica Neue", Arial, sans-serif - -$hoverColor = #34d28b -$borderColor = #fff -$bgColor = #fff -$sidebarColor = #000 -$headerColor = #343c56 -$textColor = rgba(0,0,0,.84) -$primaryColor = green; - -html, -body, -.navbar, -.sidebar, -.navbar .links, -.theme-container .navbar, -.theme-container .sidebar - background-color $bgColor - -body .content .default - color $textColor - -.nav-item - color: $headerColor - - @media (max-width: 719px) - color $headerColor - -.nav-item a:hover - color: $hoverColor - - @media (max-width: 719px) - color $hoverColor - -div .sidebar-button svg - color: $sidebarColor - -.navbar .site-name - color $sidebarColor - margin-left: -25px - margin-top: 1px - -p, ul:not(ul ul, ul ol), ol:not(ol ul, ol ol) { - display: block - margin-block-start: 1em - margin-block-end: 1em - margin-inline-start: 0px - margin-inline-end: 0px -} - -*:not(li)>ul { - margin-top: 1em; -} - -/** - * Copy code to clipboard style fixes - */ -.code-copy { - width: 0; - height: 0; - overflow-y: hidden; -} - - -/** - * Fix margin bottom spacing on tips with li's - */ -.custom-block li:last-of-type { - margin-bottom: 1rem; -} - - - -.pd-copy-workflow { - background-color: #35d28b; - size: 1em; - padding: 0.5em 1em 0.5em 1em; - color: #fff; - - display: inline-flex; - align-items: center; - font-weight: 800; - text-decoration: none !important; - - img { - display: inline-block; - } - - border-bottom: none !important; -} - -.pd-copy-workflow:hover { - text-decoration: none !important; - color: #fff !important; -} diff --git a/docs/docs/.vuepress/theme/LICENSE b/docs/docs/.vuepress/theme/LICENSE deleted file mode 100644 index 15f1f7e7a490f..0000000000000 --- a/docs/docs/.vuepress/theme/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2018-present, Yuxi (Evan) You - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/docs/docs/.vuepress/theme/components/AlgoliaSearchBox.vue b/docs/docs/.vuepress/theme/components/AlgoliaSearchBox.vue deleted file mode 100644 index 7b2a5807cfbe8..0000000000000 --- a/docs/docs/.vuepress/theme/components/AlgoliaSearchBox.vue +++ /dev/null @@ -1,171 +0,0 @@ - - - - - diff --git a/docs/docs/.vuepress/theme/components/DropdownLink.vue b/docs/docs/.vuepress/theme/components/DropdownLink.vue deleted file mode 100644 index 4b659a1ffbf1e..0000000000000 --- a/docs/docs/.vuepress/theme/components/DropdownLink.vue +++ /dev/null @@ -1,266 +0,0 @@ - - - - - diff --git a/docs/docs/.vuepress/theme/components/DropdownTransition.vue b/docs/docs/.vuepress/theme/components/DropdownTransition.vue deleted file mode 100644 index eeaf12b5c3e41..0000000000000 --- a/docs/docs/.vuepress/theme/components/DropdownTransition.vue +++ /dev/null @@ -1,33 +0,0 @@ - - - - - diff --git a/docs/docs/.vuepress/theme/components/Home.vue b/docs/docs/.vuepress/theme/components/Home.vue deleted file mode 100644 index 8cc0519bc96a4..0000000000000 --- a/docs/docs/.vuepress/theme/components/Home.vue +++ /dev/null @@ -1,175 +0,0 @@ - - - - - diff --git a/docs/docs/.vuepress/theme/components/NavLink.vue b/docs/docs/.vuepress/theme/components/NavLink.vue deleted file mode 100644 index b2c1ae2be4f71..0000000000000 --- a/docs/docs/.vuepress/theme/components/NavLink.vue +++ /dev/null @@ -1,100 +0,0 @@ - - - diff --git a/docs/docs/.vuepress/theme/components/NavLinks.vue b/docs/docs/.vuepress/theme/components/NavLinks.vue deleted file mode 100644 index bdf1fd3a5d94b..0000000000000 --- a/docs/docs/.vuepress/theme/components/NavLinks.vue +++ /dev/null @@ -1,188 +0,0 @@ - - - - - diff --git a/docs/docs/.vuepress/theme/components/Navbar.vue b/docs/docs/.vuepress/theme/components/Navbar.vue deleted file mode 100644 index d099067459bfc..0000000000000 --- a/docs/docs/.vuepress/theme/components/Navbar.vue +++ /dev/null @@ -1,184 +0,0 @@ - - - - - diff --git a/docs/docs/.vuepress/theme/components/NavbarGrid.vue b/docs/docs/.vuepress/theme/components/NavbarGrid.vue deleted file mode 100644 index c4d34d4970986..0000000000000 --- a/docs/docs/.vuepress/theme/components/NavbarGrid.vue +++ /dev/null @@ -1,112 +0,0 @@ - - - - diff --git a/docs/docs/.vuepress/theme/components/Page.vue b/docs/docs/.vuepress/theme/components/Page.vue deleted file mode 100644 index ee548f50e8094..0000000000000 --- a/docs/docs/.vuepress/theme/components/Page.vue +++ /dev/null @@ -1,37 +0,0 @@ - - - - - diff --git a/docs/docs/.vuepress/theme/components/PageEdit.vue b/docs/docs/.vuepress/theme/components/PageEdit.vue deleted file mode 100644 index de5c39ae650ec..0000000000000 --- a/docs/docs/.vuepress/theme/components/PageEdit.vue +++ /dev/null @@ -1,166 +0,0 @@ - - - - - diff --git a/docs/docs/.vuepress/theme/components/PageNav.vue b/docs/docs/.vuepress/theme/components/PageNav.vue deleted file mode 100644 index 4c19aae5f88bd..0000000000000 --- a/docs/docs/.vuepress/theme/components/PageNav.vue +++ /dev/null @@ -1,163 +0,0 @@ - - - - - diff --git a/docs/docs/.vuepress/theme/components/Sidebar.vue b/docs/docs/.vuepress/theme/components/Sidebar.vue deleted file mode 100644 index 93aaff28bd4fa..0000000000000 --- a/docs/docs/.vuepress/theme/components/Sidebar.vue +++ /dev/null @@ -1,89 +0,0 @@ - - - - - diff --git a/docs/docs/.vuepress/theme/components/SidebarButton.vue b/docs/docs/.vuepress/theme/components/SidebarButton.vue deleted file mode 100644 index 3f54afd5590e0..0000000000000 --- a/docs/docs/.vuepress/theme/components/SidebarButton.vue +++ /dev/null @@ -1,40 +0,0 @@ - - - diff --git a/docs/docs/.vuepress/theme/components/SidebarGroup.vue b/docs/docs/.vuepress/theme/components/SidebarGroup.vue deleted file mode 100644 index d7f192946aed9..0000000000000 --- a/docs/docs/.vuepress/theme/components/SidebarGroup.vue +++ /dev/null @@ -1,141 +0,0 @@ - - - - - diff --git a/docs/docs/.vuepress/theme/components/SidebarLink.vue b/docs/docs/.vuepress/theme/components/SidebarLink.vue deleted file mode 100644 index ec1be898cdb11..0000000000000 --- a/docs/docs/.vuepress/theme/components/SidebarLink.vue +++ /dev/null @@ -1,158 +0,0 @@ - - - diff --git a/docs/docs/.vuepress/theme/components/SidebarLinks.vue b/docs/docs/.vuepress/theme/components/SidebarLinks.vue deleted file mode 100644 index 1096358b001e0..0000000000000 --- a/docs/docs/.vuepress/theme/components/SidebarLinks.vue +++ /dev/null @@ -1,90 +0,0 @@ - - - diff --git a/docs/docs/.vuepress/theme/components/svgs/code-icon.vue b/docs/docs/.vuepress/theme/components/svgs/code-icon.vue deleted file mode 100644 index bf29a02c477f6..0000000000000 --- a/docs/docs/.vuepress/theme/components/svgs/code-icon.vue +++ /dev/null @@ -1,14 +0,0 @@ - diff --git a/docs/docs/.vuepress/theme/components/svgs/component-icon.vue b/docs/docs/.vuepress/theme/components/svgs/component-icon.vue deleted file mode 100644 index 74728ed7d7c6c..0000000000000 --- a/docs/docs/.vuepress/theme/components/svgs/component-icon.vue +++ /dev/null @@ -1,19 +0,0 @@ - - diff --git a/docs/docs/.vuepress/theme/components/svgs/integration-icon.vue b/docs/docs/.vuepress/theme/components/svgs/integration-icon.vue deleted file mode 100644 index 003249d2bcca7..0000000000000 --- a/docs/docs/.vuepress/theme/components/svgs/integration-icon.vue +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/docs/docs/.vuepress/theme/components/svgs/step-icon.vue b/docs/docs/.vuepress/theme/components/svgs/step-icon.vue deleted file mode 100644 index 6f5aaddcf64ad..0000000000000 --- a/docs/docs/.vuepress/theme/components/svgs/step-icon.vue +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/docs/docs/.vuepress/theme/components/svgs/trigger-icon.vue b/docs/docs/.vuepress/theme/components/svgs/trigger-icon.vue deleted file mode 100644 index bcc707b7a74fe..0000000000000 --- a/docs/docs/.vuepress/theme/components/svgs/trigger-icon.vue +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/docs/docs/.vuepress/theme/components/svgs/workflow-icon.vue b/docs/docs/.vuepress/theme/components/svgs/workflow-icon.vue deleted file mode 100644 index 8bd7a425e213e..0000000000000 --- a/docs/docs/.vuepress/theme/components/svgs/workflow-icon.vue +++ /dev/null @@ -1,13 +0,0 @@ - diff --git a/docs/docs/.vuepress/theme/global-components/Badge.vue b/docs/docs/.vuepress/theme/global-components/Badge.vue deleted file mode 100644 index 53951f9d505df..0000000000000 --- a/docs/docs/.vuepress/theme/global-components/Badge.vue +++ /dev/null @@ -1,44 +0,0 @@ - - - diff --git a/docs/docs/.vuepress/theme/index.js b/docs/docs/.vuepress/theme/index.js deleted file mode 100644 index baaf102ba7767..0000000000000 --- a/docs/docs/.vuepress/theme/index.js +++ /dev/null @@ -1,59 +0,0 @@ -const path = require('path') - -// Theme API. -module.exports = (options, ctx) => { - const { themeConfig, siteConfig } = ctx - - // resolve algolia - const isAlgoliaSearch = ( - themeConfig.algolia - || Object - .keys(siteConfig.locales && themeConfig.locales || {}) - .some(base => themeConfig.locales[base].algolia) - ) - - const enableSmoothScroll = themeConfig.smoothScroll === true - - return { - alias () { - return { - '@AlgoliaSearchBox': isAlgoliaSearch - ? path.resolve(__dirname, 'components/AlgoliaSearchBox.vue') - : path.resolve(__dirname, 'noopModule.js') - } - }, - - plugins: [ - ['@vuepress/active-header-links', options.activeHeaderLinks], - '@vuepress/search', - '@vuepress/plugin-nprogress', - ['container', { - type: 'tip', - defaultTitle: { - '/': 'TIP', - '/zh/': '提示' - } - }], - ['container', { - type: 'warning', - defaultTitle: { - '/': 'WARNING', - '/zh/': '注意' - } - }], - ['container', { - type: 'danger', - defaultTitle: { - '/': 'WARNING', - '/zh/': '警告' - } - }], - ['container', { - type: 'details', - before: info => `
${info ? `${info}` : ''}\n`, - after: () => '
\n' - }], - ['smooth-scroll', enableSmoothScroll] - ] - } -} diff --git a/docs/docs/.vuepress/theme/layouts/404.vue b/docs/docs/.vuepress/theme/layouts/404.vue deleted file mode 100644 index 2cbfa0f179572..0000000000000 --- a/docs/docs/.vuepress/theme/layouts/404.vue +++ /dev/null @@ -1,30 +0,0 @@ - - - diff --git a/docs/docs/.vuepress/theme/layouts/Layout.vue b/docs/docs/.vuepress/theme/layouts/Layout.vue deleted file mode 100644 index 3298070997d0c..0000000000000 --- a/docs/docs/.vuepress/theme/layouts/Layout.vue +++ /dev/null @@ -1,151 +0,0 @@ - - - diff --git a/docs/docs/.vuepress/theme/noopModule.js b/docs/docs/.vuepress/theme/noopModule.js deleted file mode 100644 index b1c6ea436a540..0000000000000 --- a/docs/docs/.vuepress/theme/noopModule.js +++ /dev/null @@ -1 +0,0 @@ -export default {} diff --git a/docs/docs/.vuepress/theme/styles/arrow.styl b/docs/docs/.vuepress/theme/styles/arrow.styl deleted file mode 100644 index 20bffc0dc8733..0000000000000 --- a/docs/docs/.vuepress/theme/styles/arrow.styl +++ /dev/null @@ -1,22 +0,0 @@ -@require './config' - -.arrow - display inline-block - width 0 - height 0 - &.up - border-left 4px solid transparent - border-right 4px solid transparent - border-bottom 6px solid $arrowBgColor - &.down - border-left 4px solid transparent - border-right 4px solid transparent - border-top 6px solid $arrowBgColor - &.right - border-top 4px solid transparent - border-bottom 4px solid transparent - border-left 6px solid $arrowBgColor - &.left - border-top 4px solid transparent - border-bottom 4px solid transparent - border-right 6px solid $arrowBgColor diff --git a/docs/docs/.vuepress/theme/styles/code.styl b/docs/docs/.vuepress/theme/styles/code.styl deleted file mode 100644 index 9d3aa9a54130c..0000000000000 --- a/docs/docs/.vuepress/theme/styles/code.styl +++ /dev/null @@ -1,137 +0,0 @@ -{$contentClass} - code - color lighten($textColor, 20%) - padding 0.25rem 0.5rem - margin 0 - font-size 0.85em - background-color rgba(27,31,35,0.05) - border-radius 3px - .token - &.deleted - color #EC5975 - &.inserted - color $accentColor - -{$contentClass} - pre, pre[class*="language-"] - line-height 1.4 - padding 1.25rem 1.5rem - margin 0.85rem 0 - background-color $codeBgColor - border-radius 6px - overflow auto - code - color #fff - padding 0 - background-color transparent - border-radius 0 - -div[class*="language-"] - position relative - background-color $codeBgColor - border-radius 6px - .highlight-lines - user-select none - padding-top 1.3rem - position absolute - top 0 - left 0 - width 100% - line-height 1.4 - .highlighted - background-color rgba(0, 0, 0, 66%) - pre, pre[class*="language-"] - background transparent - position relative - z-index 1 - &::before - position absolute - z-index 3 - top 0.8em - right 1em - font-size 0.75rem - color rgba(255, 255, 255, 0.4) - &:not(.line-numbers-mode) - .line-numbers-wrapper - display none - &.line-numbers-mode - .highlight-lines .highlighted - position relative - &:before - content ' ' - position absolute - z-index 3 - left 0 - top 0 - display block - width $lineNumbersWrapperWidth - height 100% - background-color rgba(0, 0, 0, 66%) - pre - padding-left $lineNumbersWrapperWidth + 1 rem - vertical-align middle - .line-numbers-wrapper - position absolute - top 0 - width $lineNumbersWrapperWidth - text-align center - color rgba(255, 255, 255, 0.3) - padding 1.25rem 0 - line-height 1.4 - br - user-select none - .line-number - position relative - z-index 4 - user-select none - font-size 0.85em - &::after - content '' - position absolute - z-index 2 - top 0 - left 0 - width $lineNumbersWrapperWidth - height 100% - border-radius 6px 0 0 6px - border-right 1px solid rgba(0, 0, 0, 66%) - background-color $codeBgColor - - -for lang in $codeLang - div{'[class~="language-' + lang + '"]'} - &:before - content ('' + lang) - -div[class~="language-javascript"] - &:before - content "js" - -div[class~="language-typescript"] - &:before - content "ts" - -div[class~="language-markup"] - &:before - content "html" - -div[class~="language-markdown"] - &:before - content "md" - -div[class~="language-json"]:before - content "json" - -div[class~="language-ruby"]:before - content "rb" - -div[class~="language-python"]:before - content "py" - -div[class~="language-bash"]:before - content "sh" - -div[class~="language-php"]:before - content "php" - -@import '~prismjs/themes/prism-tomorrow.css' diff --git a/docs/docs/.vuepress/theme/styles/config.styl b/docs/docs/.vuepress/theme/styles/config.styl deleted file mode 100644 index 9e403210fd385..0000000000000 --- a/docs/docs/.vuepress/theme/styles/config.styl +++ /dev/null @@ -1 +0,0 @@ -$contentClass = '.theme-default-content' diff --git a/docs/docs/.vuepress/theme/styles/custom-blocks.styl b/docs/docs/.vuepress/theme/styles/custom-blocks.styl deleted file mode 100644 index 5b868166a434c..0000000000000 --- a/docs/docs/.vuepress/theme/styles/custom-blocks.styl +++ /dev/null @@ -1,44 +0,0 @@ -.custom-block - .custom-block-title - font-weight 600 - margin-bottom -0.4rem - &.tip, &.warning, &.danger - padding .1rem 1.5rem - border-left-width .5rem - border-left-style solid - margin 1rem 0 - &.tip - background-color #f3f5f7 - border-color #42b983 - &.warning - background-color rgba(255,229,100,.3) - border-color darken(#ffe564, 35%) - color darken(#ffe564, 70%) - .custom-block-title - color darken(#ffe564, 50%) - a - color $textColor - &.danger - background-color #ffe6e6 - border-color darken(red, 20%) - color darken(red, 70%) - .custom-block-title - color darken(red, 40%) - a - color $textColor - &.details - display block - position relative - border-radius 2px - margin 1.6em 0 - padding 1.6em - background-color #eee - h4 - margin-top 0 - figure, p - &:last-child - margin-bottom 0 - padding-bottom 0 - summary - outline none - cursor pointer diff --git a/docs/docs/.vuepress/theme/styles/index.styl b/docs/docs/.vuepress/theme/styles/index.styl deleted file mode 100644 index 7ad6e8f6e44c3..0000000000000 --- a/docs/docs/.vuepress/theme/styles/index.styl +++ /dev/null @@ -1,214 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -@layer base { - ul, ol { - list-style: revert; - } -} - -@require './config' -@require './code' -@require './custom-blocks' -@require './arrow' -@require './wrapper' -@require './toc' -@require './navbar' - -html, body - padding 0 - margin 0 - background-color #fff - -body - font-family -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif - -webkit-font-smoothing antialiased - -moz-osx-font-smoothing grayscale - font-size 16px - color $textColor - -.page - padding-left $sidebarWidth - -.navbar - display flex - justify-content space-between - position fixed - z-index 20 - top 0 - left 0 - right 0 - height $navbarHeight - background-color #fff - box-sizing border-box - border-bottom 1px solid $borderColor - -.sidebar-mask - position fixed - z-index 9 - top 0 - left 0 - width 100vw - height 100vh - display none - -.sidebar - font-size 16px - background-color #fff - width $sidebarWidth - position fixed - z-index 10 - margin 0 - top $navbarHeight - left 0 - bottom 0 - box-sizing border-box - border-right 1px solid $borderColor - overflow-y auto - -{$contentClass}:not(.custom) - @extend $wrapper - > *:first-child - margin-top $navbarHeight - - a:hover - text-decoration underline - - p.demo - padding 1rem 1.5rem - border 1px solid #ddd - border-radius 4px - - img - max-width 100% - -{$contentClass}.custom - padding 0 - margin 0 - - img - max-width 100% - -a - font-weight 500 - color $accentColor - text-decoration none - -p a code - font-weight 400 - color $accentColor - -kbd - background #eee - border solid 0.15rem #ddd - border-bottom solid 0.25rem #ddd - border-radius 0.15rem - padding 0 0.15em - -blockquote - font-size 1rem - color #999; - border-left .2rem solid #dfe2e5 - margin 1rem 0 - padding .25rem 0 .25rem 1rem - - & > p - margin 0 - -ul, ol - padding-left 1.2em - -strong - font-weight 600 - -h1, h2, h3, h4, h5, h6 - font-weight 600 - line-height 1.25 - - {$contentClass}:not(.custom) > & - margin-top (0.5rem - $navbarHeight) - padding-top ($navbarHeight + 1rem) - margin-bottom 0 - - &:first-child - margin-top -1.5rem - margin-bottom 1rem - - + p, + pre, + .custom-block - margin-top 2rem - - &:hover .header-anchor - opacity: 1 - -h1 - font-size 2.2rem - -h2 - font-size 1.65rem - padding-bottom .3rem - border-bottom 1px solid $borderColor - -h3 - font-size 1.35rem - -a.header-anchor - font-size 0.85em - float left - margin-left -0.87em - padding-right 0.23em - margin-top 0.125em - opacity 0 - - &:hover - text-decoration none - -code, kbd, .line-number - font-family source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace - -p, ul, ol - line-height 1.7 - -hr - border 0 - border-top 1px solid $borderColor - -table - border-collapse collapse - margin 1rem 0 - display: block - overflow-x: auto - -tr - border-top 1px solid #dfe2e5 - - &:nth-child(2n) - background-color #f6f8fa - -th, td - border 1px solid #dfe2e5 - padding .6em 1em - -.theme-container - &.sidebar-open - .sidebar-mask - display: block - - &.no-navbar - {$contentClass}:not(.custom) > h1, h2, h3, h4, h5, h6 - margin-top 1.5rem - padding-top 0 - - .sidebar - top 0 - - -@media (min-width: ($MQMobile + 1px)) - .theme-container.no-sidebar - .sidebar - display none - - .page - padding-left 0 - -@require 'mobile.styl' diff --git a/docs/docs/.vuepress/theme/styles/mobile.styl b/docs/docs/.vuepress/theme/styles/mobile.styl deleted file mode 100644 index f5bd32739d774..0000000000000 --- a/docs/docs/.vuepress/theme/styles/mobile.styl +++ /dev/null @@ -1,37 +0,0 @@ -@require './config' - -$mobileSidebarWidth = $sidebarWidth * 0.82 - -// narrow desktop / iPad -@media (max-width: $MQNarrow) - .sidebar - font-size 15px - width $mobileSidebarWidth - .page - padding-left $mobileSidebarWidth - -// wide mobile -@media (max-width: $MQMobile) - .sidebar - top 0 - padding-top $navbarHeight - transform translateX(-100%) - transition transform .2s ease - .page - padding-left 0 - .theme-container - &.sidebar-open - .sidebar - transform translateX(0) - &.no-navbar - .sidebar - padding-top: 0 - -// narrow mobile -@media (max-width: $MQMobileNarrow) - h1 - font-size 1.9rem - {$contentClass} - div[class*="language-"] - margin 0.85rem -1.5rem - border-radius 0 diff --git a/docs/docs/.vuepress/theme/styles/navbar.styl b/docs/docs/.vuepress/theme/styles/navbar.styl deleted file mode 100644 index 597ecd80c0f26..0000000000000 --- a/docs/docs/.vuepress/theme/styles/navbar.styl +++ /dev/null @@ -1,34 +0,0 @@ -.nav-link - .badge - font-size: 0.8em - font-weight: 800 - text-transform: uppercase - - // generated from https://www.cssportal.com/css-text-gradient-generator/ - .primary - background: #3EAF7C; - background: -webkit-linear-gradient(to right, #3EAF7C 25%, #8F33B5 100%); - background: -moz-linear-gradient(to right, #3EAF7C 25%, #8F33B5 100%); - background: linear-gradient(to right, #3EAF7C 25%, #8F33B5 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - -.nav-link.primary - color: #3EAF7C; - // color: #fff - // padding: 0.6em; - // border-radius: 0.375em; - -.nav-link.primary:hover - // text-decoration: none; - // border-bottom: none !important; - // margin-bottom: auto !important; - // color: #fff !important; - -.nav-link.primary:visited - // text-decoration: none; - // border-bottom: none !important; - // margin-bottom: auto !important; - // color: #fff !important; - - diff --git a/docs/docs/.vuepress/theme/styles/toc.styl b/docs/docs/.vuepress/theme/styles/toc.styl deleted file mode 100644 index d3e71069ba79f..0000000000000 --- a/docs/docs/.vuepress/theme/styles/toc.styl +++ /dev/null @@ -1,3 +0,0 @@ -.table-of-contents - .badge - vertical-align middle diff --git a/docs/docs/.vuepress/theme/styles/wrapper.styl b/docs/docs/.vuepress/theme/styles/wrapper.styl deleted file mode 100644 index a99262c71ab37..0000000000000 --- a/docs/docs/.vuepress/theme/styles/wrapper.styl +++ /dev/null @@ -1,9 +0,0 @@ -$wrapper - max-width $contentWidth - margin 0 auto - padding 2rem 2.5rem - @media (max-width: $MQNarrow) - padding 2rem - @media (max-width: $MQMobileNarrow) - padding 1.5rem - diff --git a/docs/docs/.vuepress/theme/util/index.js b/docs/docs/.vuepress/theme/util/index.js deleted file mode 100644 index 92fcd3b311af4..0000000000000 --- a/docs/docs/.vuepress/theme/util/index.js +++ /dev/null @@ -1,244 +0,0 @@ -export const hashRE = /#.*$/ -export const extRE = /\.(md|html)$/ -export const endingSlashRE = /\/$/ -export const outboundRE = /^[a-z]+:/i - -export function normalize (path) { - return decodeURI(path) - .replace(hashRE, '') - .replace(extRE, '') -} - -export function getHash (path) { - const match = path.match(hashRE) - if (match) { - return match[0] - } -} - -export function isExternal (path) { - return outboundRE.test(path) -} - -export function isMailto (path) { - return /^mailto:/.test(path) -} - -export function isTel (path) { - return /^tel:/.test(path) -} - -export function ensureExt (path) { - if (isExternal(path)) { - return path - } - const hashMatch = path.match(hashRE) - const hash = hashMatch ? hashMatch[0] : '' - const normalized = normalize(path) - - if (endingSlashRE.test(normalized)) { - return path - } - return normalized + '.html' + hash -} - -export function isActive (route, path) { - const routeHash = decodeURIComponent(route.hash) - const linkHash = getHash(path) - if (linkHash && routeHash !== linkHash) { - return false - } - const routePath = normalize(route.path) - const pagePath = normalize(path) - return routePath === pagePath -} - -export function resolvePage (pages, rawPath, base) { - if (isExternal(rawPath)) { - return { - type: 'external', - path: rawPath - } - } - if (base) { - rawPath = resolvePath(rawPath, base) - } - const path = normalize(rawPath) - for (let i = 0; i < pages.length; i++) { - if (normalize(pages[i].regularPath) === path) { - return Object.assign({}, pages[i], { - type: 'page', - path: ensureExt(pages[i].path) - }) - } - } - console.error(`[vuepress] No matching page found for sidebar item "${rawPath}"`) - return {} -} - -function resolvePath (relative, base, append) { - const firstChar = relative.charAt(0) - if (firstChar === '/') { - return relative - } - - if (firstChar === '?' || firstChar === '#') { - return base + relative - } - - const stack = base.split('/') - - // remove trailing segment if: - // - not appending - // - appending to trailing slash (last segment is empty) - if (!append || !stack[stack.length - 1]) { - stack.pop() - } - - // resolve relative path - const segments = relative.replace(/^\//, '').split('/') - for (let i = 0; i < segments.length; i++) { - const segment = segments[i] - if (segment === '..') { - stack.pop() - } else if (segment !== '.') { - stack.push(segment) - } - } - - // ensure leading slash - if (stack[0] !== '') { - stack.unshift('') - } - - return stack.join('/') -} - -/** - * @param { Page } page - * @param { string } regularPath - * @param { SiteData } site - * @param { string } localePath - * @returns { SidebarGroup } - */ -export function resolveSidebarItems (page, regularPath, site, localePath) { - const { pages, themeConfig } = site - - const localeConfig = localePath && themeConfig.locales - ? themeConfig.locales[localePath] || themeConfig - : themeConfig - - const pageSidebarConfig = page.frontmatter.sidebar || localeConfig.sidebar || themeConfig.sidebar - if (pageSidebarConfig === 'auto') { - return resolveHeaders(page) - } - - const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar - if (!sidebarConfig) { - return [] - } else { - const { base, config } = resolveMatchingConfig(regularPath, sidebarConfig) - if (config === 'auto') { - return resolveHeaders(page) - } - return config - ? config.map(item => resolveItem(item, pages, base)) - : [] - } -} - -/** - * @param { Page } page - * @returns { SidebarGroup } - */ -function resolveHeaders (page) { - const headers = groupHeaders(page.headers || []) - return [{ - type: 'group', - collapsable: false, - title: page.title, - path: null, - children: headers.map(h => ({ - type: 'auto', - title: h.title, - basePath: page.path, - path: page.path + '#' + h.slug, - children: h.children || [] - })) - }] -} - -export function groupHeaders (headers) { - // group h3s under h2 - headers = headers.map(h => Object.assign({}, h)) - let lastH2 - headers.forEach(h => { - if (h.level === 2) { - lastH2 = h - } else if (lastH2) { - (lastH2.children || (lastH2.children = [])).push(h) - } - }) - return headers.filter(h => h.level === 2) -} - -export function resolveNavLinkItem (linkItem) { - return Object.assign(linkItem, { - type: linkItem.items && linkItem.items.length ? 'links' : 'link' - }) -} - -/** - * @param { Route } route - * @param { Array | Array | [link: string]: SidebarConfig } config - * @returns { base: string, config: SidebarConfig } - */ -export function resolveMatchingConfig (regularPath, config) { - if (Array.isArray(config)) { - return { - base: '/', - config: config - } - } - for (const base in config) { - if (ensureEndingSlash(regularPath).indexOf(encodeURI(base)) === 0) { - return { - base, - config: config[base] - } - } - } - return {} -} - -function ensureEndingSlash (path) { - return /(\.html|\/)$/.test(path) - ? path - : path + '/' -} - -function resolveItem (item, pages, base, groupDepth = 1) { - if (typeof item === 'string') { - return resolvePage(pages, item, base) - } else if (Array.isArray(item)) { - return Object.assign(resolvePage(pages, item[0], base), { - title: item[1] - }) - } else { - const children = item.children || [] - if (children.length === 0 && item.path) { - return Object.assign(resolvePage(pages, item.path, base), { - title: item.title - }) - } - return { - type: 'group', - path: item.path, - title: item.title, - sidebarDepth: item.sidebarDepth, - initialOpenGroupIndex: item.initialOpenGroupIndex, - children: children.map(child => resolveItem(child, pages, base, groupDepth + 1)), - collapsable: item.collapsable !== false - } - } -} diff --git a/docs/docs/README.md b/docs/docs/README.md deleted file mode 100644 index f74adc328c81b..0000000000000 --- a/docs/docs/README.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -prev: false -next: false ---- - -# Introduction to Pipedream - -Pipedream is the fastest way to automate any process that connects APIs. Build and run workflows with code-level control when you need it, and no code when you don't. - -The Pipedream platform includes: - -- A [serverless runtime](/code/) and [workflow service](/workflows/) -- Source-available [triggers](/workflows/steps/triggers/) and [actions](/workflows/steps/actions/) for [hundreds of integrated apps](https://pipedream.com/explore/) -- One-click [OAuth and key-based authentication](/connected-accounts/) for more than 1000 APIs (use tokens directly in code or with pre-built actions) - -Watch a demo or review our [quickstart guide](/quickstart/): - - - -## Getting Started - -To get started, [sign up for a free account](https://pipedream.com/auth/signup) (no credit card required) and follow our [quickstart guide](/quickstart/) to create your first workflow. - -![build, test,deploy](https://res.cloudinary.com/pipedreamin/image/upload/v1672810771/mjckfcgsoxs4vccutdbj.png) - -Once you understand the basics of workflow development, learn how to get more out of Pipedream: - -- [Use code in workflows](/code/) -- [Develop custom actions](/components/quickstart/nodejs/actions/) -- [Develop custom triggers](/components/quickstart/nodejs/sources/) - -## Use Cases - -Pipedream supports use cases from prototype to production and is trusted by 200k+ developers from startups to Fortune 500 companies: - -![logos](https://res.cloudinary.com/pipedreamin/image/upload/v1612919944/homepage/logos_kcbviz.png) - -The platform processes billions of events and is built and [priced](https://pipedream.com/pricing/) for use at scale. [Our team](https://pipedream.com/about) has built internet scale applications and managed data pipelines in excess of 10 million events per second (EPS) at startups and high-growth environments like BrightRoll, Yahoo!, Affirm and Instacart. - -Our [community](https://pipedream.com/community) uses Pipedream for a wide variety of use cases including: - -- Connecting SaaS apps -- General API orchestration and automation -- Database automations ([reach out](https://pipedream.com/community) to learn about connecting to resources behind a firewall) -- Custom notifications and alerting -- Mobile and JAMstack backends -- Rate limiting, request smoothing -- Event queueing and concurrency management -- Webhook inspection and routing -- Prototyping and demos - -## Source-available - -Pipedream maintains a [source-available component registry](https://github.com/pipedreamhq/pipedream/) on GitHub so you can avoid writing boilerplate code for common API integrations. Use components as no code building blocks in workflows, or use them to scaffold code that you can customize. You can also [create a PR to contribute new components](/apps/contributing/#contribution-process) via GitHub. - -## Contributing - -We hope is that by providing a generous free tier, you will not only get value from Pipedream, but you will give back to help us improve the product for the entire community and grow the platform by: - -- [Contributing components](/apps/contributing/) to the [Pipedream registry](https://github.com/pipedreamhq/pipedream) or sharing via your own GitHub repo -- Asking and answering questions in our [public community](https://pipedream.com/community/) -- [Reporting bugs](https://pipedream.com/community/c/bugs/9) and [requesting features](https://github.com/PipedreamHQ/pipedream/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=%5BFEATURE%5D+) that help us build a better product -- Following us on [Twitter](https://twitter.com/pipedream), starring our [GitHub repo](https://github.com/PipedreamHQ/pipedream) and subscribing to our [YouTube channel](https://www.youtube.com/c/pipedreamhq) -- Recommending us to your friends and colleagues - -Learn about [all the ways you can contribute](https://pipedream.com/contributing). - -## Support & Community - -If you have any questions or feedback, please [reach out in our community forum](https://pipedream.com/community). - -## Service Status - -Pipedream operates a status page at [https://status.pipedream.com](https://status.pipedream.com). That page displays the uptime history and current status of every Pipedream service. - -When incidents occur, updates are published to the **#incidents** channel of [Pipedream's Slack Community](https://pipedream.com/support) and to the [@PipedreamStatus](https://twitter.com/PipedreamStatus) account on Twitter. On the status page itself, you can also subscribe to updates directly. diff --git a/docs/docs/abuse/README.md b/docs/docs/abuse/README.md deleted file mode 100644 index decaf96031cb8..0000000000000 --- a/docs/docs/abuse/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Reporting abuse - -If you suspect Pipedream resources are being used for illegal purposes, or otherwise violate [the Pipedream Terms](https://pipedream.com/terms), please reach out to abuse@pipedream.com. - -In your abuse report, please provide as many details as possible, including: - -- The specific issue you're seeing, and what date / time it started / you observed it. -- Relevant Pipedream resources involved in the abuse, for example: the HTTP endpoint to which traffic is being sent. -- Any logs / code involved in the abuse. For example, if you're encountering a Denial-of-service attack, please include any HTTP / networking logs related to the issue. If you're reporting malware that exfiltrates data to Pipedream, please include relevant code from that malware or links to relevant reports. \ No newline at end of file diff --git a/docs/docs/airtable/oauth-migration-2024-02/README.md b/docs/docs/airtable/oauth-migration-2024-02/README.md deleted file mode 100644 index e8657ebf14970..0000000000000 --- a/docs/docs/airtable/oauth-migration-2024-02/README.md +++ /dev/null @@ -1,105 +0,0 @@ -# Update to the Airtable Integration on Pipedream (January 2024) - -Effective February 1st 2024, Airtable's API Key authentication method will be deprecated. To learn more about this change, please visit Airtable’s [dedicated support page](https://support.airtable.com/docs/airtable-api-key-deprecation-notice). - -### How will this impact my workflows? - -Starting February 1st 2024, all Pipedream steps using the legacy Airtable (API Key) integration including triggers and actions will no longer be able to authenticate with Airtable. - -### What do I need to do? -
- -1. **Reconnect your Airtable account**: - -- Visit the [accounts page in Pipedream](https://pipedream.com/accounts) -- Search for Airtable and connect your account -- This newer Pipedream integration uses OAuth instead of an API Key - -![Airtable Account Connection](https://res.cloudinary.com/dpenc2lit/image/upload/v1704392183/Screenshot_2024-01-04_at_10.16.12_AM_le364k.png) - -You can determine which workflows are connected to the legacy Airtable (API Key) app by expanding the account row on your Airtable (API Key) account connection. -![Airtable Accounts](https://res.cloudinary.com/dpenc2lit/image/upload/v1704347928/Screenshot_2024-01-03_at_9.58.43_PM_haaqlb.png) - -2. **Update Your Workflows**: - -- After reconnecting to Airtable via OAuth, you'll need to update your existing workflows that use the legacy Airtable app. -- Remove any legacy Airtable sources and re-add the source using the new Airtable app -- Remove any legacy Airtable actions and re-add them using the new Airtable app - -
- -3. **If you're using Airtable in code:** - -- Change any of your code steps to reference `airtable_oauth` instead of `airtable`. -- Modify your authorization headers accordingly - -In Node.js, you would modify your authorization header from this: - - `"Authorization": ${this.airtable.$auth.api_key}` - - to - -``` Authorization: `Bearer ${this.airtable_oauth.$auth.oauth_access_token}` ``` - -This is what your Node.js code step may have looked like before: - -``` javascript -import { axios } from "@pipedream/platform" -export default defineComponent({ - props: { - airtable: { - type: "app", - app: "airtable", - } - }, - async run({steps, $}) { - return await axios($, { - url: `https://api.airtable.com/v0/meta/whoami`, - headers: { - "Authorization": `${this.airtable.$auth.api_key}`, - "Content-Type": `application/json`, - }, - }) - }, -}) - -``` - -And here's an example of the updated code step that uses the updated app, **`airtable_oauth`** instead with the updated authentication method: - -``` javascript -import { axios } from "@pipedream/platform" -export default defineComponent({ - props: { - airtable_oauth: { - type: "app", - app: "airtable_oauth", - } - }, - async run({steps, $}) { - return await axios($, { - url: `https://api.airtable.com/v0/meta/whoami`, - headers: { - Authorization: `Bearer ${this.airtable_oauth.$auth.oauth_access_token}`, - }, - }) - }, -}) - -``` - -In Python, here's a snippet of what your code step might have looked like before: -``` python -def handler(pd: "pipedream"): - headers = {"X-Airtable-Api-Key": f'{pd.inputs["airtable"]["$auth"]["api_key"]}'} -``` - -And here's the updated Python code step, with **`airtable_oauth`** and the appropriate token and authorization headers: -``` python -def handler(pd: "pipedream"): - token = f'{pd.inputs["airtable_oauth"]["$auth"]["oauth_access_token"]}' - authorization = f'Bearer {token}' - headers = {"Authorization": authorization} -``` - -3. **Test and redeploy your workflows.** \ No newline at end of file diff --git a/docs/docs/api/README.md b/docs/docs/api/README.md deleted file mode 100644 index 7859f2099e54f..0000000000000 --- a/docs/docs/api/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# API Overview - -Pipedream currently offers [REST](/api/rest/) and [Server-sent Events (SSE)](/api/sse/) APIs. - -- Use the [REST API](/api/rest/) to create and manage sources, workflows, and - events -- Use the [SSE API](/api/sse/) to subscribe to real-time event streams for - sources - -