From 0d7b169d6a342b1dee7e006f048e8c4b5865681d Mon Sep 17 00:00:00 2001 From: Danny Roosevelt Date: Tue, 9 Apr 2024 16:11:45 -0700 Subject: [PATCH 01/13] first cut of sql-proxy docs --- docs-v2/pages/_meta.json | 9 ++++++--- docs-v2/pages/databases.mdx | 29 +++++++++++++++++++++++++++++ docs-v2/pages/workflows/_meta.json | 2 +- docs-v2/pages/workflows/vpc.mdx | 6 +----- 4 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 docs-v2/pages/databases.mdx diff --git a/docs-v2/pages/_meta.json b/docs-v2/pages/_meta.json index 4c2817881118a..543d25b57cd23 100644 --- a/docs-v2/pages/_meta.json +++ b/docs-v2/pages/_meta.json @@ -23,12 +23,15 @@ "sources": "Sources", "event-history": "Event History", "http": "HTTP", - "apps": "Integrations", - "rest-api": "API Reference", - "cli": "CLI", "environment-variables": { "title": "Environment Variables" }, + "databases": { + "title": "Databases" + }, + "apps": "Integrations", + "rest-api": "API Reference", + "cli": "CLI", "destinations": "Destinations", "user-settings": "User and Billing Settings", "troubleshooting": "Troubleshooting", diff --git a/docs-v2/pages/databases.mdx b/docs-v2/pages/databases.mdx new file mode 100644 index 0000000000000..b31b1f18992ea --- /dev/null +++ b/docs-v2/pages/databases.mdx @@ -0,0 +1,29 @@ +# Connecting to Databases +_need an intro here_ + +Every Pipedream workflow is deployed to its own virtual machine in AWS. This means your workflow runs in its own execution environment with dedicated RAM and disk space, isolated from other users' workflows. + +However, outbound traffic from your workflows uses the same network as other AWS services in the us-east-1 region. That means network requests from your workflows (e.g. an HTTP request or a connection to a database) originate from the standard range of AWS IP addresses. + +## Working with restricted databases +When you need to connect to a database that restricts access to a specific set of IP addresses, you have a couple options: + +### Create a Virtual Private Cloud (VPC) +- The most secure, recommended approach +- VPCs give you a dedicated static IP for all the workflows within your workspace +- Learn more about VPCs [here](/workflows/vpcs/) + +### Use Pipedream's SQL Proxy +- The Pipedream SQL Proxy routes network requests via a static IP block +- When you create a connected account with any of the [supported database apps](#supported-databases), steps using this account will route through the SQL Proxy, using the static IPs listed below. + +#### Supported Databases +The SQL Proxy currently supports [MySQL](https://pipedream.com/apps/mysql), [PostgreSQL](https://pipedream.com/apps/postgresql), and [Snowflake](https://pipedream.com/apps/snowflake). Please let us know if you'd like to see support for other database types! + +#### Static IPs +For database allow-listing, add the following IP block: +``` +44.223.89.56/29 +``` + +## Frequently Asked Questions \ No newline at end of file diff --git a/docs-v2/pages/workflows/_meta.json b/docs-v2/pages/workflows/_meta.json index cc843e4a1bc11..0cbcd929fb3f5 100644 --- a/docs-v2/pages/workflows/_meta.json +++ b/docs-v2/pages/workflows/_meta.json @@ -10,7 +10,7 @@ "errors": "Handling errors", "concurrency-and-throttling": "Concurrency and Throttling", "settings": "Settings", - "vpc": "Virtual Private Clouds", + "vpc": "Virtual Private Clouds (VPCs)", "domains": "Custom Domains", "sharing": "Sharing workflows", "build-and-run": { diff --git a/docs-v2/pages/workflows/vpc.mdx b/docs-v2/pages/workflows/vpc.mdx index 3f095d02f820d..8d4e2ca78f8f8 100644 --- a/docs-v2/pages/workflows/vpc.mdx +++ b/docs-v2/pages/workflows/vpc.mdx @@ -1,13 +1,9 @@ import VideoPlayer from '@/components/VideoPlayer'; -# Virtual Private Clouds +# Virtual Private Clouds (VPCs) -Every Pipedream workflow is deployed to its own virtual machine in AWS. This means your workflow's execution environment has its own RAM and disk, isolated from other users’ workflows. - -However, outbound traffic shares the same network as other AWS services deployed in the `us-east-1` region. That means network requests from your workflows (e.g. an HTTP request or a connection to a database) originate from the standard range of AWS IP addresses. - Pipedream VPCs enable you to run workflows in dedicated and isolated networks with static outbound egress IP addresses that are unique to your workspace (unlike other platforms that provide static IPs common to all customers on the platform). Outbound network requests from workflows that run in a VPC will originate from these static IP addresses, so you can whitelist access to sensitive resources (like databases and APIs) with confidence that the requests will only originate from the Pipedream workflows in your workspace. From d9f0a2e4b7006a56f19512b66bc63fbca38d2af2 Mon Sep 17 00:00:00 2001 From: Danny Roosevelt Date: Wed, 10 Apr 2024 13:07:32 -0700 Subject: [PATCH 02/13] Update databases.mdx --- docs-v2/pages/databases.mdx | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/docs-v2/pages/databases.mdx b/docs-v2/pages/databases.mdx index b31b1f18992ea..35519d6e1c384 100644 --- a/docs-v2/pages/databases.mdx +++ b/docs-v2/pages/databases.mdx @@ -1,21 +1,20 @@ -# Connecting to Databases -_need an intro here_ +# Working with Databases +Connecting to databases is a fundamental aspect of creating powerful workflows for your applications. Whether you're storing application data, querying user information, or analyzing event logs, most workflows require querying data at some step. Every Pipedream workflow is deployed to its own virtual machine in AWS. This means your workflow runs in its own execution environment with dedicated RAM and disk space, isolated from other users' workflows. -However, outbound traffic from your workflows uses the same network as other AWS services in the us-east-1 region. That means network requests from your workflows (e.g. an HTTP request or a connection to a database) originate from the standard range of AWS IP addresses. +However, outbound traffic from your workflows uses the same network as other AWS services in the us-east-1 region. **This means your workflows share AWS's `us-east-1` network, sending requests from standard AWS IP ranges.** -## Working with restricted databases +## How to Connect to Restricted Databases When you need to connect to a database that restricts access to a specific set of IP addresses, you have a couple options: ### Create a Virtual Private Cloud (VPC) -- The most secure, recommended approach -- VPCs give you a dedicated static IP for all the workflows within your workspace +- The most secure, recommended approach, since it gives you a dedicated static IP only for workflows within your workspace - Learn more about VPCs [here](/workflows/vpcs/) ### Use Pipedream's SQL Proxy -- The Pipedream SQL Proxy routes network requests via a static IP block -- When you create a connected account with any of the [supported database apps](#supported-databases), steps using this account will route through the SQL Proxy, using the static IPs listed below. +- The Pipedream SQL Proxy routes network requests through a static IP block +- When you create a connected account with any of [the apps](#supported-databases) that are currently supported by the SQL Proxy, steps using that account will route through the SQL Proxy, using the static IPs listed below. #### Supported Databases The SQL Proxy currently supports [MySQL](https://pipedream.com/apps/mysql), [PostgreSQL](https://pipedream.com/apps/postgresql), and [Snowflake](https://pipedream.com/apps/snowflake). Please let us know if you'd like to see support for other database types! @@ -26,4 +25,8 @@ For database allow-listing, add the following IP block: 44.223.89.56/29 ``` -## Frequently Asked Questions \ No newline at end of file +## Frequently Asked Questions + +#### What's the difference between the SQL Proxy and a VPC? +- While both the SQL Proxy and a VPC enable secure database connections, a VPC offers enhanced isolation and security by providing a dedicated static IP that for workflows within your workspace. +- When enabled on a workflow, all requests from that workflow go through the VPC. The SQL Proxy, on the other hand, routes requests for the relevant database connection through a shared static IP block. \ No newline at end of file From 77961900b2cb9bfa6497bcdecf39ff2d3cc5e8b0 Mon Sep 17 00:00:00 2001 From: Danny Roosevelt Date: Wed, 1 May 2024 22:56:20 -0700 Subject: [PATCH 03/13] Database docs --- docs-v2/pages/connected-accounts/api.mdx | 2 +- docs-v2/pages/databases.mdx | 32 ------------ docs-v2/pages/databases/_meta.json | 4 ++ docs-v2/pages/databases/index.mdx | 51 ++++++++++++++++++++ docs-v2/pages/databases/working-with-sql.mdx | 14 ++++++ 5 files changed, 70 insertions(+), 33 deletions(-) delete mode 100644 docs-v2/pages/databases.mdx create mode 100644 docs-v2/pages/databases/_meta.json create mode 100644 docs-v2/pages/databases/index.mdx create mode 100644 docs-v2/pages/databases/working-with-sql.mdx diff --git a/docs-v2/pages/connected-accounts/api.mdx b/docs-v2/pages/connected-accounts/api.mdx index d45f515fc3748..02f96bbef1c81 100644 --- a/docs-v2/pages/connected-accounts/api.mdx +++ b/docs-v2/pages/connected-accounts/api.mdx @@ -7,7 +7,7 @@ When you [connect an account](/connected-accounts/#connecting-accounts), you can You can also access account credentials from the Pipedream API, using them in any other tool or app where you need auth. - + The credentials API is in beta Accessing credentials via API is in **beta**, and we're looking for feedback. Please [let us know](https://pipedream.com/support) how you're using it, what's not working, and what else you'd like to see. diff --git a/docs-v2/pages/databases.mdx b/docs-v2/pages/databases.mdx deleted file mode 100644 index 35519d6e1c384..0000000000000 --- a/docs-v2/pages/databases.mdx +++ /dev/null @@ -1,32 +0,0 @@ -# Working with Databases -Connecting to databases is a fundamental aspect of creating powerful workflows for your applications. Whether you're storing application data, querying user information, or analyzing event logs, most workflows require querying data at some step. - -Every Pipedream workflow is deployed to its own virtual machine in AWS. This means your workflow runs in its own execution environment with dedicated RAM and disk space, isolated from other users' workflows. - -However, outbound traffic from your workflows uses the same network as other AWS services in the us-east-1 region. **This means your workflows share AWS's `us-east-1` network, sending requests from standard AWS IP ranges.** - -## How to Connect to Restricted Databases -When you need to connect to a database that restricts access to a specific set of IP addresses, you have a couple options: - -### Create a Virtual Private Cloud (VPC) -- The most secure, recommended approach, since it gives you a dedicated static IP only for workflows within your workspace -- Learn more about VPCs [here](/workflows/vpcs/) - -### Use Pipedream's SQL Proxy -- The Pipedream SQL Proxy routes network requests through a static IP block -- When you create a connected account with any of [the apps](#supported-databases) that are currently supported by the SQL Proxy, steps using that account will route through the SQL Proxy, using the static IPs listed below. - -#### Supported Databases -The SQL Proxy currently supports [MySQL](https://pipedream.com/apps/mysql), [PostgreSQL](https://pipedream.com/apps/postgresql), and [Snowflake](https://pipedream.com/apps/snowflake). Please let us know if you'd like to see support for other database types! - -#### Static IPs -For database allow-listing, add the following IP block: -``` -44.223.89.56/29 -``` - -## Frequently Asked Questions - -#### What's the difference between the SQL Proxy and a VPC? -- While both the SQL Proxy and a VPC enable secure database connections, a VPC offers enhanced isolation and security by providing a dedicated static IP that for workflows within your workspace. -- When enabled on a workflow, all requests from that workflow go through the VPC. The SQL Proxy, on the other hand, routes requests for the relevant database connection through a shared static IP block. \ No newline at end of file diff --git a/docs-v2/pages/databases/_meta.json b/docs-v2/pages/databases/_meta.json new file mode 100644 index 0000000000000..b9a7a332c130a --- /dev/null +++ b/docs-v2/pages/databases/_meta.json @@ -0,0 +1,4 @@ +{ + "index": "Connecting to Databases", + "working-with-sql": "Working with SQL" +} \ No newline at end of file diff --git a/docs-v2/pages/databases/index.mdx b/docs-v2/pages/databases/index.mdx new file mode 100644 index 0000000000000..245c9deb9d1f9 --- /dev/null +++ b/docs-v2/pages/databases/index.mdx @@ -0,0 +1,51 @@ +import Callout from '@/components/Callout' +import { Steps } from 'nextra/components' + +# Connecting to Databases +Communicating with a database is a fundamental component of creating powerful workflows for any application. Whether you're storing application data, querying user information, or analyzing event logs, most workflows require querying data at some step. + +Every Pipedream workflow is deployed to its own virtual machine in AWS. This means your workflow runs in its own execution environment with dedicated RAM and disk space, isolated from other users' workflows. + +However, outbound traffic from your workflows uses the same network as other AWS services in the us-east-1 region. + + + This means your workflows share the`us-east-1` network in AWS, sending requests from standard AWS IP ranges. + + +## Securing Database Connections +- The most secure way to connect to a database that restricts access to a specific set of IP addresses is to create a [Virtual Private Cloud (VPC)](/workflows/vpc). +- When you need to connect to a database that restricts access to a specific set of IP addresses, you have a couple options: + +### Create a dedicated static IP for outbound traffic +- The most secure, recommended approach, since it gives you a dedicated egress IP only for workflows within your workspace +- Learn more about VPCs in Pipedream [here](/workflows/vpc) + +### Use a shared static IP +- The Pipedream SQL Proxy routes network requests through a static IP block +- When you create a connected account with any of [the apps](#supported-databases) that are currently supported by the SQL Proxy, requests to that database from within your Pipedream workflows will come from the IP block below. + +#### Supported Databases +The SQL Proxy currently supports [MySQL](https://pipedream.com/apps/mysql), [PostgreSQL](https://pipedream.com/apps/postgresql), and [Snowflake](https://pipedream.com/apps/snowflake). Please let us know if you'd like to see support for other database types. + +#### Static IPs +For database allow-listing, add the following IP block: +``` +44.223.89.56/29 +``` + +## Connect to your database + + +### Select the "Query a Database" action + +### Select the database type + +### Configure your account + + + + +## Frequently Asked Questions + +### What's the difference between the SQL Proxy and a VPC? +Both the SQL Proxy and a VPC provide secure database connections. A VPC offers enhanced isolation and security by providing a single dedicated static IP for workflows within your workspace, and the SQL proxy routes your database connections through a dedicated set of static IP addresses. diff --git a/docs-v2/pages/databases/working-with-sql.mdx b/docs-v2/pages/databases/working-with-sql.mdx new file mode 100644 index 0000000000000..af19e998ddf0b --- /dev/null +++ b/docs-v2/pages/databases/working-with-sql.mdx @@ -0,0 +1,14 @@ +# Working with SQL + +Pipedream makes it easy to interact with SQL databases from your workflows. You can securley connect to your database and use eithr pre-built no-code triggers and actions to interact with your database, or execute custom SQL queries. + +## Schema Explorer +When querying a database, it's critical that you can understand the schema of the tables you're working with. The Schema Explorer provides a visual interface to explore the tables in your database, view their columns, and understand the relationships between them. + +- Once you connect your account with one of the [supported database apps](/databases#supported-databases), we'll fetch the schema of your database and render it in the Schema Explorer +[insert schema explorer here] +- You can view the columns of a table, their data types, and relationships between tables + +## SQL Editor +With the built-in SQL editor, + From ea78e9e5db9fbca16048b67b3d3d3c22ad7a2f85 Mon Sep 17 00:00:00 2001 From: Danny Roosevelt Date: Thu, 2 May 2024 10:02:56 -0700 Subject: [PATCH 04/13] Update _meta.json --- docs-v2/pages/databases/_meta.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-v2/pages/databases/_meta.json b/docs-v2/pages/databases/_meta.json index b9a7a332c130a..ce5a7d40ad23a 100644 --- a/docs-v2/pages/databases/_meta.json +++ b/docs-v2/pages/databases/_meta.json @@ -1,4 +1,4 @@ { "index": "Connecting to Databases", "working-with-sql": "Working with SQL" -} \ No newline at end of file +} From 36b6bf1a43d0622a81ff7cda007a882de31d8ad6 Mon Sep 17 00:00:00 2001 From: Danny Roosevelt Date: Mon, 13 May 2024 22:51:02 -0700 Subject: [PATCH 05/13] Rev on docs --- docs-v2/pages/databases/index.mdx | 30 +++++++++----------- docs-v2/pages/databases/working-with-sql.mdx | 11 ++++--- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/docs-v2/pages/databases/index.mdx b/docs-v2/pages/databases/index.mdx index 245c9deb9d1f9..280917ca39a8d 100644 --- a/docs-v2/pages/databases/index.mdx +++ b/docs-v2/pages/databases/index.mdx @@ -2,38 +2,36 @@ import Callout from '@/components/Callout' import { Steps } from 'nextra/components' # Connecting to Databases -Communicating with a database is a fundamental component of creating powerful workflows for any application. Whether you're storing application data, querying user information, or analyzing event logs, most workflows require querying data at some step. +Connecting to a database is essential for developing production workflows. Whether you're storing application data, querying user information, or analyzing event logs, most workflows and serverless functions require querying data at some step. -Every Pipedream workflow is deployed to its own virtual machine in AWS. This means your workflow runs in its own execution environment with dedicated RAM and disk space, isolated from other users' workflows. - -However, outbound traffic from your workflows uses the same network as other AWS services in the us-east-1 region. +When your workflow runs, it sends outbound traffic (requests to other APIs, databases and services) through the same network as other AWS services in the `us-east-1` region. - This means your workflows share the`us-east-1` network in AWS, sending requests from standard AWS IP ranges. + Pipedream workflows run in the AWS `us-east-1` network, sending requests from standard AWS IP ranges -## Securing Database Connections -- The most secure way to connect to a database that restricts access to a specific set of IP addresses is to create a [Virtual Private Cloud (VPC)](/workflows/vpc). -- When you need to connect to a database that restricts access to a specific set of IP addresses, you have a couple options: +## Connecting to Restricted Databases +**Unless your database is publicly accessible, you'll need to add specific IPs to your database's allow-list.** To do this, you can configure your database connection to run through a shared or dedicated static IP address from Pipedream. -### Create a dedicated static IP for outbound traffic -- The most secure, recommended approach, since it gives you a dedicated egress IP only for workflows within your workspace -- Learn more about VPCs in Pipedream [here](/workflows/vpc) +### Create a Dedicated Static IP for Outbound Traffic +- This is the most secure, recommended approach, and gives you a dedicated egress IP that is unique to your workspace +- When deployed to a VPC in Pipedream, all outbound traffic from your workflow runs through the VPC's static IP +- [Learn more here](/workflows/vpc) -### Use a shared static IP -- The Pipedream SQL Proxy routes network requests through a static IP block +### Send Requests from a Shared Static IP +- When enabled on your connected account, Pipedream will route network requests through a static IP block - When you create a connected account with any of [the apps](#supported-databases) that are currently supported by the SQL Proxy, requests to that database from within your Pipedream workflows will come from the IP block below. #### Supported Databases -The SQL Proxy currently supports [MySQL](https://pipedream.com/apps/mysql), [PostgreSQL](https://pipedream.com/apps/postgresql), and [Snowflake](https://pipedream.com/apps/snowflake). Please let us know if you'd like to see support for other database types. +The SQL Proxy currently supports [MySQL](https://pipedream.com/apps/mysql) and [PostgreSQL](https://pipedream.com/apps/postgresql), and [Snowflake](https://pipedream.com/apps/snowflake) is coming soon. Please let us know if you'd like to see support for other database types. -#### Static IPs +#### Static Shared IPs For database allow-listing, add the following IP block: ``` 44.223.89.56/29 ``` -## Connect to your database +## Connect to your Database ### Select the "Query a Database" action diff --git a/docs-v2/pages/databases/working-with-sql.mdx b/docs-v2/pages/databases/working-with-sql.mdx index af19e998ddf0b..8b9a59196035f 100644 --- a/docs-v2/pages/databases/working-with-sql.mdx +++ b/docs-v2/pages/databases/working-with-sql.mdx @@ -1,14 +1,17 @@ # Working with SQL -Pipedream makes it easy to interact with SQL databases from your workflows. You can securley connect to your database and use eithr pre-built no-code triggers and actions to interact with your database, or execute custom SQL queries. +Pipedream makes it easy to interact with SQL databases within your workflows. You can securely connect to your database and use either pre-built no-code triggers and actions to interact with your database, or execute custom SQL queries. ## Schema Explorer -When querying a database, it's critical that you can understand the schema of the tables you're working with. The Schema Explorer provides a visual interface to explore the tables in your database, view their columns, and understand the relationships between them. +When querying a database, you need to understand the schema of the tables you're working with. The schema explorer provides a visual interface to explore the tables in your database, view their columns, and understand the relationships between them. -- Once you connect your account with one of the [supported database apps](/databases#supported-databases), we'll fetch the schema of your database and render it in the Schema Explorer +- Once you connect your account with one of the [supported database apps](/databases#supported-databases), we automatically fetch and display the details of your database's schema below [insert schema explorer here] - You can view the columns of a table, their data types, and relationships between tables ## SQL Editor -With the built-in SQL editor, +With the built-in SQL editor, you access linting and auto-complete features typical of modern SQL editors. +## Prepared Statements +Prepared statements let you safely execute the same SQL query multiple times with dynamic inputs (parameters). += Prepared statements use dynamic inputs to safely execute repeated SQL queries, thus helping prevent SQL injection attacks \ No newline at end of file From a9f1d9086ebf980f750b9b16c84bedfaad2b3a56 Mon Sep 17 00:00:00 2001 From: Danny Roosevelt Date: Mon, 13 May 2024 22:59:25 -0700 Subject: [PATCH 06/13] Update index.mdx --- docs-v2/pages/databases/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-v2/pages/databases/index.mdx b/docs-v2/pages/databases/index.mdx index 280917ca39a8d..cec2b899b3d28 100644 --- a/docs-v2/pages/databases/index.mdx +++ b/docs-v2/pages/databases/index.mdx @@ -7,7 +7,7 @@ Connecting to a database is essential for developing production workflows. Wheth When your workflow runs, it sends outbound traffic (requests to other APIs, databases and services) through the same network as other AWS services in the `us-east-1` region. - Pipedream workflows run in the AWS `us-east-1` network, sending requests from standard AWS IP ranges +Pipedream workflows run in the AWS `us-east-1` network, sending requests from standard AWS IP ranges ## Connecting to Restricted Databases From 7375722395f3ec3ad45710c71ebf753bf908b00c Mon Sep 17 00:00:00 2001 From: Danny Roosevelt Date: Tue, 14 May 2024 14:29:16 -0700 Subject: [PATCH 07/13] Docs updates --- docs-v2/pages/databases/index.mdx | 23 +++++++------------- docs-v2/pages/databases/working-with-sql.mdx | 18 ++++++++++++++- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/docs-v2/pages/databases/index.mdx b/docs-v2/pages/databases/index.mdx index cec2b899b3d28..0108c4933f86f 100644 --- a/docs-v2/pages/databases/index.mdx +++ b/docs-v2/pages/databases/index.mdx @@ -1,5 +1,4 @@ import Callout from '@/components/Callout' -import { Steps } from 'nextra/components' # Connecting to Databases Connecting to a database is essential for developing production workflows. Whether you're storing application data, querying user information, or analyzing event logs, most workflows and serverless functions require querying data at some step. @@ -23,27 +22,21 @@ Pipedream workflows run in the AWS `us-east-1` network, sending requests from st - When you create a connected account with any of [the apps](#supported-databases) that are currently supported by the SQL Proxy, requests to that database from within your Pipedream workflows will come from the IP block below. #### Supported Databases -The SQL Proxy currently supports [MySQL](https://pipedream.com/apps/mysql) and [PostgreSQL](https://pipedream.com/apps/postgresql), and [Snowflake](https://pipedream.com/apps/snowflake) is coming soon. Please let us know if you'd like to see support for other database types. +Pipedream's SQL Proxy, which enables the shared static IP, currently supports [MySQL](https://pipedream.com/apps/mysql) and [PostgreSQL](https://pipedream.com/apps/postgresql), and [Snowflake](https://pipedream.com/apps/snowflake) is coming soon. Please let us know if you'd like to see support for other database types. #### Static Shared IPs -For database allow-listing, add the following IP block: +Add the following IP block to your database allow-list: ``` 44.223.89.56/29 ``` -## Connect to your Database - - -### Select the "Query a Database" action - -### Select the database type - -### Configure your account - - - ## Frequently Asked Questions -### What's the difference between the SQL Proxy and a VPC? +### What's the difference between using a shared static IP with the SQL Proxy vs a dedicated IP using a VPC? Both the SQL Proxy and a VPC provide secure database connections. A VPC offers enhanced isolation and security by providing a single dedicated static IP for workflows within your workspace, and the SQL proxy routes your database connections through a dedicated set of static IP addresses. + +| Operation | Shared Static IP (SQL Proxy) | Dedicated Static IP (VPC) | +| :----- | :----: | ----: | +| All requests to the connected database will originate from the static IP | bar | baz | +| banana | apple | kiwi | \ No newline at end of file diff --git a/docs-v2/pages/databases/working-with-sql.mdx b/docs-v2/pages/databases/working-with-sql.mdx index 8b9a59196035f..c87edc7952e78 100644 --- a/docs-v2/pages/databases/working-with-sql.mdx +++ b/docs-v2/pages/databases/working-with-sql.mdx @@ -1,3 +1,5 @@ +import { Steps } from 'nextra/components' + # Working with SQL Pipedream makes it easy to interact with SQL databases within your workflows. You can securely connect to your database and use either pre-built no-code triggers and actions to interact with your database, or execute custom SQL queries. @@ -14,4 +16,18 @@ With the built-in SQL editor, you access linting and auto-complete features typi ## Prepared Statements Prepared statements let you safely execute the same SQL query multiple times with dynamic inputs (parameters). -= Prepared statements use dynamic inputs to safely execute repeated SQL queries, thus helping prevent SQL injection attacks \ No newline at end of file += Prepared statements use dynamic inputs to safely execute repeated SQL queries, thus helping prevent SQL injection attacks + +## Getting started + + +### Select the "Execute SQL Query" action + +### Configure your account + +### Explore the schema + +### Write and execute your query + + + \ No newline at end of file From 9d693dd0a8282bbff7d456646839819429dd3963 Mon Sep 17 00:00:00 2001 From: Danny Roosevelt Date: Thu, 23 May 2024 11:12:22 -0700 Subject: [PATCH 08/13] More docs --- docs-v2/pages/databases/index.mdx | 9 +++------ docs-v2/pages/databases/working-with-sql.mdx | 17 +++++++++++++---- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/docs-v2/pages/databases/index.mdx b/docs-v2/pages/databases/index.mdx index 0108c4933f86f..86c2c0f3897b4 100644 --- a/docs-v2/pages/databases/index.mdx +++ b/docs-v2/pages/databases/index.mdx @@ -34,9 +34,6 @@ Add the following IP block to your database allow-list: ## Frequently Asked Questions ### What's the difference between using a shared static IP with the SQL Proxy vs a dedicated IP using a VPC? -Both the SQL Proxy and a VPC provide secure database connections. A VPC offers enhanced isolation and security by providing a single dedicated static IP for workflows within your workspace, and the SQL proxy routes your database connections through a dedicated set of static IP addresses. - -| Operation | Shared Static IP (SQL Proxy) | Dedicated Static IP (VPC) | -| :----- | :----: | ----: | -| All requests to the connected database will originate from the static IP | bar | baz | -| banana | apple | kiwi | \ No newline at end of file +Both the SQL Proxy and VPCs enable secure database connections from a static IP. +- VPCs offer enhanced isolation and security by providing a **dedicated** static IP for workflows within your workspace +- The SQL proxy routes requests to your database connections through a set of **shared** static IPs \ No newline at end of file diff --git a/docs-v2/pages/databases/working-with-sql.mdx b/docs-v2/pages/databases/working-with-sql.mdx index c87edc7952e78..c8a13e9eb7a81 100644 --- a/docs-v2/pages/databases/working-with-sql.mdx +++ b/docs-v2/pages/databases/working-with-sql.mdx @@ -18,16 +18,25 @@ With the built-in SQL editor, you access linting and auto-complete features typi Prepared statements let you safely execute the same SQL query multiple times with dynamic inputs (parameters). = Prepared statements use dynamic inputs to safely execute repeated SQL queries, thus helping prevent SQL injection attacks -## Getting started +## Getting Started -### Select the "Execute SQL Query" action +### Select the "Execute SQL Query" action +- From the step selector, select either "Execute MySQL Query" or "Execute PostgreSQL Query" -### Configure your account +_[add a screenshot here]_ + +### Connect your account +- If you already have a connected account for the selected database, select it from the dropdown +- If you don't have a connected account yet, click "Connect Account" to configure the database connection +- Follow the prompts to connect your account, then click "Test connection" to ensure Pipedream can connect to your database + +_[add a screenshot here]_ ### Explore the schema +_[add a screenshot here]_ ### Write and execute your query - +_[add a screenshot here]_ \ No newline at end of file From 4660c6483c97fc781af66e637aaf677c4789cb6c Mon Sep 17 00:00:00 2001 From: Danny Roosevelt Date: Thu, 23 May 2024 20:56:52 -0700 Subject: [PATCH 09/13] Update working-with-sql.mdx --- docs-v2/pages/databases/working-with-sql.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs-v2/pages/databases/working-with-sql.mdx b/docs-v2/pages/databases/working-with-sql.mdx index c8a13e9eb7a81..d509b65f84fc7 100644 --- a/docs-v2/pages/databases/working-with-sql.mdx +++ b/docs-v2/pages/databases/working-with-sql.mdx @@ -15,8 +15,7 @@ When querying a database, you need to understand the schema of the tables you're With the built-in SQL editor, you access linting and auto-complete features typical of modern SQL editors. ## Prepared Statements -Prepared statements let you safely execute the same SQL query multiple times with dynamic inputs (parameters). -= Prepared statements use dynamic inputs to safely execute repeated SQL queries, thus helping prevent SQL injection attacks +Prepared statements let you safely execute the same SQL query using dynamic inputs as parameters to help prevent SQL injection attacks. ## Getting Started @@ -34,6 +33,7 @@ _[add a screenshot here]_ _[add a screenshot here]_ ### Explore the schema + _[add a screenshot here]_ ### Write and execute your query From 4ea91a6f44e69e5e2cf127190257534c3240f59a Mon Sep 17 00:00:00 2001 From: Danny Roosevelt Date: Thu, 23 May 2024 20:58:35 -0700 Subject: [PATCH 10/13] BYOA and SQL Proxy Docs (#12118) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixing typo in GH error message (#11816) * Fixing typo in GH error message * Incrementing versions * Bump tqdm from 4.66.1 to 4.66.3 in /blog/pi (#11818) Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.66.1 to 4.66.3. - [Release notes](https://github.com/tqdm/tqdm/releases) - [Commits](https://github.com/tqdm/tqdm/compare/v4.66.1...v4.66.3) --- updated-dependencies: - dependency-name: tqdm dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump tqdm from 4.66.1 to 4.66.3 in /packages/component_code_gen (#11819) Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.66.1 to 4.66.3. - [Release notes](https://github.com/tqdm/tqdm/releases) - [Commits](https://github.com/tqdm/tqdm/compare/v4.66.1...v4.66.3) --- updated-dependencies: - dependency-name: tqdm dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Adding HIPAA docs, removing deprecated migration plan (#11729) * New Components - vryno (#11628) * vryno init * [Components] vryno #11624 Actions - Create Unique Lead * pnpm update * remove console.log * some adjusts * New Components - orimon (#11802) * orimon init * [Components] orimon #11796 Actions - Send Message * pnpm update * bug fix (#11812) * New Components - relavate (#11803) * relavate init * new components * pnpm-lock.yaml * fix typo * Telnyx - Ability to retrieve a message (#11710) * Added the ability to list phone numbers * Adjusted component/action versions * eslint * Updated constants file * constants.mjs * version * Added Retrieve Message action * Fixed error message details * versions --------- Co-authored-by: michelle0927 Co-authored-by: michelle0927 * Adding newest YT intro video * Reusing Gitlab components for Developer App (#11603) * New app/package * Creating actions reusing Gitlab actions * Package update * pnpm * Mapping propDefinitions to new app * Adjusting actions' propDefinitions * Adjusting sources to use mapped propDefinitions * Removing unused file * Fixing import error for sources * Updating list projects * Gitlab version bumps * package newline * Description update * Making username required * Fix labels and parentId optional * Fixing 'new milstone' * Adjusting 'new review request' * Update issue: fixing labels on request * Replacing node-fetch with axios * Splittin group path and group id * Adding error treatment for source * Bump jinja2 from 3.1.3 to 3.1.4 in /blog/pi (#11825) Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.3 to 3.1.4. - [Release notes](https://github.com/pallets/jinja/releases) - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/jinja/compare/3.1.3...3.1.4) --- updated-dependencies: - dependency-name: jinja2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * WooCommerce App Marketplace page (#11826) * Adding troubleshooting and getting started steps * correcting hierarchy * example use case docs * Migrated to latest rest api version (#11707) * Added actions (#11808) * New Components - tripadvisor_content_api (#11403) * tripadvisor_content_api init * Added actons * Update components/tripadvisor_content_api/actions/location-reviews/location-reviews.mjs Co-authored-by: Luan Cazarine * Update components/tripadvisor_content_api/actions/location-search/location-search.mjs Co-authored-by: Luan Cazarine * Update components/tripadvisor_content_api/tripadvisor_content_api.app.mjs Co-authored-by: Luan Cazarine * Update components/tripadvisor_content_api/tripadvisor_content_api.app.mjs Co-authored-by: Luan Cazarine * Update components/tripadvisor_content_api/tripadvisor_content_api.app.mjs Co-authored-by: Luan Cazarine * Update components/tripadvisor_content_api/tripadvisor_content_api.app.mjs Co-authored-by: Luan Cazarine * Bump package.json version * Remove redundant prop * Remove redundant prop * Revert "Remove redundant prop" This reverts commit 80b81c4f59ae96f73c1e48e99307cbc55b475b6f. * Revert "Remove redundant prop" This reverts commit bca8cf11d8ec5722f757099feca1d44be7a73329. * Fixing file name --------- Co-authored-by: Luan Cazarine Co-authored-by: Leo Vu <18277920+vunguyenhung@users.noreply.github.com> Co-authored-by: Leo Vu * Adding app scaffolding for fly_io * Adding mongodb connecting and troubleshooting (#11835) * Tested component with queries (#11828) * Firestore Firebase - parse boolean values in Update Document & Create Document (#11801) * parse boolean values * versions * Remove 0 and 1 case for boolean * Fix typos --------- Co-authored-by: Leo Vu Co-authored-by: Leo Vu <18277920+vunguyenhung@users.noreply.github.com> * Merge branch 'issue-11579' into 9395-action-zoom-app-zoom-phones-get-call-logs-action (#11781) * AWS readme (#11841) * Google Drive Triggers text/usability improvements (#11652) * Description update * Adding descriptions for update types * description adjustment * Adjustments * Text improvements * text update * Reverting specific drive change * Version bumps * Creating common file interval deduping * Applying dedupe changes for file modification sources * Version bumps * Version bumps * Moving constants to common folder * adjusting 'changes' schema * 'Share File' improvements * Sharing file adjustments * Adding advanced role options * Version adjustment * Updating component code to use PD Proxy for requests. (#11842) * Updating component code to use PD Proxy for requests. * Version bump. * Tested components (#11820) * GDrive Component description updates (#11844) * SendGrid readme (#11850) * New Components - bilflo (#11840) * bilflo init * [Components] bilflo #11732 Actions - Create Client - Assign Contract Job To Invoice - Create Contract Job * pnpm update * add datetime format description * New Components - cradl_ai (#11830) * cradl_ai init * new components * pnpm-lock.yaml * Klayvio Readme (#11852) * Zendesk API (#11853) * 9395 action zoom app zoom phones get call logs action (#11854) * Merge branch 'issue-11579' into 9395-action-zoom-app-zoom-phones-get-call-logs-action * bump version * Auto-generated documentation for apps (#11800) * Auto-generated documentation for apps * Overledger (`overledger`) * Chaser (`chaser`) * SARE (`sare`) * Encodian (`encodian`) * ByteNite (`bytenite`) * Sigma (`sigma`) * Thoughtly (`thoughtly`) * Plate Recognizer (`platerecognizer`) * Flipando (`flipando`) * Relavate (`relavate`) * TimeTonic (`timetonic`) * Cradl AI (`cradl_ai`) * Bilflo (`bilflo`) * Navigatr (`navigatr`) * ChatFly (`chatfly`) * Perplexity (`perplexity`) * FracTEL (`fractel`) * y.gy (`y_gy`) * SkyCiv (`skyciv`) * Zip Archive API (`zip_archive_api`) * Connectwise PSA (`connectwise_psa`) * Botpress (`botpress`) * Orimon (`orimon`) * Soax (`scrapein_`) * Twenty (`twenty`) * Delete components/bytenite/README.md hallucination * Delete components/flipando/README.md * Delete components/navigatr/README.md * Delete components/orimon/README.md * Delete components/thoughtly/README.md incorrect * Delete components/twenty/README.md --------- Co-authored-by: Pipedream Component Development <106282845+pipedream-component-development@users.noreply.github.com> Co-authored-by: Michael Lim <42158621+malexanderlim@users.noreply.github.com> * ESLint PR check improvement (#11846) * adding eslintignore extensions * Adding quiet to suppress non-error warnings * New Components - timetonic (#11813) * timetonic init * new components * pnpm-lock.yaml * handle link fields * upload files * parse boolean values * versions & form-data dependency * pnpm-lock.yaml * updates * Slack readme * Component republishes (#11898) * Google Sheets version bumps * Google Drive & Gitlab Dev App bumps * Zoom Admin version bumps * Google sheets import fix * Auto-generated documentation for apps (#11897) * Auto-generated documentation for apps * Fly.io (`fly_io`) * Flipando (`flipando`) * Orimon (`orimon`) * Delete components/orimon/README.md * Delete components/flipando/README.md --------- Co-authored-by: Pipedream Component Development <106282845+pipedream-component-development@users.noreply.github.com> Co-authored-by: Michael Lim <42158621+malexanderlim@users.noreply.github.com> * Updating Perplexity to latest models. (#11904) * Updating Perplexity to latest models. * Updating constants. * Updating package. * Adding app scaffolding for pro_ledger * Adding app scaffolding for wiza * Adding app scaffolding for summit * Adding app scaffolding for leadiq * docs: add tap info for brew installation (#11895) Without tapping it first you get the following error: ``` Warning: 'pipedreamhq/pd-cli/pipedream' formula is unreadable: No available formula with the name "pipedreamhq/pd-cli/pipedream". Please tap it and then try again: brew tap pipedreamhq/pd-cli ``` * Hubspot - New Event source (#11855) * update description * add configuration error * Google Sheet Sources/Triggers Improvements (#11711) * add test events * move constants.mjs to common folder * update deduping to support longer ids * update versions * combine shared & non-shared drive sources * new-comment source * add new-comment test-event * versions * versions * Google Sheets Actions Improvements (#11782) * add doc links * replace sheetName w/ worksheetId, ConfigurationErrors, updates * use alert prop for rows description * combine find-row & find-row-vlookup * combine delete-row & delete-rows * versions * fix version * bump package version * parse array inputs * combine get-values & get-values-in-range * update action names * headersDisplay prop * update cells description * update clear-row to clear-rows * versions * versions * [Components] Hotmart #8023 (#11626) * Added actions * Fixed requested changes * Fixed requested changes * Adding app scaffolding for pitchlane * Adding app scaffolding for appwrite * Bump actions/checkout from 4.1.4 to 4.1.5 (#11928) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.4 to 4.1.5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.1.4...v4.1.5) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump pnpm/action-setup from 3.0.0 to 4.0.0 (#11929) Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 3.0.0 to 4.0.0. - [Release notes](https://github.com/pnpm/action-setup/releases) - [Commits](https://github.com/pnpm/action-setup/compare/v3.0.0...v4.0.0) --- updated-dependencies: - dependency-name: pnpm/action-setup dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * New Components - twenty (#11823) * twenty init * [Components] twenty #11736 Sources - New Record (Instant) Actions - Create Update Delete Record * pnpm update * some adjusts * some adjusts * Adding app scaffolding for pidj * Adding app scaffolding for keycloak * Adding app scaffolding for insighto_ai * Adding app scaffolding for kodagpt * HIPAA on Enterprise (#11941) * Update eslintignore to ignore *.mdx (#11948) * [ACTION] Unsplash - Search Photos, Get Photo (#11939) * Tested components * pnpm-lock.yaml * Added tested components (#11845) * Tested components (#11893) * Adding app scaffolding for boloforms * New Components - chatfly (#11931) * chatfly init * [Components] chatfly #11730 Actions - SendMessage * pnpm update * Update components/chatfly/chatfly.app.mjs * fix description --------- Co-authored-by: Leo Vu <18277920+vunguyenhung@users.noreply.github.com> * New Components - thoughtly (#11933) * thoughtly init * [Components] thoughtly #11728 Sources - New Response (Instant) Actions - Trigger Call - Create Contact * pnpm update * fix import * add doc links --------- Co-authored-by: michelle0927 * Adding app partners page to docs and fixing some typos. (#11947) * Adding app partners page to docs and fixing some typos. * Update docs-v2/pages/apps/app-partners.mdx Co-authored-by: Danny Roosevelt * Update docs-v2/pages/apps/app-partners.mdx Co-authored-by: Danny Roosevelt --------- Co-authored-by: Danny Roosevelt * Adding app scaffolding for upstash_redis * chore: replace unmaintained jitterbit/get-changed-files@v1 (#11949) * [Components] leadiq (#11923) * Tested component * pnpm-lock.yaml * fix(types): dedupe unique and greatest strategy needs id (#11945) * Added actions (#11951) * New Components - fractel (#11807) * fractel init * [Components] fractel #11791 Actions - Call Phone - Send SMS/MMS * pnpm update * some adjusts * New Components - platerecognizer (#11944) * platerecognizer init * [Components] platerecognizer #11727 Actions - Run Recognition * pnpm update * [Docs] Data stores atomic operations (#11961) * add section for data stores atomic operations * link to integrated apps * chore: remove temporary continue-on-error fix for changed files (#11959) * Tested components (#11958) * [BUG] Postmark - New Inbound Email Received #11950 (#11952) * [BUG] Postmark - New Inbound Email Received #11950 Sources - New Inbound Email Received * update package.json * bump version * fix prop * New Components - boloforms (#11955) * boloforms init * [Components] boloforms #11953 Sources - New Form Response (Instant) - New Template Response (Instant) - New Signature Completed (Instant) Ations - Send Form - Send Template For Signature * pnpm update * some adjusts * Tested components (#11921) * Bump actions/checkout from 4.1.5 to 4.1.6 (#12002) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.5 to 4.1.6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.1.5...v4.1.6) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * ServiceNow docs (#11899) * ServiceNow readme instructions * Proofreading pass * Notion readme (#11900) * Update README.md (#12006) * Update README.md (#12007) * first commit for byoa docs * more BYOA docs * arcade embed * Update oauth-clients.mdx * Update oauth-clients.mdx --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dylan J. Sather Co-authored-by: Luan Cazarine Co-authored-by: michelle0927 Co-authored-by: Marcelo Jabali Co-authored-by: michelle0927 Co-authored-by: Dylan Pierce Co-authored-by: Guilherme Falcão <48412907+GTFalcao@users.noreply.github.com> Co-authored-by: Jorge Cortes Co-authored-by: Lucas Caresia Co-authored-by: Leo Vu <18277920+vunguyenhung@users.noreply.github.com> Co-authored-by: Leo Vu Co-authored-by: danhsiung <35384182+danhsiung@users.noreply.github.com> Co-authored-by: Michael Lim <42158621+malexanderlim@users.noreply.github.com> Co-authored-by: js07 <19861096+js07@users.noreply.github.com> Co-authored-by: Pipedream Component Development <106282845+pipedream-component-development@users.noreply.github.com> Co-authored-by: Joscha Feth Co-authored-by: Andrew Chuang Co-authored-by: Tod Sacerdoti --- .eslintignore | 9 + .../actions/git-diff-on-components/README.md | 2 +- .github/workflows/components-pr.yaml | 16 +- .github/workflows/docs.yaml | 2 +- .github/workflows/publish-components.yaml | 12 +- .../publish-marketplace-content.yaml | 6 +- .github/workflows/publish-packages.yaml | 4 +- .github/workflows/pull-request-checks.yaml | 14 +- README.md | 2 +- blog/pi/poetry.lock | 12 +- .../appwrite.app.mjs} | 8 +- components/appwrite/package.json | 15 ++ components/aws/README.md | 45 ++++ components/bilflo/README.md | 11 + .../assign-contract-job-to-invoice.mjs | 33 +++ .../actions/create-client/create-client.mjs | 27 +++ .../create-contract-job.mjs | 93 ++++++++ components/bilflo/bilflo.app.mjs | 68 +++++- components/bilflo/package.json | 8 +- .../boloforms/actions/send-form/send-form.mjs | 57 +++++ .../send-template-signature.mjs | 68 ++++++ components/boloforms/boloforms.app.mjs | 120 ++++++++++ components/boloforms/common/utils.mjs | 24 ++ components/boloforms/package.json | 19 ++ components/boloforms/sources/common/base.mjs | 83 +++++++ .../new-response-instant.mjs | 39 ++++ .../new-response-instant/test-event.mjs | 15 ++ .../new-signature-completed-instant.mjs | 32 +++ .../test-event.mjs | 20 ++ .../new-template-response-instant.mjs | 35 +++ .../test-event.mjs | 20 ++ components/botpress/README.md | 11 + components/chaser/README.md | 11 + components/chatfly/README.md | 11 + .../actions/send-message/send-message.mjs | 45 ++++ components/chatfly/chatfly.app.mjs | 85 ++++++- components/chatfly/common/utils.mjs | 5 + components/chatfly/package.json | 7 +- .../add-assets-to-gallery.mjs | 63 +++++ .../actions/create-gallery/create-gallery.mjs | 57 +++++ .../upload-asset-from-url.mjs | 57 +++++ components/cincopa/cincopa.app.mjs | 79 ++++++- components/cincopa/common/constants.mjs | 11 + components/cincopa/common/utils.mjs | 28 +++ components/cincopa/package.json | 19 ++ .../asset-uploaded-instant.mjs | 25 ++ components/cincopa/sources/common/events.mjs | 15 ++ components/cincopa/sources/common/webhook.mjs | 73 ++++++ .../gallery-created-instant.mjs | 25 ++ components/connectwise_psa/README.md | 11 + components/cradl_ai/README.md | 14 ++ components/cradl_ai/actions/common/common.mjs | 63 +++++ .../parse-document-human-in-loop.mjs | 41 ++++ .../actions/parse-document/parse-document.mjs | 34 +++ components/cradl_ai/common/constants.mjs | 10 + components/cradl_ai/cradl_ai.app.mjs | 205 +++++++++++++++- components/cradl_ai/package.json | 7 +- ...new-document-parsing-completed-instant.mjs | 86 +++++++ components/encodian/README.md | 11 + .../actions/common/base.mjs | 15 ++ .../create-document/create-document.mjs | 7 +- .../create-realtime-db-record.mjs | 2 +- .../actions/list-documents/list-documents.mjs | 2 +- .../update-document/update-document.mjs | 9 +- components/firebase_admin_sdk/package.json | 2 +- components/fly_io/README.md | 11 + .../fly_io/actions/create-app/create-app.mjs | 59 +++++ .../actions/create-machine/create-machine.mjs | 76 ++++++ .../actions/create-volume/create-volume.mjs | 68 ++++++ components/fly_io/common/constants.mjs | 7 + components/fly_io/common/utils.mjs | 26 +++ components/fly_io/fly_io.app.mjs | 97 ++++++++ components/fly_io/package.json | 18 ++ components/fly_io/sources/common/polling.mjs | 48 ++++ .../new-event-created/new-event-created.mjs | 55 +++++ components/fractel/README.md | 11 + .../fractel/actions/call-phone/call-phone.mjs | 44 ++++ .../actions/send-sms-mms/send-sms-mms.mjs | 60 +++++ components/fractel/common/utils.mjs | 22 ++ components/fractel/fractel.app.mjs | 71 +++++- components/fractel/package.json | 7 +- components/github/README.md | 2 +- .../actions/create-branch/create-branch.mjs | 2 +- .../actions/create-gist/create-gist.mjs | 2 +- .../create-issue-comment.mjs | 2 +- .../actions/create-issue/create-issue.mjs | 2 +- .../create-or-update-file-contents.mjs | 2 +- .../create-pull-request.mjs | 2 +- .../create-repository/create-repository.mjs | 2 +- .../get-issue-assignees.mjs | 2 +- .../get-repository-content.mjs | 2 +- .../actions/get-repository/get-repository.mjs | 2 +- .../actions/get-reviewers/get-reviewers.mjs | 2 +- .../list-gists-for-a-user.mjs | 2 +- .../actions/list-releases/list-releases.mjs | 2 +- .../search-issues-and-pull-requests.mjs | 2 +- .../actions/update-gist/update-gist.mjs | 2 +- .../actions/update-issue/update-issue.mjs | 2 +- components/github/package.json | 2 +- .../github/sources/new-branch/new-branch.mjs | 2 +- .../new-card-in-column/new-card-in-column.mjs | 2 +- .../new-collaborator/new-collaborator.mjs | 2 +- .../new-commit-comment/new-commit-comment.mjs | 2 +- .../github/sources/new-commit/new-commit.mjs | 2 +- .../sources/new-discussion/new-discussion.mjs | 2 +- .../github/sources/new-fork/new-fork.mjs | 2 +- .../github/sources/new-gist/new-gist.mjs | 2 +- .../new-issue-with-status.mjs | 2 +- .../github/sources/new-label/new-label.mjs | 2 +- .../sources/new-mention/new-mention.mjs | 2 +- .../new-notification/new-notification.mjs | 2 +- .../new-or-updated-issue.mjs | 2 +- .../new-or-updated-milestone.mjs | 2 +- .../new-or-updated-pull-request.mjs | 2 +- .../new-organization/new-organization.mjs | 2 +- .../sources/new-release/new-release.mjs | 2 +- .../sources/new-repository/new-repository.mjs | 2 +- .../new-review-request/new-review-request.mjs | 2 +- .../new-security-alert/new-security-alert.mjs | 2 +- .../new-star-by-user/new-star-by-user.mjs | 2 +- .../github/sources/new-star/new-star.mjs | 2 +- .../github/sources/new-team/new-team.mjs | 2 +- .../sources/webhook-events/webhook-events.mjs | 2 +- .../actions/create-branch/create-branch.mjs | 2 +- .../actions/create-epic/create-epic.mjs | 17 +- .../actions/create-issue/create-issue.mjs | 2 +- .../gitlab/actions/get-issue/get-issue.mjs | 2 +- .../get-repo-branch/get-repo-branch.mjs | 2 +- .../actions/list-commits/list-commits.mjs | 2 +- .../list-repo-branches/list-repo-branches.mjs | 2 +- .../actions/search-issues/search-issues.mjs | 2 +- .../actions/update-epic/update-epic.mjs | 28 +-- .../actions/update-issue/update-issue.mjs | 7 +- components/gitlab/gitlab.app.mjs | 28 ++- components/gitlab/package.json | 2 +- .../new-audit-event/new-audit-event.mjs | 13 +- .../gitlab/sources/new-branch/new-branch.mjs | 2 +- .../new-commit-comment/new-commit-comment.mjs | 2 +- .../gitlab/sources/new-commit/new-commit.mjs | 2 +- .../gitlab/sources/new-issue/new-issue.mjs | 2 +- .../sources/new-mention/new-mention.mjs | 3 +- .../new-merge-request/new-merge-request.mjs | 4 +- .../sources/new-milestone/new-milestone.mjs | 25 +- .../sources/new-project/new-project.mjs | 2 +- .../new-review-request/new-review-request.mjs | 10 +- .../actions/create-branch/create-branch.mjs | 22 ++ .../actions/create-epic/create-epic.mjs | 21 ++ .../actions/create-issue/create-issue.mjs | 22 ++ .../actions/get-issue/get-issue.mjs | 22 ++ .../get-repo-branch/get-repo-branch.mjs | 22 ++ .../actions/list-commits/list-commits.mjs | 22 ++ .../list-repo-branches/list-repo-branches.mjs | 22 ++ .../actions/search-issues/search-issues.mjs | 22 ++ .../actions/update-epic/update-epic.mjs | 22 ++ .../actions/update-issue/update-issue.mjs | 22 ++ .../gitlab_developer_app/common/utils.mjs | 40 ++++ .../gitlab_developer_app.app.mjs | 11 +- components/gitlab_developer_app/package.json | 9 +- .../new-audit-event/new-audit-event.mjs | 21 ++ .../sources/new-branch/new-branch.mjs | 21 ++ .../new-commit-comment/new-commit-comment.mjs | 21 ++ .../sources/new-commit/new-commit.mjs | 21 ++ .../sources/new-issue/new-issue.mjs | 21 ++ .../sources/new-mention/new-mention.mjs | 21 ++ .../new-merge-request/new-merge-request.mjs | 21 ++ .../sources/new-milestone/new-milestone.mjs | 21 ++ .../sources/new-project/new-project.mjs | 21 ++ .../new-review-request/new-review-request.mjs | 21 ++ .../add-contact-to-list-by-email.mjs | 2 +- components/google_ads/google_ads.app.mjs | 12 +- components/google_ads/package.json | 2 +- .../add-file-sharing-preference.mjs | 87 ++++--- .../actions/copy-file/copy-file.mjs | 2 +- .../create-file-from-template.mjs | 2 +- .../create-file-from-text.mjs | 2 +- .../actions/create-file/create-file.mjs | 2 +- .../actions/create-folder/create-folder.mjs | 4 +- .../create-shared-drive.mjs | 4 +- .../actions/delete-file/delete-file.mjs | 4 +- .../delete-shared-drive.mjs | 2 +- .../actions/download-file/download-file.mjs | 4 +- .../actions/find-file/find-file.mjs | 2 +- .../actions/find-folder/find-folder.mjs | 4 +- .../actions/find-forms/find-forms.mjs | 2 +- .../find-spreadsheets/find-spreadsheets.mjs | 2 +- .../get-folder-id-for-path.mjs | 4 +- .../get-shared-drive/get-shared-drive.mjs | 2 +- .../actions/list-files/list-files.mjs | 2 +- .../move-file-to-trash/move-file-to-trash.mjs | 4 +- .../actions/move-file/move-file.mjs | 2 +- .../actions/replace-file/replace-file.mjs | 4 +- .../search-shared-drives.mjs | 4 +- .../actions/update-file/update-file.mjs | 2 +- .../update-shared-drive.mjs | 2 +- .../actions/upload-file/upload-file.mjs | 4 +- .../google_drive/{ => common}/constants.mjs | 59 +++++ components/google_drive/common/utils.mjs | 2 +- components/google_drive/google_drive.app.mjs | 41 +--- components/google_drive/package.json | 2 +- ...changes-to-specific-files-shared-drive.mjs | 19 +- .../changes-to-specific-files.mjs | 16 +- .../sources/common-dedupe-changes.mjs | 53 +++++ .../google_drive/sources/common-webhook.mjs | 10 +- .../new-files-instant/new-files-instant.mjs | 10 +- .../new-or-modified-comments.mjs | 13 +- .../new-or-modified-files.mjs | 31 ++- .../new-or-modified-folders.mjs | 13 +- .../new-shared-drive/new-shared-drive.mjs | 2 +- .../new-spreadsheet/new-spreadsheet.mjs | 12 +- .../add-column.mjs} | 6 +- .../add-multiple-rows/add-multiple-rows.mjs | 57 ++++- .../actions/add-single-row/add-single-row.mjs | 55 +++-- .../actions/clear-cell/clear-cell.mjs | 13 +- .../actions/clear-row/clear-row.mjs | 50 ---- .../clear-rows.mjs} | 43 ++-- .../actions/copy-worksheet/copy-worksheet.mjs | 6 +- .../create-spreadsheet/create-spreadsheet.mjs | 4 +- .../create-worksheet/create-worksheet.mjs | 4 +- .../actions/delete-rows/delete-rows.mjs | 7 +- .../delete-worksheet/delete-worksheet.mjs | 4 +- .../find-row-vlookup/find-row-vlookup.mjs | 85 ------- .../actions/find-row/find-row.mjs | 42 +++- .../actions/get-cell/get-cell.mjs | 18 +- .../get-spreadsheet-by-id.mjs | 5 +- .../get-values-in-range.mjs | 16 +- .../actions/get-values/get-values.mjs | 44 ---- .../insert-anchored-note.mjs | 4 +- .../actions/insert-comment/insert-comment.mjs | 4 +- .../list-worksheets/list-worksheets.mjs | 4 +- .../actions/update-cell/update-cell.mjs | 13 +- .../update-multiple-rows.mjs} | 58 +++-- .../actions/update-row/update-row.mjs | 31 +-- .../actions/upsert-row/upsert-row.mjs | 23 +- .../google_sheets/{ => common}/constants.mjs | 0 .../google_sheets/{ => common}/utils.mjs | 32 +++ .../google_sheets/google_sheets.app.mjs | 37 +-- components/google_sheets/package.json | 2 +- .../sources/common/http-based/base.mjs | 7 +- .../sources/common/new-row-added.mjs | 30 ++- .../sources/common/new-updates.mjs | 17 +- .../sources/new-comment/new-comment.mjs | 62 +++++ .../sources/new-comment/test-event.mjs | 21 ++ .../new-row-added-shared-drive.mjs | 23 -- .../new-row-added-shared-drive/test-event.mjs | 19 -- .../sources/new-row-added/new-row-added.mjs | 6 +- .../sources/new-row-added/test-event.mjs | 19 ++ .../new-updates-shared-drive.mjs | 23 -- .../new-updates-shared-drive/test-event.mjs | 1 - .../sources/new-updates/new-updates.mjs | 4 +- .../new-worksheet-shared-drive.mjs | 25 -- .../sources/new-worksheet/new-worksheet.mjs | 6 +- .../sources/new-worksheet/test-event.mjs | 12 + components/google_slides/package.json | 2 +- .../new-presentation/new-presentation.mjs | 12 +- components/hotmart/.gitignore | 3 - .../actions/get-comissions/get-comissions.mjs | 55 +++++ .../get-sales-history/get-sales-history.mjs | 56 +++++ .../get-subscriptions/get-subscriptions.mjs | 30 +++ components/hotmart/hotmart.app.mjs | 93 ++++++++ components/hotmart/package.json | 11 +- components/hubspot/package.json | 2 +- .../sources/new-engagement/new-engagement.mjs | 8 +- .../hubspot/sources/new-event/new-event.mjs | 19 +- .../actions/add-text-blob/add-text-blob.mjs | 88 +++++++ .../actions/create-contact/create-contact.mjs | 107 +++++++++ components/insighto_ai/common/constants.mjs | 11 + components/insighto_ai/common/utils.mjs | 39 ++++ components/insighto_ai/insighto_ai.app.mjs | 210 +++++++++++++++++ components/insighto_ai/package.json | 18 ++ .../insighto_ai/sources/common/polling.mjs | 59 +++++ .../sources/new-contact/new-contact.mjs | 32 +++ components/keycloak/keycloak.app.mjs | 11 + components/keycloak/package.json | 15 ++ components/klaviyo/README.md | 96 ++++++++ components/kodagpt/kodagpt.app.mjs | 11 + components/kodagpt/package.json | 15 ++ .../actions/find-contact/find-contact.mjs | 88 +++++++ components/leadiq/common/queries.mjs | 59 +++++ components/leadiq/leadiq.app.mjs | 36 +++ components/leadiq/package.json | 18 ++ .../actions/create-bucket/create-bucket.mjs | 2 +- .../actions/create-plan/create-plan.mjs | 2 +- .../actions/create-task/create-task.mjs | 2 +- .../list-user-tasks/list-user-tasks.mjs | 21 ++ .../actions/update-task/update-task.mjs | 192 +++++++++++++++ .../microsoft_365_planner/common/utils.mjs | 28 +++ .../microsoft_365_planner.app.mjs | 66 +++++- components/microsoft_365_planner/package.json | 4 +- .../new-plan-created/new-plan-created.mjs | 2 +- .../new-task-created/new-task-created.mjs | 2 +- components/mongodb/README.md | 64 +++++ .../execute-aggregation.mjs | 64 +++++ .../actions/find-document/find-document.mjs | 75 ++++++ .../update-documents/update-documents.mjs | 69 ++++++ components/mongodb/common/utils.mjs | 63 +++++ components/mongodb/package.json | 4 +- components/notion/README.md | 72 +++++- .../actions/send-message/send-message.mjs | 43 ++++ components/orimon/orimon.app.mjs | 31 ++- components/orimon/package.json | 8 +- components/overledger/README.md | 11 + components/perplexity/README.md | 11 + .../chat-completions/chat-completions.mjs | 48 ++++ components/perplexity/common/constants.mjs | 16 ++ components/perplexity/package.json | 7 +- components/perplexity/perplexity.app.mjs | 53 ++++- components/pidj/package.json | 15 ++ components/pidj/pidj.app.mjs | 11 + .../actions/delete-vectors/delete-vectors.mjs | 37 ++- .../actions/fetch-vectors/fetch-vectors.mjs | 34 ++- .../pinecone/actions/query-ids/query-ids.mjs | 39 ++-- .../actions/update-vector/update-vector.mjs | 29 +-- .../actions/upsert-vector/upsert-vector.mjs | 27 +-- components/pinecone/common/constants.mjs | 26 +-- components/pinecone/package.json | 2 +- components/pinecone/pinecone.app.mjs | 166 +++---------- components/pitchlane/package.json | 15 ++ components/pitchlane/pitchlane.app.mjs | 11 + components/platerecognizer/README.md | 11 + .../run-recognition/run-recognition.mjs | 67 ++++++ components/platerecognizer/common/utils.mjs | 6 + components/platerecognizer/package.json | 8 +- .../platerecognizer/platerecognizer.app.mjs | 32 ++- .../actions/create-domain/create-domain.mjs | 2 +- .../create-signature/create-signature.mjs | 2 +- .../actions/delete-domain/delete-domain.mjs | 2 +- .../delete-signature/delete-signature.mjs | 2 +- .../get-bounce-counts/get-bounce-counts.mjs | 2 +- .../get-browser-usage/get-browser-usage.mjs | 2 +- .../get-click-counts/get-click-counts.mjs | 2 +- .../actions/get-domain/get-domain.mjs | 2 +- .../get-email-open-counts.mjs | 2 +- .../get-email-platform-usage.mjs | 2 +- .../get-outbound-overview.mjs | 2 +- .../get-sent-counts/get-sent-counts.mjs | 2 +- .../get-spam-complaints.mjs | 2 +- .../get-tracked-email-counts.mjs | 2 +- .../actions/list-domains/list-domains.mjs | 2 +- .../list-sender-signatures.mjs | 2 +- .../resend-confirmation.mjs | 2 +- .../rotate-dkim-keys/rotate-dkim-keys.mjs | 2 +- .../send-batch-with-templates.mjs | 2 +- .../send-email-with-template.mjs | 31 ++- .../send-single-email/send-single-email.mjs | 2 +- .../update-signature/update-signature.mjs | 2 +- .../actions/verify-dkim/verify-dkim.mjs | 2 +- .../verify-return-path/verify-return-path.mjs | 2 +- components/postmark/package.json | 2 +- components/postmark/postmark.app.mjs | 42 +++- .../sources/new-domain/new-domain.mjs | 2 +- .../new-email-opened/new-email-opened.mjs | 2 +- .../new-inbound-email-received.mjs | 27 ++- .../new-sender-signature.mjs | 2 +- components/pro_ledger/package.json | 15 ++ components/pro_ledger/pro_ledger.app.mjs | 11 + components/relavate/README.md | 14 ++ .../create-affiliate-lead.mjs | 100 ++++++++ components/relavate/package.json | 7 +- components/relavate/relavate.app.mjs | 98 +++++++- components/relavate/sources/common/base.mjs | 57 +++++ .../new-affiliate-campaign.mjs | 22 ++ .../new-affiliate-campaign/test-event.mjs | 15 ++ .../sources/new-referral/new-referral.mjs | 39 ++++ .../sources/new-referral/test-event.mjs | 19 ++ components/sare/README.md | 11 + components/scrapein_/README.md | 11 + components/sendgrid/README.md | 52 +++++ components/servicenow/README.md | 71 +++--- components/sigma/README.md | 11 + components/skyciv/README.md | 11 + components/slack/README.md | 53 ++++- components/snowflake/package.json | 2 +- .../change-to-warehouse.mjs | 2 +- components/snowflake/sources/common.mjs | 13 +- .../failed-task-in-schema.mjs | 2 +- .../sources/new-database/new-database.mjs | 2 +- .../snowflake/sources/new-role/new-role.mjs | 2 +- .../snowflake/sources/new-row/new-row.mjs | 2 +- .../sources/new-schema/new-schema.mjs | 2 +- .../snowflake/sources/new-table/new-table.mjs | 2 +- .../snowflake/sources/new-user/new-user.mjs | 2 +- .../sources/query-results/query-results.mjs | 2 +- components/summit/package.json | 15 ++ components/summit/summit.app.mjs | 11 + .../actions/get-message/get-message.mjs | 6 +- .../get-messaging-profiles.mjs | 2 +- .../get-phone-numbers/get-phone-numbers.mjs | 2 +- .../send-group-message/send-group-message.mjs | 2 +- .../actions/send-message/send-message.mjs | 2 +- components/telnyx/package.json | 2 +- components/telnyx/telnyx.app.mjs | 2 +- .../actions/create-contact/create-contact.mjs | 62 +++++ .../actions/trigger-call/trigger-call.mjs | 43 ++++ components/thoughtly/common/utils.mjs | 24 ++ components/thoughtly/package.json | 8 +- .../new-response-instant.mjs | 50 ++++ .../new-response-instant/test-event.mjs | 44 ++++ components/thoughtly/thoughtly.app.mjs | 104 ++++++++- components/timetonic/README.md | 11 + .../actions/common/create-update-row.mjs | 178 ++++++++++++++ .../actions/create-row/create-row.mjs | 10 + .../actions/delete-row/delete-row.mjs | 46 ++++ .../actions/search-rows/search-rows.mjs | 100 ++++++++ .../actions/update-row/update-row.mjs | 28 +++ components/timetonic/common/constants.mjs | 17 ++ components/timetonic/package.json | 9 +- components/timetonic/sources/common/base.mjs | 67 ++++++ .../new-table-row-in-view.mjs | 40 ++++ .../sources/new-table-row/new-table-row.mjs | 27 +++ .../sources/row-deleted/row-deleted.mjs | 41 ++++ .../sources/row-updated/row-updated.mjs | 41 ++++ components/timetonic/timetonic.app.mjs | 190 ++++++++++++++- .../location-details/location-details.mjs | 37 +++ .../location-reviews/location-reviews.mjs | 37 +++ .../location-search/location-search.mjs | 44 ++++ .../tripadvisor_content_api/package.json | 7 +- .../tripadvisor_content_api.app.mjs | 89 ++++++- .../create-update-delete-record.mjs | 137 +++++++++++ components/twenty/common/utils.mjs | 31 +++ components/twenty/package.json | 8 +- .../new-record-modified-instant.mjs | 45 ++++ .../test-event.mjs | 61 +++++ components/twenty/twenty.app.mjs | 138 ++++++++++- .../unsplash/actions/get-photo/get-photo.mjs | 41 ++++ .../actions/search-photos/search-photos.mjs | 74 ++++++ components/unsplash/common/constants.mjs | 81 +++++++ components/unsplash/common/utils.mjs | 34 +++ components/unsplash/package.json | 18 ++ components/unsplash/unsplash.app.mjs | 95 +++++++- components/upstash_redis/package.json | 15 ++ .../upstash_redis/upstash_redis.app.mjs | 11 + .../create-unique-lead/create-unique-lead.mjs | 221 ++++++++++++++++++ components/vryno/package.json | 8 +- components/vryno/vryno.app.mjs | 30 ++- .../wiza/actions/create-list/create-list.mjs | 78 +++++++ .../actions/get-contacts/get-contacts.mjs | 37 +++ components/wiza/actions/get-list/get-list.mjs | 28 +++ components/wiza/common/constants.mjs | 11 + components/wiza/package.json | 18 ++ components/wiza/wiza.app.mjs | 100 ++++++++ components/woocommerce/README.md | 50 ++++ components/y_gy/README.md | 13 ++ components/zendesk/README.md | 80 +++++++ components/zip_archive_api/README.md | 11 + .../add-meeting-registrant.mjs | 2 +- .../add-webinar-registrant.mjs | 2 +- .../actions/create-meeting/create-meeting.mjs | 2 +- .../zoom/actions/create-user/create-user.mjs | 2 +- .../zoom/actions/delete-user/delete-user.mjs | 2 +- .../get-meeting-details.mjs | 2 +- .../get-webinar-details.mjs | 2 +- .../actions/list-channels/list-channels.mjs | 2 +- .../list-past-meeting-participants.mjs | 2 +- .../list-past-webinar-qa.mjs | 2 +- .../list-user-call-logs.mjs | 31 +++ .../list-webinar-participants-report.mjs | 4 +- .../send-chat-message/send-chat-message.mjs | 2 +- .../actions/update-meeting/update-meeting.mjs | 2 +- .../actions/update-webinar/update-webinar.mjs | 2 +- .../zoom/actions/view-user/view-user.mjs | 2 +- components/zoom/package.json | 2 +- .../sources/custom-event/custom-event.mjs | 2 +- .../meeting-created/meeting-created.mjs | 2 +- .../meeting-deleted/meeting-deleted.mjs | 2 +- .../sources/meeting-ended/meeting-ended.mjs | 2 +- .../meeting-started/meeting-started.mjs | 2 +- .../meeting-updated/meeting-updated.mjs | 2 +- .../zoom/sources/phone-event/phone-event.mjs | 2 +- .../recording-completed.mjs | 2 +- .../webinar-created/webinar-created.mjs | 2 +- .../webinar-deleted/webinar-deleted.mjs | 2 +- .../sources/webinar-ended/webinar-ended.mjs | 2 +- .../webinar-started/webinar-started.mjs | 2 +- .../webinar-updated/webinar-updated.mjs | 2 +- components/zoom/zoom.app.mjs | 39 ++++ .../add-meeting-registrant.mjs | 6 +- .../add-webinar-panelist.mjs | 6 +- .../add-webinar-registrant.mjs | 6 +- .../actions/create-meeting/create-meeting.mjs | 6 +- .../actions/create-webinar/create-webinar.mjs | 6 +- .../delete-cloud-recording.mjs | 6 +- .../actions/delete-meeting/delete-meeting.mjs | 6 +- .../delete-webinar-panelist.mjs | 6 +- .../actions/delete-webinar/delete-webinar.mjs | 6 +- .../actions/end-meeting/end-meeting.mjs | 6 +- .../actions/get-meeting/get-meeting.mjs | 6 +- .../actions/get-webinar/get-webinar.mjs | 6 +- .../list-account-call-logs.mjs | 23 ++ .../list-cloud-recordings.mjs | 6 +- .../list-meeting-registrants.mjs | 6 +- .../actions/list-meetings/list-meetings.mjs | 6 +- .../list-webinar-participants.mjs | 2 +- .../list-webinar-registrants.mjs | 6 +- .../actions/list-webinars/list-webinars.mjs | 2 +- .../actions/update-meeting/update-meeting.mjs | 8 +- .../update-webinar-registrant-status.mjs | 6 +- .../actions/update-webinar/update-webinar.mjs | 8 +- components/zoom_admin/package.json | 2 +- .../account-created/account-created.mjs | 2 +- .../account-settings-updated.mjs | 2 +- .../account-updated/account-updated.mjs | 2 +- .../sources/custom-events/custom-events.mjs | 2 +- .../meeting-created/meeting-created.mjs | 2 +- .../meeting-deleted/meeting-deleted.mjs | 2 +- .../sources/meeting-ended/meeting-ended.mjs | 2 +- .../meeting-started/meeting-started.mjs | 4 +- .../meeting-updated/meeting-updated.mjs | 2 +- .../recording-completed.mjs | 2 +- .../sources/user-activated/user-activated.mjs | 6 +- .../sources/user-created/user-created.mjs | 2 +- .../user-deactivated/user-deactivated.mjs | 2 +- .../sources/user-deleted/user-deleted.mjs | 2 +- .../user-invitation-accepted.mjs | 2 +- .../sources/user-updated/user-updated.mjs | 2 +- .../webinar-changes-to-panelists.mjs | 6 +- .../webinar-created/webinar-created.mjs | 2 +- .../webinar-deleted/webinar-deleted.mjs | 2 +- .../sources/webinar-ended/webinar-ended.mjs | 2 +- .../webinar-started/webinar-started.mjs | 2 +- .../webinar-updated/webinar-updated.mjs | 2 +- components/zoom_admin/zoom_admin.app.mjs | 12 +- docs-v2/components/ArcadeEmbed.tsx | 34 +++ docs-v2/pages/_meta.json | 3 - docs-v2/pages/apps/_meta.json | 3 +- docs-v2/pages/apps/app-partners.mdx | 23 ++ docs-v2/pages/apps/contributing.mdx | 2 +- docs-v2/pages/apps/index.mdx | 2 +- docs-v2/pages/cli/install.mdx | 1 + docs-v2/pages/components/guidelines.mdx | 10 +- docs-v2/pages/connected-accounts/_meta.json | 3 +- .../connected-accounts/oauth-clients.mdx | 51 ++++ docs-v2/pages/data-stores.mdx | 5 + docs-v2/pages/index.mdx | 2 +- docs-v2/pages/privacy-and-security/_meta.json | 1 + .../privacy-and-security/best-practices.mdx | 2 - docs-v2/pages/privacy-and-security/hipaa.mdx | 38 +++ docs-v2/pages/privacy-and-security/index.mdx | 4 + .../workspaces-and-credits-faq/_meta.json | 5 - .../workspaces-and-credits-faq/index.mdx | 55 ----- docs-v2/vercel.json | 4 + packages/component_code_gen/poetry.lock | 6 +- pnpm-lock.yaml | 161 +++++++++++-- types/src/index.ts | 44 +++- types/src/types.test.ts | 14 ++ 544 files changed, 10553 insertions(+), 1512 deletions(-) rename components/{hotmart/app/hotmart.app.ts => appwrite/appwrite.app.mjs} (64%) create mode 100644 components/appwrite/package.json create mode 100644 components/bilflo/README.md create mode 100644 components/bilflo/actions/assign-contract-job-to-invoice/assign-contract-job-to-invoice.mjs create mode 100644 components/bilflo/actions/create-client/create-client.mjs create mode 100644 components/bilflo/actions/create-contract-job/create-contract-job.mjs create mode 100644 components/boloforms/actions/send-form/send-form.mjs create mode 100644 components/boloforms/actions/send-template-signature/send-template-signature.mjs create mode 100644 components/boloforms/boloforms.app.mjs create mode 100644 components/boloforms/common/utils.mjs create mode 100644 components/boloforms/package.json create mode 100644 components/boloforms/sources/common/base.mjs create mode 100644 components/boloforms/sources/new-response-instant/new-response-instant.mjs create mode 100644 components/boloforms/sources/new-response-instant/test-event.mjs create mode 100644 components/boloforms/sources/new-signature-completed-instant/new-signature-completed-instant.mjs create mode 100644 components/boloforms/sources/new-signature-completed-instant/test-event.mjs create mode 100644 components/boloforms/sources/new-template-response-instant/new-template-response-instant.mjs create mode 100644 components/boloforms/sources/new-template-response-instant/test-event.mjs create mode 100644 components/botpress/README.md create mode 100644 components/chaser/README.md create mode 100644 components/chatfly/README.md create mode 100644 components/chatfly/actions/send-message/send-message.mjs create mode 100644 components/chatfly/common/utils.mjs create mode 100644 components/cincopa/actions/add-assets-to-gallery/add-assets-to-gallery.mjs create mode 100644 components/cincopa/actions/create-gallery/create-gallery.mjs create mode 100644 components/cincopa/actions/upload-asset-from-url/upload-asset-from-url.mjs create mode 100644 components/cincopa/common/constants.mjs create mode 100644 components/cincopa/common/utils.mjs create mode 100644 components/cincopa/package.json create mode 100644 components/cincopa/sources/asset-uploaded-instant/asset-uploaded-instant.mjs create mode 100644 components/cincopa/sources/common/events.mjs create mode 100755 components/cincopa/sources/common/webhook.mjs create mode 100644 components/cincopa/sources/gallery-created-instant/gallery-created-instant.mjs create mode 100644 components/connectwise_psa/README.md create mode 100644 components/cradl_ai/README.md create mode 100644 components/cradl_ai/actions/common/common.mjs create mode 100644 components/cradl_ai/actions/parse-document-human-in-loop/parse-document-human-in-loop.mjs create mode 100644 components/cradl_ai/actions/parse-document/parse-document.mjs create mode 100644 components/cradl_ai/common/constants.mjs create mode 100644 components/cradl_ai/sources/new-document-parsing-completed-instant/new-document-parsing-completed-instant.mjs create mode 100644 components/encodian/README.md create mode 100644 components/fly_io/README.md create mode 100644 components/fly_io/actions/create-app/create-app.mjs create mode 100644 components/fly_io/actions/create-machine/create-machine.mjs create mode 100644 components/fly_io/actions/create-volume/create-volume.mjs create mode 100644 components/fly_io/common/constants.mjs create mode 100644 components/fly_io/common/utils.mjs create mode 100644 components/fly_io/fly_io.app.mjs create mode 100644 components/fly_io/package.json create mode 100644 components/fly_io/sources/common/polling.mjs create mode 100644 components/fly_io/sources/new-event-created/new-event-created.mjs create mode 100644 components/fractel/README.md create mode 100644 components/fractel/actions/call-phone/call-phone.mjs create mode 100644 components/fractel/actions/send-sms-mms/send-sms-mms.mjs create mode 100644 components/fractel/common/utils.mjs create mode 100644 components/gitlab_developer_app/actions/create-branch/create-branch.mjs create mode 100644 components/gitlab_developer_app/actions/create-epic/create-epic.mjs create mode 100644 components/gitlab_developer_app/actions/create-issue/create-issue.mjs create mode 100644 components/gitlab_developer_app/actions/get-issue/get-issue.mjs create mode 100644 components/gitlab_developer_app/actions/get-repo-branch/get-repo-branch.mjs create mode 100644 components/gitlab_developer_app/actions/list-commits/list-commits.mjs create mode 100644 components/gitlab_developer_app/actions/list-repo-branches/list-repo-branches.mjs create mode 100644 components/gitlab_developer_app/actions/search-issues/search-issues.mjs create mode 100644 components/gitlab_developer_app/actions/update-epic/update-epic.mjs create mode 100644 components/gitlab_developer_app/actions/update-issue/update-issue.mjs create mode 100644 components/gitlab_developer_app/common/utils.mjs create mode 100644 components/gitlab_developer_app/sources/new-audit-event/new-audit-event.mjs create mode 100644 components/gitlab_developer_app/sources/new-branch/new-branch.mjs create mode 100644 components/gitlab_developer_app/sources/new-commit-comment/new-commit-comment.mjs create mode 100644 components/gitlab_developer_app/sources/new-commit/new-commit.mjs create mode 100644 components/gitlab_developer_app/sources/new-issue/new-issue.mjs create mode 100644 components/gitlab_developer_app/sources/new-mention/new-mention.mjs create mode 100644 components/gitlab_developer_app/sources/new-merge-request/new-merge-request.mjs create mode 100644 components/gitlab_developer_app/sources/new-milestone/new-milestone.mjs create mode 100644 components/gitlab_developer_app/sources/new-project/new-project.mjs create mode 100644 components/gitlab_developer_app/sources/new-review-request/new-review-request.mjs rename components/google_drive/{ => common}/constants.mjs (75%) create mode 100644 components/google_drive/sources/common-dedupe-changes.mjs rename components/google_sheets/actions/{create-column/create-column.mjs => add-column/add-column.mjs} (91%) delete mode 100644 components/google_sheets/actions/clear-row/clear-row.mjs rename components/google_sheets/actions/{delete-row/delete-row.mjs => clear-rows/clear-rows.mjs} (51%) delete mode 100644 components/google_sheets/actions/find-row-vlookup/find-row-vlookup.mjs delete mode 100644 components/google_sheets/actions/get-values/get-values.mjs rename components/google_sheets/actions/{update-rows/update-rows.mjs => update-multiple-rows/update-multiple-rows.mjs} (61%) rename components/google_sheets/{ => common}/constants.mjs (100%) rename components/google_sheets/{ => common}/utils.mjs (67%) create mode 100644 components/google_sheets/sources/new-comment/new-comment.mjs create mode 100644 components/google_sheets/sources/new-comment/test-event.mjs delete mode 100644 components/google_sheets/sources/new-row-added-shared-drive/new-row-added-shared-drive.mjs delete mode 100644 components/google_sheets/sources/new-row-added-shared-drive/test-event.mjs create mode 100644 components/google_sheets/sources/new-row-added/test-event.mjs delete mode 100644 components/google_sheets/sources/new-updates-shared-drive/new-updates-shared-drive.mjs delete mode 100644 components/google_sheets/sources/new-updates-shared-drive/test-event.mjs delete mode 100644 components/google_sheets/sources/new-worksheet-shared-drive/new-worksheet-shared-drive.mjs create mode 100644 components/google_sheets/sources/new-worksheet/test-event.mjs delete mode 100644 components/hotmart/.gitignore create mode 100644 components/hotmart/actions/get-comissions/get-comissions.mjs create mode 100644 components/hotmart/actions/get-sales-history/get-sales-history.mjs create mode 100644 components/hotmart/actions/get-subscriptions/get-subscriptions.mjs create mode 100644 components/hotmart/hotmart.app.mjs create mode 100644 components/insighto_ai/actions/add-text-blob/add-text-blob.mjs create mode 100644 components/insighto_ai/actions/create-contact/create-contact.mjs create mode 100644 components/insighto_ai/common/constants.mjs create mode 100644 components/insighto_ai/common/utils.mjs create mode 100644 components/insighto_ai/insighto_ai.app.mjs create mode 100644 components/insighto_ai/package.json create mode 100644 components/insighto_ai/sources/common/polling.mjs create mode 100644 components/insighto_ai/sources/new-contact/new-contact.mjs create mode 100644 components/keycloak/keycloak.app.mjs create mode 100644 components/keycloak/package.json create mode 100644 components/kodagpt/kodagpt.app.mjs create mode 100644 components/kodagpt/package.json create mode 100644 components/leadiq/actions/find-contact/find-contact.mjs create mode 100644 components/leadiq/common/queries.mjs create mode 100644 components/leadiq/leadiq.app.mjs create mode 100644 components/leadiq/package.json create mode 100644 components/microsoft_365_planner/actions/list-user-tasks/list-user-tasks.mjs create mode 100644 components/microsoft_365_planner/actions/update-task/update-task.mjs create mode 100644 components/microsoft_365_planner/common/utils.mjs create mode 100644 components/mongodb/actions/execute-aggregation/execute-aggregation.mjs create mode 100644 components/mongodb/actions/find-document/find-document.mjs create mode 100644 components/mongodb/actions/update-documents/update-documents.mjs create mode 100644 components/mongodb/common/utils.mjs create mode 100644 components/orimon/actions/send-message/send-message.mjs create mode 100644 components/overledger/README.md create mode 100644 components/perplexity/README.md create mode 100644 components/perplexity/actions/chat-completions/chat-completions.mjs create mode 100644 components/perplexity/common/constants.mjs create mode 100644 components/pidj/package.json create mode 100644 components/pidj/pidj.app.mjs create mode 100644 components/pitchlane/package.json create mode 100644 components/pitchlane/pitchlane.app.mjs create mode 100644 components/platerecognizer/README.md create mode 100644 components/platerecognizer/actions/run-recognition/run-recognition.mjs create mode 100644 components/platerecognizer/common/utils.mjs create mode 100644 components/pro_ledger/package.json create mode 100644 components/pro_ledger/pro_ledger.app.mjs create mode 100644 components/relavate/README.md create mode 100644 components/relavate/actions/create-affiliate-lead/create-affiliate-lead.mjs create mode 100644 components/relavate/sources/common/base.mjs create mode 100644 components/relavate/sources/new-affiliate-campaign/new-affiliate-campaign.mjs create mode 100644 components/relavate/sources/new-affiliate-campaign/test-event.mjs create mode 100644 components/relavate/sources/new-referral/new-referral.mjs create mode 100644 components/relavate/sources/new-referral/test-event.mjs create mode 100644 components/sare/README.md create mode 100644 components/scrapein_/README.md create mode 100644 components/sigma/README.md create mode 100644 components/skyciv/README.md create mode 100644 components/summit/package.json create mode 100644 components/summit/summit.app.mjs create mode 100644 components/thoughtly/actions/create-contact/create-contact.mjs create mode 100644 components/thoughtly/actions/trigger-call/trigger-call.mjs create mode 100644 components/thoughtly/common/utils.mjs create mode 100644 components/thoughtly/sources/new-response-instant/new-response-instant.mjs create mode 100644 components/thoughtly/sources/new-response-instant/test-event.mjs create mode 100644 components/timetonic/README.md create mode 100644 components/timetonic/actions/common/create-update-row.mjs create mode 100644 components/timetonic/actions/create-row/create-row.mjs create mode 100644 components/timetonic/actions/delete-row/delete-row.mjs create mode 100644 components/timetonic/actions/search-rows/search-rows.mjs create mode 100644 components/timetonic/actions/update-row/update-row.mjs create mode 100644 components/timetonic/common/constants.mjs create mode 100644 components/timetonic/sources/common/base.mjs create mode 100644 components/timetonic/sources/new-table-row-in-view/new-table-row-in-view.mjs create mode 100644 components/timetonic/sources/new-table-row/new-table-row.mjs create mode 100644 components/timetonic/sources/row-deleted/row-deleted.mjs create mode 100644 components/timetonic/sources/row-updated/row-updated.mjs create mode 100644 components/tripadvisor_content_api/actions/location-details/location-details.mjs create mode 100644 components/tripadvisor_content_api/actions/location-reviews/location-reviews.mjs create mode 100644 components/tripadvisor_content_api/actions/location-search/location-search.mjs create mode 100644 components/twenty/actions/create-update-delete-record/create-update-delete-record.mjs create mode 100644 components/twenty/common/utils.mjs create mode 100644 components/twenty/sources/new-record-modified-instant/new-record-modified-instant.mjs create mode 100644 components/twenty/sources/new-record-modified-instant/test-event.mjs create mode 100644 components/unsplash/actions/get-photo/get-photo.mjs create mode 100644 components/unsplash/actions/search-photos/search-photos.mjs create mode 100644 components/unsplash/common/constants.mjs create mode 100644 components/unsplash/common/utils.mjs create mode 100644 components/unsplash/package.json create mode 100644 components/upstash_redis/package.json create mode 100644 components/upstash_redis/upstash_redis.app.mjs create mode 100644 components/vryno/actions/create-unique-lead/create-unique-lead.mjs create mode 100644 components/wiza/actions/create-list/create-list.mjs create mode 100644 components/wiza/actions/get-contacts/get-contacts.mjs create mode 100644 components/wiza/actions/get-list/get-list.mjs create mode 100644 components/wiza/common/constants.mjs create mode 100644 components/wiza/package.json create mode 100644 components/wiza/wiza.app.mjs create mode 100644 components/y_gy/README.md create mode 100644 components/zip_archive_api/README.md create mode 100644 components/zoom/actions/list-user-call-logs/list-user-call-logs.mjs create mode 100644 components/zoom_admin/actions/list-account-call-logs/list-account-call-logs.mjs create mode 100644 docs-v2/components/ArcadeEmbed.tsx create mode 100644 docs-v2/pages/apps/app-partners.mdx create mode 100644 docs-v2/pages/connected-accounts/oauth-clients.mdx create mode 100644 docs-v2/pages/privacy-and-security/hipaa.mdx delete mode 100644 docs-v2/pages/workspaces-and-credits-faq/_meta.json delete mode 100644 docs-v2/pages/workspaces-and-credits-faq/index.mdx diff --git a/.eslintignore b/.eslintignore index 9581c2fd7b508..6d9adfdec6164 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,3 +3,12 @@ node_modules package-lock.json pnpm-lock.yaml platform/dist +*.md +*.mdx +*.txt +*.yml +*.yaml +*.lock +*.py +*.png + diff --git a/.github/actions/git-diff-on-components/README.md b/.github/actions/git-diff-on-components/README.md index cff8d551b9a4c..5a2dcd1988d67 100644 --- a/.github/actions/git-diff-on-components/README.md +++ b/.github/actions/git-diff-on-components/README.md @@ -14,7 +14,7 @@ This action takes care of all components with dependencies that were modified bu ### `all_files` -**Required** List of all files comming from `changed_files` step in `check_version` job github action workflow. It is necessary to set the action `jitterbit/get-changed-files@v1` output in json format like +**Required** List of all files comming from `changed_files` step in `check_version` job github action workflow. It is necessary to set the action `Ana06/get-changed-files@v2.3.0` output in json format like ``` ... with: diff --git a/.github/workflows/components-pr.yaml b/.github/workflows/components-pr.yaml index a22f284f36890..58dfadcb6f4a0 100644 --- a/.github/workflows/components-pr.yaml +++ b/.github/workflows/components-pr.yaml @@ -18,7 +18,7 @@ jobs: pull-requests: write steps: - - uses: actions/checkout@v4.1.4 + - uses: actions/checkout@v4.1.6 name: Checkout repo with: # See https://github.com/actions/checkout#checkout-v2 @@ -30,7 +30,7 @@ jobs: # we have to fetch the entire history. See # https://github.com/actions/checkout/issues/266#issuecomment-638346893 fetch-depth: 0 - - uses: jitterbit/get-changed-files@v1 + - uses: Ana06/get-changed-files@v2.3.0 id: changed_files name: Get changed files with: @@ -48,8 +48,8 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4.1.4 - - uses: pnpm/action-setup@v3.0.0 + uses: actions/checkout@v4.1.6 + - uses: pnpm/action-setup@v4.0.0 with: version: 7.33.6 - name: Get pnpm store directory @@ -76,7 +76,7 @@ jobs: run: npm run build > files.txt - name: Get Changed Files id: files - uses: jitterbit/get-changed-files@v1 + uses: Ana06/get-changed-files@v2.3.0 with: format: 'csv' - name: Check For Compiled TypeScript Files @@ -137,8 +137,8 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4.1.4 - - uses: pnpm/action-setup@v3.0.0 + uses: actions/checkout@v4.1.6 + - uses: pnpm/action-setup@v4.0.0 with: version: 7.33.6 - name: Get pnpm store directory @@ -165,7 +165,7 @@ jobs: run: npm run build > files.txt - name: Get Changed Files id: files - uses: jitterbit/get-changed-files@v1 + uses: Ana06/get-changed-files@v2.3.0 with: format: 'csv' - name: Publish TypeScript components (dry run) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 4da835d33a488..ac0a7c969af23 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.4 + - uses: actions/checkout@v4.1.6 name: Checkout repo with: # See https://github.com/actions/checkout#checkout-v2 diff --git a/.github/workflows/publish-components.yaml b/.github/workflows/publish-components.yaml index f51b229dae3d2..838b158390c3e 100644 --- a/.github/workflows/publish-components.yaml +++ b/.github/workflows/publish-components.yaml @@ -11,8 +11,8 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4.1.4 - - uses: pnpm/action-setup@v3.0.0 + uses: actions/checkout@v4.1.6 + - uses: pnpm/action-setup@v4.0.0 with: version: 7.33.6 - name: Get pnpm store directory @@ -48,7 +48,7 @@ jobs: echo "org_id = $PD_ORG_ID" >> $HOME/.config/pipedream/config - name: Get Changed Files id: files - uses: jitterbit/get-changed-files@v1 + uses: Ana06/get-changed-files@v2.3.0 with: format: 'csv' - name: Publish and add to registry components/*.*js (that aren't .app.js files) @@ -117,8 +117,8 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4.1.4 - - uses: pnpm/action-setup@v3.0.0 + uses: actions/checkout@v4.1.6 + - uses: pnpm/action-setup@v4.0.0 with: version: 7.33.6 - name: Get pnpm store directory @@ -155,7 +155,7 @@ jobs: run: npm run build > files.txt - name: Get Changed Files id: files - uses: jitterbit/get-changed-files@v1 + uses: Ana06/get-changed-files@v2.3.0 with: format: 'csv' - name: Publish TypeScript components (dry run) diff --git a/.github/workflows/publish-marketplace-content.yaml b/.github/workflows/publish-marketplace-content.yaml index c72454150e32c..242cb8eb1c86b 100644 --- a/.github/workflows/publish-marketplace-content.yaml +++ b/.github/workflows/publish-marketplace-content.yaml @@ -11,8 +11,8 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4.1.4 - - uses: pnpm/action-setup@v3.0.0 + uses: actions/checkout@v4.1.6 + - uses: pnpm/action-setup@v4.0.0 with: version: 7.33.6 - name: Get pnpm store directory @@ -36,7 +36,7 @@ jobs: cache: 'pnpm' - name: Get Changed Files id: files - uses: jitterbit/get-changed-files@v1 + uses: Ana06/get-changed-files@v2.3.0 with: format: 'csv' - name: Publish changes to marketplace content diff --git a/.github/workflows/publish-packages.yaml b/.github/workflows/publish-packages.yaml index 17c14d86f0379..f2696bf7b6938 100644 --- a/.github/workflows/publish-packages.yaml +++ b/.github/workflows/publish-packages.yaml @@ -20,8 +20,8 @@ jobs: concurrency: group: ${{ github.workflow }}-${{ github.ref }} steps: - - uses: actions/checkout@v4.1.4 - - uses: pnpm/action-setup@v3.0.0 + - uses: actions/checkout@v4.1.6 + - uses: pnpm/action-setup@v4.0.0 with: version: 7.33.6 - name: Get pnpm store directory diff --git a/.github/workflows/pull-request-checks.yaml b/.github/workflows/pull-request-checks.yaml index 12a90e3ad68e1..9e9a416017902 100644 --- a/.github/workflows/pull-request-checks.yaml +++ b/.github/workflows/pull-request-checks.yaml @@ -16,9 +16,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.4 + - uses: actions/checkout@v4.1.6 name: Checkout - - uses: jitterbit/get-changed-files@v1 + - uses: Ana06/get-changed-files@v2.3.0 id: changed_files name: Get changed files - id: md_changed_files @@ -48,12 +48,12 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.6 with: # Full git history is needed to get a proper list of changed files # within `super-linter` fetch-depth: 0 - - uses: pnpm/action-setup@v3.0.0 + - uses: pnpm/action-setup@v4.0.0 with: version: 7.33.6 - name: Get pnpm store directory @@ -90,14 +90,14 @@ jobs: # ESLint only on changed files (not the same as the above super-linter) - name: Get Changed Files (space-separated) id: changed_files_space - uses: jitterbit/get-changed-files@v1 + uses: Ana06/get-changed-files@v2.3.0 with: format: 'space-delimited' - name: Lint changed files - run: npx eslint ${{ steps.changed_files_space.outputs.added_modified }} ${{ steps.changed_files_space.outputs.renamed }} + run: npx eslint --quiet ${{ steps.changed_files_space.outputs.added_modified }} ${{ steps.changed_files_space.outputs.renamed }} - name: Get Changed Files (comma-separated) id: changed_files - uses: jitterbit/get-changed-files@v1 + uses: Ana06/get-changed-files@v2.3.0 with: format: 'csv' # NOTE: These steps are kept in this workflow to avoid re-rerunning the rest of the lint job diff --git a/README.md b/README.md index 748b496cdb9b1..503e48572e622 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Click the image below to watch a brief demo on YouTube.


- + Pipedream demo static image

diff --git a/blog/pi/poetry.lock b/blog/pi/poetry.lock index fe4614acd34a5..6b09e85be9427 100644 --- a/blog/pi/poetry.lock +++ b/blog/pi/poetry.lock @@ -1428,13 +1428,13 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] [[package]] name = "jinja2" -version = "3.1.3" +version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] @@ -4851,13 +4851,13 @@ files = [ [[package]] name = "tqdm" -version = "4.66.1" +version = "4.66.3" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" files = [ - {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"}, - {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"}, + {file = "tqdm-4.66.3-py3-none-any.whl", hash = "sha256:4f41d54107ff9a223dca80b53efe4fb654c67efaba7f47bada3ee9d50e05bd53"}, + {file = "tqdm-4.66.3.tar.gz", hash = "sha256:23097a41eba115ba99ecae40d06444c15d1c0c698d527a01c6c8bd1c5d0647e5"}, ] [package.dependencies] diff --git a/components/hotmart/app/hotmart.app.ts b/components/appwrite/appwrite.app.mjs similarity index 64% rename from components/hotmart/app/hotmart.app.ts rename to components/appwrite/appwrite.app.mjs index 69f74aaab0709..b521c0f3fe439 100644 --- a/components/hotmart/app/hotmart.app.ts +++ b/components/appwrite/appwrite.app.mjs @@ -1,8 +1,6 @@ -import { defineApp } from "@pipedream/types"; - -export default defineApp({ +export default { type: "app", - app: "hotmart", + app: "appwrite", propDefinitions: {}, methods: { // this.$auth contains connected account data @@ -10,4 +8,4 @@ export default defineApp({ console.log(Object.keys(this.$auth)); }, }, -}); \ No newline at end of file +}; \ No newline at end of file diff --git a/components/appwrite/package.json b/components/appwrite/package.json new file mode 100644 index 0000000000000..fdc7a3bac1427 --- /dev/null +++ b/components/appwrite/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pipedream/appwrite", + "version": "0.0.1", + "description": "Pipedream Appwrite Components", + "main": "appwrite.app.mjs", + "keywords": [ + "pipedream", + "appwrite" + ], + "homepage": "https://pipedream.com/apps/appwrite", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/components/aws/README.md b/components/aws/README.md index 1ef5a777c26c6..d67c26dadb3ac 100644 --- a/components/aws/README.md +++ b/components/aws/README.md @@ -2,6 +2,51 @@ The AWS API unlocks endless possibilities for automation with Pipedream. With this powerful combo, you can manage your AWS services and resources, automate deployment workflows, process data, and react to events across your AWS infrastructure. Pipedream offers a serverless platform for creating workflows triggered by various events that can execute AWS SDK functions, making it an efficient tool to integrate, automate, and orchestrate tasks across AWS services and other apps. +# Getting Started + +To get started, first [log in to the AWS console](https://signin.aws.amazon.com/signin). + +Once you've logged in, navigate to the Identity and Access Management (IAM) service. Then click the **Users** section: + +![Open the users management area in the AWS IAM service](https://res.cloudinary.com/pipedreamin/image/upload/v1715097590/marketplace/apps/aws/CleanShot_2024-05-07_at_11.59.24_mgqvr5.png) + +From within the users management section, create a new user by clicking **Create User** in the top right: + +![Creating a new user from within the AWS IAM console](https://res.cloudinary.com/pipedreamin/image/upload/v1715097847/marketplace/apps/aws/CleanShot_2024-05-07_at_12.03.52_rm4kae.png) + +On the next page, you'll be prompted to name the user. We recommend naming the user `pipedream` so you can easily remember which service this user is tied to: + +![Naming the new IAM user](https://res.cloudinary.com/pipedreamin/image/upload/v1715097913/marketplace/apps/aws/CleanShot_2024-05-07_at_12.04.30_acgthh.png) + +Next, you'll be prompted to define this user's *permissions*. You have three options: +1. Attach the user to a group - the new user will inherit the group's permission policies. +2. Copy permissions - copy the permission policies from another existing IAM user. +3. Attach policies directly - attach a policy directly to the new user. + +If you're unfamiliar with defining permissions in AWS, consider using a pre-made permission policy. For example, if you need Pipedream to integrate with S3, you can choose the `S3FullAccessPolicy` by searching for "s3" in the search bar: + +![Searching for s3 in the permissions search bar within IAM to attach the S3FullAccessPolicy directly to the pipedream user](https://res.cloudinary.com/pipedreamin/image/upload/v1715098770/marketplace/apps/aws/CleanShot_2024-05-07_at_12.19.01_zwgldj.png) + +Alternatively, you can craft specific policies within IAM that only grant specific access to specific AWS resources to this new `pipedream` user. + +Click **Create Policy** to create a new custom policy, and from within this view, you can use either JSON or the UI to include permissions to specific services and resources. + +After you’ve created your IAM user, it will display a **Client Key** and **Secret**. Copy these fields into Pipedream to connect your AWS account. + +Please note, the AWS Client Secret will not be shown again after closing the window. So make sure that your credentials are properly copied into Pipedream before closing the IAM window. + +# Troubleshooting + +## Permissions issues + +The most common issue when integrating Pipedream with AWS is permissions issues. + +The IAM user you create for Pipedream must have access to the AWS resources it’s attempting to use within your triggers, actions, Node.js, or Python code steps. + +You can use the AWS IAM console to attach additional policies to your IAM user associated with Pipedream. + + + # Example Use Cases - **Automated Backup to S3**: Trigger a workflow when a new row is added to a Google Sheets document, process the data within Pipedream, and automatically back it up to an AWS S3 bucket. This ensures important data is stored safely without manual intervention. diff --git a/components/bilflo/README.md b/components/bilflo/README.md new file mode 100644 index 0000000000000..77f6d0bdfb46a --- /dev/null +++ b/components/bilflo/README.md @@ -0,0 +1,11 @@ +# Overview + +The Bilflo API allows users to automate and integrate staffing and workforce management processes. By connecting Bilflo with Pipedream, users can streamline data flows between Bilflo and other business applications, enabling automated reporting, payroll processing, and seamless data synchronization across platforms. This integration can significantly enhance operational efficiencies, reduce manual data entry, and provide real-time analytics for better decision-making. + +# Example Use Cases + +- **Automated Payroll Processing**: Set up a workflow on Pipedream where hours logged in Bilflo are automatically pushed to a payroll system like ADP or Paychex. When an employee submits their timesheet, the workflow triggers, validates the data, and sends it to the payroll app, ensuring timely and accurate payroll execution. + +- **Real-time Staffing Updates to CRM**: Create a workflow that syncs new employee details or updates from Bilflo to a CRM platform such as Salesforce. Whenever there is a new hire or an update in employee status in Bilflo, the workflow automatically updates the corresponding records in Salesforce, keeping sales and service teams informed about staffing changes. + +- **Automated Compliance Reporting**: Develop a workflow where employee data from Bilflo is used to automatically generate compliance reports. This workflow could connect Bilflo to a document generation tool like Google Docs or an analytics tool like Tableau, compiling necessary compliance information and creating reports at regular intervals or on demand. diff --git a/components/bilflo/actions/assign-contract-job-to-invoice/assign-contract-job-to-invoice.mjs b/components/bilflo/actions/assign-contract-job-to-invoice/assign-contract-job-to-invoice.mjs new file mode 100644 index 0000000000000..c6ecb84ad0a8b --- /dev/null +++ b/components/bilflo/actions/assign-contract-job-to-invoice/assign-contract-job-to-invoice.mjs @@ -0,0 +1,33 @@ +import bilflo from "../../bilflo.app.mjs"; + +export default { + key: "bilflo-assign-contract-job-to-invoice", + name: "Assign Contract Job to Invoice Group", + description: "Assigns a contract job to a specified invoice group for a client. [See the documentation](https://developer.bilflo.com/documentation#operations-tag-Clients)", + version: "0.0.1", + type: "action", + props: { + bilflo, + jobId: { + type: "integer", + label: "Contract Job Identifier", + description: "The unique identifier for the contract job.", + }, + invoiceGroupId: { + type: "integer", + label: "Invoice Group Identifier", + description: "The unique identifier for the invoice group.", + }, + }, + async run({ $ }) { + const response = await this.bilflo.assignContractJobToInvoiceGroup({ + $, + data: { + jobId: this.jobId, + invoiceGroupId: this.invoiceGroupId, + }, + }); + $.export("$summary", `Successfully assigned contract job ${this.jobId} to invoice group ${this.invoiceGroupId}`); + return response; + }, +}; diff --git a/components/bilflo/actions/create-client/create-client.mjs b/components/bilflo/actions/create-client/create-client.mjs new file mode 100644 index 0000000000000..eeb120268d1bd --- /dev/null +++ b/components/bilflo/actions/create-client/create-client.mjs @@ -0,0 +1,27 @@ +import bilflo from "../../bilflo.app.mjs"; + +export default { + key: "bilflo-create-client", + name: "Create Client", + description: "Creates a new client account in Bilflo. [See the documentation](https://developer.bilflo.com/documentation#operations-tag-Clients)", + version: "0.0.1", + type: "action", + props: { + bilflo, + businessName: { + type: "string", + label: "Business Name", + description: "The name of the business for the new client account.", + }, + }, + async run({ $ }) { + const response = await this.bilflo.createClient({ + $, + data: { + businessName: this.businessName, + }, + }); + $.export("$summary", `Successfully created new client account with Id: ${response.data.clientId}`); + return response; + }, +}; diff --git a/components/bilflo/actions/create-contract-job/create-contract-job.mjs b/components/bilflo/actions/create-contract-job/create-contract-job.mjs new file mode 100644 index 0000000000000..d92ed39753e83 --- /dev/null +++ b/components/bilflo/actions/create-contract-job/create-contract-job.mjs @@ -0,0 +1,93 @@ +import bilflo from "../../bilflo.app.mjs"; + +export default { + key: "bilflo-create-contract-job", + name: "Create Contract Job", + description: "Creates a new contract job in Bilflo. [See the documentation](https://developer.bilflo.com/documentation)", + version: "0.0.1", + type: "action", + props: { + bilflo, + clientId: { + propDefinition: [ + bilflo, + "clientId", + ], + }, + contractorId: { + type: "integer", + label: "Contractor ID", + description: "The unique identifier for the contractor.", + }, + contractorTypeId: { + type: "integer", + label: "Contractor Type ID", + description: "The unique identifier for the contractor type.", + options: [ + { + label: "W2", + value: 1, + }, + { + label: "1099", + value: 2, + }, + ], + }, + timeCardMethodId: { + type: "integer", + label: "Time Card Method ID", + description: "The unique identifier for the time card method.", + }, + overtimeRuleId: { + type: "integer", + label: "Overtime Rule ID", + description: "The unique identifier for the overtime rule.", + }, + jobTitle: { + type: "string", + label: "Job Title", + description: "The title of the job.", + }, + startDate: { + type: "string", + label: "Start Date", + description: "The start date of the contract job. **Format YYYY-MM-DDTHH:MM:SSZ**", + }, + endDate: { + type: "string", + label: "End Date", + description: "The end date of the contract job. **Format YYYY-MM-DDTHH:MM:SSZ**", + }, + firstWeekEndingDate: { + type: "string", + label: "First Week Ending Date", + description: "The first week ending date of the contract job. **Format YYYY-MM-DDTHH:MM:SSZ**", + }, + burdenTypeId: { + type: "integer", + label: "Burden Type ID", + description: "The unique identifier for the burden type.", + }, + }, + async run({ $ }) { + const response = await this.bilflo.createContractJob({ + $, + data: { + clientId: this.clientId, + contractorId: this.contractorId, + contractorTypeId: this.contractorTypeId, + timeCardMethodId: this.timeCardMethodId, + overtimeRuleId: this.overtimeRuleId, + jobTitle: this.jobTitle, + startDate: this.startDate, + endDate: this.endDate, + firstWeekEndingDate: this.firstWeekEndingDate, + burdenTypeId: this.burdenTypeId, + }, + }); + + $.export("$summary", `Successfully created contract job Id: ${response.data.jobId}`); + return response; + }, +}; diff --git a/components/bilflo/bilflo.app.mjs b/components/bilflo/bilflo.app.mjs index 195db44b114ae..116dd928b5d6a 100644 --- a/components/bilflo/bilflo.app.mjs +++ b/components/bilflo/bilflo.app.mjs @@ -1,11 +1,69 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "bilflo", - propDefinitions: {}, + propDefinitions: { + clientId: { + type: "string", + label: "Client ID", + description: "The unique identifier for the client.", + async options() { + const { data } = await this.listClients(); + + return data.map(({ + clientId: value, businessName: label, + }) => ({ + label, + value, + })); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.bilflo.com/v1"; + }, + _headers() { + return { + "company-id": this.$auth.company_id, + "Authorization": `Bearer ${this.$auth.api_key}`, + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + headers: this._headers(), + ...opts, + }); + }, + createClient(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/Clients", + ...opts, + }); + }, + listClients() { + return this._makeRequest({ + path: "/Clients", + }); + }, + assignContractJobToInvoiceGroup(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/Clients/contractInvoiceGroups/assignContractJob", + ...opts, + }); + }, + createContractJob(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/ContractJobs", + ...opts, + }); }, }, -}; \ No newline at end of file +}; diff --git a/components/bilflo/package.json b/components/bilflo/package.json index e8461ebb5b31c..def44d80c96e4 100644 --- a/components/bilflo/package.json +++ b/components/bilflo/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/bilflo", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Bilflo Components", "main": "bilflo.app.mjs", "keywords": [ @@ -11,5 +11,9 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^1.6.5" } -} \ No newline at end of file +} + diff --git a/components/boloforms/actions/send-form/send-form.mjs b/components/boloforms/actions/send-form/send-form.mjs new file mode 100644 index 0000000000000..911b80fe7e355 --- /dev/null +++ b/components/boloforms/actions/send-form/send-form.mjs @@ -0,0 +1,57 @@ +import { ConfigurationError } from "@pipedream/platform"; +import boloforms from "../../boloforms.app.mjs"; + +export default { + key: "boloforms-send-form", + name: "Send Form", + description: "Enables form dispatching to a specific recipient. [See the documentation](https://help.boloforms.com/en/articles/8557660-sending-for-signing)", + version: "0.0.1", + type: "action", + props: { + boloforms, + formId: { + propDefinition: [ + boloforms, + "formId", + ], + type: "string", + }, + email: { + type: "string", + label: "Email", + description: "The email of the receiver.", + }, + subject: { + propDefinition: [ + boloforms, + "subject", + ], + }, + message: { + propDefinition: [ + boloforms, + "message", + ], + }, + }, + async run({ $ }) { + const response = await this.boloforms.dispatchForm({ + $, + data: { + formId: this.formId, + email: { + to: this.email, + subject: this.subject, + message: this.message, + }, + }, + }); + + if (response.error) { + throw new ConfigurationError(response.error); + } + + $.export("$summary", `Form dispatched successfully to ${this.email}`); + return response; + }, +}; diff --git a/components/boloforms/actions/send-template-signature/send-template-signature.mjs b/components/boloforms/actions/send-template-signature/send-template-signature.mjs new file mode 100644 index 0000000000000..850b22502297b --- /dev/null +++ b/components/boloforms/actions/send-template-signature/send-template-signature.mjs @@ -0,0 +1,68 @@ +import { ConfigurationError } from "@pipedream/platform"; +import boloforms from "../../boloforms.app.mjs"; +import { parseObject } from "../../common/utils.mjs"; + +export default { + key: "boloforms-send-template-signature", + name: "Send Template Signature", + description: "Dispatch a predefined template to obtain a signature. [See the documentation](https://help.boloforms.com/en/articles/8557564-sending-for-signing)", + version: "0.0.1", + type: "action", + props: { + boloforms, + documentId: { + propDefinition: [ + boloforms, + "documentId", + () => ({ + isStandAloneTemplate: true, + }), + ], + type: "string", + }, + subject: { + propDefinition: [ + boloforms, + "subject", + ], + }, + message: { + propDefinition: [ + boloforms, + "message", + ], + }, + receiversList: { + type: "string[]", + label: "Receivers List", + description: "A list of receiver objects. **Format: {\"name\": \"Chirag Gupta\", \"email\": \"support@boloforms.com\", \"roleTitle\": \"Junior Doctor\", \"roleColour\": \"#8FB1C8\"}** `RoleTitle has to exactly same as the role you added otherwise it will not work properly`. `Give color to your role you can pass any hex code but it's necessary`.", + }, + customVariables: { + type: "string[]", + label: "Custom Variables", + description: "A list of custom variable objects. **Format: {\"varName\": \"[v1]\", \"varValue\": \"v1 value from api\" }**. `Variable name has to be in square barckets`. `If you don't pass customVariables then also the call would work, Boloforms will pass each variable as empty.`.", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.boloforms.dispatchPDFTemplate({ + $, + data: { + documentId: this.documentId, + mailData: { + subject: this.subject, + message: this.message, + }, + receiversList: parseObject(this.receiversList), + customVariables: parseObject(this.customVariables), + }, + }); + + if (response.error) { + throw new ConfigurationError(response.error); + } + + $.export("$summary", `Template dispatched successfully to ${parseObject(this.receiversList).length} receivers`); + return response; + }, +}; diff --git a/components/boloforms/boloforms.app.mjs b/components/boloforms/boloforms.app.mjs new file mode 100644 index 0000000000000..efdc7899358c6 --- /dev/null +++ b/components/boloforms/boloforms.app.mjs @@ -0,0 +1,120 @@ +import { axios } from "@pipedream/platform"; + +export default { + type: "app", + app: "boloforms", + propDefinitions: { + formId: { + type: "string[]", + label: "Form ID", + description: "The ID of the form.", + async options({ page }) { + const { forms } = await this.getForms({ + params: { + page: page + 1, + filter: "ALL", + }, + }); + return forms.map(({ + formId: value, formJson: { title: label }, + }) => ({ + label, + value, + })); + }, + }, + documentId: { + type: "string[]", + label: "Document ID", + description: "The ID of the document.", + async options({ + page, isStandAloneTemplate = false, + }) { + const { documents } = await this.getDocuments({ + params: { + page: page + 1, + filter: "ALL", + isStandAloneTemplate, + }, + }); + return documents.map(({ + documentId: value, documentName: label, + }) => ({ + label, + value, + })); + }, + }, + subject: { + type: "string", + label: "Subject", + description: "The subject will be sent.", + }, + message: { + type: "string", + label: "Message", + description: "The message will be sent.", + }, + }, + methods: { + _baseUrl() { + return "https://signature-backend.boloforms.com/api/v1/signature"; + }, + _headers() { + return { + "x-api-key": this.$auth.api_key, + "Workspaceid": this.$auth.workspace_id, + "Content-Type": "application/json", + }; + }, + _data(data) { + return { + email: this.$auth.email, + ...data, + }; + }, + _makeRequest({ + $ = this, data, path, ...opts + }) { + return axios($, { + url: `${this._baseUrl()}${path}`, + headers: this._headers(), + data: this._data(data), + ...opts, + }); + }, + dispatchPDFTemplate(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/add-respondent-pdfTemplate", + ...opts, + }); + }, + dispatchForm(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/forms/send-form-email", + ...opts, + }); + }, + getDocuments(opts = {}) { + return this._makeRequest({ + path: "/get-all-documents/v1", + ...opts, + }); + }, + getForms(opts = {}) { + return this._makeRequest({ + path: "/get-all-forms/v1", + ...opts, + }); + }, + updateHooks(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/workspace/webhooks", + ...opts, + }); + }, + }, +}; diff --git a/components/boloforms/common/utils.mjs b/components/boloforms/common/utils.mjs new file mode 100644 index 0000000000000..dcc9cc61f6f41 --- /dev/null +++ b/components/boloforms/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/boloforms/package.json b/components/boloforms/package.json new file mode 100644 index 0000000000000..6cd9f0d344c31 --- /dev/null +++ b/components/boloforms/package.json @@ -0,0 +1,19 @@ +{ + "name": "@pipedream/boloforms", + "version": "0.1.0", + "description": "Pipedream Boloforms Components", + "main": "boloforms.app.mjs", + "keywords": [ + "pipedream", + "boloforms" + ], + "homepage": "https://pipedream.com/apps/boloforms", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^1.6.5", + "uuid": "^9.0.1" + } +} diff --git a/components/boloforms/sources/common/base.mjs b/components/boloforms/sources/common/base.mjs new file mode 100644 index 0000000000000..5dbc32ae90839 --- /dev/null +++ b/components/boloforms/sources/common/base.mjs @@ -0,0 +1,83 @@ +import { v4 as uuidv4 } from "uuid"; +import boloforms from "../../boloforms.app.mjs"; +import { parseObject } from "../../common/utils.mjs"; + +export default { + props: { + boloforms, + db: "$.service.db", + http: { + type: "$.interface.http", + customResponse: true, + }, + webhookName: { + type: "string", + label: "Webhook Name", + description: "The name of the webhook to identify on the BoloForms platform.", + }, + }, + methods: { + _setHookId(hookId) { + this.db.set("webhookId", hookId); + }, + _getHookId() { + return this.db.get("webhookId"); + }, + getDocs() { + return this.documentId; + }, + generateMeta(body) { + const ts = Date.now(); + return { + id: `${body.documentId}-${ts}`, + summary: this.getSummary(body), + ts: ts, + }; + }, + }, + hooks: { + async activate() { + const uuid = uuidv4(); + const response = await this.boloforms.updateHooks({ + data: { + "task": "ADD", + "webhookObj": { + "webhookId": uuid, + "webhookName": this.webhookName, + "webhookUrl": this.http.endpoint, + "webhookEvent": this.getWebhookEvent(), + "selectedDocs": parseObject(this.getDocs()), + "webhookStatus": "ACTIVE", + }, + }, + }); + + const newHook = response.owner.webhooks.filter((hook) => hook.webhookId === uuid); + + this._setHookId({ + webhookId: uuid, + _id: newHook[0]._id, + }); + }, + async deactivate() { + await this.boloforms.updateHooks({ + data: { + "task": "DELETE", + "webhookObj": this._getHookId(), + }, + }); + }, + }, + async run({ body }) { + this.http.respond({ + status: 200, + body: "Success", + }); + if (!Array.isArray(body)) { + body = [ + body, + ]; + } + body.forEach((item) => this.$emit(item, this.generateMeta(item))); + }, +}; diff --git a/components/boloforms/sources/new-response-instant/new-response-instant.mjs b/components/boloforms/sources/new-response-instant/new-response-instant.mjs new file mode 100644 index 0000000000000..b40b2af3960fd --- /dev/null +++ b/components/boloforms/sources/new-response-instant/new-response-instant.mjs @@ -0,0 +1,39 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "boloforms-new-response-instant", + name: "New Response (Instant)", + description: "Emit new event when a filled form response is received.", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + formId: { + propDefinition: [ + common.props.boloforms, + "formId", + ], + withLabel: true, + }, + }, + methods: { + ...common.methods, + getWebhookEvent() { + return "new_form_response"; + }, + getDocs() { + return this.formId; + }, + generateMeta(body) { + return { + id: body.formResponseId, + summary: `New response for form ID: ${body.formId}`, + ts: Date.now(), + }; + }, + }, + sampleEmit, +}; diff --git a/components/boloforms/sources/new-response-instant/test-event.mjs b/components/boloforms/sources/new-response-instant/test-event.mjs new file mode 100644 index 0000000000000..659e3c37459ce --- /dev/null +++ b/components/boloforms/sources/new-response-instant/test-event.mjs @@ -0,0 +1,15 @@ +export default { + "formId": "12345678-1234-1234-1234-123456789012", + "formResponseId": "12345678-1234-1234-1234-123456789012", + "formTitle": "Title", + "documentUrl": "https://boloforms-files-ap-south-1.s3.ap-south-1.amazonaws.com/documents/12345678-1234-1234-1234-123456789012.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=12343565678%12313123%2Fap-south-1%2Fs3%2Faws4_request&X-Amz-Date=123123123123&X-Amz-Expires=604800&X-Amz-Signature=1233452667864fdgu54323weq65432rwe&X-Amz-SignedHeaders=host&x-id=GetObject", + "respondentEmail": "test@email.com", + "response": [ + { + "type": "Text", + "question": "Email", + "answer": "test@email.com" + } + ], + "response[Email]": "test@email.com" +} \ No newline at end of file diff --git a/components/boloforms/sources/new-signature-completed-instant/new-signature-completed-instant.mjs b/components/boloforms/sources/new-signature-completed-instant/new-signature-completed-instant.mjs new file mode 100644 index 0000000000000..a09a677cc93dd --- /dev/null +++ b/components/boloforms/sources/new-signature-completed-instant/new-signature-completed-instant.mjs @@ -0,0 +1,32 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "boloforms-new-signature-completed-instant", + name: "New Signature Completed (Instant)", + description: "Emit new event when a PDF document is fully signed. [See the documentation](https://help.boloforms.com/en/collections/8024362-webhooks)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + documentId: { + propDefinition: [ + common.props.boloforms, + "documentId", + ], + withLabel: true, + }, + }, + methods: { + ...common.methods, + getWebhookEvent() { + return "new_signature_completed"; + }, + getSummary(body) { + return `Document ${body.documentId} fully signed`; + }, + }, + sampleEmit, +}; diff --git a/components/boloforms/sources/new-signature-completed-instant/test-event.mjs b/components/boloforms/sources/new-signature-completed-instant/test-event.mjs new file mode 100644 index 0000000000000..94b3be7e84248 --- /dev/null +++ b/components/boloforms/sources/new-signature-completed-instant/test-event.mjs @@ -0,0 +1,20 @@ +export default { + "documentName": "Sample.pdf", + "documentId": "12345678-1234-1234-1234-123456789012", + "documentUrl": "https://boloforms-files-ap-south-1.s3.ap-south-1.amazonaws.com/documents/12345678-1234-1234-1234-123456789012.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=12343565678%12313123%2Fap-south-1%2Fs3%2Faws4_request&X-Amz-Date=123123123123&X-Amz-Expires=604800&X-Amz-Signature=1233452667864fdgu54323weq65432rwe&X-Amz-SignedHeaders=host&x-id=GetObject", + "authorEmail": "author@email.com", + "finishedPdfUrl": "https://sample-pdf-url.pdf", + "Signer 1 Email": "email@example.com", + "Signer 1 Name": "Sample User", + "signers": [ + { + "email": "email@example.com", + "name": "Signer Name", + "status": "DRAFT", + "signerIp": "", + "hasDeclined": false + } + ], + "text(page4)[Signer Name]": "Sample Response", + "date(page4)[Signer Name]": "Sample Response" +} \ No newline at end of file diff --git a/components/boloforms/sources/new-template-response-instant/new-template-response-instant.mjs b/components/boloforms/sources/new-template-response-instant/new-template-response-instant.mjs new file mode 100644 index 0000000000000..4ba20b25e9ca6 --- /dev/null +++ b/components/boloforms/sources/new-template-response-instant/new-template-response-instant.mjs @@ -0,0 +1,35 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "boloforms-new-template-response-instant", + name: "New Template Response (Instant)", + description: "Emit new event when a new form template response is filled.", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + documentId: { + propDefinition: [ + common.props.boloforms, + "documentId", + () => ({ + isStandAloneTemplate: true, + }), + ], + withLabel: true, + }, + }, + methods: { + ...common.methods, + getWebhookEvent() { + return "new_template_response"; + }, + getSummary(body) { + return `New response for template ID ${body.documentId}`; + }, + }, + sampleEmit, +}; diff --git a/components/boloforms/sources/new-template-response-instant/test-event.mjs b/components/boloforms/sources/new-template-response-instant/test-event.mjs new file mode 100644 index 0000000000000..cc9559a1c2f64 --- /dev/null +++ b/components/boloforms/sources/new-template-response-instant/test-event.mjs @@ -0,0 +1,20 @@ +export default { + "documentName": "invoice.pdf", + "documentId": "12345678-1234-1234-1234-123456789012", + "documentUrl": "https://boloforms-files-ap-south-1.s3.ap-south-1.amazonaws.com/documents/12345678-1234-1234-1234-123456789012.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=12343565678%12313123%2Fap-south-1%2Fs3%2Faws4_request&X-Amz-Date=123123123123&X-Amz-Expires=604800&X-Amz-Signature=1233452667864fdgu54323weq65432rwe&X-Amz-SignedHeaders=host&x-id=GetObject", + "authorEmail": "author@email.com", + "finishedPdfUrl": "https://sample-pdf-url.pdf", + "Signer 1 Email": "Developer@example.com", + "Signer 1 Name": "Sample User", + "Signer 1 Role title": "Developer", + "signers": [ + { + "roleTitle": "Developer", + "email": "Developer@example.com", + "name": "Sample User", + "status": "", + "signerIp": "" + } + ], + "name(page1)[Developer]": "Developer Name" +} \ No newline at end of file diff --git a/components/botpress/README.md b/components/botpress/README.md new file mode 100644 index 0000000000000..99094e3f40f00 --- /dev/null +++ b/components/botpress/README.md @@ -0,0 +1,11 @@ +# Overview + +Botpress is a powerful, open-source chatbot platform that enables developers to create intelligent, conversational bots. With its natural language understanding (NLU) capabilities, users can design bots that can interpret human language and respond appropriately. Utilizing the Botpress API on Pipedream allows you to automate interactions, analyze chat data, and integrate with various other services to enhance the functionality of your bots. + +# Example Use Cases + +- **Customer Support Automation**: Automatically handle common customer inquiries by connecting Botpress with a CRM like Salesforce on Pipedream. When a Botpress bot identifies a user query about order status, it triggers a workflow that retrieves order details from Salesforce and provides real-time updates to the customer through the chat interface. + +- **Event Registration via Chatbot**: Use Botpress to create a chatbot on your website that handles event registrations. Connect the bot with Google Sheets on Pipedream. When a user expresses interest in an event and provides details, the bot captures this data and automatically populates a row in Google Sheets, registering the user for the event without manual data entry. + +- **Feedback Collection and Analysis**: Deploy a Botpress chatbot to collect feedback on a product or service. Integrate this bot with Twilio on Pipedream to send a follow-up SMS to thank the customer for their feedback. Additionally, connect to a tool like Airtable or Google Sheets to store and analyze feedback data systematically. diff --git a/components/chaser/README.md b/components/chaser/README.md new file mode 100644 index 0000000000000..6ee9e1ee2b749 --- /dev/null +++ b/components/chaser/README.md @@ -0,0 +1,11 @@ +# Overview + +The Chaser API enables users to automate their accounts receivable process efficiently, making it possible to manage and follow up on invoices programmatically. By integrating Chaser with Pipedream, users can automate invoice reminders, synchronize financial data with other business applications, and generate detailed accounts receivable reports. These capabilities help businesses improve their cash flow and minimize the time spent on manual follow-ups. + +# Example Use Cases + +- **Automated Invoice Reminders**: Set up a workflow on Pipedream that triggers weekly, based on the payment due date of invoices. Use the Chaser API to send customized reminder emails to clients whose payments are overdue. This reduces the manual effort required in managing follow-ups and helps maintain consistent cash flow. + +- **Sync Invoices with Accounting Software**: Create a workflow that triggers whenever a new invoice is added in Chaser. Use this trigger to automatically copy the invoice details to your accounting software (like QuickBooks or Xero), ensuring that your financial records are always up-to-date without manual data entry. + +- **Generate and Send Financial Reports**: Implement a monthly workflow on Pipedream that uses the Chaser API to gather all receivables data and compile it into a comprehensive financial report. This report can then be automatically emailed to stakeholders using an email service like SendGrid or directly through SMTP steps available in Pipedream. This automation ensures that all pertinent parties are regularly updated with the latest financial statuses without any manual intervention. diff --git a/components/chatfly/README.md b/components/chatfly/README.md new file mode 100644 index 0000000000000..8050608483730 --- /dev/null +++ b/components/chatfly/README.md @@ -0,0 +1,11 @@ +# Overview + +The ChatFly API allows developers to integrate chat functionalities into their applications, making it easier to build custom chat solutions or enhance existing communication systems. On Pipedream, leveraging the ChatFly API can automate interactions, analyze chat data, or sync with other services to create a more dynamic user experience. + +# Example Use Cases + +- **Customer Support Automation**: Automatically create and manage customer support tickets from chat messages using the ChatFly API on Pipedream. Connect ChatFly with a ticketing system like Zendesk, where each chat message tagged as a support request triggers the creation of a new ticket, streamlining customer support workflows. + +- **Chat Analytics Pipeline**: Analyze chat data by connecting ChatFly to Google Sheets or BigQuery on Pipedream. Every chat message can trigger a workflow that processes and logs data into Google Sheets for real-time analysis or into BigQuery for more complex data analysis, helping in understanding user interactions and improving service delivery. + +- **Real-Time Notifications System**: Enhance communication by setting up real-time alerts using the ChatFly API on Pipedream. Connect ChatFly with Slack to send customized alerts to specific channels or users whenever key phrases or words are detected in a chat, enabling immediate response to important messages. diff --git a/components/chatfly/actions/send-message/send-message.mjs b/components/chatfly/actions/send-message/send-message.mjs new file mode 100644 index 0000000000000..67f43f90398bd --- /dev/null +++ b/components/chatfly/actions/send-message/send-message.mjs @@ -0,0 +1,45 @@ +import chatfly from "../../chatfly.app.mjs"; + +export default { + key: "chatfly-send-message", + name: "Send Message", + description: "Dispatches a text message to a specified group or individual in Chatfly.", + version: "0.0.1", + type: "action", + props: { + chatfly, + botId: { + propDefinition: [ + chatfly, + "botId", + ], + }, + sessionId: { + propDefinition: [ + chatfly, + "sessionId", + ({ botId }) => ({ + botId, + }), + ], + }, + message: { + propDefinition: [ + chatfly, + "message", + ], + }, + }, + async run({ $ }) { + const response = await this.chatfly.dispatchMessage({ + $, + data: { + bot_id: this.botId, + message: this.message, + session_id: this.sessionId, + }, + }); + $.export("$summary", "Message dispatched successfully"); + return response; + }, +}; diff --git a/components/chatfly/chatfly.app.mjs b/components/chatfly/chatfly.app.mjs index 7b3d96606629f..25b33229e5824 100644 --- a/components/chatfly/chatfly.app.mjs +++ b/components/chatfly/chatfly.app.mjs @@ -1,11 +1,86 @@ +import { axios } from "@pipedream/platform"; +import { prepareSessionLabel } from "./common/utils.mjs"; + export default { type: "app", app: "chatfly", - propDefinitions: {}, + propDefinitions: { + botId: { + type: "string", + label: "Bot ID", + description: "The ID of the bot to send the message to.", + async options() { + const data = await this.listBots(); + + return data.map(({ + id: value, bot_name: label, + }) => ({ + label, + value, + })); + }, + }, + message: { + type: "string", + label: "Message", + description: "The message to send.", + }, + sessionId: { + type: "string", + label: "Session ID", + description: "The session ID for the message. To initiate a new session, use a unique string in a Custom Expression, for example: `8g12h`", + async options({ botId }) { + const data = await this.listSessions({ + params: { + bot_id: botId, + }, + }); + + return data.map(({ + session_id: value, chat_history_response, + }) => ({ + label: prepareSessionLabel(chat_history_response), + value, + })); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://backend.chatfly.co/api"; + }, + _headers() { + return { + "CHATFLY-API-KEY": `${this.$auth.api_key}`, + }; + }, + _makeRequest({ + $ = this, path = "/", ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + headers: this._headers(), + ...opts, + }); + }, + dispatchMessage(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/chat/get-streaming-response", + ...opts, + }); + }, + listBots(opts = {}) { + return this._makeRequest({ + path: "/bot", + ...opts, + }); + }, + listSessions(opts = {}) { + return this._makeRequest({ + path: "/chat/history", + ...opts, + }); }, }, -}; \ No newline at end of file +}; diff --git a/components/chatfly/common/utils.mjs b/components/chatfly/common/utils.mjs new file mode 100644 index 0000000000000..646098290a789 --- /dev/null +++ b/components/chatfly/common/utils.mjs @@ -0,0 +1,5 @@ +export const prepareSessionLabel = (messages) => { + return `${messages[messages.length - 1].content.substring(0, 60)} ${(messages[messages.length - 1].content.length > 60) + ? "..." + : ""}`; +}; diff --git a/components/chatfly/package.json b/components/chatfly/package.json index 95b42f46f4bc6..453c9d3179c95 100644 --- a/components/chatfly/package.json +++ b/components/chatfly/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/chatfly", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream ChatFly Components", "main": "chatfly.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^1.6.5" } -} \ No newline at end of file +} diff --git a/components/cincopa/actions/add-assets-to-gallery/add-assets-to-gallery.mjs b/components/cincopa/actions/add-assets-to-gallery/add-assets-to-gallery.mjs new file mode 100644 index 0000000000000..c267056af23e0 --- /dev/null +++ b/components/cincopa/actions/add-assets-to-gallery/add-assets-to-gallery.mjs @@ -0,0 +1,63 @@ +import app from "../../cincopa.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "cincopa-add-assets-to-gallery", + name: "Add Assets to Gallery", + description: "Adds an asset or a list of assets to an existing gallery. [See the documentation](https://www.cincopa.com/media-platform/api-documentation-v2#gallery.add_item)", + version: "0.0.1", + type: "action", + props: { + app, + fid: { + propDefinition: [ + app, + "fid", + ], + }, + rid: { + propDefinition: [ + app, + "rid", + ], + }, + insertPosition: { + type: "string", + label: "Insert Position", + description: "Add the assets at the top or the bottom of the gallery. Options: top or bottom (default is `bottom`)", + optional: true, + options: [ + "top", + "bottom", + ], + }, + }, + methods: { + addAssetToGallery(args = {}) { + return this.app._makeRequest({ + path: "/gallery.add_item.json", + ...args, + }); + }, + }, + async run({ $ }) { + const { + addAssetToGallery, + fid, + rid, + insertPosition, + } = this; + + const response = await addAssetToGallery({ + $, + params: { + fid, + rid: utils.parseArray(rid).join(","), + insert_position: insertPosition, + }, + }); + + $.export("$summary", "Successfully added assets to gallery"); + return response; + }, +}; diff --git a/components/cincopa/actions/create-gallery/create-gallery.mjs b/components/cincopa/actions/create-gallery/create-gallery.mjs new file mode 100644 index 0000000000000..8330e9f608d2f --- /dev/null +++ b/components/cincopa/actions/create-gallery/create-gallery.mjs @@ -0,0 +1,57 @@ +import app from "../../cincopa.app.mjs"; + +export default { + key: "cincopa-create-gallery", + name: "Create Gallery", + description: "Creates a new gallery, returning the new gallery FID (unique ID). [See the documentation](https://www.cincopa.com/media-platform/api-documentation-v2#gallery.create)", + version: "0.0.1", + type: "action", + props: { + app, + name: { + type: "string", + label: "Name", + description: "Name of the gallery", + }, + description: { + type: "string", + label: "Description", + description: "Description of the gallery", + optional: true, + }, + template: { + type: "string", + label: "Template", + description: "Set the gallery skin to this template", + optional: true, + }, + }, + methods: { + createGallery(args = {}) { + return this.app._makeRequest({ + path: "/gallery.create.json", + ...args, + }); + }, + }, + async run({ $ }) { + const { + createGallery, + name, + description, + template, + } = this; + + const response = await createGallery({ + $, + params: { + name, + description, + template, + }, + }); + + $.export("$summary", `Successfully created gallery with FID \`${response.fid}\``); + return response; + }, +}; diff --git a/components/cincopa/actions/upload-asset-from-url/upload-asset-from-url.mjs b/components/cincopa/actions/upload-asset-from-url/upload-asset-from-url.mjs new file mode 100644 index 0000000000000..51edb7d9069ae --- /dev/null +++ b/components/cincopa/actions/upload-asset-from-url/upload-asset-from-url.mjs @@ -0,0 +1,57 @@ +import app from "../../cincopa.app.mjs"; + +export default { + key: "cincopa-upload-asset-from-url", + name: "Upload Asset From URL", + description: "Upload an asset from an input URL to a Cincopa gallery. [See the documentation](https://www.cincopa.com/media-platform/api-documentation-v2#asset_upload_from_url)", + version: "0.0.1", + type: "action", + props: { + app, + input: { + type: "string", + label: "Input URL", + description: "Input URL for the source asset to upload", + }, + fid: { + propDefinition: [ + app, + "fid", + ], + }, + type: { + type: "string", + label: "Type", + description: "Type of the attached asset", + optional: true, + }, + }, + methods: { + uploadAssetFromUrl(args = {}) { + return this.app._makeRequest({ + path: "/asset.upload_from_url.json", + ...args, + }); + }, + }, + async run({ $ }) { + const { + uploadAssetFromUrl, + input, + fid, + type, + } = this; + + const response = await uploadAssetFromUrl({ + $, + params: { + input, + fid, + type, + }, + }); + + $.export("$summary", `Successfully uploaded asset from URL with status ID \`${response.status_id}\``); + return response; + }, +}; diff --git a/components/cincopa/cincopa.app.mjs b/components/cincopa/cincopa.app.mjs index 1dfaebdd98c49..1b58ceeb2b666 100644 --- a/components/cincopa/cincopa.app.mjs +++ b/components/cincopa/cincopa.app.mjs @@ -1,11 +1,82 @@ +import { axios } from "@pipedream/platform"; +import constants from "./common/constants.mjs"; + export default { type: "app", app: "cincopa", - propDefinitions: {}, + propDefinitions: { + fid: { + type: "string", + label: "Gallery FID", + description: "Gallery FID to add the assets", + async options({ page }) { + const { galleries } = await this.listGalleries({ + params: { + page: page + 1, + }, + }); + return galleries.map(({ + fid: value, name: label, + }) => ({ + value, + label, + })); + }, + }, + rid: { + type: "string[]", + label: "Asset RIDs", + description: "List of RIDs (assets id) to be added", + optional: true, + useQuery: true, + async options({ + query: search, page, + }) { + const { items } = await this.listAssets({ + params: { + search, + page: page + 1, + }, + }); + return items.map(({ + rid: value, filename: label, + }) => ({ + value, + label, + })); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + getUrl(path) { + return `${constants.BASE_URL}${constants.VERSION_PATH}${path}`; + }, + getAuthParams(params) { + return { + ...params, + api_token: this.$auth.api_token, + }; + }, + _makeRequest({ + $ = this, path, params, ...args + } = {}) { + return axios($, { + ...args, + url: this.getUrl(path), + params: this.getAuthParams(params), + }); + }, + listGalleries(args = {}) { + return this._makeRequest({ + path: "/gallery.list.json", + ...args, + }); + }, + listAssets(args = {}) { + return this._makeRequest({ + path: "/asset.list.json", + ...args, + }); }, }, }; diff --git a/components/cincopa/common/constants.mjs b/components/cincopa/common/constants.mjs new file mode 100644 index 0000000000000..3a9e619bdc79c --- /dev/null +++ b/components/cincopa/common/constants.mjs @@ -0,0 +1,11 @@ +const BASE_URL = "https://api.cincopa.com"; +const VERSION_PATH = "/v2"; +const SECURITY_KEY = "securityKey"; +const DEFAULT_MAX = 600; + +export default { + BASE_URL, + VERSION_PATH, + SECURITY_KEY, + DEFAULT_MAX, +}; diff --git a/components/cincopa/common/utils.mjs b/components/cincopa/common/utils.mjs new file mode 100644 index 0000000000000..d9df0aa935aef --- /dev/null +++ b/components/cincopa/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/cincopa/package.json b/components/cincopa/package.json new file mode 100644 index 0000000000000..3b7209b16efd0 --- /dev/null +++ b/components/cincopa/package.json @@ -0,0 +1,19 @@ +{ + "name": "@pipedream/cincopa", + "version": "0.0.1", + "description": "Pipedream Cincopa Components", + "main": "cincopa.app.mjs", + "keywords": [ + "pipedream", + "cincopa" + ], + "homepage": "https://pipedream.com/apps/cincopa", + "author": "Pipedream (https://pipedream.com/)", + "dependencies": { + "@pipedream/platform": "^1.6.5", + "uuid": "^8.3.2" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/components/cincopa/sources/asset-uploaded-instant/asset-uploaded-instant.mjs b/components/cincopa/sources/asset-uploaded-instant/asset-uploaded-instant.mjs new file mode 100644 index 0000000000000..208636c9c4ed2 --- /dev/null +++ b/components/cincopa/sources/asset-uploaded-instant/asset-uploaded-instant.mjs @@ -0,0 +1,25 @@ +import common from "../common/webhook.mjs"; +import events from "../common/events.mjs"; + +export default { + ...common, + key: "cincopa-asset-uploaded-instant", + name: "New Asset Uploaded (Instant)", + description: "Emit new event when a new asset is uploaded. [See the documentation](https://www.cincopa.com/media-platform/api-documentation-v2#webhook.set)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEvents() { + return events.ASSET_UPLOADED; + }, + generateMeta(resource) { + return { + id: resource.id, + summary: `New Asset: ${resource.filename}`, + ts: Date.parse(resource.modified), + }; + }, + }, +}; diff --git a/components/cincopa/sources/common/events.mjs b/components/cincopa/sources/common/events.mjs new file mode 100644 index 0000000000000..7d040342a67f0 --- /dev/null +++ b/components/cincopa/sources/common/events.mjs @@ -0,0 +1,15 @@ +export default { + GALLERY_ALL: "gallery.*", + GALLERY_CREATED: "gallery.created", + GALLERY_UPDATED: "gallery.updated", + GALLERY_DELETED: "gallery.deleted", + GALLERY_CLAIM: "gallery.claim", + GALLERY_SYNC: "gallery.sync", + GALLERY_SYNCED: "gallery.synced", + ASSET_ALL: "asset.*", + ASSET_UPLOADED: "asset.uploaded", + ASSET_SYNCED: "asset.synced", + ACCOUNT_ALL: "account.*", + ANALYTICS_ALL: "analytics.*", + LEADS_ALL: "leads.*", +}; diff --git a/components/cincopa/sources/common/webhook.mjs b/components/cincopa/sources/common/webhook.mjs new file mode 100755 index 0000000000000..22b924eac30f6 --- /dev/null +++ b/components/cincopa/sources/common/webhook.mjs @@ -0,0 +1,73 @@ +import { v4 as uuid } from "uuid"; +import { ConfigurationError } from "@pipedream/platform"; +import app from "../../cincopa.app.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + props: { + app, + db: "$.service.db", + http: { + type: "$.interface.http", + customResponse: true, + }, + }, + hooks: { + async activate() { + const securityKey = uuid(); + await this.createWebhook({ + params: { + hook_url: this.http.endpoint, + security_key: securityKey, + events: this.getEvents(), + }, + }); + this.setSecurityKey(securityKey); + }, + async deactivate() { + await this.deleteWebhook({ + params: { + hook_url: this.http.endpoint, + }, + }); + }, + }, + methods: { + setSecurityKey(value) { + this.db.set(constants.SECURITY_KEY, value); + }, + getSecurityKey() { + return this.db.get(constants.SECURITY_KEY); + }, + generateMeta() { + throw new ConfigurationError("generateMeta is not implemented"); + }, + getEvents() { + throw new ConfigurationError("getEvents is not implemented"); + }, + processResource(resource) { + this.$emit(resource, this.generateMeta(resource)); + }, + createWebhook(args = {}) { + return this.app._makeRequest({ + debug: true, + path: "/webhook.set.json", + ...args, + }); + }, + deleteWebhook(args = {}) { + return this.app._makeRequest({ + debug: true, + path: "/webhook.delete.json", + ...args, + }); + }, + }, + async run({ body }) { + this.http.respond({ + status: 200, + }); + + this.processResource(body); + }, +}; diff --git a/components/cincopa/sources/gallery-created-instant/gallery-created-instant.mjs b/components/cincopa/sources/gallery-created-instant/gallery-created-instant.mjs new file mode 100644 index 0000000000000..a4cd0a01b0523 --- /dev/null +++ b/components/cincopa/sources/gallery-created-instant/gallery-created-instant.mjs @@ -0,0 +1,25 @@ +import common from "../common/webhook.mjs"; +import events from "../common/events.mjs"; + +export default { + ...common, + key: "cincopa-gallery-created-instant", + name: "New Gallery Created (Instant)", + description: "Emit new event when a new gallery is created. [See the documentation](https://www.cincopa.com/media-platform/api-documentation-v2#webhook.set)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEvents() { + return events.GALLERY_CREATED; + }, + generateMeta(resource) { + return { + id: resource.fid, + summary: `New Gallery: ${resource.name}`, + ts: Date.parse(resource.modified), + }; + }, + }, +}; diff --git a/components/connectwise_psa/README.md b/components/connectwise_psa/README.md new file mode 100644 index 0000000000000..22737308aaf46 --- /dev/null +++ b/components/connectwise_psa/README.md @@ -0,0 +1,11 @@ +# Overview + +ConnectWise PSA (Professional Services Automation) API offers a powerful avenue for managing business processes related to technology services. By integrating with ConnectWise PSA via Pipedream, developers can automate complex workflows, synchronize data across various platforms, and enhance operational efficiencies. This API allows for control over modules like service tickets, project management, and account management, essentially streamlining operations and making data management more effective. + +# Example Use Cases + +- **Ticket Management Automation**: Automatically create or update tickets in ConnectWise PSA whenever specific triggers occur in other apps, such as receiving a high-priority email in Gmail or a new form entry in Typeform. This workflow can help in ensuring rapid response times and better issue tracking. + +- **Client Onboarding**: Streamline the onboarding process for new clients by using ConnectWise PSA to manage project setups, configurations, and initial assessments whenever a new client is added in CRM platforms like Salesforce. Automate task creation and assignment to ensure every client setup is thorough and consistent. + +- **Invoice and Payment Sync**: Sync invoices and payments between ConnectWise PSA and accounting software like QuickBooks. Automate the process of updating financial records whenever a new invoice is created or a payment is received in ConnectWise, ensuring that your financial data remains accurate and up to date. diff --git a/components/cradl_ai/README.md b/components/cradl_ai/README.md new file mode 100644 index 0000000000000..29aaadbb0a8fd --- /dev/null +++ b/components/cradl_ai/README.md @@ -0,0 +1,14 @@ +# Overview + +Cradl AI is a powerful tool for creating customized machine learning models without extensive coding knowledge. It specializes in processing and understanding various forms of data, including text and images. Utilizing the Cradl AI API on Pipedream, developers can automate workflows involving data analysis, enhance data-driven decision-making, and integrate AI capabilities into applications seamlessly. This opens up opportunities for dynamic interactions with other services, streamlining operations and enhancing user experiences. + +# Example Use Cases + +- **Automated Document Processing Workflow**: + Extract text from uploaded documents on cloud storage platforms like Dropbox or Google Drive using Cradl AI, then analyze and store extracted data in a database such as Google Sheets or Airtable. This workflow can be used for automated invoice processing, extracting data from standardized forms, or updating databases with information from physical documents. + +- **Image Content Analysis and Notification System**: + Set up a workflow where images uploaded to an AWS S3 bucket are automatically sent to Cradl AI for content analysis. Based on the analysis, if certain criteria are met (like detecting specific objects or text within the image), trigger notifications via email or messaging platforms such as Slack. This can be particularly useful for security systems, quality control in manufacturing, or content moderation in social media. + +- **Real-Time Feedback Collection and Analysis**: + Implement a system where feedback submitted through a form (e.g., Google Forms or Typeform) is automatically sent to Cradl AI for sentiment analysis. Aggregate and visualize this data in tools like Tableau or Google Data Studio, providing real-time insights into customer satisfaction and helping businesses quickly adjust to feedback trends. diff --git a/components/cradl_ai/actions/common/common.mjs b/components/cradl_ai/actions/common/common.mjs new file mode 100644 index 0000000000000..fafa7eb27e763 --- /dev/null +++ b/components/cradl_ai/actions/common/common.mjs @@ -0,0 +1,63 @@ +import cradlAi from "../../cradl_ai.app.mjs"; +import constants from "../../common/constants.mjs"; +import fs from "fs"; + +export default { + props: { + cradlAi, + filePath: { + type: "string", + label: "File Path", + description: "The path to the document file saved to the `/tmp` directory (e.g. `/tmp/example.pdf`). [See the documentation](https://pipedream.com/docs/workflows/steps/code/nodejs/working-with-files/#the-tmp-directory).", + }, + contentType: { + type: "string", + label: "Content Type", + description: "The content type of the document", + options: constants.CONTENT_TYPES, + }, + name: { + type: "string", + label: "Name", + description: "Name of the document", + optional: true, + }, + description: { + type: "string", + label: "Description", + description: "Description of the document", + optional: true, + }, + datasetId: { + propDefinition: [ + cradlAi, + "datasetId", + ], + }, + }, + methods: { + createDocumentHandle($) { + return this.cradlAi.createDocumentHandle({ + $, + data: { + name: this.name, + description: this.description, + datasetId: this.datasetId, + }, + }); + }, + uploadFile($, fileUrl) { + const fileData = fs.readFileSync(this.filePath.includes("/tmp") + ? this.filePath + : `/tmp/${this.filePath}`); + return this.cradlAi.uploadDocument({ + $, + fileUrl, + data: fileData, + headers: { + "Content-Type": this.contentType, + }, + }); + }, + }, +}; diff --git a/components/cradl_ai/actions/parse-document-human-in-loop/parse-document-human-in-loop.mjs b/components/cradl_ai/actions/parse-document-human-in-loop/parse-document-human-in-loop.mjs new file mode 100644 index 0000000000000..c4a16d6eff9c1 --- /dev/null +++ b/components/cradl_ai/actions/parse-document-human-in-loop/parse-document-human-in-loop.mjs @@ -0,0 +1,41 @@ +import common from "../common/common.mjs"; + +export default { + ...common, + key: "cradl_ai-parse-document-human-in-loop", + name: "Parse Document with Human in the Loop", + description: "Sends a document to an existing flow for human-in-the-loop processing. [See the documentation](https://docs.cradl.ai/integrations/rest-api)", + version: "0.0.1", + type: "action", + props: { + ...common.props, + workflowId: { + propDefinition: [ + common.props.cradlAi, + "workflowId", + ], + }, + }, + async run({ $ }) { + const { + documentId, fileUrl, + } = await this.createDocumentHandle($); + await this.uploadFile($, fileUrl); + const { executionId } = await this.cradlAi.runWorkflow({ + $, + workflowId: this.workflowId, + data: { + input: { + documentId, + }, + }, + }); + const result = await this.cradlAi.getRunResult({ + $, + workflowId: this.workflowId, + executionId, + }); + $.export("$summary", `Successfully parsed document with ID: ${documentId}`); + return result; + }, +}; diff --git a/components/cradl_ai/actions/parse-document/parse-document.mjs b/components/cradl_ai/actions/parse-document/parse-document.mjs new file mode 100644 index 0000000000000..5b320fb1af25b --- /dev/null +++ b/components/cradl_ai/actions/parse-document/parse-document.mjs @@ -0,0 +1,34 @@ +import common from "../common/common.mjs"; + +export default { + ...common, + key: "cradl_ai-parse-document", + name: "Parse Document", + description: "Parses data from a document using a custom selected model. [See the documentation](https://docs.cradl.ai/integrations/rest-api)", + version: "0.0.1", + type: "action", + props: { + ...common.props, + modelId: { + propDefinition: [ + common.props.cradlAi, + "modelId", + ], + }, + }, + async run({ $ }) { + const { + documentId, fileUrl, + } = await this.createDocumentHandle($); + await this.uploadFile($, fileUrl); + const result = await this.cradlAi.createPrediction({ + $, + data: { + documentId, + modelId: this.modelId, + }, + }); + $.export("$summary", `Successfully parsed document with ID: ${documentId}`); + return result; + }, +}; diff --git a/components/cradl_ai/common/constants.mjs b/components/cradl_ai/common/constants.mjs new file mode 100644 index 0000000000000..6749916005b10 --- /dev/null +++ b/components/cradl_ai/common/constants.mjs @@ -0,0 +1,10 @@ +const CONTENT_TYPES = [ + "application/pdf", + "image/jpeg", + "image/png", + "image/tiff", +]; + +export default { + CONTENT_TYPES, +}; diff --git a/components/cradl_ai/cradl_ai.app.mjs b/components/cradl_ai/cradl_ai.app.mjs index 9d024c4b84146..3683f3997cc93 100644 --- a/components/cradl_ai/cradl_ai.app.mjs +++ b/components/cradl_ai/cradl_ai.app.mjs @@ -1,11 +1,206 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "cradl_ai", - propDefinitions: {}, + propDefinitions: { + modelId: { + type: "string", + label: "Model ID", + description: "The ID of the model to be used for parsing", + async options({ prevContext }) { + const params = prevContext?.nextToken + ? { + nextToken: prevContext.nextToken, + } + : {}; + const { + models, nextToken, + } = await this.listModels({ + params, + }); + const options = models?.map(({ + modelId: value, name: label, + }) => ({ + value, + label, + })) || []; + return { + options, + context: { + nextToken, + }, + }; + }, + }, + workflowId: { + type: "string", + label: "Workflow ID", + description: "The ID of the existing flow where the document will be sent", + async options({ prevContext }) { + const params = prevContext?.nextToken + ? { + nextToken: prevContext.nextToken, + } + : {}; + const { + workflows, nextToken, + } = await this.listWorkflows({ + params, + }); + const options = workflows?.map(({ + workflowId: value, name: label, + }) => ({ + value, + label, + })) || []; + return { + options, + context: { + nextToken, + }, + }; + }, + }, + datasetId: { + type: "string", + label: "Dataset ID", + description: "The ID of the dataset to use", + optional: true, + async options({ prevContext }) { + const params = prevContext?.nextToken + ? { + nextToken: prevContext.nextToken, + } + : {}; + const { + datasets, nextToken, + } = await this.listDatasets({ + params, + }); + const options = datasets?.map(({ + datasetId: value, name: label, + }) => ({ + value, + label, + })) || []; + return { + options, + context: { + nextToken, + }, + }; + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.lucidtech.ai/v1"; + }, + _makeRequest(opts = {}) { + const { + $ = this, + path, + headers, + ...otherOpts + } = opts; + return axios($, { + url: `${this._baseUrl()}${path}`, + headers: { + ...headers, + Authorization: `Bearer ${this.$auth.oauth_access_token}`, + }, + ...otherOpts, + }); + }, + listModels(opts = {}) { + return this._makeRequest({ + path: "/models", + ...opts, + }); + }, + listWorkflows(opts = {}) { + return this._makeRequest({ + path: "/workflows", + ...opts, + }); + }, + listDatasets(opts = {}) { + return this._makeRequest({ + path: "/datasets", + ...opts, + }); + }, + listExecutions({ + workflowId, ...opts + }) { + return this._makeRequest({ + path: `/workflows/${workflowId}/executions`, + ...opts, + }); + }, + createDocumentHandle(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/documents", + ...opts, + }); + }, + uploadDocument({ + fileUrl, ...opts + }) { + return this._makeRequest({ + method: "PUT", + url: fileUrl, + ...opts, + }); + }, + runWorkflow({ + workflowId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/workflows/${workflowId}/executions`, + ...opts, + }); + }, + getRunResult({ + workflowId, executionId, ...opts + }) { + return this._makeRequest({ + path: `/workflows/${workflowId}/executions/${executionId}`, + ...opts, + }); + }, + createPrediction(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/predictions", + ...opts, + }); + }, + async *paginate({ + resourceFn, + args, + resourceType, + max, + }) { + args.params = { + ...args.params, + }; + let count = 0; + do { + const response = await resourceFn(args); + const results = response[resourceType]; + for (const item of results) { + yield item; + count++; + if (max && count >= max) { + return; + } + } + args.params.nextToken = response?.nextToken; + } while (args.params.nextToken); }, }, -}; \ No newline at end of file +}; diff --git a/components/cradl_ai/package.json b/components/cradl_ai/package.json index 5276e696197f8..5e42698c0847f 100644 --- a/components/cradl_ai/package.json +++ b/components/cradl_ai/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/cradl_ai", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Cradl AI Components", "main": "cradl_ai.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^1.6.5" } -} \ No newline at end of file +} diff --git a/components/cradl_ai/sources/new-document-parsing-completed-instant/new-document-parsing-completed-instant.mjs b/components/cradl_ai/sources/new-document-parsing-completed-instant/new-document-parsing-completed-instant.mjs new file mode 100644 index 0000000000000..a5e1dd3d05139 --- /dev/null +++ b/components/cradl_ai/sources/new-document-parsing-completed-instant/new-document-parsing-completed-instant.mjs @@ -0,0 +1,86 @@ +import cradlAi from "../../cradl_ai.app.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + key: "cradl_ai-new-document-parsing-completed-instant", + name: "New Document Parsing Completed (Instant)", + description: "Emit new event when a document processing flow has completed.", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + cradlAi, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + workflowId: { + propDefinition: [ + cradlAi, + "workflowId", + ], + }, + }, + hooks: { + async deploy() { + await this.processEvent(25); + }, + }, + methods: { + _getLastTs() { + return this.db.get("lastTs") || 0; + }, + _setLastTs(lastTs) { + this.db.set("lastTs", lastTs); + }, + emitEvent(item) { + const meta = this.generateMeta(item); + this.$emit(item, meta); + }, + generateMeta(item) { + return { + id: item.executionId, + summary: `Execution completed with ID ${item.executionId}`, + ts: Date.parse(item.endTime), + }; + }, + async processEvent(max) { + const lastTs = this._getLastTs(); + const results = this.cradlAi.paginate({ + resourceFn: this.cradlAi.listExecutions, + args: { + workflowId: this.workflowId, + params: { + order: "descending", + sortBy: "endTime", + status: [ + "completed", + ], + }, + }, + resourceType: "executions", + max, + }); + const executions = []; + for await (const item of results) { + const ts = Date.parse(item.endTime); + if (ts >= lastTs) { + executions.push(item); + } else { + break; + } + } + if (!executions.length) { + return; + } + this._setLastTs(Date.parse(executions[0].endTime)); + executions.reverse().forEach((item) => this.emitEvent(item)); + }, + }, + async run() { + await this.processEvent(); + }, +}; diff --git a/components/encodian/README.md b/components/encodian/README.md new file mode 100644 index 0000000000000..7fc6059d813e7 --- /dev/null +++ b/components/encodian/README.md @@ -0,0 +1,11 @@ +# Overview + +The Encodian API provides robust document management and manipulation capabilities, enabling users to convert, merge, split, OCR, and watermark documents. This API integrates smoothly with Pipedream, allowing for the automation of document processing tasks within various workflows. By leveraging Encodian with Pipedream, users can create efficient automations that handle large volumes of documents, reducing manual effort and improving productivity in document-centric processes. + +# Example Use Cases + +- **Automated Invoice Processing**: Integrate Encodian with an email app like Gmail on Pipedream to automatically extract invoices received via email, convert them from PDF to a more editable format like Word, apply OCR if needed, and store the processed files in a cloud storage service like Google Drive. + +- **Contract Management Automation**: Use Encodian to merge multiple contract sections stored in Dropbox into a single document, apply a digital signature using a service like DocuSign, and then archive the signed contract in a SharePoint folder, all orchestrated within a Pipedream workflow. + +- **Employee Onboarding Documents Handling**: Set up a workflow on Pipedream where Encodian converts all new employee onboarding documents uploaded to a specific Google Drive folder into PDF format, watermarks them with the company logo, and then emails the final copies to the HR department using Microsoft Outlook. diff --git a/components/firebase_admin_sdk/actions/common/base.mjs b/components/firebase_admin_sdk/actions/common/base.mjs index c232242646bbd..6afc233f5c348 100644 --- a/components/firebase_admin_sdk/actions/common/base.mjs +++ b/components/firebase_admin_sdk/actions/common/base.mjs @@ -10,6 +10,21 @@ export default { ], }, }, + methods: { + parseBooleanValues(data) { + Object.entries(data).forEach(([ + key, + value, + ]) => { + data[key] = value === "false" + ? false + : value === "true" + ? true + : value; + }); + return data; + }, + }, async run({ $ }) { try { await this.firebase.initializeApp(this.databaseRegion); diff --git a/components/firebase_admin_sdk/actions/create-document/create-document.mjs b/components/firebase_admin_sdk/actions/create-document/create-document.mjs index b210ec4951c1c..0884da4ad448f 100644 --- a/components/firebase_admin_sdk/actions/create-document/create-document.mjs +++ b/components/firebase_admin_sdk/actions/create-document/create-document.mjs @@ -4,8 +4,8 @@ export default { ...common, key: "firebase_admin_sdk-create-document", name: "Create Document", - description: "Creates a New Document. [See the docs here](https://googleapis.dev/nodejs/firestore/latest/CollectionReference.html#add)", - version: "0.0.7", + description: "Creates a New Document. [See the documentation](https://googleapis.dev/nodejs/firestore/latest/CollectionReference.html#add)", + version: "0.0.8", type: "action", props: { ...common.props, @@ -34,7 +34,8 @@ export default { methods: { ...common.methods, async getResponse() { - return this.firebase.createDocument(this.collection, this.data, this.customId); + const data = this.parseBooleanValues(this.data); + return this.firebase.createDocument(this.collection, data, this.customId); }, emitSummary($, response) { $.export("$summary", `Successfully added document ${response?._path?.segments[1] ?? ""}`); diff --git a/components/firebase_admin_sdk/actions/create-realtime-db-record/create-realtime-db-record.mjs b/components/firebase_admin_sdk/actions/create-realtime-db-record/create-realtime-db-record.mjs index 147c5f63eb431..b0f3358d5de0f 100644 --- a/components/firebase_admin_sdk/actions/create-realtime-db-record/create-realtime-db-record.mjs +++ b/components/firebase_admin_sdk/actions/create-realtime-db-record/create-realtime-db-record.mjs @@ -5,7 +5,7 @@ export default { key: "firebase_admin_sdk-create-realtime-db-record", name: "Create Firebase Realtime Database Record", description: "Creates or replaces a child object within your Firebase Realtime Database. [See the docs here](https://firebase.google.com/docs/reference/js/database#update)", - version: "0.0.4", + version: "0.0.5", type: "action", props: { ...common.props, diff --git a/components/firebase_admin_sdk/actions/list-documents/list-documents.mjs b/components/firebase_admin_sdk/actions/list-documents/list-documents.mjs index 23b1587ee2340..b11b79c0e3d42 100644 --- a/components/firebase_admin_sdk/actions/list-documents/list-documents.mjs +++ b/components/firebase_admin_sdk/actions/list-documents/list-documents.mjs @@ -5,7 +5,7 @@ export default { key: "firebase_admin_sdk-list-documents", name: "List Documents", description: "Lists documents in a collection. [See the docs here](https://googleapis.dev/nodejs/firestore/latest/CollectionReference.html#listDocuments)", - version: "0.0.4", + version: "0.0.5", type: "action", props: { ...common.props, diff --git a/components/firebase_admin_sdk/actions/update-document/update-document.mjs b/components/firebase_admin_sdk/actions/update-document/update-document.mjs index 4f14c8d495012..a860194e52a18 100644 --- a/components/firebase_admin_sdk/actions/update-document/update-document.mjs +++ b/components/firebase_admin_sdk/actions/update-document/update-document.mjs @@ -3,9 +3,9 @@ import common from "../common/base.mjs"; export default { ...common, key: "firebase_admin_sdk-update-document", - name: "Update Documents", - description: "Updates a Document. [See the docs here](https://googleapis.dev/nodejs/firestore/latest/DocumentReference.html#update)", - version: "0.0.4", + name: "Update Document", + description: "Updates a Document. [See the documentation](https://googleapis.dev/nodejs/firestore/latest/DocumentReference.html#update)", + version: "0.0.5", type: "action", props: { ...common.props, @@ -39,7 +39,8 @@ export default { methods: { ...common.methods, async getResponse() { - return this.firebase.updateDocument(this.collection, this.document, this.data); + const data = this.parseBooleanValues(this.data); + return this.firebase.updateDocument(this.collection, this.document, data); }, emitSummary($) { $.export("$summary", `Successfully updated document ${this.document}`); diff --git a/components/firebase_admin_sdk/package.json b/components/firebase_admin_sdk/package.json index 6c5b4f7472d96..db161b1aebc3f 100644 --- a/components/firebase_admin_sdk/package.json +++ b/components/firebase_admin_sdk/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/firebase_admin_sdk", - "version": "0.0.7", + "version": "0.0.8", "description": "Pipedream Firebase Admin SDK Components", "main": "firebase_admin_sdk.app.mjs", "keywords": [ diff --git a/components/fly_io/README.md b/components/fly_io/README.md new file mode 100644 index 0000000000000..e4141fa40e63e --- /dev/null +++ b/components/fly_io/README.md @@ -0,0 +1,11 @@ +# Overview + +Fly.io is a platform that allows you to run full-stack apps and databases close to your users globally. The Fly.io API enables developers to manage applications, handle deployments, and scale their services dynamically. Using the Fly.io API with Pipedream provides a seamless way to automate these operations, integrate with other services, and enhance serverless workflow capabilities. + +# Example Use Cases + +- **Dynamic App Scaling Based on Traffic**: Automate the scaling of your applications on Fly.io based on real-time traffic data from Google Analytics. Set up a Pipedream workflow that triggers when a specified traffic threshold is reached on Google Analytics, then scales your Fly.io application instances up or down accordingly. + +- **Deployment Automation from GitHub**: Automate the deployment of your applications hosted on GitHub to Fly.io whenever a new commit is pushed to the main branch. Use a Pipedream workflow that listens for GitHub push events, builds your application, and deploys it to Fly.io, ensuring your live applications are always up-to-date with the latest code. + +- **Database Backup Notifications**: Set up a workflow on Pipedream to automate database backups on Fly.io and send notifications via Slack whenever a backup is completed. This workflow can ensure team members are always informed about the status of backups, enhancing data management practices. diff --git a/components/fly_io/actions/create-app/create-app.mjs b/components/fly_io/actions/create-app/create-app.mjs new file mode 100644 index 0000000000000..a8a913f807391 --- /dev/null +++ b/components/fly_io/actions/create-app/create-app.mjs @@ -0,0 +1,59 @@ +import app from "../../fly_io.app.mjs"; + +export default { + key: "fly_io-create-app", + name: "Create App", + description: "Create an app with the specified details in the request body. [See the documentation](https://docs.machines.dev/#tag/apps/post/apps)", + version: "0.0.1", + type: "action", + props: { + app, + appName: { + type: "string", + label: "App Name", + description: "The name of the app", + }, + enableSubdomains: { + type: "boolean", + label: "Enable Subdomains", + description: "Whether to enable subdomains for the app", + optional: true, + }, + network: { + type: "string", + label: "Network", + description: "The network for the app", + optional: true, + }, + }, + methods: { + createApp(args = {}) { + return this.app.post({ + path: "/apps", + ...args, + }); + }, + }, + async run({ $ }) { + const { + app, + createApp, + appName, + enableSubdomains, + network, + } = this; + + const response = await createApp({ + $, + data: { + app_name: appName, + enable_subdomains: enableSubdomains, + network, + org_slug: app.getOrgSlug(), + }, + }); + + $.export("$summary", `Successfully created app with ID \`${response.id}\``); + return response; + }, +}; diff --git a/components/fly_io/actions/create-machine/create-machine.mjs b/components/fly_io/actions/create-machine/create-machine.mjs new file mode 100644 index 0000000000000..d0852f6fd69a4 --- /dev/null +++ b/components/fly_io/actions/create-machine/create-machine.mjs @@ -0,0 +1,76 @@ +import app from "../../fly_io.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "fly_io-create-machine", + name: "Create Machine", + description: "Create a machine within a specific app using the details provided in the request body. [See the documentation](https://docs.machines.dev/#tag/machines/post/apps/%7Bapp_name%7D/machines)", + version: "0.0.1", + type: "action", + props: { + app, + appName: { + propDefinition: [ + app, + "appId", + () => ({ + mapper: ({ name }) => name, + }), + ], + }, + name: { + type: "string", + label: "Machine Name", + description: "The name of the machine within the app", + }, + config: { + type: "string", + label: "Machine Configuration", + description: "The configuration details of the machine in JSON format. Eg. `{ \"auto_destroy\": true, \"image\": \"ubuntu:latest\" }`", + }, + skipLaunch: { + type: "boolean", + label: "Skip Launch", + description: "Whether to skip the launch of the machine", + optional: true, + }, + skipServiceRegistration: { + type: "boolean", + label: "Skip Service Registration", + description: "Whether to skip the registration of the machine as a service", + optional: true, + }, + }, + methods: { + createMachine({ + appName, ...args + } = {}) { + return this.app.post({ + path: `/apps/${appName}/machines`, + ...args, + }); + }, + }, + async run({ $ }) { + const { + createMachine, + appName, + name, + config, + skipLaunch, + skipServiceRegistration, + } = this; + const response = await createMachine({ + $, + appName, + data: { + name, + config: utils.valueToObject(config), + skip_launch: skipLaunch, + skip_service_registration: skipServiceRegistration, + }, + }); + $.export("$summary", `Successfully created machine with ID \`${response.id}\`.`); + return response; + }, +}; diff --git a/components/fly_io/actions/create-volume/create-volume.mjs b/components/fly_io/actions/create-volume/create-volume.mjs new file mode 100644 index 0000000000000..1d6cf4357ef9f --- /dev/null +++ b/components/fly_io/actions/create-volume/create-volume.mjs @@ -0,0 +1,68 @@ +import app from "../../fly_io.app.mjs"; + +export default { + key: "fly_io-create-volume", + name: "Create Volume", + description: "Create a volume for a specific app using the details provided in the request body. [See the documentation](https://docs.machines.dev/#tag/volumes/post/apps/%7Bapp_name%7D/volumes)", + version: "0.0.1", + type: "action", + props: { + app, + appName: { + propDefinition: [ + app, + "appId", + () => ({ + mapper: ({ name }) => name, + }), + ], + }, + name: { + type: "string", + label: "Volume Name", + description: "The name of the volume", + }, + sizeGb: { + type: "integer", + label: "Volume Size (GB)", + description: "The size of the volume in GB", + }, + region: { + type: "string", + label: "Volume Region", + description: "The region where the volume will be created", + optional: true, + }, + }, + methods: { + createVolume({ + appName, ...args + } = {}) { + return this.app.post({ + path: `/apps/${appName}/volumes`, + ...args, + }); + }, + }, + async run({ $ }) { + const { + createVolume, + appName, + name, + sizeGb, + region, + } = this; + const response = await createVolume({ + $, + appName, + data: { + name, + size_gb: sizeGb, + region, + }, + }); + + $.export("$summary", `Successfully created volume with ID \`${response.id}\``); + return response; + }, +}; diff --git a/components/fly_io/common/constants.mjs b/components/fly_io/common/constants.mjs new file mode 100644 index 0000000000000..8adcb50cc3a20 --- /dev/null +++ b/components/fly_io/common/constants.mjs @@ -0,0 +1,7 @@ +const BASE_URL = "https://api.machines.dev"; +const VERSION_PATH = "/v1"; + +export default { + BASE_URL, + VERSION_PATH, +}; diff --git a/components/fly_io/common/utils.mjs b/components/fly_io/common/utils.mjs new file mode 100644 index 0000000000000..b7e58f17e7776 --- /dev/null +++ b/components/fly_io/common/utils.mjs @@ -0,0 +1,26 @@ +import { ConfigurationError } from "@pipedream/platform"; + +function isJson(value) { + try { + JSON.parse(value); + } catch (e) { + return false; + } + return true; +} + +function valueToObject(value) { + if (typeof(value) === "object" || value === undefined) { + return value; + } + + if (!isJson(value)) { + throw new ConfigurationError(`Make sure the custom expression contains a valid JSON object: \`${value}\``); + } + + return JSON.parse(value); +} + +export default { + valueToObject, +}; diff --git a/components/fly_io/fly_io.app.mjs b/components/fly_io/fly_io.app.mjs new file mode 100644 index 0000000000000..e9d7cde3bedfd --- /dev/null +++ b/components/fly_io/fly_io.app.mjs @@ -0,0 +1,97 @@ +import { axios } from "@pipedream/platform"; +import constants from "./common/constants.mjs"; + +export default { + type: "app", + app: "fly_io", + propDefinitions: { + appId: { + type: "string", + label: "App Name", + description: "The name of the app", + async options({ + mapper = ({ + id: value, name: label, + }) => ({ + value, + label, + }), + }) { + const { apps } = await this.listApps({ + params: { + org_slug: this.getOrgSlug(), + }, + }); + return apps.map(mapper); + }, + }, + machineId: { + type: "string", + label: "Machine ID", + description: "The ID of the machine", + async options({ appName }) { + const machines = await this.listMachines({ + appName, + }); + return machines.map(({ + name: label, id: value, + }) => ({ + label, + value, + })); + }, + }, + }, + methods: { + getOrgSlug() { + return this.$auth.org_slug; + }, + getUrl(path) { + return `${constants.BASE_URL}${constants.VERSION_PATH}${path}`; + }, + getHeaders(headers) { + return { + "Content-Type": "application/json", + "Authorization": `Bearer ${this.$auth.access_token}`, + ...headers, + }; + }, + _makeRequest({ + $ = this, path, headers, ...args + } = {}) { + return axios($, { + ...args, + url: this.getUrl(path), + headers: this.getHeaders(headers), + }); + }, + post(args = {}) { + return this._makeRequest({ + method: "POST", + ...args, + }); + }, + listApps(args = {}) { + return this._makeRequest({ + path: "/apps", + ...args, + }); + }, + listMachines({ + appName, ...args + } = {}) { + return this._makeRequest({ + path: `/apps/${appName}/machines`, + ...args, + }); + }, + listEvents({ + appName, machineId, ...args + } = {}) { + return this._makeRequest({ + path: `/apps/${appName}/machines/${machineId}/events`, + ...args, + }); + }, + }, +}; diff --git a/components/fly_io/package.json b/components/fly_io/package.json new file mode 100644 index 0000000000000..a6828bbad94a1 --- /dev/null +++ b/components/fly_io/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/fly_io", + "version": "0.1.0", + "description": "Pipedream Fly.io Components", + "main": "fly_io.app.mjs", + "keywords": [ + "pipedream", + "fly_io" + ], + "homepage": "https://pipedream.com/apps/fly_io", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "1.6.5" + } +} diff --git a/components/fly_io/sources/common/polling.mjs b/components/fly_io/sources/common/polling.mjs new file mode 100644 index 0000000000000..258511922ddd1 --- /dev/null +++ b/components/fly_io/sources/common/polling.mjs @@ -0,0 +1,48 @@ +import { + ConfigurationError, + DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; +import app from "../../fly_io.app.mjs"; + +export default { + props: { + app, + timer: { + type: "$.interface.timer", + label: "Polling Schedule", + description: "How often to poll the API", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + generateMeta() { + throw new ConfigurationError("generateMeta is not implemented"); + }, + getResourcesFn() { + throw new ConfigurationError("getResourcesFn is not implemented"); + }, + getResourcesFnArgs() { + throw new ConfigurationError("getResourcesFnArgs is not implemented"); + }, + processResource(resource) { + const meta = this.generateMeta(resource); + this.$emit(resource, meta); + }, + }, + async run() { + const { + getResourcesFn, + getResourcesFnArgs, + processResource, + } = this; + + const resourcesFn = getResourcesFn(); + const resources = await resourcesFn(getResourcesFnArgs()); + + Array.from(resources) + .reverse() + .forEach(processResource); + }, +}; diff --git a/components/fly_io/sources/new-event-created/new-event-created.mjs b/components/fly_io/sources/new-event-created/new-event-created.mjs new file mode 100644 index 0000000000000..df7980f0717de --- /dev/null +++ b/components/fly_io/sources/new-event-created/new-event-created.mjs @@ -0,0 +1,55 @@ +import common from "../common/polling.mjs"; + +export default { + ...common, + key: "fly_io-new-event-created", + name: "New Event Created", + description: "Emit new event when a new event is created in Fly.io. [See the documentation](https://docs.machines.dev/#tag/machines/get/apps/{app_name}/machines/{machine_id}/events)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + appName: { + propDefinition: [ + common.props.app, + "appId", + () => ({ + mapper: ({ name }) => name, + }), + ], + }, + machineId: { + propDefinition: [ + common.props.app, + "machineId", + ({ appName }) => ({ + appName, + }), + ], + }, + }, + methods: { + ...common.methods, + getResourcesFn() { + return this.app.listEvents; + }, + getResourcesFnArgs() { + const { + appName, + machineId, + } = this; + return { + appName, + machineId, + }; + }, + generateMeta(resource) { + return { + id: resource.id, + summary: `New Event: ${resource.id}`, + ts: resource.timestamp, + }; + }, + }, +}; diff --git a/components/fractel/README.md b/components/fractel/README.md new file mode 100644 index 0000000000000..14400923f7160 --- /dev/null +++ b/components/fractel/README.md @@ -0,0 +1,11 @@ +# Overview + +The FracTEL API enables integration of advanced telecommunications features into applications, allowing for the management of voice and messaging functionalities. This API can be particularly powerful when used on Pipedream, where it can be combined with hundreds of other apps to create robust, automated workflows. These can range from triggering calls based on specific events to automating SMS notifications for critical alerts. + +# Example Use Cases + +- **Customer Support Ticket Voice Alerts**: Automatically initiate a phone call via FracTEL to a support manager when a high-priority support ticket is created in a system like Zendesk. This ensures immediate attention to critical issues without delay. + +- **Event-Driven SMS Notifications**: Set up a workflow where FracTEL sends a text message to a list of attendees from a Google Sheets spreadsheet whenever a new event is scheduled in a Google Calendar. This keeps everyone informed and can increase event attendance. + +- **Voicemail Transcription and Analysis**: Capture voicemails received on FracTEL, transcribe them using an AI service like Google Cloud Speech-to-Text, and analyze the sentiment with Google Cloud Natural Language API. This can help in prioritizing responses based on the urgency and sentiment of the voicemail. diff --git a/components/fractel/actions/call-phone/call-phone.mjs b/components/fractel/actions/call-phone/call-phone.mjs new file mode 100644 index 0000000000000..6e963cd74ec30 --- /dev/null +++ b/components/fractel/actions/call-phone/call-phone.mjs @@ -0,0 +1,44 @@ +import fractel from "../../fractel.app.mjs"; + +export default { + key: "fractel-call-phone", + name: "Call Phone", + description: "Initiates a new phone call to the provided number.", + version: "0.0.1", + type: "action", + props: { + fractel, + phoneNumber: { + propDefinition: [ + fractel, + "phoneNumber", + ], + }, + to: { + propDefinition: [ + fractel, + "to", + ], + }, + message: { + propDefinition: [ + fractel, + "message", + ], + }, + }, + async run({ $ }) { + const response = await this.fractel.initiateCall({ + $, + data: { + fonenumber: this.phoneNumber, + to: this.to, + service_type: "TTS", + service_id: this.message, + }, + }); + + $.export("$summary", `Successfully initiated a call with Id: ${response.call.id}`); + return response; + }, +}; diff --git a/components/fractel/actions/send-sms-mms/send-sms-mms.mjs b/components/fractel/actions/send-sms-mms/send-sms-mms.mjs new file mode 100644 index 0000000000000..a3d88a8299f29 --- /dev/null +++ b/components/fractel/actions/send-sms-mms/send-sms-mms.mjs @@ -0,0 +1,60 @@ +import { parseObject } from "../../common/utils.mjs"; +import fractel from "../../fractel.app.mjs"; + +export default { + key: "fractel-send-sms-mms", + name: "Send SMS or MMS", + description: "Allows to send an SMS or MMS to a particular phone number. [See the documentation](https://developer.fonestorm.com/reference/sendmessage)", + version: "0.0.1", + type: "action", + props: { + fractel, + phoneNumber: { + propDefinition: [ + fractel, + "phoneNumber", + ], + description: "The phone number to send the message to, including country code.", + }, + to: { + propDefinition: [ + fractel, + "to", + ], + }, + message: { + propDefinition: [ + fractel, + "message", + ], + description: "The message content for SMS or MMS.", + optional: true, + }, + media: { + propDefinition: [ + fractel, + "media", + ], + optional: true, + }, + }, + async run({ $ }) { + if (!this.message && !this.media) { + throw new Error("Either message or media must be provided."); + } + const response = await this.fractel.sendMessage({ + $, + data: { + to: this.to, + fonenumber: this.phoneNumber, + message: this.message, + media: this.media && parseObject(this.media), + }, + }); + + $.export("$summary", `Successfully sent ${this.message + ? "SMS" + : "MMS"} with Id: ${response.message.id}`); + return response; + }, +}; diff --git a/components/fractel/common/utils.mjs b/components/fractel/common/utils.mjs new file mode 100644 index 0000000000000..4b5f3f9d301f8 --- /dev/null +++ b/components/fractel/common/utils.mjs @@ -0,0 +1,22 @@ +export const parseObject = (obj) => { + 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/fractel/fractel.app.mjs b/components/fractel/fractel.app.mjs index 65866d7e2e844..b111abfb23be6 100644 --- a/components/fractel/fractel.app.mjs +++ b/components/fractel/fractel.app.mjs @@ -1,11 +1,72 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "fractel", - propDefinitions: {}, + propDefinitions: { + phoneNumber: { + type: "string", + label: "Phone Number", + description: "The phone number to call to, including country code.", + async options() { + const { fonenumbers } = await this.listFoneNumbers(); + return fonenumbers; + }, + }, + to: { + type: "string", + label: "To", + description: "The recipient phone number, including country code.", + }, + message: { + type: "string", + label: "Message", + description: "The message content for TTS.", + }, + media: { + type: "string", + label: "Media URL", + description: "Media URL of the media file for MMS.", + optional: true, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.fonestorm.com/v2"; + }, + _headers() { + return { + token: `${this.$auth.oauth_access_token}`, + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + headers: this._headers(), + ...opts, + }); + }, + initiateCall(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/calls", + ...opts, + }); + }, + listFoneNumbers(opts = {}) { + return this._makeRequest({ + path: "/fonenumbers", + ...opts, + }); + }, + sendMessage(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/messages/send", + ...opts, + }); }, }, -}; \ No newline at end of file +}; diff --git a/components/fractel/package.json b/components/fractel/package.json index 548b3402eeaf2..509e5d1ceacba 100644 --- a/components/fractel/package.json +++ b/components/fractel/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/fractel", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream FracTEL Components", "main": "fractel.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^1.6.5" } -} \ No newline at end of file +} diff --git a/components/github/README.md b/components/github/README.md index 00806a4407715..2dfa41e54a328 100644 --- a/components/github/README.md +++ b/components/github/README.md @@ -18,7 +18,7 @@ Click the image below to watch a brief demo on YouTube.


- + Pipedream demo static image

diff --git a/components/github/actions/create-branch/create-branch.mjs b/components/github/actions/create-branch/create-branch.mjs index d9417cc7fce00..673380183e0bc 100644 --- a/components/github/actions/create-branch/create-branch.mjs +++ b/components/github/actions/create-branch/create-branch.mjs @@ -5,7 +5,7 @@ export default { key: "github-create-branch", name: "Create Branch", description: "Create a new branch in a Github repo. [See docs here](https://docs.github.com/en/rest/git/refs?apiVersion=2022-11-28#create-a-reference)", - version: "0.0.9", + version: "0.0.10", type: "action", props: { github, diff --git a/components/github/actions/create-gist/create-gist.mjs b/components/github/actions/create-gist/create-gist.mjs index 32f1401e5a6b4..21159da508266 100644 --- a/components/github/actions/create-gist/create-gist.mjs +++ b/components/github/actions/create-gist/create-gist.mjs @@ -5,7 +5,7 @@ export default { key: "github-create-gist", name: "Create Gist", description: "Allows you to add a new gist with one or more files. [See docs here](https://docs.github.com/en/rest/gists/gists?apiVersion=2022-11-28#create-a-gist)", - version: "0.0.4", + version: "0.0.5", type: "action", props: { github, diff --git a/components/github/actions/create-issue-comment/create-issue-comment.mjs b/components/github/actions/create-issue-comment/create-issue-comment.mjs index 7fb9b0a4e631d..a40b27c11165f 100644 --- a/components/github/actions/create-issue-comment/create-issue-comment.mjs +++ b/components/github/actions/create-issue-comment/create-issue-comment.mjs @@ -4,7 +4,7 @@ export default { key: "github-create-issue-comment", name: "Create Issue Comment", description: "Create a new comment in a issue. [See docs here](https://docs.github.com/en/rest/issues/comments#create-an-issue-comment)", - version: "0.0.15", + version: "0.0.16", type: "action", props: { github, diff --git a/components/github/actions/create-issue/create-issue.mjs b/components/github/actions/create-issue/create-issue.mjs index 4399308772200..48745fa13c40e 100644 --- a/components/github/actions/create-issue/create-issue.mjs +++ b/components/github/actions/create-issue/create-issue.mjs @@ -4,7 +4,7 @@ export default { key: "github-create-issue", name: "Create Issue", description: "Create a new issue in a Gihub repo. [See docs here](https://docs.github.com/en/rest/issues/issues#create-an-issue)", - version: "0.2.14", + version: "0.2.15", type: "action", props: { github, diff --git a/components/github/actions/create-or-update-file-contents/create-or-update-file-contents.mjs b/components/github/actions/create-or-update-file-contents/create-or-update-file-contents.mjs index 6cfa9b26c24fc..2475c6026ea98 100644 --- a/components/github/actions/create-or-update-file-contents/create-or-update-file-contents.mjs +++ b/components/github/actions/create-or-update-file-contents/create-or-update-file-contents.mjs @@ -4,7 +4,7 @@ export default { key: "github-create-or-update-file-contents", name: "Create or update file contents", description: "Create or update a file in a repository. This will replace an existing file. [See docs here](https://docs.github.com/en/rest/repos/contents#create-or-update-file-contents)", - version: "0.0.11", + version: "0.0.12", type: "action", props: { github, diff --git a/components/github/actions/create-pull-request/create-pull-request.mjs b/components/github/actions/create-pull-request/create-pull-request.mjs index 988465a3b5828..a5fe02ac598ad 100644 --- a/components/github/actions/create-pull-request/create-pull-request.mjs +++ b/components/github/actions/create-pull-request/create-pull-request.mjs @@ -9,7 +9,7 @@ export default { Creates a new pull request for a specified repository. [See docs here](https://docs.github.com/en/rest/pulls/pulls#create-a-pull-request) `), - version: "0.0.6", + version: "0.0.7", type: "action", props: { github, diff --git a/components/github/actions/create-repository/create-repository.mjs b/components/github/actions/create-repository/create-repository.mjs index 8708477b070d9..89f0e89c9e9bb 100644 --- a/components/github/actions/create-repository/create-repository.mjs +++ b/components/github/actions/create-repository/create-repository.mjs @@ -4,7 +4,7 @@ export default { key: "github-create-repository", name: "Create Repository", description: "Creates a new repository for the authenticated user. [See docs here](https://docs.github.com/en/rest/repos/repos#create-a-repository-for-the-authenticated-user)", - version: "0.0.10", + version: "0.0.11", type: "action", props: { github, diff --git a/components/github/actions/get-issue-assignees/get-issue-assignees.mjs b/components/github/actions/get-issue-assignees/get-issue-assignees.mjs index 701ee0d40e489..3154e3acd8125 100644 --- a/components/github/actions/get-issue-assignees/get-issue-assignees.mjs +++ b/components/github/actions/get-issue-assignees/get-issue-assignees.mjs @@ -4,7 +4,7 @@ export default { key: "github-get-issue-assignees", name: "Get Issue Assignees", description: "Get assignees for an issue in a Gihub repo. [See docs here](https://docs.github.com/en/rest/issues/issues#get-an-issue)", - version: "0.0.15", + version: "0.0.16", type: "action", props: { github, diff --git a/components/github/actions/get-repository-content/get-repository-content.mjs b/components/github/actions/get-repository-content/get-repository-content.mjs index b1d541277348a..acec2a983da0e 100644 --- a/components/github/actions/get-repository-content/get-repository-content.mjs +++ b/components/github/actions/get-repository-content/get-repository-content.mjs @@ -8,7 +8,7 @@ export default { Get the content of a file or directory in a specific repository. [See docs here](https://docs.github.com/en/rest/repos/contents#get-repository-content) `), - version: "0.0.14", + version: "0.0.15", type: "action", props: { github, diff --git a/components/github/actions/get-repository/get-repository.mjs b/components/github/actions/get-repository/get-repository.mjs index 7fe6af05ecd7f..60dd0f4072cab 100644 --- a/components/github/actions/get-repository/get-repository.mjs +++ b/components/github/actions/get-repository/get-repository.mjs @@ -4,7 +4,7 @@ export default { key: "github-get-repository", name: "Get Repository", description: "Get specific repository. [See docs here](https://docs.github.com/en/rest/repos/repos#get-a-repository)", - version: "0.0.15", + version: "0.0.16", type: "action", props: { github, diff --git a/components/github/actions/get-reviewers/get-reviewers.mjs b/components/github/actions/get-reviewers/get-reviewers.mjs index 8fa7f55201703..b672ac625d6cd 100644 --- a/components/github/actions/get-reviewers/get-reviewers.mjs +++ b/components/github/actions/get-reviewers/get-reviewers.mjs @@ -6,7 +6,7 @@ export default { key: "github-get-reviewers", name: "Get Reviewers", description: "Get reviewers for a PR ([see docs](https://docs.github.com/en/rest/pulls/reviews#list-reviews-for-a-pull-request)) or Commit SHA ([see docs](https://docs.github.com/en/rest/commits/commits#list-pull-requests-associated-with-a-commit)).", - version: "0.0.15", + version: "0.0.16", type: "action", props: { github, diff --git a/components/github/actions/list-gists-for-a-user/list-gists-for-a-user.mjs b/components/github/actions/list-gists-for-a-user/list-gists-for-a-user.mjs index ce2a1b90be816..4d23f898ed49b 100644 --- a/components/github/actions/list-gists-for-a-user/list-gists-for-a-user.mjs +++ b/components/github/actions/list-gists-for-a-user/list-gists-for-a-user.mjs @@ -4,7 +4,7 @@ export default { key: "github-list-gists-for-a-user", name: "List Gists for a User", description: "Lists public gists for the specified user. [See docs here](https://docs.github.com/en/rest/gists/gists?apiVersion=2022-11-28#list-gists-for-a-user)", - version: "0.0.4", + version: "0.0.5", type: "action", props: { github, diff --git a/components/github/actions/list-releases/list-releases.mjs b/components/github/actions/list-releases/list-releases.mjs index a0fe41f07fbe0..b64fd55b5e9be 100644 --- a/components/github/actions/list-releases/list-releases.mjs +++ b/components/github/actions/list-releases/list-releases.mjs @@ -6,7 +6,7 @@ export default { key: "github-list-releases", name: "List Releases", description: "List releases for a repository [See the documentation](https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#list-releases)", - version: "0.0.3", + version: "0.0.4", type: "action", props: { github, diff --git a/components/github/actions/search-issues-and-pull-requests/search-issues-and-pull-requests.mjs b/components/github/actions/search-issues-and-pull-requests/search-issues-and-pull-requests.mjs index 0c1574c5acaab..a8e360ce44c2f 100644 --- a/components/github/actions/search-issues-and-pull-requests/search-issues-and-pull-requests.mjs +++ b/components/github/actions/search-issues-and-pull-requests/search-issues-and-pull-requests.mjs @@ -4,7 +4,7 @@ export default { key: "github-search-issues-and-pull-requests", name: "Search Issues and Pull Requests", description: "Find issues and pull requests by state and keyword. [See docs here](https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests)", - version: "0.1.14", + version: "0.1.15", type: "action", props: { github, diff --git a/components/github/actions/update-gist/update-gist.mjs b/components/github/actions/update-gist/update-gist.mjs index 2fdd60faaf4ef..eddd208125e02 100644 --- a/components/github/actions/update-gist/update-gist.mjs +++ b/components/github/actions/update-gist/update-gist.mjs @@ -6,7 +6,7 @@ export default { key: "github-update-gist", name: "Update Gist", description: "Allows you to update a gist's description and to update, delete, or rename gist files. Files from the previous version of the gist that aren't explicitly changed during an edit are unchanged. At least one of description or files is required. [See docs here](https://docs.github.com/en/rest/gists/gists?apiVersion=2022-11-28#update-a-gist)", - version: "0.0.4", + version: "0.0.5", type: "action", props: { github, diff --git a/components/github/actions/update-issue/update-issue.mjs b/components/github/actions/update-issue/update-issue.mjs index fe1f2af0d6459..f71335b5f9441 100644 --- a/components/github/actions/update-issue/update-issue.mjs +++ b/components/github/actions/update-issue/update-issue.mjs @@ -4,7 +4,7 @@ export default { key: "github-update-issue", name: "Update Issue", description: "Update a new issue in a Gihub repo. [See docs here](https://docs.github.com/en/rest/issues/issues#update-an-issue)", - version: "0.1.14", + version: "0.1.15", type: "action", props: { github, diff --git a/components/github/package.json b/components/github/package.json index 802db4b0c6f59..c7c791bb5c559 100644 --- a/components/github/package.json +++ b/components/github/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/github", - "version": "1.1.1", + "version": "1.1.2", "description": "Pipedream Github Components", "main": "github.app.mjs", "keywords": [ diff --git a/components/github/sources/new-branch/new-branch.mjs b/components/github/sources/new-branch/new-branch.mjs index dc26230a350b3..ef019e1a87d6e 100644 --- a/components/github/sources/new-branch/new-branch.mjs +++ b/components/github/sources/new-branch/new-branch.mjs @@ -11,7 +11,7 @@ export default { key: "github-new-branch", name: "New Branch", description: `Emit new event when a branch is created [See the documentation](${DOCS_LINK})`, - version: "1.0.1", + version: "1.0.2", type: "source", dedupe: "unique", methods: { diff --git a/components/github/sources/new-card-in-column/new-card-in-column.mjs b/components/github/sources/new-card-in-column/new-card-in-column.mjs index c1501205182aa..2352d6ac35a79 100644 --- a/components/github/sources/new-card-in-column/new-card-in-column.mjs +++ b/components/github/sources/new-card-in-column/new-card-in-column.mjs @@ -6,7 +6,7 @@ export default { key: "github-new-card-in-column", name: "New Card in Column (Classic Projects)", description: "Emit new event when a (classic) project card is created or moved to a specific column. For Projects V2 use `New Issue with Status` trigger. [More information here](https://docs.github.com/en/issues/organizing-your-work-with-project-boards/tracking-work-with-project-boards/adding-issues-and-pull-requests-to-a-project-board)", - version: "1.0.1", + version: "1.0.2", type: "source", props: { ...common.props, diff --git a/components/github/sources/new-collaborator/new-collaborator.mjs b/components/github/sources/new-collaborator/new-collaborator.mjs index 91c73d5e37d17..418f779055930 100644 --- a/components/github/sources/new-collaborator/new-collaborator.mjs +++ b/components/github/sources/new-collaborator/new-collaborator.mjs @@ -11,7 +11,7 @@ export default { key: "github-new-collaborator", name: "New Collaborator", description: `Emit new event when a collaborator is added [See the documentation](${DOCS_LINK})`, - version: "1.0.1", + version: "1.0.2", type: "source", dedupe: "unique", methods: { diff --git a/components/github/sources/new-commit-comment/new-commit-comment.mjs b/components/github/sources/new-commit-comment/new-commit-comment.mjs index 0c57ecc521330..6201dfa6c8742 100644 --- a/components/github/sources/new-commit-comment/new-commit-comment.mjs +++ b/components/github/sources/new-commit-comment/new-commit-comment.mjs @@ -11,7 +11,7 @@ export default { key: "github-new-commit-comment", name: "New Commit Comment", description: `Emit new event when a commit comment is created [See the documentation](${DOCS_LINK})`, - version: "1.0.1", + version: "1.0.2", type: "source", dedupe: "unique", methods: { diff --git a/components/github/sources/new-commit/new-commit.mjs b/components/github/sources/new-commit/new-commit.mjs index 4e57d804c1b54..6ddc22327d1e1 100644 --- a/components/github/sources/new-commit/new-commit.mjs +++ b/components/github/sources/new-commit/new-commit.mjs @@ -11,7 +11,7 @@ export default { key: "github-new-commit", name: "New Commit", description: `Emit new event when commits are pushed to a branch [See the documentation](${DOCS_LINK})`, - version: "1.0.1", + version: "1.0.2", type: "source", dedupe: "unique", props: { diff --git a/components/github/sources/new-discussion/new-discussion.mjs b/components/github/sources/new-discussion/new-discussion.mjs index a55086a228faf..f0003bb09e24f 100644 --- a/components/github/sources/new-discussion/new-discussion.mjs +++ b/components/github/sources/new-discussion/new-discussion.mjs @@ -11,7 +11,7 @@ export default { key: "github-new-discussion", name: "New Discussion", description: `Emit new event when a discussion is created [See the documentation](${DOCS_LINK})`, - version: "1.0.1", + version: "1.0.2", type: "source", dedupe: "unique", methods: { diff --git a/components/github/sources/new-fork/new-fork.mjs b/components/github/sources/new-fork/new-fork.mjs index 1b151185ee455..4a81190069841 100644 --- a/components/github/sources/new-fork/new-fork.mjs +++ b/components/github/sources/new-fork/new-fork.mjs @@ -11,7 +11,7 @@ export default { key: "github-new-fork", name: "New Fork", description: `Emit new event when a repository is forked [See the documentation](${DOCS_LINK})`, - version: "1.0.1", + version: "1.0.2", type: "source", dedupe: "unique", methods: { diff --git a/components/github/sources/new-gist/new-gist.mjs b/components/github/sources/new-gist/new-gist.mjs index 10d88f8c2385c..cb4ef11835abe 100644 --- a/components/github/sources/new-gist/new-gist.mjs +++ b/components/github/sources/new-gist/new-gist.mjs @@ -5,7 +5,7 @@ export default { key: "github-new-gist", name: "New Gist", description: "Emit new events when new gists are created by the authenticated user", - version: "0.1.15", + version: "0.1.16", type: "source", dedupe: "unique", async run() { diff --git a/components/github/sources/new-issue-with-status/new-issue-with-status.mjs b/components/github/sources/new-issue-with-status/new-issue-with-status.mjs index 5c7206fac4c22..65be865a1bb31 100644 --- a/components/github/sources/new-issue-with-status/new-issue-with-status.mjs +++ b/components/github/sources/new-issue-with-status/new-issue-with-status.mjs @@ -7,7 +7,7 @@ export default { key: "github-new-issue-with-status", name: "New Issue with Status (Projects V2)", description: "Emit new event when a project issue is tagged with a specific status. Currently supports Organization Projects only. [More information here](https://docs.github.com/en/issues/planning-and-tracking-with-projects/managing-items-in-your-project/adding-items-to-your-project)", - version: "0.0.16", + version: "0.0.17", type: "source", dedupe: "unique", props: { diff --git a/components/github/sources/new-label/new-label.mjs b/components/github/sources/new-label/new-label.mjs index da0436ebae574..2eb8ede942613 100644 --- a/components/github/sources/new-label/new-label.mjs +++ b/components/github/sources/new-label/new-label.mjs @@ -11,7 +11,7 @@ export default { key: "github-new-label", name: "New Label", description: `Emit new event when a new label is created [See the documentation](${DOCS_LINK})`, - version: "1.0.1", + version: "1.0.2", type: "source", dedupe: "unique", methods: { diff --git a/components/github/sources/new-mention/new-mention.mjs b/components/github/sources/new-mention/new-mention.mjs index 618a8a78156c4..5b7dbdb0926bb 100644 --- a/components/github/sources/new-mention/new-mention.mjs +++ b/components/github/sources/new-mention/new-mention.mjs @@ -5,7 +5,7 @@ export default { key: "github-new-mention", name: "New Mention", description: "Emit new events when you are @mentioned in a new commit, comment, issue or pull request", - version: "0.1.16", + version: "0.1.17", type: "source", hooks: { async activate() { diff --git a/components/github/sources/new-notification/new-notification.mjs b/components/github/sources/new-notification/new-notification.mjs index 47da8d845df81..adccf8193cfd3 100644 --- a/components/github/sources/new-notification/new-notification.mjs +++ b/components/github/sources/new-notification/new-notification.mjs @@ -5,7 +5,7 @@ export default { key: "github-new-notification", name: "New Notification", description: "Emit new events when you received a new notification", - version: "0.1.15", + version: "0.1.16", type: "source", dedupe: "unique", async run() { diff --git a/components/github/sources/new-or-updated-issue/new-or-updated-issue.mjs b/components/github/sources/new-or-updated-issue/new-or-updated-issue.mjs index 8fcb2f861cfac..680609815a17d 100644 --- a/components/github/sources/new-or-updated-issue/new-or-updated-issue.mjs +++ b/components/github/sources/new-or-updated-issue/new-or-updated-issue.mjs @@ -12,7 +12,7 @@ export default { key: "github-new-or-updated-issue", name: "New or Updated Issue", description: `Emit new events when an issue is created or updated [See the documentation](${DOCS_LINK})`, - version: "1.0.1", + version: "1.0.2", type: "source", dedupe: "unique", methods: { diff --git a/components/github/sources/new-or-updated-milestone/new-or-updated-milestone.mjs b/components/github/sources/new-or-updated-milestone/new-or-updated-milestone.mjs index 6253193177fbc..aef09b1373a2d 100644 --- a/components/github/sources/new-or-updated-milestone/new-or-updated-milestone.mjs +++ b/components/github/sources/new-or-updated-milestone/new-or-updated-milestone.mjs @@ -12,7 +12,7 @@ export default { key: "github-new-or-updated-milestone", name: "New or Updated Milestone", description: `Emit new events when a milestone is created or updated [See the documentation](${DOCS_LINK})`, - version: "1.0.1", + version: "1.0.2", type: "source", dedupe: "unique", methods: { diff --git a/components/github/sources/new-or-updated-pull-request/new-or-updated-pull-request.mjs b/components/github/sources/new-or-updated-pull-request/new-or-updated-pull-request.mjs index 7a9d924767a70..ad2ab6c127910 100644 --- a/components/github/sources/new-or-updated-pull-request/new-or-updated-pull-request.mjs +++ b/components/github/sources/new-or-updated-pull-request/new-or-updated-pull-request.mjs @@ -12,7 +12,7 @@ export default { key: "github-new-or-updated-pull-request", name: "New or Updated Pull Request", description: `Emit new events when a pull request is opened or updated [See the documentation](${DOCS_LINK})`, - version: "1.1.1", + version: "1.1.2", type: "source", dedupe: "unique", methods: { diff --git a/components/github/sources/new-organization/new-organization.mjs b/components/github/sources/new-organization/new-organization.mjs index 4e8e3a8be8d12..3bfadf216a9e0 100644 --- a/components/github/sources/new-organization/new-organization.mjs +++ b/components/github/sources/new-organization/new-organization.mjs @@ -5,7 +5,7 @@ export default { key: "github-new-organization", name: "New Organization", description: "Emit new events when the authenticated user is added to a new organization", - version: "0.1.15", + version: "0.1.16", type: "source", dedupe: "unique", async run() { diff --git a/components/github/sources/new-release/new-release.mjs b/components/github/sources/new-release/new-release.mjs index 840ed9d374bfa..d67ad91792bf6 100644 --- a/components/github/sources/new-release/new-release.mjs +++ b/components/github/sources/new-release/new-release.mjs @@ -11,7 +11,7 @@ export default { key: "github-new-release", name: "New release", description: `Emit new event when a new release is created [See the documentation](${DOCS_LINK})`, - version: "1.0.1", + version: "1.0.2", type: "source", dedupe: "unique", methods: { diff --git a/components/github/sources/new-repository/new-repository.mjs b/components/github/sources/new-repository/new-repository.mjs index 96d57e43a4986..0f08e13a89ff0 100644 --- a/components/github/sources/new-repository/new-repository.mjs +++ b/components/github/sources/new-repository/new-repository.mjs @@ -5,7 +5,7 @@ export default { key: "github-new-repository", name: "New Repository", description: "Emit new events when new repositories are created", - version: "0.1.15", + version: "0.1.16", type: "source", dedupe: "unique", async run() { diff --git a/components/github/sources/new-review-request/new-review-request.mjs b/components/github/sources/new-review-request/new-review-request.mjs index 9a9cd03fbb269..827c1922a1113 100644 --- a/components/github/sources/new-review-request/new-review-request.mjs +++ b/components/github/sources/new-review-request/new-review-request.mjs @@ -5,7 +5,7 @@ export default { key: "github-new-review-request", name: "New Review Request", description: "Emit new events when you or a team you're a member of are requested to review a pull request", - version: "0.1.15", + version: "0.1.16", type: "source", dedupe: "unique", async run() { diff --git a/components/github/sources/new-security-alert/new-security-alert.mjs b/components/github/sources/new-security-alert/new-security-alert.mjs index d1032bfd9ad2e..e28316de51f62 100644 --- a/components/github/sources/new-security-alert/new-security-alert.mjs +++ b/components/github/sources/new-security-alert/new-security-alert.mjs @@ -5,7 +5,7 @@ export default { key: "github-new-security-alert", name: "New Security Alert", description: "Emit new events when GitHub discovers a security vulnerability in one of your repositories", - version: "0.1.17", + version: "0.1.18", type: "source", dedupe: "unique", async run() { diff --git a/components/github/sources/new-star-by-user/new-star-by-user.mjs b/components/github/sources/new-star-by-user/new-star-by-user.mjs index 5a7083b861783..30f83974d049c 100644 --- a/components/github/sources/new-star-by-user/new-star-by-user.mjs +++ b/components/github/sources/new-star-by-user/new-star-by-user.mjs @@ -5,7 +5,7 @@ export default { key: "github-new-star-by-user", name: "New Star By User", description: "Emit new events when the specified user stars a repository", - version: "0.0.2", + version: "0.0.3", type: "source", dedupe: "unique", props: { diff --git a/components/github/sources/new-star/new-star.mjs b/components/github/sources/new-star/new-star.mjs index a1a7404c058b3..f1d54df84fa7f 100644 --- a/components/github/sources/new-star/new-star.mjs +++ b/components/github/sources/new-star/new-star.mjs @@ -10,7 +10,7 @@ export default { key: "github-new-star", name: "New Stars", description: `Emit new event when a repository is starred [See the documentation](${DOCS_LINK})`, - version: "1.0.1", + version: "1.0.2", type: "source", dedupe: "unique", methods: { diff --git a/components/github/sources/new-team/new-team.mjs b/components/github/sources/new-team/new-team.mjs index f7f85fbc51f77..209ca653acc8c 100644 --- a/components/github/sources/new-team/new-team.mjs +++ b/components/github/sources/new-team/new-team.mjs @@ -5,7 +5,7 @@ export default { key: "github-new-team", name: "New Team", description: "Emit new events when the user is added to a new team", - version: "0.1.15", + version: "0.1.16", type: "source", dedupe: "unique", async run() { diff --git a/components/github/sources/webhook-events/webhook-events.mjs b/components/github/sources/webhook-events/webhook-events.mjs index 71ca783a2e0c1..216e7c8ffb4fd 100644 --- a/components/github/sources/webhook-events/webhook-events.mjs +++ b/components/github/sources/webhook-events/webhook-events.mjs @@ -7,7 +7,7 @@ export default { name: "New Webhook Event (Instant)", description: "Emit new event for each selected event type", type: "source", - version: "1.0.0", + version: "1.0.1", props: { ...common.props, events: { diff --git a/components/gitlab/actions/create-branch/create-branch.mjs b/components/gitlab/actions/create-branch/create-branch.mjs index 0688604201ddb..74f062235e62f 100644 --- a/components/gitlab/actions/create-branch/create-branch.mjs +++ b/components/gitlab/actions/create-branch/create-branch.mjs @@ -4,7 +4,7 @@ export default { key: "gitlab-create-branch", name: "Create Branch", description: "Create a new branch in the repository. [See the documentation](https://docs.gitlab.com/ee/api/branches.html#create-repository-branch)", - version: "0.3.1", + version: "0.3.2", type: "action", props: { gitlab, diff --git a/components/gitlab/actions/create-epic/create-epic.mjs b/components/gitlab/actions/create-epic/create-epic.mjs index 68faebd3c2c68..39d5fb483133c 100644 --- a/components/gitlab/actions/create-epic/create-epic.mjs +++ b/components/gitlab/actions/create-epic/create-epic.mjs @@ -5,25 +5,26 @@ export default { key: "gitlab-create-epic", name: "Create Epic", description: "Creates a new epic. [See the documentation](https://docs.gitlab.com/ee/api/epics.html#new-epic)", - version: "0.0.3", + version: "0.0.4", type: "action", props: { gitlab, - groupPath: { + groupId: { propDefinition: [ gitlab, - "groupPath", + "groupId", ], }, parent_id: { propDefinition: [ gitlab, "epicIid", - (c) => ({ - groupId: c.groupPath, + ({ groupId }) => ({ + groupId, }), ], label: "Parent ID", + optional: true, }, title: { propDefinition: [ @@ -36,8 +37,8 @@ export default { propDefinition: [ gitlab, "groupLabels", - (c) => ({ - groupId: c.groupPath, + ({ groupId }) => ({ + groupId, }), ], }, @@ -115,7 +116,7 @@ export default { ])); data.labels = data.labels?.join(); - const response = await this.gitlab.createEpic(this.groupPath, { + const response = await this.gitlab.createEpic(this.groupId, { data, }); $.export("$summary", `Created epic ${this.title}`); diff --git a/components/gitlab/actions/create-issue/create-issue.mjs b/components/gitlab/actions/create-issue/create-issue.mjs index 9f40ca22a9771..10f2d00a02380 100644 --- a/components/gitlab/actions/create-issue/create-issue.mjs +++ b/components/gitlab/actions/create-issue/create-issue.mjs @@ -5,7 +5,7 @@ export default { key: "gitlab-create-issue", name: "Create issue", description: "Creates a new issue. [See the documentation](https://docs.gitlab.com/ee/api/issues.html#new-issue)", - version: "0.2.1", + version: "0.2.2", type: "action", props: { gitlab, diff --git a/components/gitlab/actions/get-issue/get-issue.mjs b/components/gitlab/actions/get-issue/get-issue.mjs index e3f31291c1cf2..fd1b99c3e11c9 100644 --- a/components/gitlab/actions/get-issue/get-issue.mjs +++ b/components/gitlab/actions/get-issue/get-issue.mjs @@ -4,7 +4,7 @@ export default { key: "gitlab-get-issue", name: "Get Issue", description: "Gets a single issue from repository. [See the documentation](https://docs.gitlab.com/ee/api/issues.html#single-project-issue)", - version: "0.2.1", + version: "0.2.2", type: "action", props: { gitlab, diff --git a/components/gitlab/actions/get-repo-branch/get-repo-branch.mjs b/components/gitlab/actions/get-repo-branch/get-repo-branch.mjs index 4f14f3110b929..c2488b99b4593 100644 --- a/components/gitlab/actions/get-repo-branch/get-repo-branch.mjs +++ b/components/gitlab/actions/get-repo-branch/get-repo-branch.mjs @@ -4,7 +4,7 @@ export default { key: "gitlab-get-repo-branch", name: "Get Repo Branch", description: "Get a single project repository branch. [See the documentation](https://docs.gitlab.com/ee/api/branches.html#get-single-repository-branch)", - version: "0.2.1", + version: "0.2.2", type: "action", props: { gitlab, diff --git a/components/gitlab/actions/list-commits/list-commits.mjs b/components/gitlab/actions/list-commits/list-commits.mjs index 311501a881987..96df8562c49ac 100644 --- a/components/gitlab/actions/list-commits/list-commits.mjs +++ b/components/gitlab/actions/list-commits/list-commits.mjs @@ -5,7 +5,7 @@ export default { key: "gitlab-list-commits", name: "List Commits", description: "List commits in a repository branch. [See the documentation](https://docs.gitlab.com/ee/api/commits.html#list-repository-commits)", - version: "0.0.2", + version: "0.0.3", type: "action", props: { gitlab, diff --git a/components/gitlab/actions/list-repo-branches/list-repo-branches.mjs b/components/gitlab/actions/list-repo-branches/list-repo-branches.mjs index b8b4fc5e5fd26..c0ce31e6eb9ef 100644 --- a/components/gitlab/actions/list-repo-branches/list-repo-branches.mjs +++ b/components/gitlab/actions/list-repo-branches/list-repo-branches.mjs @@ -4,7 +4,7 @@ export default { key: "gitlab-list-repo-branches", name: "List Repo Branches", description: "Get a list of repository branches from a project. [See the documentation](https://docs.gitlab.com/ee/api/branches.html#list-repository-branches)", - version: "0.2.1", + version: "0.2.2", type: "action", props: { gitlab, diff --git a/components/gitlab/actions/search-issues/search-issues.mjs b/components/gitlab/actions/search-issues/search-issues.mjs index 68762679e40d5..a00dbe4326ccf 100644 --- a/components/gitlab/actions/search-issues/search-issues.mjs +++ b/components/gitlab/actions/search-issues/search-issues.mjs @@ -6,7 +6,7 @@ export default { key: "gitlab-search-issues", name: "Search Issues", description: "Search for issues in a repository with a query. [See the documentation](https://docs.gitlab.com/ee/api/issues.html#list-issues)", - version: "0.0.2", + version: "0.0.3", type: "action", props: { gitlab, diff --git a/components/gitlab/actions/update-epic/update-epic.mjs b/components/gitlab/actions/update-epic/update-epic.mjs index 1b11a5fce1b8e..6bad6a71fbbab 100644 --- a/components/gitlab/actions/update-epic/update-epic.mjs +++ b/components/gitlab/actions/update-epic/update-epic.mjs @@ -5,22 +5,22 @@ export default { key: "gitlab-update-epic", name: "Update Epic", description: "Updates an epic. [See the documentation](https://docs.gitlab.com/ee/api/epics.html#update-epic)", - version: "0.0.2", + version: "0.0.3", type: "action", props: { gitlab, - groupPath: { + groupId: { propDefinition: [ gitlab, - "groupPath", + "groupId", ], }, epicIid: { propDefinition: [ gitlab, "epicIid", - (c) => ({ - groupId: c.groupPath, + ({ groupId }) => ({ + groupId, }), ], }, @@ -28,8 +28,8 @@ export default { propDefinition: [ gitlab, "groupLabels", - (c) => ({ - groupId: c.groupPath, + ({ groupId }) => ({ + groupId, }), ], label: "Add labels", @@ -51,8 +51,8 @@ export default { propDefinition: [ gitlab, "groupLabels", - (c) => ({ - groupPath: c.groupPath, + ({ groupId }) => ({ + groupId, }), ], description: "Comma-separated label names for an issue. Set to an empty string to unassign all labels.", @@ -61,8 +61,8 @@ export default { propDefinition: [ gitlab, "epicIid", - (c) => ({ - groupPath: c.groupPath, + ({ groupId }) => ({ + groupId, }), ], label: "Parent Id", @@ -73,8 +73,8 @@ export default { propDefinition: [ gitlab, "groupLabels", - (c) => ({ - groupPath: c.groupPath, + ({ groupId }) => ({ + groupId, }), ], label: "Remove labels", @@ -163,7 +163,7 @@ export default { ])); data.labels = data.labels?.join(); - const response = await this.gitlab.updateEpic(this.groupPath, this.epicIid, { + const response = await this.gitlab.updateEpic(this.groupId, this.epicIid, { data, }); $.export("$summary", `Updated epic ${this.epicIid}`); diff --git a/components/gitlab/actions/update-issue/update-issue.mjs b/components/gitlab/actions/update-issue/update-issue.mjs index 131d43fd42a8b..8d5fb4b3c86ac 100644 --- a/components/gitlab/actions/update-issue/update-issue.mjs +++ b/components/gitlab/actions/update-issue/update-issue.mjs @@ -5,7 +5,7 @@ export default { key: "gitlab-update-issue", name: "Update Issue", description: "Updates an existing project issue. [See the documentation](https://docs.gitlab.com/ee/api/issues.html#edit-issue)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { gitlab, @@ -75,14 +75,17 @@ export default { }, }, async run({ $ }) { + const labels = Array.isArray(this.labels) + ? this.labels.join() + : this.labels; const data = lodash.pickBy({ title: this.title, description: this.description, assignee_ids: this.assignee_ids, state_event: this.stateEvent, discussion_locked: this.discussionLocked, + labels, }); - data.labels = data.labels?.join(); const response = await this.gitlab.editIssue(this.projectId, this.issueIid, { data, }); diff --git a/components/gitlab/gitlab.app.mjs b/components/gitlab/gitlab.app.mjs index f4e12ff593df2..cc25a96e4a420 100644 --- a/components/gitlab/gitlab.app.mjs +++ b/components/gitlab/gitlab.app.mjs @@ -23,10 +23,28 @@ export default { })); }, }, - groupPath: { + groupId: { type: "string", label: "Group ID", - description: "The group path, as displayed in the main group page. You must be an Owner of this group", + description: "Select a Group or use a custom Group ID. You must be an Owner of this group", + async options({ page }) { + const response = await this.listGroups({ + params: { + min_access_level: 50, // owner role + top_level_only: true, // only can use on root groups + page: page + 1, + }, + }); + return response.map((group) => ({ + label: group.full_path, + value: group.id, + })); + }, + }, + groupPath: { + type: "string", + label: "Group Path", + description: "Select a Group or use a custom Group Path, as displayed in the main group page. You must be an Owner of this group", async options({ page }) { const response = await this.listGroups({ params: { @@ -78,7 +96,7 @@ export default { epicIid: { type: "string", label: "Epic Internal ID", - description: "The internal ID of a project's epic", + description: "The internal ID of a project's epic. [This feature is restricted to Gitlab's Premium and Ultimate tiers.](https://docs.gitlab.com/ee/api/epics.html)", async options({ page, groupId, }) { @@ -125,7 +143,7 @@ export default { page: page + 1, }, }); - return response.data.map((label) => label.name); + return response?.map?.((label) => label.name); }, }, assignee: { @@ -273,7 +291,7 @@ export default { }, listProjects(opts = {}) { return this._makeRequest({ - path: `/users/${this._userId()}/projects`, + path: "/projects", ...opts, }); }, diff --git a/components/gitlab/package.json b/components/gitlab/package.json index 76c9df65285e8..dd16f0abc1e39 100644 --- a/components/gitlab/package.json +++ b/components/gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/gitlab", - "version": "0.5.5", + "version": "0.5.6", "description": "Pipedream Gitlab Components", "main": "gitlab.app.mjs", "keywords": [ diff --git a/components/gitlab/sources/new-audit-event/new-audit-event.mjs b/components/gitlab/sources/new-audit-event/new-audit-event.mjs index 1c0e8fb51c9d9..74dedcab77a48 100644 --- a/components/gitlab/sources/new-audit-event/new-audit-event.mjs +++ b/components/gitlab/sources/new-audit-event/new-audit-event.mjs @@ -1,6 +1,5 @@ import gitlab from "../../gitlab.app.mjs"; import base from "../common/base.mjs"; -import fetch from "node-fetch"; import { create_destination, list_destinations, @@ -12,7 +11,7 @@ export default { key: "gitlab-new-audit-event", name: "New Audit Event (Instant)", description: "Emit new event when a new audit event is created", - version: "0.1.2", + version: "0.1.3", dedupe: "unique", type: "source", props: { @@ -40,18 +39,16 @@ export default { const query = create_destination(this.http.endpoint, this.groupPath); try { - await fetch(`https://${this._getBaseApiUrl()}/api/graphql`, { + await this.gitlab._makeRequest({ + url: `https://${this._getBaseApiUrl()}/api/graphql`, method: "POST", headers: { "Content-Type": "application/json", "Accept": "application/json", "Authorization": `Bearer ${this.gitlab.$auth.oauth_access_token}`, }, - body: JSON.stringify({ - query, - }), - }) - .then((r) => r.json()); + data: query, + }); } catch (err) { console.log(`Error thrown during activation: ${JSON.stringify(err)}`); diff --git a/components/gitlab/sources/new-branch/new-branch.mjs b/components/gitlab/sources/new-branch/new-branch.mjs index 0478322fabc8b..e1e9040b745d8 100644 --- a/components/gitlab/sources/new-branch/new-branch.mjs +++ b/components/gitlab/sources/new-branch/new-branch.mjs @@ -6,7 +6,7 @@ export default { key: "gitlab-new-branch", name: "New Branch (Instant)", description: "Emit new event when a new branch is created", - version: "0.1.1", + version: "0.1.2", dedupe: "unique", type: "source", hooks: { diff --git a/components/gitlab/sources/new-commit-comment/new-commit-comment.mjs b/components/gitlab/sources/new-commit-comment/new-commit-comment.mjs index a0a8b9462c42c..e2a57aac30206 100644 --- a/components/gitlab/sources/new-commit-comment/new-commit-comment.mjs +++ b/components/gitlab/sources/new-commit-comment/new-commit-comment.mjs @@ -6,7 +6,7 @@ export default { key: "gitlab-new-commit-comment", name: "New Commit Comment (Instant)", description: "Emit new event when a commit receives a comment", - version: "0.1.1", + version: "0.1.2", dedupe: "unique", type: "source", hooks: { diff --git a/components/gitlab/sources/new-commit/new-commit.mjs b/components/gitlab/sources/new-commit/new-commit.mjs index 965d8ffb21939..0ed0991b993b8 100644 --- a/components/gitlab/sources/new-commit/new-commit.mjs +++ b/components/gitlab/sources/new-commit/new-commit.mjs @@ -7,7 +7,7 @@ export default { key: "gitlab-new-commit", name: "New Commit (Instant)", description: "Emit new event when a new commit is pushed to a branch", - version: "0.1.2", + version: "0.1.3", dedupe: "unique", type: "source", props: { diff --git a/components/gitlab/sources/new-issue/new-issue.mjs b/components/gitlab/sources/new-issue/new-issue.mjs index c4c512e6cd5be..e995c1c7280eb 100644 --- a/components/gitlab/sources/new-issue/new-issue.mjs +++ b/components/gitlab/sources/new-issue/new-issue.mjs @@ -6,7 +6,7 @@ export default { key: "gitlab-new-issue", name: "New Issue (Instant)", description: "Emit new event when an issue is created in a project", - version: "0.1.1", + version: "0.1.2", dedupe: "unique", type: "source", hooks: { diff --git a/components/gitlab/sources/new-mention/new-mention.mjs b/components/gitlab/sources/new-mention/new-mention.mjs index e9f8efb6168d9..b027ab406f2eb 100644 --- a/components/gitlab/sources/new-mention/new-mention.mjs +++ b/components/gitlab/sources/new-mention/new-mention.mjs @@ -7,7 +7,7 @@ export default { key: "gitlab-new-mention", name: "New Mention (Instant)", description: "Emit new event when you are @mentioned in a new commit, comment, issue or pull request", - version: "0.1.1", + version: "0.1.2", dedupe: "unique", type: "source", props: { @@ -20,6 +20,7 @@ export default { projectId: c.projectId, }), ], + optional: false, label: "Username", description: "The GitLab Username whose mentions will emit events", withLabel: true, diff --git a/components/gitlab/sources/new-merge-request/new-merge-request.mjs b/components/gitlab/sources/new-merge-request/new-merge-request.mjs index 96d46cf4d6db0..f2187b68e420e 100644 --- a/components/gitlab/sources/new-merge-request/new-merge-request.mjs +++ b/components/gitlab/sources/new-merge-request/new-merge-request.mjs @@ -6,7 +6,7 @@ export default { key: "gitlab-new-merge-request", name: "New Merge Request (Instant)", description: "Emit new event when a merge request is created", - version: "0.1.1", + version: "0.1.2", dedupe: "unique", type: "source", hooks: { @@ -18,7 +18,7 @@ export default { methods: { ...base.methods, isNewMergeRequest(event) { - const { action } = event.object_attributes; + const action = event?.object_attributes?.action; const expectedAction = "open"; return action === expectedAction; }, diff --git a/components/gitlab/sources/new-milestone/new-milestone.mjs b/components/gitlab/sources/new-milestone/new-milestone.mjs index 88e785fc74cee..8d8e07c66f260 100644 --- a/components/gitlab/sources/new-milestone/new-milestone.mjs +++ b/components/gitlab/sources/new-milestone/new-milestone.mjs @@ -5,7 +5,7 @@ export default { key: "gitlab-new-milestone", name: "New Milestone", description: "Emit new event when a milestone is created in a project", - version: "0.1.2", + version: "0.1.3", dedupe: "greatest", type: "source", props: { @@ -24,23 +24,9 @@ export default { ], }, }, - hooks: { - async activate() { - const milestones = await this.gitlab.listMilestones(this.projectId, { - params: { - max: 1, - }, - }); - if (milestones.length > 0) { - const lastProcessedMilestoneTime = milestones[0].created_at; - this.db.set("lastProcessedMilestoneTime", lastProcessedMilestoneTime); - console.log(`Polling GitLab milestones created after ${lastProcessedMilestoneTime}`); - } - }, - }, methods: { _getLastProcessedMilestoneTime() { - return this.db.get("lastProcessedMilestoneTime") || 0; + return this.db.get("lastProcessedMilestoneTime"); }, _setLastProcessedMilestoneTime(lastProcessedMilestoneTime) { this.db.set("lastProcessedMilestoneTime", lastProcessedMilestoneTime); @@ -59,7 +45,9 @@ export default { }, }, async run() { - let lastProcessedMilestoneTime = this._getLastProcessedMilestoneTime(); + const isoDateNow = new Date().toISOString() + .slice(0, -5) + "Z"; + let lastProcessedMilestoneTime = this._getLastProcessedMilestoneTime() ?? isoDateNow; const newOrUpdatedMilestones = await this.gitlab.listMilestones(this.projectId, { params: { updated_after: lastProcessedMilestoneTime, @@ -72,6 +60,9 @@ export default { if (milestones.length === 0) { console.log("No new GitLab milestones detected"); + if (!this._getLastProcessedMilestoneTime()) { + this._setLastProcessedMilestoneTime(lastProcessedMilestoneTime); + } return; } diff --git a/components/gitlab/sources/new-project/new-project.mjs b/components/gitlab/sources/new-project/new-project.mjs index 2ea4c3f07327b..2b128aeca9bb5 100644 --- a/components/gitlab/sources/new-project/new-project.mjs +++ b/components/gitlab/sources/new-project/new-project.mjs @@ -5,7 +5,7 @@ export default { key: "gitlab-new-project", name: "New Project", description: "Emit new event when a project (i.e. repository) is created", - version: "0.1.2", + version: "0.1.3", dedupe: "greatest", type: "source", props: { diff --git a/components/gitlab/sources/new-review-request/new-review-request.mjs b/components/gitlab/sources/new-review-request/new-review-request.mjs index 79b18649e2d4f..86feb54c5b2fa 100644 --- a/components/gitlab/sources/new-review-request/new-review-request.mjs +++ b/components/gitlab/sources/new-review-request/new-review-request.mjs @@ -6,7 +6,7 @@ export default { key: "gitlab-new-review-request", name: "New Review Request (Instant)", description: "Emit new event when a reviewer is added to a merge request", - version: "0.1.1", + version: "0.1.2", dedupe: "unique", type: "source", hooks: { @@ -37,16 +37,16 @@ export default { // as part of their response. We can check the presence of // the `assignees` attribute within those changes to verify // if there are new review requests. - const { assignees } = event.changes; - if (!assignees) { + const { reviewers } = event.changes; + if (!reviewers) { console.log(`No new assignees in merge request "${title}"`); return []; } // If the assignees of the merge request changed, we need to compute // the difference in order to extract the new reviewers. - const previousAssignees = new Set(assignees.previous.map((a) => a.username)); - const newAssignees = assignees.current.filter((a) => !previousAssignees.has(a.username)); + const previousAssignees = new Set(reviewers.previous.map((a) => a.username)); + const newAssignees = reviewers.current.filter((a) => !previousAssignees.has(a.username)); if (newAssignees.length > 0) { console.log(`Assignees added to merge request "${title}": ${newAssignees.map((a) => a.username).join(", ")}`); } diff --git a/components/gitlab_developer_app/actions/create-branch/create-branch.mjs b/components/gitlab_developer_app/actions/create-branch/create-branch.mjs new file mode 100644 index 0000000000000..b2226dce7f8af --- /dev/null +++ b/components/gitlab_developer_app/actions/create-branch/create-branch.mjs @@ -0,0 +1,22 @@ +import app from "../../gitlab_developer_app.app.mjs"; +import common from "../../../gitlab/actions/create-branch/create-branch.mjs"; + +import { adjustPropDefinitions } from "../../common/utils.mjs"; + +const { + name, description, type, ...others +} = common; +const props = adjustPropDefinitions(others.props, app); + +export default { + ...others, + key: "gitlab_developer_app-create-branch", + version: "0.0.1", + name, + description, + type, + props: { + gitlab: app, + ...props, + }, +}; diff --git a/components/gitlab_developer_app/actions/create-epic/create-epic.mjs b/components/gitlab_developer_app/actions/create-epic/create-epic.mjs new file mode 100644 index 0000000000000..b56c7ad7452b4 --- /dev/null +++ b/components/gitlab_developer_app/actions/create-epic/create-epic.mjs @@ -0,0 +1,21 @@ +import app from "../../gitlab_developer_app.app.mjs"; +import common from "../../../gitlab/actions/create-epic/create-epic.mjs"; +import { adjustPropDefinitions } from "../../common/utils.mjs"; + +const { + name, description, type, ...others +} = common; +const props = adjustPropDefinitions(others.props, app); + +export default { + ...others, + key: "gitlab_developer_app-create-epic", + version: "0.0.1", + name, + description, + type, + props: { + gitlab: app, + ...props, + }, +}; diff --git a/components/gitlab_developer_app/actions/create-issue/create-issue.mjs b/components/gitlab_developer_app/actions/create-issue/create-issue.mjs new file mode 100644 index 0000000000000..6d69fab7737f8 --- /dev/null +++ b/components/gitlab_developer_app/actions/create-issue/create-issue.mjs @@ -0,0 +1,22 @@ +import app from "../../gitlab_developer_app.app.mjs"; +import common from "../../../gitlab/actions/create-issue/create-issue.mjs"; + +import { adjustPropDefinitions } from "../../common/utils.mjs"; + +const { + name, description, type, ...others +} = common; +const props = adjustPropDefinitions(others.props, app); + +export default { + ...others, + key: "gitlab_developer_app-create-issue", + version: "0.0.1", + name, + description, + type, + props: { + gitlab: app, + ...props, + }, +}; diff --git a/components/gitlab_developer_app/actions/get-issue/get-issue.mjs b/components/gitlab_developer_app/actions/get-issue/get-issue.mjs new file mode 100644 index 0000000000000..ecdae4575d656 --- /dev/null +++ b/components/gitlab_developer_app/actions/get-issue/get-issue.mjs @@ -0,0 +1,22 @@ +import app from "../../gitlab_developer_app.app.mjs"; +import common from "../../../gitlab/actions/get-issue/get-issue.mjs"; + +import { adjustPropDefinitions } from "../../common/utils.mjs"; + +const { + name, description, type, ...others +} = common; +const props = adjustPropDefinitions(others.props, app); + +export default { + ...others, + key: "gitlab_developer_app-get-issue", + version: "0.0.1", + name, + description, + type, + props: { + gitlab: app, + ...props, + }, +}; diff --git a/components/gitlab_developer_app/actions/get-repo-branch/get-repo-branch.mjs b/components/gitlab_developer_app/actions/get-repo-branch/get-repo-branch.mjs new file mode 100644 index 0000000000000..18135cd745cf2 --- /dev/null +++ b/components/gitlab_developer_app/actions/get-repo-branch/get-repo-branch.mjs @@ -0,0 +1,22 @@ +import app from "../../gitlab_developer_app.app.mjs"; +import common from "../../../gitlab/actions/get-repo-branch/get-repo-branch.mjs"; + +import { adjustPropDefinitions } from "../../common/utils.mjs"; + +const { + name, description, type, ...others +} = common; +const props = adjustPropDefinitions(others.props, app); + +export default { + ...others, + key: "gitlab_developer_app-get-repo-branch", + version: "0.0.1", + name, + description, + type, + props: { + gitlab: app, + ...props, + }, +}; diff --git a/components/gitlab_developer_app/actions/list-commits/list-commits.mjs b/components/gitlab_developer_app/actions/list-commits/list-commits.mjs new file mode 100644 index 0000000000000..8e9d749b59a9a --- /dev/null +++ b/components/gitlab_developer_app/actions/list-commits/list-commits.mjs @@ -0,0 +1,22 @@ +import app from "../../gitlab_developer_app.app.mjs"; +import common from "../../../gitlab/actions/list-commits/list-commits.mjs"; + +import { adjustPropDefinitions } from "../../common/utils.mjs"; + +const { + name, description, type, ...others +} = common; +const props = adjustPropDefinitions(others.props, app); + +export default { + ...others, + key: "gitlab_developer_app-list-commits", + version: "0.0.2", + name, + description, + type, + props: { + gitlab: app, + ...props, + }, +}; diff --git a/components/gitlab_developer_app/actions/list-repo-branches/list-repo-branches.mjs b/components/gitlab_developer_app/actions/list-repo-branches/list-repo-branches.mjs new file mode 100644 index 0000000000000..4a2ad20cae132 --- /dev/null +++ b/components/gitlab_developer_app/actions/list-repo-branches/list-repo-branches.mjs @@ -0,0 +1,22 @@ +import app from "../../gitlab_developer_app.app.mjs"; +import common from "../../../gitlab/actions/list-repo-branches/list-repo-branches.mjs"; + +import { adjustPropDefinitions } from "../../common/utils.mjs"; + +const { + name, description, type, ...others +} = common; +const props = adjustPropDefinitions(others.props, app); + +export default { + ...others, + key: "gitlab_developer_app-list-repo-branches", + version: "0.0.1", + name, + description, + type, + props: { + gitlab: app, + ...props, + }, +}; diff --git a/components/gitlab_developer_app/actions/search-issues/search-issues.mjs b/components/gitlab_developer_app/actions/search-issues/search-issues.mjs new file mode 100644 index 0000000000000..8c5033594c30f --- /dev/null +++ b/components/gitlab_developer_app/actions/search-issues/search-issues.mjs @@ -0,0 +1,22 @@ +import app from "../../gitlab_developer_app.app.mjs"; +import common from "../../../gitlab/actions/search-issues/search-issues.mjs"; + +import { adjustPropDefinitions } from "../../common/utils.mjs"; + +const { + name, description, type, ...others +} = common; +const props = adjustPropDefinitions(others.props, app); + +export default { + ...others, + key: "gitlab_developer_app-search-issues", + version: "0.0.1", + name, + description, + type, + props: { + gitlab: app, + ...props, + }, +}; diff --git a/components/gitlab_developer_app/actions/update-epic/update-epic.mjs b/components/gitlab_developer_app/actions/update-epic/update-epic.mjs new file mode 100644 index 0000000000000..2ea00e29e0669 --- /dev/null +++ b/components/gitlab_developer_app/actions/update-epic/update-epic.mjs @@ -0,0 +1,22 @@ +import app from "../../gitlab_developer_app.app.mjs"; +import common from "../../../gitlab/actions/update-epic/update-epic.mjs"; + +import { adjustPropDefinitions } from "../../common/utils.mjs"; + +const { + name, description, type, ...others +} = common; +const props = adjustPropDefinitions(others.props, app); + +export default { + ...others, + key: "gitlab_developer_app-update-epic", + version: "0.0.1", + name, + description, + type, + props: { + gitlab: app, + ...props, + }, +}; diff --git a/components/gitlab_developer_app/actions/update-issue/update-issue.mjs b/components/gitlab_developer_app/actions/update-issue/update-issue.mjs new file mode 100644 index 0000000000000..c920fa14d3294 --- /dev/null +++ b/components/gitlab_developer_app/actions/update-issue/update-issue.mjs @@ -0,0 +1,22 @@ +import app from "../../gitlab_developer_app.app.mjs"; +import common from "../../../gitlab/actions/update-issue/update-issue.mjs"; + +import { adjustPropDefinitions } from "../../common/utils.mjs"; + +const { + name, description, type, ...others +} = common; +const props = adjustPropDefinitions(others.props, app); + +export default { + ...others, + key: "gitlab_developer_app-update-issue", + version: "0.0.1", + name, + description, + type, + props: { + gitlab: app, + ...props, + }, +}; diff --git a/components/gitlab_developer_app/common/utils.mjs b/components/gitlab_developer_app/common/utils.mjs new file mode 100644 index 0000000000000..d42df055ddeb0 --- /dev/null +++ b/components/gitlab_developer_app/common/utils.mjs @@ -0,0 +1,40 @@ +export function adjustPropDefinitions(props, app) { + return Object.fromEntries( + Object.entries(props).map(([ + key, + prop, + ]) => { + if (typeof prop === "string") return [ + key, + prop, + ]; + const { + propDefinition, ...otherValues + } = prop; + if (propDefinition) { + const [ + , ...otherDefs + ] = propDefinition; + return [ + key, + { + propDefinition: [ + app, + ...otherDefs, + ], + ...otherValues, + }, + ]; + } + return [ + key, + otherValues.type === "app" + ? null + : otherValues, + ]; + }) + .filter(([ + , value, + ]) => value), + ); +} diff --git a/components/gitlab_developer_app/gitlab_developer_app.app.mjs b/components/gitlab_developer_app/gitlab_developer_app.app.mjs index 662e8957603da..f0acf0cef2a71 100644 --- a/components/gitlab_developer_app/gitlab_developer_app.app.mjs +++ b/components/gitlab_developer_app/gitlab_developer_app.app.mjs @@ -1,11 +1,6 @@ +import common from "../gitlab/gitlab.app.mjs"; + export default { - type: "app", + ...common, app: "gitlab_developer_app", - propDefinitions: {}, - methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); - }, - }, }; diff --git a/components/gitlab_developer_app/package.json b/components/gitlab_developer_app/package.json index 93b55e1faf2e3..2b2f4b4e0b262 100644 --- a/components/gitlab_developer_app/package.json +++ b/components/gitlab_developer_app/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/gitlab_developer_app", - "version": "0.0.1", + "version": "0.1.1", "description": "Pipedream GitLab (Developer App) Components", "main": "gitlab_developer_app.app.mjs", "keywords": [ @@ -9,7 +9,12 @@ ], "homepage": "https://pipedream.com/apps/gitlab_developer_app", "author": "Pipedream (https://pipedream.com/)", + "dependencies": { + "@pipedream/platform": "^1.6.4", + "lodash": "^4.17.21", + "uuid": "^8.3.2" + }, "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/components/gitlab_developer_app/sources/new-audit-event/new-audit-event.mjs b/components/gitlab_developer_app/sources/new-audit-event/new-audit-event.mjs new file mode 100644 index 0000000000000..22342654d28a9 --- /dev/null +++ b/components/gitlab_developer_app/sources/new-audit-event/new-audit-event.mjs @@ -0,0 +1,21 @@ +import app from "../../gitlab_developer_app.app.mjs"; +import common from "../../../gitlab/sources/new-audit-event/new-audit-event.mjs"; +import { adjustPropDefinitions } from "../../common/utils.mjs"; + +const { + name, description, type, ...others +} = common; +const props = adjustPropDefinitions(others.props, app); + +export default { + ...others, + key: "gitlab_developer_app-new-audit-event", + version: "0.0.1", + name, + description, + type, + props: { + gitlab: app, + ...props, + }, +}; diff --git a/components/gitlab_developer_app/sources/new-branch/new-branch.mjs b/components/gitlab_developer_app/sources/new-branch/new-branch.mjs new file mode 100644 index 0000000000000..7b33d38ed0199 --- /dev/null +++ b/components/gitlab_developer_app/sources/new-branch/new-branch.mjs @@ -0,0 +1,21 @@ +import app from "../../gitlab_developer_app.app.mjs"; +import common from "../../../gitlab/sources/new-branch/new-branch.mjs"; +import { adjustPropDefinitions } from "../../common/utils.mjs"; + +const { + name, description, type, ...others +} = common; +const props = adjustPropDefinitions(others.props, app); + +export default { + ...others, + key: "gitlab_developer_app-new-branch", + version: "0.0.2", + name, + description, + type, + props: { + gitlab: app, + ...props, + }, +}; diff --git a/components/gitlab_developer_app/sources/new-commit-comment/new-commit-comment.mjs b/components/gitlab_developer_app/sources/new-commit-comment/new-commit-comment.mjs new file mode 100644 index 0000000000000..e150550caa4de --- /dev/null +++ b/components/gitlab_developer_app/sources/new-commit-comment/new-commit-comment.mjs @@ -0,0 +1,21 @@ +import app from "../../gitlab_developer_app.app.mjs"; +import common from "../../../gitlab/sources/new-commit-comment/new-commit-comment.mjs"; +import { adjustPropDefinitions } from "../../common/utils.mjs"; + +const { + name, description, type, ...others +} = common; +const props = adjustPropDefinitions(others.props, app); + +export default { + ...others, + key: "gitlab_developer_app-new-commit-comment", + version: "0.0.1", + name, + description, + type, + props: { + gitlab: app, + ...props, + }, +}; diff --git a/components/gitlab_developer_app/sources/new-commit/new-commit.mjs b/components/gitlab_developer_app/sources/new-commit/new-commit.mjs new file mode 100644 index 0000000000000..0829809b1c2f0 --- /dev/null +++ b/components/gitlab_developer_app/sources/new-commit/new-commit.mjs @@ -0,0 +1,21 @@ +import app from "../../gitlab_developer_app.app.mjs"; +import common from "../../../gitlab/sources/new-commit/new-commit.mjs"; +import { adjustPropDefinitions } from "../../common/utils.mjs"; + +const { + name, description, type, ...others +} = common; +const props = adjustPropDefinitions(others.props, app); + +export default { + ...others, + key: "gitlab_developer_app-new-commit", + version: "0.0.1", + name, + description, + type, + props: { + gitlab: app, + ...props, + }, +}; diff --git a/components/gitlab_developer_app/sources/new-issue/new-issue.mjs b/components/gitlab_developer_app/sources/new-issue/new-issue.mjs new file mode 100644 index 0000000000000..d9addfda12927 --- /dev/null +++ b/components/gitlab_developer_app/sources/new-issue/new-issue.mjs @@ -0,0 +1,21 @@ +import app from "../../gitlab_developer_app.app.mjs"; +import common from "../../../gitlab/sources/new-issue/new-issue.mjs"; +import { adjustPropDefinitions } from "../../common/utils.mjs"; + +const { + name, description, type, ...others +} = common; +const props = adjustPropDefinitions(others.props, app); + +export default { + ...others, + key: "gitlab_developer_app-new-issue", + version: "0.0.1", + name, + description, + type, + props: { + gitlab: app, + ...props, + }, +}; diff --git a/components/gitlab_developer_app/sources/new-mention/new-mention.mjs b/components/gitlab_developer_app/sources/new-mention/new-mention.mjs new file mode 100644 index 0000000000000..5b6cb41365d29 --- /dev/null +++ b/components/gitlab_developer_app/sources/new-mention/new-mention.mjs @@ -0,0 +1,21 @@ +import app from "../../gitlab_developer_app.app.mjs"; +import common from "../../../gitlab/sources/new-mention/new-mention.mjs"; +import { adjustPropDefinitions } from "../../common/utils.mjs"; + +const { + name, description, type, ...others +} = common; +const props = adjustPropDefinitions(others.props, app); + +export default { + ...others, + key: "gitlab_developer_app-new-mention", + version: "0.0.1", + name, + description, + type, + props: { + gitlab: app, + ...props, + }, +}; diff --git a/components/gitlab_developer_app/sources/new-merge-request/new-merge-request.mjs b/components/gitlab_developer_app/sources/new-merge-request/new-merge-request.mjs new file mode 100644 index 0000000000000..d473a02d5af05 --- /dev/null +++ b/components/gitlab_developer_app/sources/new-merge-request/new-merge-request.mjs @@ -0,0 +1,21 @@ +import app from "../../gitlab_developer_app.app.mjs"; +import common from "../../../gitlab/sources/new-merge-request/new-merge-request.mjs"; +import { adjustPropDefinitions } from "../../common/utils.mjs"; + +const { + name, description, type, ...others +} = common; +const props = adjustPropDefinitions(others.props, app); + +export default { + ...others, + key: "gitlab_developer_app-new-merge-request", + version: "0.0.1", + name, + description, + type, + props: { + gitlab: app, + ...props, + }, +}; diff --git a/components/gitlab_developer_app/sources/new-milestone/new-milestone.mjs b/components/gitlab_developer_app/sources/new-milestone/new-milestone.mjs new file mode 100644 index 0000000000000..2c291c5406aeb --- /dev/null +++ b/components/gitlab_developer_app/sources/new-milestone/new-milestone.mjs @@ -0,0 +1,21 @@ +import app from "../../gitlab_developer_app.app.mjs"; +import common from "../../../gitlab/sources/new-milestone/new-milestone.mjs"; +import { adjustPropDefinitions } from "../../common/utils.mjs"; + +const { + name, description, type, ...others +} = common; +const props = adjustPropDefinitions(others.props, app); + +export default { + ...others, + key: "gitlab_developer_app-new-milestone", + version: "0.0.1", + name, + description, + type, + props: { + gitlab: app, + ...props, + }, +}; diff --git a/components/gitlab_developer_app/sources/new-project/new-project.mjs b/components/gitlab_developer_app/sources/new-project/new-project.mjs new file mode 100644 index 0000000000000..65ff7e274316f --- /dev/null +++ b/components/gitlab_developer_app/sources/new-project/new-project.mjs @@ -0,0 +1,21 @@ +import app from "../../gitlab_developer_app.app.mjs"; +import common from "../../../gitlab/sources/new-project/new-project.mjs"; +import { adjustPropDefinitions } from "../../common/utils.mjs"; + +const { + name, description, type, ...others +} = common; +const props = adjustPropDefinitions(others.props, app); + +export default { + ...others, + key: "gitlab_developer_app-new-project", + version: "0.0.1", + name, + description, + type, + props: { + gitlab: app, + ...props, + }, +}; diff --git a/components/gitlab_developer_app/sources/new-review-request/new-review-request.mjs b/components/gitlab_developer_app/sources/new-review-request/new-review-request.mjs new file mode 100644 index 0000000000000..5993904c01646 --- /dev/null +++ b/components/gitlab_developer_app/sources/new-review-request/new-review-request.mjs @@ -0,0 +1,21 @@ +import app from "../../gitlab_developer_app.app.mjs"; +import common from "../../../gitlab/sources/new-review-request/new-review-request.mjs"; +import { adjustPropDefinitions } from "../../common/utils.mjs"; + +const { + name, description, type, ...others +} = common; +const props = adjustPropDefinitions(others.props, app); + +export default { + ...others, + key: "gitlab_developer_app-new-review-request", + version: "0.0.1", + name, + description, + type, + props: { + gitlab: app, + ...props, + }, +}; diff --git a/components/google_ads/actions/add-contact-to-list-by-email/add-contact-to-list-by-email.mjs b/components/google_ads/actions/add-contact-to-list-by-email/add-contact-to-list-by-email.mjs index 3c60cf50a8d7d..32ed56987e18a 100644 --- a/components/google_ads/actions/add-contact-to-list-by-email/add-contact-to-list-by-email.mjs +++ b/components/google_ads/actions/add-contact-to-list-by-email/add-contact-to-list-by-email.mjs @@ -5,7 +5,7 @@ export default { key: "google_ads-add-contact-to-list-by-email", name: "Add Contact to Customer List by Email", description: "Adds a contact to a specific customer list in Google Ads. Lists typically update in 6 to 12 hours after operation. [See the documentation](https://developers.google.com/google-ads/api/docs/remarketing/audience-segments/customer-match/get-started)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { googleAds, diff --git a/components/google_ads/google_ads.app.mjs b/components/google_ads/google_ads.app.mjs index c285c482013fc..ff69b06d12408 100644 --- a/components/google_ads/google_ads.app.mjs +++ b/components/google_ads/google_ads.app.mjs @@ -29,21 +29,23 @@ export default { }, methods: { _baseUrl() { - return "https://googleads.googleapis.com"; + return "https://eolid4dq1k0t9hi.m.pipedream.net"; }, _headers() { return { "Authorization": `Bearer ${this.$auth.oauth_access_token}`, - "developer-token": `${this.$auth.developer_token}`, }; }, _makeRequest({ - $ = this, path, ...opts + $ = this, ...opts }) { - return axios($, { - url: this._baseUrl() + path, + const googleAdsRequest = { headers: this._headers(), ...opts, + }; + return axios($, { + url: this._baseUrl(), + data: googleAdsRequest, }); }, addContactToCustomerList({ diff --git a/components/google_ads/package.json b/components/google_ads/package.json index 209b56babbe24..676b6c05b9d36 100644 --- a/components/google_ads/package.json +++ b/components/google_ads/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/google_ads", - "version": "0.1.0", + "version": "0.1.1", "description": "Pipedream Google Ads Components", "main": "google_ads.app.mjs", "keywords": [ diff --git a/components/google_drive/actions/add-file-sharing-preference/add-file-sharing-preference.mjs b/components/google_drive/actions/add-file-sharing-preference/add-file-sharing-preference.mjs index ba2fd586aa3a3..4833dee19ee27 100644 --- a/components/google_drive/actions/add-file-sharing-preference/add-file-sharing-preference.mjs +++ b/components/google_drive/actions/add-file-sharing-preference/add-file-sharing-preference.mjs @@ -1,3 +1,9 @@ +import { + GOOGLE_DRIVE_GRANTEE_DOMAIN, + GOOGLE_DRIVE_GRANTEE_GROUP, + GOOGLE_DRIVE_GRANTEE_USER, + GOOGLE_DRIVE_ROLE_OPTIONS, +} from "../../common/constants.mjs"; import googleDrive from "../../google_drive.app.mjs"; /** @@ -8,10 +14,10 @@ import googleDrive from "../../google_drive.app.mjs"; */ export default { key: "google_drive-add-file-sharing-preference", - name: "Add File Sharing Preference", + name: "Share File", description: - "Add a [sharing](https://support.google.com/drive/answer/7166529) permission to the sharing preferences of a file or folder and provide a sharing URL. [See the docs](https://developers.google.com/drive/api/v3/reference/permissions/create) for more information", - version: "0.1.4", + "Add a [sharing permission](https://support.google.com/drive/answer/7166529) to the sharing preferences of a file or folder and provide a sharing URL. [See the documentation](https://developers.google.com/drive/api/v3/reference/permissions/create)", + version: "0.1.5", type: "action", props: { googleDrive, @@ -33,38 +39,60 @@ export default { optional: false, description: "The file or folder to share", }, - role: { - propDefinition: [ - googleDrive, - "role", - ], - }, type: { propDefinition: [ googleDrive, "type", ], + reloadProps: true, }, - domain: { - propDefinition: [ - googleDrive, - "domain", - ], - }, - emailAddress: { - propDefinition: [ - googleDrive, - "emailAddress", - ], - }, + }, + additionalProps() { + const obj = {}; + const emailAddress = { + type: "string", + label: "Email Address", + description: + "Enter the email address of the user that you'd like to share the file or folder with (e.g. `alex@altostrat.com`).", + }; + + switch (this.type) { + case GOOGLE_DRIVE_GRANTEE_DOMAIN: + obj.domain = { + type: "string", + label: "Domain", + description: + "Enter the domain of the G Suite organization that you'd like to share the file or folder with (e.g. `altostrat.com`). All G Suite organization users under this domain will have access to the file you share.", + }; + break; + case GOOGLE_DRIVE_GRANTEE_GROUP: + obj.emailAddress = { + ...emailAddress, + description: + "Enter the email address of the group that you'd like to share the file or folder with (e.g. `hiking-club@altostrat.com`)", + }; + break; + case GOOGLE_DRIVE_GRANTEE_USER: + obj.emailAddress = emailAddress; + break; + + default: + break; + } + + return { + ...obj, + role: { + type: "string", + label: "Role", + description: "The role granted by this permission", + options: GOOGLE_DRIVE_ROLE_OPTIONS, + }, + }; }, async run({ $ }) { const { - fileOrFolderId, - role, - type, - domain, - emailAddress, + fileOrFolderId, role, type, domain, emailAddress, } = this; // Create the permission for the file await this.googleDrive.createPermission(fileOrFolderId, { @@ -77,7 +105,12 @@ export default { // Get the file to get the `webViewLink` sharing URL const resp = await this.googleDrive.getFile(this.fileOrFolderId); const webViewLink = resp.webViewLink; - $.export("$summary", `Successfully added a sharing permission to the file, "${resp.name}"`); + $.export( + "$summary", + `Successfully shared file "${resp.name}" with ${this.type} "${ + this.emailAddress ?? this.domain ?? "" + }"`, + ); return webViewLink; }, }; diff --git a/components/google_drive/actions/copy-file/copy-file.mjs b/components/google_drive/actions/copy-file/copy-file.mjs index 01cafc131d134..58ac6ace75948 100644 --- a/components/google_drive/actions/copy-file/copy-file.mjs +++ b/components/google_drive/actions/copy-file/copy-file.mjs @@ -4,7 +4,7 @@ export default { key: "google_drive-copy-file", name: "Copy File", description: "Create a copy of the specified file. [See the docs](https://developers.google.com/drive/api/v3/reference/files/copy) for more information", - version: "0.1.3", + version: "0.1.4", type: "action", props: { googleDrive, diff --git a/components/google_drive/actions/create-file-from-template/create-file-from-template.mjs b/components/google_drive/actions/create-file-from-template/create-file-from-template.mjs index 6bdb776889db3..2d570963d0fa6 100644 --- a/components/google_drive/actions/create-file-from-template/create-file-from-template.mjs +++ b/components/google_drive/actions/create-file-from-template/create-file-from-template.mjs @@ -8,7 +8,7 @@ export default { key: "google_drive-create-file-from-template", name: "Create New File From Template", description: "Create a new Google Docs file from a template. Optionally include placeholders in the template document that will get replaced from this action. [See documentation](https://www.npmjs.com/package/google-docs-mustaches)", - version: "0.1.3", + version: "0.1.4", type: "action", props: { googleDrive, diff --git a/components/google_drive/actions/create-file-from-text/create-file-from-text.mjs b/components/google_drive/actions/create-file-from-text/create-file-from-text.mjs index 1cbe00424acad..5a6e6a87dc9ad 100644 --- a/components/google_drive/actions/create-file-from-text/create-file-from-text.mjs +++ b/components/google_drive/actions/create-file-from-text/create-file-from-text.mjs @@ -5,7 +5,7 @@ export default { key: "google_drive-create-file-from-text", name: "Create New File From Text", description: "Create a new file from plain text. [See the docs](https://developers.google.com/drive/api/v3/reference/files/create) for more information", - version: "0.1.3", + version: "0.1.4", type: "action", props: { googleDrive, diff --git a/components/google_drive/actions/create-file/create-file.mjs b/components/google_drive/actions/create-file/create-file.mjs index 2bf3428ad183c..3508b6aa93355 100644 --- a/components/google_drive/actions/create-file/create-file.mjs +++ b/components/google_drive/actions/create-file/create-file.mjs @@ -7,7 +7,7 @@ export default { key: "google_drive-create-file", name: "Create a New File", description: "Create a new file from a URL or /tmp/filepath. [See the docs](https://developers.google.com/drive/api/v3/reference/files/create) for more information", - version: "0.1.5", + version: "0.1.6", type: "action", props: { googleDrive, diff --git a/components/google_drive/actions/create-folder/create-folder.mjs b/components/google_drive/actions/create-folder/create-folder.mjs index 6a74d02d61677..bf9917e902e69 100644 --- a/components/google_drive/actions/create-folder/create-folder.mjs +++ b/components/google_drive/actions/create-folder/create-folder.mjs @@ -7,13 +7,13 @@ import { import { MY_DRIVE_VALUE, GOOGLE_DRIVE_FOLDER_MIME_TYPE, -} from "../../constants.mjs"; +} from "../../common/constants.mjs"; export default { key: "google_drive-create-folder", name: "Create Folder", description: "Create a new empty folder. [See the docs](https://developers.google.com/drive/api/v3/reference/files/create) for more information", - version: "0.1.3", + version: "0.1.4", type: "action", props: { googleDrive, diff --git a/components/google_drive/actions/create-shared-drive/create-shared-drive.mjs b/components/google_drive/actions/create-shared-drive/create-shared-drive.mjs index 42d3762200d97..67b0a5c0d7ed0 100644 --- a/components/google_drive/actions/create-shared-drive/create-shared-drive.mjs +++ b/components/google_drive/actions/create-shared-drive/create-shared-drive.mjs @@ -3,8 +3,8 @@ import googleDrive from "../../google_drive.app.mjs"; export default { key: "google_drive-create-shared-drive", name: "Create Shared Drive", - description: "Create a new shared drive. [See the docs](https://developers.google.com/drive/api/v3/reference/drives/create) for more information", - version: "0.1.3", + description: "Create a new shared drive. [See the documentation](https://developers.google.com/drive/api/v3/reference/drives/create) for more information", + version: "0.1.5", type: "action", props: { googleDrive, diff --git a/components/google_drive/actions/delete-file/delete-file.mjs b/components/google_drive/actions/delete-file/delete-file.mjs index 62222d7e23739..e4b98835f7b3f 100644 --- a/components/google_drive/actions/delete-file/delete-file.mjs +++ b/components/google_drive/actions/delete-file/delete-file.mjs @@ -4,8 +4,8 @@ export default { key: "google_drive-delete-file", name: "Delete File", description: - "Permanently delete a file or folder without moving it to the trash. [See the docs](https://developers.google.com/drive/api/v3/reference/files/delete) for more information", - version: "0.1.3", + "Permanently delete a file or folder without moving it to the trash. [See the documentation](https://developers.google.com/drive/api/v3/reference/files/delete) for more information", + version: "0.1.5", type: "action", props: { googleDrive, diff --git a/components/google_drive/actions/delete-shared-drive/delete-shared-drive.mjs b/components/google_drive/actions/delete-shared-drive/delete-shared-drive.mjs index 74783ebe10724..5b934328e91f6 100644 --- a/components/google_drive/actions/delete-shared-drive/delete-shared-drive.mjs +++ b/components/google_drive/actions/delete-shared-drive/delete-shared-drive.mjs @@ -4,7 +4,7 @@ export default { key: "google_drive-delete-shared-drive", name: "Delete Shared Drive", description: "Delete a shared drive without any content. [See the docs](https://developers.google.com/drive/api/v3/reference/drives/delete) for more information", - version: "0.1.3", + version: "0.1.4", type: "action", props: { googleDrive, diff --git a/components/google_drive/actions/download-file/download-file.mjs b/components/google_drive/actions/download-file/download-file.mjs index 1b37a519060d3..7b6368dc8998e 100644 --- a/components/google_drive/actions/download-file/download-file.mjs +++ b/components/google_drive/actions/download-file/download-file.mjs @@ -2,7 +2,7 @@ import googleDrive from "../../google_drive.app.mjs"; import fs from "fs"; import stream from "stream"; import { promisify } from "util"; -import { GOOGLE_DRIVE_MIME_TYPE_PREFIX } from "../../constants.mjs"; +import { GOOGLE_DRIVE_MIME_TYPE_PREFIX } from "../../common/constants.mjs"; import googleWorkspaceExportFormats from "../google-workspace-export-formats.mjs"; import { toSingleLineString } from "../../common/utils.mjs"; @@ -18,7 +18,7 @@ export default { key: "google_drive-download-file", name: "Download File", description: "Download a file. [See the docs](https://developers.google.com/drive/api/v3/manage-downloads) for more information", - version: "0.1.3", + version: "0.1.4", type: "action", props: { googleDrive, diff --git a/components/google_drive/actions/find-file/find-file.mjs b/components/google_drive/actions/find-file/find-file.mjs index a2449a5023ec0..844ec474164e5 100644 --- a/components/google_drive/actions/find-file/find-file.mjs +++ b/components/google_drive/actions/find-file/find-file.mjs @@ -5,7 +5,7 @@ export default { key: "google_drive-find-file", name: "Find File", description: "Search for a specific file by name. [See the docs](https://developers.google.com/drive/api/v3/search-files) for more information", - version: "0.1.3", + version: "0.1.4", type: "action", props: { googleDrive, diff --git a/components/google_drive/actions/find-folder/find-folder.mjs b/components/google_drive/actions/find-folder/find-folder.mjs index 8c863d02be954..7aac56a5876fc 100644 --- a/components/google_drive/actions/find-folder/find-folder.mjs +++ b/components/google_drive/actions/find-folder/find-folder.mjs @@ -1,13 +1,13 @@ import googleDrive from "../../google_drive.app.mjs"; import { getListFilesOpts } from "../../common/utils.mjs"; -import { GOOGLE_DRIVE_FOLDER_MIME_TYPE } from "../../constants.mjs"; +import { GOOGLE_DRIVE_FOLDER_MIME_TYPE } from "../../common/constants.mjs"; export default { key: "google_drive-find-folder", name: "Find Folder", description: "Search for a specific folder by name. [See the docs](https://developers.google.com/drive/api/v3/search-files) for more information", - version: "0.1.3", + version: "0.1.4", type: "action", props: { googleDrive, diff --git a/components/google_drive/actions/find-forms/find-forms.mjs b/components/google_drive/actions/find-forms/find-forms.mjs index 7f6ceac316836..f31162f178449 100644 --- a/components/google_drive/actions/find-forms/find-forms.mjs +++ b/components/google_drive/actions/find-forms/find-forms.mjs @@ -5,7 +5,7 @@ export default { key: "google_drive-find-forms", name: "Find Forms", description: "List Google Form documents or search for a Form by name. [See the docs](https://developers.google.com/drive/api/v3/search-files) for more information", - version: "0.0.4", + version: "0.0.5", type: "action", props: { googleDrive, diff --git a/components/google_drive/actions/find-spreadsheets/find-spreadsheets.mjs b/components/google_drive/actions/find-spreadsheets/find-spreadsheets.mjs index d27f2699bcc42..a83c6c722b02d 100644 --- a/components/google_drive/actions/find-spreadsheets/find-spreadsheets.mjs +++ b/components/google_drive/actions/find-spreadsheets/find-spreadsheets.mjs @@ -5,7 +5,7 @@ export default { key: "google_drive-find-spreadsheets", name: "Find Spreadsheets", description: "Search for a specific spreadsheet by name. [See the docs](https://developers.google.com/drive/api/v3/search-files) for more information", - version: "0.1.3", + version: "0.1.4", type: "action", props: { googleDrive, diff --git a/components/google_drive/actions/get-folder-id-for-path/get-folder-id-for-path.mjs b/components/google_drive/actions/get-folder-id-for-path/get-folder-id-for-path.mjs index be5b57c51fb9c..c1daa23048ec8 100644 --- a/components/google_drive/actions/get-folder-id-for-path/get-folder-id-for-path.mjs +++ b/components/google_drive/actions/get-folder-id-for-path/get-folder-id-for-path.mjs @@ -11,8 +11,8 @@ import googleDrive from "../../google_drive.app.mjs"; export default { key: "google_drive-get-folder-id-for-path", name: "Get Folder ID for a Path", - description: "Retrieve a folderId for a path. [See the docs](https://developers.google.com/drive/api/v3/search-files) for more information", - version: "0.1.3", + description: "Retrieve a folderId for a path. [See the documentation](https://developers.google.com/drive/api/v3/search-files) for more information", + version: "0.1.6", type: "action", props: { googleDrive, diff --git a/components/google_drive/actions/get-shared-drive/get-shared-drive.mjs b/components/google_drive/actions/get-shared-drive/get-shared-drive.mjs index 98051f21e30f5..c809beadd973c 100644 --- a/components/google_drive/actions/get-shared-drive/get-shared-drive.mjs +++ b/components/google_drive/actions/get-shared-drive/get-shared-drive.mjs @@ -4,7 +4,7 @@ export default { key: "google_drive-get-shared-drive", name: "Get Shared Drive", description: "Get a shared drive's metadata by ID. [See the docs](https://developers.google.com/drive/api/v3/reference/drives/get) for more information", - version: "0.1.3", + version: "0.1.4", type: "action", props: { googleDrive, diff --git a/components/google_drive/actions/list-files/list-files.mjs b/components/google_drive/actions/list-files/list-files.mjs index d5127f573d6f7..5b719be28be05 100644 --- a/components/google_drive/actions/list-files/list-files.mjs +++ b/components/google_drive/actions/list-files/list-files.mjs @@ -5,7 +5,7 @@ export default { key: "google_drive-list-files", name: "List Files", description: "List files from a specific folder. [See the documentation](https://developers.google.com/drive/api/v3/reference/files/list) for more information", - version: "0.1.7", + version: "0.1.8", type: "action", props: { googleDrive, diff --git a/components/google_drive/actions/move-file-to-trash/move-file-to-trash.mjs b/components/google_drive/actions/move-file-to-trash/move-file-to-trash.mjs index 12d3667c880cb..6bf866e7695b9 100644 --- a/components/google_drive/actions/move-file-to-trash/move-file-to-trash.mjs +++ b/components/google_drive/actions/move-file-to-trash/move-file-to-trash.mjs @@ -1,11 +1,11 @@ import googleDrive from "../../google_drive.app.mjs"; -import { GOOGLE_DRIVE_FOLDER_MIME_TYPE } from "../../constants.mjs"; +import { GOOGLE_DRIVE_FOLDER_MIME_TYPE } from "../../common/constants.mjs"; export default { key: "google_drive-move-file-to-trash", name: "Move File to Trash", description: "Move a file or folder to trash. [See the docs](https://developers.google.com/drive/api/v3/reference/files/update) for more information", - version: "0.1.3", + version: "0.1.4", type: "action", props: { googleDrive, diff --git a/components/google_drive/actions/move-file/move-file.mjs b/components/google_drive/actions/move-file/move-file.mjs index 80ad0abb6181d..34823fa750977 100644 --- a/components/google_drive/actions/move-file/move-file.mjs +++ b/components/google_drive/actions/move-file/move-file.mjs @@ -4,7 +4,7 @@ export default { key: "google_drive-move-file", name: "Move File", description: "Move a file from one folder to another. [See the docs](https://developers.google.com/drive/api/v3/reference/files/update) for more information", - version: "0.1.3", + version: "0.1.4", type: "action", props: { googleDrive, diff --git a/components/google_drive/actions/replace-file/replace-file.mjs b/components/google_drive/actions/replace-file/replace-file.mjs index 210a36a6164d7..22069d48aa728 100644 --- a/components/google_drive/actions/replace-file/replace-file.mjs +++ b/components/google_drive/actions/replace-file/replace-file.mjs @@ -9,13 +9,13 @@ import { import { GOOGLE_DRIVE_UPLOAD_TYPE_MEDIA, GOOGLE_DRIVE_UPLOAD_TYPE_RESUMABLE, -} from "../../constants.mjs"; +} from "../../common/constants.mjs"; export default { key: "google_drive-replace-file", name: "Replace File", description: "Upload a file that replaces an existing file. [See the docs](https://developers.google.com/drive/api/v3/reference/files/update) for more information", - version: "0.1.3", + version: "0.1.4", type: "action", props: { googleDrive, diff --git a/components/google_drive/actions/search-shared-drives/search-shared-drives.mjs b/components/google_drive/actions/search-shared-drives/search-shared-drives.mjs index aea071b7b3320..96f26164c00db 100644 --- a/components/google_drive/actions/search-shared-drives/search-shared-drives.mjs +++ b/components/google_drive/actions/search-shared-drives/search-shared-drives.mjs @@ -3,8 +3,8 @@ import googleDrive from "../../google_drive.app.mjs"; export default { key: "google_drive-search-shared-drives", name: "Search for Shared Drives", - description: "Search for shared drives with query options. [See the docs](https://developers.google.com/drive/api/v3/search-shareddrives) for more information", - version: "0.1.3", + description: "Search for shared drives with query options. [See the documentation](https://developers.google.com/drive/api/v3/search-shareddrives) for more information", + version: "0.1.5", type: "action", props: { googleDrive, diff --git a/components/google_drive/actions/update-file/update-file.mjs b/components/google_drive/actions/update-file/update-file.mjs index e4741c83a11c3..0957ecf728f6d 100644 --- a/components/google_drive/actions/update-file/update-file.mjs +++ b/components/google_drive/actions/update-file/update-file.mjs @@ -8,7 +8,7 @@ export default { key: "google_drive-update-file", name: "Update File", description: "Update a file's metadata and/or content. [See the docs](https://developers.google.com/drive/api/v3/reference/files/update) for more information", - version: "0.1.3", + version: "0.1.4", type: "action", props: { googleDrive, diff --git a/components/google_drive/actions/update-shared-drive/update-shared-drive.mjs b/components/google_drive/actions/update-shared-drive/update-shared-drive.mjs index 05907853ca94d..3e66e26d0ebb1 100644 --- a/components/google_drive/actions/update-shared-drive/update-shared-drive.mjs +++ b/components/google_drive/actions/update-shared-drive/update-shared-drive.mjs @@ -4,7 +4,7 @@ export default { key: "google_drive-update-shared-drive", name: "Update Shared Drive", description: "Update an existing shared drive. [See the docs](https://developers.google.com/drive/api/v3/reference/drives/update) for more information", - version: "0.1.3", + version: "0.1.4", type: "action", props: { googleDrive, diff --git a/components/google_drive/actions/upload-file/upload-file.mjs b/components/google_drive/actions/upload-file/upload-file.mjs index c6bcae145bd98..d582ab5c2c002 100644 --- a/components/google_drive/actions/upload-file/upload-file.mjs +++ b/components/google_drive/actions/upload-file/upload-file.mjs @@ -4,13 +4,13 @@ import { getFileStream, omitEmptyStringValues, } from "../../common/utils.mjs"; -import { GOOGLE_DRIVE_UPLOAD_TYPE_MULTIPART } from "../../constants.mjs"; +import { GOOGLE_DRIVE_UPLOAD_TYPE_MULTIPART } from "../../common/constants.mjs"; export default { key: "google_drive-upload-file", name: "Upload File", description: "Copy an existing file to Google Drive. [See the docs](https://developers.google.com/drive/api/v3/manage-uploads) for more information", - version: "0.1.5", + version: "0.1.6", type: "action", props: { googleDrive, diff --git a/components/google_drive/constants.mjs b/components/google_drive/common/constants.mjs similarity index 75% rename from components/google_drive/constants.mjs rename to components/google_drive/common/constants.mjs index ccbe3885ade95..826bfb184d06f 100644 --- a/components/google_drive/constants.mjs +++ b/components/google_drive/common/constants.mjs @@ -66,6 +66,36 @@ const GOOGLE_DRIVE_UPDATE_TYPES = [ GOOGLE_DRIVE_NOTIFICATION_UNTRASH, GOOGLE_DRIVE_NOTIFICATION_CHANGE, ]; +const GOOGLE_DRIVE_UPDATE_TYPE_OPTIONS = [ + { + label: `'${GOOGLE_DRIVE_NOTIFICATION_SYNC}' - A channel was successfully created. You can expect to start receiving notifications for it.`, + value: GOOGLE_DRIVE_NOTIFICATION_SYNC, + }, + { + label: `'${GOOGLE_DRIVE_NOTIFICATION_ADD}' - A resource was created or shared.`, + value: GOOGLE_DRIVE_NOTIFICATION_ADD, + }, + { + label: `'${GOOGLE_DRIVE_NOTIFICATION_REMOVE}' - An existing resource was deleted or unshared.`, + value: GOOGLE_DRIVE_NOTIFICATION_REMOVE, + }, + { + label: `'${GOOGLE_DRIVE_NOTIFICATION_UPDATE}' - One or more properties (metadata) of a resource have been updated.`, + value: GOOGLE_DRIVE_NOTIFICATION_UPDATE, + }, + { + label: `'${GOOGLE_DRIVE_NOTIFICATION_TRASH}' - A resource has been moved to the trash.`, + value: GOOGLE_DRIVE_NOTIFICATION_TRASH, + }, + { + label: `'${GOOGLE_DRIVE_NOTIFICATION_UNTRASH}' - A resource has been removed from the trash.`, + value: GOOGLE_DRIVE_NOTIFICATION_UNTRASH, + }, + { + label: `'${GOOGLE_DRIVE_NOTIFICATION_CHANGE}' - One or more changelog items have been added.`, + value: GOOGLE_DRIVE_NOTIFICATION_CHANGE, + }, +]; /** * This is a custom string value to represent the 'My Drive' Google Drive, which @@ -142,6 +172,33 @@ const GOOGLE_DRIVE_ROLES = [ GOOGLE_DRIVE_ROLE_READER, ]; +const GOOGLE_DRIVE_ROLE_OPTIONS = [ + { + label: "Writer - Can make changes, accept or reject suggestions, and share the file with others.", + value: GOOGLE_DRIVE_ROLE_WRITER, + }, + { + label: "Commenter - Can make comments and suggestions, but can't change or share the file with others.", + value: GOOGLE_DRIVE_ROLE_COMMENTER, + }, + { + label: "Reader - Can access, but can't change or share the file with others.", + value: GOOGLE_DRIVE_ROLE_READER, + }, + { + label: "(Advanced) File Owner - this will transfer ownership of the file.", + value: GOOGLE_DRIVE_ROLE_OWNER, + }, + { + label: "(Advanced) File Organizar", + value: GOOGLE_DRIVE_ROLE_FILEORGANIZER, + }, + { + label: "(Advanced) Organizar", + value: GOOGLE_DRIVE_ROLE_ORGANIZER, + }, +]; + const GOOGLE_DRIVE_GRANTEE_USER = "user"; const GOOGLE_DRIVE_GRANTEE_GROUP = "group"; const GOOGLE_DRIVE_GRANTEE_DOMAIN = "domain"; @@ -175,6 +232,7 @@ export { GOOGLE_DRIVE_NOTIFICATION_UNTRASH, GOOGLE_DRIVE_NOTIFICATION_CHANGE, GOOGLE_DRIVE_UPDATE_TYPES, + GOOGLE_DRIVE_UPDATE_TYPE_OPTIONS, MY_DRIVE_VALUE, LEGACY_MY_DRIVE_VALUE, WEBHOOK_SUBSCRIPTION_EXPIRATION_TIME_MILLISECONDS, @@ -191,6 +249,7 @@ export { GOOGLE_DRIVE_ROLE_COMMENTER, GOOGLE_DRIVE_ROLE_READER, GOOGLE_DRIVE_ROLES, + GOOGLE_DRIVE_ROLE_OPTIONS, // Google Drive Grantee Types GOOGLE_DRIVE_GRANTEE_USER, GOOGLE_DRIVE_GRANTEE_GROUP, diff --git a/components/google_drive/common/utils.mjs b/components/google_drive/common/utils.mjs index bdcc306c67e7e..9ff1989b5737f 100644 --- a/components/google_drive/common/utils.mjs +++ b/components/google_drive/common/utils.mjs @@ -4,7 +4,7 @@ import { MY_DRIVE_VALUE, LEGACY_MY_DRIVE_VALUE, MAX_FILE_OPTION_PATH_SEGMENTS, -} from "../constants.mjs"; +} from "../common/constants.mjs"; /** * Returns whether the specified drive ID corresponds to the authenticated diff --git a/components/google_drive/google_drive.app.mjs b/components/google_drive/google_drive.app.mjs index 54d1df43ac7c3..d86699e080ed5 100644 --- a/components/google_drive/google_drive.app.mjs +++ b/components/google_drive/google_drive.app.mjs @@ -10,12 +10,10 @@ import { MY_DRIVE_VALUE, WEBHOOK_SUBSCRIPTION_EXPIRATION_TIME_MILLISECONDS, GOOGLE_DRIVE_FOLDER_MIME_TYPE, - GOOGLE_DRIVE_ROLES, GOOGLE_DRIVE_GRANTEE_TYPES, - GOOGLE_DRIVE_GRANTEE_ANYONE, - GOOGLE_DRIVE_ROLE_READER, GOOGLE_DRIVE_UPLOAD_TYPES, -} from "./constants.mjs"; + GOOGLE_DRIVE_UPDATE_TYPE_OPTIONS, +} from "./common/constants.mjs"; import googleMimeTypes from "./actions/google-mime-types.mjs"; import { @@ -117,16 +115,15 @@ export default { type: "string[]", label: "Types of updates", description: `The types of updates you want to watch for on these files. - [See Google's docs] - (https://developers.google.com/drive/api/v3/push#understanding-drive-api-notification-events).`, + [See Google's docs](https://developers.google.com/drive/api/v3/push#understanding-drive-api-notification-events).`, default: GOOGLE_DRIVE_UPDATE_TYPES, - options: GOOGLE_DRIVE_UPDATE_TYPES, + options: GOOGLE_DRIVE_UPDATE_TYPE_OPTIONS, }, watchForPropertiesChanges: { type: "boolean", label: "Watch for changes to file properties", - description: `Watch for changes to [file properties](https://developers.google.com/drive/api/v3/properties) - in addition to changes to content. **Defaults to \`false\`, watching for only changes to content**.`, + description: `Watch for changes to [custom file properties](https://developers.google.com/drive/api/v3/properties) + in addition to changes to content. **Defaults to \`false\`, watching only for changes to content**.`, optional: true, default: false, }, @@ -198,37 +195,13 @@ export default { optional: true, default: false, }, - role: { - type: "string", - label: "Role", - description: "The role granted by this permission", - optional: true, - default: GOOGLE_DRIVE_ROLE_READER, - options: GOOGLE_DRIVE_ROLES, - }, type: { type: "string", label: "Type", description: - "The type of the grantee. If **Type** is `user` or `group`, you must provide an **Email Address** for the user or group. When **Type** is `domain`, you must provide a `Domain`. Sharing with a domain is only valid for G Suite users.", - optional: true, - default: GOOGLE_DRIVE_GRANTEE_ANYONE, + "The type of the grantee. Sharing with a domain is only valid for G Suite users.", options: GOOGLE_DRIVE_GRANTEE_TYPES, }, - domain: { - type: "string", - label: "Domain", - description: - "The domain of the G Suite organization to which this permission refers if **Type** is `domain` (e.g., `yourcomapany.com`)", - optional: true, - }, - emailAddress: { - type: "string", - label: "Email Address", - description: - "The email address of the user or group to which this permission refers if **Type** is `user` or `group`", - optional: true, - }, ocrLanguage: { type: "string", label: "OCR Language", diff --git a/components/google_drive/package.json b/components/google_drive/package.json index d11574d7a2369..9e6a15398de66 100644 --- a/components/google_drive/package.json +++ b/components/google_drive/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/google_drive", - "version": "0.6.19", + "version": "0.7.2", "description": "Pipedream Google_drive Components", "main": "google_drive.app.mjs", "keywords": [ diff --git a/components/google_drive/sources/changes-to-specific-files-shared-drive/changes-to-specific-files-shared-drive.mjs b/components/google_drive/sources/changes-to-specific-files-shared-drive/changes-to-specific-files-shared-drive.mjs index 0b2b41fdff641..b49892eb45bff 100644 --- a/components/google_drive/sources/changes-to-specific-files-shared-drive/changes-to-specific-files-shared-drive.mjs +++ b/components/google_drive/sources/changes-to-specific-files-shared-drive/changes-to-specific-files-shared-drive.mjs @@ -14,7 +14,8 @@ import { GOOGLE_DRIVE_NOTIFICATION_CHANGE, GOOGLE_DRIVE_NOTIFICATION_ADD, GOOGLE_DRIVE_NOTIFICATION_UPDATE, -} from "../../constants.mjs"; +} from "../../common/constants.mjs"; +import commonDedupeChanges from "../common-dedupe-changes.mjs"; /** * This source uses the Google Drive API's @@ -25,8 +26,8 @@ export default { ...common, key: "google_drive-changes-to-specific-files-shared-drive", name: "Changes to Specific Files (Shared Drive)", - description: "Watches for changes to specific files in a shared drive, emitting an event any time a change is made to one of those files", - version: "0.1.5", + description: "Watches for changes to specific files in a shared drive, emitting an event when a change is made to one of those files", + version: "0.2.0", type: "source", // Dedupe events based on the "x-goog-message-number" header for the target channel: // https://developers.google.com/drive/api/v3/push#making-watch-requests @@ -37,13 +38,12 @@ export default { type: "string[]", label: "Files", description: "The files you want to watch for changes.", - optional: true, - default: [], options({ prevContext }) { const { nextPageToken } = prevContext; return this.googleDrive.listFilesOptions(nextPageToken, this.getListFilesOpts()); }, }, + ...commonDedupeChanges.props, }, hooks: { async deploy() { @@ -54,6 +54,7 @@ export default { const args = this.getListFilesOpts({ q: `mimeType != "application/vnd.google-apps.folder" and modifiedTime > "${timeString}" and trashed = false`, fields: "files", + pageSize: 5, }); const { files } = await this.googleDrive.listFilesInPage(null, args); @@ -96,7 +97,9 @@ export default { }, getChanges(headers) { if (!headers) { - return; + return { + change: { }, + }; } return { change: { @@ -119,7 +122,9 @@ export default { console.log(`Processing ${changedFiles.length} changed files`); console.log(`Changed files: ${JSON.stringify(changedFiles, null, 2)}!!!`); console.log(`Files: ${this.files}!!!`); - for (const file of changedFiles) { + + const filteredFiles = this.checkMinimumInterval(changedFiles); + for (const file of filteredFiles) { if (!this.isFileRelevant(file)) { console.log(`Skipping event for irrelevant file ${file.id}`); continue; diff --git a/components/google_drive/sources/changes-to-specific-files/changes-to-specific-files.mjs b/components/google_drive/sources/changes-to-specific-files/changes-to-specific-files.mjs index c98a55e389f94..0f4345a85dea4 100644 --- a/components/google_drive/sources/changes-to-specific-files/changes-to-specific-files.mjs +++ b/components/google_drive/sources/changes-to-specific-files/changes-to-specific-files.mjs @@ -3,9 +3,8 @@ import sampleEmit from "./test-event.mjs"; import includes from "lodash/includes.js"; import { v4 as uuid } from "uuid"; -import { MY_DRIVE_VALUE } from "../../constants.mjs"; - import changesToSpecificFiles from "../changes-to-specific-files-shared-drive/changes-to-specific-files-shared-drive.mjs"; +import { MY_DRIVE_VALUE } from "../../common/constants.mjs"; /** * This source uses the Google Drive API's @@ -16,8 +15,8 @@ export default { ...changesToSpecificFiles, key: "google_drive-changes-to-specific-files", name: "Changes to Specific Files", - description: "Watches for changes to specific files, emitting an event any time a change is made to one of those files. To watch for changes to [shared drive](https://support.google.com/a/users/answer/9310351) files, use the **Changes to Specific Files (Shared Drive)** source instead.", - version: "0.1.5", + description: "Watches for changes to specific files, emitting an event when a change is made to one of those files. To watch for changes to [shared drive](https://support.google.com/a/users/answer/9310351) files, use the **Changes to Specific Files (Shared Drive)** source instead.", + version: "0.2.0", type: "source", // Dedupe events based on the "x-goog-message-number" header for the target channel: // https://developers.google.com/drive/api/v3/push#making-watch-requests @@ -222,7 +221,14 @@ export default { return; } - this.processChange(file, headers); + const [ + checkedFile, + ] = this.checkMinimumInterval([ + file, + ]); + if (checkedFile) { + this.processChange(file, headers); + } }, sampleEmit, }; diff --git a/components/google_drive/sources/common-dedupe-changes.mjs b/components/google_drive/sources/common-dedupe-changes.mjs new file mode 100644 index 0000000000000..dea635457bda8 --- /dev/null +++ b/components/google_drive/sources/common-dedupe-changes.mjs @@ -0,0 +1,53 @@ +export default { + props: { + intervalAlert: { + type: "alert", + alertType: "info", + content: `This source can emit many events in quick succession while a file is being edited. By default, it will not emit another event for the same file for at least 1 minute. +\\ +You can change or disable this minimum interval using the prop \`Minimum Interval Per File\`.`, + }, + perFileInterval: { + type: "integer", + label: "Minimum Interval Per File", + description: "How many minutes to wait until the same file can emit another event.\n\nIf set to `0`, this interval is disabled and all events will be emitted.", + min: 0, + max: 60, + default: 1, + optional: true, + }, + }, + methods: { + _getFileIntervals() { + return this.db.get("fileIntervals") ?? {}; + }, + _setFileIntervals(value) { + this.db.set("fileIntervals", value); + }, + checkMinimumInterval(files) { + const interval = this.perFileInterval; + if (!interval) return files; + + const now = Date.now(); + const minTimestamp = now - (interval * 1000 * 60); + + const savedData = this._getFileIntervals(); + Object.entries(savedData).forEach(([ + key, + value, + ]) => { + if (value < minTimestamp) delete savedData[key]; + }); + + const filteredFiles = files.filter(({ id }) => { + const exists = !!savedData[id]; + if (!exists) { + savedData[id] = now; + } + return !exists; + }); + this._setFileIntervals(savedData); + return filteredFiles; + }, + }, +}; diff --git a/components/google_drive/sources/common-webhook.mjs b/components/google_drive/sources/common-webhook.mjs index 361556c10ef7c..33c0224e391d0 100644 --- a/components/google_drive/sources/common-webhook.mjs +++ b/components/google_drive/sources/common-webhook.mjs @@ -2,8 +2,9 @@ import includes from "lodash/includes.js"; import { v4 as uuid } from "uuid"; import googleDrive from "../google_drive.app.mjs"; -import { WEBHOOK_SUBSCRIPTION_RENEWAL_SECONDS } from "../constants.mjs"; +import { WEBHOOK_SUBSCRIPTION_RENEWAL_SECONDS } from "../common/constants.mjs"; import { getListFilesOpts } from "../common/utils.mjs"; +import commonDedupeChanges from "./common-dedupe-changes.mjs"; export default { props: { @@ -18,12 +19,6 @@ export default { description: "Defaults to My Drive. To select a [Shared Drive](https://support.google.com/a/users/answer/9310351) instead, select it from this list.", optional: false, }, - watchForPropertiesChanges: { - propDefinition: [ - googleDrive, - "watchForPropertiesChanges", - ], - }, timer: { label: "Push notification renewal schedule", description: @@ -73,6 +68,7 @@ export default { }, }, methods: { + ...commonDedupeChanges.methods, _getSubscription() { return this.db.get("subscription"); }, diff --git a/components/google_drive/sources/new-files-instant/new-files-instant.mjs b/components/google_drive/sources/new-files-instant/new-files-instant.mjs index 1bba9f0c5ed0d..71a70b062f064 100644 --- a/components/google_drive/sources/new-files-instant/new-files-instant.mjs +++ b/components/google_drive/sources/new-files-instant/new-files-instant.mjs @@ -3,14 +3,14 @@ import sampleEmit from "./test-event.mjs"; import { GOOGLE_DRIVE_NOTIFICATION_ADD, GOOGLE_DRIVE_NOTIFICATION_CHANGE, -} from "../../constants.mjs"; +} from "../../common/constants.mjs"; export default { ...common, key: "google_drive-new-files-instant", name: "New Files (Instant)", - description: "Emit new event any time a new file is added in your linked Google Drive", - version: "0.1.6", + description: "Emit new event when a new file is added in your linked Google Drive", + version: "0.1.7", type: "source", dedupe: "unique", props: { @@ -19,7 +19,7 @@ export default { type: "string[]", label: "Folders", description: - "(Optional) The folders you want to watch for changes. Leave blank to watch for any new file in the Drive.", + "(Optional) The folders you want to watch. Leave blank to watch for any new file in the Drive.", optional: true, default: [], options({ prevContext }) { @@ -50,7 +50,7 @@ export default { q: `mimeType != "application/vnd.google-apps.folder" and createdTime > "${timeString}" and trashed = false`, orderBy: "createdTime desc", fields: "*", - pageSize: 25, + pageSize: 5, }); const { files } = await this.googleDrive.listFilesInPage(null, args); diff --git a/components/google_drive/sources/new-or-modified-comments/new-or-modified-comments.mjs b/components/google_drive/sources/new-or-modified-comments/new-or-modified-comments.mjs index 2b3f762c38739..af99284e40add 100644 --- a/components/google_drive/sources/new-or-modified-comments/new-or-modified-comments.mjs +++ b/components/google_drive/sources/new-or-modified-comments/new-or-modified-comments.mjs @@ -9,15 +9,15 @@ // 2) A timer that runs on regular intervals, renewing the notification channel as needed import common from "../common-webhook.mjs"; -import { GOOGLE_DRIVE_NOTIFICATION_CHANGE } from "../../constants.mjs"; +import { GOOGLE_DRIVE_NOTIFICATION_CHANGE } from "../../common/constants.mjs"; export default { ...common, key: "google_drive-new-or-modified-comments", - name: "New or Modified Comments", + name: "New or Modified Comments (Instant)", description: - "Emits a new event any time a file comment is added, modified, or deleted in your linked Google Drive", - version: "0.1.6", + "Emit new event when a file comment is created or modified in the selected Drive", + version: "0.1.7", type: "source", // Dedupe events based on the "x-goog-message-number" header for the target channel: // https://developers.google.com/drive/api/v3/push#making-watch-requests @@ -31,6 +31,7 @@ export default { const args = this.getListFilesOpts({ q: `mimeType != "application/vnd.google-apps.folder" and modifiedTime > "${timeString}" and trashed = false`, fields: "files", + pageSize: 5, }); const { files } = await this.googleDrive.listFilesInPage(null, args); @@ -82,7 +83,9 @@ export default { }, getChanges(headers) { if (!headers) { - return; + return { + change: { }, + }; } return { change: { diff --git a/components/google_drive/sources/new-or-modified-files/new-or-modified-files.mjs b/components/google_drive/sources/new-or-modified-files/new-or-modified-files.mjs index d9d594aea6e70..12735cbf7e421 100644 --- a/components/google_drive/sources/new-or-modified-files/new-or-modified-files.mjs +++ b/components/google_drive/sources/new-or-modified-files/new-or-modified-files.mjs @@ -14,14 +14,17 @@ import { GOOGLE_DRIVE_NOTIFICATION_ADD, GOOGLE_DRIVE_NOTIFICATION_CHANGE, GOOGLE_DRIVE_NOTIFICATION_UPDATE, -} from "../../constants.mjs"; +} from "../../common/constants.mjs"; +import commonDedupeChanges from "../common-dedupe-changes.mjs"; + +const { googleDrive } = common.props; export default { ...common, key: "google_drive-new-or-modified-files", - name: "New or Modified Files", - description: "Emit new event any time any file in your linked Google Drive is added, modified, or deleted", - version: "0.2.2", + name: "New or Modified Files (Instant)", + description: "Emit new event when a file in the selected Drive is created, modified or trashed.", + version: "0.3.0", type: "source", // Dedupe events based on the "x-goog-message-number" header for the target channel: // https://developers.google.com/drive/api/v3/push#making-watch-requests @@ -30,9 +33,9 @@ export default { ...common.props, folders: { type: "string[]", - label: "Folders", + label: "Folder(s)", description: - "(Optional) The folders you want to watch for changes. Leave blank to watch for any new file in the Drive.", + "The folder(s) to watch for changes. Leave blank to watch for any new or modified file in the Drive.", optional: true, default: [], options({ prevContext }) { @@ -52,6 +55,13 @@ export default { return this.googleDrive.listFilesOptions(nextPageToken, opts); }, }, + watchForPropertiesChanges: { + propDefinition: [ + googleDrive, + "watchForPropertiesChanges", + ], + }, + ...commonDedupeChanges.props, }, hooks: { async deploy() { @@ -62,6 +72,7 @@ export default { const args = this.getListFilesOpts({ q: `mimeType != "application/vnd.google-apps.folder" and modifiedTime > "${timeString}" and trashed = false`, fields: "files", + pageSize: 5, }); const { files } = await this.googleDrive.listFilesInPage(null, args); @@ -105,7 +116,9 @@ export default { }, async getChanges(headers) { if (!headers) { - return; + return { + change: { }, + }; } const resourceUri = headers["x-goog-resource-uri"]; const metadata = await this.googleDrive.getFileMetadata(`${resourceUri}&fields=*`); @@ -121,7 +134,9 @@ export default { async processChanges(changedFiles, headers) { const changes = await this.getChanges(headers); - for (const file of changedFiles) { + const filteredFiles = this.checkMinimumInterval(changedFiles); + + for (const file of filteredFiles) { file.parents = (await this.googleDrive.getFile(file.id, { fields: "parents", })).parents; diff --git a/components/google_drive/sources/new-or-modified-folders/new-or-modified-folders.mjs b/components/google_drive/sources/new-or-modified-folders/new-or-modified-folders.mjs index 2e2d0fdbfe2b9..1a823be2db872 100644 --- a/components/google_drive/sources/new-or-modified-folders/new-or-modified-folders.mjs +++ b/components/google_drive/sources/new-or-modified-folders/new-or-modified-folders.mjs @@ -13,14 +13,14 @@ import { GOOGLE_DRIVE_NOTIFICATION_ADD, GOOGLE_DRIVE_NOTIFICATION_CHANGE, GOOGLE_DRIVE_NOTIFICATION_UPDATE, -} from "../../constants.mjs"; +} from "../../common/constants.mjs"; export default { ...common, key: "google_drive-new-or-modified-folders", - name: "New or Modified Folders", - description: "Emit new event any time any folder in your linked Google Drive is added, modified, or deleted", - version: "0.1.4", + name: "New or Modified Folders (Instant)", + description: "Emit new event when a folder is created or modified in the selected Drive", + version: "0.1.5", type: "source", // Dedupe events based on the "x-goog-message-number" header for the target channel: // https://developers.google.com/drive/api/v3/push#making-watch-requests @@ -34,6 +34,7 @@ export default { const args = this.getListFilesOpts({ q: `mimeType = "application/vnd.google-apps.folder" and modifiedTime > "${timeString}" and trashed = false`, fields: "files(id, mimeType)", + pageSize: 5, }); const { files } = await this.googleDrive.listFilesInPage(null, args); @@ -70,7 +71,9 @@ export default { }, async getChanges(headers) { if (!headers) { - return; + return { + change: { }, + }; } const resourceUri = headers["x-goog-resource-uri"]; const metadata = await this.googleDrive.getFileMetadata(`${resourceUri}&fields=*`); diff --git a/components/google_drive/sources/new-shared-drive/new-shared-drive.mjs b/components/google_drive/sources/new-shared-drive/new-shared-drive.mjs index f8cf9949940fa..d1f935bb09c22 100644 --- a/components/google_drive/sources/new-shared-drive/new-shared-drive.mjs +++ b/components/google_drive/sources/new-shared-drive/new-shared-drive.mjs @@ -5,7 +5,7 @@ export default { key: "google_drive-new-shared-drive", name: "New Shared Drive", description: "Emits a new event any time a shared drive is created.", - version: "0.1.3", + version: "0.1.4", type: "source", dedupe: "unique", props: { diff --git a/components/google_drive/sources/new-spreadsheet/new-spreadsheet.mjs b/components/google_drive/sources/new-spreadsheet/new-spreadsheet.mjs index 84d48907e7e53..aa32650841611 100644 --- a/components/google_drive/sources/new-spreadsheet/new-spreadsheet.mjs +++ b/components/google_drive/sources/new-spreadsheet/new-spreadsheet.mjs @@ -5,21 +5,25 @@ export default { key: "google_drive-new-spreadsheet", type: "source", name: "New Spreadsheet (Instant)", - description: "Emit new event each time a new spreadsheet is created in a drive.", - version: "0.1.4", + description: "Emit new event when a new spreadsheet is created in a drive.", + version: "0.1.5", props: { googleDrive: newFilesInstant.props.googleDrive, db: newFilesInstant.props.db, http: newFilesInstant.props.http, drive: newFilesInstant.props.drive, timer: newFilesInstant.props.timer, - folders: newFilesInstant.props.folders, + folders: { + ...newFilesInstant.props.folders, + description: + "(Optional) The folders you want to watch. Leave blank to watch for any new spreadsheet in the Drive.", + }, }, hooks: { ...newFilesInstant.hooks, async deploy() { // Emit sample records on the first run - const spreadsheets = await this.getSpreadsheets(10); + const spreadsheets = await this.getSpreadsheets(5); for (const fileInfo of spreadsheets) { const createdTime = Date.parse(fileInfo.createdTime); this.$emit(fileInfo, { diff --git a/components/google_sheets/actions/create-column/create-column.mjs b/components/google_sheets/actions/add-column/add-column.mjs similarity index 91% rename from components/google_sheets/actions/create-column/create-column.mjs rename to components/google_sheets/actions/add-column/add-column.mjs index 6294113ea0500..5ca35d5cac3d6 100644 --- a/components/google_sheets/actions/create-column/create-column.mjs +++ b/components/google_sheets/actions/add-column/add-column.mjs @@ -1,10 +1,10 @@ import googleSheets from "../../google_sheets.app.mjs"; export default { - key: "google_sheets-create-column", + key: "google_sheets-add-column", name: "Create Column", - description: "Create a new column in a spreadsheet", - version: "0.1.3", + description: "Create a new column in a spreadsheet. [See the documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchUpdate)", + version: "0.1.5", type: "action", props: { googleSheets, diff --git a/components/google_sheets/actions/add-multiple-rows/add-multiple-rows.mjs b/components/google_sheets/actions/add-multiple-rows/add-multiple-rows.mjs index 285c489d5e87b..b6f4bbe41bf25 100644 --- a/components/google_sheets/actions/add-multiple-rows/add-multiple-rows.mjs +++ b/components/google_sheets/actions/add-multiple-rows/add-multiple-rows.mjs @@ -1,10 +1,14 @@ import googleSheets from "../../google_sheets.app.mjs"; +import { ConfigurationError } from "@pipedream/platform"; +import { + parseArray, getWorksheetHeaders, +} from "../../common/utils.mjs"; export default { key: "google_sheets-add-multiple-rows", name: "Add Multiple Rows", - description: "Add multiple rows of data to a Google Sheet", - version: "0.2.3", + description: "Add multiple rows of data to a Google Sheet. [See the documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/append)", + version: "0.2.6", type: "action", props: { googleSheets, @@ -23,14 +27,24 @@ export default { }), ], }, - sheetName: { + worksheetId: { propDefinition: [ googleSheets, - "sheetName", + "worksheetIDs", (c) => ({ sheetId: c.sheetId, }), ], + type: "string", + label: "Worksheet Id", + withLabel: true, + reloadProps: true, + }, + headersDisplay: { + propDefinition: [ + googleSheets, + "headersDisplay", + ], }, rows: { propDefinition: [ @@ -38,6 +52,12 @@ export default { "rows", ], }, + rowsDescription: { + propDefinition: [ + googleSheets, + "rowsDescription", + ], + }, resetRowFormat: { type: "boolean", label: "Reset Row Format", @@ -45,16 +65,29 @@ export default { optional: true, }, }, + async additionalProps() { + const props = {}; + if (!this.sheetId || !this.worksheetId) { + return props; + } + const rowHeaders = await getWorksheetHeaders(this, this.sheetId, this.worksheetId.label); + if (rowHeaders.length) { + return { + headersDisplay: { + type: "alert", + alertType: "info", + content: `Possible Row Headers: **\`${rowHeaders.join(", ")}\`**`, + hidden: false, + }, + }; + } + }, async run() { - let rows = this.rows; - let inputValidated = true; - if (!Array.isArray(rows)) { - rows = JSON.parse(this.rows); - } + const rows = parseArray(this.rows); - if (!rows || !rows.length || !Array.isArray(rows)) { + if (!rows) { inputValidated = false; } else { rows.forEach((row) => { if (!Array.isArray(row)) { inputValidated = false; } }); @@ -64,12 +97,12 @@ export default { if (!inputValidated) { console.error("Data Submitted:"); console.error(rows); - throw new Error("Rows data is not an array of arrays. Please enter an array of arrays in the `Rows` parameter above. If you're trying to send a single rows to Google Sheets, search for the action to add a single row to Sheets or try modifying the code for this step."); + throw new ConfigurationError("Rows data is not an array of arrays. Please enter an array of arrays in the `Rows` parameter above. If you're trying to send a single rows to Google Sheets, search for the action to add a single row to Sheets or try modifying the code for this step."); } const addRowsResponse = await this.googleSheets.addRowsToSheet({ spreadsheetId: this.sheetId, - range: this.sheetName, + range: this.worksheetId.label, rows, }); diff --git a/components/google_sheets/actions/add-single-row/add-single-row.mjs b/components/google_sheets/actions/add-single-row/add-single-row.mjs index 85fad10d84211..b28518bd771c0 100644 --- a/components/google_sheets/actions/add-single-row/add-single-row.mjs +++ b/components/google_sheets/actions/add-single-row/add-single-row.mjs @@ -1,11 +1,12 @@ import googleSheets from "../../google_sheets.app.mjs"; import { ConfigurationError } from "@pipedream/platform"; +import { parseArray } from "../../common/utils.mjs"; export default { key: "google_sheets-add-single-row", name: "Add Single Row", - description: "Add a single row of data to Google Sheets", - version: "2.1.4", + description: "Add a single row of data to Google Sheets. [See the documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/append)", + version: "2.1.7", type: "action", props: { googleSheets, @@ -23,35 +24,33 @@ export default { driveId: googleSheets.methods.getDriveId(c.drive), }), ], - description: "", withLabel: true, }, - sheetName: { + worksheetId: { propDefinition: [ googleSheets, - "sheetName", + "worksheetIDs", (c) => ({ sheetId: c.sheetId?.value || c.sheetId, }), ], - description: "", + type: "string", + label: "Worksheet Id", + withLabel: true, }, hasHeaders: { - type: "string", + type: "boolean", label: "Does the first row of the sheet have headers?", - description: "If the first row of your document has headers we'll retrieve them to make it easy to enter the value for each column.", - options: [ - "Yes", - "No", - ], + description: "If the first row of your document has headers, we'll retrieve them to make it easy to enter the value for each column.", reloadProps: true, }, }, async additionalProps() { const sheetId = this.sheetId?.value || this.sheetId; + const worksheetName = this.worksheetId?.label; const props = {}; - if (this.hasHeaders === "Yes") { - const { values } = await this.googleSheets.getSpreadsheetValues(sheetId, `${this.sheetName}!1:1`); + if (this.hasHeaders) { + const { values } = await this.googleSheets.getSpreadsheetValues(sheetId, `${worksheetName}!1:1`); if (!values[0]?.length) { throw new ConfigurationError("Could not find a header row. Please either add headers and click \"Refresh fields\" or adjust the action configuration to continue."); } @@ -62,7 +61,12 @@ export default { optional: true, }; } - } else if (this.hasHeaders === "No") { + props.allColumns = { + type: "string", + hidden: true, + default: JSON.stringify(values), + }; + } else { props.myColumnData = { type: "string[]", label: "Values", @@ -73,11 +77,10 @@ export default { }, async run({ $ }) { const sheetId = this.sheetId?.value || this.sheetId; + const worksheetName = this.worksheetId.label; let cells; - if (this.hasHeaders === "Yes") { - // TODO: If we could create a variable using this.allColumns in additionalProps, we dont need - // to call getSpreadsheetValues here again. - const { values: rows } = await this.googleSheets.getSpreadsheetValues(sheetId, `${this.sheetName}!1:1`); + if (this.hasHeaders) { + const rows = JSON.parse(this.allColumns); const [ headers, ] = rows; @@ -90,11 +93,13 @@ export default { // validate input if (!cells || !cells.length) { - throw new Error("Please enter an array of elements in `Cells / Column Values`."); - } else if (!Array.isArray(cells)) { - throw new Error("Cell / Column data is not an array. Please enter an array of elements in `Cells / Column Values`."); + throw new ConfigurationError("Please enter an array of elements in `Cells / Column Values`."); + } + cells = parseArray(cells); + if (!cells) { + throw new ConfigurationError("Cell / Column data is not an array. Please enter an array of elements in `Cells / Column Values`."); } else if (Array.isArray(cells[0])) { - throw new Error("Cell / Column data is a multi-dimensional array. A one-dimensional is expected. If you're trying to send multiple rows to Google Sheets, search for the action to add multiple rows to Sheets."); + throw new ConfigurationError("Cell / Column data is a multi-dimensional array. A one-dimensional is expected. If you're trying to send multiple rows to Google Sheets, search for the action to add multiple rows to Sheets."); } const { @@ -104,13 +109,13 @@ export default { const data = await this.googleSheets.addRowsToSheet({ spreadsheetId: sheetId, - range: this.sheetName, + range: worksheetName, rows: [ arr, ], }); - let summary = `Added 1 row to [${this.sheetId?.label || this.sheetId} (${data.updatedRange})](https://docs.google.com/spreadsheets/d/${sheetId}).`; + let summary = `Added 1 row to [${this.sheetId?.label || sheetId} (${data.updatedRange})](https://docs.google.com/spreadsheets/d/${sheetId}).`; if (convertedIndexes.length > 0) { summary += " We detected something other than a string/number/boolean in at least one of the fields and automatically converted it to a string."; } diff --git a/components/google_sheets/actions/clear-cell/clear-cell.mjs b/components/google_sheets/actions/clear-cell/clear-cell.mjs index f5840b60bdb21..dc13b93747985 100644 --- a/components/google_sheets/actions/clear-cell/clear-cell.mjs +++ b/components/google_sheets/actions/clear-cell/clear-cell.mjs @@ -3,8 +3,8 @@ import googleSheets from "../../google_sheets.app.mjs"; export default { key: "google_sheets-clear-cell", name: "Clear Cell", - description: "Delete the content of a specific cell in a spreadsheet", - version: "0.1.4", + description: "Delete the content of a specific cell in a spreadsheet. [See the documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/clear)", + version: "0.1.7", type: "action", props: { googleSheets, @@ -24,14 +24,17 @@ export default { }), ], }, - sheetName: { + worksheetId: { propDefinition: [ googleSheets, - "sheetName", + "worksheetIDs", (c) => ({ sheetId: c.sheetId, }), ], + type: "string", + label: "Worksheet Id", + withLabel: true, }, cell: { type: "string", @@ -42,7 +45,7 @@ export default { async run() { const request = { spreadsheetId: this.sheetId, - range: `${this.sheetName}!${this.cell}`, + range: `${this.worksheetId.label}!${this.cell}`, }; return await this.googleSheets.clearSheetValues(request); }, diff --git a/components/google_sheets/actions/clear-row/clear-row.mjs b/components/google_sheets/actions/clear-row/clear-row.mjs deleted file mode 100644 index 123e5c94b4ea6..0000000000000 --- a/components/google_sheets/actions/clear-row/clear-row.mjs +++ /dev/null @@ -1,50 +0,0 @@ -import googleSheets from "../../google_sheets.app.mjs"; - -export default { - key: "google_sheets-clear-row", - name: "Clear Row", - description: "Delete the content of a row in a spreadsheet. Deleted rows will appear as blank rows.", - version: "0.1.3", - type: "action", - props: { - googleSheets, - drive: { - propDefinition: [ - googleSheets, - "watchedDrive", - ], - description: "The drive containing the spreadsheet to edit. If you are connected with any [Google Shared Drives](https://support.google.com/a/users/answer/9310351), you can select it here.", - }, - sheetId: { - propDefinition: [ - googleSheets, - "sheetID", - (c) => ({ - driveId: googleSheets.methods.getDriveId(c.drive), - }), - ], - }, - sheetName: { - propDefinition: [ - googleSheets, - "sheetName", - (c) => ({ - sheetId: c.sheetId, - }), - ], - }, - row: { - propDefinition: [ - googleSheets, - "row", - ], - }, - }, - async run() { - const request = { - spreadsheetId: this.sheetId, - range: `${this.sheetName}!${this.row}:${this.row}`, - }; - return await this.googleSheets.clearSheetValues(request); - }, -}; diff --git a/components/google_sheets/actions/delete-row/delete-row.mjs b/components/google_sheets/actions/clear-rows/clear-rows.mjs similarity index 51% rename from components/google_sheets/actions/delete-row/delete-row.mjs rename to components/google_sheets/actions/clear-rows/clear-rows.mjs index 9e639d2cf2557..b4862515daa6b 100644 --- a/components/google_sheets/actions/delete-row/delete-row.mjs +++ b/components/google_sheets/actions/clear-rows/clear-rows.mjs @@ -1,10 +1,10 @@ import googleSheets from "../../google_sheets.app.mjs"; export default { - key: "google_sheets-delete-row", - name: "Delete Row", - description: "Deletes a specific row in a spreadsheet", - version: "0.1.3", + key: "google_sheets-clear-rows", + name: "Clear Rows", + description: "Delete the content of a row or rows in a spreadsheet. Deleted rows will appear as blank rows. [See the documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/clear)", + version: "0.1.5", type: "action", props: { googleSheets, @@ -33,33 +33,26 @@ export default { }), ], type: "string", - label: "Worksheet", + label: "Worksheet Id", + withLabel: true, }, - row: { - propDefinition: [ - googleSheets, - "row", - ], + startIndex: { + type: "integer", + label: "Start Index", + description: "Row number of the start (inclusive) of the range of rows to clear", + }, + endIndex: { + type: "integer", + label: "End Index", + description: "Row number of the end (exclusive) of the range of rows to clear", + optional: true, }, }, async run() { const request = { spreadsheetId: this.sheetId, - requestBody: { - requests: [ - { - deleteDimension: { - range: { - "sheetId": this.worksheetId, - "dimension": "ROWS", - "startIndex": this.row - 1, - "endIndex": this.row, - }, - }, - }, - ], - }, + range: `${this.worksheetId.label}!${this.startIndex}:${this.endIndex || this.startIndex}`, }; - return await this.googleSheets.batchUpdate(request); + return await this.googleSheets.clearSheetValues(request); }, }; diff --git a/components/google_sheets/actions/copy-worksheet/copy-worksheet.mjs b/components/google_sheets/actions/copy-worksheet/copy-worksheet.mjs index 10a4d86aa41e1..a720d4e95a628 100644 --- a/components/google_sheets/actions/copy-worksheet/copy-worksheet.mjs +++ b/components/google_sheets/actions/copy-worksheet/copy-worksheet.mjs @@ -3,8 +3,8 @@ import googleSheets from "../../google_sheets.app.mjs"; export default { key: "google_sheets-copy-worksheet", name: "Copy Worksheet", - description: "Copy an existing worksheet to another Google Sheets file", - version: "0.1.3", + description: "Copy an existing worksheet to another Google Sheets file. [See the documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.sheets/copyTo)", + version: "0.1.5", type: "action", props: { googleSheets, @@ -45,7 +45,7 @@ export default { driveId: googleSheets.methods.getDriveId(c.drive), }), ], - description: "The spreadsheet to copy the worksheetsheet to", + description: "The spreadsheet to copy the worksheet to", }, }, async run() { diff --git a/components/google_sheets/actions/create-spreadsheet/create-spreadsheet.mjs b/components/google_sheets/actions/create-spreadsheet/create-spreadsheet.mjs index 0c13ae60010d8..54984aebcc1a1 100644 --- a/components/google_sheets/actions/create-spreadsheet/create-spreadsheet.mjs +++ b/components/google_sheets/actions/create-spreadsheet/create-spreadsheet.mjs @@ -3,8 +3,8 @@ import googleSheets from "../../google_sheets.app.mjs"; export default { key: "google_sheets-create-spreadsheet", name: "Create Spreadsheet", - description: "Create a blank spreadsheet or duplicate an existing spreadsheet", - version: "0.1.4", + description: "Create a blank spreadsheet or duplicate an existing spreadsheet. [See the documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/create)", + version: "0.1.6", type: "action", props: { googleSheets, diff --git a/components/google_sheets/actions/create-worksheet/create-worksheet.mjs b/components/google_sheets/actions/create-worksheet/create-worksheet.mjs index a17e5389af3ad..75931d20cd208 100644 --- a/components/google_sheets/actions/create-worksheet/create-worksheet.mjs +++ b/components/google_sheets/actions/create-worksheet/create-worksheet.mjs @@ -3,8 +3,8 @@ import googleSheets from "../../google_sheets.app.mjs"; export default { key: "google_sheets-create-worksheet", name: "Create Worksheet", - description: "Create a blank worksheet with a title", - version: "0.1.3", + description: "Create a blank worksheet with a title. [See the documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchUpdate)", + version: "0.1.5", type: "action", props: { googleSheets, diff --git a/components/google_sheets/actions/delete-rows/delete-rows.mjs b/components/google_sheets/actions/delete-rows/delete-rows.mjs index 75b8bca14f09a..c3a161b26a4d0 100644 --- a/components/google_sheets/actions/delete-rows/delete-rows.mjs +++ b/components/google_sheets/actions/delete-rows/delete-rows.mjs @@ -4,7 +4,7 @@ export default { key: "google_sheets-delete-rows", name: "Delete Rows", description: "Deletes the specified rows from a spreadsheet. [See the documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#deletedimensionrequest)", - version: "0.0.3", + version: "0.0.5", type: "action", props: { googleSheets, @@ -44,6 +44,7 @@ export default { type: "integer", label: "End Index", description: "Row number of the end (exclusive) of the range of rows to delete", + optional: true, }, }, async run() { @@ -57,7 +58,9 @@ export default { "sheetId": this.worksheetId, "dimension": "ROWS", "startIndex": this.startIndex - 1, - "endIndex": this.endIndex - 1, + "endIndex": this.endIndex + ? this.endIndex - 1 + : this.startIndex, }, }, }, diff --git a/components/google_sheets/actions/delete-worksheet/delete-worksheet.mjs b/components/google_sheets/actions/delete-worksheet/delete-worksheet.mjs index d0645db35e0af..9c1b035f5a942 100644 --- a/components/google_sheets/actions/delete-worksheet/delete-worksheet.mjs +++ b/components/google_sheets/actions/delete-worksheet/delete-worksheet.mjs @@ -3,8 +3,8 @@ import googleSheets from "../../google_sheets.app.mjs"; export default { key: "google_sheets-delete-worksheet", name: "Delete Worksheet", - description: "Delete a specific worksheet", - version: "0.1.3", + description: "Delete a specific worksheet. [See the documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchUpdate)", + version: "0.1.5", type: "action", props: { googleSheets, diff --git a/components/google_sheets/actions/find-row-vlookup/find-row-vlookup.mjs b/components/google_sheets/actions/find-row-vlookup/find-row-vlookup.mjs deleted file mode 100644 index ae0068f03a5d2..0000000000000 --- a/components/google_sheets/actions/find-row-vlookup/find-row-vlookup.mjs +++ /dev/null @@ -1,85 +0,0 @@ -import app from "../../google_sheets.app.mjs"; - -export default { - key: "google_sheets-find-row-vlookup", - name: "Find Row Vlookup", - description: "Find one or more rows by the first column value and a search text using [VLOOKUP](https://support.google.com/docs/answer/3093318) formula", - version: "0.0.2", - type: "action", - props: { - app, - drive: { - propDefinition: [ - app, - "watchedDrive", - ], - }, - sheetId: { - propDefinition: [ - app, - "sheetID", - (c) => ({ - driveId: app.methods.getDriveId(c.drive), - }), - ], - }, - sheetName: { - propDefinition: [ - app, - "sheetName", - (c) => ({ - sheetId: c.sheetId, - }), - ], - }, - range: { - description: "The upper and lower values to consider for the search. Eg `A1:C5`", - propDefinition: [ - app, - "range", - ], - }, - searchValue: { - type: "string", - label: "Search Value", - description: "The value to search for in the first column of the **Range**.", - }, - }, - methods: { - isRangeValid(range) { - return range?.match(/^[A-Z]+\d+:[A-Z]+\d+$/); - }, - }, - async run() { - const { - app, - sheetId, - sheetName, - range, - searchValue, - } = this; - - const sheets = app.sheets(); - - const validRange = this.isRangeValid(range) - ? range - : `${range}:${range}`; - - const { data: { values } } = - await sheets.spreadsheets.values.get({ - spreadsheetId: sheetId, - range: `${sheetName}!${validRange}`, - }); - - return values.reduce((acc, row, index) => { - if (row[0] === searchValue) { - return acc.concat({ - row, - index, - googleSheetsRowNumber: index + 1, - }); - } - return acc; - }, []); - }, -}; diff --git a/components/google_sheets/actions/find-row/find-row.mjs b/components/google_sheets/actions/find-row/find-row.mjs index febecb0128e70..6bdedba773e45 100644 --- a/components/google_sheets/actions/find-row/find-row.mjs +++ b/components/google_sheets/actions/find-row/find-row.mjs @@ -3,8 +3,8 @@ import googleSheets from "../../google_sheets.app.mjs"; export default { key: "google_sheets-find-row", name: "Find Row", - description: "Find one or more rows by a column and value", - version: "0.2.3", + 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.5", type: "action", props: { googleSheets, @@ -23,14 +23,17 @@ export default { }), ], }, - sheetName: { + worksheetId: { propDefinition: [ googleSheets, - "sheetName", + "worksheetIDs", (c) => ({ sheetId: c.sheetId, }), ], + type: "string", + label: "Worksheet Id", + withLabel: true, }, column: { propDefinition: [ @@ -43,17 +46,23 @@ export default { label: "Value", description: "The value to search for", }, + exportRow: { + type: "boolean", + label: "Export Row", + description: "Set to `true` to return cell values for the entire row", + optional: true, + }, }, async run() { const sheets = this.googleSheets.sheets(); const colValues = (await sheets.spreadsheets.values.get({ spreadsheetId: this.sheetId, - range: `${this.sheetName}!${this.column}:${this.column}`, + range: `${this.worksheetId.label}!${this.column}:${this.column}`, })).data.values; const rows = []; - return colValues.reduce((values, value, index) => { + const result = colValues.reduce((values, value, index) => { if (value == this.value) { rows.push({ value, @@ -63,5 +72,26 @@ export default { } return rows; }); + + if (!this.exportRow) { + return result; + } + + const indexes = result.map(({ index }) => index); + const { data: { values } } = + await sheets.spreadsheets.values.get({ + spreadsheetId: this.sheetId, + range: `${this.worksheetId.label}`, + }); + return values.reduce((acc, row, index) => { + if (indexes.includes(index)) { + return acc.concat({ + row, + index, + googleSheetsRowNumber: index + 1, + }); + } + return acc; + }, []); }, }; diff --git a/components/google_sheets/actions/get-cell/get-cell.mjs b/components/google_sheets/actions/get-cell/get-cell.mjs index 568d2afc55ec3..982be14579bd8 100644 --- a/components/google_sheets/actions/get-cell/get-cell.mjs +++ b/components/google_sheets/actions/get-cell/get-cell.mjs @@ -3,8 +3,8 @@ import googleSheets from "../../google_sheets.app.mjs"; export default { key: "google_sheets-get-cell", name: "Get Cell", - description: "Fetch the contents of a specific cell in a spreadsheet", - version: "0.1.3", + description: "Fetch the contents of a specific cell in a spreadsheet. [See the documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/get)", + version: "0.1.5", type: "action", props: { googleSheets, @@ -23,14 +23,17 @@ export default { }), ], }, - sheetName: { + worksheetId: { propDefinition: [ googleSheets, - "sheetName", + "worksheetIDs", (c) => ({ sheetId: c.sheetId, }), ], + type: "string", + label: "Worksheet Id", + withLabel: true, }, cell: { propDefinition: [ @@ -42,9 +45,12 @@ export default { async run() { const sheets = this.googleSheets.sheets(); - return (await sheets.spreadsheets.values.get({ + const values = (await sheets.spreadsheets.values.get({ spreadsheetId: this.sheetId, - range: `${this.sheetName}!${this.cell}:${this.cell}`, + range: `${this.worksheetId.label}!${this.cell}:${this.cell}`, })).data.values; + if (values?.length) { + return values[0][0]; + } }, }; diff --git a/components/google_sheets/actions/get-spreadsheet-by-id/get-spreadsheet-by-id.mjs b/components/google_sheets/actions/get-spreadsheet-by-id/get-spreadsheet-by-id.mjs index 9b3bc4418ab52..831421933d42a 100644 --- a/components/google_sheets/actions/get-spreadsheet-by-id/get-spreadsheet-by-id.mjs +++ b/components/google_sheets/actions/get-spreadsheet-by-id/get-spreadsheet-by-id.mjs @@ -3,8 +3,8 @@ import googleSheets from "../../google_sheets.app.mjs"; export default { key: "google_sheets-get-spreadsheet-by-id", name: "Get Spreadsheet by ID", - description: "Returns the spreadsheet at the given ID. [See the docs](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/get) for more information", - version: "0.1.3", + description: "Returns the spreadsheet at the given ID. [See the documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/get) for more information", + version: "0.1.6", type: "action", props: { googleSheets, @@ -22,7 +22,6 @@ export default { driveId: googleSheets.methods.getDriveId(c.drive), }), ], - optional: false, }, }, async run({ $ }) { diff --git a/components/google_sheets/actions/get-values-in-range/get-values-in-range.mjs b/components/google_sheets/actions/get-values-in-range/get-values-in-range.mjs index 43e7816dc3f05..4febfdf2e00fb 100644 --- a/components/google_sheets/actions/get-values-in-range/get-values-in-range.mjs +++ b/components/google_sheets/actions/get-values-in-range/get-values-in-range.mjs @@ -3,8 +3,8 @@ import googleSheets from "../../google_sheets.app.mjs"; export default { key: "google_sheets-get-values-in-range", name: "Get Values in Range", - description: "Get values from a range of cells using A1 notation.", - version: "0.1.3", + description: "Get all values or values from a range of cells using A1 notation. [See the documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/get)", + version: "0.1.5", type: "action", props: { googleSheets, @@ -23,20 +23,24 @@ export default { }), ], }, - sheetName: { + worksheetId: { propDefinition: [ googleSheets, - "sheetName", + "worksheetIDs", (c) => ({ sheetId: c.sheetId, }), ], + type: "string", + label: "Worksheet Id", + withLabel: true, }, range: { propDefinition: [ googleSheets, "range", ], + optional: true, }, }, async run() { @@ -44,7 +48,9 @@ export default { return (await sheets.spreadsheets.values.get({ spreadsheetId: this.sheetId, - range: `${this.sheetName}!${this.range}`, + range: this.range + ? `${this.worksheetId.label}!${this.range}` + : `${this.worksheetId.label}`, })).data.values; }, }; diff --git a/components/google_sheets/actions/get-values/get-values.mjs b/components/google_sheets/actions/get-values/get-values.mjs deleted file mode 100644 index 820c2f023b3af..0000000000000 --- a/components/google_sheets/actions/get-values/get-values.mjs +++ /dev/null @@ -1,44 +0,0 @@ -import googleSheets from "../../google_sheets.app.mjs"; - -export default { - key: "google_sheets-get-values", - name: "Get Values", - description: "Get all values from a sheet.", - version: "0.1.3", - type: "action", - props: { - googleSheets, - drive: { - propDefinition: [ - googleSheets, - "watchedDrive", - ], - }, - sheetId: { - propDefinition: [ - googleSheets, - "sheetID", - (c) => ({ - driveId: googleSheets.methods.getDriveId(c.drive), - }), - ], - }, - sheetName: { - propDefinition: [ - googleSheets, - "sheetName", - (c) => ({ - sheetId: c.sheetId, - }), - ], - }, - }, - async run() { - const sheets = this.googleSheets.sheets(); - - return (await sheets.spreadsheets.values.get({ - spreadsheetId: this.sheetId, - range: `${this.sheetName}`, - })).data.values; - }, -}; diff --git a/components/google_sheets/actions/insert-anchored-note/insert-anchored-note.mjs b/components/google_sheets/actions/insert-anchored-note/insert-anchored-note.mjs index e9834896c3ee0..18ec7481a4b15 100644 --- a/components/google_sheets/actions/insert-anchored-note/insert-anchored-note.mjs +++ b/components/google_sheets/actions/insert-anchored-note/insert-anchored-note.mjs @@ -4,8 +4,8 @@ import app from "../../google_sheets.app.mjs"; export default { key: "google_sheets-insert-anchored-note", name: "Insert an Anchored Note", - description: "Insert a note on a spreadsheet cell. [See the docs here](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/batchUpdate)", - version: "0.1.3", + description: "Insert a note on a spreadsheet cell. [See the documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/batchUpdate)", + version: "0.1.5", type: "action", props: { app, diff --git a/components/google_sheets/actions/insert-comment/insert-comment.mjs b/components/google_sheets/actions/insert-comment/insert-comment.mjs index 57b41ba226f9b..1fdebf96d8769 100644 --- a/components/google_sheets/actions/insert-comment/insert-comment.mjs +++ b/components/google_sheets/actions/insert-comment/insert-comment.mjs @@ -3,8 +3,8 @@ import app from "../../google_sheets.app.mjs"; export default { key: "google_sheets-insert-comment", name: "Insert Comment", - description: "Insert a comment into a spreadsheet. [See the docs here](https://developers.google.com/drive/api/v3/reference/comments/create)", - version: "0.1.3", + description: "Insert a comment into a spreadsheet. [See the documentation](https://developers.google.com/drive/api/v3/reference/comments/create)", + version: "0.1.6", type: "action", props: { app, diff --git a/components/google_sheets/actions/list-worksheets/list-worksheets.mjs b/components/google_sheets/actions/list-worksheets/list-worksheets.mjs index a5baa79f74510..0384462e1ffa2 100644 --- a/components/google_sheets/actions/list-worksheets/list-worksheets.mjs +++ b/components/google_sheets/actions/list-worksheets/list-worksheets.mjs @@ -3,8 +3,8 @@ import googleSheets from "../../google_sheets.app.mjs"; export default { key: "google_sheets-list-worksheets", name: "List Worksheets", - description: "Get a list of all worksheets in a spreadsheet", - version: "0.1.3", + description: "Get a list of all worksheets in a spreadsheet. [See the documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/get)", + version: "0.1.5", type: "action", props: { googleSheets, diff --git a/components/google_sheets/actions/update-cell/update-cell.mjs b/components/google_sheets/actions/update-cell/update-cell.mjs index 9dea6847ab518..c4581da5d71ff 100644 --- a/components/google_sheets/actions/update-cell/update-cell.mjs +++ b/components/google_sheets/actions/update-cell/update-cell.mjs @@ -3,8 +3,8 @@ import googleSheets from "../../google_sheets.app.mjs"; export default { key: "google_sheets-update-cell", name: "Update Cell", - description: "Update a cell in a spreadsheet", - version: "0.1.3", + description: "Update a cell in a spreadsheet. [See the documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/update)", + version: "0.1.5", type: "action", props: { googleSheets, @@ -25,14 +25,17 @@ export default { ], description: "The spreadsheet containing the worksheet to update", }, - sheetName: { + worksheetId: { propDefinition: [ googleSheets, - "sheetName", + "worksheetIDs", (c) => ({ sheetId: c.sheetId, }), ], + type: "string", + label: "Worksheet Id", + withLabel: true, }, cell: { propDefinition: [ @@ -53,7 +56,7 @@ export default { async run() { const request = { spreadsheetId: this.sheetId, - range: `${this.sheetName}!${this.cell}:${this.cell}`, + range: `${this.worksheetId.label}!${this.cell}:${this.cell}`, valueInputOption: "USER_ENTERED", resource: { values: [ diff --git a/components/google_sheets/actions/update-rows/update-rows.mjs b/components/google_sheets/actions/update-multiple-rows/update-multiple-rows.mjs similarity index 61% rename from components/google_sheets/actions/update-rows/update-rows.mjs rename to components/google_sheets/actions/update-multiple-rows/update-multiple-rows.mjs index f96dcc6111892..8affa4dbb4107 100644 --- a/components/google_sheets/actions/update-rows/update-rows.mjs +++ b/components/google_sheets/actions/update-multiple-rows/update-multiple-rows.mjs @@ -1,10 +1,13 @@ import googleSheets from "../../google_sheets.app.mjs"; +import { + parseArray, getWorksheetHeaders, +} from "../../common/utils.mjs"; export default { - key: "google_sheets-update-rows", - name: "Update Rows", - description: "Update multiple rows in a spreadsheet defined by a range", - version: "0.1.3", + key: "google_sheets-update-multiple-rows", + name: "Update Multiple Rows", + description: "Update multiple rows in a spreadsheet defined by a range. [See the documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/update)", + version: "0.1.5", type: "action", props: { googleSheets, @@ -26,14 +29,24 @@ export default { ], description: "The spreadsheet containing the worksheet to update", }, - sheetName: { + worksheetId: { propDefinition: [ googleSheets, - "sheetName", + "worksheetIDs", (c) => ({ sheetId: c.sheetId, }), ], + type: "string", + label: "Worksheet Id", + withLabel: true, + reloadProps: true, + }, + headersDisplay: { + propDefinition: [ + googleSheets, + "headersDisplay", + ], }, range: { propDefinition: [ @@ -47,17 +60,36 @@ export default { "rows", ], }, + rowsDescription: { + propDefinition: [ + googleSheets, + "rowsDescription", + ], + }, + }, + async additionalProps() { + const props = {}; + if (!this.sheetId || !this.worksheetId) { + return props; + } + const rowHeaders = await getWorksheetHeaders(this, this.sheetId, this.worksheetId.label); + if (rowHeaders.length) { + return { + headersDisplay: { + type: "alert", + alertType: "info", + content: `Possible Row Headers: **\`${rowHeaders.join(", ")}\`**`, + hidden: false, + }, + }; + } }, async run() { - let rows = this.rows; - let inputValidated = true; - if (!Array.isArray(rows)) { - rows = JSON.parse(this.rows); - } + const rows = parseArray(this.rows); - if (!rows || !rows.length || !Array.isArray(rows)) { + if (!rows) { inputValidated = false; } else { rows.forEach((row) => { @@ -78,7 +110,7 @@ export default { const request = { spreadsheetId: this.sheetId, - range: `${this.sheetName}!${this.range}`, + range: `${this.worksheetId.label}!${this.range}`, valueInputOption: "USER_ENTERED", resource: { values: rows, diff --git a/components/google_sheets/actions/update-row/update-row.mjs b/components/google_sheets/actions/update-row/update-row.mjs index 2a61380103149..946a266f51f70 100644 --- a/components/google_sheets/actions/update-row/update-row.mjs +++ b/components/google_sheets/actions/update-row/update-row.mjs @@ -1,10 +1,12 @@ import googleSheets from "../../google_sheets.app.mjs"; +import { ConfigurationError } from "@pipedream/platform"; +import { parseArray } from "../../common/utils.mjs"; export default { key: "google_sheets-update-row", name: "Update Row", - description: "Update a row in a spreadsheet", - version: "0.1.3", + description: "Update a row in a spreadsheet. [See the documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/update)", + version: "0.1.5", type: "action", props: { googleSheets, @@ -25,14 +27,17 @@ export default { ], description: "The spreadsheet containing the worksheet to update", }, - sheetName: { + worksheetId: { propDefinition: [ googleSheets, - "sheetName", + "worksheetIDs", (c) => ({ sheetId: c.sheetId, }), ], + type: "string", + label: "Worksheet Id", + withLabel: true, }, row: { propDefinition: [ @@ -43,26 +48,26 @@ export default { cells: { propDefinition: [ googleSheets, - "cells", + "rows", ], + description: "Enter an array, with each element of the array representing a cell/column value (e.g. `[\"Foo\",1,2]`). You may reference an arrays exported by a previous step (e.g., `{{steps.foo.$return_value}}`). You may also enter or construct a string that will JSON.parse() to an array.", }, }, async run() { - const cells = this.cells; - // validate input - if (!cells || !cells.length) { - throw new Error("Please enter an array of elements in `Cells / Column Values`."); + if (!this.cells || !this.cells.length) { + throw new ConfigurationError("Please enter an array of elements in `Row Values`."); } - if (!Array.isArray(cells)) { - throw new Error("Cell / Column data is not an array. Please enter an array of elements in `Cells / Column Values`."); + const cells = parseArray(this.cells); + if (!cells) { + throw new ConfigurationError("Row Values is not an array. Please enter an array of elements in `Row Values`."); } if (Array.isArray(cells[0])) { - throw new Error("Cell / Column data is a multi-dimensional array. A one-dimensional is expected."); + throw new ConfigurationError("Row Values is a multi-dimensional array. A one-dimensional is expected."); } const request = { spreadsheetId: this.sheetId, - range: `${this.sheetName}!${this.row}:${this.row}`, + range: `${this.worksheetId.label}!${this.row}:${this.row}`, valueInputOption: "USER_ENTERED", resource: { values: [ diff --git a/components/google_sheets/actions/upsert-row/upsert-row.mjs b/components/google_sheets/actions/upsert-row/upsert-row.mjs index d2df493f04c1c..50466a22a31e4 100644 --- a/components/google_sheets/actions/upsert-row/upsert-row.mjs +++ b/components/google_sheets/actions/upsert-row/upsert-row.mjs @@ -1,9 +1,9 @@ import { v4 as uuid } from "uuid"; -import { VALUE_RENDER_OPTION } from "../../constants.mjs"; +import { VALUE_RENDER_OPTION } from "../../common/constants.mjs"; import googleSheets from "../../google_sheets.app.mjs"; import { omitEmptyKey, toSingleLineString, -} from "../../utils.mjs"; +} from "../../common/utils.mjs"; /** * This action performs an upsert operation, similar to the MySQL `INSERT INTO ... ON DUPLICATE KEY @@ -20,8 +20,8 @@ import { export default { key: "google_sheets-upsert-row", name: "Upsert Row", - description: "Upsert a row of data in a Google Sheet", - version: "0.1.4", + description: "Upsert a row of data in a Google Sheet. [See the documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/append)", + version: "0.1.7", type: "action", props: { googleSheets, @@ -40,14 +40,17 @@ export default { }), ], }, - sheetName: { + worksheetId: { propDefinition: [ googleSheets, - "sheetName", + "worksheetIDs", (c) => ({ sheetId: c.sheetId, }), ], + type: "string", + label: "Worksheet Id", + withLabel: true, }, insert: { propDefinition: [ @@ -94,7 +97,7 @@ export default { async run({ $ }) { const { sheetId, - sheetName, + worksheetId, insert, column, value, @@ -126,7 +129,7 @@ export default { keyValue, // A1 ], [ - this.googleSheets.buildMatchFormula("A1", sheetName, { + this.googleSheets.buildMatchFormula("A1", worksheetId.label, { column, searchType: 0, }), // A2 @@ -146,7 +149,7 @@ export default { // INSERT ROW const result = await this.googleSheets.addRowsToSheet({ spreadsheetId: sheetId, - range: sheetName, + range: worksheetId.label, rows: [ insert, ], @@ -158,7 +161,7 @@ export default { // UPDATE ROW const updateParams = [ sheetId, - sheetName, + worksheetId.label, matchedRow, ]; const sanitizedUpdates = omitEmptyKey(updates); diff --git a/components/google_sheets/constants.mjs b/components/google_sheets/common/constants.mjs similarity index 100% rename from components/google_sheets/constants.mjs rename to components/google_sheets/common/constants.mjs diff --git a/components/google_sheets/utils.mjs b/components/google_sheets/common/utils.mjs similarity index 67% rename from components/google_sheets/utils.mjs rename to components/google_sheets/common/utils.mjs index a8c4216e8df78..b895e4130da61 100644 --- a/components/google_sheets/utils.mjs +++ b/components/google_sheets/common/utils.mjs @@ -41,7 +41,39 @@ function toSingleLineString(multiLineString) { .replace(/\s{2,}/g, " "); } +function parseArray(value) { + if (!value) { + return []; + } + if (Array.isArray(value)) { + return value; + } + try { + const parsed = JSON.parse(value); + if (!Array.isArray(parsed)) { + return false; + } + return parsed; + } catch { + return false; + } +} + +async function getWorksheetHeaders(ctx, sheetId, worksheetName) { + const headers = []; + const { values } = await ctx.googleSheets.getSpreadsheetValues(sheetId, `${worksheetName}!1:1`); + if (!values[0]?.length) { + return headers; + } + for (let i = 0; i < values[0]?.length; i++) { + headers.push(values[0][i]); + } + return headers; +} + export { omitEmptyKey, toSingleLineString, + parseArray, + getWorksheetHeaders, }; diff --git a/components/google_sheets/google_sheets.app.mjs b/components/google_sheets/google_sheets.app.mjs index 37be5a1e2ed4b..24943c4f67524 100644 --- a/components/google_sheets/google_sheets.app.mjs +++ b/components/google_sheets/google_sheets.app.mjs @@ -3,7 +3,7 @@ import sheets from "@googleapis/sheets"; import googleDrive from "../google_drive/google_drive.app.mjs"; import { INSERT_DATA_OPTION, VALUE_INPUT_OPTION, -} from "./constants.mjs"; +} from "./common/constants.mjs"; import isArray from "lodash/isArray.js"; import get from "lodash/get.js"; import isString from "lodash/isString.js"; @@ -22,8 +22,7 @@ export default { cells: { type: "string[]", label: "Cells / Column Values", - description: - "Enter individual cell values or a custom expression to pass an array with each element representing a cell/column value.", + description: "Enter individual cell values or a custom expression to pass an array with each element representing a cell/column value.", }, range: { type: "string", @@ -43,8 +42,18 @@ export default { rows: { type: "string", label: "Row Values", - description: - "Provide an array of arrays. Each nested array should represent a row, with each element of the nested array representing a cell/column value (e.g., passing `[[\"Foo\",1,2],[\"Bar\",3,4]]` will insert two rows of data with three columns each). The most common pattern is to reference an array of arrays exported by a previous step (e.g., `{{steps.foo.$return_value}}`). You may also enter or construct a string that will `JSON.parse()` to an array of arrays.", + description: "Provide an array of arrays", + }, + rowsDescription: { + type: "alert", + alertType: "neutral", + content: "Each nested array should represent a row, with each element of the nested array representing a cell/column value (e.g., passing `[[\"Foo\",1,2],[\"Bar\",3,4]]` will insert two rows of data with three columns each). The most common pattern is to reference an array of arrays exported by a previous step (e.g., `{{steps.foo.$return_value}}`). You may also enter or construct a string that will `JSON.parse()` to an array of arrays.", + }, + headersDisplay: { + type: "alert", + alertType: "info", + content: "", + hidden: true, }, sheetID: { type: "string", @@ -58,16 +67,6 @@ export default { return this.listSheetsOptions(driveId, nextPageToken); }, }, - sheetName: { - type: "string", - label: "Sheet Name", - description: "Your sheet name", - async options({ sheetId }) { - const { sheets } = await this.getSpreadsheet(sheetId); - return sheets.map((sheet) => sheet.properties.title); - }, - }, - // TODO: is this a duplicate of the prop above? worksheetIDs: { type: "string[]", label: "Worksheet(s)", @@ -134,9 +133,13 @@ export default { endCoord, ] = range.split(":"); const startCol = startCoord.replace(/\d/g, ""); - const endCol = endCoord.replace(/\d/g, ""); + const endCol = endCoord + ? endCoord.replace(/\d/g, "") + : startCol; const startRow = parseInt(startCoord.replace(/\D/g, ""), 10) - 1; // 0-based index - const endRow = parseInt(endCoord.replace(/\D/g, ""), 10); + const endRow = endCoord + ? parseInt(endCoord.replace(/\D/g, ""), 10) + : startRow; return { sheetName, diff --git a/components/google_sheets/package.json b/components/google_sheets/package.json index bf3a5f0a37270..e7fa1ed295509 100644 --- a/components/google_sheets/package.json +++ b/components/google_sheets/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/google_sheets", - "version": "0.7.1", + "version": "0.7.4", "description": "Pipedream Google_sheets Components", "main": "google_sheets.app.mjs", "keywords": [ diff --git a/components/google_sheets/sources/common/http-based/base.mjs b/components/google_sheets/sources/common/http-based/base.mjs index 9789ed003936a..8c33ca9ff00ae 100644 --- a/components/google_sheets/sources/common/http-based/base.mjs +++ b/components/google_sheets/sources/common/http-based/base.mjs @@ -10,9 +10,10 @@ import { v4 as uuid } from "uuid"; -import { WEBHOOK_SUBSCRIPTION_RENEWAL_SECONDS } from "../../../../google_drive/constants.mjs"; +import { + MY_DRIVE_VALUE, WEBHOOK_SUBSCRIPTION_RENEWAL_SECONDS, +} from "../../../../google_drive/common/constants.mjs"; import googleSheets from "../../../google_sheets.app.mjs"; -import { MY_DRIVE_VALUE } from "../../../../google_drive/constants.mjs"; /** * The number of events that will be automatically sent whenever the event @@ -137,7 +138,7 @@ export default { throw new Error("activateHook is not implemented"); }, processSpreadsheet() { - throw new Error("processEvent is not implemented"); + throw new Error("processSpreadsheet is not implemented"); }, async renewSubscription() { throw new Error("renewSubscription is not implemented"); diff --git a/components/google_sheets/sources/common/new-row-added.mjs b/components/google_sheets/sources/common/new-row-added.mjs index 7ffff7ad5892d..0923500c6ee88 100644 --- a/components/google_sheets/sources/common/new-row-added.mjs +++ b/components/google_sheets/sources/common/new-row-added.mjs @@ -19,16 +19,15 @@ export default { }, }, methods: { - getMeta(spreadsheet, worksheet, rowNumber, row) { - const { sheetId: worksheetId } = worksheet; - const { spreadsheetId: sheetId } = spreadsheet; + _getRowHashes() { + return this.db.get("rowHashes") || {}; + }, + _setRowHashes(rowHashes) { + this.db.set("rowHashes", rowHashes); + }, + getMeta(worksheet, rowNumber) { const ts = Date.now(); - const rowHash = crypto - .createHash("md5") - .update(JSON.stringify(row)) - .digest("base64"); - // Include a hash of the row data in id to dedupe events for the same new row - const id = `${sheetId}${worksheetId}${rowNumber}${rowHash}`; + const id = `${worksheet.properties.sheetId}${rowNumber}${ts}`; const summary = `New row #${rowNumber} in ${worksheet.properties.title}`; return { id, @@ -154,11 +153,21 @@ export default { : newRowCount, ); + const rowHashes = this._getRowHashes(); for (const [ index, newRow, ] of newRowValues.values.entries()) { const rowNumber = lowerBound + index; + const rowHash = crypto + .createHash("md5") + .update(JSON.stringify(newRow)) + .digest("base64"); + const rowHashString = `${worksheet.properties.sheetId}${rowNumber}${rowHash}`; + if (rowHashes[rowHashString]) { + continue; + } + rowHashes[rowHashString] = true; this.$emit( { newRow, @@ -166,9 +175,10 @@ export default { worksheet, rowNumber, }, - this.getMeta(spreadsheet, worksheet, rowNumber, newRow), + this.getMeta(worksheet, rowNumber), ); } + this._setRowHashes(rowHashes); } }, }, diff --git a/components/google_sheets/sources/common/new-updates.mjs b/components/google_sheets/sources/common/new-updates.mjs index b8e2e1e16dd88..d36aa070f61dc 100644 --- a/components/google_sheets/sources/common/new-updates.mjs +++ b/components/google_sheets/sources/common/new-updates.mjs @@ -1,4 +1,3 @@ -import crypto from "crypto"; import base from "./http-based/base.mjs"; import zlib from "zlib"; @@ -20,23 +19,15 @@ export default { }, }, methods: { - getMeta(spreadsheet, worksheet, changes) { + getMeta(spreadsheet, worksheet) { const { sheetId: worksheetId, title: worksheetTitle, } = worksheet.properties; - const { - spreadsheetId: sheetId, - properties: { title: sheetTitle }, - } = spreadsheet; - - const changesHash = crypto - .createHash("md5") - .update(JSON.stringify(changes)) - .digest("base64"); + const { properties: { title: sheetTitle } } = spreadsheet; const ts = Date.now(); - const id = `${sheetId}${worksheetId}${changesHash}${ts}`; + const id = `${worksheetId}${ts}`; const summary = `${sheetTitle} - ${worksheetTitle}`; return { id, @@ -217,7 +208,7 @@ export default { worksheet, changes, }, - this.getMeta(spreadsheet, worksheet, changes), + this.getMeta(spreadsheet, worksheet), ); } this._setSheetValues( diff --git a/components/google_sheets/sources/new-comment/new-comment.mjs b/components/google_sheets/sources/new-comment/new-comment.mjs new file mode 100644 index 0000000000000..277e90d167be9 --- /dev/null +++ b/components/google_sheets/sources/new-comment/new-comment.mjs @@ -0,0 +1,62 @@ +import httpBase from "../common/http-based/sheet.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...httpBase, + key: "google_sheets-new-comment", + name: "New Comment (Instant)", + description: "Emit new event each time a comment is added to a spreadsheet.", + version: "0.0.2", + dedupe: "unique", + type: "source", + methods: { + ...httpBase.methods, + _getLastTs() { + return this.db.get("lastTs"); + }, + _setLastTs(lastTs) { + this.db.set("lastTs", lastTs); + }, + generateMeta(comment) { + return { + id: comment.id, + summary: `New Comment: ${comment.content}`, + ts: Date.parse(comment.createdTime), + }; + }, + takeSheetSnapshot() {}, + getSheetId() { + return this.sheetID.toString(); + }, + async processSpreadsheet() { + const comments = []; + const lastTs = this._getLastTs(); + const results = this.googleSheets.listComments(this.sheetID, lastTs); + for await (const comment of results) { + comments.push(comment); + } + if (!comments.length) { + return; + } + this._setLastTs(comments[0].createdTime); + comments.reverse().forEach((comment) => { + const meta = this.generateMeta(comment); + this.$emit(comment, meta); + }); + }, + }, + async run(event) { + if (event.timestamp) { + // Component was invoked by timer + return this.renewSubscription(); + } + + if (!this.isEventRelevant(event)) { + console.log("Sync notification, exiting early"); + return; + } + + await this.processSpreadsheet(); + }, + sampleEmit, +}; diff --git a/components/google_sheets/sources/new-comment/test-event.mjs b/components/google_sheets/sources/new-comment/test-event.mjs new file mode 100644 index 0000000000000..cf1631d72304f --- /dev/null +++ b/components/google_sheets/sources/new-comment/test-event.mjs @@ -0,0 +1,21 @@ +export default { + "id": "AAABM3vICvg", + "kind": "drive#comment", + "createdTime": "2024-05-08T21:32:04.823Z", + "modifiedTime": "2024-05-08T21:32:04.823Z", + "anchor": "{\"type\":\"workbook-range\",\"uid\":0,\"range\":\"1600938329\"}", + "replies": [], + "author": { + "displayName": "Test User", + "kind": "drive#user", + "me": true, + "photoLink": "//lh3.googleusercontent.com/a/ACg8ocKv3FxHiUdLT981ghC9w01W50yqe5fi2XWOSA4TgnZf8pCxmg=s50-c-k-no" + }, + "deleted": false, + "htmlContent": "comment", + "content": "comment", + "quotedFileContent": { + "mimeType": "text/html", + "value": "1" + } +} \ No newline at end of file diff --git a/components/google_sheets/sources/new-row-added-shared-drive/new-row-added-shared-drive.mjs b/components/google_sheets/sources/new-row-added-shared-drive/new-row-added-shared-drive.mjs deleted file mode 100644 index efcf521e24007..0000000000000 --- a/components/google_sheets/sources/new-row-added-shared-drive/new-row-added-shared-drive.mjs +++ /dev/null @@ -1,23 +0,0 @@ -import httpBase from "../common/http-based/drive.mjs"; -import sampleEmit from "./test-event.mjs"; -import newRowAdded from "../common/new-row-added.mjs"; - -export default { - ...httpBase, - ...newRowAdded, - key: "google_sheets-new-row-added-shared-drive", - name: "New Row Added (Shared Drive, Instant)", - description: "Emit new events each time a row or rows are added to the bottom of a spreadsheet in a shared drive", - version: "0.1.4", - dedupe: "unique", - type: "source", - props: { - ...httpBase.props, - ...newRowAdded.props, - }, - methods: { - ...httpBase.methods, - ...newRowAdded.methods, - }, - sampleEmit, -}; diff --git a/components/google_sheets/sources/new-row-added-shared-drive/test-event.mjs b/components/google_sheets/sources/new-row-added-shared-drive/test-event.mjs deleted file mode 100644 index 8d4f37bee6460..0000000000000 --- a/components/google_sheets/sources/new-row-added-shared-drive/test-event.mjs +++ /dev/null @@ -1,19 +0,0 @@ -export default { - "newRow": [ - "BA" - ], - "range": "Sheet2!6:1000", - "worksheet": { - "properties": { - "sheetId": 29223230, - "title": "Sheet2", - "index": 1, - "sheetType": "GRID", - "gridProperties": { - "rowCount": 1000, - "columnCount": 26 - } - } - }, - "rowNumber": 10 - } \ No newline at end of file diff --git a/components/google_sheets/sources/new-row-added/new-row-added.mjs b/components/google_sheets/sources/new-row-added/new-row-added.mjs index 5793dd11587db..685d6cff64116 100644 --- a/components/google_sheets/sources/new-row-added/new-row-added.mjs +++ b/components/google_sheets/sources/new-row-added/new-row-added.mjs @@ -1,13 +1,14 @@ import httpBase from "../common/http-based/sheet.mjs"; import newRowAdded from "../common/new-row-added.mjs"; +import sampleEmit from "./test-event.mjs"; export default { ...httpBase, ...newRowAdded, key: "google_sheets-new-row-added", name: "New Row Added (Instant)", - description: "Emit new events each time a row or rows are added to the bottom of a spreadsheet. To use this source with a spreadsheet in a [Shared Drive](https://support.google.com/a/users/answer/9310351), use the **New Row Added (Shared Drive, Instant)** source instead.", - version: "0.1.3", + description: "Emit new event each time a row or rows are added to the bottom of a spreadsheet.", + version: "0.1.6", dedupe: "unique", type: "source", props: { @@ -18,4 +19,5 @@ export default { ...httpBase.methods, ...newRowAdded.methods, }, + sampleEmit, }; diff --git a/components/google_sheets/sources/new-row-added/test-event.mjs b/components/google_sheets/sources/new-row-added/test-event.mjs new file mode 100644 index 0000000000000..4df198f8611f2 --- /dev/null +++ b/components/google_sheets/sources/new-row-added/test-event.mjs @@ -0,0 +1,19 @@ +export default { + "newRow": [ + "a" + ], + "range": "Sheet1!1:1000", + "worksheet": { + "properties": { + "sheetId": 1942312195, + "title": "Sheet1", + "index": 1, + "sheetType": "GRID", + "gridProperties": { + "rowCount": 1000, + "columnCount": 26 + } + } + }, + "rowNumber": 1 +} \ No newline at end of file diff --git a/components/google_sheets/sources/new-updates-shared-drive/new-updates-shared-drive.mjs b/components/google_sheets/sources/new-updates-shared-drive/new-updates-shared-drive.mjs deleted file mode 100644 index acac2507c83af..0000000000000 --- a/components/google_sheets/sources/new-updates-shared-drive/new-updates-shared-drive.mjs +++ /dev/null @@ -1,23 +0,0 @@ -import httpBase from "../common/http-based/drive.mjs"; -import sampleEmit from "./test-event.mjs"; -import newUpdates from "../common/new-updates.mjs"; - -export default { - ...httpBase, - ...newUpdates, - key: "google_sheets-new-updates-shared-drive", - type: "source", - name: "New Updates (Shared Drive, Instant)", - description: "Emit new event each time a row or cell is updated in a spreadsheet in a shared drive", - version: "0.2.1", - dedupe: "unique", - props: { - ...httpBase.props, - ...newUpdates.props, - }, - methods: { - ...httpBase.methods, - ...newUpdates.methods, - }, - sampleEmit, -}; diff --git a/components/google_sheets/sources/new-updates-shared-drive/test-event.mjs b/components/google_sheets/sources/new-updates-shared-drive/test-event.mjs deleted file mode 100644 index ed30007ffddc6..0000000000000 --- a/components/google_sheets/sources/new-updates-shared-drive/test-event.mjs +++ /dev/null @@ -1 +0,0 @@ -export default "{\n \"worksheet\": {\n \"properties\": {\n \"sheetId\": 358595775,\n \"title\": \"Test\",\n \"index\": 0,\n \"sheetType\": \"GRID\",\n \"gridProperties\": {\n \"rowCount\": 1031,\n \"columnCount\": 50\n }\n }\n },\n \"currentValues\": {\n \"values\": [\n [\n \"C1\",\n \"C2\",\n \"\",\n \"C3\",\n \"\",\n \"C5\",\n \"\",\n \"C7\",\n \"C8\"\n ],\n [\n \"V1\",\n \"V2\",\n \"V0002\",\n \"V3\",\n \"V4\",\n \"V5\",\n \"V0006\",\n \"V7\",\n \"V8\"\n ],\n [\n \"A\"\n ],\n [\n \"B\"\n ],\n [\n \"C1\"\n ]\n ],\n \"range\": \"Test!A1:AX1031\",\n \"majorDimension\": \"ROWS\"\n },\n \"changes\": [\n {\n \"cell\": \"A:5\",\n \"previous_value\": \"\",\n \"new_value\": \"C1\"\n }\n ]\n}" \ No newline at end of file diff --git a/components/google_sheets/sources/new-updates/new-updates.mjs b/components/google_sheets/sources/new-updates/new-updates.mjs index 5667a434f41bc..46e4041e6088a 100644 --- a/components/google_sheets/sources/new-updates/new-updates.mjs +++ b/components/google_sheets/sources/new-updates/new-updates.mjs @@ -8,8 +8,8 @@ export default { key: "google_sheets-new-updates", type: "source", name: "New Updates (Instant)", - description: "Emit new event each time a row or cell is updated in a spreadsheet. To use this source with a spreadsheet in a [Shared Drive](https://support.google.com/a/users/answer/9310351), use the **New Updates (Shared Drive, Instant)** source instead.", - version: "0.2.1", + description: "Emit new event each time a row or cell is updated in a spreadsheet.", + version: "0.2.4", dedupe: "unique", props: { ...httpBase.props, diff --git a/components/google_sheets/sources/new-worksheet-shared-drive/new-worksheet-shared-drive.mjs b/components/google_sheets/sources/new-worksheet-shared-drive/new-worksheet-shared-drive.mjs deleted file mode 100644 index 4cbd35be30325..0000000000000 --- a/components/google_sheets/sources/new-worksheet-shared-drive/new-worksheet-shared-drive.mjs +++ /dev/null @@ -1,25 +0,0 @@ -import httpBase from "../common/http-based/drive.mjs"; -import newWorksheet from "../common/new-worksheet.mjs"; - -export default { - ...httpBase, - ...newWorksheet, - key: "google_sheets-new-worksheet-shared-drive", - type: "source", - name: "New Worksheet (Shared Drive, Instant)", - description: "Emit new event each time a new worksheet is created in a spreadsheet in a shared drive", - version: "0.1.3", - dedupe: "unique", - hooks: { - ...httpBase.hooks, - ...newWorksheet.hooks, - }, - props: { - ...httpBase.props, - ...newWorksheet.props, - }, - methods: { - ...httpBase.methods, - ...newWorksheet.methods, - }, -}; diff --git a/components/google_sheets/sources/new-worksheet/new-worksheet.mjs b/components/google_sheets/sources/new-worksheet/new-worksheet.mjs index 63ac4958588f7..94732513f2849 100644 --- a/components/google_sheets/sources/new-worksheet/new-worksheet.mjs +++ b/components/google_sheets/sources/new-worksheet/new-worksheet.mjs @@ -1,5 +1,6 @@ import httpBase from "../common/http-based/sheet.mjs"; import newWorksheet from "../common/new-worksheet.mjs"; +import sampleEmit from "./test-event.mjs"; export default { ...httpBase, @@ -7,8 +8,8 @@ export default { key: "google_sheets-new-worksheet", type: "source", name: "New Worksheet (Instant)", - description: "Emit new event each time a new worksheet is created in a spreadsheet. To use this source with a spreadsheet in a [Shared Drive](https://support.google.com/a/users/answer/9310351), use the **New Worksheet (Shared Drive, Instant)** source instead.", - version: "0.1.3", + description: "Emit new event each time a new worksheet is created in a spreadsheet.", + version: "0.1.7", dedupe: "unique", hooks: { ...httpBase.hooks, @@ -22,4 +23,5 @@ export default { ...httpBase.methods, ...newWorksheet.methods, }, + sampleEmit, }; diff --git a/components/google_sheets/sources/new-worksheet/test-event.mjs b/components/google_sheets/sources/new-worksheet/test-event.mjs new file mode 100644 index 0000000000000..2dbe48d891e23 --- /dev/null +++ b/components/google_sheets/sources/new-worksheet/test-event.mjs @@ -0,0 +1,12 @@ +export default { + "properties": { + "sheetId": 0, + "title": "Sheet1", + "index": 0, + "sheetType": "GRID", + "gridProperties": { + "rowCount": 1007, + "columnCount": 28 + } + } +} \ No newline at end of file diff --git a/components/google_slides/package.json b/components/google_slides/package.json index 0b2020347857f..3f8e077f87e0d 100644 --- a/components/google_slides/package.json +++ b/components/google_slides/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/google_slides", - "version": "0.1.0", + "version": "0.1.1", "description": "Pipedream Google Slides Components", "main": "google_slides.app.mjs", "keywords": [ diff --git a/components/google_slides/sources/new-presentation/new-presentation.mjs b/components/google_slides/sources/new-presentation/new-presentation.mjs index 6f8b18035a065..03f67a74017f2 100644 --- a/components/google_slides/sources/new-presentation/new-presentation.mjs +++ b/components/google_slides/sources/new-presentation/new-presentation.mjs @@ -6,12 +6,12 @@ export default { type: "source", name: "New Presentation (Instant)", description: "Emit new event each time a new presentation is created in a drive.", - version: "0.0.2", + version: "0.0.3", hooks: { ...newFilesInstant.hooks, async deploy() { // Emit sample records on the first run - const slides = await this.getPresentations(10); + const slides = await this.getPresentations(5); for (const fileInfo of slides) { const createdTime = Date.parse(fileInfo.createdTime); this.$emit(fileInfo, { @@ -22,6 +22,14 @@ export default { } }, }, + props: { + ...newFilesInstant.props, + folders: { + ...newFilesInstant.props.folders, + description: + "(Optional) The folders you want to watch. Leave blank to watch for any new presentation in the Drive.", + }, + }, methods: { ...newFilesInstant.methods, shouldProcess(file) { diff --git a/components/hotmart/.gitignore b/components/hotmart/.gitignore deleted file mode 100644 index ec761ccab7595..0000000000000 --- a/components/hotmart/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.js -*.mjs -dist \ No newline at end of file diff --git a/components/hotmart/actions/get-comissions/get-comissions.mjs b/components/hotmart/actions/get-comissions/get-comissions.mjs new file mode 100644 index 0000000000000..e6ba898b9a87f --- /dev/null +++ b/components/hotmart/actions/get-comissions/get-comissions.mjs @@ -0,0 +1,55 @@ +import app from "../../hotmart.app.mjs"; + +export default { + key: "hotmart-get-comissions", + name: "Get Comissions", + description: "Get sales commission information for sale participants. [See the documentation](https://developers.hotmart.com/docs/pt-BR/v1/sales/sales-commissions/)", + version: "0.0.1", + type: "action", + props: { + app, + productId: { + propDefinition: [ + app, + "productId", + ], + }, + startDate: { + propDefinition: [ + app, + "startDate", + ], + }, + endDate: { + propDefinition: [ + app, + "endDate", + ], + }, + }, + async run({ $ }) { + let results = []; + let stop = false; + + while (!stop) { + const { + items, page_info: { total_results }, + } = await this.app.getComissions({ + $, + params: { + product_id: this.productId, + start_date: this.startDate, + end_date: this.endDate, + }, + }); + + results = results.concat(items); + + stop = total_results <= results.length; + } + + $.export("$summary", `Successfully retrieved ${results.length} comission(s)`); + + return results; + }, +}; diff --git a/components/hotmart/actions/get-sales-history/get-sales-history.mjs b/components/hotmart/actions/get-sales-history/get-sales-history.mjs new file mode 100644 index 0000000000000..2d7297fbc42f3 --- /dev/null +++ b/components/hotmart/actions/get-sales-history/get-sales-history.mjs @@ -0,0 +1,56 @@ +import app from "../../hotmart.app.mjs"; + +export default { + key: "hotmart-get-sales-history", + name: "Get Sales History", + description: "Retrieve sales history from the Hotmart account. [See the documentation](https://developers.hotmart.com/docs/pt-BR/v1/sales/sales-history/)", + version: "0.0.1", + type: "action", + props: { + app, + productId: { + propDefinition: [ + app, + "productId", + ], + }, + buyerEmail: { + propDefinition: [ + app, + "buyerEmail", + ], + }, + offerCode: { + propDefinition: [ + app, + "offerCode", + ], + }, + }, + async run({ $ }) { + let results = []; + let stop = false; + + while (!stop) { + const { + items, page_info: { total_results }, + } = await this.app.getSalesHistory({ + $, + params: { + product_id: this.productId, + buyer_email: this.buyerEmail, + offer_code: this.offerCode, + }, + subscriber_code: this.subscriberCode, + }); + + results = results.concat(items); + + stop = total_results <= results.length; + } + + $.export("$summary", `Successfully retrieved ${results.length} sale(s)`); + + return results; + }, +}; diff --git a/components/hotmart/actions/get-subscriptions/get-subscriptions.mjs b/components/hotmart/actions/get-subscriptions/get-subscriptions.mjs new file mode 100644 index 0000000000000..9a4ae8c145d1b --- /dev/null +++ b/components/hotmart/actions/get-subscriptions/get-subscriptions.mjs @@ -0,0 +1,30 @@ +import app from "../../hotmart.app.mjs"; + +export default { + key: "hotmart-get-subscriptions", + name: "Get Subscriptions", + description: "Get subscribers from hotmart profile. [See the documentation](https://developers.hotmart.com/docs/pt-BR/v1/subscription/get-subscribers/)", + version: "0.0.1", + type: "action", + props: { + app, + productId: { + propDefinition: [ + app, + "productId", + ], + }, + }, + async run({ $ }) { + const response = await this.app.getSubscriptions({ + $, + params: { + product_id: this.productId, + }, + }); + + $.export("$summary", `Successfully retrieved ${response.items.length} subscription(s)`); + + return response; + }, +}; diff --git a/components/hotmart/hotmart.app.mjs b/components/hotmart/hotmart.app.mjs new file mode 100644 index 0000000000000..506d34d2eaf3d --- /dev/null +++ b/components/hotmart/hotmart.app.mjs @@ -0,0 +1,93 @@ +import { axios } from "@pipedream/platform"; + +export default { + type: "app", + app: "hotmart", + propDefinitions: { + offerCode: { + type: "string", + label: "Offer Code", + description: "Code of the offer", + optional: true, + }, + buyerEmail: { + type: "string", + label: "Buyer Email", + description: "Email of the buyer", + optional: true, + }, + productId: { + type: "string", + label: "Product ID", + description: "ID of the product", + optional: true, + }, + startDate: { + type: "string", + label: "Start Date", + description: "Start date for the query, in milliseconds", + optional: true, + }, + endDate: { + type: "string", + label: "End Date", + description: "End date for the query, in milliseconds", + optional: true, + }, + subscriberCode: { + type: "string", + label: "Subscriber Code", + description: "ID code of the subscriber", + async options() { + const response = await this.getSubscriprions({}); + const accountIDs = response.items; + return accountIDs.map(({ + subscriber_code, subscriber, + }) => ({ + value: subscriber_code, + label: subscriber.name, + })); + }, + }, + }, + methods: { + _baseUrl() { + return `https://${this.$auth.environment}.hotmart.com/payments/api/v1`; + }, + async _makeRequest(opts = {}) { + const { + $ = this, + path, + headers, + ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + url: this._baseUrl() + path, + headers: { + ...headers, + "Authorization": `Bearer ${this.$auth.oauth_access_token}`, + "Content-Type": "application/json", + }, + }); + }, + async getSubscriptions(args = {}) { + return this._makeRequest({ + path: "/subscriptions", + ...args, + }); + }, + async getComissions(args = {}) { + return this._makeRequest({ + path: "/sales/commissions", + ...args, + }); + }, + async getSalesHistory(args = {}) { + return this._makeRequest({ + path: "/sales/history", + ...args, + }); + }, + }, +}; diff --git a/components/hotmart/package.json b/components/hotmart/package.json index f92046d769fb9..fcaad941c8f05 100644 --- a/components/hotmart/package.json +++ b/components/hotmart/package.json @@ -1,16 +1,19 @@ { "name": "@pipedream/hotmart", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Hotmart Components", - "main": "dist/app/hotmart.app.mjs", + "main": "hotmart.app.mjs", "keywords": [ "pipedream", "hotmart" ], - "files": ["dist"], "homepage": "https://pipedream.com/apps/hotmart", "author": "Pipedream (https://pipedream.com/)", + "dependencies": { + "@pipedream/platform": "^1.6.4" + }, + "gitHead": "e12480b94cc03bed4808ebc6b13e7fdb3a1ba535", "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/components/hubspot/package.json b/components/hubspot/package.json index 77d470d195e7e..0e553d5017fa1 100644 --- a/components/hubspot/package.json +++ b/components/hubspot/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/hubspot", - "version": "0.8.2", + "version": "0.8.4", "description": "Pipedream Hubspot Components", "main": "hubspot.app.mjs", "keywords": [ diff --git a/components/hubspot/sources/new-engagement/new-engagement.mjs b/components/hubspot/sources/new-engagement/new-engagement.mjs index 6e3d78e9a1b68..a5d9c33e71c84 100644 --- a/components/hubspot/sources/new-engagement/new-engagement.mjs +++ b/components/hubspot/sources/new-engagement/new-engagement.mjs @@ -5,21 +5,21 @@ export default { key: "hubspot-new-engagement", name: "New Engagement", description: "Emit new event for each new engagement created. This action returns a maximum of 5000 records at a time, make sure you set a correct time range so you don't miss any events", - version: "0.0.17", + version: "0.0.18", dedupe: "unique", type: "source", hooks: {}, methods: { ...common.methods, getTs(engagement) { - return Date.parse(engagement.createdAt); + return engagement.engagement.createdAt; }, generateMeta(engagement) { const { id, type, } = engagement.engagement; - const ts = this.getTs(engagement.engagement); + const ts = this.getTs(engagement); return { id, summary: type, @@ -27,7 +27,7 @@ export default { }; }, isRelevant(engagement, createdAfter) { - return this.getTs(engagement.engagement) > createdAfter; + return this.getTs(engagement) > createdAfter; }, }, async run() { diff --git a/components/hubspot/sources/new-event/new-event.mjs b/components/hubspot/sources/new-event/new-event.mjs index b97704c4d21cf..5acb677087654 100644 --- a/components/hubspot/sources/new-event/new-event.mjs +++ b/components/hubspot/sources/new-event/new-event.mjs @@ -1,11 +1,12 @@ import common from "../common/common.mjs"; +import { ConfigurationError } from "@pipedream/platform"; export default { ...common, key: "hubspot-new-event", name: "New Events", - description: "Emit new event for each new Hubspot event.", - version: "0.0.16", + description: "Emit new event for each new Hubspot event. Note: Only available for Marketing Hub Enterprise, Sales Hub Enterprise, Service Hub Enterprise, or CMS Hub Enterprise accounts", + version: "0.0.17", dedupe: "unique", type: "source", props: { @@ -26,7 +27,19 @@ export default { ], }, }, - hooks: {}, + hooks: { + async deploy() { + try { + await this.hubspot.getEvents({ + objectType: this.objectType, + objectId: this.objectIds[0], + }); + } + catch { + throw new ConfigurationError("Error occurred. Please verify that your Hubspot account is one of: Marketing Hub Enterprise, Sales Hub Enterprise, Service Hub Enterprise, or CMS Hub Enterprise"); + } + }, + }, methods: { ...common.methods, getTs() { diff --git a/components/insighto_ai/actions/add-text-blob/add-text-blob.mjs b/components/insighto_ai/actions/add-text-blob/add-text-blob.mjs new file mode 100644 index 0000000000000..05ee99152fadd --- /dev/null +++ b/components/insighto_ai/actions/add-text-blob/add-text-blob.mjs @@ -0,0 +1,88 @@ +import app from "../../insighto_ai.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "insighto_ai-add-text-blob", + name: "Add Text Blob", + description: "Adds a text blob into an existing data source. [See the documentation](https://api.insighto.ai/docs#/datasource/add_datasourcefile_text_blob_api_v1_datasource__datasource_id__text_blob_post)", + version: "0.0.1", + type: "action", + props: { + app, + dataSourceId: { + propDefinition: [ + app, + "dataSourceId", + ], + }, + dataSourceType: { + propDefinition: [ + app, + "dataSourceType", + ({ dataSourceId }) => ({ + dataSourceId, + }), + ], + }, + name: { + type: "string", + label: "Name", + description: "The name of the text blob.", + optional: true, + }, + description: { + type: "string", + label: "Description", + description: "The description of the text blob.", + optional: true, + }, + orgId: { + optional: true, + propDefinition: [ + app, + "orgId", + ], + }, + data: { + type: "object", + label: "Attributes", + description: "The attributes of the text blob.", + optional: true, + }, + }, + methods: { + addTextBlob({ + dataSourceId, ...args + } = {}) { + return this.app.post({ + path: `/datasource/${dataSourceId}/text_blob`, + ...args, + }); + }, + }, + async run({ $ }) { + const { + addTextBlob, + dataSourceId, + dataSourceType, + name, + description, + orgId, + data, + } = this; + + const response = await addTextBlob({ + $, + dataSourceId, + params: { + ds_type: dataSourceType, + name, + description, + org_id: orgId, + }, + data: utils.parse(data), + }); + $.export("$summary", `Successfully added text blob with ID \`${response.data?.id}\``); + return response; + }, +}; diff --git a/components/insighto_ai/actions/create-contact/create-contact.mjs b/components/insighto_ai/actions/create-contact/create-contact.mjs new file mode 100644 index 0000000000000..a605053bfbc7b --- /dev/null +++ b/components/insighto_ai/actions/create-contact/create-contact.mjs @@ -0,0 +1,107 @@ +import app from "../../insighto_ai.app.mjs"; + +export default { + key: "insighto_ai-create-contact", + name: "Create Contact", + description: "Creates a new contact within the system. [See the documentation](https://api.insighto.ai/docs#/contact/create_contact_api_v1_contact_post)", + version: "0.0.1", + type: "action", + props: { + app, + 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.", + }, + email: { + type: "string", + label: "Email", + description: "The email address of the contact.", + }, + orgId: { + optional: true, + propDefinition: [ + app, + "orgId", + ], + }, + firstAssistantId: { + label: "First Assistant ID", + description: "The ID of the first assistant.", + optional: true, + propDefinition: [ + app, + "assistantId", + ], + }, + lastAssistantId: { + label: "Last Assistant ID", + description: "The ID of the last assistant.", + optional: true, + propDefinition: [ + app, + "assistantId", + ], + }, + firstWidgetId: { + label: "First Widget ID", + description: "The ID of the first widget.", + optional: true, + propDefinition: [ + app, + "widgetId", + ], + }, + lastWidgetId: { + label: "Last Widget ID", + description: "The ID of the last widget.", + optional: true, + propDefinition: [ + app, + "widgetId", + ], + }, + }, + methods: { + createContact(args = {}) { + return this.app.post({ + path: "/contact", + ...args, + }); + }, + }, + async run({ $ }) { + const { + createContact, + firstName, + lastName, + email, + orgId, + firstAssistantId, + lastAssistantId, + firstWidgetId, + lastWidgetId, + } = this; + + const response = await createContact({ + $, + data: { + first_name: firstName, + last_name: lastName, + email, + org_id: orgId, + first_assistant_id: firstAssistantId, + last_assistant_id: lastAssistantId, + first_widget_id: firstWidgetId, + last_widget_id: lastWidgetId, + }, + }); + $.export("$summary", `Successfully created contact with ID \`${response.data?.id}\``); + return response; + }, +}; diff --git a/components/insighto_ai/common/constants.mjs b/components/insighto_ai/common/constants.mjs new file mode 100644 index 0000000000000..937b2cc3cdd11 --- /dev/null +++ b/components/insighto_ai/common/constants.mjs @@ -0,0 +1,11 @@ +const BASE_URL = "https://api.insighto.ai"; +const VERSION_PATH = "/api/v1"; +const LAST_CREATED_AT = "lastCreatedAt"; +const DEFAULT_MAX = 600; + +export default { + BASE_URL, + VERSION_PATH, + DEFAULT_MAX, + LAST_CREATED_AT, +}; diff --git a/components/insighto_ai/common/utils.mjs b/components/insighto_ai/common/utils.mjs new file mode 100644 index 0000000000000..22064a1364b96 --- /dev/null +++ b/components/insighto_ai/common/utils.mjs @@ -0,0 +1,39 @@ +import { ConfigurationError } from "@pipedream/platform"; + +function emptyStrToUndefined(value) { + const trimmed = typeof(value) === "string" && value.trim(); + return trimmed === "" + ? undefined + : value; +} + +function parse(value) { + const valueToParse = emptyStrToUndefined(value); + if (typeof(valueToParse) === "object" || valueToParse === undefined) { + return valueToParse; + } + try { + return JSON.parse(valueToParse); + } catch (e) { + throw new ConfigurationError("Make sure the custom expression contains a valid object"); + } +} + +async function iterate(iterations) { + const items = []; + for await (const item of iterations) { + items.push(item); + } + return items; +} + +function getNestedProperty(obj, propertyString) { + const properties = propertyString.split("."); + return properties.reduce((prev, curr) => prev && prev[curr], obj); +} + +export default { + parse, + iterate, + getNestedProperty, +}; diff --git a/components/insighto_ai/insighto_ai.app.mjs b/components/insighto_ai/insighto_ai.app.mjs new file mode 100644 index 0000000000000..2345f026323ff --- /dev/null +++ b/components/insighto_ai/insighto_ai.app.mjs @@ -0,0 +1,210 @@ +import { axios } from "@pipedream/platform"; +import constants from "./common/constants.mjs"; +import utils from "./common/utils.mjs"; + +export default { + type: "app", + app: "insighto_ai", + propDefinitions: { + assistantId: { + type: "string", + label: "Assistant ID", + description: "The ID of the assistant.", + async options({ page }) { + const { data: { items } } = await this.listAssistants({ + params: { + page: page + 1, + }, + }); + return items.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + widgetId: { + type: "string", + label: "Widget ID", + description: "The ID of the widget.", + async options({ page }) { + const { data: { items } } = await this.listWidgets({ + params: { + page: page + 1, + }, + }); + return items.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + orgId: { + type: "string", + label: "Organization ID", + description: "The ID of the organization.", + async options({ page }) { + const { data: { items } } = await this.listUserOrgs({ + params: { + page: page + 1, + }, + }); + return items.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + dataSourceId: { + type: "string", + label: "Data Source ID", + description: "The ID of the target data source.", + async options({ page }) { + const { data: { items } } = await this.listDataSources({ + params: { + page: page + 1, + }, + }); + return items.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + dataSourceType: { + type: "string", + label: "Data Source Type", + description: "The type of the data source.", + async options({ dataSourceId }) { + const { data: { ds_type: dsType } } = await this.getDataSource({ + dataSourceId, + }); + return [ + dsType, + ]; + }, + }, + }, + methods: { + getUrl(path) { + return `${constants.BASE_URL}${constants.VERSION_PATH}${path}`; + }, + getHeaders(headers) { + return { + ...headers, + "accept": "application/json", + }; + }, + getAuthParams(params) { + return { + ...params, + "api_key": this.$auth.api_key, + }; + }, + _makeRequest({ + $ = this, path, headers, params, ...args + } = {}) { + return axios($, { + ...args, + url: this.getUrl(path), + headers: this.getHeaders(headers), + params: this.getAuthParams(params), + }); + }, + post(args = {}) { + return this._makeRequest({ + method: "POST", + ...args, + }); + }, + listAssistants(args = {}) { + return this._makeRequest({ + path: "/assistant/list", + ...args, + }); + }, + listWidgets(args = {}) { + return this._makeRequest({ + path: "/widget/list", + ...args, + }); + }, + listUserOrgs(args = {}) { + return this._makeRequest({ + path: "/user/list/orgs", + ...args, + }); + }, + listDataSources(args = {}) { + return this._makeRequest({ + path: "/datasource/list", + ...args, + }); + }, + getDataSource({ + dataSourceId, ...args + } = {}) { + return this._makeRequest({ + path: `/datasource/${dataSourceId}`, + ...args, + }); + }, + listContacts(args = {}) { + return this._makeRequest({ + path: "/contact/list", + ...args, + }); + }, + async *getIterations({ + resourcesFn, resourcesFnArgs, resourceName, + max = constants.DEFAULT_MAX, + }) { + let page = 1; + let resourcesCount = 0; + + while (true) { + const response = + await resourcesFn({ + ...resourcesFnArgs, + params: { + ...resourcesFnArgs?.params, + page, + }, + }); + + const nextResources = utils.getNestedProperty(response, resourceName); + + if (!nextResources?.length) { + console.log("No more resources found"); + return; + } + + for (const resource of nextResources) { + yield resource; + resourcesCount += 1; + + if (resourcesCount >= max) { + return; + } + } + + if (resourcesCount >= response.total) { + console.log("There are no more resources to fetch"); + return; + } + + page += 1; + } + }, + paginate(args = {}) { + return utils.iterate(this.getIterations(args)); + }, + }, +}; diff --git a/components/insighto_ai/package.json b/components/insighto_ai/package.json new file mode 100644 index 0000000000000..5cd5205974819 --- /dev/null +++ b/components/insighto_ai/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/insighto_ai", + "version": "0.1.0", + "description": "Pipedream Insighto.ai Components", + "main": "insighto_ai.app.mjs", + "keywords": [ + "pipedream", + "insighto_ai" + ], + "homepage": "https://pipedream.com/apps/insighto_ai", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "1.6.5" + } +} diff --git a/components/insighto_ai/sources/common/polling.mjs b/components/insighto_ai/sources/common/polling.mjs new file mode 100644 index 0000000000000..902c28c4fb60d --- /dev/null +++ b/components/insighto_ai/sources/common/polling.mjs @@ -0,0 +1,59 @@ +import { + ConfigurationError, + DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; +import app from "../../insighto_ai.app.mjs"; +export default { + 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, + }, + }, + }, + methods: { + generateMeta() { + throw new ConfigurationError("generateMeta is not implemented"); + }, + getResourceName() { + throw new ConfigurationError("getResourceName is not implemented"); + }, + getResourcesFn() { + throw new ConfigurationError("getResourcesFn is not implemented"); + }, + getResourcesFnArgs() { + throw new ConfigurationError("getResourcesFnArgs is not implemented"); + }, + processResource(resource) { + const meta = this.generateMeta(resource); + this.$emit(resource, meta); + }, + processResources(resources) { + Array.from(resources) + .reverse() + .forEach(this.processResource); + }, + }, + async run({ $ }) { + const { + app, + getResourcesFn, + getResourcesFnArgs, + getResourceName, + processResources, + } = this; + + const resources = await app.paginate({ + resourcesFn: getResourcesFn(), + resourcesFnArgs: getResourcesFnArgs($), + resourceName: getResourceName(), + }); + + processResources(resources); + }, +}; diff --git a/components/insighto_ai/sources/new-contact/new-contact.mjs b/components/insighto_ai/sources/new-contact/new-contact.mjs new file mode 100644 index 0000000000000..bdd9375f12d5c --- /dev/null +++ b/components/insighto_ai/sources/new-contact/new-contact.mjs @@ -0,0 +1,32 @@ +import common from "../common/polling.mjs"; + +export default { + ...common, + key: "insighto_ai-new-contact", + name: "New Contact Created", + description: "Emit new event when a new contact is created in Insighto AI. [See the documentation](https://api.insighto.ai/docs#/contact/read_contact_list_api_v1_contact_list_get)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getResourceName() { + return "data.items"; + }, + getResourcesFn() { + return this.app.listContacts; + }, + getResourcesFnArgs($) { + return { + $, + }; + }, + generateMeta(resource) { + return { + id: resource.id, + summary: `New Contact: ${resource.email}`, + ts: Date.parse(resource.created_at), + }; + }, + }, +}; diff --git a/components/keycloak/keycloak.app.mjs b/components/keycloak/keycloak.app.mjs new file mode 100644 index 0000000000000..ea22458978e8d --- /dev/null +++ b/components/keycloak/keycloak.app.mjs @@ -0,0 +1,11 @@ +export default { + type: "app", + app: "keycloak", + 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/keycloak/package.json b/components/keycloak/package.json new file mode 100644 index 0000000000000..febebeeafec28 --- /dev/null +++ b/components/keycloak/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pipedream/keycloak", + "version": "0.0.1", + "description": "Pipedream Keycloak Components", + "main": "keycloak.app.mjs", + "keywords": [ + "pipedream", + "keycloak" + ], + "homepage": "https://pipedream.com/apps/keycloak", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/components/klaviyo/README.md b/components/klaviyo/README.md index 5a2c465b213ba..912c8bf1a26c2 100644 --- a/components/klaviyo/README.md +++ b/components/klaviyo/README.md @@ -9,3 +9,99 @@ The Klaviyo API grants you the power to automate and personalize your email mark - **Automate Lead Scoring Based on User Activity**: Use Pipedream to listen for webhooks from Klaviyo that indicate user actions, like email opens, link clicks, or form submissions. Combine this with data from other sources, like CRMs or analytics tools, to calculate a lead score. Update the contact in Klaviyo with this score to segment audiences and target high-scoring leads with specialized campaigns. - **Streamline Event Invitations and Follow-Ups with Zoom Webinar Registrations**: When a user registers for a webinar on Zoom, Pipedream can capture this event and create a corresponding profile in Klaviyo, automatically adding them to an event-specific list. Post-webinar, use Klaviyo to send out tailored content, such as a recording of the event, additional resources, or calls to action, based on their engagement. + +# Getting Started + +To get started using Klaviyo with Pipedream, you’ll need to create a new Klaviyo API key. + +First, log in to your Klaviyo account, then open *Settings* in the bottom left-hand corner: + +![Open the drawer in the bottom left-hand drawer to open your Klaviyo account settings](https://res.cloudinary.com/pipedreamin/image/upload/v1715178061/marketplace/apps/klayvio/CleanShot_2024-05-08_at_10.11.15_2x_cusdgp.png) + +Then, on the next page, click **Create API Key** to begin creating a new private API key. + +![Create a new Klaviyo API key](https://res.cloudinary.com/pipedreamin/image/upload/v1715178067/marketplace/apps/klayvio/CleanShot_2024-05-08_at_10.11.29_2x_uenkfm.png) + +On this page, you can configure your API key settings, such as its name and permissions. We recommend naming this API key `Pipedream` so you can easily track where it’s used. + +Next, you'll need to define the permissions for this API key. You can grant specific permissions, read-only access to all resources, or full read/write access. Don’t worry, you can change these settings later. + +![Choosing between permission levels](https://res.cloudinary.com/pipedreamin/image/upload/v1715178064/marketplace/apps/klayvio/CleanShot_2024-05-08_at_10.12.15_2x_ebxwdq.png) + +Once you have created your Klaviyo private API key, make sure to copy it to your clipboard and save it within Pipedream through either a Klaviyo trigger or action in a workflow, or by opening the dedicated Connected Accounts area in Pipedream. + +![Save the Klaviyo API key to Pipedream](https://res.cloudinary.com/pipedreamin/image/upload/v1715178053/marketplace/apps/klayvio/CleanShot_2024-05-08_at_10.19.51_2x_otps4k.png) + +Ensure you save the API key before closing the Klaviyo window, as this is the only time this private API key will be displayed. + +# Troubleshooting + +Klavyio uses standard HTTP status codes to communicate errors over it’s API. + +## Status Codes + +### 200 OK + +The request completed successfully. + +### 201 Created + +The request succeeded, and a new resource was created as a result. + +### 202 Accepted + +The request has been received but not yet acted upon. We return this status code for requests that were accepted but are processed asynchronously. + +### 204 No Content + +The request succeeded, but the API doesn’t provide a response body. + +### 400 Bad Request + +Request is missing a required parameter or has an invalid parameter. + +### 401 Not Authorized + +Request is lacking required authentication information. + +Please follow the guidance here for more details on authenticating your API requests. + +### 403 Forbidden + +The request contains valid authentication information, but does not have permissions to perform the specified action. + +See API key scopes for more information. + +### 404 Not Found + +The requested resource doesn't exist. + +### 405 Method not Allowed + +The requested resource doesn't support the provided HTTP method, e.g., DELETE. + +### 409 Conflict + +The request conflicts with the current state of the server. + +### 410 Gone + +The requested content has been permanently deleted from Klaviyo’s server. This status code will occur for requested endpoints that no longer exist in our API. + +### 415 Unsupported Media Type + +The Content-Type or Content-Encoding header is set incorrectly. + +### 429 Rate Limit + +You hit the rate limit for this endpoint (different endpoints have different rate limits). + +### 500 Server Error + +Something is wrong with the destination server. This may be on Klaviyo's end. + +### 503 Service Unavailable + +Something is wrong on Klaviyo’s end leading to service unavailability. + +Check Klaviyo’s Status for updates. diff --git a/components/kodagpt/kodagpt.app.mjs b/components/kodagpt/kodagpt.app.mjs new file mode 100644 index 0000000000000..dbb826344e59e --- /dev/null +++ b/components/kodagpt/kodagpt.app.mjs @@ -0,0 +1,11 @@ +export default { + type: "app", + app: "kodagpt", + 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/kodagpt/package.json b/components/kodagpt/package.json new file mode 100644 index 0000000000000..ab051b7be5652 --- /dev/null +++ b/components/kodagpt/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pipedream/kodagpt", + "version": "0.0.1", + "description": "Pipedream KodaGPT Components", + "main": "kodagpt.app.mjs", + "keywords": [ + "pipedream", + "kodagpt" + ], + "homepage": "https://pipedream.com/apps/kodagpt", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/components/leadiq/actions/find-contact/find-contact.mjs b/components/leadiq/actions/find-contact/find-contact.mjs new file mode 100644 index 0000000000000..cb3698b04e284 --- /dev/null +++ b/components/leadiq/actions/find-contact/find-contact.mjs @@ -0,0 +1,88 @@ +import app from "../../leadiq.app.mjs"; +import queries from "../../common/queries.mjs"; + +export default { + key: "leadiq-find-contact", + name: "Find Contact", + description: "Searches for contact information based on user-defined props which may include identifiers such as name, email, or company. Returns the contact data if a match is found within the LeadIQ database. [See the documentation](https://developer.leadiq.com/#query-searchPeople)", + version: "0.0.1", + type: "action", + props: { + app, + fullName: { + type: "string", + label: "Full Name", + description: "The full name of the contact.", + optional: true, + }, + email: { + type: "string", + label: "Email", + description: "The email of the contact", + optional: true, + }, + companyName: { + type: "string", + label: "Company Name", + description: "The name of the company.", + optional: true, + }, + searchInPastCompanies: { + type: "boolean", + label: "Search in Past Companies", + description: "If enabled, the search will include past companies.", + optional: true, + }, + }, + methods: { + searchContact({ + data, ...args + } = {}) { + return this.app.post({ + ...args, + data: { + ...data, + query: queries.searchPeople, + }, + }); + }, + }, + async run({ $ }) { + const { + searchContact, + fullName, + email, + companyName, + searchInPastCompanies, + } = this; + + const { data: { searchPeople: { results } } } = await searchContact({ + $, + data: { + variables: { + input: { + limit: 1, + fullName, + email, + ...(companyName && { + company: { + name: companyName, + searchInPastCompanies, + }, + }), + }, + }, + }, + }); + + if (!results.length) { + $.export("$summary", `No contact information found for \`${fullName || email || companyName}\``); + return { + success: false, + }; + } + + $.export("$summary", `Successfully found \`${results.length}\` contact(s)`); + return results; + }, +}; diff --git a/components/leadiq/common/queries.mjs b/components/leadiq/common/queries.mjs new file mode 100644 index 0000000000000..277865f3b2a64 --- /dev/null +++ b/components/leadiq/common/queries.mjs @@ -0,0 +1,59 @@ +export default { + searchPeople: ` + query SearchPeople($input: SearchPeopleInput!) { + searchPeople(input: $input) { + results { + _id + name { + first + fullName + last + middle + } + currentPositions { + companyId + title + updatedAt + emails { + type + status + updatedAt + value + } + companyInfo { + name + alternativeNames + domain + description + emailDomains + type + phones + country + } + } + pastPositions { + companyId + title + updatedAt + emails { + type + status + updatedAt + value + } + companyInfo { + name + alternativeNames + domain + description + emailDomains + type + phones + country + } + } + } + } + } + `, +}; diff --git a/components/leadiq/leadiq.app.mjs b/components/leadiq/leadiq.app.mjs new file mode 100644 index 0000000000000..25afabca1d7fe --- /dev/null +++ b/components/leadiq/leadiq.app.mjs @@ -0,0 +1,36 @@ +import { axios } from "@pipedream/platform"; + +export default { + type: "app", + app: "leadiq", + methods: { + _baseUrl() { + return "https://api.leadiq.com/graphql"; + }, + getAuth() { + return { + username: this.$auth.api_key, + password: "", + }; + }, + async _makeRequest({ + $ = this, ...args + } = {}) { + const response = await axios($, { + ...args, + url: this._baseUrl(), + auth: this.getAuth(), + }); + if (response?.errors?.length) { + throw new Error(JSON.stringify(response.errors)); + } + return response; + }, + post(args = {}) { + return this._makeRequest({ + method: "POST", + ...args, + }); + }, + }, +}; diff --git a/components/leadiq/package.json b/components/leadiq/package.json new file mode 100644 index 0000000000000..930b0315c1a69 --- /dev/null +++ b/components/leadiq/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/leadiq", + "version": "0.1.0", + "description": "Pipedream LeadIQ Components", + "main": "leadiq.app.mjs", + "keywords": [ + "pipedream", + "leadiq" + ], + "homepage": "https://pipedream.com/apps/leadiq", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "1.6.5" + } +} diff --git a/components/microsoft_365_planner/actions/create-bucket/create-bucket.mjs b/components/microsoft_365_planner/actions/create-bucket/create-bucket.mjs index 92a7e4a08825c..015af17e48be5 100644 --- a/components/microsoft_365_planner/actions/create-bucket/create-bucket.mjs +++ b/components/microsoft_365_planner/actions/create-bucket/create-bucket.mjs @@ -4,7 +4,7 @@ export default { key: "microsoft_365_planner-create-bucket", name: "Create Bucket", description: "Create a new bucket in Microsoft 365 Planner. [See the documentation](https://learn.microsoft.com/en-us/graph/api/planner-post-buckets)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { microsoft365Planner, diff --git a/components/microsoft_365_planner/actions/create-plan/create-plan.mjs b/components/microsoft_365_planner/actions/create-plan/create-plan.mjs index d701d751d6138..3cc81953e63b5 100644 --- a/components/microsoft_365_planner/actions/create-plan/create-plan.mjs +++ b/components/microsoft_365_planner/actions/create-plan/create-plan.mjs @@ -4,7 +4,7 @@ export default { key: "microsoft_365_planner-create-plan", name: "Create Plan", description: "Create a new plan in Microsoft 365 Planner. [See the documentation](https://learn.microsoft.com/en-us/graph/api/planner-post-plans)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { microsoft365Planner, diff --git a/components/microsoft_365_planner/actions/create-task/create-task.mjs b/components/microsoft_365_planner/actions/create-task/create-task.mjs index 5efa9b6f1c531..84bc40bc065cf 100644 --- a/components/microsoft_365_planner/actions/create-task/create-task.mjs +++ b/components/microsoft_365_planner/actions/create-task/create-task.mjs @@ -4,7 +4,7 @@ export default { key: "microsoft_365_planner-create-task", name: "Create Task", description: "Create a new task in Microsoft 365 Planner. [See the documentation](https://learn.microsoft.com/en-us/graph/api/planner-post-tasks)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { microsoft365Planner, diff --git a/components/microsoft_365_planner/actions/list-user-tasks/list-user-tasks.mjs b/components/microsoft_365_planner/actions/list-user-tasks/list-user-tasks.mjs new file mode 100644 index 0000000000000..f1a583f975e3a --- /dev/null +++ b/components/microsoft_365_planner/actions/list-user-tasks/list-user-tasks.mjs @@ -0,0 +1,21 @@ +import app from "../../microsoft_365_planner.app.mjs"; + +export default { + key: "microsoft_365_planner-list-user-tasks", + name: "List User Tasks", + description: "List all user tasks in Microsoft 365 Planner. [See the documentation](https://learn.microsoft.com/en-us/graph/api/planneruser-list-tasks?view=graph-rest-1.0&tabs=http)", + version: "0.0.1", + type: "action", + props: { + app, + }, + async run({ $ }) { + const response = await this.app.listUserTasks({ + $, + }); + + $.export("$summary", `Successfully retrieved \`${response?.value?.length}\` task(s).`); + + return response; + }, +}; diff --git a/components/microsoft_365_planner/actions/update-task/update-task.mjs b/components/microsoft_365_planner/actions/update-task/update-task.mjs new file mode 100644 index 0000000000000..f4fcf2940dc2f --- /dev/null +++ b/components/microsoft_365_planner/actions/update-task/update-task.mjs @@ -0,0 +1,192 @@ +import app from "../../microsoft_365_planner.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "microsoft_365_planner-update-task", + name: "Update Task", + description: "Updates a task in Microsoft 365 Planner. [See the documentation](https://learn.microsoft.com/en-us/graph/api/plannertask-update?view=graph-rest-1.0&tabs=http)", + version: "0.0.1", + type: "action", + props: { + app, + taskId: { + propDefinition: [ + app, + "userTaskId", + ], + }, + title: { + type: "string", + label: "Title", + description: "Title of the task", + optional: true, + }, + priority: { + type: "integer", + label: "Priority", + description: "Priority of the task. The valid range of values is between `0` and `10`, with the increasing value being lower priority (`0` has the highest priority and `10` has the lowest priority)", + optional: true, + min: 0, + max: 10, + }, + percentComplete: { + type: "integer", + label: "Percent Complete", + description: "Percentage of task completion. When set to `100`, the task is considered completed.", + optional: true, + }, + dueDateTime: { + type: "string", + label: "Due Date Time", + description: "Date and time at which the task is due. The Timestamp type represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 is `2014-01-01T00:00:00Z`.", + optional: true, + }, + startDateTime: { + type: "string", + label: "Start Date Time", + description: "Date and time at which the task starts. The Timestamp type represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 is `2014-01-01T00:00:00Z`.", + optional: true, + }, + orderHint: { + type: "string", + label: "Order Hint", + description: "Hint used to order items of this type in a list view. The format is defined in [Using order hints in Planner](https://learn.microsoft.com/en-us/graph/api/resources/planner-order-hint-format?view=graph-rest-1.0).", + optional: true, + }, + assigneePriority: { + type: "string", + label: "Assignee Priority", + description: "Hint used to order items of this type in a list view. The format is defined as outlined [here](https://learn.microsoft.com/en-us/graph/api/resources/planner-order-hint-format?view=graph-rest-1.0).", + optional: true, + }, + groupId: { + optional: true, + propDefinition: [ + app, + "groupId", + ], + }, + conversationThreadId: { + propDefinition: [ + app, + "conversationThreadId", + ({ groupId }) => ({ + groupId, + }), + ], + }, + assignmentIds: { + propDefinition: [ + app, + "assigneeIds", + ({ groupId }) => ({ + groupId, + }), + ], + }, + planId: { + optional: true, + propDefinition: [ + app, + "planId", + ({ groupId }) => ({ + groupId, + }), + ], + }, + bucketId: { + propDefinition: [ + app, + "bucketId", + ({ planId }) => ({ + planId, + }), + ], + }, + appliedCategories: { + type: "string[]", + label: "Applied Categories", + description: "The categories to which the task has been applied. See [applied Categories](https://learn.microsoft.com/en-us/graph/api/resources/plannerappliedcategories?view=graph-rest-1.0) for possible values.", + optional: true, + options: Array.from({ + length: 6, + }, (_, idx) => `category${idx + 1}`), + }, + }, + methods: { + getAppliedCategories(appliedCategories = []) { + return utils.parseArray(appliedCategories)?.reduce((acc, category) => ({ + ...acc, + [category]: true, + }), {}); + }, + getAssignments(assignmentIds = []) { + return utils.parseArray(assignmentIds)?.reduce((acc, id) => ({ + ...acc, + [id]: { + "@odata.type": "microsoft.graph.plannerAssignment", + "orderHint": " !", + }, + }), {}); + }, + updateTask({ + taskId, ...args + } = {}) { + return this.app._makeRequest({ + method: "PATCH", + path: `/planner/tasks/${taskId}`, + ...args, + }); + }, + }, + async run({ $ }) { + const { + app, + getAssignments, + getAppliedCategories, + updateTask, + taskId, + title, + priority, + percentComplete, + startDateTime, + dueDateTime, + assigneePriority, + conversationThreadId, + assignmentIds, + bucketId, + appliedCategories, + } = this; + + const { ["@odata.etag"]: etag } = await app.getTask({ + $, + taskId, + }); + + const response = await updateTask({ + $, + taskId, + headers: { + "Content-Type": "application/json", + "If-Match": etag, + "Prefer": "return=representation", + }, + data: { + title, + priority, + percentComplete, + startDateTime, + dueDateTime, + assigneePriority, + conversationThreadId, + bucketId, + assignments: getAssignments(assignmentIds), + appliedCategories: getAppliedCategories(appliedCategories), + }, + }); + + $.export("$summary", `Successfully updated task with ID \`${response.id}\`.`); + + return response; + }, +}; diff --git a/components/microsoft_365_planner/common/utils.mjs b/components/microsoft_365_planner/common/utils.mjs new file mode 100644 index 0000000000000..d9df0aa935aef --- /dev/null +++ b/components/microsoft_365_planner/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/microsoft_365_planner/microsoft_365_planner.app.mjs b/components/microsoft_365_planner/microsoft_365_planner.app.mjs index 18339bff36a62..36bc46e1a2ba9 100644 --- a/components/microsoft_365_planner/microsoft_365_planner.app.mjs +++ b/components/microsoft_365_planner/microsoft_365_planner.app.mjs @@ -156,25 +156,57 @@ export default { }; }, }, + userTaskId: { + type: "string", + label: "User Task ID", + description: "Identifier of a user task", + async options() { + const response = await this.listUserTasks(); + return response.value?.map(({ + id: value, title: label, + }) => ({ + value, + label, + })); + }, + }, + conversationThreadId: { + type: "string", + label: "Conversation Thread ID", + description: "Identifier of the conversation thread associated with the task.", + optional: true, + async options({ groupId }) { + if (!groupId) { + return []; + } + const response = await this.listThreads({ + groupId, + }); + return response.value?.map(({ + id: value, topic: label, + }) => ({ + value, + label, + })); + }, + }, }, methods: { _baseUrl() { return "https://graph.microsoft.com/v1.0"; }, - _headers() { + _headers(headers) { return { + ...headers, Authorization: `Bearer ${this.$auth.oauth_access_token}`, }; }, _makeRequest({ - $ = this, - path, - url, - ...args + $ = this, path, url, headers, ...args }) { return axios($, { url: url || `${this._baseUrl()}${path}`, - headers: this._headers(), + headers: this._headers(headers), ...args, }); }, @@ -237,6 +269,28 @@ export default { ...args, }); }, + getTask({ + taskId, ...args + }) { + return this._makeRequest({ + path: `/planner/tasks/${taskId}`, + ...args, + }); + }, + listUserTasks(args = {}) { + return this._makeRequest({ + path: "/me/planner/tasks", + ...args, + }); + }, + listThreads({ + groupId, ...args + }) { + return this._makeRequest({ + path: `/groups/${groupId}/threads`, + ...args, + }); + }, async *paginate({ fn, args, }) { diff --git a/components/microsoft_365_planner/package.json b/components/microsoft_365_planner/package.json index 696e6a90b9e13..0d4ebb4e144f2 100644 --- a/components/microsoft_365_planner/package.json +++ b/components/microsoft_365_planner/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/microsoft_365_planner", - "version": "0.1.0", + "version": "0.2.0", "description": "Pipedream Microsoft 365 Planner Components", "main": "microsoft_365_planner.app.mjs", "keywords": [ @@ -13,6 +13,6 @@ "access": "public" }, "dependencies": { - "@pipedream/platform": "^1.5.1" + "@pipedream/platform": "^1.6.5" } } diff --git a/components/microsoft_365_planner/sources/new-plan-created/new-plan-created.mjs b/components/microsoft_365_planner/sources/new-plan-created/new-plan-created.mjs index 8b31b449d0e9b..87695e7d73751 100644 --- a/components/microsoft_365_planner/sources/new-plan-created/new-plan-created.mjs +++ b/components/microsoft_365_planner/sources/new-plan-created/new-plan-created.mjs @@ -5,7 +5,7 @@ export default { key: "microsoft_365_planner-new-plan-created", name: "New Plan Created", description: "Emit new event when a new Plan is created in Microsoft 365 Planner", - version: "0.0.1", + version: "0.0.2", type: "source", dedupe: "unique", props: { diff --git a/components/microsoft_365_planner/sources/new-task-created/new-task-created.mjs b/components/microsoft_365_planner/sources/new-task-created/new-task-created.mjs index 43ad5b3cf5cf8..c6cc9cb743802 100644 --- a/components/microsoft_365_planner/sources/new-task-created/new-task-created.mjs +++ b/components/microsoft_365_planner/sources/new-task-created/new-task-created.mjs @@ -5,7 +5,7 @@ export default { key: "microsoft_365_planner-new-task-created", name: "New Task Created", description: "Emit new event when a new Task is created in Microsoft 365 Planner", - version: "0.0.1", + version: "0.0.2", type: "source", dedupe: "unique", props: { diff --git a/components/mongodb/README.md b/components/mongodb/README.md index 6a8d1c82e0c97..4a478f9738bf3 100644 --- a/components/mongodb/README.md +++ b/components/mongodb/README.md @@ -9,3 +9,67 @@ The MongoDB API provides powerful capabilities to interact with a MongoDB databa - **Automated Backup Notifications via Email**: Set up a daily database backup and use Pipedream to monitor the completion status of the backup operation. Once a backup is successfully completed, trigger an SMTP action to send an email notification to the system admin, ensuring peace of mind with regular backup status updates. - **Slack Alerts for High-Value Transactions**: Monitor your MongoDB for transactions exceeding a certain value and send alerts to a Slack channel when such transactions occur. Finance teams can stay on top of significant movements without manual database checks, improving response time and financial oversight. + +# Getting Started + +To get started, you need to set up a user to connect to your MongoDB cluster. This guide assumes your MongoDB cluster is hosted on Mongo Atlas. + +First, [log in to Mongo Atlas](https://account.mongodb.com/account/login) to complete the next steps. + +## Create the MongoDB User + +First, create a dedicated user for connecting MongoDB to Pipedream. + +Select the MongoDB project where your cluster resides, then select the *Database Access* tab. Here, you'll see a list of users who can connect to your MongoDB cluster. + +Click *Create new database user* to get started. + +![Open the MongoDB project from within MongoDB Atlas and select the Database Access panel, then create the user](https://res.cloudinary.com/pipedreamin/image/upload/v1715025845/marketplace/apps/mongodb/CleanShot_2024-05-06_at_16.02.42_apamii.png) + +In the new pop-up, choose the *Password* authentication method first. Name the user `pipedream` so it’s easy to remember which service this user account is used for. + +Generate a password and select a pre-built or custom role. Pre-built roles offer options like read-only or read and write access to all databases in the cluster. For more fine-tuned control, like restricting Pipedream to reading a single database, use a custom role. + +Finally click *Add user* to create this new user account. + +![Filling out the details of the new user account in MongoDB Atlas for connecting this new user to Pipedream](https://res.cloudinary.com/pipedreamin/image/upload/v1715026023/marketplace/apps/mongodb/CleanShot_2024-05-06_at_16.06.22_rfz4ur.png) + +## Provide the Credentials + +Now, with a dedicated MongoDB user, connect to Pipedream. Return to Pipedream to set up a new MongoDB action in a workflow or to add your MongoDB account in the Connected Accounts area. + +Copy and paste the *Username* and *Password* fields from the previous step, when you created the `pipedream` user in Mongo Atlas. + +To find your database hostname, go to the **Databases** view in Mongo Atlas and click the **Connect** button for your chosen database. + +Select the **Compass or Node.js** option. Then you’ll see the connection URI string. The hostname is the latter portion of the string, as shown below: + +![Finding the MongoDB database host name in MongoDB Atlas by using the Connect button to show the connection URI](https://res.cloudinary.com/pipedreamin/image/upload/v1715026375/marketplace/apps/mongodb/CleanShot_2024-05-06_at_16.12.19_sgr7wd.png) + +Lastly, provide the **Database** name to connect to. + +## Allowing Pipedream Connections + +By default, Pipedream workflows can start anywhere within the `us-east-1` AWS IP range. You'll need to expose your MongoDB Atlas Cluster to the internet or use a Pipedream VPC to assign a specific outbound IP address to your workflows. + +### Allowing any IP Address Range + +First, open the **Network Access** section of your MongoDB Atlas project. Then click **Add new IP address** in the top right-hand corner: + +![Add a new IP address to the allowed list of IP addresses within MongoDB Atlas](https://res.cloudinary.com/pipedreamin/image/upload/v1715026727/marketplace/apps/mongodb/CleanShot_2024-05-06_at_16.18.24_a3u6y0.png) + +Then enter `0.0.0.0/0` in the IP address field to allow connections from any IP address. In the description field, we recommend adding a note about this requirement for Pipedream. + +![Allowing any IP address to connect to your MongoDB Atlas Database Cluster. Necessary for services like Pipedream workflows that don’t have a static IP address unless you’re using Pipedream VPCs.](https://res.cloudinary.com/pipedreamin/image/upload/v1715026860/marketplace/apps/mongodb/CleanShot_2024-05-06_at_16.20.51_ixxmoa.png) + +Finally, click **Confirm** + +### Using a Pipedream VPC + +Pipedream VPCs allow you to define a single static IP address for your workflows and allow you to run these workflows in a separate network. + +[Follow this guide to create a VPC and assign the IP address to your workflow.](https://pipedream.com/docs/workflows/vpc) + +Then create a new allowed IP address in MongoDB by opening the **Network Access** section in Atlas: + +![Add a new IP address to the allowed list of IP addresses within MongoDB Atlas](https://res.cloudinary.com/pipedreamin/image/upload diff --git a/components/mongodb/actions/execute-aggregation/execute-aggregation.mjs b/components/mongodb/actions/execute-aggregation/execute-aggregation.mjs new file mode 100644 index 0000000000000..47c731cb8266b --- /dev/null +++ b/components/mongodb/actions/execute-aggregation/execute-aggregation.mjs @@ -0,0 +1,64 @@ +import app from "../../mongodb.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "mongodb-execute-aggregation", + name: "Execute Aggregation", + description: "Execute an aggregation pipeline on a MongoDB collection. [See the documentation](https://www.mongodb.com/docs/drivers/node/current/fundamentals/aggregation/)", + version: "0.0.1", + type: "action", + props: { + app, + database: { + propDefinition: [ + app, + "database", + ], + }, + collectionName: { + propDefinition: [ + app, + "collection", + ({ database }) => ({ + database, + }), + ], + }, + pipeline: { + type: "string[]", + label: "Pipeline", + description: "The aggregation pipeline to execute where each row represents a stage as a JSON string. Eg. `[ { \"$match\": { \"categories\": \"Bakery\" } }, { \"$group\": { \"_id\": \"$stars\", \"count\": { \"$sum\": 1 } } } ]`", + }, + }, + methods: { + async excecuteAggregation({ + database, collectionName, pipeline, + } = {}) { + const { app } = this; + const client = await app.getClient(); + const collection = app.getCollection(client, database, collectionName); + const cursor = collection.aggregate(pipeline); + const result = await utils.iterate(cursor); + await client.close(); + return result; + }, + }, + async run({ $ }) { + const { + excecuteAggregation, + database, + collectionName, + pipeline, + } = this; + + const response = await excecuteAggregation({ + database, + collectionName, + pipeline: utils.parseArray(pipeline), + }); + + $.export("$summary", `Successfully executed aggregation pipeline on collection with \`${response.length}\` document(s).`); + + return response; + }, +}; diff --git a/components/mongodb/actions/find-document/find-document.mjs b/components/mongodb/actions/find-document/find-document.mjs new file mode 100644 index 0000000000000..3286b349f851b --- /dev/null +++ b/components/mongodb/actions/find-document/find-document.mjs @@ -0,0 +1,75 @@ +import app from "../../mongodb.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "mongodb-find-document", + name: "Find Document", + description: "Finds a document by a query filter. [See the documentation](https://docs.mongodb.com/manual/reference/method/db.collection.find/)", + version: "0.0.1", + type: "action", + props: { + app, + database: { + propDefinition: [ + app, + "database", + ], + }, + collectionName: { + propDefinition: [ + app, + "collection", + ({ database }) => ({ + database, + }), + ], + }, + filter: { + type: "string", + label: "Filter", + description: "JSON string to use as a filter. Eg. `{ \"name\": \"Twitter\" }`", + }, + options: { + type: "string", + label: "Options", + description: "JSON string to use as options. Eg. `{ \"projection\": { \"_id\": 0, \"title\": 1 } }`", + optional: true, + }, + }, + methods: { + async findDocument({ + database, collectionName, filter, options, + } = {}) { + const { app } = this; + const client = await app.getClient(); + const collection = app.getCollection(client, database, collectionName); + const result = await collection.findOne(filter, options); + await client.close(); + return result; + }, + }, + async run({ $ }) { + const { + findDocument, + database, + collectionName, + filter, + options, + } = this; + + const response = await findDocument({ + database, + collectionName, + filter: utils.valueToObject(filter), + options: utils.valueToObject(options), + }); + + if (!response) { + $.export("$summary", "Document not found in the collection."); + return; + } + + $.export("$summary", "Successfully found a document in the collection."); + return response; + }, +}; diff --git a/components/mongodb/actions/update-documents/update-documents.mjs b/components/mongodb/actions/update-documents/update-documents.mjs new file mode 100644 index 0000000000000..5ad3bec2acaf6 --- /dev/null +++ b/components/mongodb/actions/update-documents/update-documents.mjs @@ -0,0 +1,69 @@ +import app from "../../mongodb.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "mongodb-update-documents", + name: "Update Documents", + description: "Updates many documents by query filter. [See the documentation](https://www.mongodb.com/docs/drivers/node/current/usage-examples/updateMany/)", + version: "0.0.1", + type: "action", + props: { + app, + database: { + propDefinition: [ + app, + "database", + ], + }, + collectionName: { + propDefinition: [ + app, + "collection", + ({ database }) => ({ + database, + }), + ], + }, + filter: { + type: "string", + label: "Filter", + description: "JSON string to use as a filter. Eg. `{ \"rated\": \"G\" }`", + }, + data: { + label: "Data To Update", + type: "string", + description: "JSON data to use as the update. Eg. `{ \"$set\": { \"rating\": \"Everyone\" } }`", + }, + }, + methods: { + async updateDocuments({ + database, collectionName, filter, data, + } = {}) { + const { app } = this; + const client = await app.getClient(); + const collection = app.getCollection(client, database, collectionName); + const result = await collection.updateMany(filter, data); + await client.close(); + return result; + }, + }, + async run({ $ }) { + const { + updateDocuments, + database, + collectionName, + filter, + data, + } = this; + + const response = await updateDocuments({ + database, + collectionName, + filter: utils.valueToObject(filter), + data: utils.valueToObject(data), + }); + + $.export("$summary", `Successfully updated \`${response.modifiedCount}\` document(s) in the collection.`); + return response; + }, +}; diff --git a/components/mongodb/common/utils.mjs b/components/mongodb/common/utils.mjs new file mode 100644 index 0000000000000..16af2678bc5b2 --- /dev/null +++ b/components/mongodb/common/utils.mjs @@ -0,0 +1,63 @@ +import { ConfigurationError } from "@pipedream/platform"; + +function isJson(value) { + if (typeof(value) !== "string") { + return false; + } + + try { + JSON.parse(value); + } catch (e) { + return false; + } + + return true; +} + +function valueToObject(value) { + if (value === undefined || typeof(value) === "object") { + return value; + } + + if (!isJson(value)) { + throw new ConfigurationError(`Make sure the value contains a valid JSON string: ${value}`); + } + return JSON.parse(value); +} + +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 value contains a valid JSON array"); + } +} + +async function iterate(iterations) { + const items = []; + for await (const item of iterations) { + items.push(item); + } + return items; +} + +export default { + valueToObject, + parseArray: (value) => parseArray(value).map(valueToObject), + iterate, +}; diff --git a/components/mongodb/package.json b/components/mongodb/package.json index 7152f670adbcf..975efc996701c 100644 --- a/components/mongodb/package.json +++ b/components/mongodb/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/mongodb", - "version": "0.0.8", + "version": "0.1.0", "description": "Pipedream MongoDB Components", "main": "mongodb.app.mjs", "keywords": [ @@ -14,7 +14,7 @@ "access": "public" }, "dependencies": { - "@pipedream/platform": "^1.2.0", + "@pipedream/platform": "^1.6.5", "mongodb": "^4.6.0" } } diff --git a/components/notion/README.md b/components/notion/README.md index a6b0ad0b1a91f..8917a45a49490 100644 --- a/components/notion/README.md +++ b/components/notion/README.md @@ -1,17 +1,81 @@ # Overview -Notion's API allows for the creation, reading, updating, and deleting of pages, databases, and their contents within Notion. Leveraging Pipedream's platform, you can build workflows that connect Notion with a myriad of other services to automate tasks such as content management, task tracking, and data synchronization. With Pipedream's serverless execution, you can trigger these workflows on a schedule, or by external events from other services, without managing any infrastructure. +Notion's API allows for the creation, reading, updating, and deleting of pages, databases, and their contents within Notion. Using Pipedream's platform, you can build workflows that connect Notion with various other services to automate tasks such as content management, task tracking, and data synchronization. With Pipedream's serverless execution, you can trigger these workflows on a schedule, or by external events from other services, without managing any infrastructure. # Example Use Cases -- **Content Sync Between Notion and a CMS**: Automatically push new blog posts from a Notion database to a CMS like WordPress, ensuring a seamless content flow from drafting to publishing. +- **Content Sync Between Notion and a CMS**: Automatically push new blog posts from Notion to WordPress, ensuring a seamless content flow from drafting to publishing. - **Task Management with Todoist Integration**: When a new task is added to a Notion database, create a corresponding task in Todoist, and vice versa, keeping task lists synced across platforms. - **Daily Sales Report Generation**: Gather sales data from a tool like Shopify at the end of each day, sum up total sales, and create a page in Notion with the day's sales summary for easy reporting. +# Getting Started + +To get started, first log in to or create your [Pipedream account](https://pipedream.com) and start a new workflow. + +Add a Notion action or trigger to your workflow, then click **Select a Notion account** to open a Notion connection window: + +![Selecting your Notion API account](https://res.cloudinary.com/pipedreamin/image/upload/v1715267108/marketplace/apps/notion/CleanShot_2024-05-09_at_11.04.34_pdb2rx.png) + +From within this window, select pages you'd like Pipedream to access: + +![Select the Notion pages that you'd like Pipedream to have access to](https://res.cloudinary.com/pipedreamin/image/upload/v1715267109/marketplace/apps/notion/CleanShot_2024-05-09_at_11.04.44_awccry.png) + +Click **Accept** to connect your Notion account to Pipedream. + # Troubleshooting -Note: After creating a new database, please reconnect your account and select it to enable access to Pipedream. +## Unable to find a new database + +After creating a new database, reconnect your account and select it to enable Pipedream access. + +## HTTP errors + +### 400 "invalid_json" +Ensure the request body is formatted as valid JSON. + +### 400 "invalid_request_url" +The URL in your request is incorrect. Ensure the correct format and parameters. + +### 400 "invalid_request" +The request type isn't supported. Ensure the correct format and parameters. + +### 400 "invalid_grant" +Your authorization grant or refresh token is invalid, expired, or mismatched. Refer to OAuth 2.0 documentation. + +### 400 "validation_error" +The request body doesn't match the expected parameters schema. You're most likely missing a key parameter, or a parameter is malformed. + +### 400 "missing_version" +If your request lacks the required `Notion-Version` header, try adding a new Notion step in Pipedream, as it should handle this automatically. + +### 401 "unauthorized" +If your API token is invalid, reconnect your Notion account to Pipedream. + +### 403 "restricted_resource" +Your API token doesn't have permission to perform this operation. Try reconnecting your Notion account to Pipedream. + +### 404 "object_not_found" +The requested resource doesn't exist or isn't shared with your API token. Double check that the block, database or page exists. + +### 409 "conflict_error" +A conflict occurred, possibly due to outdated parameters. Try updating your parameters. + +### 429 "rate_limited" +You've exceeded the number of allowed requests. Slow down your request rate. + +### 500 "internal_server_error" +An unexpected error has occurred. Contact Notion support if it persists. + +### 502 "bad_gateway" +Notion had a problem processing your request, possibly due to an upstream server issue. + +### 503 "service_unavailable" +Notion is temporarily unavailable, possibly due to a long response time. Try again later. + +### 503 "database_connection_unavailable" +Notion's database cannot be queried at the moment. Try again later. -Please [reach out](https://pipedream.com/support/) to the Pipedream team with any technical issues or questions about the Notion integration. We're happy to help! +### 504 "gateway_timeout" +Your request to Notion timed out. Try resending it after a while. diff --git a/components/orimon/actions/send-message/send-message.mjs b/components/orimon/actions/send-message/send-message.mjs new file mode 100644 index 0000000000000..085726cf5f592 --- /dev/null +++ b/components/orimon/actions/send-message/send-message.mjs @@ -0,0 +1,43 @@ +import orimon from "../../orimon.app.mjs"; + +export default { + key: "orimon-send-message", + name: "Send Message to Orimon", + description: "Sends a direct message to Orimon. [See the documentation](https://orimon.gitbook.io/docs/developer-api/message-api)", + version: "0.0.1", + type: "action", + props: { + orimon, + text: { + type: "string", + label: "Message Text", + description: "The text message you want Orimon to receive.", + }, + }, + async run({ $ }) { + const tenantId = this.orimon.$auth.tenant_id; + + const response = await this.orimon.sendMessage({ + $, + data: { + type: "message", + info: { + psid: `${Date.parse(new Date())}_${tenantId}`, + sender: "user", + tenantId: tenantId, + platformName: "web", + }, + message: { + id: tenantId, + type: "text", + payload: { + text: this.text, + }, + }, + }, + }); + + $.export("$summary", "Message sent successfully to Orimon!"); + return response; + }, +}; diff --git a/components/orimon/orimon.app.mjs b/components/orimon/orimon.app.mjs index 848a461e9cd4f..ef3284d6ef2a6 100644 --- a/components/orimon/orimon.app.mjs +++ b/components/orimon/orimon.app.mjs @@ -1,11 +1,32 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "orimon", - propDefinitions: {}, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://channel-connector.orimon.ai/orimon/v1"; + }, + _headers() { + return { + "Authorization": `apiKey ${this.$auth.api_key}`, + }; + }, + _makeRequest({ + $ = this, path = "/", ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + headers: this._headers(), + ...opts, + }); + }, + sendMessage(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/conversation/api/message", + ...opts, + }); }, }, -}; \ No newline at end of file +}; diff --git a/components/orimon/package.json b/components/orimon/package.json index d191d066b9a83..84c6209c9806c 100644 --- a/components/orimon/package.json +++ b/components/orimon/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/orimon", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Orimon Components", "main": "orimon.app.mjs", "keywords": [ @@ -11,5 +11,9 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^1.6.2" } -} \ No newline at end of file +} + diff --git a/components/overledger/README.md b/components/overledger/README.md new file mode 100644 index 0000000000000..48a3966747e7a --- /dev/null +++ b/components/overledger/README.md @@ -0,0 +1,11 @@ +# Overview + +The Overledger API by Quant Network allows developers to interact with multiple blockchain networks. It facilitates the creation, reading, and execution of smart contracts and transactions across different blockchains. This API simplifies the complex nature of blockchain interoperability, enabling multi-chain applications (MApps) through a single gateway. + +# Example Use Cases + +- **Automated Multi-chain Financial Reporting**: Build a workflow on Pipedream that triggers monthly, pulling transaction data from various blockchains via the Overledger API. Integrate this data with Google Sheets or a similar app on Pipedream to generate consolidated financial reports. + +- **Cross-Blockchain Event Monitoring**: Set up a Pipedream workflow to monitor specific events (like transactions or smart contract interactions) across multiple blockchains using Overledger. This can trigger notifications via Slack or email when predefined conditions are met, such as unusual transaction volumes or large transfers. + +- **Decentralized Application (DApp) Management**: Develop a workflow in Pipedream that utilizes Overledger to deploy and manage smart contracts across different blockchains. The workflow could, for instance, automatically update or verify contracts on Ethereum and Binance Smart Chain when a GitHub repository is updated, using a combination of Overledger and GitHub triggers on Pipedream. diff --git a/components/perplexity/README.md b/components/perplexity/README.md new file mode 100644 index 0000000000000..6be2fba8fd8c7 --- /dev/null +++ b/components/perplexity/README.md @@ -0,0 +1,11 @@ +# Overview + +The Perplexity API offers advanced natural language processing capabilities, enabling users to generate answers, summaries, and insights from texts. Leveraging this API on Pipedream allows for the automation of content analysis, intelligent alert systems, and dynamic data enrichment, integrating seamlessly with various data sources and services for real-time processing. + +# Example Use Cases + +- **Customer Support Automation**: Automatically handle incoming customer queries by setting up a workflow where support emails trigger the Perplexity API to analyze and generate responses. Connect this with a customer relationship management (CRM) app on Pipedream to log interactions and follow-ups. + +- **Content Summarization for News Articles**: Streamline the process of summarizing news articles by creating a workflow where new articles fetched from RSS feeds are passed to the Perplexity API for summarization. The summaries can then be automatically posted to a company’s internal communication tool like Slack. + +- **Real-time Social Media Monitoring**: Develop a workflow to monitor social media for brand mentions. Use the Perplexity API to assess sentiment and extract key phrases from posts. This data can be piped into a data visualization tool on Pipedream, like Google Sheets, for analysis and reporting. diff --git a/components/perplexity/actions/chat-completions/chat-completions.mjs b/components/perplexity/actions/chat-completions/chat-completions.mjs new file mode 100644 index 0000000000000..29ca8a907eaf9 --- /dev/null +++ b/components/perplexity/actions/chat-completions/chat-completions.mjs @@ -0,0 +1,48 @@ +import app from "../../perplexity.app.mjs"; + +export default { + key: "perplexity-chat-completions", + name: "Chat Completions", + description: "Generates a model's response for the given chat conversation. [See the documentation](https://docs.perplexity.ai/reference/post_chat_completions)", + version: "0.0.2", + type: "action", + props: { + app, + model: { + propDefinition: [ + app, + "model", + ], + }, + content: { + propDefinition: [ + app, + "content", + ], + }, + role: { + propDefinition: [ + app, + "role", + ], + }, + }, + async run({ $ }) { + const response = await this.app.chatCompletions({ + $, + data: { + model: this.model, + messages: [ + { + role: this.role, + content: this.content, + }, + ], + }, + }); + + $.export("$summary", "Successfully generated a response from the selected model"); + + return response; + }, +}; diff --git a/components/perplexity/common/constants.mjs b/components/perplexity/common/constants.mjs new file mode 100644 index 0000000000000..8e5be428ec334 --- /dev/null +++ b/components/perplexity/common/constants.mjs @@ -0,0 +1,16 @@ +export default { + MODELS: [ + "llama-3-sonar-small-32k-chat", + "llama-3-sonar-small-32k-online", + "llama-3-sonar-large-32k-chat", + "llama-3-sonar-large-32k-online", + "llama-3-8b-instruct", + "llama-3-70b-instruct", + "mixtral-8x7b-instruct", + ], + ROLES: [ + "system", + "user", + "assistant", + ], +}; diff --git a/components/perplexity/package.json b/components/perplexity/package.json index 9e6ad06f0b0bd..777d1932bc8e4 100644 --- a/components/perplexity/package.json +++ b/components/perplexity/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/perplexity", - "version": "0.0.1", + "version": "0.1.1", "description": "Pipedream Perplexity Components", "main": "perplexity.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^1.6.5" } -} \ No newline at end of file +} diff --git a/components/perplexity/perplexity.app.mjs b/components/perplexity/perplexity.app.mjs index 8cb047fa417ed..b7cba4a78ee95 100644 --- a/components/perplexity/perplexity.app.mjs +++ b/components/perplexity/perplexity.app.mjs @@ -1,11 +1,54 @@ +import { axios } from "@pipedream/platform"; +import constants from "./common/constants.mjs"; + export default { type: "app", app: "perplexity", - propDefinitions: {}, + propDefinitions: { + model: { + type: "string", + label: "Model", + description: "The name of the model that will complete your prompt", + options: constants.MODELS, + }, + content: { + type: "string", + label: "Content", + description: "The contents of the message in this turn of conversation", + }, + role: { + type: "string", + label: "Role", + description: "The role of the speaker in this turn of conversation. After the (optional) system message, user and assistant roles should alternate with 'user' then 'assistant', ending in 'user'.", + options: constants.ROLES, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.perplexity.ai"; + }, + async _makeRequest(opts = {}) { + const { + $ = this, + path, + headers, + ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + url: this._baseUrl() + path, + headers: { + ...headers, + Authorization: `Bearer ${this.$auth.api_key}`, + }, + }); + }, + async chatCompletions(args = {}) { + return this._makeRequest({ + method: "post", + path: "/chat/completions", + ...args, + }); }, }, -}; \ No newline at end of file +}; diff --git a/components/pidj/package.json b/components/pidj/package.json new file mode 100644 index 0000000000000..a89e4b3ad710b --- /dev/null +++ b/components/pidj/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pipedream/pidj", + "version": "0.0.1", + "description": "Pipedream Pidj Components", + "main": "pidj.app.mjs", + "keywords": [ + "pipedream", + "pidj" + ], + "homepage": "https://pipedream.com/apps/pidj", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/components/pidj/pidj.app.mjs b/components/pidj/pidj.app.mjs new file mode 100644 index 0000000000000..6041c775ac137 --- /dev/null +++ b/components/pidj/pidj.app.mjs @@ -0,0 +1,11 @@ +export default { + type: "app", + app: "pidj", + 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/pinecone/actions/delete-vectors/delete-vectors.mjs b/components/pinecone/actions/delete-vectors/delete-vectors.mjs index 9855c6e3b85b3..0e38590a69832 100644 --- a/components/pinecone/actions/delete-vectors/delete-vectors.mjs +++ b/components/pinecone/actions/delete-vectors/delete-vectors.mjs @@ -4,9 +4,9 @@ import utils from "../../common/utils.mjs"; export default { key: "pinecone-delete-vectors", name: "Delete Vectors", - description: "Deletes one or more vectors by ID, from a single namespace. [See the docs](https://docs.pinecone.io/reference/delete_post).", + description: "Deletes one or more vectors by ID, from a single namespace. [See the documentation](https://docs.pinecone.io/reference/api/data-plane/delete).", type: "action", - version: "0.0.1", + version: "0.0.2", props: { app, indexName: { @@ -15,10 +15,16 @@ export default { "indexName", ], }, - projectId: { + prefix: { propDefinition: [ app, - "projectId", + "prefix", + ], + }, + namespace: { + propDefinition: [ + app, + "namespace", ], }, ids: { @@ -28,24 +34,12 @@ export default { propDefinition: [ app, "vectorId", - ({ - indexName, projectId, - }) => ({ - indexName, - projectId, - }), - ], - }, - namespace: { - propDefinition: [ - app, - "namespace", ], }, }, methods: { deleteVector(args = {}) { - return this.app.create({ + return this.app.post({ path: "/vectors/delete", ...args, }); @@ -53,14 +47,14 @@ export default { }, async run({ $: step }) { const { + deleteVector, indexName, - projectId, ids, namespace, } = this; - await this.deleteVector({ - projectId, + await deleteVector({ + step, indexName, data: { ids: utils.parseArray(ids), @@ -69,5 +63,8 @@ export default { }); step.export("$summary", "Successfully deleted vectors"); + return { + success: true, + }; }, }; diff --git a/components/pinecone/actions/fetch-vectors/fetch-vectors.mjs b/components/pinecone/actions/fetch-vectors/fetch-vectors.mjs index 54d1a9b50f3c7..bdd62171b2c79 100644 --- a/components/pinecone/actions/fetch-vectors/fetch-vectors.mjs +++ b/components/pinecone/actions/fetch-vectors/fetch-vectors.mjs @@ -4,9 +4,9 @@ import app from "../../pinecone.app.mjs"; export default { key: "pinecone-fetch-vectors", name: "Fetch Vectors", - description: "Looks up and returns vectors by ID, from a single namespace.. [See the docs](https://docs.pinecone.io/reference/fetch).", + description: "Looks up and returns vectors by ID, from a single namespace.. [See the documentation](https://docs.pinecone.io/reference/api/data-plane/fetch).", type: "action", - version: "0.0.1", + version: "0.0.2", props: { app, indexName: { @@ -15,10 +15,10 @@ export default { "indexName", ], }, - projectId: { + namespace: { propDefinition: [ app, - "projectId", + "namespace", ], }, ids: { @@ -28,31 +28,27 @@ export default { propDefinition: [ app, "vectorId", - ({ - indexName, projectId, - }) => ({ - indexName, - projectId, - }), ], }, - namespace: { - propDefinition: [ - app, - "namespace", - ], + }, + methods: { + fetchVectors(args = {}) { + return this.app.makeRequest({ + path: "/vectors/fetch", + ...args, + }); }, }, async run({ $: step }) { const { + fetchVectors, indexName, - projectId, ids, namespace, } = this; - const response = await this.app.fetchVectors({ - projectId, + const response = await fetchVectors({ + step, indexName, params: { ids: utils.parseArray(ids), @@ -60,7 +56,7 @@ export default { }, }); - step.export("$summary", `Successfully fetched ${Object.keys(response?.vectors).length} vectors.`); + step.export("$summary", `Successfully fetched \`${Object.keys(response.vectors).length}\` vector(s).`); return response; }, diff --git a/components/pinecone/actions/query-ids/query-ids.mjs b/components/pinecone/actions/query-ids/query-ids.mjs index 5f99a0a86e449..ab4267a8a658c 100644 --- a/components/pinecone/actions/query-ids/query-ids.mjs +++ b/components/pinecone/actions/query-ids/query-ids.mjs @@ -4,9 +4,9 @@ import utils from "../../common/utils.mjs"; export default { key: "pinecone-query-ids", name: "Query IDs", - description: "Searches a namespace, using a query vector. It retrieves the ids of the most similar items in a namespace, along with their similarity scores. [See the docs](https://docs.pinecone.io/reference/query).", + description: "Searches a namespace, using a query vector. It retrieves the ids of the most similar items in a namespace, along with their similarity scores. [See the documentation](https://docs.pinecone.io/reference/api/data-plane/query).", type: "action", - version: "0.0.1", + version: "0.0.2", props: { app, indexName: { @@ -15,12 +15,6 @@ export default { "indexName", ], }, - projectId: { - propDefinition: [ - app, - "projectId", - ], - }, topK: { type: "integer", label: "Top K", @@ -32,7 +26,7 @@ export default { filter: { type: "object", label: "Filter", - description: "The filter to apply. You can use vector metadata to limit your search. [See the docs](https://www.pinecone.io/docs/metadata-filtering/).", + description: "The filter to apply. You can use vector metadata to limit your search. For guidance and examples, see [filtering-with-metadata](https://docs.pinecone.io/guides/data/filtering-with-metadata).", optional: true, }, includeValues: { @@ -59,16 +53,9 @@ export default { propDefinition: [ app, "vectorId", - ({ - indexName, projectId, - }) => ({ - indexName, - projectId, - }), ], }, vector: { - optional: true, description: `${app.propDefinitions.vectorValues.description} Each request can contain only one of the parameters either **Vector Values** or **Vector ID**.`, propDefinition: [ app, @@ -76,10 +63,18 @@ export default { ], }, }, + methods: { + query(args = {}) { + return this.app.post({ + path: "/query", + ...args, + }); + }, + }, async run({ $: step }) { const { + query, indexName, - projectId, id, vector, topK, @@ -89,12 +84,16 @@ export default { namespace, } = this; - const response = await this.app.query({ - projectId, + const vectorParsed = utils.parseArray(vector); + + const response = await query({ + step, indexName, data: { id, - vector: utils.parseArray(vector), + ...(vectorParsed.length && { + vector: vectorParsed, + }), topK, filter: utils.parse(filter), includeValues, diff --git a/components/pinecone/actions/update-vector/update-vector.mjs b/components/pinecone/actions/update-vector/update-vector.mjs index 688c6b332819e..f8ed46d514702 100644 --- a/components/pinecone/actions/update-vector/update-vector.mjs +++ b/components/pinecone/actions/update-vector/update-vector.mjs @@ -4,9 +4,9 @@ import app from "../../pinecone.app.mjs"; export default { key: "pinecone-update-vector", name: "Update Vector", - description: "Updates vector in a namespace. If a value is included, it will overwrite the previous value. [See the docs](https://docs.pinecone.io/reference/update).", + description: "Updates vector in a namespace. If a value is included, it will overwrite the previous value. [See the documentation](https://docs.pinecone.io/reference/api/data-plane/update).", type: "action", - version: "0.0.1", + version: "0.0.2", props: { app, indexName: { @@ -15,22 +15,10 @@ export default { "indexName", ], }, - projectId: { - propDefinition: [ - app, - "projectId", - ], - }, id: { propDefinition: [ app, "vectorId", - ({ - indexName, projectId, - }) => ({ - indexName, - projectId, - }), ], }, values: { @@ -54,7 +42,7 @@ export default { }, methods: { updateVector(args = {}) { - return this.app.create({ + return this.app.post({ path: "/vectors/update", ...args, }); @@ -62,25 +50,28 @@ export default { }, async run({ $: step }) { const { + updateVector, indexName, - projectId, id, values, metadata, namespace, } = this; - await this.updateVector({ - projectId, + await updateVector({ + step, indexName, data: { id, - values, + values: utils.parseArray(values), setMetadata: utils.parse(metadata), namespace, }, }); step.export("$summary", `Successfully updated vector with ID ${id}.`); + return { + success: true, + }; }, }; diff --git a/components/pinecone/actions/upsert-vector/upsert-vector.mjs b/components/pinecone/actions/upsert-vector/upsert-vector.mjs index 8e8a8e883f1fb..48663bb8b1afc 100644 --- a/components/pinecone/actions/upsert-vector/upsert-vector.mjs +++ b/components/pinecone/actions/upsert-vector/upsert-vector.mjs @@ -4,9 +4,9 @@ import utils from "../../common/utils.mjs"; export default { key: "pinecone-upsert-vector", name: "Upsert Vector", - description: "Writes vectors into a namespace. If a new value is upserted for an existing vector ID, it will overwrite the previous value. [See the docs](https://docs.pinecone.io/reference/upsert).", + description: "Writes vectors into a namespace. If a new value is upserted for an existing vector ID, it will overwrite the previous value. [See the documentation](https://docs.pinecone.io/reference/api/data-plane/upsert).", type: "action", - version: "0.0.1", + version: "0.0.2", props: { app, indexName: { @@ -15,25 +15,14 @@ export default { "indexName", ], }, - projectId: { - propDefinition: [ - app, - "projectId", - ], - }, id: { propDefinition: [ app, "vectorId", - ({ - indexName, projectId, - }) => ({ - indexName, - projectId, - }), ], }, values: { + optional: false, propDefinition: [ app, "vectorValues", @@ -54,7 +43,7 @@ export default { }, methods: { upsertVector(args = {}) { - return this.app.create({ + return this.app.post({ path: "/vectors/upsert", ...args, }); @@ -62,22 +51,22 @@ export default { }, async run({ $: step }) { const { + upsertVector, indexName, - projectId, id, values, metadata, namespace, } = this; - const response = await this.upsertVector({ - projectId, + const response = await upsertVector({ + step, indexName, data: { vectors: [ { id, - values, + values: utils.parseArray(values), metadata: utils.parse(metadata), }, ], diff --git a/components/pinecone/common/constants.mjs b/components/pinecone/common/constants.mjs index 3ddf0a85a967a..25613cf3f06ce 100644 --- a/components/pinecone/common/constants.mjs +++ b/components/pinecone/common/constants.mjs @@ -1,23 +1,8 @@ const INDEX_NAME_PLACEHOLDER = ""; -const PROJECT_ID_PLACEHOLDER = ""; -const ENV_PLACEHOLDER = ""; -const BASE_URL = `https://${INDEX_NAME_PLACEHOLDER}-${PROJECT_ID_PLACEHOLDER}.svc.${ENV_PLACEHOLDER}.pinecone.io`; -const CONTROLLER_URL = `https://controller.${ENV_PLACEHOLDER}.pinecone.io`; -const LAST_CREATED_AT = "lastCreatedAt"; +const BASE_URL = `https://${INDEX_NAME_PLACEHOLDER}`; +const CONTROLLER_URL = "https://api.pinecone.io"; const DEFAULT_MAX = 600; -const FILE_PROP_NAMES = [ - "attachment", - "uploaddoc", - "upload_file", -]; - -const CONTENT_TYPE_KEY_HEADER = "Content-Type"; -const MULTIPART_FORM_DATA_VALUE_HEADER = "multipart/form-data"; -const MULTIPART_FORM_DATA_HEADERS = { - [CONTENT_TYPE_KEY_HEADER]: MULTIPART_FORM_DATA_VALUE_HEADER, -}; - const API = { DEFAULT: "default", CONTROLLER: "controller", @@ -25,15 +10,8 @@ const API = { export default { API, - ENV_PLACEHOLDER, - PROJECT_ID_PLACEHOLDER, INDEX_NAME_PLACEHOLDER, BASE_URL, CONTROLLER_URL, DEFAULT_MAX, - LAST_CREATED_AT, - FILE_PROP_NAMES, - CONTENT_TYPE_KEY_HEADER, - MULTIPART_FORM_DATA_VALUE_HEADER, - MULTIPART_FORM_DATA_HEADERS, }; diff --git a/components/pinecone/package.json b/components/pinecone/package.json index 2698522ba74ae..a1141179b4f8f 100644 --- a/components/pinecone/package.json +++ b/components/pinecone/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/pinecone", - "version": "0.0.3", + "version": "0.0.4", "description": "Pipedream Pinecone Components", "main": "pinecone.app.mjs", "keywords": [ diff --git a/components/pinecone/pinecone.app.mjs b/components/pinecone/pinecone.app.mjs index 292adf8610a69..a74e8c2e75e6a 100644 --- a/components/pinecone/pinecone.app.mjs +++ b/components/pinecone/pinecone.app.mjs @@ -9,44 +9,22 @@ export default { indexName: { type: "string", label: "Index Name", - description: "The name of the index. E.g. `my-index`", - options() { - return this.listIndexes(); + description: "The name of the index. E.g. `my-index.pinecone.io`", + async options() { + const { indexes } = await this.listIndexes(); + return indexes.map(({ + name: label, host: value, + }) => ({ + label, + value, + })); }, }, - projectId: { + prefix: { type: "string", - label: "Project ID", - description: "The ID of the project. You can get the project from the Pinecone URL, for example https://app.pinecone.io/organizations/abcde/projects/us-east-1-aws:611e7f9/indexes, then the Project ID is `611e7f9`", - }, - vectorId: { - type: "string", - label: "Vector ID", - description: "The ID of the vector.", - async options({ - indexName, projectId, - }) { - if (!indexName || !projectId) { - return []; - } - - const { database: { dimension } } = await this.describeIndex({ - indexName, - }); - - const { matches } = await this.query({ - projectId, - indexName, - data: { - topK: dimension, - vector: Array.from({ - length: dimension, - }).map(() => 0), - }, - }); - - return matches?.map(({ id }) => id); - }, + label: "Prefix", + description: "The vector IDs to fetch. Does not accept values containing spaces. E.g. `my-prefix`", + optional: true, }, namespace: { type: "string", @@ -54,10 +32,16 @@ export default { description: "The namespace to use.", optional: true, }, + vectorId: { + type: "string", + label: "Vector ID", + description: "The ID of the vector.", + }, vectorValues: { type: "string[]", label: "Vector Values", description: "The values of the vector.", + optional: true, }, vectorMetadata: { type: "object", @@ -68,25 +52,17 @@ export default { }, methods: { getBaseUrl({ - api, projectId, indexName, + api, indexName, } = {}) { - const { environment } = this.$auth; return api === constants.API.CONTROLLER ? constants.CONTROLLER_URL - .replace(constants.ENV_PLACEHOLDER, environment) : constants.BASE_URL - .replace(constants.INDEX_NAME_PLACEHOLDER, indexName) - .replace(constants.PROJECT_ID_PLACEHOLDER, projectId) - .replace(constants.ENV_PLACEHOLDER, environment); + .replace(constants.INDEX_NAME_PLACEHOLDER, indexName); }, getUrl({ - api, path, projectId, indexName, + path, ...args } = {}) { - const baseUrl = this.getBaseUrl({ - api, - projectId, - indexName, - }); + const baseUrl = this.getBaseUrl(args); return `${baseUrl}${path}`; }, getHeaders(headers) { @@ -102,118 +78,32 @@ export default { }); }, makeRequest({ - step = this, api, headers, projectId, indexName, path, ...args + step = this, api, headers, indexName, path, ...args } = {}) { - const config = { + ...args, paramsSerializer: this.paramsSerializer, headers: this.getHeaders(headers), url: this.getUrl({ api, indexName, - projectId, path, }), - ...args, }; - return axios(step, config); }, - create(args = {}) { + post(args = {}) { return this.makeRequest({ method: "post", ...args, }); }, - update(args = {}) { - return this.makeRequest({ - method: "put", - ...args, - }); - }, - delete(args = {}) { - return this.makeRequest({ - method: "delete", - ...args, - }); - }, - patch(args = {}) { - return this.makeRequest({ - method: "patch", - ...args, - }); - }, - query(args = {}) { - return this.create({ - path: "/query", - ...args, - }); - }, - describeIndex({ - indexName, ...args - } = {}) { + listIndexes(args = {}) { return this.makeRequest({ api: constants.API.CONTROLLER, - path: `/databases/${indexName}`, + path: "/indexes", ...args, }); }, - listIndexes() { - return this.makeRequest({ - api: constants.API.CONTROLLER, - path: "/databases", - }); - }, - fetchVectors(args = {}) { - return this.makeRequest({ - path: "/vectors/fetch", - ...args, - }); - }, - async *getResourcesStream({ - resourceFn, - resourceFnArgs, - resourceName, - lastCreatedAt, - max = constants.DEFAULT_MAX, - }) { - let page = 1; - let resourcesCount = 0; - - while (true) { - const response = - await resourceFn({ - ...resourceFnArgs, - params: { - ...resourceFnArgs.params, - page, - }, - }); - - const nextResources = resourceName && response[resourceName] || response; - - if (!nextResources?.length) { - console.log("No more resources found"); - return; - } - - for (const resource of nextResources) { - const dateFilter = - lastCreatedAt - && Date.parse(resource.created_at) > Date.parse(lastCreatedAt); - - if (!lastCreatedAt || dateFilter) { - yield resource; - resourcesCount += 1; - } - - if (resourcesCount >= max) { - return; - } - } - - page += 1; - } - }, }, }; diff --git a/components/pitchlane/package.json b/components/pitchlane/package.json new file mode 100644 index 0000000000000..e314f9e6a188f --- /dev/null +++ b/components/pitchlane/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pipedream/pitchlane", + "version": "0.0.1", + "description": "Pipedream Pitchlane Components", + "main": "pitchlane.app.mjs", + "keywords": [ + "pipedream", + "pitchlane" + ], + "homepage": "https://pipedream.com/apps/pitchlane", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/components/pitchlane/pitchlane.app.mjs b/components/pitchlane/pitchlane.app.mjs new file mode 100644 index 0000000000000..d57b8cd12d152 --- /dev/null +++ b/components/pitchlane/pitchlane.app.mjs @@ -0,0 +1,11 @@ +export default { + type: "app", + app: "pitchlane", + 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/platerecognizer/README.md b/components/platerecognizer/README.md new file mode 100644 index 0000000000000..1d02ff6d86325 --- /dev/null +++ b/components/platerecognizer/README.md @@ -0,0 +1,11 @@ +# Overview + +The Plate Recognizer API provides robust tools for converting images of vehicle license plates into text data. Using Pipedream, you can harness this capability to automate various tasks involving vehicle identification and monitoring. This integration is particularly useful in scenarios involving security, parking management, and logistics optimization, where automated plate recognition can streamline operations significantly. + +# Example Use Cases + +- **Automated Parking Access**: Set up a workflow where a camera at a parking entrance snaps pictures of incoming vehicles. Use the Plate Recognizer API to decode the license plate and check against a database of authorized vehicles (hosted on a service like Airtable). If the vehicle is authorized, trigger an IoT device to open the gate. + +- **Security Alert System**: Create a workflow that monitors a feed from security cameras. Use the Plate Recognizer API to identify license plates of vehicles entering a restricted area. If a plate matches a list of flagged vehicles (maintained in a Google Sheets file), send an alert message via Slack or email to security personnel. + +- **Logistics Tracking**: Develop a system to track the arrival and departure of trucks at a warehouse. Capture license plate images, decode them through the Plate Recognizer API, and log the timestamps in a database. Use this data to analyze logistics efficiency and notify managers via SMS (using Twilio) if there are notable delays or early arrivals. diff --git a/components/platerecognizer/actions/run-recognition/run-recognition.mjs b/components/platerecognizer/actions/run-recognition/run-recognition.mjs new file mode 100644 index 0000000000000..e424d3541f8bf --- /dev/null +++ b/components/platerecognizer/actions/run-recognition/run-recognition.mjs @@ -0,0 +1,67 @@ +import fs from "fs"; +import { checkTmp } from "../../common/utils.mjs"; +import platerecognizer from "../../platerecognizer.app.mjs"; + +export default { + key: "platerecognizer-run-recognition", + name: "Run Recognition", + description: "Triggers a recognition process using the Plate Recognizer SDK.", + version: "0.0.1", + type: "action", + props: { + platerecognizer, + imageFileOrUrl: { + type: "string", + label: "Image File or URL", + description: "The image file or URL to be recognized.", + }, + regions: { + type: "string[]", + label: "Regions", + description: "Regions to select specific license plate patterns. [See further details here](https://guides.platerecognizer.com/docs/other/country-codes/#country-codes)", + optional: true, + }, + cameraId: { + type: "string", + label: "Camera ID", + description: "The ID of the camera that took the image.", + optional: true, + }, + mmc: { + type: "boolean", + label: "MMC", + description: "Whether to detect vehicle make, model, and color.", + optional: true, + }, + config: { + type: "object", + label: "Config", + description: "Additional configuration. [See further details here](https://guides.platerecognizer.com/docs/snapshot/api-reference/#engine-configuration)", + optional: true, + }, + }, + async run({ $ }) { + const fileObj = {}; + + if (this.imageFileOrUrl.startsWith("http")) { + fileObj.upload_url = this.imageFileOrUrl; + } else { + const file = fs.readFileSync(checkTmp(this.imageFileOrUrl)); + fileObj.upload = Buffer(file).toString("base64"); + } + + const response = await this.platerecognizer.runRecognition({ + $, + data: { + ...fileObj, + regions: this.regions, + camera_id: this.cameraId, + mmc: this.mmc, + config: this.config, + }, + }); + + $.export("$summary", "Recognition process triggered successfully"); + return response; + }, +}; diff --git a/components/platerecognizer/common/utils.mjs b/components/platerecognizer/common/utils.mjs new file mode 100644 index 0000000000000..1a5e36f32a603 --- /dev/null +++ b/components/platerecognizer/common/utils.mjs @@ -0,0 +1,6 @@ +export const checkTmp = (filename) => { + if (!filename.startsWith("/tmp")) { + return `/tmp/${filename}`; + } + return filename; +}; diff --git a/components/platerecognizer/package.json b/components/platerecognizer/package.json index 61e75bdaccd64..763385296dc5f 100644 --- a/components/platerecognizer/package.json +++ b/components/platerecognizer/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/platerecognizer", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Plate Recognizer Components", "main": "platerecognizer.app.mjs", "keywords": [ @@ -11,5 +11,9 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^1.6.5" } -} \ No newline at end of file +} + diff --git a/components/platerecognizer/platerecognizer.app.mjs b/components/platerecognizer/platerecognizer.app.mjs index eb436c25ff5aa..5c83ad58b5f5e 100644 --- a/components/platerecognizer/platerecognizer.app.mjs +++ b/components/platerecognizer/platerecognizer.app.mjs @@ -1,11 +1,33 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "platerecognizer", - propDefinitions: {}, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.platerecognizer.com/v1"; + }, + _headers(headers) { + return { + "Authorization": `Token ${this.$auth.api_token}`, + ...headers, + }; + }, + _makeRequest({ + $ = this, path, headers = {}, ...opts + }) { + return axios($, { + url: `${this._baseUrl()}${path}`, + headers: this._headers(headers), + ...opts, + }); + }, + runRecognition(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/plate-reader/", + ...opts, + }); }, }, -}; \ No newline at end of file +}; diff --git a/components/postmark/actions/create-domain/create-domain.mjs b/components/postmark/actions/create-domain/create-domain.mjs index ad7d316f477c1..ea3154682769c 100644 --- a/components/postmark/actions/create-domain/create-domain.mjs +++ b/components/postmark/actions/create-domain/create-domain.mjs @@ -4,7 +4,7 @@ export default { key: "postmark-create-domain", name: "Create Domain", description: "Create a new domain. [See the documentation](https://postmarkapp.com/developer/api/domains-api#create-domain)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { postmark, diff --git a/components/postmark/actions/create-signature/create-signature.mjs b/components/postmark/actions/create-signature/create-signature.mjs index 09ee97ba63fde..8dff1b93f6aeb 100644 --- a/components/postmark/actions/create-signature/create-signature.mjs +++ b/components/postmark/actions/create-signature/create-signature.mjs @@ -4,7 +4,7 @@ export default { key: "postmark-create-signature", name: "Create Sender Signature", description: "Create a new sender signature. [See the documentation](https://postmarkapp.com/developer/api/signatures-api#create-signature)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { postmark, diff --git a/components/postmark/actions/delete-domain/delete-domain.mjs b/components/postmark/actions/delete-domain/delete-domain.mjs index e99d214e61c68..f87f4adf08402 100644 --- a/components/postmark/actions/delete-domain/delete-domain.mjs +++ b/components/postmark/actions/delete-domain/delete-domain.mjs @@ -4,7 +4,7 @@ export default { key: "postmark-delete-domain", name: "Delete Domain", description: "Delete a specific domain. [See the documentation](https://postmarkapp.com/developer/api/domains-api#delete-domain)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { postmark, diff --git a/components/postmark/actions/delete-signature/delete-signature.mjs b/components/postmark/actions/delete-signature/delete-signature.mjs index 67d161666170a..d0e0f54bd4d43 100644 --- a/components/postmark/actions/delete-signature/delete-signature.mjs +++ b/components/postmark/actions/delete-signature/delete-signature.mjs @@ -4,7 +4,7 @@ export default { key: "postmark-delete-signature", name: "Delete Sender Signature", description: "Delete a specific sender signature. [See the documentation](https://postmarkapp.com/developer/api/signatures-api#delete-signature)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { postmark, diff --git a/components/postmark/actions/get-bounce-counts/get-bounce-counts.mjs b/components/postmark/actions/get-bounce-counts/get-bounce-counts.mjs index b54f2f2369915..94eaef54442a7 100644 --- a/components/postmark/actions/get-bounce-counts/get-bounce-counts.mjs +++ b/components/postmark/actions/get-bounce-counts/get-bounce-counts.mjs @@ -4,7 +4,7 @@ export default { key: "postmark-get-bounce-counts", name: "Get Bounce Counts", description: "Gets total counts of emails you've sent out that have been returned as bounced. [See the documentation](https://postmarkapp.com/developer/api/stats-api#bounce-counts)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { postmark, diff --git a/components/postmark/actions/get-browser-usage/get-browser-usage.mjs b/components/postmark/actions/get-browser-usage/get-browser-usage.mjs index 0f4f4f60de42e..b4d985c736d15 100644 --- a/components/postmark/actions/get-browser-usage/get-browser-usage.mjs +++ b/components/postmark/actions/get-browser-usage/get-browser-usage.mjs @@ -4,7 +4,7 @@ export default { key: "postmark-get-browser-usage", name: "Get Browser Usage", description: "Gets an overview of the browsers used to open links in your emails. This is only recorded when Link Tracking is enabled for that email. [See the documentation](https://postmarkapp.com/developer/api/stats-api#browser-usage)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { postmark, diff --git a/components/postmark/actions/get-click-counts/get-click-counts.mjs b/components/postmark/actions/get-click-counts/get-click-counts.mjs index 4f60637812f44..85addbe853f41 100644 --- a/components/postmark/actions/get-click-counts/get-click-counts.mjs +++ b/components/postmark/actions/get-click-counts/get-click-counts.mjs @@ -4,7 +4,7 @@ export default { key: "postmark-get-click-counts", name: "Get Click Counts", description: "Gets total counts of unique links that were clicked. [See the documentation](https://postmarkapp.com/developer/api/stats-api#click-counts)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { postmark, diff --git a/components/postmark/actions/get-domain/get-domain.mjs b/components/postmark/actions/get-domain/get-domain.mjs index de3f09b42c2b4..c027b0a6b89fd 100644 --- a/components/postmark/actions/get-domain/get-domain.mjs +++ b/components/postmark/actions/get-domain/get-domain.mjs @@ -4,7 +4,7 @@ export default { key: "postmark-get-domain", name: "Get Domain", description: "Gets all the details for a specific domain. [See the documentation](https://postmarkapp.com/developer/api/domains-api#domain)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { postmark, diff --git a/components/postmark/actions/get-email-open-counts/get-email-open-counts.mjs b/components/postmark/actions/get-email-open-counts/get-email-open-counts.mjs index cb02b5d460988..7e35f2fd92211 100644 --- a/components/postmark/actions/get-email-open-counts/get-email-open-counts.mjs +++ b/components/postmark/actions/get-email-open-counts/get-email-open-counts.mjs @@ -4,7 +4,7 @@ export default { key: "postmark-get-email-open-counts", name: "Get Email Open Counts", description: "Gets total counts of recipients who opened your emails. This is only recorded when open tracking is enabled for that email. [See the documentation](https://postmarkapp.com/developer/api/stats-api#email-open-counts)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { postmark, diff --git a/components/postmark/actions/get-email-platform-usage/get-email-platform-usage.mjs b/components/postmark/actions/get-email-platform-usage/get-email-platform-usage.mjs index 955f43f0ae536..33bc07d98075e 100644 --- a/components/postmark/actions/get-email-platform-usage/get-email-platform-usage.mjs +++ b/components/postmark/actions/get-email-platform-usage/get-email-platform-usage.mjs @@ -4,7 +4,7 @@ export default { key: "postmark-get-email-platform-usage", name: "Get Email Platform Usage", description: "Gets an overview of the platforms used to open your emails. This is only recorded when open tracking is enabled for that email. [See the documentation](https://postmarkapp.com/developer/api/stats-api#email-platform-usage)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { postmark, diff --git a/components/postmark/actions/get-outbound-overview/get-outbound-overview.mjs b/components/postmark/actions/get-outbound-overview/get-outbound-overview.mjs index 99d090b8ded8f..bc1ba33be61f1 100644 --- a/components/postmark/actions/get-outbound-overview/get-outbound-overview.mjs +++ b/components/postmark/actions/get-outbound-overview/get-outbound-overview.mjs @@ -4,7 +4,7 @@ export default { key: "postmark-get-outbound-overview", name: "Get Outbound Overview", description: "Gets a brief overview of statistics for all of your outbound email. [See the documentation](https://postmarkapp.com/developer/api/stats-api#overview)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { postmark, diff --git a/components/postmark/actions/get-sent-counts/get-sent-counts.mjs b/components/postmark/actions/get-sent-counts/get-sent-counts.mjs index 52667f693a26b..70ea58fd6891c 100644 --- a/components/postmark/actions/get-sent-counts/get-sent-counts.mjs +++ b/components/postmark/actions/get-sent-counts/get-sent-counts.mjs @@ -4,7 +4,7 @@ export default { key: "postmark-get-sent-counts", name: "Get Sent Counts", description: "Gets a total count of emails you've sent out. [See the documentation](https://postmarkapp.com/developer/api/stats-api#sent-counts)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { postmark, diff --git a/components/postmark/actions/get-spam-complaints/get-spam-complaints.mjs b/components/postmark/actions/get-spam-complaints/get-spam-complaints.mjs index 31d02fe0f3242..8d6e7a5be8e0e 100644 --- a/components/postmark/actions/get-spam-complaints/get-spam-complaints.mjs +++ b/components/postmark/actions/get-spam-complaints/get-spam-complaints.mjs @@ -4,7 +4,7 @@ export default { key: "postmark-get-spam-complaints", name: "Get Spam Complaints", description: "Gets a total count of recipients who have marked your email as spam. [See the documentation](https://postmarkapp.com/developer/api/stats-api#spam-complaints)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { postmark, diff --git a/components/postmark/actions/get-tracked-email-counts/get-tracked-email-counts.mjs b/components/postmark/actions/get-tracked-email-counts/get-tracked-email-counts.mjs index 3098cff545f03..6dc3f8320bd1b 100644 --- a/components/postmark/actions/get-tracked-email-counts/get-tracked-email-counts.mjs +++ b/components/postmark/actions/get-tracked-email-counts/get-tracked-email-counts.mjs @@ -4,7 +4,7 @@ export default { key: "postmark-get-tracked-email-counts", name: "Get Tracked Email Counts", description: "Gets a total count of emails you've sent with open tracking or link tracking enabled. [See the documentation](https://postmarkapp.com/developer/api/stats-api#email-tracked-count)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { postmark, diff --git a/components/postmark/actions/list-domains/list-domains.mjs b/components/postmark/actions/list-domains/list-domains.mjs index 96fee5c5e1f55..85d64fe76ef95 100644 --- a/components/postmark/actions/list-domains/list-domains.mjs +++ b/components/postmark/actions/list-domains/list-domains.mjs @@ -4,7 +4,7 @@ export default { key: "postmark-list-domains", name: "List Domains", description: "Gets a list of domains containing an overview of the domain and authentication status. [See the documentation](https://postmarkapp.com/developer/api/domains-api#list-domains)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { postmark, diff --git a/components/postmark/actions/list-sender-signatures/list-sender-signatures.mjs b/components/postmark/actions/list-sender-signatures/list-sender-signatures.mjs index 12f8e28a0e3ac..7f57496261f0b 100644 --- a/components/postmark/actions/list-sender-signatures/list-sender-signatures.mjs +++ b/components/postmark/actions/list-sender-signatures/list-sender-signatures.mjs @@ -4,7 +4,7 @@ export default { key: "postmark-list-sender-signatures", name: "List Sender Signatures", description: "Gets a list of sender signatures containing brief details associated with your account. [See the documentation](https://postmarkapp.com/developer/api/signatures-api#list-sender-signatures)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { postmark, diff --git a/components/postmark/actions/resend-confirmation/resend-confirmation.mjs b/components/postmark/actions/resend-confirmation/resend-confirmation.mjs index b4c5eefffead7..291daa7e555e5 100644 --- a/components/postmark/actions/resend-confirmation/resend-confirmation.mjs +++ b/components/postmark/actions/resend-confirmation/resend-confirmation.mjs @@ -4,7 +4,7 @@ export default { key: "postmark-resend-confirmation", name: "Resend Confirmation", description: "Resend a confirmation email for a specific sender signature. [See the documentation](https://postmarkapp.com/developer/api/signatures-api#resend-confirmation)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { postmark, diff --git a/components/postmark/actions/rotate-dkim-keys/rotate-dkim-keys.mjs b/components/postmark/actions/rotate-dkim-keys/rotate-dkim-keys.mjs index 2f1e6e9dfb7f6..0b898ea6af166 100644 --- a/components/postmark/actions/rotate-dkim-keys/rotate-dkim-keys.mjs +++ b/components/postmark/actions/rotate-dkim-keys/rotate-dkim-keys.mjs @@ -4,7 +4,7 @@ export default { key: "postmark-rotate-dkim-keys", name: "Rotate DKIM Keys", description: "Creates a new DKIM key to replace your current key. Until the new DNS entries are confirmed, the pending values will be in DKIMPendingHost and DKIMPendingTextValue fields. After the new DKIM value is verified in DNS, the pending values will migrate to DKIMTextValue and DKIMPendingTextValue and Postmark will begin to sign emails with the new DKIM key. [See the documentation](https://postmarkapp.com/developer/api/domains-api#rotate-dkim)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { postmark, diff --git a/components/postmark/actions/send-batch-with-templates/send-batch-with-templates.mjs b/components/postmark/actions/send-batch-with-templates/send-batch-with-templates.mjs index 6320e6e611ee3..c760873756963 100644 --- a/components/postmark/actions/send-batch-with-templates/send-batch-with-templates.mjs +++ b/components/postmark/actions/send-batch-with-templates/send-batch-with-templates.mjs @@ -9,7 +9,7 @@ export default { key: "postmark-send-batch-with-templates", name: "Send Batch With Templates", description: "Send a batch of emails using a template [See the documentation](https://postmarkapp.com/developer/api/templates-api#send-batch-with-templates)", - version: "0.0.2", + version: "0.0.3", type: "action", props: { postmark, diff --git a/components/postmark/actions/send-email-with-template/send-email-with-template.mjs b/components/postmark/actions/send-email-with-template/send-email-with-template.mjs index 3df083349cf19..f35b212131368 100644 --- a/components/postmark/actions/send-email-with-template/send-email-with-template.mjs +++ b/components/postmark/actions/send-email-with-template/send-email-with-template.mjs @@ -1,5 +1,4 @@ import common from "../common/common.mjs"; -import templateProps from "../common/templateProps.mjs"; const { postmark, ...props @@ -10,11 +9,37 @@ export default { key: "postmark-send-email-with-template", name: "Send Email With Template", description: "Send a single email with Postmark using a template [See the documentation](https://postmarkapp.com/developer/api/templates-api#email-with-template)", - version: "0.0.3", + version: "0.0.4", type: "action", props: { postmark, - ...templateProps, + templateAlias: { + type: "string", + label: "Template", + description: "The template to use for this email.", + async options ({ page }) { + const data = await this.postmark.listTemplates(page); + const options = data.Templates?.map((obj) => { + return { + label: obj.Name, + value: obj.Alias, + }; + }) ?? []; + + return { + options, + context: { + page, + }, + }; + }, + }, + templateModel: { + type: "object", + label: "Template Model", + description: + "The model to be applied to the specified template to generate the email body and subject.", + }, ...props, inlineCss: { type: "boolean", diff --git a/components/postmark/actions/send-single-email/send-single-email.mjs b/components/postmark/actions/send-single-email/send-single-email.mjs index b631b5f033f95..2b6fcd4d07880 100644 --- a/components/postmark/actions/send-single-email/send-single-email.mjs +++ b/components/postmark/actions/send-single-email/send-single-email.mjs @@ -9,7 +9,7 @@ export default { key: "postmark-send-single-email", name: "Send Single Email", description: "Send a single email with Postmark [See the documentation](https://postmarkapp.com/developer/api/email-api#send-a-single-email)", - version: "0.2.2", + version: "0.2.3", type: "action", props: { postmark, diff --git a/components/postmark/actions/update-signature/update-signature.mjs b/components/postmark/actions/update-signature/update-signature.mjs index 44f67016b1b24..3b7d3df8409b9 100644 --- a/components/postmark/actions/update-signature/update-signature.mjs +++ b/components/postmark/actions/update-signature/update-signature.mjs @@ -4,7 +4,7 @@ export default { key: "postmark-update-signature", name: "Update Sender Signature", description: "Create a new sender signature. [See the documentation](https://postmarkapp.com/developer/api/signatures-api#create-signature)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { postmark, diff --git a/components/postmark/actions/verify-dkim/verify-dkim.mjs b/components/postmark/actions/verify-dkim/verify-dkim.mjs index 83d8aceac3a60..0f4a660d8107a 100644 --- a/components/postmark/actions/verify-dkim/verify-dkim.mjs +++ b/components/postmark/actions/verify-dkim/verify-dkim.mjs @@ -4,7 +4,7 @@ export default { key: "postmark-verify-dkim", name: "Verify DKIM", description: "Verify DKIM keys for the specified domain. [See the documentation](https://postmarkapp.com/developer/api/domains-api#domains-verify-dkim)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { postmark, diff --git a/components/postmark/actions/verify-return-path/verify-return-path.mjs b/components/postmark/actions/verify-return-path/verify-return-path.mjs index aa04fe618ee12..3e36054d45ac9 100644 --- a/components/postmark/actions/verify-return-path/verify-return-path.mjs +++ b/components/postmark/actions/verify-return-path/verify-return-path.mjs @@ -4,7 +4,7 @@ export default { key: "postmark-verify-return-path", name: "Verify Return-Path DNS", description: "Verify Return-Path DNS record for the specified domain. [See the documentation](https://postmarkapp.com/developer/api/domains-api#domains-verify-return-path)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { postmark, diff --git a/components/postmark/package.json b/components/postmark/package.json index d754609505078..d43e0f39f7422 100644 --- a/components/postmark/package.json +++ b/components/postmark/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/postmark", - "version": "0.1.1", + "version": "0.1.2", "description": "Pipedream Postmark Components", "main": "postmark.app.mjs", "keywords": [ diff --git a/components/postmark/postmark.app.mjs b/components/postmark/postmark.app.mjs index d1202ced85874..2974e7afbab02 100644 --- a/components/postmark/postmark.app.mjs +++ b/components/postmark/postmark.app.mjs @@ -27,6 +27,28 @@ export default { }); }, }, + serverId: { + type: "string", + label: "Server Id", + description: "The identifier of the server.", + async options({ page }) { + const { Servers = [] } = await this.listServers({ + params: { + count: LIMIT, + offset: LIMIT * page, + }, + }); + + return Servers.map(({ + Name: label, ID: value, + }) => { + return { + label, + value, + }; + }); + }, + }, signatureId: { type: "string", label: "Signature Id", @@ -155,11 +177,14 @@ export default { ...opts, }); }, - async setServerInfo(data) { - return this.sharedRequest(this, { - endpoint: "server", - method: "put", - data, + setServerInfo({ + serverId, ...opts + }) { + return this._makeRequest({ + method: "PUT", + tokenType: "account", + path: `servers/${serverId}`, + ...opts, }); }, createDataRemoval(opts = {}) { @@ -305,6 +330,13 @@ export default { ...opts, }); }, + listServers(opts = {}) { + return this._makeRequest({ + tokenType: "account", + path: "servers", + ...opts, + }); + }, resendConfirmation({ signatureId, ...opts }) { diff --git a/components/postmark/sources/new-domain/new-domain.mjs b/components/postmark/sources/new-domain/new-domain.mjs index 76c315d571daa..cdfb5a4900066 100644 --- a/components/postmark/sources/new-domain/new-domain.mjs +++ b/components/postmark/sources/new-domain/new-domain.mjs @@ -5,7 +5,7 @@ export default { key: "postmark-new-domain", name: "New Domain", description: "Emit new event when a new domain is created.", - version: "0.0.1", + version: "0.0.2", type: "source", methods: { ...common.methods, diff --git a/components/postmark/sources/new-email-opened/new-email-opened.mjs b/components/postmark/sources/new-email-opened/new-email-opened.mjs index d62b3564465ad..fae6417f1baa3 100644 --- a/components/postmark/sources/new-email-opened/new-email-opened.mjs +++ b/components/postmark/sources/new-email-opened/new-email-opened.mjs @@ -6,7 +6,7 @@ export default { name: "New Email Opened", description: "Emit new event when an email is opened by a recipient [(See docs here)](https://postmarkapp.com/developer/webhooks/open-tracking-webhook)", - version: "0.0.3", + version: "0.0.4", type: "source", props: { ...common.props, diff --git a/components/postmark/sources/new-inbound-email-received/new-inbound-email-received.mjs b/components/postmark/sources/new-inbound-email-received/new-inbound-email-received.mjs index 0d99fa05c7447..647980b22d7c4 100644 --- a/components/postmark/sources/new-inbound-email-received/new-inbound-email-received.mjs +++ b/components/postmark/sources/new-inbound-email-received/new-inbound-email-received.mjs @@ -4,10 +4,18 @@ export default { ...common, key: "postmark-new-inbound-email-received", name: "New Inbound Email Received", - description: - "Emit new event when an email is received by the Postmark server [(See docs here)](https://postmarkapp.com/developer/webhooks/inbound-webhook)", - version: "0.0.3", + description: "Emit new event when an email is received by the Postmark server. This source updates the server's inbound URL. You cannot create multiple inbound sources for the same server. [See the documentation](https://postmarkapp.com/developer/webhooks/inbound-webhook#set-the-webhook-url)", + version: "1.0.0", type: "source", + props: { + ...common.props, + serverId: { + propDefinition: [ + common.props.postmark, + "serverId", + ], + }, + }, methods: { ...common.methods, getWebhookProps() { @@ -19,4 +27,17 @@ export default { return `New email received! MessageID - ${body.MessageID}`; }, }, + hooks: { + async activate() { + await this.postmark.setServerInfo({ + serverId: this.serverId, + data: { + InboundHookUrl: this.http.endpoint, + }, + }); + }, + async deactivate() { + return true; + }, + }, }; diff --git a/components/postmark/sources/new-sender-signature/new-sender-signature.mjs b/components/postmark/sources/new-sender-signature/new-sender-signature.mjs index 6b70f4a396a69..983cb6fc71b01 100644 --- a/components/postmark/sources/new-sender-signature/new-sender-signature.mjs +++ b/components/postmark/sources/new-sender-signature/new-sender-signature.mjs @@ -5,7 +5,7 @@ export default { key: "postmark-new-sender-signature", name: "New Sender Signature", description: "Emit new event when a new sender signature is created.", - version: "0.0.1", + version: "0.0.2", type: "source", methods: { ...common.methods, diff --git a/components/pro_ledger/package.json b/components/pro_ledger/package.json new file mode 100644 index 0000000000000..056fece420426 --- /dev/null +++ b/components/pro_ledger/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pipedream/pro_ledger", + "version": "0.0.1", + "description": "Pipedream Pro-Ledger Components", + "main": "pro_ledger.app.mjs", + "keywords": [ + "pipedream", + "pro_ledger" + ], + "homepage": "https://pipedream.com/apps/pro_ledger", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/components/pro_ledger/pro_ledger.app.mjs b/components/pro_ledger/pro_ledger.app.mjs new file mode 100644 index 0000000000000..5902c73143d9c --- /dev/null +++ b/components/pro_ledger/pro_ledger.app.mjs @@ -0,0 +1,11 @@ +export default { + type: "app", + app: "pro_ledger", + propDefinitions: {}, + methods: { + // this.$auth contains connected account data + authKeys() { + console.log(Object.keys(this.$auth)); + }, + }, +}; diff --git a/components/relavate/README.md b/components/relavate/README.md new file mode 100644 index 0000000000000..b3a0422456c4f --- /dev/null +++ b/components/relavate/README.md @@ -0,0 +1,14 @@ +# Overview + +The Relavate API provides tools for managing customer relations and sales efforts, enabling users to automate and optimize their CRM activities. With capabilities for managing contacts, companies, deals, and tasks, the API can serve as a backbone for CRM automation, enhancing customer interaction and business processes efficiency. On Pipedream, you can leverage these functionalities to create advanced workflows, integrate with other apps, and automate tasks based on events from the Relavate system or external triggers. + +# Example Use Cases + +- **Automated Lead Capture and Follow-Up**: + Automatically add new leads captured from various sources like web forms (using Typeform or Google Forms) directly into Relavate as contacts. Set up subsequent automated emails (using SendGrid or Mailgun) to these contacts to ensure timely follow-up, enhancing lead engagement without manual intervention. + +- **Deal Progress Notification**: + Monitor changes in deal stages within Relavate and automatically send notifications via Slack or email whenever a deal progresses or closes. This keeps teams informed in real-time, improving coordination and response times in dynamic sales environments. + +- **Task Synchronization Across Platforms**: + Sync tasks and reminders from Relavate with other project management tools like Asana or Trello. Whenever a task is updated or a new task is created in Relavate, reflect these changes in the chosen project management tool, ensuring consistency and visibility across different platforms and teams. diff --git a/components/relavate/actions/create-affiliate-lead/create-affiliate-lead.mjs b/components/relavate/actions/create-affiliate-lead/create-affiliate-lead.mjs new file mode 100644 index 0000000000000..b2cf19aab6b6e --- /dev/null +++ b/components/relavate/actions/create-affiliate-lead/create-affiliate-lead.mjs @@ -0,0 +1,100 @@ +import relavate from "../../relavate.app.mjs"; + +export default { + key: "relavate-create-affiliate-lead", + name: "Create Affiliate Lead", + description: "This component enables you to create a new affiliate lead for marketing. [See the documentation](https://api.relavate.co/#2a307851-9d54-42ea-9f54-3fb600b152a5)", + version: "0.0.1", + type: "action", + props: { + relavate, + campaignId: { + propDefinition: [ + relavate, + "campaignId", + ], + reloadProps: true, + }, + status: { + type: "string", + label: "Status", + description: "The status of the lead being created", + options: [ + "Pending", + "Won", + ], + }, + client: { + type: "string", + label: "Client", + description: "The company or name of the client", + }, + }, + async additionalProps() { + const props = {}; + const partnerType = this.relavate.getPartnerType(); + if (partnerType === "partner") { + props.vendorId = { + type: "string", + label: "Vendor ID", + description: "The ID of the vendor", + options: async () => await this.getVendorIdPropOptions(), + }; + } + if (partnerType === "vendor") { + props.partnerId = { + type: "string", + label: "Partner ID", + description: "The ID of the partner", + options: async () => await this.getPartnerIdPropOptions(), + }; + } + return props; + }, + methods: { + async getVendorIdPropOptions() { + const vendors = await this.relavate.listVendors(); + const vendorIds = vendors?.map(({ vendorID }) => vendorID ); + const options = []; + for (const vendorId of vendorIds) { + const { name } = await this.relavate.getVendor({ + vendorId, + }); + options.push({ + value: vendorId, + label: name, + }); + } + return options; + }, + async getPartnerIdPropOptions() { + const partners = await this.relavate.listPartners(); + const partnerIds = partners?.map(({ partnerID }) => partnerID ); + const options = []; + for (const partnerId of partnerIds) { + const { name } = await this.relavate.getPartner({ + partnerId, + }); + options.push({ + value: partnerId, + label: name, + }); + } + return options; + }, + }, + async run({ $ }) { + const response = await this.relavate.createAffiliateLead({ + $, + params: { + campaignID: this.campaignId, + client: this.client, + status: this.status, + vendorID: this.vendorId, + partnerID: this.partnerId, + }, + }); + $.export("$summary", `Created affiliate lead with ID: ${response.id}`); + return response; + }, +}; diff --git a/components/relavate/package.json b/components/relavate/package.json index 9bbaf81006d6a..c3b9d59277f2b 100644 --- a/components/relavate/package.json +++ b/components/relavate/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/relavate", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Relavate Components", "main": "relavate.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^1.6.5" } -} \ No newline at end of file +} diff --git a/components/relavate/relavate.app.mjs b/components/relavate/relavate.app.mjs index 758274fee0919..3b2d691a52c37 100644 --- a/components/relavate/relavate.app.mjs +++ b/components/relavate/relavate.app.mjs @@ -1,11 +1,99 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "relavate", - propDefinitions: {}, + propDefinitions: { + campaignId: { + type: "string", + label: "Campaign ID", + description: "The ID of the campaign", + async options() { + const campaigns = await this.listCampaigns(); + return campaigns?.map(({ + id: value, name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://app.relavate.co/api"; + }, + _makeRequest(opts = {}) { + const { + $ = this, + path, + ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + url: `${this._baseUrl()}${path}`, + headers: { + "X-key": this.$auth.api_key, + "X-secret": this.$auth.api_secret, + "X-partnerType": this.$auth.partner_type, + }, + }); + }, + getPartnerType() { + return this.$auth.partner_type; + }, + getVendor({ + vendorId, ...opts + }) { + return this._makeRequest({ + path: `/vendors/${vendorId}`, + ...opts, + }); + }, + getPartner({ + partnerId, ...opts + }) { + return this._makeRequest({ + path: `/partners/${partnerId}`, + ...opts, + }); + }, + listVendors(opts = {}) { + return this._makeRequest({ + path: "/partners/vendors/all", + ...opts, + }); + }, + listPartners(opts = {}) { + return this._makeRequest({ + path: "/vendors/partners/all", + ...opts, + }); + }, + listCampaigns(opts = {}) { + return this._makeRequest({ + path: "/affiliate-campaigns", + ...opts, + }); + }, + listAffiliateLeads(opts = {}) { + return this._makeRequest({ + path: "/affiliate-leads", + ...opts, + }); + }, + listReferrals(opts = {}) { + return this._makeRequest({ + path: "/referrals", + ...opts, + }); + }, + createAffiliateLead(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/affiliate-leads", + ...opts, + }); }, }, -}; \ No newline at end of file +}; diff --git a/components/relavate/sources/common/base.mjs b/components/relavate/sources/common/base.mjs new file mode 100644 index 0000000000000..ecdedc29dfc1a --- /dev/null +++ b/components/relavate/sources/common/base.mjs @@ -0,0 +1,57 @@ +import relavate from "../../relavate.app.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + props: { + relavate, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + _getStartDate() { + return this.db.get("startDate") || this._today(); + }, + _setStartDate(startDate) { + this.db.set("startDate", startDate); + }, + _today() { + return new Date().toISOString() + .split("T")[0]; + }, + generateMeta(item) { + return { + id: item.id, + summary: this.getSummary(item), + ts: Date.parse(item.created_at), + }; + }, + getParams() { + return {}; + }, + getResourceFn() { + throw new Error("getResourceFn is not implemented"); + }, + getSummary() { + throw new Error("getSummary is not implemented"); + }, + }, + async run() { + const resourceFn = this.getResourceFn(); + const params = this.getParams(); + const results = await resourceFn({ + params, + }); + if (!results?.length) { + return; + } + for (const item of results) { + const meta = this.generateMeta(item); + this.$emit(item, meta); + } + }, +}; diff --git a/components/relavate/sources/new-affiliate-campaign/new-affiliate-campaign.mjs b/components/relavate/sources/new-affiliate-campaign/new-affiliate-campaign.mjs new file mode 100644 index 0000000000000..9b8ba0c3e4891 --- /dev/null +++ b/components/relavate/sources/new-affiliate-campaign/new-affiliate-campaign.mjs @@ -0,0 +1,22 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + type: "source", + key: "relavate-new-affiliate-campaign", + name: "New Affiliate Campaign", + description: "Emit new event when a new affiliate campaign is created.", + version: "0.0.1", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.relavate.listCampaigns; + }, + getSummary(campaign) { + return `New campaign: ${campaign.name}`; + }, + }, + sampleEmit, +}; diff --git a/components/relavate/sources/new-affiliate-campaign/test-event.mjs b/components/relavate/sources/new-affiliate-campaign/test-event.mjs new file mode 100644 index 0000000000000..ed0d08473883d --- /dev/null +++ b/components/relavate/sources/new-affiliate-campaign/test-event.mjs @@ -0,0 +1,15 @@ +export default { + "id": 4, + "partnerID": 2, + "vendorID": 4, + "name": "Tracking link campaign", + "type": "Link", + "created_at": "2023-10-17T10:23:37.000000Z", + "updated_at": "2023-10-17T10:23:37.000000Z", + "status": "Active", + "campaignBaseURL": "https://nichols.com", + "campaignUrlPath": null, + "linkFormat": "vendorWebsite", + "couponCode": null, + "subtitle": null +} \ No newline at end of file diff --git a/components/relavate/sources/new-referral/new-referral.mjs b/components/relavate/sources/new-referral/new-referral.mjs new file mode 100644 index 0000000000000..2e29ae0207d99 --- /dev/null +++ b/components/relavate/sources/new-referral/new-referral.mjs @@ -0,0 +1,39 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "relavate-new-referral", + name: "New Referral", + description: "Emit new event when a new referral is created in Relavate", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + _getStartDate() { + return this.db.get("startDate") || this._today(); + }, + _setStartDate(startDate) { + this.db.set("startDate", startDate); + }, + _today() { + return new Date().toISOString() + .split("T")[0]; + }, + getResourceFn() { + return this.relavate.listReferrals; + }, + getParams() { + const startDate = this._getStartDate(); + this._setStartDate(this._today()); + return { + startDate, + }; + }, + getSummary(referral) { + return `New referral with ID ${referral.id}`; + }, + }, + sampleEmit, +}; diff --git a/components/relavate/sources/new-referral/test-event.mjs b/components/relavate/sources/new-referral/test-event.mjs new file mode 100644 index 0000000000000..9a2b1a30030c5 --- /dev/null +++ b/components/relavate/sources/new-referral/test-event.mjs @@ -0,0 +1,19 @@ +export default { + "id": 4, + "vendorID": 4, + "partnerID": 2, + "company": "Sample Company", + "status": "New", + "revenue": 5000, + "frequency": "Annual", + "dateAdded": "2023-05-20", + "comment": "{\"ops\":[{\"insert\":\"This is a sample comment\"}]}", + "clientContactID": 7, + "created_at": "2023-10-17T09:52:11.000000Z", + "updated_at": "2023-10-17T09:54:28.000000Z", + "wonDate": null, + "lostDate": "2023-10-17", + "annualRevenue": 5000, + "lostID": 10, + "comment_text": "This is a sample comment" +} \ No newline at end of file diff --git a/components/sare/README.md b/components/sare/README.md new file mode 100644 index 0000000000000..ed76ff00fec12 --- /dev/null +++ b/components/sare/README.md @@ -0,0 +1,11 @@ +# Overview + +The SARE API provides robust tools for email marketing and automation, enabling users to manage subscribers, send emails, and analyze campaigns directly via API. By leveraging SARE's features on Pipedream, you can automate workflows involving email operations, integrate with other platforms for enhanced data handling and customer engagement, and streamline communications in various marketing campaigns. This integration can particularly benefit marketers looking to automate responses based on subscriber behavior or integrate email marketing metrics with other business tools. + +# Example Use Cases + +- **Automated Welcome Email Sequence**: Trigger a workflow on Pipedream when a new user signs up via your app, using the Shopify or WordPress trigger. The workflow would then use the SARE API to add the subscriber to a specific mailing list and send a series of pre-defined welcome emails over several days, enhancing user engagement right from the start. + +- **User Feedback Collection Campaign**: After a customer purchases a product, use a Pipedream workflow triggered by a Stripe or PayPal purchase event. This workflow could utilize the SARE API to send a personalized email requesting product feedback or a review, thereby increasing direct interaction and gathering valuable customer insights. + +- **Re-engagement Campaigns for Dormant Users**: Set up a workflow triggered by a custom cron job that checks for users who haven't interacted with your service for a set period, perhaps 6 months. Using the SARE API through Pipedream, you can automatically send re-engagement emails offering special promotions or updates about new features, aiming to reactivate these dormant users. diff --git a/components/scrapein_/README.md b/components/scrapein_/README.md new file mode 100644 index 0000000000000..92200206baeef --- /dev/null +++ b/components/scrapein_/README.md @@ -0,0 +1,11 @@ +# Overview + +The Soax API provides access to a robust proxy and scraping service that allows users to gather data efficiently and safely from across the web. By leveraging Soax’s rotating proxies, you can access and retrieve data without triggering anti-scraping measures, making it useful for tasks like competitive analysis, market research, SEO monitoring, and more. Integrating Soax with Pipedream enhances these capabilities by automating data collection workflows, enriching the data with other services, and triggering actions based on the retrieved data. + +# Example Use Cases + +- **Market Trends Analysis**: Automate the periodic collection of pricing and product availability data from e-commerce sites using Soax’s rotating proxies. Use Pipedream to schedule these requests, process the data, and store the results in a Google Sheets document for further analysis and trend spotting. + +- **SEO Monitoring**: Set up a workflow on Pipedream using Soax to regularly scrape search engine results for specified keywords related to your business. Analyze changes in search rankings and automatically send this data to a Slack channel to keep your marketing team informed in real-time. + +- **Social Media Sentiment Analysis**: Use the Soax API on Pipedream to scrape social media platforms for mentions of your brand or product. Automatically process this data through a sentiment analysis tool and push the results to a CRM like HubSpot to help your customer service team respond proactively to customer feedback. diff --git a/components/sendgrid/README.md b/components/sendgrid/README.md index da3584d5bd307..2eb53d88be80b 100644 --- a/components/sendgrid/README.md +++ b/components/sendgrid/README.md @@ -9,3 +9,55 @@ The Twilio SendGrid API opens up a world of possibilities for email automation, - **Email Campaign Stats to Google Sheets**: After sending an email campaign through SendGrid, you may want to analyze the performance data. Pipedream can automatically fetch the stats, like opens, clicks, and bounces, and log them into a Google Sheet for easy tracking and visualization. - **E-commerce Order Confirmation and Follow-up**: When a new order is received in an e-commerce platform like Shopify, you can use Pipedream to trigger an order confirmation email via SendGrid. After a set period, you can follow up with another email asking for feedback or offering a discount on future purchases. + +# Getting Started + +First, open the SendGrid console and log in. + +Then open the [Integration Guide](https://app.sendgrid.com/guide/integrate) and select the **Web API** option. + +![Open the Twilio SendGrid Integration Guide to begin the process of creating an API key to connect with Pipedream](https://res.cloudinary.com/pipedreamin/image/upload/v1715176223/marketplace/apps/sendgrid/CleanShot_2024-05-07_at_15.17.56_2x_zhynua.png) + +Then choose any language from the next menu. Choose any programming language; this selection only affects the example code shown. The key step is obtaining the API key. + +After picking a language, you'll be prompted to generate an API key, we recommend naming it `Pipedream` for easy identification. + +![Name the API key "pipedream" and then after clicking the Generate button, copy the API key and paste it into Pipedream](https://res.cloudinary.com/pipedreamin/image/upload/v1715176222/marketplace/apps/sendgrid/CleanShot_2024-05-07_at_15.19.44_2x_hcduuf.png) + +After creating the API key, copy it and paste it into the appropriate configuration field in a Pipedream SendGrid connected account, either through a Pipedream action/trigger or through the Connected Accounts section of the dashboard. + +You can skip the remaining steps of the Integration guide that test your API key, as these are not necessary for the integration with Pipedream. + +# Troubleshooting + +SendGrid uses standard HTTP status codes to help troubleshoot issues. + +If a SendGrid API call fails, Pipedream will show the error code which you can match with below: + +## 400 - Bad Request + +This error is shown when the request is missing data or is malformed. An example is a malformed email address; SendGrid won't accept invalid email addresses to deliver mail. + +## 401 - Unauthorized + +This means that your SendGrid API key connected to Pipedream is invalid or is missing from the request. + +Ensure the API key is copied correctly from your SendGrid console and included properly as an `Authorization` header in the format `Bearer ${your token here}`. + +## 403 - Sender email isn't verified + +This error occurs when you attempt to use an unverified sender email address under your account. To resolve this error, use the correct email address or verify the email address. See the [verification requirements here](https://docs.sendgrid.com/for-developers/sending-email/sender-identity/). + +## 406 - Missing Accept header + +Make sure to pass `Accept: application/json` in the headers of your HTTP request in order for SendGrid to process your request correctly. + +## 429 - Rate Limit + +This error is thrown when you're sending too many API requests in a short window. The `X-RateLimit-Remaining` and `X-RateLimit-Reset` headers in the response give you the amount of requests remaining in the current rate limit window. + +You can use [Concurrency and Throttling](https://pipedream.com/docs/workflows/concurrency-and-throttling) in your workflow to throttle how quickly your workflow processes new events. + +## 500 - Internal Server Error + +This means that SendGrid is having issues processing requests; check their [status page](https://status.sendgrid.com/) for updates on the service. For these issues, contact SendGrid directly. diff --git a/components/servicenow/README.md b/components/servicenow/README.md index 7085fa99f56de..7f98c15ad2499 100644 --- a/components/servicenow/README.md +++ b/components/servicenow/README.md @@ -1,68 +1,61 @@ # Overview -The ServiceNow API enables developers to tap into the robust capabilities of ServiceNow's IT service management platform. With the API, you can create, read, update, and delete records, manage workflows, and integrate with other services. By leveraging these capabilities, you can automate routine tasks, sync data across multiple platforms, and enhance operational efficiencies. +The ServiceNow API lets developers access and manipulate records, manage workflows, and integrate with other services on its IT service management platform. These capabilities support automating tasks, syncing data across platforms, and boosting operational efficiencies. # Getting Started -Before you can use the ServiceNow REST API from a workflow, you need to configure an OAuth app in your ServiceNow instance that will grant access tokens to your users and authenticate requests to its REST API. +Before using the ServiceNow REST API from a workflow, configure **two** OAuth apps in your ServiceNow instance. These apps will grant access tokens to your users and authenticate requests to its REST API. -1. In your ServiceNow instance, visit the **Application Registry** and create a new app, choosing the **Create an OAuth API endpoint for external clients** option. -2. Name it something memorable, then leave every other field blank or keep the defaults, except for the **Redirect URL**, which should be: `https://api.pipedream.com/connect/oauth/oa_g2oiqA/callback`. Your app should look something like this: +## Create an External Client OAuth App -
-ServiceNow OAuth app config -
+First, sign into your [ServiceNow Developer Portal](https://developer.servicenow.com/dev.do#!/home) account to create or access an instance. -1. Next, you'll need to copy the client ID and secret generated in **Step 2**, and add another app. This time, select the option to **Connect to a third party OAuth Provider**. -2. Name this app something like **Pipedream OAuth Validator**, and add the client ID / secret from **Step 2**. Change the grant type to **Authorization Code**, and set the **Token URL** to `oauth_token.do` (without any hostname, this refers to the current instance). Finally, add the same **Redirect URL** as you did above: `https://api.pipedream.com/connect/oauth/oa_g2oiqA/callback`. This app's configuration should look something like this when complete: +1. Go to **System OAuth > Application Registry**. -
-ServiceNow OAuth validator app config -
+ ![Find the OAuth Client option under the ServiceNow application registry](https://res.cloudinary.com/pipedreamin/image/upload/v1715264549/marketplace/apps/servicenow/CleanShot_2024-05-09_at_10.18.36_ntausg.png) -1. Visit [https://pipedream.com/accounts](https://pipedream.com/accounts), and click the button labeled **Click Here to Connect An App** in the top-right. In the modal that appears, search for **ServiceNow** and select it. You'll be prompted to enter the client ID and client secret from **Step 2** above, as well as the name of your instance. The instance name is the _host_ portion of your instance's URL: that is, the `dev98042` in `https://dev98042.service-now.com/`. +2. Create a new app by selecting **New** in the top right corner. -
-Pipedream app config -
+ ![Create a new ServiceNow application under the OAuth Clients section in the Application Registry](https://res.cloudinary.com/pipedreamin/image/upload/v1715265062/marketplace/apps/servicenow/CleanShot_2024-05-09_at_10.30.51_jpi4ct.png) -6. Press **Connect** in the bottom-right of the modal. This should open up a new window asking you to login to your ServiceNow instance. This authorizes Pipedream's access to your ServiceNow account, and you should be ready to connect to your instance's REST API! +3. Choose **Create an OAuth API endpoint for external clients**: -Collectively, the two apps you configured in your ServiceNow instance allow your instance to issue new OAuth access tokens for the user who authenticated in **Step 6**. This allows Pipedream to retrieve a fresh access token before it makes requests to the ServiceNow REST API. + ![Create a new app, and make sure to choose the OAuth API endpoint for external clients option](https://res.cloudinary.com/pipedreamin/image/upload/v1715264615/marketplace/apps/servicenow/CleanShot_2024-05-09_at_10.19.09_pgezqf.png) -## ServiceNow Authorization Reference +4. Name your app, such as `Pipedream`. Use the default settings but specify the **Redirect URL**: `https://api.pipedream.com/connect/oauth/oa_g2oiqA/callback`. -[This ServiceNow doc](https://docs.servicenow.com/bundle/orlando-platform-administration/page/administer/security/concept/c_OAuthAuthorizationCodeFlow.html) describes the general flow we ask you to implement above. In that doc, the app you create in **Step 2** is referred to as the **client application**, and the app in **Step 4** is referred to as the **OAuth provider application registry record**. +5. Click **Create**. It will appear in the Application Registry once created. -## Additional Guidance For Hardened or Mature Instances ### + ![You should see the Pipedream app listed in the ServiceNow Registry after making those changes](https://res.cloudinary.com/pipedreamin/image/upload/v1715264960/marketplace/apps/servicenow/CleanShot_2024-05-09_at_10.21.12_iwlxgq.png) -The instructions above are likely to work on a fresh, out-of-the-box instance but may work imperfectly on ServiceNow instances that have been customized or have applied various security hardening recommendations such as the [explicit roles plugin](https://docs.servicenow.com/en-US/bundle/vancouver-platform-security/page/administer/security/reference/explicit-role-plugin.html). +### Create the OAuth Validator app -Symptoms of problems here may include getting a **504 Gateway Time-out** error when completing step 6 above. If you manually test the connection deatails in a tool like Postman, you may get an error like this: +1. Copy the client ID and secret from the `Pipedream` app you created above. +2. Name this app `Pipedream OAuth Validator` and add the previously copied client ID and secret. +3. Set the grant type to **Authorization Code** and the **Token URL** to `oauth_token.do`. +4. Use the same **Redirect URL** as before. -``` -{ - "error_description":"access_denied", - "error":"server_error" -} -``` +5. Visit [Pipedream's account page](https://pipedream.com/accounts), and click **Click Here to Connect An App**. Search for **ServiceNow** and select it. Enter the client ID, client secret, and your instance name (e.g., `dev98042` from `https://dev98042.service-now.com/`). -In these instances, the following tips may be helpful: +6. Press **Connect**. A new window will prompt you to login to your ServiceNow instance, authorizing Pipedream's access to the ServiceNow REST API. -* Create a dedicated role for this purpose, and assign it to a service account that is only used for this purpose. You should not set it for web service access only, since interactive access is required to complete Pipedream setup. -* Ensure that the dedicated role has ACLs configured to allow read for the oauth_credential table - both the table and table.\* for all fields. -* Assign snc_internal to this service account. This is important if you are using the explicit roles plugin as part of instance security hardening. +## ServiceNow Authorization Reference -Finally, while not required, you should also check that the role has associated ACLs for any tables you want to work with; by default they may if you use snc_internal, but some fields extended from task or other tables may require additional ACLs based on your instance's configuration. +[This ServiceNow doc](https://docs.servicenow.com/bundle/orlando-platform-administration/page/administer/security/concept/c_OAuthAuthorizationCodeFlow.html) outlines the flow you should implement. -# Example Use Cases +## Additional Guidance For Hardened or Mature Instances -- **Incident Management Automation**: Automatically create incidents in ServiceNow when critical alerts are triggered in monitoring tools like Datadog or New Relic. This ensures timely attention to potential issues and streamlines the incident response process. +The standard instructions may not apply perfectly to customized or hardened ServiceNow instances. If you face a **504 Gateway Time-out** error or similar, consider these tips: -- **HR Onboarding Workflow**: When a new employee is added to an HR system like Workday, trigger a workflow in Pipedream that creates a new user account in ServiceNow, assigns the necessary roles, and initiates onboarding tasks. This can significantly cut the time needed to get a new team member up and running. +* Assign a dedicated role and service account for this integration. +* Ensure the role has ACLs configured for the `oauth_credential` table and other necessary tables. -- **Customer Support Ticket Sync**: Synchronize customer support tickets between ServiceNow and a CRM platform like Salesforce. When a ticket is updated in ServiceNow, automatically reflect those changes in Salesforce to keep all teams aligned and informed about customer issues, ensuring a unified approach to customer service. +# Example Use Cases +- **Incident Management Automation**: Automatically create incidents in ServiceNow from alerts in Datadog or New Relic. +- **HR Onboarding Workflow**: Trigger a Pipedream workflow to set up new employee accounts in ServiceNow from HR systems like Workday. +- **Customer Support Ticket Sync**: Keep customer support tickets synced between ServiceNow and CRM platforms like Salesforce. # Troubleshooting -If you're getting a **504 Gateway Time-out** error when attempting to connect your ServiceNow account, review the section above on "Additional Guidance For Hardened or Mature Instances". + +If you encounter a **504 Gateway Time-out** error, refer to the 'Additional Guidance For Hardened or Mature Instances' section for solutions. diff --git a/components/sigma/README.md b/components/sigma/README.md new file mode 100644 index 0000000000000..b5f82cc9d3e51 --- /dev/null +++ b/components/sigma/README.md @@ -0,0 +1,11 @@ +# Overview + +The Sigma API allows users to automate and integrate their business intelligence directly from Sigma Computing into other services and workflows. Sigma is a powerful tool for creating live, actionable dashboards and reports from cloud data warehouses. With Pipedream, users can harness this capability to trigger workflows based on Sigma events, manipulate and analyze Sigma data, and synchronize insights across other apps, enhancing data-driven decision-making processes. + +# Example Use Cases + +- **Automatically Distribute Sigma Reports**: Set up a workflow on Pipedream where Sigma reports are automatically fetched and emailed to stakeholders at regular intervals or in response to specific data changes. This can be integrated with email platforms like SendGrid to streamline communication. + +- **Real-time Slack Notifications for Sigma Dashboard Updates**: Create a Pipedream workflow that monitors changes in Sigma dashboards or reports and sends notifications through Slack. This helps teams stay updated on the latest insights without constantly checking Sigma. + +- **Sync Sigma Data with Google Sheets for Further Analysis**: Use Pipedream to develop a workflow where data from Sigma is automatically extracted and pushed into Google Sheets. This allows for additional data manipulation or sharing within teams who prefer accessing data through Google Sheets. diff --git a/components/skyciv/README.md b/components/skyciv/README.md new file mode 100644 index 0000000000000..4f3a34395d8a6 --- /dev/null +++ b/components/skyciv/README.md @@ -0,0 +1,11 @@ +# Overview + +SkyCiv API offers engineering analysis and design capabilities within the cloud, allowing structural calculations and manipulations over the web. By integrating SkyCiv with Pipedream, users can automate complex engineering workflows, connect with other apps for enhanced data handling, and trigger actions based on structural analysis results. This seamless integration facilitates real-time decision-making and can significantly streamline operations in construction, architecture, and engineering projects. + +# Example Use Cases + +- **Automated Load Analysis Report Generation**: Trigger a SkyCiv analysis for a new project design uploaded to Google Drive. Once the analysis is complete, use the results to generate a report with Google Docs, and email it directly to stakeholders using Gmail. This workflow ensures that project updates and critical analyses are communicated efficiently and without manual intervention. + +- **Project Status Updates via Slack**: Configure a workflow where SkyCiv sends structural analysis updates to Slack. Whenever a structural analysis reaches a certain threshold or fails, a notification is pushed to a dedicated Slack channel, keeping the team informed and responsive to potential issues. This automation enhances project monitoring and team communication. + +- **Real-time Material Cost Estimation**: Integrate SkyCiv with a financial app like QuickBooks to automatically estimate and update the cost implications of various material choices based on the structural analysis results. Whenever a new analysis is run, update the budget in QuickBooks to reflect changes in material costs, helping maintain financial accuracy throughout the project lifecycle. diff --git a/components/slack/README.md b/components/slack/README.md index 997850e1c1d99..72f31a7d6dcc7 100644 --- a/components/slack/README.md +++ b/components/slack/README.md @@ -1,6 +1,6 @@ # Overview -The Pipedream Slack app enables you to build event-driven workflows that interact with the Slack API. When you authorize the Pipedream app's access to your workspace, you can use [Pipedream workflows](/workflows/) to perform common Slack [actions](#workflow-actions), or [write your own code](/code/) against the Slack API. +The Pipedream Slack app enables you to build event-driven workflows that interact with the Slack API. Once you authorize the Pipedream app's access to your workspace, you can use [Pipedream workflows](/workflows/) to perform common Slack [actions](#workflow-actions) or [write your own code](/code/) against the Slack API. The Pipedream Slack app is not a typical app. You don't interact with it directly as a bot, and it doesn't add custom functionality to your workspace out of the box. It makes it easier to automate anything you'd typically use the Slack API for, using Pipedream workflows. @@ -13,38 +13,71 @@ The Pipedream Slack app is not a typical app. You don't interact with it directl ## Should I use the Slack or Slack Bot app on Pipedream? -The Slack app is the easiest and most convienent option to get started. It installs the official Pipedream bot into your Slack workspace with just a few clicks. +The Slack app is the easiest and most convenient option to get started. It installs the official Pipedream bot into your Slack workspace with just a few clicks. However, if you'd like to use your own bot registered with the [Slack API](https://api.slack.com), you can use the [Slack Bot app](https://pipedream.com/apps/slack-bot) instead. -The Slack Bot requires a bot token to allow your Pipedream workflows to authenticate as your bot. The extra set up steps allow you to list your custom bot on the Slack Marketplace, or install the bot on other workspaces as your bot's name instead of as Pipedream. +The Slack Bot requires a bot token to allow your Pipedream workflows to authenticate as your bot. The extra setup steps allow you to list your custom bot on the Slack Marketplace or install the bot on other workspaces as your bot's name instead of as Pipedream. ## Accounts 1. Visit [https://pipedream.com/accounts](https://pipedream.com/accounts). 2. Click on the **Click Here To Connect An App** button in the top-right. -3. Search for "Slack" among the list of apps, and select it. +3. Search for "Slack" among the list of apps and select it. 4. This will open a new window asking you to allow Pipedream access to your Slack workspace. Choose the right workspace where you'd like to install the app, then click **Allow**. -5. That's it! You can now use this Slack account in any [actions](#workflow-actions), or [link it to any code step](/connected-accounts/#connecting-accounts). +5. That's it! You can now use this Slack account in any [actions](#workflow-actions) or [link it to any code step](/connected-accounts/#connecting-accounts). ## Within a workflow 1. [Create a new workflow](https://pipedream.com/new). 2. Select your trigger (HTTP, Cron, etc.). -3. Click on the **+** button below the trigger step, and search for "Slack". +3. Click the **+** button below the trigger step and search for "Slack". 4. Select the **Send a Message** action. 5. Click the **Connect Account** button near the top of the step. This will prompt you to select any existing Slack accounts you've previously authenticated with Pipedream, or you can select a **New** account. Clicking **New** opens a new window asking you to allow Pipedream access to your Slack workspace. Choose the right workspace where you'd like to install the app, then click **Allow**. -6. That's it! You can now connect to the Slack API using any of the Slack actions within a Pipedream workflow. +6. After allowing access, you can connect to the Slack API using any of the Slack actions within a Pipedream workflow. # Example Use Cases -- **Automated Standup Reports**: Trigger a workflow on Pipedream to collect standup updates from team members within a Slack channel at a scheduled time. The workflow can compile the updates into a formatted report and post it to a designated channel or send it via email using an app like SendGrid. +- **Automated Standup Reports**: Trigger a workflow on Pipedream to collect standup updates from team members within a Slack channel at a scheduled time. The workflow compiles updates into a formatted report and posts it to a designated channel or sends it via email using an app like SendGrid. -- **Customer Support Ticketing**: Use Pipedream to monitor a Slack support channel for new messages. On detecting a message, the workflow can create a ticket in a customer support platform like Zendesk or Jira. It can also format and forward critical information back to the Slack channel to keep the team updated. +- **Customer Support Ticketing**: Use Pipedream to monitor a Slack support channel for new messages. On detecting a message, the workflow creates a ticket in a customer support platform like Zendesk or Jira. It can also format and forward critical information back to the Slack channel to keep the team updated. - **Real-time CRM Updates**: Configure a Pipedream workflow to listen for specific trigger words in sales-related Slack channels. When mentioned, the workflow fetches corresponding data from a CRM tool like Salesforce and posts the latest deal status or customer information in the Slack conversation for quick reference. # Troubleshooting -Please [reach out](https://pipedream.com/support/) to the Pipedream team with any technical issues or questions about the Slack integration. We're happy to help! +## Error Responses + +Slack's API will always return JSON, regardless if the request was successfully processed or not. + +Each JSON response includes an `ok` boolean property indicating whether the action succeeded or failed. + +Example of a successful response: + +```json +{ + "ok": true +} +``` + +If the `ok` property is false, Slack will also include an `error` property with a short machine-readable code that describes the error. + +Example of a failure: +```json +{ + "ok": false, + "error": "invalid_parameters" +} +``` + +Additionally, if the action is successful, there's still a chance of a `warning` property in the response. This may contain a comma-separated list of warning codes. + +Example of a successful response, but with warnings: + +```json +{ + "ok": true, + "warnings": "invalid_character_set" +} +``` diff --git a/components/snowflake/package.json b/components/snowflake/package.json index edc042320cf11..4b85ca566db5e 100644 --- a/components/snowflake/package.json +++ b/components/snowflake/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/snowflake", - "version": "0.9.6", + "version": "0.9.7", "description": "Pipedream Snowflake Components", "main": "snowflake.app.mjs", "keywords": [ diff --git a/components/snowflake/sources/change-to-warehouse/change-to-warehouse.mjs b/components/snowflake/sources/change-to-warehouse/change-to-warehouse.mjs index b892f5cd7de76..9d075b43d14a7 100644 --- a/components/snowflake/sources/change-to-warehouse/change-to-warehouse.mjs +++ b/components/snowflake/sources/change-to-warehouse/change-to-warehouse.mjs @@ -17,7 +17,7 @@ export default { // eslint-disable-next-line name: "New, Updated, or Deleted Warehouse", description: "Emit new events when a warehouse is created, altered, or dropped", - version: "0.0.10", + version: "0.0.11", async run() { await this.watchObjectsAndEmitChanges("WAREHOUSE", this.warehouses, this.queryTypes); }, diff --git a/components/snowflake/sources/common.mjs b/components/snowflake/sources/common.mjs index 414fbfd5400e9..bf9d119f5725e 100644 --- a/components/snowflake/sources/common.mjs +++ b/components/snowflake/sources/common.mjs @@ -21,12 +21,19 @@ export default { _setLastMaxTimestamp(lastMaxTimestamp) { this.db.set("lastMaxTimestamp", lastMaxTimestamp); }, + async streamToArray(stream) { + const result = []; + for await (const item of stream) { + result.push(item); + } + return result; + }, async processCollection(statement, timestamp) { const rowStream = await this.snowflake.getRows(statement); - this.$emit({ - rows: rowStream, + const rows = await this.streamToArray(rowStream); + this.$emit(rows, this.generateMetaForCollection({ timestamp, - }); + })); }, async processSingle(statement, timestamp) { let lastResultId; diff --git a/components/snowflake/sources/failed-task-in-schema/failed-task-in-schema.mjs b/components/snowflake/sources/failed-task-in-schema/failed-task-in-schema.mjs index 66938729e30af..df552a146f2e6 100644 --- a/components/snowflake/sources/failed-task-in-schema/failed-task-in-schema.mjs +++ b/components/snowflake/sources/failed-task-in-schema/failed-task-in-schema.mjs @@ -39,7 +39,7 @@ export default { // eslint-disable-next-line name: "Failed Task in Schema", description: "Emit new events when a task fails in a database schema", - version: "0.0.11", + version: "0.0.12", async run() { await this.emitFailedTasks({ database: this.database, diff --git a/components/snowflake/sources/new-database/new-database.mjs b/components/snowflake/sources/new-database/new-database.mjs index 4e85882b49c59..1f301cc730d53 100644 --- a/components/snowflake/sources/new-database/new-database.mjs +++ b/components/snowflake/sources/new-database/new-database.mjs @@ -7,7 +7,7 @@ export default { key: "snowflake-new-database", name: "New Database", description: "Emit new event when a database is created", - version: "0.0.9", + version: "0.0.10", methods: { ...common.methods, alwaysRunInSingleProcessMode() { diff --git a/components/snowflake/sources/new-role/new-role.mjs b/components/snowflake/sources/new-role/new-role.mjs index 7670c3452bd8b..1e9c167913de0 100644 --- a/components/snowflake/sources/new-role/new-role.mjs +++ b/components/snowflake/sources/new-role/new-role.mjs @@ -6,7 +6,7 @@ export default { key: "snowflake-new-role", name: "New Role", description: "Emit new event when a role is created", - version: "0.0.8", + version: "0.0.9", methods: { ...common.methods, alwaysRunInSingleProcessMode() { diff --git a/components/snowflake/sources/new-row/new-row.mjs b/components/snowflake/sources/new-row/new-row.mjs index 9eb9951a22242..c122251f01dd0 100644 --- a/components/snowflake/sources/new-row/new-row.mjs +++ b/components/snowflake/sources/new-row/new-row.mjs @@ -6,7 +6,7 @@ export default { key: "snowflake-new-row", name: "New Row", description: "Emit new event when a row is added to a table", - version: "0.1.10", + version: "0.1.11", methods: { ...common.methods, async getStatement(lastResultId) { diff --git a/components/snowflake/sources/new-schema/new-schema.mjs b/components/snowflake/sources/new-schema/new-schema.mjs index 94e8dd1ca00c7..8b47f6e0a1985 100644 --- a/components/snowflake/sources/new-schema/new-schema.mjs +++ b/components/snowflake/sources/new-schema/new-schema.mjs @@ -7,7 +7,7 @@ export default { key: "snowflake-new-schema", name: "New Schema", description: "Emit new event when a schema is created", - version: "0.0.9", + version: "0.0.10", methods: { ...common.methods, alwaysRunInSingleProcessMode() { diff --git a/components/snowflake/sources/new-table/new-table.mjs b/components/snowflake/sources/new-table/new-table.mjs index 796467ca029dc..8e22bf8e515be 100644 --- a/components/snowflake/sources/new-table/new-table.mjs +++ b/components/snowflake/sources/new-table/new-table.mjs @@ -7,7 +7,7 @@ export default { key: "snowflake-new-table", name: "New Table", description: "Emit new event when a table is created", - version: "0.0.9", + version: "0.0.10", methods: { ...common.methods, alwaysRunInSingleProcessMode() { diff --git a/components/snowflake/sources/new-user/new-user.mjs b/components/snowflake/sources/new-user/new-user.mjs index e457b935e041d..fdb1a3c1c6fd7 100644 --- a/components/snowflake/sources/new-user/new-user.mjs +++ b/components/snowflake/sources/new-user/new-user.mjs @@ -6,7 +6,7 @@ export default { key: "snowflake-new-user", name: "New User", description: "Emit new event when a user is created", - version: "0.0.8", + version: "0.0.9", methods: { ...common.methods, alwaysRunInSingleProcessMode() { diff --git a/components/snowflake/sources/query-results/query-results.mjs b/components/snowflake/sources/query-results/query-results.mjs index 5c5aacbf3b77c..2d754f376e4db 100644 --- a/components/snowflake/sources/query-results/query-results.mjs +++ b/components/snowflake/sources/query-results/query-results.mjs @@ -8,7 +8,7 @@ export default { name: "New Query Results", // eslint-disable-next-line description: "Run a SQL query on a schedule, triggering a workflow for each row of results", - version: "0.1.10", + version: "0.1.11", props: { ...common.props, sqlQuery: { diff --git a/components/summit/package.json b/components/summit/package.json new file mode 100644 index 0000000000000..4bd6762f87a1f --- /dev/null +++ b/components/summit/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pipedream/summit", + "version": "0.0.1", + "description": "Pipedream Summit Components", + "main": "summit.app.mjs", + "keywords": [ + "pipedream", + "summit" + ], + "homepage": "https://pipedream.com/apps/summit", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/components/summit/summit.app.mjs b/components/summit/summit.app.mjs new file mode 100644 index 0000000000000..8a95eab6c41fd --- /dev/null +++ b/components/summit/summit.app.mjs @@ -0,0 +1,11 @@ +export default { + type: "app", + app: "summit", + propDefinitions: {}, + methods: { + // this.$auth contains connected account data + authKeys() { + console.log(Object.keys(this.$auth)); + }, + }, +}; diff --git a/components/telnyx/actions/get-message/get-message.mjs b/components/telnyx/actions/get-message/get-message.mjs index e0945662000f3..504602a73e211 100644 --- a/components/telnyx/actions/get-message/get-message.mjs +++ b/components/telnyx/actions/get-message/get-message.mjs @@ -4,7 +4,7 @@ export default { key: "telnyx-get-message", name: "Retrieve a Message", description: "Retrieve a message. See documentation [here](https://developers.telnyx.com/api/messaging/get-message)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { telnyxApp, @@ -17,7 +17,9 @@ export default { async run({ $ }) { const message = await this.telnyxApp.getMessage({ $, - id: this.id, + params: { + id: this.id, + }, }); $.export("$summary", `Successfully retrieved message ${message.data.id}.`); return message; diff --git a/components/telnyx/actions/get-messaging-profiles/get-messaging-profiles.mjs b/components/telnyx/actions/get-messaging-profiles/get-messaging-profiles.mjs index 4a64c605bdb73..d1976e06905cb 100644 --- a/components/telnyx/actions/get-messaging-profiles/get-messaging-profiles.mjs +++ b/components/telnyx/actions/get-messaging-profiles/get-messaging-profiles.mjs @@ -4,7 +4,7 @@ export default { key: "telnyx-get-messaging-profiles", name: "Get Messaging Profiles", description: "Get a list of messaging profiles. See documentation [here](https://developers.telnyx.com/api/messaging/list-messaging-profiles)", - version: "0.0.3", + version: "0.0.4", type: "action", props: { telnyxApp, diff --git a/components/telnyx/actions/get-phone-numbers/get-phone-numbers.mjs b/components/telnyx/actions/get-phone-numbers/get-phone-numbers.mjs index 11a1f2e9d58ee..14d082f54cd79 100644 --- a/components/telnyx/actions/get-phone-numbers/get-phone-numbers.mjs +++ b/components/telnyx/actions/get-phone-numbers/get-phone-numbers.mjs @@ -5,7 +5,7 @@ export default { key: "telnyx-get-phone-numbers", name: "Get Phone Numbers", description: "Get a list of phone numbers. See documentation [here](https://developers.telnyx.com/api/numbers/list-phone-numbers)", - version: "0.0.2", + version: "0.0.3", type: "action", props: { telnyxApp, diff --git a/components/telnyx/actions/send-group-message/send-group-message.mjs b/components/telnyx/actions/send-group-message/send-group-message.mjs index b1079effe0944..ab22be9c28196 100644 --- a/components/telnyx/actions/send-group-message/send-group-message.mjs +++ b/components/telnyx/actions/send-group-message/send-group-message.mjs @@ -4,7 +4,7 @@ export default { key: "telnyx-send-group-message", name: "Send Group Message", description: "Send a group MMS message. See documentation [here](https://developers.telnyx.com/api/messaging/create-group-mms-message)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { telnyxApp, diff --git a/components/telnyx/actions/send-message/send-message.mjs b/components/telnyx/actions/send-message/send-message.mjs index 523a07b4293b9..58a934d41668f 100644 --- a/components/telnyx/actions/send-message/send-message.mjs +++ b/components/telnyx/actions/send-message/send-message.mjs @@ -4,7 +4,7 @@ export default { key: "telnyx-send-message", name: "Send Message", description: "Send an SMS or MMS message. See documentation [here](https://developers.telnyx.com/docs/messaging/messages/send-message)", - version: "0.0.3", + version: "0.0.4", type: "action", props: { telnyxApp, diff --git a/components/telnyx/package.json b/components/telnyx/package.json index 9db9725b286ad..c936356473e67 100644 --- a/components/telnyx/package.json +++ b/components/telnyx/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/telnyx", - "version": "0.2.1", + "version": "0.2.2", "description": "Pipedream Telnyx Components", "main": "telnyx.app.mjs", "keywords": [ diff --git a/components/telnyx/telnyx.app.mjs b/components/telnyx/telnyx.app.mjs index 55119c994496d..ed057df87f133 100644 --- a/components/telnyx/telnyx.app.mjs +++ b/components/telnyx/telnyx.app.mjs @@ -69,7 +69,7 @@ export default { }, throwFormattedError(error) { error = error.response; - throw new Error(`${error.status} - ${error.statusText} - ${error.data.message}`); + throw new Error(`${error.status} - ${error.statusText} - ${error.data.errors[0].detail}`); }, async sendMessage(args) { return this.makeRequest({ diff --git a/components/thoughtly/actions/create-contact/create-contact.mjs b/components/thoughtly/actions/create-contact/create-contact.mjs new file mode 100644 index 0000000000000..94c48ccc79b62 --- /dev/null +++ b/components/thoughtly/actions/create-contact/create-contact.mjs @@ -0,0 +1,62 @@ +import thoughtly from "../../thoughtly.app.mjs"; + +export default { + key: "thoughtly-create-contact", + name: "Create Contact", + description: "Generates a new contact within your Thoughtly team. [See the documentation](https://api.thought.ly/docs/#/contact/post_contact_create)", + version: "0.0.1", + type: "action", + props: { + thoughtly, + phoneNumber: { + type: "string", + label: "Phone Number", + description: "The phone number of the new contact.", + }, + name: { + type: "string", + label: "Name", + description: "The name of the new contact.", + optional: true, + }, + email: { + type: "string", + label: "Email", + description: "The email of the new contact.", + optional: true, + }, + countryCode: { + type: "string", + label: "Country Code", + description: "The country code of the new contact's phone number.", + optional: true, + }, + tags: { + type: "string[]", + label: "Tags", + description: "Tags associated with the new contact.", + optional: true, + }, + attributes: { + type: "object", + label: "Attributes", + description: "Additional attributes for the new contact.", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.thoughtly.createContact({ + $, + data: { + phone_number: this.phoneNumber, + name: this.name, + email: this.email, + country_code: this.countryCode, + tags: this.tags, + attributes: this.attributes, + }, + }); + $.export("$summary", `Successfully created contact with ID: ${response.data.id}`); + return response; + }, +}; diff --git a/components/thoughtly/actions/trigger-call/trigger-call.mjs b/components/thoughtly/actions/trigger-call/trigger-call.mjs new file mode 100644 index 0000000000000..65c718ccd7637 --- /dev/null +++ b/components/thoughtly/actions/trigger-call/trigger-call.mjs @@ -0,0 +1,43 @@ +import { parseObject } from "../../common/utils.mjs"; +import thoughtly from "../../thoughtly.app.mjs"; + +export default { + key: "thoughtly-trigger-call", + name: "Trigger a Call", + description: "Triggers a call to a designated contact. [See the documentation](https://api.thought.ly/docs/#/contact/post_contact_call)", + version: "0.0.1", + type: "action", + props: { + thoughtly, + contactId: { + propDefinition: [ + thoughtly, + "contactId", + ], + }, + interviewId: { + propDefinition: [ + thoughtly, + "interviewId", + ], + }, + metadata: { + type: "object", + label: "Metadata", + description: "An object of metadata.", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.thoughtly.callContact({ + $, + data: { + contact_id: this.contactId, + interview_id: this.interviewId, + metadata: parseObject(this.metadata), + }, + }); + $.export("$summary", `Successfully triggered a call to contact ID ${this.contactId}`); + return response; + }, +}; diff --git a/components/thoughtly/common/utils.mjs b/components/thoughtly/common/utils.mjs new file mode 100644 index 0000000000000..dcc9cc61f6f41 --- /dev/null +++ b/components/thoughtly/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/thoughtly/package.json b/components/thoughtly/package.json index 2ea3785f8db6c..9a2a969102308 100644 --- a/components/thoughtly/package.json +++ b/components/thoughtly/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/thoughtly", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Thoughtly Components", "main": "thoughtly.app.mjs", "keywords": [ @@ -11,5 +11,9 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^1.6.5" } -} \ No newline at end of file +} + diff --git a/components/thoughtly/sources/new-response-instant/new-response-instant.mjs b/components/thoughtly/sources/new-response-instant/new-response-instant.mjs new file mode 100644 index 0000000000000..d384baf29957b --- /dev/null +++ b/components/thoughtly/sources/new-response-instant/new-response-instant.mjs @@ -0,0 +1,50 @@ +import thoughtly from "../../thoughtly.app.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + key: "thoughtly-new-response-instant", + name: "New Response (Instant)", + description: "Emit new event when a thoughtly gets a new response.", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + thoughtly, + http: "$.interface.http", + db: "$.service.db", + interviewId: { + propDefinition: [ + thoughtly, + "interviewId", + ], + }, + }, + hooks: { + async activate() { + await this.thoughtly.createHook({ + data: { + type: "NEW_RESPONSE", + url: this.http.endpoint, + data: this.interviewId, + }, + }); + }, + async deactivate() { + await this.thoughtly.deleteHook({ + data: { + type: "NEW_RESPONSE", + url: this.http.endpoint, + data: this.interviewId, + }, + }); + }, + }, + async run({ body }) { + this.$emit(body, { + id: body.id, + summary: `New response received with Id: ${body.id}`, + ts: Date.parse(body.created), + }); + }, + sampleEmit, +}; diff --git a/components/thoughtly/sources/new-response-instant/test-event.mjs b/components/thoughtly/sources/new-response-instant/test-event.mjs new file mode 100644 index 0000000000000..ee83232ee5bee --- /dev/null +++ b/components/thoughtly/sources/new-response-instant/test-event.mjs @@ -0,0 +1,44 @@ +export default { + "id": "12345678-1234-1234-1234-123456789012", + "created": "2024-05-14T14:27:36.575Z", + "updated": "2024-05-14T14:27:36.704Z", + "version": 2, + "interview": { + "id": "12345678" + }, + "contact": { + "id": "12345678-1234-1234-1234-123456789012", + "created": "2024-05-14T13:26:36.017Z", + "updated": "2024-05-14T13:26:36.017Z", + "version": 1, + "team_id": "12345678-1234-1234-1234-123456789012", + "status": "ACTIVE", + "name": "Contact name", + "caller_type": null, + "phone_number": "+123456789", + "email": "contact@email.com", + "attributes": {}, + "tags": [] + }, + "team": {}, + "type": "PHONE_CALL", + "status": "NOT_STARTED", + "start_time": "2024-05-14T14:27:33.016Z", + "end_time": null, + "duration_ms": "0", + "conversation_history": [ + { + "date": "2024-05-14T14:27:36.574Z", + "text": "string", + "author": "ai", + "audio_url": "https://cdn.thoughtly.net/production-1234567890.wav", + "cumulative_duration_ms": 0 + } + ], + "ai_name": "Tessa", + "phone_number": "+123456789", + "recording_url": null, + "transcript": "Agent: agent text", + "summary_data": null, + "metadata": null +} \ No newline at end of file diff --git a/components/thoughtly/thoughtly.app.mjs b/components/thoughtly/thoughtly.app.mjs index 0eb5cd0189108..77831d8cde90a 100644 --- a/components/thoughtly/thoughtly.app.mjs +++ b/components/thoughtly/thoughtly.app.mjs @@ -1,11 +1,105 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "thoughtly", - propDefinitions: {}, + propDefinitions: { + contactId: { + type: "string", + label: "Contact ID", + description: "The ID of the contact.", + async options({ page }) { + const { data: { contacts } } = await this.listContacts({ + params: { + page, + }, + }); + + return contacts.map(({ + id: value, name, phone_number, + }) => ({ + label: `${name || phone_number}`, + value, + })); + }, + }, + interviewId: { + type: "string", + label: "Interview ID", + description: "The ID of the interview.", + async options({ page }) { + const { data: { interviews } } = await this.listInterviews({ + params: { + page, + }, + }); + + return interviews.map(({ + id: value, title: label, + }) => ({ + label, + value, + })); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.thought.ly"; + }, + _headers() { + return { + "x-api-token": `${this.$auth.api_token}`, + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + headers: this._headers(), + ...opts, + }); + }, + createContact(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/contact/create", + ...opts, + }); + }, + callContact(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/contact/call", + ...opts, + }); + }, + listContacts(opts = {}) { + return this._makeRequest({ + path: "/contact", + ...opts, + }); + }, + listInterviews(opts = {}) { + return this._makeRequest({ + path: "/interview", + ...opts, + }); + }, + createHook(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/webhooks/subscribe", + ...opts, + }); + }, + deleteHook(opts = {}) { + return this._makeRequest({ + method: "DELETE", + path: "/webhooks/unsubscribe", + ...opts, + }); }, }, -}; \ No newline at end of file +}; diff --git a/components/timetonic/README.md b/components/timetonic/README.md new file mode 100644 index 0000000000000..ebadba979b6fa --- /dev/null +++ b/components/timetonic/README.md @@ -0,0 +1,11 @@ +# Overview + +TimeTonic API on Pipedream allows you to automate workflows involving project management and data organization. With TimeTonic, you can create, update, retrieve, and manage databases and tables dynamically, ideal for businesses looking to streamline operations like task tracking, inventory management, and team collaboration. Leveraging Pipedream's capabilities, you can integrate TimeTonic with numerous other apps to automate data flow across platforms, trigger actions based on specific conditions, and maintain real-time synchronization of business operations. + +# Example Use Cases + +- **Project Task Automation**: Automatically create tasks in TimeTonic whenever a new sale is recorded in Shopify. This workflow can help ensure that each order triggers a set of fulfillment tasks, keeping your project management tightly aligned with sales activities. + +- **Team Collaboration Enhancement**: Sync TimeTonic with Slack to alert team members when a new task is assigned or updated. This integration keeps your team informed in real-time, enhancing communication and ensuring everyone is on the same page without manually checking task updates. + +- **Inventory Tracking System**: Set up a workflow where stock level updates in TimeTonic trigger reorder notifications in an email app like Gmail when inventory falls below a certain threshold. This helps maintain optimal stock levels through automated alerts, reducing the risk of stockouts and overstock situations. diff --git a/components/timetonic/actions/common/create-update-row.mjs b/components/timetonic/actions/common/create-update-row.mjs new file mode 100644 index 0000000000000..de684d75e77f0 --- /dev/null +++ b/components/timetonic/actions/common/create-update-row.mjs @@ -0,0 +1,178 @@ +import timetonic from "../../timetonic.app.mjs"; +import constants from "../../common/constants.mjs"; +import fs from "fs"; +import FormData from "form-data"; + +export default { + props: { + timetonic, + bookCode: { + propDefinition: [ + timetonic, + "bookCode", + ], + }, + tableId: { + propDefinition: [ + timetonic, + "tableId", + (c) => ({ + bookCode: c.bookCode, + }), + ], + reloadProps: true, + }, + }, + async additionalProps() { + const props = {}; + if (!this.tableId || !this.bookCode) { + return props; + } + const { bookTables: { categories } } = await this.timetonic.listTables({ + params: { + b_c: this.bookCode, + includeFields: true, + }, + }); + const { fields } = categories.find(({ id }) => id === this.tableId); + for (const field of fields) { + if (!field?.readOnly) { + const id = `${field.id}`; + props[id] = { + type: constants.FIELD_TYPES[field.type] || "string", + label: field.name, + optional: this.isUpdate() + ? true + : !field?.required, + }; + if (field.type === "link") { + const linkTableId = field.link.category.id; + const { tableRows } = await this.timetonic.listRows({ + params: { + catId: linkTableId, + }, + }); + const options = tableRows?.map(({ + id, name: label, + }) => ({ + value: `${id}`, + label, + })) || []; + props[id].options = options; + props[id].description = "The Row ID from the linked table to create a link to"; + tableRows.forEach(({ + id: rowId, name, + }) => { + props[`${id}_${rowId}_link_text`] = { + type: "string", + default: name, + hidden: true, + }; + }); + } + if (field.type === "file" || field.type === "files") { + props[id].description = "The path to the file saved to the `/tmp` directory (e.g. `/tmp/example.pdf`). [See the documentation](https://pipedream.com/docs/workflows/steps/code/nodejs/working-with-files/#the-tmp-directory)."; + props[`${id}_is_file`] = { + type: "boolean", + default: true, + hidden: true, + }; + } + } + } + return props; + }, + methods: { + isUpdate() { + return false; + }, + uploadFile($, fieldId, filePath, rowId) { + const fileStream = fs.createReadStream(filePath.includes("/tmp") + ? filePath + : `/tmp/${filePath}`); + const formData = new FormData(); + formData.append("qqfile", fileStream); + return this.timetonic.uploadFile({ + $, + params: { + b_c: this.bookCode, + fieldId, + rowId, + }, + data: formData, + headers: formData.getHeaders(), + }); + }, + }, + async run({ $ }) { + const { + timetonic, + // eslint-disable-next-line no-unused-vars + isUpdate, + uploadFile, + // eslint-disable-next-line no-unused-vars + bookCode, + tableId, + rowId = `tmp${Math.random().toString(36) + .substr(2, 9)}`, + ...fields + } = this; + + const fieldValues = {}; + const files = []; + for (const [ + key, + value, + ] of Object.entries(fields)) { + if (key.includes("link_text") || key.includes("is_file")) { + continue; + } + if (fields[`${key}_is_file`]) { + files.push({ + fieldId: key, + filePath: value, + }); + continue; + } + fieldValues[+key] = fields[`${key}_${value}_link_text`] + ? [ + { + row_id: +value, + value: fields[`${key}_${value}_link_text`], + }, + ] + : value; + } + // if fieldValues is empty, createOrUpdateRow will create a new row + // if updating, get and return the row instead + const response = isUpdate() && !Object.entries(fieldValues).length + ? await timetonic.getTableValues({ + $, + params: { + b_c: bookCode, + catId: tableId, + filterRowIds: { + row_ids: [ + rowId, + ], + }, + }, + }) + : await timetonic.createOrUpdateRow({ + $, + params: { + rowId, + catId: tableId, + fieldValues, + }, + }); + const newRowId = this.rowId || response.rows[0].id; + for (const file of files) { + await uploadFile($, file.fieldId, file.filePath, newRowId); + } + $.export("$summary", `Successfully ${isUpdate() + ? "updated" + : "created"} row in table ${tableId}`); + return response; + }, +}; diff --git a/components/timetonic/actions/create-row/create-row.mjs b/components/timetonic/actions/create-row/create-row.mjs new file mode 100644 index 0000000000000..ff71499913bcf --- /dev/null +++ b/components/timetonic/actions/create-row/create-row.mjs @@ -0,0 +1,10 @@ +import common from "../common/create-update-row.mjs"; + +export default { + ...common, + key: "timetonic-create-row", + name: "Create Row", + description: "Create a new row within an existing table in TimeTonic. [See the documentation](https://timetonic.com/live/apidoc/#api-Smart_table_operations-createOrUpdateTableRow)", + version: "0.0.1", + type: "action", +}; diff --git a/components/timetonic/actions/delete-row/delete-row.mjs b/components/timetonic/actions/delete-row/delete-row.mjs new file mode 100644 index 0000000000000..e8cf3c0b460e5 --- /dev/null +++ b/components/timetonic/actions/delete-row/delete-row.mjs @@ -0,0 +1,46 @@ +import timetonic from "../../timetonic.app.mjs"; + +export default { + key: "timetonic-delete-row", + name: "Delete Row", + description: "Deletes a row within an existing table in TimeTonic. [See the documentation](https://timetonic.com/live/apidoc/#api-Smart_table_operations-deleteTableRow)", + version: "0.0.1", + type: "action", + props: { + timetonic, + bookCode: { + propDefinition: [ + timetonic, + "bookCode", + ], + }, + tableId: { + propDefinition: [ + timetonic, + "tableId", + (c) => ({ + bookCode: c.bookCode, + }), + ], + }, + rowId: { + propDefinition: [ + timetonic, + "rowId", + (c) => ({ + tableId: c.tableId, + }), + ], + }, + }, + async run({ $ }) { + const response = await this.timetonic.deleteRow({ + $, + params: { + rowId: this.rowId, + }, + }); + $.export("$summary", `Successfully deleted row with ID ${this.rowId}`); + return response; + }, +}; diff --git a/components/timetonic/actions/search-rows/search-rows.mjs b/components/timetonic/actions/search-rows/search-rows.mjs new file mode 100644 index 0000000000000..ff5f336cb66d7 --- /dev/null +++ b/components/timetonic/actions/search-rows/search-rows.mjs @@ -0,0 +1,100 @@ +import timetonic from "../../timetonic.app.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + key: "timetonic-search-rows", + name: "Search Rows", + description: "Perform a search across table rows based on given criteria. [See the documentation](https://timetonic.com/live/apidoc/#api-Smart_table_operations-listTableRowsById)", + version: "0.0.1", + type: "action", + props: { + timetonic, + bookCode: { + propDefinition: [ + timetonic, + "bookCode", + ], + }, + tableId: { + propDefinition: [ + timetonic, + "tableId", + (c) => ({ + bookCode: c.bookCode, + }), + ], + }, + searchField: { + propDefinition: [ + timetonic, + "fieldId", + (c) => ({ + bookCode: c.bookCode, + tableId: c.tableId, + }), + ], + reloadProps: true, + }, + }, + async additionalProps() { + const props = {}; + if (!this.searchField || !this.bookCode || !this.tableId) { + return props; + } + const { tableValues: { fields } } = await this.timetonic.getTableValues({ + params: { + catId: this.tableId, + b_c: this.bookCode, + }, + }); + const field = fields.find(({ id }) => id === this.searchField); + props.searchValue = { + type: constants.FIELD_TYPES[field.type] || "string", + label: "Search Value", + description: "The value to search for", + }; + if (field.type === "link") { + props.searchValue.description = `Please enter the Row ID from ${field.link.category.name} to link to`; + } + return props; + }, + methods: { + findMatches(fields) { + const field = fields.find(({ id }) => id === this.searchField); + let matches; + if (field.type === "link") { + matches = field.values.filter(({ value }) => + value?.length && value.find((link) => link.row_id == this.searchValue)); + } else { + matches = field.values.filter(({ value }) => value == this.searchValue); + } + return matches.map(({ id }) => id); + }, + buildRow(fields, matches) { + const rows = []; + matches.forEach((match) => { + const row = {}; + fields.forEach((field) => { + row[field.name] = field.values.find(({ id }) => id === match).value; + }); + rows.push(row); + }); + return rows; + }, + }, + async run({ $ }) { + const { tableValues: { fields } } = await this.timetonic.getTableValues({ + $, + params: { + catId: this.tableId, + b_c: this.bookCode, + }, + }); + const matches = this.findMatches(fields); + const rows = this.buildRow(fields, matches); + $.export("$summary", `Found ${rows.length} matching row${rows.length === 1 + ? "" + : "s"}`); + return rows; + }, +}; diff --git a/components/timetonic/actions/update-row/update-row.mjs b/components/timetonic/actions/update-row/update-row.mjs new file mode 100644 index 0000000000000..ec742ab095e29 --- /dev/null +++ b/components/timetonic/actions/update-row/update-row.mjs @@ -0,0 +1,28 @@ +import common from "../common/create-update-row.mjs"; + +export default { + ...common, + key: "timetonic-update-row", + name: "Update Row", + description: "Updates the values within a specified row in a table. [See the documentation](https://timetonic.com/live/apidoc/#api-Smart_table_operations-createOrUpdateTableRow)", + version: "0.0.1", + type: "action", + props: { + ...common.props, + rowId: { + propDefinition: [ + common.props.timetonic, + "rowId", + (c) => ({ + tableId: c.tableId, + }), + ], + }, + }, + methods: { + ...common.methods, + isUpdate() { + return true; + }, + }, +}; diff --git a/components/timetonic/common/constants.mjs b/components/timetonic/common/constants.mjs new file mode 100644 index 0000000000000..e100733dfe6be --- /dev/null +++ b/components/timetonic/common/constants.mjs @@ -0,0 +1,17 @@ +const DEFAULT_LIMIT = 100; + +const FIELD_TYPES = { + shorttext: "string", + mediumtext: "string", + email: "string", + phone: "string", + date: "string", + link: "string", + boolean: "boolean", + int: "integer", +}; + +export default { + DEFAULT_LIMIT, + FIELD_TYPES, +}; diff --git a/components/timetonic/package.json b/components/timetonic/package.json index 2ef86a0c1ca44..7ea1d96f6b3d8 100644 --- a/components/timetonic/package.json +++ b/components/timetonic/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/timetonic", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream TimeTonic Components", "main": "timetonic.app.mjs", "keywords": [ @@ -11,5 +11,10 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^1.6.5", + "form-data": "^4.0.0", + "md5": "^2.3.0" } -} \ No newline at end of file +} diff --git a/components/timetonic/sources/common/base.mjs b/components/timetonic/sources/common/base.mjs new file mode 100644 index 0000000000000..c784c3b689a19 --- /dev/null +++ b/components/timetonic/sources/common/base.mjs @@ -0,0 +1,67 @@ +import timetonic from "../../timetonic.app.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + props: { + timetonic, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + bookCode: { + propDefinition: [ + timetonic, + "bookCode", + ], + }, + tableId: { + propDefinition: [ + timetonic, + "tableId", + (c) => ({ + bookCode: c.bookCode, + }), + ], + }, + }, + methods: { + formatFields(rows) { + rows.forEach((row) => { + const fields = {}; + for (const [ + key, + value, + ] of Object.entries(row.fields)) { + fields[key] = value.value; + } + row.fields = fields; + }); + return rows; + }, + }, + async run() { + const params = { + catId: this.tableId, + b_c: this.bookCode, + format: "rows", + }; + if (this.viewId) { + params.filterRowIds = { + applyViewFilters: this.viewId, + }; + } + const results = this.timetonic.paginate({ + resourceFn: this.timetonic.getTableValues, + params, + }); + const rows = []; + for await (const row of results) { + rows.push(row); + } + const formattedRows = this.formatFields(rows); + await this.processRows(formattedRows); + }, +}; diff --git a/components/timetonic/sources/new-table-row-in-view/new-table-row-in-view.mjs b/components/timetonic/sources/new-table-row-in-view/new-table-row-in-view.mjs new file mode 100644 index 0000000000000..c1404b26de242 --- /dev/null +++ b/components/timetonic/sources/new-table-row-in-view/new-table-row-in-view.mjs @@ -0,0 +1,40 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "timetonic-new-table-row-in-view", + name: "New Table Row in View", + description: "Emit new event when a new table row appears in a specific view.", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + viewId: { + propDefinition: [ + common.props.timetonic, + "viewId", + (c) => ({ + bookCode: c.bookCode, + tableId: c.tableId, + }), + ], + }, + }, + methods: { + ...common.methods, + generateMeta(row) { + return { + id: row.id, + summary: `New Row in View with ID: ${row.id}`, + ts: Date.now(), + }; + }, + processRows(rows) { + for (const row of rows) { + const meta = this.generateMeta(row); + this.$emit(row.fields, meta); + } + }, + }, +}; diff --git a/components/timetonic/sources/new-table-row/new-table-row.mjs b/components/timetonic/sources/new-table-row/new-table-row.mjs new file mode 100644 index 0000000000000..a48489d659514 --- /dev/null +++ b/components/timetonic/sources/new-table-row/new-table-row.mjs @@ -0,0 +1,27 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "timetonic-new-table-row", + name: "New Table Row", + description: "Emit new event when a new table row is added in TimeTonic", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + generateMeta(row) { + return { + id: row.id, + summary: `New Row with ID: ${row.id}`, + ts: Date.now(), + }; + }, + processRows(rows) { + for (const row of rows) { + const meta = this.generateMeta(row); + this.$emit(row.fields, meta); + } + }, + }, +}; diff --git a/components/timetonic/sources/row-deleted/row-deleted.mjs b/components/timetonic/sources/row-deleted/row-deleted.mjs new file mode 100644 index 0000000000000..17a5ebca7ac4e --- /dev/null +++ b/components/timetonic/sources/row-deleted/row-deleted.mjs @@ -0,0 +1,41 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "timetonic-row-deleted", + name: "Row Deleted", + description: "Emit new event when a row is deleted in a table.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + _getRowIds() { + return this.db.get("rowIds") || []; + }, + _setRowIds(rowIds) { + this.db.set("rowIds", rowIds); + }, + generateMeta(row) { + return { + id: row.id, + summary: `Row with ID: ${row.id} Deleted`, + ts: Date.now(), + }; + }, + processRows(rows) { + const previousRowIds = this._getRowIds(); + const currentRowIds = rows.map(({ id }) => id); + for (const id of previousRowIds) { + if (!currentRowIds.includes(id)) { + const row = { + id, + }; + const meta = this.generateMeta(row); + this.$emit(row, meta); + } + } + this._setRowIds(currentRowIds); + }, + }, +}; diff --git a/components/timetonic/sources/row-updated/row-updated.mjs b/components/timetonic/sources/row-updated/row-updated.mjs new file mode 100644 index 0000000000000..70e5d0c44c7c6 --- /dev/null +++ b/components/timetonic/sources/row-updated/row-updated.mjs @@ -0,0 +1,41 @@ +import common from "../common/base.mjs"; +import md5 from "md5"; + +export default { + ...common, + key: "timetonic-row-updated", + name: "Row Updated", + description: "Emit new event when a row is updated in a table.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + _getPreviousRows() { + return this.db.get("previousRows") || {}; + }, + _setPreviousRows(previousRows) { + this.db.set("previousRows", previousRows); + }, + generateMeta(row) { + const ts = Date.now(); + return { + id: `${row.id}${ts}`, + summary: `Row Updated with ID: ${row.id}`, + ts, + }; + }, + processRows(rows) { + const previousRows = this._getPreviousRows(); + for (const row of rows) { + const hash = md5(JSON.stringify(row.fields)); + if (previousRows[row.id] !== hash) { + const meta = this.generateMeta(row); + this.$emit(row.fields, meta); + previousRows[row.id] = hash; + } + } + this._setPreviousRows(previousRows); + }, + }, +}; diff --git a/components/timetonic/timetonic.app.mjs b/components/timetonic/timetonic.app.mjs index 1e8dd11f3a21a..520ad00efab77 100644 --- a/components/timetonic/timetonic.app.mjs +++ b/components/timetonic/timetonic.app.mjs @@ -1,11 +1,191 @@ +import { axios } from "@pipedream/platform"; +import constants from "./common/constants.mjs"; + export default { type: "app", app: "timetonic", - propDefinitions: {}, + propDefinitions: { + bookCode: { + type: "string", + label: "Book Code", + description: "The book code of the book", + async options() { + const { allBooks: { books } } = await this.listBooks(); + return books?.map(({ + b_c: value, ownerPrefs, + }) => ({ + value, + label: ownerPrefs?.title, + })) || []; + }, + }, + tableId: { + type: "string", + label: "Table ID", + description: "The ID of the table", + async options({ bookCode }) { + const { bookTables: { categories } } = await this.listTables({ + params: { + b_c: bookCode, + }, + }); + return categories?.map(({ + id: value, name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + rowId: { + type: "string", + label: "Row ID", + description: "The ID of the row", + async options({ tableId }) { + const { tableRows } = await this.listRows({ + params: { + catId: tableId, + }, + }); + return tableRows?.map(({ + id: value, name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + fieldId: { + type: "integer", + label: "Field ID", + description: "The ID of the field to search", + async options({ + bookCode, tableId, + }) { + const { bookTables: { categories } } = await this.listTables({ + params: { + b_c: bookCode, + includeFields: true, + }, + }); + const { fields } = categories.find(({ id }) => id === tableId); + return fields?.map(({ + id: value, name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + viewId: { + type: "string", + label: "View ID", + description: "The ID of the view", + async options({ + bookCode, tableId, + }) { + const { tableValues: { views } } = await this.getTableValues({ + params: { + catId: tableId, + b_c: bookCode, + }, + }); + return views?.map(({ + id: value, name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://timetonic.com/live/api.php"; + }, + _userId() { + return this.$auth.user_id; + }, + _makeRequest(opts = {}) { + const { + $ = this, req, params, ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + method: "POST", + url: this._baseUrl(), + params: { + ...params, + req, + o_u: this._userId(), + u_c: this._userId(), + sesskey: this.$auth.api_key, + b_o: this._userId(), + }, + }); + }, + listBooks(opts = {}) { + return this._makeRequest({ + req: "getAllBooks", + ...opts, + }); + }, + listTables(opts = {}) { + return this._makeRequest({ + req: "getBookTables", + ...opts, + }); + }, + listRows(opts = {}) { + return this._makeRequest({ + req: "listTableRowsById", + ...opts, + }); + }, + getTableValues(opts = {}) { + return this._makeRequest({ + req: "getTableValues", + ...opts, + }); + }, + createOrUpdateRow(opts = {}) { + return this._makeRequest({ + req: "createOrUpdateTableRow", + ...opts, + }); + }, + deleteRow(opts = {}) { + return this._makeRequest({ + req: "deleteTableRow", + ...opts, + }); + }, + uploadFile(opts = {}) { + return this._makeRequest({ + req: "fileUpload", + ...opts, + }); + }, + async *paginate({ + resourceFn, + params, + }) { + params = { + ...params, + maxRows: constants.DEFAULT_LIMIT, + offset: 0, + }; + let total; + do { + const { tableValues: { rows } } = await resourceFn({ + params, + }); + for (const row of rows) { + yield row; + } + total = rows?.length; + params.offset += params.maxRows; + } while (total === params.maxRows); }, }, -}; \ No newline at end of file +}; diff --git a/components/tripadvisor_content_api/actions/location-details/location-details.mjs b/components/tripadvisor_content_api/actions/location-details/location-details.mjs new file mode 100644 index 0000000000000..52446efdba796 --- /dev/null +++ b/components/tripadvisor_content_api/actions/location-details/location-details.mjs @@ -0,0 +1,37 @@ +import app from "../../tripadvisor_content_api.app.mjs"; + +export default { + key: "tripadvisor_content_api-location-details", + name: "Get Location Details", + description: "Returns comprehensive information about a location. [See the documentation](https://tripadvisor-content-api.readme.io/reference/getlocationdetails)", + version: "0.0.1", + type: "action", + props: { + app, + searchQuery: { + propDefinition: [ + app, + "searchQuery", + ], + }, + locationId: { + propDefinition: [ + app, + "locationId", + (c) => ({ + searchQuery: c.searchQuery, + }), + ], + }, + }, + async run({ $ }) { + const response = await this.app.getLocationDetails({ + $, + locationId: this.locationId, + }); + + $.export("$summary", `Successfully returned the details for location '${response.name}'`); + + return response; + }, +}; diff --git a/components/tripadvisor_content_api/actions/location-reviews/location-reviews.mjs b/components/tripadvisor_content_api/actions/location-reviews/location-reviews.mjs new file mode 100644 index 0000000000000..e34a112ea09ce --- /dev/null +++ b/components/tripadvisor_content_api/actions/location-reviews/location-reviews.mjs @@ -0,0 +1,37 @@ +import app from "../../tripadvisor_content_api.app.mjs"; + +export default { + key: "tripadvisor_content_api-location-reviews", + name: "Get Location Reviews", + description: "Returns up to 5 of the most recent reviews for a specific location. [See the documentation](https://tripadvisor-content-api.readme.io/reference/getlocationreviews)", + version: "0.0.1", + type: "action", + props: { + app, + searchQuery: { + propDefinition: [ + app, + "searchQuery", + ], + }, + locationId: { + propDefinition: [ + app, + "locationId", + (c) => ({ + searchQuery: c.searchQuery, + }), + ], + }, + }, + async run({ $ }) { + const response = await this.app.getLocationReviews({ + $, + locationId: this.locationId, + }); + + $.export("$summary", `Successfully listed ${response.data.length} of the most recent reviews for the specified location`); + + return response; + }, +}; diff --git a/components/tripadvisor_content_api/actions/location-search/location-search.mjs b/components/tripadvisor_content_api/actions/location-search/location-search.mjs new file mode 100644 index 0000000000000..f549fb12e9a9c --- /dev/null +++ b/components/tripadvisor_content_api/actions/location-search/location-search.mjs @@ -0,0 +1,44 @@ +import app from "../../tripadvisor_content_api.app.mjs"; + +export default { + key: "tripadvisor_content_api-location-search", + name: "Search Locations", + description: "Returns up to 10 locations found by the given search query. [See the documentation](https://tripadvisor-content-api.readme.io/reference/searchforlocations)", + version: "0.0.1", + type: "action", + props: { + app, + searchQuery: { + propDefinition: [ + app, + "searchQuery", + ], + }, + category: { + propDefinition: [ + app, + "category", + ], + }, + address: { + propDefinition: [ + app, + "address", + ], + }, + }, + async run({ $ }) { + const response = await this.app.searchLocations({ + $, + params: { + searchQuery: this.searchQuery, + category: this.category, + address: this.address, + }, + }); + + $.export("$summary", `Found ${response.data.length} location(s)`); + + return response; + }, +}; diff --git a/components/tripadvisor_content_api/package.json b/components/tripadvisor_content_api/package.json index fb3d8b455bd6b..a8ede8c12b0bc 100644 --- a/components/tripadvisor_content_api/package.json +++ b/components/tripadvisor_content_api/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/tripadvisor_content_api", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Tripadvisor (Content API) Components", "main": "tripadvisor_content_api.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^1.6.2" } -} \ No newline at end of file +} diff --git a/components/tripadvisor_content_api/tripadvisor_content_api.app.mjs b/components/tripadvisor_content_api/tripadvisor_content_api.app.mjs index 0d63077fb7a62..4cc75253f9c1c 100644 --- a/components/tripadvisor_content_api/tripadvisor_content_api.app.mjs +++ b/components/tripadvisor_content_api/tripadvisor_content_api.app.mjs @@ -1,11 +1,92 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "tripadvisor_content_api", - propDefinitions: {}, + propDefinitions: { + locationId: { + type: "string", + label: "Location ID", + description: "The ID of the location", + async options({ searchQuery }) { + const { data: resources } = await this.searchLocations({ + params: { + searchQuery, + }, + }); + return resources.map(({ + location_id, name, + }) => ({ + value: location_id, + label: name, + })); + }, + }, + searchQuery: { + type: "string", + label: "Search Query", + description: "The search query to find locations", + }, + address: { + type: "string", + label: "Location Address", + description: "The address of the location", + optional: true, + }, + category: { + type: "string", + label: "Location Category", + description: "The category of the location", + optional: true, + options: [ + "hotels", + "attractions", + "restaurants", + "geos", + ], + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.content.tripadvisor.com/api/v1"; + }, + _makeRequest(opts = {}) { + const { + $ = this, + path, + params, + ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + url: this._baseUrl() + path, + params: { + ...params, + key: `${this.$auth.api_key}`, + }, + }); + }, + getLocationDetails({ + locationId, ...args + }) { + return this._makeRequest({ + ...args, + path: `/location/${locationId}/details`, + }); + }, + getLocationReviews({ + locationId, ...args + }) { + return this._makeRequest({ + ...args, + path: `/location/${locationId}/reviews`, + }); + }, + searchLocations(args = {}) { + return this._makeRequest({ + ...args, + path: "/location/search", + }); }, }, }; diff --git a/components/twenty/actions/create-update-delete-record/create-update-delete-record.mjs b/components/twenty/actions/create-update-delete-record/create-update-delete-record.mjs new file mode 100644 index 0000000000000..8ea2961cf67c0 --- /dev/null +++ b/components/twenty/actions/create-update-delete-record/create-update-delete-record.mjs @@ -0,0 +1,137 @@ +import { + camelCaseToWords, parseObject, +} from "../../common/utils.mjs"; +import twenty from "../../twenty.app.mjs"; + +export default { + key: "twenty-create-update-delete-record", + name: "Create, Update, or Delete a Record in Twenty", + description: "Create, update, or delete a single record in Twenty. This action allows for dynamic handling of records based on specified action type. [See the documentation](https://api.twenty.com/docs)", + version: "0.0.1", + type: "action", + props: { + twenty, + recordId: { + propDefinition: [ + twenty, + "recordId", + ], + reloadProps: true, + }, + actionType: { + propDefinition: [ + twenty, + "actionType", + ], + reloadProps: true, + }, + additionalProp: { + type: "object", + label: "Additional Prop", + description: "Any additional prop you want to fill.", + optional: true, + }, + }, + async additionalProps() { + const props = {}; + const recordId = this.recordId; + const actionType = this.actionType; + + if (recordId && this.actionType) { + if ([ + "delete", + "update", + ].includes(actionType)) { + props.id = { + type: "string", + label: `${camelCaseToWords(recordId)} Id`, + description: `The Id of ${recordId}`, + options: async () => { + const { + components: { schemas }, tags, + } = await this.twenty.listRecords(); + const index = Object.keys(schemas).findIndex((i) => i === this.recordId); + const { data } = await this.twenty.listRecordItems(tags[index + 1].name); + const response = data[tags[index + 1].name]; + return response.map((item) => ({ + label: item.name || item.title || item.id, + value: item.id, + })); + }, + }; + } + + if ([ + "create", + "update", + ].includes(actionType)) { + + const { components: { schemas } } = await this.twenty.listRecords(); + const properties = schemas[recordId].properties; + const required = schemas[recordId].required || []; + + for (const [ + key, + value, + ] of Object.entries(properties)) { + if ( + (key != "id") && + (key != "createdAt") && + (key != "updatedAt") && + (value.type) && + (value.type != "object") && + (value.type != "array") + ) { + props[key] = { + type: value["type"] === "number" + ? "integer" + : value["type"], + label: camelCaseToWords(key), + description: value["description"], + optional: !(required.includes === key), + }; + } + } + } + } + return props; + }, + async run({ $ }) { + const { + twenty, + id, + recordId, + actionType, + additionalProp, + ...data + } = this; + + let response; + + const { + components: { schemas }, tags, + } = await this.twenty.listRecords(); + const index = Object.keys(schemas).findIndex((i) => i === this.recordId); + + try { + response = await twenty.performAction({ + $, + id, + actionType: this.actionType, + recordName: tags[index + 1].name, + data: { + ...data, + ...(additionalProp + ? parseObject(additionalProp) + : {}), + }, + }); + + $.export("$summary", `Successfully performed ${actionType} ${recordId} on record with ID: ${id || response.data[`${actionType}${recordId}`].id}`); + + return response; + } catch (error) { + throw new Error(`Failed to ${actionType} record. Error: ${error.message}`); + } + }, +}; diff --git a/components/twenty/common/utils.mjs b/components/twenty/common/utils.mjs new file mode 100644 index 0000000000000..f097aeac6c1b2 --- /dev/null +++ b/components/twenty/common/utils.mjs @@ -0,0 +1,31 @@ +export const camelCaseToWords = (s) => { + const result = s.replace(/([A-Z])/g, " $1"); + return result.charAt(0).toUpperCase() + result.slice(1); +}; + +export const capitalizeFirstLetter = (string) => { + return string.charAt(0).toLowerCase() + string.slice(1); +}; + +export const parseObject = (obj) => { + 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/twenty/package.json b/components/twenty/package.json index 5d86a2c7e0b1b..17a8e53d2d811 100644 --- a/components/twenty/package.json +++ b/components/twenty/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/twenty", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Twenty Components", "main": "twenty.app.mjs", "keywords": [ @@ -11,5 +11,9 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^1.6.5" } -} \ No newline at end of file +} + diff --git a/components/twenty/sources/new-record-modified-instant/new-record-modified-instant.mjs b/components/twenty/sources/new-record-modified-instant/new-record-modified-instant.mjs new file mode 100644 index 0000000000000..3209ad0503310 --- /dev/null +++ b/components/twenty/sources/new-record-modified-instant/new-record-modified-instant.mjs @@ -0,0 +1,45 @@ +import twenty from "../../twenty.app.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + key: "twenty-new-record-modified-instant", + name: "New Record Modified (Instant)", + description: "Emit new event when a record is created, updated, or deleted.", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + twenty, + http: "$.interface.http", + db: "$.service.db", + }, + hooks: { + async activate() { + const { data } = await this.twenty.createHook({ + data: { + targetUrl: this.http.endpoint, + operation: "*.*", + }, + }); + + this.db.set("webhookId", data.createWebhook.id); + }, + async deactivate() { + const webhookId = this.db.get("webhookId"); + await this.twenty.deleteHook(webhookId); + }, + }, + async run(event) { + const { body } = event; + + const eventType = body.eventType; + const eventName = eventType.split(".")[0]; + + this.$emit(body, { + id: `${body.objectMetadata.id}-${body.eventDate}`, + summary: `New ${body.objectMetadata.nameSingular} ${eventName}d with Id: ${body.objectMetadata.id}.`, + ts: Date.parse(body.eventDate) || Date.now(), + }); + }, + sampleEmit, +}; diff --git a/components/twenty/sources/new-record-modified-instant/test-event.mjs b/components/twenty/sources/new-record-modified-instant/test-event.mjs new file mode 100644 index 0000000000000..92f4ea7a28bb8 --- /dev/null +++ b/components/twenty/sources/new-record-modified-instant/test-event.mjs @@ -0,0 +1,61 @@ +export default { + "targetUrl": "http://webhook.url", + "eventType": "*.*", + "objectMetadata": { + "id": "12345678-1234-1234-1234-123456789012", + "nameSingular": "company" + }, + "workspaceId": "12345678-1234-1234-1234-123456789012", + "webhookId": "12345678-1234-1234-1234-123456789012", + "eventDate": "2024-05-07T14:16:39.810Z", + "record": { + "id": "12345678-1234-1234-1234-123456789012", + "name": "company test", + "people": { + "edges": [], + "__typename": "personConnection" + }, + "address": "", + "position": 1, + "createdAt": "2024-05-07T14:16:39.350276+00:00", + "employees": null, + "favorites": { + "edges": [], + "__typename": "favoriteConnection" + }, + "updatedAt": "2024-05-07T14:16:39.350276+00:00", + "__typename": "company", + "domainName": "", + "attachments": { + "edges": [], + "__typename": "attachmentConnection" + }, + "accountOwner": null, + "opportunities": { + "edges": [], + "__typename": "opportunityConnection" + }, + "accountOwnerId": null, + "activityTargets": { + "edges": [], + "__typename": "activityTargetConnection" + }, + "timelineActivities": { + "edges": [], + "__typename": "timelineActivityConnection" + }, + "idealCustomerProfile": false, + "xLink": { + "url": "", + "label": "" + }, + "linkedinLink": { + "url": "", + "label": "" + }, + "annualRecurringRevenue": { + "amountMicros": null, + "currencyCode": "" + } + } +} \ No newline at end of file diff --git a/components/twenty/twenty.app.mjs b/components/twenty/twenty.app.mjs index b7c20e7a5d07b..f3e11a5cca4b9 100644 --- a/components/twenty/twenty.app.mjs +++ b/components/twenty/twenty.app.mjs @@ -1,11 +1,139 @@ +import { axios } from "@pipedream/platform"; +import { + camelCaseToWords, capitalizeFirstLetter, +} from "./common/utils.mjs"; + export default { type: "app", app: "twenty", - propDefinitions: {}, + propDefinitions: { + recordId: { + type: "string", + label: "Record ID", + description: "The ID of the record to update or delete.", + async options() { + const { components: { schemas } } = await this.listRecords(); + + return Object.entries(schemas).filter(([ + key, + ]) => key != "Attachment") + .map(([ + key, + ]) => ({ + label: camelCaseToWords(key), + value: key, + })); + }, + }, + actionType: { + type: "string", + label: "Action Type", + description: "Specify the action to perform: create, update, or delete.", + options: [ + { + label: "Create", + value: "create", + }, + { + label: "Update", + value: "update", + }, + { + label: "Delete", + value: "delete", + }, + ], + default: "create", + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.twenty.com"; + }, + _headers() { + return { + Authorization: `Bearer ${this.$auth.api_key}`, + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + headers: this._headers(), + ...opts, + }); + }, + listRecords() { + return this._makeRequest({ + path: "/open-api/core", + }); + }, + listRecordItems(recordId) { + return this._makeRequest({ + path: `/rest/${capitalizeFirstLetter(recordId)}`, + }); + }, + createHook(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/rest/webhooks", + ...opts, + }); + }, + deleteHook(hookId) { + return this._makeRequest({ + method: "DELETE", + path: `/rest/webhooks/${hookId}`, + }); + }, + createRecord({ + recordName, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/rest/${recordName}`, + ...opts, + }); + }, + updateRecord({ + recordName, id, ...opts + }) { + return this._makeRequest({ + method: "PUT", + path: `/rest/${recordName}/${id}`, + ...opts, + }); + }, + deleteRecord({ + id, recordName, + }) { + return this._makeRequest({ + method: "DELETE", + path: `/rest/${recordName}/${id}`, + }); + }, + performAction({ + actionType, id, recordName, ...opts + }) { + switch (actionType) { + case "create": + return this.createRecord({ + recordName, + ...opts, + }); + case "update": + return this.updateRecord({ + recordName, + id, + ...opts, + }); + case "delete": + return this.deleteRecord({ + id, + recordName, + }); + } }, }, -}; \ No newline at end of file +}; diff --git a/components/unsplash/actions/get-photo/get-photo.mjs b/components/unsplash/actions/get-photo/get-photo.mjs new file mode 100644 index 0000000000000..b414abc44e060 --- /dev/null +++ b/components/unsplash/actions/get-photo/get-photo.mjs @@ -0,0 +1,41 @@ +import app from "../../unsplash.app.mjs"; + +export default { + key: "unsplash-get-photo", + name: "Get Photo", + description: "Get a specific photo from Unsplash. [See the documentation](https://unsplash.com/documentation#get-a-photo)", + version: "0.0.1", + type: "action", + props: { + app, + photoId: { + propDefinition: [ + app, + "photoId", + ], + }, + }, + methods: { + getPhoto({ + photoId, ...args + } = {}) { + return this.app._makeRequest({ + path: `/photos/${photoId}`, + ...args, + }); + }, + }, + async run({ $ }) { + const { + getPhoto, + photoId, + } = this; + const response = await getPhoto({ + $, + photoId, + }); + + $.export("$summary", `Successfully retrieved photo with ID \`${response.id}\``); + return response; + }, +}; diff --git a/components/unsplash/actions/search-photos/search-photos.mjs b/components/unsplash/actions/search-photos/search-photos.mjs new file mode 100644 index 0000000000000..342586dfd9e3c --- /dev/null +++ b/components/unsplash/actions/search-photos/search-photos.mjs @@ -0,0 +1,74 @@ +import constants from "../../common/constants.mjs"; +import app from "../../unsplash.app.mjs"; + +export default { + key: "unsplash-search-photos", + name: "Search Photos", + description: "Get a single page of photo results for a query. [See the documentation](https://unsplash.com/documentation#search-photos)", + version: "0.0.1", + type: "action", + props: { + app, + query: { + type: "string", + label: "Query", + description: "Search terms.", + }, + contentFilter: { + type: "string", + label: "Content Filter", + description: "Limit results by content safety. Valid values are `low` and `high`.", + optional: true, + options: constants.CONTENT_FILTERS, + }, + color: { + type: "string", + label: "Color", + description: "Filter results by color. Valid values are: `black_and_white`, `black`, `white`, `yellow`, `orange`, `red`, `purple`, `magenta`, `green`, `teal`, and `blue`.", + optional: true, + options: constants.COLOR_OPTIONS, + }, + orientation: { + type: "string", + label: "Orientation", + description: "Filter by photo orientation. Optional. (Valid values: `landscape`, `portrait`, `squarish`)", + optional: true, + options: constants.ORIENTATION_OPTIONS, + }, + }, + methods: { + searchPhotos(args = {}) { + return this.app._makeRequest({ + path: "/search/photos", + ...args, + }); + }, + }, + async run({ $ }) { + const { + app, + searchPhotos, + query, + contentFilter, + color, + orientation, + } = this; + + const photos = await app.paginate({ + resourcesFn: searchPhotos, + resourcesFnArgs: { + $, + params: { + query, + content_filter: contentFilter, + color, + orientation, + }, + }, + resourceName: "results", + }); + + $.export("$summary", `Successfully retrieved \`${photos.length}\` photo(s).`); + return photos; + }, +}; diff --git a/components/unsplash/common/constants.mjs b/components/unsplash/common/constants.mjs new file mode 100644 index 0000000000000..a2bfe84cfba66 --- /dev/null +++ b/components/unsplash/common/constants.mjs @@ -0,0 +1,81 @@ +const BASE_URL = "https://api.unsplash.com"; +const API_VERSION = "v1"; +const DEFAULT_MAX = 100; +const DEFAULT_LIMIT = 100; + +const CONTENT_FILTERS = [ + "low", + "high", +]; + +const COLOR_OPTIONS = [ + { + label: "Black and White", + value: "black_and_white", + }, + { + label: "Black", + value: "black", + }, + { + label: "White", + value: "white", + }, + { + label: "Yellow", + value: "yellow", + }, + { + label: "Orange", + value: "orange", + }, + { + label: "Red", + value: "red", + }, + { + label: "Purple", + value: "purple", + }, + { + label: "Magenta", + value: "magenta", + }, + { + label: "Green", + value: "green", + }, + { + label: "Teal", + value: "teal", + }, + { + label: "Blue", + value: "blue", + }, +]; + +const ORIENTATION_OPTIONS = [ + { + label: "Landscape", + value: "landscape", + }, + { + label: "Portrait", + value: "portrait", + }, + { + label: "Squarish", + value: "squarish", + }, +]; + +export default { + BASE_URL, + API_VERSION, + DEFAULT_LIMIT, + DEFAULT_MAX, + CONTENT_FILTERS, + COLOR_OPTIONS, + ORIENTATION_OPTIONS, +}; diff --git a/components/unsplash/common/utils.mjs b/components/unsplash/common/utils.mjs new file mode 100644 index 0000000000000..67cc42c8d951e --- /dev/null +++ b/components/unsplash/common/utils.mjs @@ -0,0 +1,34 @@ +function parseLinkHeader(linkHeader) { + return linkHeader?.split(",") + .reduce((props, link) => { + const [ + url, + rel, + ] = link.split(";"); + const [ + , value, + ] = url.split("<"); + const [ + , key, + ] = rel.split("="); + const clearKey = key.replace(/"/g, ""); + const clearValue = value.replace(/>/g, ""); + return { + ...props, + [clearKey]: clearValue, + }; + }, {}); +} + +async function iterate(iterations) { + const items = []; + for await (const item of iterations) { + items.push(item); + } + return items; +} + +export default { + iterate, + parseLinkHeader, +}; diff --git a/components/unsplash/package.json b/components/unsplash/package.json new file mode 100644 index 0000000000000..928813186c861 --- /dev/null +++ b/components/unsplash/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/unsplash", + "version": "0.0.1", + "description": "Pipedream Unsplash Components", + "main": "unsplash.app.mjs", + "keywords": [ + "pipedream", + "unsplash" + ], + "homepage": "https://pipedream.com/apps/unsplash", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^1.6.5" + } +} diff --git a/components/unsplash/unsplash.app.mjs b/components/unsplash/unsplash.app.mjs index 0e3ab16e1e945..6209711a84f5f 100644 --- a/components/unsplash/unsplash.app.mjs +++ b/components/unsplash/unsplash.app.mjs @@ -1,11 +1,98 @@ +import { axios } from "@pipedream/platform"; +import utils from "./common/utils.mjs"; +import constants from "./common/constants.mjs"; + export default { type: "app", app: "unsplash", - propDefinitions: {}, + propDefinitions: { + photoId: { + type: "string", + label: "Photo ID", + description: "The ID of the photo to retrieve.", + async options({ page }) { + const photos = await this.listPhotos({ + params: { + page: page + 1, + per_page: constants.DEFAULT_LIMIT, + }, + }); + return photos.map(({ + id: value, slug: label, + }) => ({ + label, + value, + })); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + getUrl(path) { + return `${constants.BASE_URL}${path}`; + }, + getHeaders(headers) { + return { + ...headers, + "Accept-Version": constants.API_VERSION, + "Authorization": `Bearer ${this.$auth.oauth_access_token}`, + }; + }, + _makeRequest({ + $ = this, path, headers, ...args + } = {}) { + return axios($, { + ...args, + debug: true, + url: this.getUrl(path), + headers: this.getHeaders(headers), + }); + }, + listPhotos(args = {}) { + return this._makeRequest({ + path: "/photos", + ...args, + }); + }, + async *getIterations({ + resourcesFn, resourcesFnArgs, resourceName, + max = constants.DEFAULT_MAX, + }) { + let page = 1; + let resourcesCount = 0; + + while (true) { + const response = + await resourcesFn({ + ...resourcesFnArgs, + params: { + ...resourcesFnArgs?.params, + page, + per_page: constants.DEFAULT_LIMIT, + }, + }); + + const nextResources = resourceName && response[resourceName] || response; + + if (!nextResources?.length) { + console.log("No more resources found"); + return; + } + + for (const resource of nextResources) { + yield resource; + resourcesCount += 1; + + if (resourcesCount >= max) { + console.log("Reached max resources"); + return; + } + } + + page += 1; + } + }, + paginate(args = {}) { + return utils.iterate(this.getIterations(args)); }, }, }; diff --git a/components/upstash_redis/package.json b/components/upstash_redis/package.json new file mode 100644 index 0000000000000..94549794c4e73 --- /dev/null +++ b/components/upstash_redis/package.json @@ -0,0 +1,15 @@ +{ + "name": "@pipedream/upstash_redis", + "version": "0.0.1", + "description": "Pipedream Upstash Redis Components", + "main": "upstash_redis.app.mjs", + "keywords": [ + "pipedream", + "upstash_redis" + ], + "homepage": "https://pipedream.com/apps/upstash_redis", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/components/upstash_redis/upstash_redis.app.mjs b/components/upstash_redis/upstash_redis.app.mjs new file mode 100644 index 0000000000000..73560f10dcf14 --- /dev/null +++ b/components/upstash_redis/upstash_redis.app.mjs @@ -0,0 +1,11 @@ +export default { + type: "app", + app: "upstash_redis", + 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/vryno/actions/create-unique-lead/create-unique-lead.mjs b/components/vryno/actions/create-unique-lead/create-unique-lead.mjs new file mode 100644 index 0000000000000..d1dc5499980df --- /dev/null +++ b/components/vryno/actions/create-unique-lead/create-unique-lead.mjs @@ -0,0 +1,221 @@ +import { ConfigurationError } from "@pipedream/platform"; +import vryno from "../../vryno.app.mjs"; + +export default { + key: "vryno-create-unique-lead", + name: "Create Unique Lead", + description: "Creates a unique lead in the Vryno system, ensuring no duplication of lead details. [See the documentation](https://vrynotest.ti2.in/docs/api-documentation/how-to-create-a-record-in-any-module-in-vryno-crm/)", + version: "0.0.1", + type: "action", + props: { + vryno, + firstName: { + type: "string", + label: "First Name", + description: "The lead's first name.", + optional: true, + }, + name: { + type: "string", + label: "Last Name", + description: "The lead's last name.", + }, + email: { + type: "string", + label: "Email", + description: "The lead's email.", + optional: true, + }, + phoneNumber: { + type: "string", + label: "Phone Number", + description: "The lead's phone number.", + optional: true, + }, + company: { + type: "string", + label: "Company", + description: "The company the lead works for.", + optional: true, + }, + website: { + type: "string", + label: "Website", + description: "The lead's website.", + optional: true, + }, + ownerId: { + type: "string", + label: "Owner Id", + description: "The user Id related to the lead.You can find the user IDs in your account -> settings -> users & controls -> users, click on some user and the ID will be in URL.", + }, + score: { + type: "integer", + label: "Score", + description: "The lead's score.", + optional: true, + }, + expectedRevenue: { + type: "integer", + label: "Expected Revenue", + description: "Expected revenue for the lead.", + optional: true, + }, + numberOfEmployees: { + type: "integer", + label: "Number Of Employees", + description: "Number of employees at the lead company.", + optional: true, + }, + billingAddress: { + type: "string", + label: "Billing Address", + description: "The lead's billing address.", + optional: true, + }, + billingCity: { + type: "string", + label: "Billing City", + description: "The lead's billing city.", + optional: true, + }, + billingState: { + type: "string", + label: "Billing State", + description: "The lead's billing state.", + optional: true, + }, + billingCountry: { + type: "string", + label: "Billing Country", + description: "The lead's billing country.", + optional: true, + }, + billingZipcode: { + type: "string", + label: "Billing Zipcode", + description: "The lead's billing zipcode", + optional: true, + }, + shippingAddress: { + type: "string", + label: "Shipping Address", + description: "The lead's shipping address.", + optional: true, + }, + shippingCity: { + type: "string", + label: "Shipping City", + description: "The lead's shipping city.", + optional: true, + }, + shippingState: { + type: "string", + label: "Shipping State", + description: "The lead's shipping state.", + optional: true, + }, + shippingCountry: { + type: "string", + label: "Shipping Country", + description: "The lead's shipping country.", + optional: true, + }, + shippingZipcode: { + type: "string", + label: "Shipping Zipcode", + description: "The lead's shipping zipcode", + optional: true, + }, + description: { + type: "string", + label: "Description", + description: "A brief description about the lead.", + optional: true, + }, + }, + async run({ $ }) { + if (!this.email && !this.phoneNumber) { + throw new ConfigurationError("You must provide at least either **Email** or **Phone Number**."); + } + const duplicateCheck = await this.vryno.post({ + data: { + query: `query { + fetchLead(filters:[${this.email + ? `{name: "email", operator:"eq",value:["${this.email}"]},` + : ""}${this.phoneNumber + ? `{name: "phoneNumber", operator:"eq",value:["${this.phoneNumber}"]}` + : ""}], + expression:"( ( a ) and b)"){ + code + status + message + messageKey + count + data { + id + } + } + }`, + }, + }); + + if (duplicateCheck.data?.fetchLead?.data?.length) { + $.export("$summary", "A lead with the same email and phone number already exists."); + return duplicateCheck.data; + } + + const { + vryno, + ...data + } = this; + + let query = `mutation { + createLead(input: { + `; + + for (const [ + field, + value, + ] of Object.entries(data)) { + query += `${field}:`; + if ([ + "score", + "expectedRevenue", + "numberOfEmployees", + ].includes(field)) { + query += ` ${value} + `; + } else { + query += ` "${value}" + `; + } + } + + query += `}) { + code + message + status + messageKey + data { + id + } + errors + } + }`; + + const response = await vryno.post({ + $, + data: { + query, + }, + }); + + if (response.data.createLead.code != 200) { + throw new ConfigurationError(response.data.createLead.message); + } + + $.export("$summary", `Successfully created new lead with Id: ${response.data.createLead.data.id}`); + return response; + }, +}; diff --git a/components/vryno/package.json b/components/vryno/package.json index 9597037e2bf7a..a0a0886d5967a 100644 --- a/components/vryno/package.json +++ b/components/vryno/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/vryno", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Vryno Components", "main": "vryno.app.mjs", "keywords": [ @@ -11,5 +11,9 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^1.6.2" } -} \ No newline at end of file +} + diff --git a/components/vryno/vryno.app.mjs b/components/vryno/vryno.app.mjs index 1359cf796fcf6..e737cd4ca0345 100644 --- a/components/vryno/vryno.app.mjs +++ b/components/vryno/vryno.app.mjs @@ -1,11 +1,33 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "vryno", - propDefinitions: {}, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return `https://${this.$auth.company_instance_name}.ms.vryno.com`; + }, + _headers() { + return { + "Authorization": `Bearer ${this.$auth.oauth_access_token}`, + "Content-Type": "application/json", + }; + }, + _makeRequest({ + $ = this, path, ...otherOpts + }) { + return axios($, { + ...otherOpts, + url: this._baseUrl() + path, + headers: this._headers(), + }); + }, + post(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/api/graphql/crm", + ...opts, + }); }, }, }; diff --git a/components/wiza/actions/create-list/create-list.mjs b/components/wiza/actions/create-list/create-list.mjs new file mode 100644 index 0000000000000..26df2cd6a46ee --- /dev/null +++ b/components/wiza/actions/create-list/create-list.mjs @@ -0,0 +1,78 @@ +import app from "../../wiza.app.mjs"; + +export default { + key: "wiza-create-list", + name: "Create List", + description: "Create a list of people to enrich. [See the documentation](https://wiza.co/api-docs#/paths/~1api~1lists/post)", + version: "0.0.1", + type: "action", + props: { + app, + name: { + propDefinition: [ + app, + "name", + ], + }, + enrichmentLevel: { + propDefinition: [ + app, + "enrichmentLevel", + ], + }, + acceptGeneric: { + propDefinition: [ + app, + "acceptGeneric", + ], + }, + acceptPersonal: { + propDefinition: [ + app, + "acceptPersonal", + ], + }, + acceptWork: { + propDefinition: [ + app, + "acceptWork", + ], + }, + fullName: { + propDefinition: [ + app, + "fullName", + ], + }, + company: { + propDefinition: [ + app, + "company", + ], + }, + }, + async run({ $ }) { + const response = await this.app.createList({ + $, + data: { + name: this.description, + enrichmentLevel: this.enrichmentLevel, + email_options: { + accept_generic: this.acceptGeneric, + accept_personal: this.acceptPersonal, + accept_work: this.acceptWork, + }, + items: [ + { + full_name: this.fullName, + company: this.company, + }, + ], + }, + }); + + $.export("$summary", `'${response.status.message}', your list's ID is '${response.data.id}'`); + + return response; + }, +}; diff --git a/components/wiza/actions/get-contacts/get-contacts.mjs b/components/wiza/actions/get-contacts/get-contacts.mjs new file mode 100644 index 0000000000000..412bc4f369666 --- /dev/null +++ b/components/wiza/actions/get-contacts/get-contacts.mjs @@ -0,0 +1,37 @@ +import app from "../../wiza.app.mjs"; + +export default { + key: "wiza-get-contacts", + name: "Get Contacts", + description: "Get contacts for a list. [See the documentation](https://wiza.co/api-docs#/paths/~1api~1lists~1%7Bid%7D/get)", + version: "0.0.1", + type: "action", + props: { + app, + id: { + propDefinition: [ + app, + "id", + ], + }, + segment: { + propDefinition: [ + app, + "segment", + ], + }, + }, + async run({ $ }) { + const response = await this.app.getContacts({ + $, + id: this.id, + params: { + segment: this.segment, + }, + }); + + $.export("$summary", "Successfully retrieved the list's contacts"); + + return response; + }, +}; diff --git a/components/wiza/actions/get-list/get-list.mjs b/components/wiza/actions/get-list/get-list.mjs new file mode 100644 index 0000000000000..8178c4dfef8ee --- /dev/null +++ b/components/wiza/actions/get-list/get-list.mjs @@ -0,0 +1,28 @@ +import app from "../../wiza.app.mjs"; + +export default { + key: "wiza-get-list", + name: "Get List", + description: "Get the list with the given id. [See the documentation](https://wiza.co/api-docs#/paths/~1api~1lists~1%7Bid%7D/get)", + version: "0.0.1", + type: "action", + props: { + app, + id: { + propDefinition: [ + app, + "id", + ], + }, + }, + async run({ $ }) { + const response = await this.app.getList({ + $, + id: this.id, + }); + + $.export("$summary", `The status of your list is: '${response.data.status}'`); + + return response; + }, +}; diff --git a/components/wiza/common/constants.mjs b/components/wiza/common/constants.mjs new file mode 100644 index 0000000000000..26fad292c8ab3 --- /dev/null +++ b/components/wiza/common/constants.mjs @@ -0,0 +1,11 @@ +export default { + SEGMENTS: [ + "people", + "valid", + "risky", + ], + ENRICHMENT_LEVELS: [ + "partial", + "full", + ], +}; diff --git a/components/wiza/package.json b/components/wiza/package.json new file mode 100644 index 0000000000000..b14fd374063dc --- /dev/null +++ b/components/wiza/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/wiza", + "version": "0.1.0", + "description": "Pipedream Wiza Components", + "main": "wiza.app.mjs", + "keywords": [ + "pipedream", + "wiza" + ], + "homepage": "https://pipedream.com/apps/wiza", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^1.6.5" + } +} diff --git a/components/wiza/wiza.app.mjs b/components/wiza/wiza.app.mjs new file mode 100644 index 0000000000000..eb16094d1028c --- /dev/null +++ b/components/wiza/wiza.app.mjs @@ -0,0 +1,100 @@ +import { axios } from "@pipedream/platform"; +import constants from "./common/constants.mjs"; + +export default { + type: "app", + app: "wiza", + propDefinitions: { + acceptWork: { + type: "boolean", + label: "Work Email", + description: "Accept professional email address? i.e. 'tim.cooke@apple.com'", + }, + acceptPersonal: { + type: "boolean", + label: "Personal Email", + description: "Accept personal email address? i.e. 'tcooke1960@gmail.com'", + }, + acceptGeneric: { + type: "boolean", + label: "Generic Email", + description: "Accept generic email address? i.e. 'hello@apple.com'", + }, + enrichmentLevel: { + type: "string", + label: "Enrichment Level", + description: "Enrichment level of the list.", + options: constants.ENRICHMENT_LEVELS, + }, + name: { + type: "string", + label: "Name", + description: "Name of the list", + }, + fullName: { + type: "string", + label: "Full Name", + description: "Full name of the contact", + }, + company: { + type: "string", + label: "Company", + description: "Name of the company", + }, + id: { + type: "string", + label: "List ID", + description: "ID of the list", + }, + segment: { + type: "string", + label: "Segment", + description: "Specify the segment of contacts to return", + options: constants.SEGMENTS, + }, + }, + methods: { + _baseUrl() { + return "https://wiza.co/api"; + }, + async _makeRequest(opts = {}) { + const { + $ = this, + path, + headers, + ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + url: this._baseUrl() + path, + headers: { + ...headers, + Authorization: `Bearer ${this.$auth.api_key}`, + }, + }); + }, + async getList({ + args, id, + }) { + return this._makeRequest({ + path: `/lists/${id}`, + ...args, + }); + }, + async getContacts({ + id, ...args + }) { + return this._makeRequest({ + path: `/lists/${id}/contacts`, + ...args, + }); + }, + async createList(args = {}) { + return this._makeRequest({ + method: "post", + path: "/lists", + ...args, + }); + }, + }, +}; diff --git a/components/woocommerce/README.md b/components/woocommerce/README.md index 05fb8622e3fbf..cb7b81097235d 100644 --- a/components/woocommerce/README.md +++ b/components/woocommerce/README.md @@ -9,3 +9,53 @@ WooCommerce is a customizable, open-source eCommerce platform built on WordPress - **Automated Order Confirmation Emails**: Use the WooCommerce API to monitor new orders and trigger personalized confirmation emails through a service like SendGrid. This workflow can include order details, expected delivery dates, and upsell opportunities. - **Customer Support Ticket Creation**: On receiving a new customer inquiry or support request via WooCommerce, instantly generate a ticket in a customer support platform like Zendesk. This ensures no customer query goes unnoticed and helps maintain high service standards. + +# Getting Started + +To connect your WooCommerce store to Pipedream, create a REST API key. + +## Creating a WooCommerce REST API Key + +Open the **WooCommerce** plugin in your WordPress admin dashboard and select the Advanced tab. Navigate to the **REST API** section and click **Create an API key**. + +![Creating an API key for WooCommerce from within your WordPress admin dashboard](https://res.cloudinary.com/pipedreamin/image/upload/v1715009382/marketplace/apps/woocommerce/CleanShot_2024-05-06_at_11.25.25_2x_tf9j1w.png) + +We recommend naming this API key "Pipedream" to easily remember its purpose. Select a user account that this API key should be tied to, ideally one with at least store manager access. + +Next, choose the level of permission you’d like Pipedream workflows to have. You can choose between: + +- **Read** - your Pipedream workflows can only read data, not update or insert new data. +- **Write** - your Pipedream workflows can update or insert data like orders and products, but cannot read them. +- **Read/Write** - your Pipedream workflows can both read and write data on your WooCommerce store. + +![Choose the user, name and the permissions level for your WooCommerce API key](https://res.cloudinary.com/pipedreamin/image/upload/v1715009382/marketplace/apps/woocommerce/CleanShot_2024-05-06_at_11.26.05_2x_rfyt9k.png) + +After generating the API key, you’ll receive a **Consumer Key** and **Consumer Secret**. Copy these values into Pipedream under the respective **Key** and **Secret** fields. + +Finally, enter your domain name. For example, for `https://example-store.com`, simply enter `example-store.com`. If your store is hosted on a subpath, like `https://my-site.com/store`, enter `my-site.com/store`. + +Double-check your store’s home URL under the **Settings area in WordPress.** + +# Troubleshooting + +## Unable to connect to the WooCommerce REST API + +### Check your Permalink structure + +![Make sure your WordPress store has the Post Name URL structure for Pipedream to be able to connect to it correctly.](https://res.cloudinary.com/pipedreamin/image/upload/v1715010321/marketplace/apps/woocommerce/CleanShot_2024-05-06_at_11.42.43_2x_qe3qu8.png) + +To enable Pipedream's access to your WooCommerce store’s REST API, ensure your WordPress site’s Permalink Structure is set to `Post name`. Open the **Settings** area in WordPress, navigate to the **Permalink** section, verify that the **Permalink** setting is set to `Post name`, and click **Save** to apply the change. + +### Check your WordPress Home URL + +If you still cannot connect your WooCommerce store to Pipedream, ensure that your site’s Home URL structure is not on a subdomain or subpath. You can find your WordPress site’s Site URL under the Settings > General section. + +If your site’s URL is `https://example.com`, then the Pipedream `domain` field for your WooCommerce connected account should be `example.com`. If your WordPress site’s home URL is under a different subdomain, such as `https://store.example.com`, then enter `store.example.com` for the `domain` field for your connected account. + +### Firewall issues + +By default, Pipedream Workflows originate from dynamic IP addresses within the `us-east-1` region in AWS. To allow Pipedream connections, set up a Pipedream VPC to assign a static IP address to your workflows. + +## Permissions issues + +Ensure the user associated with your WooCommerce API key has the necessary permissions to read or write specific resources like products or orders. Additionally, double-check that your API key has read and/or write permissions. diff --git a/components/y_gy/README.md b/components/y_gy/README.md new file mode 100644 index 0000000000000..fc0b782e12f00 --- /dev/null +++ b/components/y_gy/README.md @@ -0,0 +1,13 @@ +markdown + +# Overview + +The y.gy API allows users to shorten URLs dynamically, providing a way to manage and track URL performance in applications or marketing campaigns. Using this API on Pipedream, you can automate URL shortening and integrate these activities with other services like databases, analytics tools, or CRM systems. This capability is particularly valuable in scenarios involving high-volume link generation, targeted content distribution, and real-time link performance analysis. + +# Example Use Cases + +- **Automated URL Shortening for Social Media Posts**: Automatically shorten URLs when posting to social media platforms like Twitter or Facebook. Whenever content is scheduled or published from a CMS, trigger a workflow in Pipedream that shortens the URL using y.gy and posts the new, shortened link along with the content automatically to the targeted social media. + +- **Dynamic Link Generation for Email Marketing Campaigns**: Enhance email marketing efforts by using Pipedream to integrate y.gy with email platforms like SendGrid or Mailchimp. Automatically shorten URLs when sending out bulk or personalized emails, ensuring that links are trackable and tidy. This workflow can be set to generate links as emails are being drafted, or dynamically as they are sent. + +- **Real-Time Analytics for Shortened URLs**: Connect y.gy with analytics tools like Google Analytics through Pipedream. Set up a workflow that captures data on who clicks the shortened URLs and when, and feed this data into analytics tools for real-time analysis. This can be used for A/B testing different URLs in marketing campaigns to see which performs better in real-time. diff --git a/components/zendesk/README.md b/components/zendesk/README.md index b02f391c28123..2a422dac991c1 100644 --- a/components/zendesk/README.md +++ b/components/zendesk/README.md @@ -12,3 +12,83 @@ After a ticket is resolved, trigger a workflow to send a follow-up survey using **Real-Time Notifications for Critical Issues** Set up a Pipedream workflow that monitors Zendesk for tickets with 'Urgent' priority or specific keywords and sends instant notifications to a dedicated Slack channel or via SMS through Twilio. This ensures that critical issues are promptly addressed by support teams. + +# Getting Started + +First, log in to your [Pipedream workspace](https://pipedream.com), then connect Zendesk either through a step or trigger in a workflow, or directly from the Connected Accounts page in Pipedream. + +You'll first be prompted to enter your Zendesk subdomain. You can find this in the URL after logging into Zendesk. + +The subdomain is the portion of the URL *before* `zendesk.com`. + +For example, if the subdomain is `pipedream1903`, that's what you would enter in Pipedream. + +![Example of finding the Zendesk subdomain from the URL while logged into Zendesk](https://res.cloudinary.com/pipedreamin/image/upload/v1715183755/marketplace/apps/zendesk/CleanShot_2024-05-08_at_11.44.08_2x_ogzhhj.png) + +Next, you'll be prompted to connect your Zendesk account. Zendesk will ask if you'd like to grant Pipedream permission to perform actions on your account; accept these permissions to continue. + +And that's it! You can now automate Zendesk actions from within Pipedream workflows. + +# Troubleshooting + +## Status Codes +Responses may have the status codes described in the following sections. + +### 200 range +The request was successful. The status is 200 for successful GET and PUT requests, 201 for most POST requests, and 204 for DELETE requests. + +### 400 range +The request was not successful. The content type of the response may be text/plain for API-level error messages such as trying to call the API without SSL. The content type is application/json for business-level error messages because the response includes a JSON object with information about the error: + +```json +{ + "details": { + "value": [ + { + "type": "blank", + "description": "can't be blank" + }, + { + "type": "invalid", + "description": " is not properly formatted" + } + ] + }, + "description": "RecordValidation errors", + "error": "RecordInvalid" +} +``` + +If you see a response from a known endpoint that looks like plain text, you probably made a syntax error in your request. This type of response commonly occurs when making a request to a nonexistent Zendesk Support instance. + +### **403** + +A 403 response means the server has determined the user or the account doesn’t have the required permissions to use the API. + +### **409** + +A 409 response indicates a conflict with the resource you're trying to create or update. + +409 errors typically occur when two or more requests try to create or change the same resource simultaneously. While Zendesk APIs can handle concurrent requests, requests shouldn't change the same resource at the same time. To avoid 409 errors, serialize requests when possible. If you receive a 409 error, you can retry your request after resolving the conflict. + +The Zendesk Ticketing API provides specific parameters to prevent conflicts when updating tickets. For more information, see Protecting against ticket update collisions. + +### **422 Unprocessable Entity** + +A 422 response means that the content type and the syntax of the request entity are correct, but the content itself is not processable by the server. This is usually due to the request entity not being relevant to the resource that it's trying to create or update. Example: Trying to close a ticket that's already closed. + +### **429** + +A 429 error indicates that a usage limit has been exceeded. See the [Zendesk Rate limits](https://developer.zendesk.com/api-reference/introduction/rate-limits/). + +### **500 range** + +If you ever experience responses with status codes in the 500 range, the Zendesk API may be experiencing internal issues or having a scheduled maintenance during which you might receive a 503 Service Unavailable status code. + +A 503 response with a `Retry-After` header indicates a database timeout or deadlock. You can retry your request after the number of seconds specified in the `Retry-After` header. + +If the 503 response doesn't have a Retry-After header, Zendesk Support may be experiencing internal issues or undergoing scheduled maintenance. In such cases, check `@zendeskops` and our status page for any known issues. + +When building an API client, we recommend treating any 500 status codes as a warning or temporary state. However, if the status persists and if Zendesk doesn't have a publicly announced maintenance or service disruption, contact the [Zendesk Customer Support](https://support.zendesk.com/hc/en-us/articles/360026614173). + +If submitting a ticket to Zendesk, provide the `X-Zendesk-Request-Id` header included in the HTTP response. This helps the Support team track down the request in the logs more quickly. diff --git a/components/zip_archive_api/README.md b/components/zip_archive_api/README.md new file mode 100644 index 0000000000000..edf3ff80346f0 --- /dev/null +++ b/components/zip_archive_api/README.md @@ -0,0 +1,11 @@ +# Overview + +The Zip Archive API allows you to manage zip archives programmatically. You can create, extract, and modify zip files, making it ideal for automating routine file management tasks. Integrating this API with Pipedream enables you to automate workflows that involve file compression or decompression, combine it with triggers from other apps, and process large batches of files efficiently, saving time and reducing manual effort. + +# Example Use Cases + +- **Automated Backup and Compression for Cloud Storage**: Automatically zip files uploaded to a Dropbox folder and then store the compressed archive in Google Drive. This can be useful for archiving documents and saving space in cloud storage. + +- **Email Attachments Compression**: Monitor an email inbox for new messages with attachments using the Gmail trigger, compress the attachments into a zip file, and upload the archive to a specific FTP server or cloud storage service. This is particularly useful for businesses that regularly receive large attachments and want to streamline storage. + +- **Website Asset Management**: Automatically compress website assets like CSS, JS, and images whenever they are updated in a GitHub repository. After compression, the zip archive could be pushed back to another branch within the repo or deployed to a web server. This workflow helps in optimizing the loading speed of web pages. diff --git a/components/zoom/actions/add-meeting-registrant/add-meeting-registrant.mjs b/components/zoom/actions/add-meeting-registrant/add-meeting-registrant.mjs index 968af7856c43d..7a3d2375cdff3 100644 --- a/components/zoom/actions/add-meeting-registrant/add-meeting-registrant.mjs +++ b/components/zoom/actions/add-meeting-registrant/add-meeting-registrant.mjs @@ -4,7 +4,7 @@ export default { key: "zoom-add-meeting-registrant", name: "Add Meeting Registrant", description: "Registers a participant for a meeting. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/methods/#operation/meetingRegistrantCreate)", - version: "0.3.0", + version: "0.3.2", type: "action", props: { app, diff --git a/components/zoom/actions/add-webinar-registrant/add-webinar-registrant.mjs b/components/zoom/actions/add-webinar-registrant/add-webinar-registrant.mjs index b2e179a3e6e4b..73cad3b4387c8 100644 --- a/components/zoom/actions/add-webinar-registrant/add-webinar-registrant.mjs +++ b/components/zoom/actions/add-webinar-registrant/add-webinar-registrant.mjs @@ -4,7 +4,7 @@ export default { key: "zoom-add-webinar-registrant", name: "Add Webinar Registrant", description: "Registers a participant for a webinar. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/webinars/webinarregistrantcreate).", - version: "0.3.0", + version: "0.3.2", type: "action", props: { app, diff --git a/components/zoom/actions/create-meeting/create-meeting.mjs b/components/zoom/actions/create-meeting/create-meeting.mjs index 08175aba6d39b..12a1a8d7d4d33 100644 --- a/components/zoom/actions/create-meeting/create-meeting.mjs +++ b/components/zoom/actions/create-meeting/create-meeting.mjs @@ -5,7 +5,7 @@ export default { key: "zoom-create-meeting", name: "Create Meeting", description: "Creates a meeting for a user. A maximum of 100 meetings can be created for a user in a day.", - version: "0.1.1", + version: "0.1.3", type: "action", props: { zoom: { diff --git a/components/zoom/actions/create-user/create-user.mjs b/components/zoom/actions/create-user/create-user.mjs index fe24e092d87c1..a214eac26c107 100644 --- a/components/zoom/actions/create-user/create-user.mjs +++ b/components/zoom/actions/create-user/create-user.mjs @@ -5,7 +5,7 @@ export default { key: "zoom-create-user", name: "Create User", description: "Creates a new user in your account.", - version: "0.2.1", + version: "0.2.3", type: "action", props: { zoom: { diff --git a/components/zoom/actions/delete-user/delete-user.mjs b/components/zoom/actions/delete-user/delete-user.mjs index 7c4b6e5f8d3eb..b573b4d6705c2 100644 --- a/components/zoom/actions/delete-user/delete-user.mjs +++ b/components/zoom/actions/delete-user/delete-user.mjs @@ -5,7 +5,7 @@ export default { key: "zoom-delete-user", name: "Delete User", description: "Disassociates (unlinks) a user from the associated account or permanently deletes a user.", - version: "0.2.1", + version: "0.2.3", type: "action", props: { zoom: { diff --git a/components/zoom/actions/get-meeting-details/get-meeting-details.mjs b/components/zoom/actions/get-meeting-details/get-meeting-details.mjs index 3e409abaaf558..e13a3b6ad81b3 100644 --- a/components/zoom/actions/get-meeting-details/get-meeting-details.mjs +++ b/components/zoom/actions/get-meeting-details/get-meeting-details.mjs @@ -5,7 +5,7 @@ export default { key: "zoom-get-meeting-details", name: "Get Meeting Details", description: "Retrieves the details of a meeting.", - version: "0.3.1", + version: "0.3.3", type: "action", props: { zoom: { diff --git a/components/zoom/actions/get-webinar-details/get-webinar-details.mjs b/components/zoom/actions/get-webinar-details/get-webinar-details.mjs index 7dcbaa163fe88..3a2340a3d5cd6 100644 --- a/components/zoom/actions/get-webinar-details/get-webinar-details.mjs +++ b/components/zoom/actions/get-webinar-details/get-webinar-details.mjs @@ -4,7 +4,7 @@ export default { key: "zoom-get-webinar-details", name: "Get Webinar Details", description: "Gets details of a scheduled webinar. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/methods/#operation/webinar).", - version: "0.3.0", + version: "0.3.2", type: "action", props: { app, diff --git a/components/zoom/actions/list-channels/list-channels.mjs b/components/zoom/actions/list-channels/list-channels.mjs index d397ec8ca10de..93d647b4f630e 100644 --- a/components/zoom/actions/list-channels/list-channels.mjs +++ b/components/zoom/actions/list-channels/list-channels.mjs @@ -5,7 +5,7 @@ export default { key: "zoom-list-channels", name: "List Channels", description: "List a user's chat channels.", - version: "0.1.1", + version: "0.1.3", type: "action", props: { zoom: { diff --git a/components/zoom/actions/list-past-meeting-participants/list-past-meeting-participants.mjs b/components/zoom/actions/list-past-meeting-participants/list-past-meeting-participants.mjs index 443c2d4ca3620..2151827299278 100644 --- a/components/zoom/actions/list-past-meeting-participants/list-past-meeting-participants.mjs +++ b/components/zoom/actions/list-past-meeting-participants/list-past-meeting-participants.mjs @@ -4,7 +4,7 @@ export default { key: "zoom-list-past-meeting-participants", name: "List Past Meeting Participants", description: "Retrieve information on participants from a past meeting. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/methods/#operation/pastMeetingParticipants).", - version: "0.2.0", + version: "0.2.2", type: "action", props: { app, diff --git a/components/zoom/actions/list-past-webinar-qa/list-past-webinar-qa.mjs b/components/zoom/actions/list-past-webinar-qa/list-past-webinar-qa.mjs index 4119b9c927367..6ff11db6fa02b 100644 --- a/components/zoom/actions/list-past-webinar-qa/list-past-webinar-qa.mjs +++ b/components/zoom/actions/list-past-webinar-qa/list-past-webinar-qa.mjs @@ -5,7 +5,7 @@ export default { key: "zoom-list-past-webinar-qa", name: "List Past Webinar Q&A", description: "The feature for Webinars allows attendees to ask questions during the Webinar and for the panelists, co-hosts and host to answer their questions. Use this API to list Q&A of a specific Webinar.", - version: "0.1.1", + version: "0.1.3", type: "action", props: { zoom: { diff --git a/components/zoom/actions/list-user-call-logs/list-user-call-logs.mjs b/components/zoom/actions/list-user-call-logs/list-user-call-logs.mjs new file mode 100644 index 0000000000000..cfd18156d402c --- /dev/null +++ b/components/zoom/actions/list-user-call-logs/list-user-call-logs.mjs @@ -0,0 +1,31 @@ +import zoom from "../../zoom.app.mjs"; + +export default { + name: "List User's Call Logs", + description: "Gets a user's Zoom phone call logs. [See the documentation](https://developers.zoom.us/docs/zoom-phone/apis/#operation/phoneUserCallLogs)", + key: "zoom-list-user-call-logs", + version: "0.0.2", + type: "action", + props: { + zoom, + userId: { + propDefinition: [ + zoom, + "userId", + ], + }, + }, + async run ({ $ }) { + const data = await this.zoom.getResourcesStream({ + resourceFn: this.zoom.listCallLogs, + resourceFnArgs: { + userId: this.userId, + }, + resourceName: "call_logs", + }); + + $.export("$summary", `Successfully fetched ${data.length} call log(s)`); + + return data; + }, +}; diff --git a/components/zoom/actions/list-webinar-participants-report/list-webinar-participants-report.mjs b/components/zoom/actions/list-webinar-participants-report/list-webinar-participants-report.mjs index d07bf86075382..5830b813a2141 100644 --- a/components/zoom/actions/list-webinar-participants-report/list-webinar-participants-report.mjs +++ b/components/zoom/actions/list-webinar-participants-report/list-webinar-participants-report.mjs @@ -1,11 +1,11 @@ -import app from "../../zoom.app.mjs"; import utils from "../../common/utils.mjs"; +import app from "../../zoom.app.mjs"; export default { key: "zoom-list-webinar-participants-report", name: "List Webinar Participants Report", description: "Retrieves detailed report on each webinar attendee. You can get webinar participant reports for the last 6 months. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/methods/#operation/reportWebinarParticipants).", - version: "0.0.1", + version: "0.0.3", type: "action", props: { app, diff --git a/components/zoom/actions/send-chat-message/send-chat-message.mjs b/components/zoom/actions/send-chat-message/send-chat-message.mjs index dc338018e2a44..00267e5fa7666 100644 --- a/components/zoom/actions/send-chat-message/send-chat-message.mjs +++ b/components/zoom/actions/send-chat-message/send-chat-message.mjs @@ -5,7 +5,7 @@ export default { key: "zoom-send-chat-message", name: "Send Chat Message", description: "Send chat messages on Zoom to either an individual user who is in your contact list or to a of which you are a member.", - version: "0.1.1", + version: "0.1.3", type: "action", props: { zoom: { diff --git a/components/zoom/actions/update-meeting/update-meeting.mjs b/components/zoom/actions/update-meeting/update-meeting.mjs index 8e0d547431142..2f05804f395d2 100644 --- a/components/zoom/actions/update-meeting/update-meeting.mjs +++ b/components/zoom/actions/update-meeting/update-meeting.mjs @@ -5,7 +5,7 @@ export default { key: "zoom-update-meeting", name: "Update Meeting", description: "Updates an existing Zoom meeting", - version: "0.1.1", + version: "0.1.3", type: "action", props: { zoom: { diff --git a/components/zoom/actions/update-webinar/update-webinar.mjs b/components/zoom/actions/update-webinar/update-webinar.mjs index 072e2d6e7ffaf..65be6956735fa 100644 --- a/components/zoom/actions/update-webinar/update-webinar.mjs +++ b/components/zoom/actions/update-webinar/update-webinar.mjs @@ -5,7 +5,7 @@ export default { key: "zoom-update-webinar", name: "Update Webinar", description: "Update a webinar's topic, start time, or other settings", - version: "0.1.1", + version: "0.1.3", type: "action", props: { zoom: { diff --git a/components/zoom/actions/view-user/view-user.mjs b/components/zoom/actions/view-user/view-user.mjs index 14877c9294037..9874f18d8f517 100644 --- a/components/zoom/actions/view-user/view-user.mjs +++ b/components/zoom/actions/view-user/view-user.mjs @@ -5,7 +5,7 @@ export default { key: "zoom-view-user", name: "View User", description: "View your user information", - version: "0.1.1", + version: "0.1.3", type: "action", props: { zoom: { diff --git a/components/zoom/package.json b/components/zoom/package.json index 9e0d81808c2c4..6896378ff916c 100644 --- a/components/zoom/package.json +++ b/components/zoom/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/zoom", - "version": "0.4.0", + "version": "0.5.1", "description": "Pipedream Zoom Components", "main": "zoom.app.mjs", "keywords": [ diff --git a/components/zoom/sources/custom-event/custom-event.mjs b/components/zoom/sources/custom-event/custom-event.mjs index 86dfe464c3d85..4c7d96efce86e 100644 --- a/components/zoom/sources/custom-event/custom-event.mjs +++ b/components/zoom/sources/custom-event/custom-event.mjs @@ -6,7 +6,7 @@ export default { key: "zoom-custom-event", name: "Custom Events (Instant)", description: "Emit new events tied to your Zoom user or resources you own", - version: "0.1.0", + version: "0.1.2", type: "source", dedupe: "unique", props: { diff --git a/components/zoom/sources/meeting-created/meeting-created.mjs b/components/zoom/sources/meeting-created/meeting-created.mjs index 0ae5e4392acf4..9576ce2119b3e 100644 --- a/components/zoom/sources/meeting-created/meeting-created.mjs +++ b/components/zoom/sources/meeting-created/meeting-created.mjs @@ -6,7 +6,7 @@ export default { key: "zoom-meeting-created", name: "Meeting Created (Instant)", description: "Emit new event each time a meeting is created where you're the host", - version: "0.1.0", + version: "0.1.2", type: "source", dedupe: "unique", props: { diff --git a/components/zoom/sources/meeting-deleted/meeting-deleted.mjs b/components/zoom/sources/meeting-deleted/meeting-deleted.mjs index f2828972d3792..b83a77bc9afde 100644 --- a/components/zoom/sources/meeting-deleted/meeting-deleted.mjs +++ b/components/zoom/sources/meeting-deleted/meeting-deleted.mjs @@ -6,7 +6,7 @@ export default { key: "zoom-meeting-deleted", name: "Meeting Deleted (Instant)", description: "Emit new event each time a meeting is deleted where you're the host", - version: "0.1.0", + version: "0.1.2", type: "source", dedupe: "unique", props: { diff --git a/components/zoom/sources/meeting-ended/meeting-ended.mjs b/components/zoom/sources/meeting-ended/meeting-ended.mjs index 5adff1553cfef..6c1e1600bb82b 100644 --- a/components/zoom/sources/meeting-ended/meeting-ended.mjs +++ b/components/zoom/sources/meeting-ended/meeting-ended.mjs @@ -6,7 +6,7 @@ export default { key: "zoom-meeting-ended", name: "Meeting Ended (Instant)", description: "Emit new event each time a meeting ends where you're the host", - version: "0.1.0", + version: "0.1.2", type: "source", dedupe: "unique", props: { diff --git a/components/zoom/sources/meeting-started/meeting-started.mjs b/components/zoom/sources/meeting-started/meeting-started.mjs index dfd0b657a3a61..95fe7506376ed 100644 --- a/components/zoom/sources/meeting-started/meeting-started.mjs +++ b/components/zoom/sources/meeting-started/meeting-started.mjs @@ -6,7 +6,7 @@ export default { key: "zoom-meeting-started", name: "Meeting Started (Instant)", description: "Emit new event each time a meeting starts where you're the host", - version: "0.1.0", + version: "0.1.3", type: "source", dedupe: "unique", props: { diff --git a/components/zoom/sources/meeting-updated/meeting-updated.mjs b/components/zoom/sources/meeting-updated/meeting-updated.mjs index 9acf436e88285..1fc6f9ad38835 100644 --- a/components/zoom/sources/meeting-updated/meeting-updated.mjs +++ b/components/zoom/sources/meeting-updated/meeting-updated.mjs @@ -6,7 +6,7 @@ export default { key: "zoom-meeting-updated", name: "Meeting Updated (Instant)", description: "Emit new event each time a meeting is updated where you're the host", - version: "0.1.0", + version: "0.1.2", type: "source", dedupe: "unique", props: { diff --git a/components/zoom/sources/phone-event/phone-event.mjs b/components/zoom/sources/phone-event/phone-event.mjs index 882f223113a18..06c081c3af873 100644 --- a/components/zoom/sources/phone-event/phone-event.mjs +++ b/components/zoom/sources/phone-event/phone-event.mjs @@ -6,7 +6,7 @@ export default { key: "zoom-phone-event", name: "Zoom Phone Events (Instant)", description: "Emit new Zoom Phone event tied to your Zoom user or resources you own", - version: "0.1.0", + version: "0.1.2", type: "source", props: { ...common.props, diff --git a/components/zoom/sources/recording-completed/recording-completed.mjs b/components/zoom/sources/recording-completed/recording-completed.mjs index 93c0ac8c0d577..e152b03892a9e 100644 --- a/components/zoom/sources/recording-completed/recording-completed.mjs +++ b/components/zoom/sources/recording-completed/recording-completed.mjs @@ -6,7 +6,7 @@ export default { key: "zoom-recording-completed", name: "Recording Completed (Instant)", description: "Emit new event each time a new recording completes for a meeting or webinar where you're the host", - version: "0.1.0", + version: "0.1.2", type: "source", dedupe: "unique", props: { diff --git a/components/zoom/sources/webinar-created/webinar-created.mjs b/components/zoom/sources/webinar-created/webinar-created.mjs index 4668ff767ddf1..9a767df03e41a 100644 --- a/components/zoom/sources/webinar-created/webinar-created.mjs +++ b/components/zoom/sources/webinar-created/webinar-created.mjs @@ -6,7 +6,7 @@ export default { key: "zoom-webinar-created", name: "Webinar Created (Instant)", description: "Emit new event each time a webinar is created where you're the host", - version: "0.1.0", + version: "0.1.2", type: "source", dedupe: "unique", props: { diff --git a/components/zoom/sources/webinar-deleted/webinar-deleted.mjs b/components/zoom/sources/webinar-deleted/webinar-deleted.mjs index 8226639386408..cb8d8599ebe27 100644 --- a/components/zoom/sources/webinar-deleted/webinar-deleted.mjs +++ b/components/zoom/sources/webinar-deleted/webinar-deleted.mjs @@ -6,7 +6,7 @@ export default { key: "zoom-webinar-deleted", name: "Webinar Deleted (Instant)", description: "Emit new event each time a webinar is deleted where you're the host", - version: "0.1.0", + version: "0.1.2", type: "source", dedupe: "unique", props: { diff --git a/components/zoom/sources/webinar-ended/webinar-ended.mjs b/components/zoom/sources/webinar-ended/webinar-ended.mjs index 1ec441c0999c8..0d3ba02fb7645 100644 --- a/components/zoom/sources/webinar-ended/webinar-ended.mjs +++ b/components/zoom/sources/webinar-ended/webinar-ended.mjs @@ -6,7 +6,7 @@ export default { key: "zoom-webinar-ended", name: "Webinar Ended (Instant)", description: "Emit new event each time a webinar ends where you're the host", - version: "0.1.0", + version: "0.1.2", type: "source", dedupe: "unique", props: { diff --git a/components/zoom/sources/webinar-started/webinar-started.mjs b/components/zoom/sources/webinar-started/webinar-started.mjs index b63503f0840ee..b3dfd32b8ad09 100644 --- a/components/zoom/sources/webinar-started/webinar-started.mjs +++ b/components/zoom/sources/webinar-started/webinar-started.mjs @@ -6,7 +6,7 @@ export default { key: "zoom-webinar-started", name: "Webinar Started (Instant)", description: "Emit new event each time a webinar starts where you're the host", - version: "0.1.0", + version: "0.1.2", type: "source", dedupe: "unique", props: { diff --git a/components/zoom/sources/webinar-updated/webinar-updated.mjs b/components/zoom/sources/webinar-updated/webinar-updated.mjs index a1380e4b5297a..9ffcf53d18bf5 100644 --- a/components/zoom/sources/webinar-updated/webinar-updated.mjs +++ b/components/zoom/sources/webinar-updated/webinar-updated.mjs @@ -6,7 +6,7 @@ export default { key: "zoom-webinar-updated", name: "Webinar Updated (Instant)", description: "Emit new event each time a webinar is updated where you're the host", - version: "0.1.0", + version: "0.1.2", type: "source", dedupe: "unique", props: { diff --git a/components/zoom/zoom.app.mjs b/components/zoom/zoom.app.mjs index d71b12eac66b5..f7180ec177083 100644 --- a/components/zoom/zoom.app.mjs +++ b/components/zoom/zoom.app.mjs @@ -54,6 +54,31 @@ export default { }, optional: true, }, + userId: { + type: "string", + label: "User Id", + description: "The user ID or email address of the user.", + async options({ prevContext }) { + const { nextPageToken } = prevContext; + const response = await this.listUsers({ + params: { + next_page_token: nextPageToken, + }, + }); + + return { + options: response.users.map(({ + name, email, id: value, + }) => ({ + label: `${name} - ${email}`, + value, + })), + context: { + nextPageToken: response.next_page_token, + }, + }; + }, + }, includeAudioRecordings: { type: "boolean", label: "Include Audio Recordings", @@ -251,6 +276,20 @@ export default { ...args, }); }, + listUsers(opts = {}) { + return this._makeRequest({ + path: "/phone/users", + ...opts, + }); + }, + listCallLogs({ + userId, ...args + }) { + return this._makeRequest({ + path: `/phone/users/${userId}/call_logs`, + ...args, + }); + }, async *getResourcesStream({ resourceFn, resourceFnArgs, diff --git a/components/zoom_admin/actions/add-meeting-registrant/add-meeting-registrant.mjs b/components/zoom_admin/actions/add-meeting-registrant/add-meeting-registrant.mjs index e69b9f642f4de..7b9838f7d56e2 100644 --- a/components/zoom_admin/actions/add-meeting-registrant/add-meeting-registrant.mjs +++ b/components/zoom_admin/actions/add-meeting-registrant/add-meeting-registrant.mjs @@ -1,13 +1,13 @@ -import zoomAdmin from "../../zoom_admin.app.mjs"; +import { axios } from "@pipedream/platform"; import get from "lodash/get.js"; import isArray from "lodash/isArray.js"; -import { axios } from "@pipedream/platform"; +import zoomAdmin from "../../zoom_admin.app.mjs"; export default { name: "Add meeting registrant", description: "Register a participant for a meeting. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingregistrantcreate)", key: "zoom_admin-add-meeting-registrant", - version: "0.1.3", + version: "0.1.5", type: "action", props: { zoomAdmin, diff --git a/components/zoom_admin/actions/add-webinar-panelist/add-webinar-panelist.mjs b/components/zoom_admin/actions/add-webinar-panelist/add-webinar-panelist.mjs index 84f02f403225a..88c40fac92d7f 100644 --- a/components/zoom_admin/actions/add-webinar-panelist/add-webinar-panelist.mjs +++ b/components/zoom_admin/actions/add-webinar-panelist/add-webinar-panelist.mjs @@ -1,12 +1,12 @@ -import zoomAdmin from "../../zoom_admin.app.mjs"; -import get from "lodash/get.js"; import { axios } from "@pipedream/platform"; +import get from "lodash/get.js"; +import zoomAdmin from "../../zoom_admin.app.mjs"; export default { name: "Add webinar panelist", description: "Register a panelist for a webinar. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/webinars/webinarpanelistcreate)", key: "zoom_admin-add-webinar-panelist", - version: "0.1.3", + version: "0.1.6", type: "action", props: { zoomAdmin, diff --git a/components/zoom_admin/actions/add-webinar-registrant/add-webinar-registrant.mjs b/components/zoom_admin/actions/add-webinar-registrant/add-webinar-registrant.mjs index 220ee4514c9b8..00b7bb7c353f1 100644 --- a/components/zoom_admin/actions/add-webinar-registrant/add-webinar-registrant.mjs +++ b/components/zoom_admin/actions/add-webinar-registrant/add-webinar-registrant.mjs @@ -1,13 +1,13 @@ -import zoomAdmin from "../../zoom_admin.app.mjs"; +import { axios } from "@pipedream/platform"; import get from "lodash/get.js"; import isArray from "lodash/isArray.js"; -import { axios } from "@pipedream/platform"; +import zoomAdmin from "../../zoom_admin.app.mjs"; export default { name: "Add webinar registrant", description: "Register a participant for a webinar. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/webinars/webinarregistrantcreate)", key: "zoom_admin-add-webinar-registrant", - version: "0.1.3", + version: "0.1.5", type: "action", props: { zoomAdmin, diff --git a/components/zoom_admin/actions/create-meeting/create-meeting.mjs b/components/zoom_admin/actions/create-meeting/create-meeting.mjs index 2a1c2d8ee49da..1a1c5117733ca 100644 --- a/components/zoom_admin/actions/create-meeting/create-meeting.mjs +++ b/components/zoom_admin/actions/create-meeting/create-meeting.mjs @@ -1,7 +1,7 @@ +import { axios } from "@pipedream/platform"; +import consts from "../../consts.mjs"; import zoomAdmin from "../../zoom_admin.app.mjs"; import tzs from "../../zoom_tzs.mjs"; -import consts from "../../consts.mjs"; -import { axios } from "@pipedream/platform"; const { MEETING_TYPE_OPTIONS } = consts; @@ -9,7 +9,7 @@ export default { name: "Create a meeting", description: "Create a new room in zoom. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingcreate)", key: "zoom_admin-create-meeting", - version: "0.1.3", + version: "0.1.5", type: "action", props: { zoomAdmin, diff --git a/components/zoom_admin/actions/create-webinar/create-webinar.mjs b/components/zoom_admin/actions/create-webinar/create-webinar.mjs index 57f009172cadb..5840418f1319a 100644 --- a/components/zoom_admin/actions/create-webinar/create-webinar.mjs +++ b/components/zoom_admin/actions/create-webinar/create-webinar.mjs @@ -1,7 +1,7 @@ +import { axios } from "@pipedream/platform"; +import consts from "../../consts.mjs"; import zoomAdmin from "../../zoom_admin.app.mjs"; import tzs from "../../zoom_tzs.mjs"; -import consts from "../../consts.mjs"; -import { axios } from "@pipedream/platform"; const { RECURRENCE_TYPE_OPTIONS } = consts; @@ -9,7 +9,7 @@ export default { name: "Create Webinar", description: "Create a webinar for an user. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/webinars/webinarcreate)", key: "zoom_admin-create-webinar", - version: "0.1.3", + version: "0.1.5", type: "action", props: { zoomAdmin, diff --git a/components/zoom_admin/actions/delete-cloud-recording/delete-cloud-recording.mjs b/components/zoom_admin/actions/delete-cloud-recording/delete-cloud-recording.mjs index d40875cbd4e7d..18bcf06f74fe3 100644 --- a/components/zoom_admin/actions/delete-cloud-recording/delete-cloud-recording.mjs +++ b/components/zoom_admin/actions/delete-cloud-recording/delete-cloud-recording.mjs @@ -1,13 +1,13 @@ -import zoomAdmin from "../../zoom_admin.app.mjs"; -import isObject from "lodash/isObject.js"; import { axios } from "@pipedream/platform"; +import isObject from "lodash/isObject.js"; import consts from "../../consts.mjs"; +import zoomAdmin from "../../zoom_admin.app.mjs"; export default { name: "Delete Cloud Recording", description: "Remove a recording from a meeting or webinar. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/cloud-recording/recordingdeleteone)", key: "zoom_admin-delete-cloud-recording", - version: "0.1.3", + version: "0.1.5", type: "action", props: { zoomAdmin, diff --git a/components/zoom_admin/actions/delete-meeting/delete-meeting.mjs b/components/zoom_admin/actions/delete-meeting/delete-meeting.mjs index 9ef6f68d00672..74e7b44478083 100644 --- a/components/zoom_admin/actions/delete-meeting/delete-meeting.mjs +++ b/components/zoom_admin/actions/delete-meeting/delete-meeting.mjs @@ -1,12 +1,12 @@ -import zoomAdmin from "../../zoom_admin.app.mjs"; -import get from "lodash/get.js"; import { axios } from "@pipedream/platform"; +import get from "lodash/get.js"; +import zoomAdmin from "../../zoom_admin.app.mjs"; export default { name: "Delete meeting", description: "Delete a meeting. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingdelete)", key: "zoom_admin-delete-meeting", - version: "0.1.3", + version: "0.1.5", type: "action", props: { zoomAdmin, diff --git a/components/zoom_admin/actions/delete-webinar-panelist/delete-webinar-panelist.mjs b/components/zoom_admin/actions/delete-webinar-panelist/delete-webinar-panelist.mjs index d1d81e0cdcbbe..6e3564a9f92c7 100644 --- a/components/zoom_admin/actions/delete-webinar-panelist/delete-webinar-panelist.mjs +++ b/components/zoom_admin/actions/delete-webinar-panelist/delete-webinar-panelist.mjs @@ -1,12 +1,12 @@ -import zoomAdmin from "../../zoom_admin.app.mjs"; -import get from "lodash/get.js"; import { axios } from "@pipedream/platform"; +import get from "lodash/get.js"; +import zoomAdmin from "../../zoom_admin.app.mjs"; export default { name: "Delete webinar panelist", description: "Remove a panelist from a webinar. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/webinars/webinarpanelistdelete)", key: "zoom_admin-delete-webinar-panelist", - version: "0.1.3", + version: "0.1.5", type: "action", props: { zoomAdmin, diff --git a/components/zoom_admin/actions/delete-webinar/delete-webinar.mjs b/components/zoom_admin/actions/delete-webinar/delete-webinar.mjs index 7c72114fe0b44..5416b365831b5 100644 --- a/components/zoom_admin/actions/delete-webinar/delete-webinar.mjs +++ b/components/zoom_admin/actions/delete-webinar/delete-webinar.mjs @@ -1,12 +1,12 @@ -import zoomAdmin from "../../zoom_admin.app.mjs"; -import get from "lodash/get.js"; import { axios } from "@pipedream/platform"; +import get from "lodash/get.js"; +import zoomAdmin from "../../zoom_admin.app.mjs"; export default { name: "Delete webinar", description: "Delete a webinar. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/webinars/webinardelete)", key: "zoom_admin-delete-webinar", - version: "0.1.3", + version: "0.1.5", type: "action", props: { zoomAdmin, diff --git a/components/zoom_admin/actions/end-meeting/end-meeting.mjs b/components/zoom_admin/actions/end-meeting/end-meeting.mjs index 23c9ea2e94234..02446347a0c08 100644 --- a/components/zoom_admin/actions/end-meeting/end-meeting.mjs +++ b/components/zoom_admin/actions/end-meeting/end-meeting.mjs @@ -1,12 +1,12 @@ -import zoomAdmin from "../../zoom_admin.app.mjs"; -import get from "lodash/get.js"; import { axios } from "@pipedream/platform"; +import get from "lodash/get.js"; +import zoomAdmin from "../../zoom_admin.app.mjs"; export default { name: "End meeting", description: "End a meeting for a user. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingstatus)", key: "zoom_admin-end-meeting", - version: "0.1.3", + version: "0.1.5", type: "action", props: { zoomAdmin, diff --git a/components/zoom_admin/actions/get-meeting/get-meeting.mjs b/components/zoom_admin/actions/get-meeting/get-meeting.mjs index 6393e482fae59..076e414c40aac 100644 --- a/components/zoom_admin/actions/get-meeting/get-meeting.mjs +++ b/components/zoom_admin/actions/get-meeting/get-meeting.mjs @@ -1,12 +1,12 @@ -import zoomAdmin from "../../zoom_admin.app.mjs"; -import get from "lodash/get.js"; import { axios } from "@pipedream/platform"; +import get from "lodash/get.js"; +import zoomAdmin from "../../zoom_admin.app.mjs"; export default { name: "Get Meeting", description: "Retrieve the details of a meeting. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meeting)", key: "zoom_admin-get-meeting", - version: "0.1.4", + version: "0.1.6", type: "action", props: { zoomAdmin, diff --git a/components/zoom_admin/actions/get-webinar/get-webinar.mjs b/components/zoom_admin/actions/get-webinar/get-webinar.mjs index 2ee5224cccae3..c5e2b2042da65 100644 --- a/components/zoom_admin/actions/get-webinar/get-webinar.mjs +++ b/components/zoom_admin/actions/get-webinar/get-webinar.mjs @@ -1,12 +1,12 @@ -import zoomAdmin from "../../zoom_admin.app.mjs"; -import get from "lodash/get.js"; import { axios } from "@pipedream/platform"; +import get from "lodash/get.js"; +import zoomAdmin from "../../zoom_admin.app.mjs"; export default { name: "Get Webinar", description: "Retrieve the details of a webinar. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/webinars/webinar)", key: "zoom_admin-get-webinar", - version: "0.1.3", + version: "0.1.5", type: "action", props: { zoomAdmin, diff --git a/components/zoom_admin/actions/list-account-call-logs/list-account-call-logs.mjs b/components/zoom_admin/actions/list-account-call-logs/list-account-call-logs.mjs new file mode 100644 index 0000000000000..8be5c321acfac --- /dev/null +++ b/components/zoom_admin/actions/list-account-call-logs/list-account-call-logs.mjs @@ -0,0 +1,23 @@ +import { paginate } from "../../common/pagination.mjs"; +import zoomAdmin from "../../zoom_admin.app.mjs"; + +export default { + name: "List Account Call Logs", + description: "Returns an account's new edition call logs. [See the documentation](https://developers.zoom.us/docs/zoom-phone/apis/#operation/accountCallHistory)", + key: "zoom_admin-list-account-call-logs", + version: "0.0.2", + type: "action", + props: { + zoomAdmin, + }, + async run ({ $ }) { + const data = await paginate( + this.zoomAdmin.listAccountCallLogs, + "call_logs", + ); + + $.export("$summary", `Successfully fetched ${data.length} call log(s)`); + + return data; + }, +}; diff --git a/components/zoom_admin/actions/list-cloud-recordings/list-cloud-recordings.mjs b/components/zoom_admin/actions/list-cloud-recordings/list-cloud-recordings.mjs index e4917170d3735..ff5f7559b42ad 100644 --- a/components/zoom_admin/actions/list-cloud-recordings/list-cloud-recordings.mjs +++ b/components/zoom_admin/actions/list-cloud-recordings/list-cloud-recordings.mjs @@ -1,12 +1,12 @@ -import zoomAdmin from "../../zoom_admin.app.mjs"; -import consts from "../../consts.mjs"; import { paginate } from "../../common/pagination.mjs"; +import consts from "../../consts.mjs"; +import zoomAdmin from "../../zoom_admin.app.mjs"; export default { name: "List Cloud Recordings", description: "Search cloud recordings from a meeting or webinar. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/cloud-recording/recordingslist)", key: "zoom_admin-list-cloud-recordings", - version: "0.2.0", + version: "0.2.2", type: "action", props: { zoomAdmin, diff --git a/components/zoom_admin/actions/list-meeting-registrants/list-meeting-registrants.mjs b/components/zoom_admin/actions/list-meeting-registrants/list-meeting-registrants.mjs index 4faff23b1d667..5111e58a48f95 100644 --- a/components/zoom_admin/actions/list-meeting-registrants/list-meeting-registrants.mjs +++ b/components/zoom_admin/actions/list-meeting-registrants/list-meeting-registrants.mjs @@ -1,13 +1,13 @@ -import zoomAdmin from "../../zoom_admin.app.mjs"; import get from "lodash/get.js"; -import consts from "../../consts.mjs"; import { paginate } from "../../common/pagination.mjs"; +import consts from "../../consts.mjs"; +import zoomAdmin from "../../zoom_admin.app.mjs"; export default { name: "List meeting registrants", description: "List all users who have registered for a meeting. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingregistrants)", key: "zoom_admin-list-meeting-registrants", - version: "0.2.0", + version: "0.2.2", type: "action", props: { zoomAdmin, diff --git a/components/zoom_admin/actions/list-meetings/list-meetings.mjs b/components/zoom_admin/actions/list-meetings/list-meetings.mjs index 13ba8b89c33d4..2972359e6801d 100644 --- a/components/zoom_admin/actions/list-meetings/list-meetings.mjs +++ b/components/zoom_admin/actions/list-meetings/list-meetings.mjs @@ -1,12 +1,12 @@ -import zoomAdmin from "../../zoom_admin.app.mjs"; -import consts from "../../consts.mjs"; import { paginate } from "../../common/pagination.mjs"; +import consts from "../../consts.mjs"; +import zoomAdmin from "../../zoom_admin.app.mjs"; export default { name: "List meetings", description: "List all meetings. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetings)", key: "zoom_admin-list-meetings", - version: "0.2.0", + version: "0.2.2", type: "action", props: { zoomAdmin, diff --git a/components/zoom_admin/actions/list-webinar-participants/list-webinar-participants.mjs b/components/zoom_admin/actions/list-webinar-participants/list-webinar-participants.mjs index bebac057fb874..60a7db312ed98 100644 --- a/components/zoom_admin/actions/list-webinar-participants/list-webinar-participants.mjs +++ b/components/zoom_admin/actions/list-webinar-participants/list-webinar-participants.mjs @@ -5,7 +5,7 @@ export default { name: "List Webinar Participants", description: "Use this API to list all the participants who attended a webinar hosted in the past. [See the documentation](https://developers.zoom.us/docs/api/rest/reference/zoom-api/methods/#operation/listWebinarParticipants)", key: "zoom_admin-list-webinar-participants", - version: "0.2.0", + version: "0.2.2", type: "action", props: { zoomAdmin, diff --git a/components/zoom_admin/actions/list-webinar-registrants/list-webinar-registrants.mjs b/components/zoom_admin/actions/list-webinar-registrants/list-webinar-registrants.mjs index bbb12822ddbaf..902414a3d1b09 100644 --- a/components/zoom_admin/actions/list-webinar-registrants/list-webinar-registrants.mjs +++ b/components/zoom_admin/actions/list-webinar-registrants/list-webinar-registrants.mjs @@ -1,12 +1,12 @@ -import zoomAdmin from "../../zoom_admin.app.mjs"; -import consts from "../../consts.mjs"; import { paginate } from "../../common/pagination.mjs"; +import consts from "../../consts.mjs"; +import zoomAdmin from "../../zoom_admin.app.mjs"; export default { name: "List webinar registrants", description: "List all users that have registered for a webinar. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/webinars/webinarregistrants)", key: "zoom_admin-list-webinar-registrants", - version: "0.2.0", + version: "0.2.3", type: "action", props: { zoomAdmin, diff --git a/components/zoom_admin/actions/list-webinars/list-webinars.mjs b/components/zoom_admin/actions/list-webinars/list-webinars.mjs index ca4e03235eb52..463a310f3f7ef 100644 --- a/components/zoom_admin/actions/list-webinars/list-webinars.mjs +++ b/components/zoom_admin/actions/list-webinars/list-webinars.mjs @@ -5,7 +5,7 @@ export default { name: "List Webinars", description: "List all webinars for a user. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/webinars/webinars)", key: "zoom_admin-list-webinars", - version: "0.2.0", + version: "0.2.2", type: "action", props: { zoomAdmin, diff --git a/components/zoom_admin/actions/update-meeting/update-meeting.mjs b/components/zoom_admin/actions/update-meeting/update-meeting.mjs index e8b76493451ed..c65df35193fd4 100644 --- a/components/zoom_admin/actions/update-meeting/update-meeting.mjs +++ b/components/zoom_admin/actions/update-meeting/update-meeting.mjs @@ -1,8 +1,8 @@ -import zoomAdmin from "../../zoom_admin.app.mjs"; -import tzs from "../../zoom_tzs.mjs"; +import { axios } from "@pipedream/platform"; import get from "lodash/get.js"; import consts from "../../consts.mjs"; -import { axios } from "@pipedream/platform"; +import zoomAdmin from "../../zoom_admin.app.mjs"; +import tzs from "../../zoom_tzs.mjs"; const { MEETING_TYPE_OPTIONS } = consts; @@ -10,7 +10,7 @@ export default { name: "Update a meeting", description: "Update the details of a meeting. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingupdate)", key: "zoom_admin-update-meeting", - version: "0.1.3", + version: "0.1.5", type: "action", props: { zoomAdmin, diff --git a/components/zoom_admin/actions/update-webinar-registrant-status/update-webinar-registrant-status.mjs b/components/zoom_admin/actions/update-webinar-registrant-status/update-webinar-registrant-status.mjs index 03ec5a4f1c155..2f961620e5c67 100644 --- a/components/zoom_admin/actions/update-webinar-registrant-status/update-webinar-registrant-status.mjs +++ b/components/zoom_admin/actions/update-webinar-registrant-status/update-webinar-registrant-status.mjs @@ -1,14 +1,14 @@ -import zoomAdmin from "../../zoom_admin.app.mjs"; +import { axios } from "@pipedream/platform"; import get from "lodash/get.js"; import isObject from "lodash/isObject.js"; import consts from "../../consts.mjs"; -import { axios } from "@pipedream/platform"; +import zoomAdmin from "../../zoom_admin.app.mjs"; export default { name: "Update Webinar Registrant Status", description: "Update registrant status for a webinar. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingregistrantstatus)", key: "zoom_admin-update-webinar-registrant-status", - version: "0.1.3", + version: "0.1.5", type: "action", props: { zoomAdmin, diff --git a/components/zoom_admin/actions/update-webinar/update-webinar.mjs b/components/zoom_admin/actions/update-webinar/update-webinar.mjs index 787f4a643c139..a81e71ddec7ec 100644 --- a/components/zoom_admin/actions/update-webinar/update-webinar.mjs +++ b/components/zoom_admin/actions/update-webinar/update-webinar.mjs @@ -1,8 +1,8 @@ -import zoomAdmin from "../../zoom_admin.app.mjs"; -import tzs from "../../zoom_tzs.mjs"; +import { axios } from "@pipedream/platform"; import get from "lodash/get.js"; import consts from "../../consts.mjs"; -import { axios } from "@pipedream/platform"; +import zoomAdmin from "../../zoom_admin.app.mjs"; +import tzs from "../../zoom_tzs.mjs"; const { RECURRENCE_TYPE_OPTIONS, @@ -13,7 +13,7 @@ export default { name: "Update Webinar", description: "Update the details of a webinar. [See the docs here](https://marketplace.zoom.us/docs/api-reference/zoom-api/webinars/webinarupdate)", key: "zoom_admin-update-webinar", - version: "0.1.3", + version: "0.1.5", type: "action", props: { zoomAdmin, diff --git a/components/zoom_admin/package.json b/components/zoom_admin/package.json index 20a3b5ca659a7..6e96fe1a2d039 100644 --- a/components/zoom_admin/package.json +++ b/components/zoom_admin/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/zoom_admin", - "version": "0.6.0", + "version": "0.7.2", "description": "Pipedream Zoom_admin Components", "main": "zoom_admin.app.js", "keywords": [ diff --git a/components/zoom_admin/sources/account-created/account-created.mjs b/components/zoom_admin/sources/account-created/account-created.mjs index faab46264081e..bc7cd9ac06551 100644 --- a/components/zoom_admin/sources/account-created/account-created.mjs +++ b/components/zoom_admin/sources/account-created/account-created.mjs @@ -5,7 +5,7 @@ export default { type: "source", name: "Account Created", description: "Emits an event each time a sub-account is created in your master account", - version: "0.1.3", + version: "0.1.5", dedupe: "unique", // Dedupe based on account ID props: { zoomAdmin, diff --git a/components/zoom_admin/sources/account-settings-updated/account-settings-updated.mjs b/components/zoom_admin/sources/account-settings-updated/account-settings-updated.mjs index a3a7f4a335e6c..b6098adf66de2 100644 --- a/components/zoom_admin/sources/account-settings-updated/account-settings-updated.mjs +++ b/components/zoom_admin/sources/account-settings-updated/account-settings-updated.mjs @@ -5,7 +5,7 @@ export default { type: "source", name: "Account Settings Updated", description: "Emits an event each time your master account or sub-account settings are updated", - version: "0.1.3", + version: "0.1.5", props: { zoomAdmin, zoomApphook: { diff --git a/components/zoom_admin/sources/account-updated/account-updated.mjs b/components/zoom_admin/sources/account-updated/account-updated.mjs index 32284afce9f00..54369b05d9e6d 100644 --- a/components/zoom_admin/sources/account-updated/account-updated.mjs +++ b/components/zoom_admin/sources/account-updated/account-updated.mjs @@ -5,7 +5,7 @@ export default { type: "source", name: "Account Updated", description: "Emits an event each time your master account or sub-account profile is updated", - version: "0.1.3", + version: "0.1.5", props: { zoomAdmin, zoomApphook: { diff --git a/components/zoom_admin/sources/custom-events/custom-events.mjs b/components/zoom_admin/sources/custom-events/custom-events.mjs index 1a468f9fd6d34..d7f47b27a3ebe 100644 --- a/components/zoom_admin/sources/custom-events/custom-events.mjs +++ b/components/zoom_admin/sources/custom-events/custom-events.mjs @@ -5,7 +5,7 @@ export default { type: "source", name: "Custom Events", description: "Listen for any events tied to your Zoom account", - version: "0.1.3", + version: "0.1.5", props: { zoomAdmin, eventNameOptions: { diff --git a/components/zoom_admin/sources/meeting-created/meeting-created.mjs b/components/zoom_admin/sources/meeting-created/meeting-created.mjs index 6dc6eca7f8ce6..6ba127da2f6d0 100644 --- a/components/zoom_admin/sources/meeting-created/meeting-created.mjs +++ b/components/zoom_admin/sources/meeting-created/meeting-created.mjs @@ -5,7 +5,7 @@ export default { type: "source", name: "Meeting Created", description: "Emits an event each time a meeting is created in your Zoom account", - version: "0.1.3", + version: "0.1.5", dedupe: "unique", // Dedupe based on meeting ID props: { zoomAdmin, diff --git a/components/zoom_admin/sources/meeting-deleted/meeting-deleted.mjs b/components/zoom_admin/sources/meeting-deleted/meeting-deleted.mjs index 6d8e9d8f1c789..643002f5636da 100644 --- a/components/zoom_admin/sources/meeting-deleted/meeting-deleted.mjs +++ b/components/zoom_admin/sources/meeting-deleted/meeting-deleted.mjs @@ -5,7 +5,7 @@ export default { type: "source", name: "Meeting Deleted", description: "Emits an event each time a meeting is deleted in your Zoom account", - version: "0.1.3", + version: "0.1.5", dedupe: "unique", // Dedupe based on meeting ID props: { zoomAdmin, diff --git a/components/zoom_admin/sources/meeting-ended/meeting-ended.mjs b/components/zoom_admin/sources/meeting-ended/meeting-ended.mjs index 6780d2a443051..3d9908a73bde9 100644 --- a/components/zoom_admin/sources/meeting-ended/meeting-ended.mjs +++ b/components/zoom_admin/sources/meeting-ended/meeting-ended.mjs @@ -5,7 +5,7 @@ export default { type: "source", name: "Meeting Ended", description: "Emits an event each time a meeting ends in your Zoom account", - version: "0.1.3", + version: "0.1.5", dedupe: "unique", // Dedupe based on meeting ID props: { zoomAdmin, diff --git a/components/zoom_admin/sources/meeting-started/meeting-started.mjs b/components/zoom_admin/sources/meeting-started/meeting-started.mjs index 22d8174ae1dcc..cc3cd366089d7 100644 --- a/components/zoom_admin/sources/meeting-started/meeting-started.mjs +++ b/components/zoom_admin/sources/meeting-started/meeting-started.mjs @@ -1,12 +1,12 @@ -import zoomAdmin from "../../zoom_admin.app.mjs"; import { v4 as uuidv4 } from "uuid"; +import zoomAdmin from "../../zoom_admin.app.mjs"; export default { key: "zoom_admin-meeting-started", type: "source", name: "Meeting Started", description: "Emits an event each time a meeting starts in your Zoom account", - version: "0.1.3", + version: "0.1.5", dedupe: "unique", // Dedupe based on meeting ID props: { zoomAdmin, diff --git a/components/zoom_admin/sources/meeting-updated/meeting-updated.mjs b/components/zoom_admin/sources/meeting-updated/meeting-updated.mjs index cf5d3c42b7990..bd861abc2559f 100644 --- a/components/zoom_admin/sources/meeting-updated/meeting-updated.mjs +++ b/components/zoom_admin/sources/meeting-updated/meeting-updated.mjs @@ -5,7 +5,7 @@ export default { type: "source", name: "Meeting Updated", description: "Emits an event each time a meeting is updated in your Zoom account", - version: "0.1.3", + version: "0.1.5", dedupe: "unique", // dedupe on the meeting ID + timestamp props: { zoomAdmin, diff --git a/components/zoom_admin/sources/recording-completed/recording-completed.mjs b/components/zoom_admin/sources/recording-completed/recording-completed.mjs index 9a19891b1efa7..10ecd3ecb1d11 100644 --- a/components/zoom_admin/sources/recording-completed/recording-completed.mjs +++ b/components/zoom_admin/sources/recording-completed/recording-completed.mjs @@ -6,7 +6,7 @@ export default { type: "source", name: "Recording Completed", description: "Emits an event each time a recording is ready for viewing in your Zoom account", - version: "0.1.3", + version: "0.1.6", dedupe: "unique", // Dedupe events based on the ID of the recording file props: { zoomAdmin, diff --git a/components/zoom_admin/sources/user-activated/user-activated.mjs b/components/zoom_admin/sources/user-activated/user-activated.mjs index 265fdad7bef9e..12d0ba01064ad 100644 --- a/components/zoom_admin/sources/user-activated/user-activated.mjs +++ b/components/zoom_admin/sources/user-activated/user-activated.mjs @@ -3,9 +3,9 @@ import zoomAdmin from "../../zoom_admin.app.mjs"; export default { key: "zoom_admin-user-activated", type: "source", - name: "User Activated", - description: "Emits an event each time a user is activated in your Zoom account", - version: "0.1.3", + name: "New User Activated", + description: "Emit new event each time a user is activated in your Zoom account", + version: "0.1.5", dedupe: "unique", // Dedupe based on user ID props: { zoomAdmin, diff --git a/components/zoom_admin/sources/user-created/user-created.mjs b/components/zoom_admin/sources/user-created/user-created.mjs index 40dc3c3823b5f..fff60664895fe 100644 --- a/components/zoom_admin/sources/user-created/user-created.mjs +++ b/components/zoom_admin/sources/user-created/user-created.mjs @@ -5,7 +5,7 @@ export default { type: "source", name: "User Created", description: "Emits an event each time a user is created in your Zoom account", - version: "0.1.3", + version: "0.1.5", dedupe: "unique", // Dedupe based on user ID props: { zoomAdmin, diff --git a/components/zoom_admin/sources/user-deactivated/user-deactivated.mjs b/components/zoom_admin/sources/user-deactivated/user-deactivated.mjs index 8aa56bcc0d0fa..8eaa720b3343c 100644 --- a/components/zoom_admin/sources/user-deactivated/user-deactivated.mjs +++ b/components/zoom_admin/sources/user-deactivated/user-deactivated.mjs @@ -5,7 +5,7 @@ export default { type: "source", name: "User Deactivated", description: "Emits an event each time a user is deactivated in your Zoom account", - version: "0.1.3", + version: "0.1.5", dedupe: "unique", // Dedupe based on user ID props: { zoomAdmin, diff --git a/components/zoom_admin/sources/user-deleted/user-deleted.mjs b/components/zoom_admin/sources/user-deleted/user-deleted.mjs index 66392b97e3140..01902ff6bfd8c 100644 --- a/components/zoom_admin/sources/user-deleted/user-deleted.mjs +++ b/components/zoom_admin/sources/user-deleted/user-deleted.mjs @@ -5,7 +5,7 @@ export default { type: "source", name: "User Deleted", description: "Emits an event each time a user is deleted in your Zoom account", - version: "0.1.3", + version: "0.1.5", dedupe: "unique", // Dedupe based on user ID props: { zoomAdmin, diff --git a/components/zoom_admin/sources/user-invitation-accepted/user-invitation-accepted.mjs b/components/zoom_admin/sources/user-invitation-accepted/user-invitation-accepted.mjs index d3fab4ff076e2..920601f892059 100644 --- a/components/zoom_admin/sources/user-invitation-accepted/user-invitation-accepted.mjs +++ b/components/zoom_admin/sources/user-invitation-accepted/user-invitation-accepted.mjs @@ -5,7 +5,7 @@ export default { type: "source", name: "User Invitation Accepted", description: "Emits an event each time a user accepts an invite to your Zoom account", - version: "0.1.3", + version: "0.1.5", dedupe: "unique", // Dedupe based on user ID props: { zoomAdmin, diff --git a/components/zoom_admin/sources/user-updated/user-updated.mjs b/components/zoom_admin/sources/user-updated/user-updated.mjs index d13e3212df10c..1560baa4d66eb 100644 --- a/components/zoom_admin/sources/user-updated/user-updated.mjs +++ b/components/zoom_admin/sources/user-updated/user-updated.mjs @@ -5,7 +5,7 @@ export default { type: "source", name: "User Updated", description: "Emits an event each time a user's settings are updated in your Zoom account", - version: "0.1.3", + version: "0.1.5", dedupe: "unique", // Dedupe based on user ID props: { zoomAdmin, diff --git a/components/zoom_admin/sources/webinar-changes-to-panelists/webinar-changes-to-panelists.mjs b/components/zoom_admin/sources/webinar-changes-to-panelists/webinar-changes-to-panelists.mjs index cd1490470951c..ce071b7997756 100644 --- a/components/zoom_admin/sources/webinar-changes-to-panelists/webinar-changes-to-panelists.mjs +++ b/components/zoom_admin/sources/webinar-changes-to-panelists/webinar-changes-to-panelists.mjs @@ -1,14 +1,14 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; import crypto from "crypto"; import difference from "lodash/difference.js"; -import zoomAdmin from "../../zoom_admin.app.mjs"; import { sanitizedArray } from "../../utils.mjs"; -import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import zoomAdmin from "../../zoom_admin.app.mjs"; export default { type: "source", name: "Changes to Webinar Panelists", key: "zoom_admin-webinar-changes-to-panelists", - version: "0.1.3", + version: "0.1.5", description: "Emit new event every time a panelist is added or removed from a webinar, or any time their details change", dedupe: "unique", props: { diff --git a/components/zoom_admin/sources/webinar-created/webinar-created.mjs b/components/zoom_admin/sources/webinar-created/webinar-created.mjs index df68f58760e54..8d803dae4e763 100644 --- a/components/zoom_admin/sources/webinar-created/webinar-created.mjs +++ b/components/zoom_admin/sources/webinar-created/webinar-created.mjs @@ -6,7 +6,7 @@ export default { name: "Webinar Created", description: "Emits an event each time a webinar is created in your Zoom account", - version: "0.1.3", + version: "0.1.5", dedupe: "unique", // Dedupe based on webinar ID props: { zoomAdmin, diff --git a/components/zoom_admin/sources/webinar-deleted/webinar-deleted.mjs b/components/zoom_admin/sources/webinar-deleted/webinar-deleted.mjs index 1e0eb163426f5..b4526bcbcb01f 100644 --- a/components/zoom_admin/sources/webinar-deleted/webinar-deleted.mjs +++ b/components/zoom_admin/sources/webinar-deleted/webinar-deleted.mjs @@ -5,7 +5,7 @@ export default { name: "Webinar Deleted", description: "Emits an event each time a webinar is deleted in your Zoom account", - version: "0.1.3", + version: "0.1.5", type: "source", dedupe: "unique", // Dedupe based on webinar ID props: { diff --git a/components/zoom_admin/sources/webinar-ended/webinar-ended.mjs b/components/zoom_admin/sources/webinar-ended/webinar-ended.mjs index ae0f0a07a0d9b..a983cf2195a0f 100644 --- a/components/zoom_admin/sources/webinar-ended/webinar-ended.mjs +++ b/components/zoom_admin/sources/webinar-ended/webinar-ended.mjs @@ -5,7 +5,7 @@ export default { type: "source", name: "Webinar Ended", description: "Emits an event each time a webinar ends in your Zoom account", - version: "0.1.3", + version: "0.1.5", dedupe: "unique", // Dedupe based on webinar ID props: { zoomAdmin, diff --git a/components/zoom_admin/sources/webinar-started/webinar-started.mjs b/components/zoom_admin/sources/webinar-started/webinar-started.mjs index f4eadfc28e239..a819c886c2226 100644 --- a/components/zoom_admin/sources/webinar-started/webinar-started.mjs +++ b/components/zoom_admin/sources/webinar-started/webinar-started.mjs @@ -5,7 +5,7 @@ export default { type: "source", name: "Webinar Started", description: "Emits an event each time a webinar starts in your Zoom account", - version: "0.1.3", + version: "0.1.7", dedupe: "unique", // Dedupe based on webinar ID props: { zoomAdmin, diff --git a/components/zoom_admin/sources/webinar-updated/webinar-updated.mjs b/components/zoom_admin/sources/webinar-updated/webinar-updated.mjs index 8498450f621a2..3873638a6ec8f 100644 --- a/components/zoom_admin/sources/webinar-updated/webinar-updated.mjs +++ b/components/zoom_admin/sources/webinar-updated/webinar-updated.mjs @@ -6,7 +6,7 @@ export default { name: "Webinar Updated", description: "Emits an event each time a webinar is updated in your Zoom account", - version: "0.1.3", + version: "0.1.5", dedupe: "unique", // Dedupe based on webinar ID props: { zoomAdmin, diff --git a/components/zoom_admin/zoom_admin.app.mjs b/components/zoom_admin/zoom_admin.app.mjs index 27692e448aaa8..6aef5aeac4c63 100644 --- a/components/zoom_admin/zoom_admin.app.mjs +++ b/components/zoom_admin/zoom_admin.app.mjs @@ -1,10 +1,10 @@ /* eslint-disable camelcase */ import { axios } from "@pipedream/platform"; +import flatten from "lodash/flatten.js"; import get from "lodash/get.js"; import sortBy from "lodash/sortBy.js"; -import flatten from "lodash/flatten.js"; -import zoomCountries from "./zoom_countries.mjs"; import consts from "./consts.mjs"; +import zoomCountries from "./zoom_countries.mjs"; export default { type: "app", @@ -285,6 +285,14 @@ export default { }); return data; }, + listAccountCallLogs(nextPageToken) { + return this._makeRequest({ + path: "/phone/call_history", + params: { + next_page_token: nextPageToken, + }, + }); + }, async listWebinarPanelists(webinarID, nextPageToken) { const { data } = await this._makeRequest({ path: `/webinars/${webinarID}/panelists`, diff --git a/docs-v2/components/ArcadeEmbed.tsx b/docs-v2/components/ArcadeEmbed.tsx new file mode 100644 index 0000000000000..2101fe0f54b10 --- /dev/null +++ b/docs-v2/components/ArcadeEmbed.tsx @@ -0,0 +1,34 @@ +import React from "react"; + +const ArcadeEmbed = ({ + src, title, +}) => { + const embedUrl = `${src}`; + + return ( +
+ +
+ ); +}; + +export default ArcadeEmbed; diff --git a/docs-v2/pages/_meta.json b/docs-v2/pages/_meta.json index 7d0382c4d2533..1a1e0a9f383a7 100644 --- a/docs-v2/pages/_meta.json +++ b/docs-v2/pages/_meta.json @@ -73,9 +73,6 @@ } } }, - "workspaces-and-credits-faq": { - "display": "children" - }, "abuse": { "display": "children" }, diff --git a/docs-v2/pages/apps/_meta.json b/docs-v2/pages/apps/_meta.json index 6f84a35764b44..28f67b78db4f9 100644 --- a/docs-v2/pages/apps/_meta.json +++ b/docs-v2/pages/apps/_meta.json @@ -1,4 +1,5 @@ { "index": "Integrated Apps", - "contributing": "Contributing" + "contributing": "Contributing", + "app-partners": "App Partners" } diff --git a/docs-v2/pages/apps/app-partners.mdx b/docs-v2/pages/apps/app-partners.mdx new file mode 100644 index 0000000000000..11382e1a08236 --- /dev/null +++ b/docs-v2/pages/apps/app-partners.mdx @@ -0,0 +1,23 @@ +# App Partners + +By integrating your app with Pipedream, your users will be able to connect your app with over {process.env.PUBLIC_APPS} supported apps on our platform. You gain access and exposure to a community of over 800,000 developers, and can spend more time building your product and less time navigating app integrations. + +## Benefits of Integrating With Pipedream + +- **End-to-End Development:** Pipedream will handle the entire development process of the integration, from managing authentication, setting up an official Pipedream OAuth client where applicable, and final QA. +- **Custom Triggers and Actions:** We will create up to three triggers and three actions, based on the methods available within your APIs. +- **Extensive Developer Exposure:** Your app will be accessible to a large and growing community of developers. +- **Dedicated App Page:** Control and customize your dedicated app page to market your app to potential users, and share example use cases or workflow templates that can be built to help users get started. + +## Integration Process + +Integrating with Pipedream is a straightforward process: + +1. Pipedream requires an account for testing and development along with API documentation in order to get started. We will build the initial integration, and run test requests to test the connection. +2. Our team will build no-code triggers and actions that make the most sense from a workflow development perspective - if you have specific triggers and actions in mind to start with, we’ll start there. +3. Our QA team will thoroughly test the no-code components to ensure that they work as intended, and then we will release and announce the completed integration in our public Slack with over 5,000 active members. + +## Get Started + +Are you ready to integrate with Pipedream? [Contact our integrations team](https://pipedream.com/support) today to get started. + diff --git a/docs-v2/pages/apps/contributing.mdx b/docs-v2/pages/apps/contributing.mdx index 10e003ff07c6d..18ee19ef69042 100644 --- a/docs-v2/pages/apps/contributing.mdx +++ b/docs-v2/pages/apps/contributing.mdx @@ -78,7 +78,7 @@ and [actions](https://github.com/PipedreamHQ/pipedream/issues?q=is%3Aissue+is%3A ## Reference Components -The following components arfor developing sources and +The following components are references for developing sources and actions for Pipedream's registry. ### Reference Sources diff --git a/docs-v2/pages/apps/index.mdx b/docs-v2/pages/apps/index.mdx index cb09d27ecc91b..f06071af0caba 100644 --- a/docs-v2/pages/apps/index.mdx +++ b/docs-v2/pages/apps/index.mdx @@ -73,7 +73,7 @@ The vast majority of integrated apps on Pipedream are free to use in your workfl Missing an integration? -If we don't have an integration for an app that you'd like to see, please [request let us know](https://pipedream.com/support) or [contribute it to the source available Pipedream registry](/apps/contributing/). +If we don't have an integration for an app that you'd like to see, please [let us know](https://pipedream.com/support) or [contribute it to the source available Pipedream registry](/apps/contributing/). **Check out the full list of integrated apps [here](https://pipedream.com/apps).** diff --git a/docs-v2/pages/cli/install.mdx b/docs-v2/pages/cli/install.mdx index 7a7a438dff1f4..f2d862d172e4f 100644 --- a/docs-v2/pages/cli/install.mdx +++ b/docs-v2/pages/cli/install.mdx @@ -10,6 +10,7 @@ import VideoPlayer from '@/components/VideoPlayer' ### Homebrew ```bash +brew tap pipedreamhq/pd-cli brew install pipedreamhq/pd-cli/pipedream ``` diff --git a/docs-v2/pages/components/guidelines.mdx b/docs-v2/pages/components/guidelines.mdx index 8cbeaa0019c2b..202be64eabe16 100644 --- a/docs-v2/pages/components/guidelines.mdx +++ b/docs-v2/pages/components/guidelines.mdx @@ -202,6 +202,8 @@ Here's an example `README.md` structure: # Overview +# Example Use Cases + # Getting Started # Troubleshooting @@ -212,7 +214,7 @@ These sections will appear within the correponding app, source and action page, Here's an example of an [app `README.md` within the `discord` component on the Pipedream registry](https://github.com/PipedreamHQ/pipedream/blob/master/components/discord/README.md). That same content is rendered within the [Pipedream integration page for the Discord app](https://pipedream.com/apps/discord). -You can add additional subheadings to each of the top level `Overview`, `Getting Started` and `Troubleshooting` headings: +You can add additional subheadings to each of the top level `Overview`, `Example Use Cases`, `Getting Started` and `Troubleshooting` headings: ```markdown # Overview @@ -221,6 +223,12 @@ You can add additional subheadings to each of the top level `Overview`, `Getting Perhaps there are some limitations about the API that users should know about. +# Example Use Cases + +1. Sync data in real time +2. Automate tedious actions +3. Introduce A.I. into the workflow + # Getting Started ## Generating an API key diff --git a/docs-v2/pages/connected-accounts/_meta.json b/docs-v2/pages/connected-accounts/_meta.json index dec8bd2088c04..447042c0388bb 100644 --- a/docs-v2/pages/connected-accounts/_meta.json +++ b/docs-v2/pages/connected-accounts/_meta.json @@ -1,3 +1,4 @@ { - "index": "Connected Accounts" + "index": "Connected Accounts", + "oauth-clients": "OAuth Clients" } diff --git a/docs-v2/pages/connected-accounts/oauth-clients.mdx b/docs-v2/pages/connected-accounts/oauth-clients.mdx new file mode 100644 index 0000000000000..46a2905ab8b3c --- /dev/null +++ b/docs-v2/pages/connected-accounts/oauth-clients.mdx @@ -0,0 +1,51 @@ +import Callout from '@/components/Callout' +import ArcadeEmbed from '@/components/ArcadeEmbed' +import { Steps } from 'nextra/components' + +# OAuth Clients +By default, most OAuth apps on Pipedream use Pipedream's official OAuth client. That means when you connect an account, you'll see Pipedream's logo and name in the OAuth authorization screen, along with a set of requested permissions (scopes). +- Pipedream apps solve for a broad range of use cases, which means the scopes requested by Pipedream's official OAuth client may be more than what you need +- To define the exact scope of access you'd like to grant, you can configure a custom OAuth client for most OAuth apps in Pipedream + +## Configuring Custom OAuth Clients + + + + +### Navigate to the OAuth Clients Page +Open the [OAuth Clients page in your Pipedream account](https://pipedream.com/@/accounts/oauth-clients) and click "New OAuth Client". + +### Select the app +Find the app you need. If you can't find what you're looking for, feel free to [submit an integration request](https://pipedream.com/support). + +### Enter the required fields +- **Name:** Give the OAuth client a name so it's easy to identify +- **Description:** Optionally add a brief description for additional context +- **Client ID and Secret**: Paste these values from the app's settings that you're configuring (the client secret is sensitive – we'll encrypt and hide it from the UI) +- **Redirect URI:** Copy this Redirect URI and paste it into the app's settings +- **Scopes:** We'll list the scopes from the official Pipedream OAuth client by default. Add or remove scopes as needed, based on your use case. + +Click "Save" + + +Make sure to include all the scopes you need based on your use case. You can modify the scopes later (you'll need to reconnect your account for changes to take effect). Refer to the app's API documentation for information on what scopes you'll need. + + + + +## Connecting your account with OAuth clients +Once you've created the OAuth client, you can select "Connect account" from the menu on the right: + + + + +Now you're ready to use the connected account in any workflow, just like any other account in Pipedream: + + + \ No newline at end of file diff --git a/docs-v2/pages/data-stores.mdx b/docs-v2/pages/data-stores.mdx index 9a8d98ca1e753..002a15dfcab8c 100644 --- a/docs-v2/pages/data-stores.mdx +++ b/docs-v2/pages/data-stores.mdx @@ -149,6 +149,11 @@ You'll find your workspace's limits in the **Data Stores** section of usage dash />
+## Atomic operations + +Data store operations are not atomic or transactional, which can lead to race conditions. +To ensure atomic operations, be sure to limit access to a data store key to a [single workflow with a single worker](/workflows/concurrency-and-throttling) or use a service that supports atomic operations from among our [integrated apps](https://pipedream.com/apps). + ## Supported data types Data stores can hold any JSON-serializable data within the storage limits. This includes data types including: diff --git a/docs-v2/pages/index.mdx b/docs-v2/pages/index.mdx index a0733ae08af49..b1c5a73ad7c95 100644 --- a/docs-v2/pages/index.mdx +++ b/docs-v2/pages/index.mdx @@ -13,7 +13,7 @@ The Pipedream platform includes: Watch a demo or review our [quickstart guide](/quickstart/): diff --git a/docs-v2/pages/privacy-and-security/_meta.json b/docs-v2/pages/privacy-and-security/_meta.json index 85be1e4e815af..88848ee49f5a6 100644 --- a/docs-v2/pages/privacy-and-security/_meta.json +++ b/docs-v2/pages/privacy-and-security/_meta.json @@ -1,5 +1,6 @@ { "index": "Privacy and Security", "best-practices": "Security best-practices", + "hipaa": "HIPAA compliance", "pgp-key": "PGP key" } diff --git a/docs-v2/pages/privacy-and-security/best-practices.mdx b/docs-v2/pages/privacy-and-security/best-practices.mdx index 5e5dd02a183cf..77846fa98647c 100644 --- a/docs-v2/pages/privacy-and-security/best-practices.mdx +++ b/docs-v2/pages/privacy-and-security/best-practices.mdx @@ -2,8 +2,6 @@ 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. - - ## 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: diff --git a/docs-v2/pages/privacy-and-security/hipaa.mdx b/docs-v2/pages/privacy-and-security/hipaa.mdx new file mode 100644 index 0000000000000..33daf4caa39d1 --- /dev/null +++ b/docs-v2/pages/privacy-and-security/hipaa.mdx @@ -0,0 +1,38 @@ +# HIPAA compliance + +Pipedream can [sign Business Associate Addendums (BAAs)](#signing-a-business-associate-addendum) for Enterprise customers intending to pass PHI to Pipedream. We can also provide a third-party SOC 2 report detailing our HIPAA-related controls. + +## HIPAA-eligible services + +- [Workflows](/workflows) +- [Event sources](/sources) +- [Data stores](/data-stores) +- [Destinations](/destinations) + +### Ineligible services + +Any service not listed in the [HIPAA-eligible services](#hipaa-eligible-services) section is not eligible for use with PHI under HIPAA. Please reach out to [Pipedream support](https://pipedream.com/support) if you have questions about a specific service. + +The following services are explicitly not eligible for use with PHI under HIPAA. + +- [v1 workflows](/migrate-from-v1) +- [File stores](/file-stores) + +## Your obligations as a customer + +If you are a covered entity or business associate under HIPAA, you must ensure that [you have a BAA in place with Pipedream](#signing-a-business-associate-addendum) before passing PHI to Pipedream. + +You must also ensure that you are using Pipedream in a manner that complies with HIPAA. This includes: + +- You may only use [HIPAA-eligible services](#hipaa-eligible-services) to process or store PHI +- You may not include PHI in Pipedream resource names, like the names of projects or workflows + +## Signing a Business Associate Addendum + +Pipedream is considered a Business Associate under HIPAA regulations. If you are a Covered Entity or Business Associate under HIPAA, you must have a Business Associate Agreement (BAA) in place with Pipedream before passing PHI to Pipedream. This agreement is an addendum to our standard terms, and outlines your obligations as a customer and Pipedream's obligations as a Business Associate under HIPAA. + +Please request a BAA by visiting [https://pipedream.com/support](https://pipedream.com/support). + +## Requesting information on HIPAA controls + +Please request compliance reports from [https://pipedream.com/support](https://pipedream.com/support). Pipedream can provide a SOC 2 Type II report covering Security controls, and a SOC 2 Type I report for Confidentiality and Availability. In 2025, Pipedream plans to include Confidentiality and Availability controls in our standard Type II audit. \ No newline at end of file diff --git a/docs-v2/pages/privacy-and-security/index.mdx b/docs-v2/pages/privacy-and-security/index.mdx index e54ed92eb7c54..b0a3736f9cd9c 100644 --- a/docs-v2/pages/privacy-and-security/index.mdx +++ b/docs-v2/pages/privacy-and-security/index.mdx @@ -46,6 +46,10 @@ When you [delete your account](/user-settings/#delete-account), Pipedream delete If you need to delete data on behalf of one of your users, you can delete the event data yourself in your workflow or event source (for example, by deleting the events, or by removing the data from data stores). Your customer event data is automatically deleted from Pipedream subprocessors. +### HIPAA + +Pipedream can sign Business Associate Addendum (BAAs) for customers intending to pass PHI to Pipedream. We can also provide a third-party SOC 2 report detailing our HIPAA-related controls. See our [dedicated HIPAA docs](/privacy-and-security/hipaa) for more details. + ## Hosting Details Pipedream is hosted on the [Amazon Web Services](https://aws.amazon.com/) (AWS) platform in the `us-east-1` region. The physical hardware powering Pipedream, and the data stored by our platform, is hosted in data centers controlled and secured by AWS. You can read more about AWS’s security practices and compliance certifications [here](https://aws.amazon.com/security/). diff --git a/docs-v2/pages/workspaces-and-credits-faq/_meta.json b/docs-v2/pages/workspaces-and-credits-faq/_meta.json deleted file mode 100644 index 5e6a93c3a56e9..0000000000000 --- a/docs-v2/pages/workspaces-and-credits-faq/_meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "index": { - "display": "hidden" - } -} diff --git a/docs-v2/pages/workspaces-and-credits-faq/index.mdx b/docs-v2/pages/workspaces-and-credits-faq/index.mdx deleted file mode 100644 index 1b7aa89f16047..0000000000000 --- a/docs-v2/pages/workspaces-and-credits-faq/index.mdx +++ /dev/null @@ -1,55 +0,0 @@ -# Workspaces, Credits, and Other New Features for Pipedream Orgs - -We're excited to announce a new set of foundational changes and features! In short — orgs are now [workspaces](/workspaces), invocations are now [credits](/pricing/#credits), and you have access to new features: [free source invocations](/pricing/#source-credit-usage), unlimited team members, and more. - - - -## Orgs are now workspaces - -We've renamed organizations to [workspaces](/workspaces), which is more consistent with the language other products use. Anywhere you used to see references to "organizations", you'll now see "workspaces". - -## Invocations are now credits - -Pipedream previously charged for invocations (number of workflow executions), but not on compute time (how long your executions run). We've combined these into a single metric: [credits](/pricing/#credits). Your workspace now has a default credit limit of 50,000 credits per month, an increase of 30,000 credits a month from the Team plan. - -## Source invocations are now free - -Previously, workflows often cost two invocations: one to run your source, and another for the workflow. Now, [most source executions are free](/pricing/#source-credit-usage), and you'll only be charged credits for workflow executions. - -## The Team plan is now the Advanced plan - -As a part of this rollout, we've released new paid plans, and are automatically upgrading your org to the Advanced Plan. We're grateful to have you as an early customer, and **we're honoring your old price for the next 12 months. You should see no impact to your monthly bill with this change**. If you do, please reach out to `support@pipedream.com`. In 12 months, your workspace will move to the standard Advanced plan pricing: currently $149 / month for 50,000 credits, $0.0004 per extra credit. - -### What's on the Advanced plan? - -You have access to everything on the Team plan, including: - -- 50,000 credits per month, 30,000 more than the Team plan -- Support for annual plans at 33% off the monthly price — you can change to an annual plan after we convert your org to a workspace -- 5 team members by default, $50 / user / month for extra users ($30 for annual plans) -- [Auto-retry](/workflows/settings/#auto-retry-errors) on workflow errors -- Slack error notifications - -A GitHub integration, folders to organize workflows, and other features are coming soon. - -## The Pro plan is now the Basic plan - -As part of this rollout, we've released new paid plans and are automatically upgrading your existing Pro plan to the Basic Plan. We're grateful to have you as an early customer, and **we're honoring your old price for the next 12 months. You should see no impact on your monthly bill with this change**. If you do, please reach out to `support@pipedream.com`. In 12 months, your workspace will move to the standard Basic plan pricing: currently $29 / month for 10,000 credits, $0.0008 per extra credit. - -### What's in the Basic plan? - -You have access to everything you had in the Pro plan. A GitHub integration, folders to organize workflows, and other features are coming soon. - -### Why the plan / pricing changes? - -Overall, we try to price our products far below the value we are providing, and try to do right by the existing customers who helped us get to where we are. - -We developed our old pricing when we first launched organizations, and had limited functionality. Today, we offer: - -- Better collaboration in workspaces -- Thousands of integrated apps, triggers, and actions -- Support for Node.js, Python, Go, and Bash -- Data stores, concurrency and execution controls, and automatic retry for failed executions. -- We'll be launching a GitHub integration, extended event history, warm workers, looping and branching, and much more this year. - -Our goal is to build a product that our customers feel is wildly underpriced for the value we are providing, and we are working every day to make that happen. If there are areas in which our product isn’t delivering, please reach out anytime at `support@pipedream.com`. diff --git a/docs-v2/vercel.json b/docs-v2/vercel.json index 8755b77120529..92d65d5e46360 100644 --- a/docs-v2/vercel.json +++ b/docs-v2/vercel.json @@ -261,6 +261,10 @@ { "source": "/docs/workflows/built-in-functions", "destination": "/docs/workflows/flow-control" + }, + { + "source": "/docs/workspaces-and-credits-faq", + "destination": "https://pipedream.com/pricing" } ] } diff --git a/packages/component_code_gen/poetry.lock b/packages/component_code_gen/poetry.lock index 26ac5420847c6..43b4bd7095305 100644 --- a/packages/component_code_gen/poetry.lock +++ b/packages/component_code_gen/poetry.lock @@ -1717,13 +1717,13 @@ files = [ [[package]] name = "tqdm" -version = "4.66.1" +version = "4.66.3" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" files = [ - {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"}, - {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"}, + {file = "tqdm-4.66.3-py3-none-any.whl", hash = "sha256:4f41d54107ff9a223dca80b53efe4fb654c67efaba7f47bada3ee9d50e05bd53"}, + {file = "tqdm-4.66.3.tar.gz", hash = "sha256:23097a41eba115ba99ecae40d06444c15d1c0c698d527a01c6c8bd1c5d0647e5"}, ] [package.dependencies] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43d164ccca46f..fc9e9ffa2c5a2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -528,6 +528,9 @@ importers: dependencies: '@pipedream/platform': 1.6.0 + components/appwrite: + specifiers: {} + components/arcgis_online: specifiers: {} @@ -840,7 +843,10 @@ importers: specifiers: {} components/bilflo: - specifiers: {} + specifiers: + '@pipedream/platform': ^1.6.5 + dependencies: + '@pipedream/platform': 1.6.5 components/bilionis: specifiers: @@ -930,6 +936,14 @@ importers: dependencies: '@pipedream/platform': 1.6.0 + components/boloforms: + specifiers: + '@pipedream/platform': ^1.6.5 + uuid: ^9.0.1 + dependencies: + '@pipedream/platform': 1.6.5 + uuid: 9.0.1 + components/bolt_iot: specifiers: {} @@ -1299,7 +1313,10 @@ importers: '@pipedream/platform': 1.6.0 components/chatfly: - specifiers: {} + specifiers: + '@pipedream/platform': ^1.6.5 + dependencies: + '@pipedream/platform': 1.6.5 components/chatfuel_dashboard_api_: specifiers: {} @@ -1340,6 +1357,14 @@ importers: dependencies: '@pipedream/platform': 1.6.2 + components/cincopa: + specifiers: + '@pipedream/platform': ^1.6.5 + uuid: ^8.3.2 + dependencies: + '@pipedream/platform': 1.6.5 + uuid: 8.3.2 + components/circl_hash_lookup: specifiers: {} @@ -1798,7 +1823,10 @@ importers: specifiers: {} components/cradl_ai: - specifiers: {} + specifiers: + '@pipedream/platform': ^1.6.5 + dependencies: + '@pipedream/platform': 1.6.5 components/craftboxx: specifiers: @@ -2924,6 +2952,12 @@ importers: components/fluxguard: specifiers: {} + components/fly_io: + specifiers: + '@pipedream/platform': 1.6.5 + dependencies: + '@pipedream/platform': 1.6.5 + components/focuster: specifiers: {} @@ -3020,7 +3054,10 @@ importers: specifiers: {} components/fractel: - specifiers: {} + specifiers: + '@pipedream/platform': ^1.6.5 + dependencies: + '@pipedream/platform': 1.6.5 components/frame: specifiers: @@ -3244,7 +3281,14 @@ importers: uuid: 8.3.2 components/gitlab_developer_app: - specifiers: {} + specifiers: + '@pipedream/platform': ^1.6.4 + lodash: ^4.17.21 + uuid: ^8.3.2 + dependencies: + '@pipedream/platform': 1.6.4 + lodash: 4.17.21 + uuid: 8.3.2 components/givebutter: specifiers: {} @@ -3876,7 +3920,10 @@ importers: specifiers: {} components/hotmart: - specifiers: {} + specifiers: + '@pipedream/platform': ^1.6.4 + dependencies: + '@pipedream/platform': 1.6.4 components/hotspotsystem: specifiers: @@ -4116,6 +4163,12 @@ importers: dependencies: '@pipedream/platform': 1.5.1 + components/insighto_ai: + specifiers: + '@pipedream/platform': 1.6.5 + dependencies: + '@pipedream/platform': 1.6.5 + components/insites: specifiers: '@pipedream/platform': ^1.5.1 @@ -4367,6 +4420,9 @@ importers: components/key_app_demo_1: specifiers: {} + components/keycloak: + specifiers: {} + components/keysender: specifiers: '@pipedream/platform': ^1.6.0 @@ -4459,6 +4515,9 @@ importers: components/koala_ai: specifiers: {} + components/kodagpt: + specifiers: {} + components/kontent_ai: specifiers: '@kontent-ai/delivery-sdk': ^14.4.0 @@ -4528,6 +4587,12 @@ importers: dependencies: '@pipedream/platform': 1.5.1 + components/leadiq: + specifiers: + '@pipedream/platform': 1.6.5 + dependencies: + '@pipedream/platform': 1.6.5 + components/leap: specifiers: '@pipedream/platform': ^1.5.1 @@ -5105,9 +5170,9 @@ importers: components/microsoft_365_planner: specifiers: - '@pipedream/platform': ^1.5.1 + '@pipedream/platform': ^1.6.5 dependencies: - '@pipedream/platform': 1.5.1 + '@pipedream/platform': 1.6.5 components/microsoft_advertising: specifiers: {} @@ -5278,10 +5343,10 @@ importers: components/mongodb: specifiers: - '@pipedream/platform': ^1.2.0 + '@pipedream/platform': ^1.6.5 mongodb: ^4.6.0 dependencies: - '@pipedream/platform': 1.5.1 + '@pipedream/platform': 1.6.5 mongodb: 4.17.1 components/monkeylearn: @@ -5852,7 +5917,10 @@ importers: '@pipedream/platform': 1.5.1 components/orimon: - specifiers: {} + specifiers: + '@pipedream/platform': ^1.6.2 + dependencies: + '@pipedream/platform': 1.6.4 components/ortto: specifiers: {} @@ -6070,7 +6138,10 @@ importers: '@pipedream/platform': 1.6.2 components/perplexity: - specifiers: {} + specifiers: + '@pipedream/platform': ^1.6.5 + dependencies: + '@pipedream/platform': 1.6.5 components/perry_github_test: specifiers: {} @@ -6131,6 +6202,9 @@ importers: dependencies: '@pipedream/platform': 1.5.1 + components/pidj: + specifiers: {} + components/piggy: specifiers: '@pipedream/platform': ^1.5.1 @@ -6221,6 +6295,9 @@ importers: components/pirate_weather: specifiers: {} + components/pitchlane: + specifiers: {} + components/pivotal_tracker: specifiers: '@pipedream/platform': ^1.3.0 @@ -6269,7 +6346,10 @@ importers: specifiers: {} components/platerecognizer: - specifiers: {} + specifiers: + '@pipedream/platform': ^1.6.5 + dependencies: + '@pipedream/platform': 1.6.5 components/platform_ly: specifiers: {} @@ -6451,6 +6531,9 @@ importers: components/prismic: specifiers: {} + components/pro_ledger: + specifiers: {} + components/proabono: specifiers: '@pipedream/platform': ^1.6.0 @@ -6879,7 +6962,10 @@ importers: '@pipedream/platform': 1.6.4 components/relavate: - specifiers: {} + specifiers: + '@pipedream/platform': ^1.6.5 + dependencies: + '@pipedream/platform': 1.6.5 components/relevance_ai: specifiers: @@ -8227,6 +8313,9 @@ importers: components/suitedash: specifiers: {} + components/summit: + specifiers: {} + components/sumup: specifiers: '@pipedream/platform': ^1.6.2 @@ -8605,7 +8694,10 @@ importers: '@pipedream/platform': 1.6.0 components/thoughtly: - specifiers: {} + specifiers: + '@pipedream/platform': ^1.6.5 + dependencies: + '@pipedream/platform': 1.6.5 components/threads: specifiers: @@ -8678,7 +8770,14 @@ importers: specifiers: {} components/timetonic: - specifiers: {} + specifiers: + '@pipedream/platform': ^1.6.5 + form-data: ^4.0.0 + md5: ^2.3.0 + dependencies: + '@pipedream/platform': 1.6.5 + form-data: 4.0.0 + md5: 2.3.0 components/timeular: specifiers: @@ -8847,7 +8946,10 @@ importers: specifiers: {} components/tripadvisor_content_api: - specifiers: {} + specifiers: + '@pipedream/platform': ^1.6.2 + dependencies: + '@pipedream/platform': 1.6.2 components/truss: specifiers: {} @@ -8874,7 +8976,10 @@ importers: specifiers: {} components/twenty: - specifiers: {} + specifiers: + '@pipedream/platform': ^1.6.5 + dependencies: + '@pipedream/platform': 1.6.5 components/twilio: specifiers: @@ -8990,6 +9095,12 @@ importers: dependencies: '@pipedream/platform': 0.10.0 + components/unsplash: + specifiers: + '@pipedream/platform': ^1.6.5 + dependencies: + '@pipedream/platform': 1.6.5 + components/updown_io: specifiers: '@pipedream/platform': ^1.6.0 @@ -9025,6 +9136,9 @@ importers: components/upollo: specifiers: {} + components/upstash_redis: + specifiers: {} + components/uptimerobot: specifiers: {} @@ -9253,7 +9367,10 @@ importers: specifiers: {} components/vryno: - specifiers: {} + specifiers: + '@pipedream/platform': ^1.6.2 + dependencies: + '@pipedream/platform': 1.6.4 components/vtiger_crm: specifiers: @@ -9485,6 +9602,12 @@ importers: dependencies: '@pipedream/platform': 1.5.1 + components/wiza: + specifiers: + '@pipedream/platform': ^1.6.5 + dependencies: + '@pipedream/platform': 1.6.5 + components/wonderchat: specifiers: {} diff --git a/types/src/index.ts b/types/src/index.ts index 80a5d8183339d..01846d400223c 100644 --- a/types/src/index.ts +++ b/types/src/index.ts @@ -310,15 +310,25 @@ export interface EmitMetadata { ts?: number; } +export interface IdEmitMetadata extends EmitMetadata { + id: string | number; +} + type EmitFunction = { $emit: (event: JSONValue, metadata?: EmitMetadata) => Promise; }; +type IdEmitFunction = { + $emit: (event: JSONValue, metadata: IdEmitMetadata) => Promise; +}; + type PropThis = { [Prop in keyof Props]: Props[Prop] extends App ? any : any }; -export interface Source< +type Modify = Omit & R; + +interface BaseSource< Methods, SourcePropDefinitions > { @@ -337,6 +347,38 @@ export interface Source< run: (this: PropThis & Methods & EmitFunction, options?: SourceRunOptions) => void | Promise; } +export interface LastSource< + Methods, + SourcePropDefinitions +> extends BaseSource< + Methods, + SourcePropDefinitions +> { + dedupe?: "last"; +} + +export type DedupedSource< + Methods, + SourcePropDefinitions +> = Modify, { + dedupe: "greatest" | "unique"; + run: (this: PropThis & Methods & IdEmitFunction, options?: SourceRunOptions) => void | Promise; +}>; + +export type Source< + Methods, + SourcePropDefinitions +> = LastSource< + Methods, + SourcePropDefinitions +> | DedupedSource< + Methods, + SourcePropDefinitions +>; + export function defineSource< Methods, SourcePropDefinitions, diff --git a/types/src/types.test.ts b/types/src/types.test.ts index 7178af09fd261..5812ed35d3d7f 100644 --- a/types/src/types.test.ts +++ b/types/src/types.test.ts @@ -352,3 +352,17 @@ const actionWrongType: Pipedream.Action Date: Thu, 23 May 2024 21:39:04 -0700 Subject: [PATCH 11/13] Update working-with-sql.mdx --- docs-v2/pages/databases/working-with-sql.mdx | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/docs-v2/pages/databases/working-with-sql.mdx b/docs-v2/pages/databases/working-with-sql.mdx index d509b65f84fc7..6cc2f743add46 100644 --- a/docs-v2/pages/databases/working-with-sql.mdx +++ b/docs-v2/pages/databases/working-with-sql.mdx @@ -1,4 +1,5 @@ import { Steps } from 'nextra/components' +import ArcadeEmbed from '@/components/ArcadeEmbed' # Working with SQL @@ -7,16 +8,23 @@ Pipedream makes it easy to interact with SQL databases within your workflows. Yo ## Schema Explorer When querying a database, you need to understand the schema of the tables you're working with. The schema explorer provides a visual interface to explore the tables in your database, view their columns, and understand the relationships between them. -- Once you connect your account with one of the [supported database apps](/databases#supported-databases), we automatically fetch and display the details of your database's schema below -[insert schema explorer here] +- Once you connect your account with one of the [supported database apps](/databases#supported-databases), we automatically fetch and display the details of the database schema below - You can view the columns of a table, their data types, and relationships between tables +![SQL Schema Explorer](https://res.cloudinary.com/pipedreamin/image/upload/v1716524699/sql-demo-schema-explorer_xcmorc.png) + ## SQL Editor With the built-in SQL editor, you access linting and auto-complete features typical of modern SQL editors. +![SQL Editor](https://res.cloudinary.com/pipedreamin/image/upload/v1716525371/sql-demo-editor_uqat7x.png) + ## Prepared Statements Prepared statements let you safely execute the same SQL query using dynamic inputs as parameters to help prevent SQL injection attacks. +To compare the prepared vs computed statement, click the toggle in the **Statements** tab: + +_[insert screenshot here]_ + ## Getting Started @@ -39,4 +47,9 @@ _[add a screenshot here]_ ### Write and execute your query _[add a screenshot here]_ - \ No newline at end of file +
+ + + \ No newline at end of file From b6d369f31b26c1b5b7847f81ee8e75f56130ade8 Mon Sep 17 00:00:00 2001 From: Danny Roosevelt Date: Sat, 25 May 2024 09:33:55 -0700 Subject: [PATCH 12/13] More docs --- .../connected-accounts/oauth-clients.mdx | 29 ++++++++---- docs-v2/pages/databases/index.mdx | 20 +++++---- docs-v2/pages/databases/working-with-sql.mdx | 45 ++++++++++++++----- 3 files changed, 66 insertions(+), 28 deletions(-) diff --git a/docs-v2/pages/connected-accounts/oauth-clients.mdx b/docs-v2/pages/connected-accounts/oauth-clients.mdx index 46a2905ab8b3c..df34b16b9c12b 100644 --- a/docs-v2/pages/connected-accounts/oauth-clients.mdx +++ b/docs-v2/pages/connected-accounts/oauth-clients.mdx @@ -3,9 +3,9 @@ import ArcadeEmbed from '@/components/ArcadeEmbed' import { Steps } from 'nextra/components' # OAuth Clients -By default, most OAuth apps on Pipedream use Pipedream's official OAuth client. That means when you connect an account, you'll see Pipedream's logo and name in the OAuth authorization screen, along with a set of requested permissions (scopes). -- Pipedream apps solve for a broad range of use cases, which means the scopes requested by Pipedream's official OAuth client may be more than what you need -- To define the exact scope of access you'd like to grant, you can configure a custom OAuth client for most OAuth apps in Pipedream +By default, most OAuth apps on Pipedream use the Pipedream official OAuth client. That means when you connect an account, you'll see Pipedream's logo and name in the OAuth authorization screen along with a set of requested permissions (scopes). +- Pipedream apps solve for a broad range of use cases, which means the scopes requested by Pipedream's official OAuth client may request a different set of scopes than your specific use case. +- To define the exact scope of access you'd like to grant, you can configure a custom OAuth client for most OAuth apps in Pipedream. ## Configuring Custom OAuth Clients -### Navigate to the OAuth Clients Page +### Create an OAuth client in the relevant app +For example, if you want to use a custom OAuth client for GitHub, you'll need to locate their documentation and create an OAuth app in your developer settings. + +### Navigate to the OAuth Clients page in Pipedream Open the [OAuth Clients page in your Pipedream account](https://pipedream.com/@/accounts/oauth-clients) and click "New OAuth Client". ### Select the app -Find the app you need. If you can't find what you're looking for, feel free to [submit an integration request](https://pipedream.com/support). +Choose the app you need. If you can't find what you're looking for, feel free to [submit an integration request](https://pipedream.com/support). ### Enter the required fields - **Name:** Give the OAuth client a name so it's easy to identify - **Description:** Optionally add a brief description for additional context - **Client ID and Secret**: Paste these values from the app's settings that you're configuring (the client secret is sensitive – we'll encrypt and hide it from the UI) - **Redirect URI:** Copy this Redirect URI and paste it into the app's settings -- **Scopes:** We'll list the scopes from the official Pipedream OAuth client by default. Add or remove scopes as needed, based on your use case. +- **Scopes:** We'll list the scopes from the official Pipedream OAuth client by default. Add or remove scopes as needed based on your use case. -Click "Save" +And finally, click "Save". Make sure to include all the scopes you need based on your use case. You can modify the scopes later (you'll need to reconnect your account for changes to take effect). Refer to the app's API documentation for information on what scopes you'll need. @@ -36,7 +39,12 @@ Make sure to include all the scopes you need based on your use case. You can mod ## Connecting your account with OAuth clients -Once you've created the OAuth client, you can select "Connect account" from the menu on the right: +Once you've created the OAuth client, anyone in your workspace can connect their account: +- From within the workflow builder, add step with the app, then select the custom OAuth client from the dropdown: + +_[screenshot of connect account split button dropdown]_ + +- Or you can also select "Connect account" from the menu on the right after creating the OAuth client: - \ No newline at end of file + + +### Limitations +- The vast majority of OAuth apps in Pipedream with with custom OAuth clients. However, due to the unique integration requirements for **Discord**, **Dropbox**, **Slack**, and **Zoom**, you can only use the Pipedream official OAuth client in **triggers** for those apps. \ No newline at end of file diff --git a/docs-v2/pages/databases/index.mdx b/docs-v2/pages/databases/index.mdx index 86c2c0f3897b4..44143390cf4ef 100644 --- a/docs-v2/pages/databases/index.mdx +++ b/docs-v2/pages/databases/index.mdx @@ -1,29 +1,33 @@ import Callout from '@/components/Callout' # Connecting to Databases -Connecting to a database is essential for developing production workflows. Whether you're storing application data, querying user information, or analyzing event logs, most workflows and serverless functions require querying data at some step. +Connecting to a database is essential for developing production workflows. Whether you're storing application data, querying user information, or analyzing event logs, most workflows and serverless functions require querying data at some point. -When your workflow runs, it sends outbound traffic (requests to other APIs, databases and services) through the same network as other AWS services in the `us-east-1` region. +When your workflow runs, it sends outbound traffic (requests to other APIs and databases) through the same network as other AWS services in the `us-east-1` region. -Pipedream workflows run in the AWS `us-east-1` network, sending requests from standard AWS IP ranges +Pipedream workflows run in the AWS `us-east-1` network, sending requests from standard AWS IP ranges. ## Connecting to Restricted Databases -**Unless your database is publicly accessible, you'll need to add specific IPs to your database's allow-list.** To do this, you can configure your database connection to run through a shared or dedicated static IP address from Pipedream. +**Unless your database is publicly accessible, you'll need to add specific IPs to your database allow-list.** To do this, you can configure your database connection to run through a shared or dedicated static IP address from Pipedream. ### Create a Dedicated Static IP for Outbound Traffic -- This is the most secure, recommended approach, and gives you a dedicated egress IP that is unique to your workspace -- When deployed to a VPC in Pipedream, all outbound traffic from your workflow runs through the VPC's static IP +- Pipedream supports creating Virtual Private Clouds (VPCs) for any workflow in your workspace. +- This is the most secure and recommended approach, and gives you a dedicated egress IP that is unique to your workspace. +- When deployed to a VPC in Pipedream, all outbound traffic from your workflow runs through the VPC's static IP. - [Learn more here](/workflows/vpc) ### Send Requests from a Shared Static IP -- When enabled on your connected account, Pipedream will route network requests through a static IP block -- When you create a connected account with any of [the apps](#supported-databases) that are currently supported by the SQL Proxy, requests to that database from within your Pipedream workflows will come from the IP block below. +- Configured when adding your database connection ([connected account](/connected-accounts)) to Pipedream, you can choose to route network requests through a static IP block. +- When you create a connected account with any of [the apps that are currently supported by the SQL Proxy](#supported-databases), requests to that database from within your Pipedream workflows will come from the IP block below. #### Supported Databases Pipedream's SQL Proxy, which enables the shared static IP, currently supports [MySQL](https://pipedream.com/apps/mysql) and [PostgreSQL](https://pipedream.com/apps/postgresql), and [Snowflake](https://pipedream.com/apps/snowflake) is coming soon. Please let us know if you'd like to see support for other database types. +#### Enabling the Shared Static IP +To configure your database connection to use the shared static IP, refer to [these steps](/databases/working-with-sql#getting-started). + #### Static Shared IPs Add the following IP block to your database allow-list: ``` diff --git a/docs-v2/pages/databases/working-with-sql.mdx b/docs-v2/pages/databases/working-with-sql.mdx index 6cc2f743add46..30e361b970763 100644 --- a/docs-v2/pages/databases/working-with-sql.mdx +++ b/docs-v2/pages/databases/working-with-sql.mdx @@ -1,29 +1,44 @@ +import Callout from '@/components/Callout' import { Steps } from 'nextra/components' import ArcadeEmbed from '@/components/ArcadeEmbed' # Working with SQL - Pipedream makes it easy to interact with SQL databases within your workflows. You can securely connect to your database and use either pre-built no-code triggers and actions to interact with your database, or execute custom SQL queries. +## SQL Editor +With the built-in SQL editor, you access linting and auto-complete features typical of modern SQL editors. + +![SQL Editor](https://res.cloudinary.com/pipedreamin/image/upload/v1716525371/sql-demo-editor_uqat7x.png) + ## Schema Explorer When querying a database, you need to understand the schema of the tables you're working with. The schema explorer provides a visual interface to explore the tables in your database, view their columns, and understand the relationships between them. -- Once you connect your account with one of the [supported database apps](/databases#supported-databases), we automatically fetch and display the details of the database schema below -- You can view the columns of a table, their data types, and relationships between tables +- Once you connect your account with one of the [supported database apps](#supported-databases), we automatically fetch and display the details of the database schema below +- You can **view the columns of a table**, their data types, and relationships between tables +- You can also **search and filter** the set of tables that are listed in your schema based on table or column name ![SQL Schema Explorer](https://res.cloudinary.com/pipedreamin/image/upload/v1716524699/sql-demo-schema-explorer_xcmorc.png) -## SQL Editor -With the built-in SQL editor, you access linting and auto-complete features typical of modern SQL editors. +## Prepared Statements +Prepared statements let you safely execute SQL queries with dynamic inputs that are automatically defined as parameters, in order to help prevent SQL injection attacks. -![SQL Editor](https://res.cloudinary.com/pipedreamin/image/upload/v1716525371/sql-demo-editor_uqat7x.png) +To reference dynamic data in a SQL query, simply use the standard `{{ }}` notation just like any other code step in Pipedream. For example, -## Prepared Statements -Prepared statements let you safely execute the same SQL query using dynamic inputs as parameters to help prevent SQL injection attacks. +```mysql +SELECT * + FROM products + WHERE type = {{steps.get_product_info.$return_value}} +``` + +Below your query input, you can toggle between the computed and prepared statements: + +**Prepared statement:** -To compare the prepared vs computed statement, click the toggle in the **Statements** tab: -_[insert screenshot here]_ +**Computed statement:** + + +_[insert screenshot or gif here]_ ## Getting Started @@ -52,4 +67,12 @@ _[add a screenshot here]_ - \ No newline at end of file + + +### Supported Databases +The first-class database support described above is currently supported for these apps: +- [MySQL](https://pipedream.com/apps/mysql) +- [PostgreSQL](https://pipedream.com/apps/postgresql) +- [Snowflake](https://pipedream.com/apps/snowflake) + +Need to query a different database type? Let us know! \ No newline at end of file From bad66e3490d58ca47c795befb3d63b5d03e102b2 Mon Sep 17 00:00:00 2001 From: Danny Roosevelt Date: Mon, 27 May 2024 19:51:13 -0700 Subject: [PATCH 13/13] Docs --- .../connected-accounts/oauth-clients.mdx | 20 +++++++++++++------ docs-v2/pages/databases/index.mdx | 12 +++++------ docs-v2/pages/databases/working-with-sql.mdx | 2 +- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/docs-v2/pages/connected-accounts/oauth-clients.mdx b/docs-v2/pages/connected-accounts/oauth-clients.mdx index df34b16b9c12b..74bb829dcb49a 100644 --- a/docs-v2/pages/connected-accounts/oauth-clients.mdx +++ b/docs-v2/pages/connected-accounts/oauth-clients.mdx @@ -3,9 +3,9 @@ import ArcadeEmbed from '@/components/ArcadeEmbed' import { Steps } from 'nextra/components' # OAuth Clients -By default, most OAuth apps on Pipedream use the Pipedream official OAuth client. That means when you connect an account, you'll see Pipedream's logo and name in the OAuth authorization screen along with a set of requested permissions (scopes). +By default, most OAuth apps on Pipedream use the Pipedream official OAuth client. That means when you connect an account, you'll see Pipedream's logo and name in the OAuth authorization screen along with a set of requested scopes (permissions). - Pipedream apps solve for a broad range of use cases, which means the scopes requested by Pipedream's official OAuth client may request a different set of scopes than your specific use case. -- To define the exact scope of access you'd like to grant, you can configure a custom OAuth client for most OAuth apps in Pipedream. +- If the included set of scopes with Pipedream's default OAuth client don't match your need, you can configure a custom OAuth client. ## Configuring Custom OAuth Clients Make sure to include all the scopes you need based on your use case. You can modify the scopes later (you'll need to reconnect your account for changes to take effect). Refer to the app's API documentation for information on what scopes you'll need. @@ -38,12 +38,20 @@ Make sure to include all the scopes you need based on your use case. You can mod -## Connecting your account with OAuth clients +## Connecting your account with a custom OAuth client Once you've created the OAuth client, anyone in your workspace can connect their account: -- From within the workflow builder, add step with the app, then select the custom OAuth client from the dropdown: + + +### Add a step and select the app +From within the workflow builder, add step with the app _[screenshot of connect account split button dropdown]_ +### Connect your account +- Click the dropdown on the right side of the "Connect your account" button +- Select your OAuth client from the list + + - Or you can also select "Connect account" from the menu on the right after creating the OAuth client: ### Limitations -- The vast majority of OAuth apps in Pipedream with with custom OAuth clients. However, due to the unique integration requirements for **Discord**, **Dropbox**, **Slack**, and **Zoom**, you can only use the Pipedream official OAuth client in **triggers** for those apps. \ No newline at end of file +- The vast majority of OAuth apps in Pipedream with with custom OAuth clients. However, due to the unique integration requirements for **[Discord](https://pipedream.com/apps/discord)**, **[Dropbox](https://pipedream.com/apps/dropbox)**, **[Slack](https://pipedream.com/apps/slack)**, and **[Zoom](https://pipedream.com/apps/zoom)**, you can only use the Pipedream official OAuth client in **triggers** for those apps. \ No newline at end of file diff --git a/docs-v2/pages/databases/index.mdx b/docs-v2/pages/databases/index.mdx index 44143390cf4ef..3de885a80bd1a 100644 --- a/docs-v2/pages/databases/index.mdx +++ b/docs-v2/pages/databases/index.mdx @@ -12,23 +12,23 @@ Pipedream workflows run in the AWS `us-east-1` network, sending requests from st ## Connecting to Restricted Databases **Unless your database is publicly accessible, you'll need to add specific IPs to your database allow-list.** To do this, you can configure your database connection to run through a shared or dedicated static IP address from Pipedream. -### Create a Dedicated Static IP for Outbound Traffic +### Create a dedicated static IP for outbound traffic - Pipedream supports creating Virtual Private Clouds (VPCs) for any workflow in your workspace. - This is the most secure and recommended approach, and gives you a dedicated egress IP that is unique to your workspace. - When deployed to a VPC in Pipedream, all outbound traffic from your workflow runs through the VPC's static IP. -- [Learn more here](/workflows/vpc) +- [Get started](https://pipedream.com/settings/networks) or [learn more here](/workflows/vpc). -### Send Requests from a Shared Static IP +### Send requests from a shared static IP - Configured when adding your database connection ([connected account](/connected-accounts)) to Pipedream, you can choose to route network requests through a static IP block. - When you create a connected account with any of [the apps that are currently supported by the SQL Proxy](#supported-databases), requests to that database from within your Pipedream workflows will come from the IP block below. -#### Supported Databases +#### Supported databases Pipedream's SQL Proxy, which enables the shared static IP, currently supports [MySQL](https://pipedream.com/apps/mysql) and [PostgreSQL](https://pipedream.com/apps/postgresql), and [Snowflake](https://pipedream.com/apps/snowflake) is coming soon. Please let us know if you'd like to see support for other database types. -#### Enabling the Shared Static IP +#### Enabling the shared static IP To configure your database connection to use the shared static IP, refer to [these steps](/databases/working-with-sql#getting-started). -#### Static Shared IPs +#### Static shared IPs Add the following IP block to your database allow-list: ``` 44.223.89.56/29 diff --git a/docs-v2/pages/databases/working-with-sql.mdx b/docs-v2/pages/databases/working-with-sql.mdx index 30e361b970763..d78936e34b6d5 100644 --- a/docs-v2/pages/databases/working-with-sql.mdx +++ b/docs-v2/pages/databases/working-with-sql.mdx @@ -70,7 +70,7 @@ _[add a screenshot here]_ ### Supported Databases -The first-class database support described above is currently supported for these apps: +The [native SQL editor](#sql-editor) and [schema explorer](#schema-explorer) described above are currently supported for these database apps: - [MySQL](https://pipedream.com/apps/mysql) - [PostgreSQL](https://pipedream.com/apps/postgresql) - [Snowflake](https://pipedream.com/apps/snowflake)