diff --git a/README.md b/README.md index 89f0c8f..32bd3d7 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,175 @@ -# Ballerina HubSpot CRM Import connector - -[![Build](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.import/actions/workflows/ci.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.import/actions/workflows/ci.yml) -[![Trivy](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.import/actions/workflows/trivy-scan.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.import/actions/workflows/trivy-scan.yml) -[![GraalVM Check](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.import/actions/workflows/build-with-bal-test-graalvm.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.import/actions/workflows/build-with-bal-test-graalvm.yml) -[![GitHub Last Commit](https://img.shields.io/github/last-commit/ballerina-platform/module-ballerinax-hubspot.crm.import.svg)](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.import/commits/master) -[![GitHub Issues](https://img.shields.io/github/issues/ballerina-platform/ballerina-library/module/hubspot.crm.import.svg?label=Open%20Issues)](https://github.com/ballerina-platform/ballerina-library/labels/module%hubspot.crm.import) - ## Overview -[//]: # (TODO: Add overview mentioning the purpose of the module, supported REST API versions, and other high-level details.) +[HubSpot](https://www.hubspot.com/our-story) is an AI-powered customer relationship management (CRM) platform. + +The `ballerinax/hubspot.crm.imports` offers APIs to connect and interact with the [HubSpot CRM Imports API](https://developers.hubspot.com/docs/api/crm/imports) endpoints, specifically based on the [HubSpot CRM Imports REST API](https://developers.hubspot.com/docs/reference/api/crm/imports) ## Setup guide -[//]: # (TODO: Add detailed steps to obtain credentials and configure the module.) +To use the HubSpot CRM imports connector, you must have access to the HubSpot API through a HubSpot developer account and a HubSpot App under it. Therefore you need to register for a developer account at HubSpot if you don't have one already. + +### Step 1: Create/Login to a HubSpot Developer Account + +If you have an account already, go to the [HubSpot developer portal](https://app.hubspot.com/) + +If you don't have a HubSpot Developer Account you can sign up to a free account [here](https://developers.hubspot.com/get-started) + +### Step 2 (Optional): Create a [Developer Test Account](https://developers.hubspot.com/beta-docs/getting-started/account-types#developer-test-accounts) under your account + +Within app developer accounts, you can create developer test accounts to test apps and integrations without affecting any real HubSpot data. + +> **Note:**These accounts are only for development and testing purposes. In production you should not use Developer Test Accounts. + +1. Go to Test Account section from the left sidebar. + + ![Hubspot developer portal](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/test_acc_1.png) + +2. Click Create developer test account. + + ![Hubspot developer test account](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/test_acc_2.png) + +3. In the dialogue box, give a name to your test account and click create. + + ![Create Hubspot developer test account](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/test_acc_3.png) + +### Step 3: Create a HubSpot App under your account. + +1. In your developer account, navigate to the "Apps" section. Click on "Create App" + + ![Hubspot apps](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/create_app_1.png) + +2. Provide the necessary details, including the app name and description. + +### Step 4: Configure the Authentication Flow. + +1. Move to the "Auth" Tab. + + ![Hubspot app auth tab](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/create_app_2.png) + +2. In the Scopes section, add the following scopes for your app using the "Add new scope" button. + + `crm.objects.import` + + ![Add new scope](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/scope_set.png) + +4. Add your Redirect URI in the relevant section. You can also use localhost addresses for local development purposes. Click Create App. + + ![Create app](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/create_app_final.png) + +### Step 5: Get your Client ID and Client Secret + +- Navigate to the Auth section of your app. Make sure to save the provided Client ID and Client Secret. + + ![Auth settings](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/get_credentials.png) + +### Step 6: Setup Authentication Flow + +Before proceeding with the Quickstart, ensure you have obtained the Access Token using the following steps: + +1. Create an authorization URL using the following format: + + ``` + https://app.hubspot.com/oauth/authorize?client_id=&scope=&redirect_uri= + ``` + + Replace the ``, `` and `` with your specific value. + +2. Paste it in the browser and select your developer test account to install the app when prompted. + + ![Choose account](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/install_app.png) + +3. A code will be displayed in the browser. Copy the code. + +4. Run the following curl command. Replace the ``, ` and `` with your specific value. Use the code you received in the above step 3 as the ``. + + - Linux/macOS + + ```bash + curl --request POST \ + --url https://api.hubapi.com/oauth/v1/token \ + --header 'content-type: application/x-www-form-urlencoded' \ + --data 'grant_type=authorization_code&code=&redirect_uri=&client_id=&client_secret=' + ``` + + - Windows + + ```bash + curl --request POST ^ + --url https://api.hubapi.com/oauth/v1/token ^ + --header 'content-type: application/x-www-form-urlencoded' ^ + --data 'grant_type=authorization_code&code=&redirect_uri=&client_id=&client_secret=' + ``` + + This command will return the access token necessary for API calls. + + ```json + { + "token_type": "bearer", + "refresh_token": "", + "access_token": "", + "expires_in": 1800 + } + ``` + +5. Store the access token securely for use in your application. ## Quickstart -[//]: # (TODO: Add a quickstart guide to demonstrate a basic functionality of the module, including sample code snippets.) +To use the `HubSpot CRM Imports` connector in your Ballerina application, update the `.bal` file as follows: -## Examples +### Step 1: Import the module + +Import the `hubspot.crm.import` module and `oauth2` module. + +```ballerina +import ballerina/oauth2; +import ballerinax/hubspot.crm.'import as crmImport; +``` + +### Step 2: Instantiate a new connector + +1. Create a `Config.toml` file and, configure the obtained credentials in the above steps as follows: + + ```toml + clientId = "" + clientSecret = "" + refreshToken = "" + ``` + +2. Instantiate a `crmImport:ConnectionConfig` with the obtained credentials and initialize the connector with it. -The `HubSpot CRM Import` connector provides practical examples illustrating usage in various scenarios. Explore these [examples](https://github.com/module-ballerinax-hubspot.crm.import/tree/main/examples/), covering the following use cases: + ```ballerina + configurable string clientId = ?; + configurable string clientSecret = ?; + configurable string refreshToken = ?; + + final crmImport:ConnectionConfig config = { + auth : { + clientId, + clientSecret, + refreshToken, + credentialBearer: oauth2:POST_BODY_BEARER + } + }; + + final crmImport:Client baseClient = check new(config); + ``` + +### Step 3: Invoke the connector operation + +Now, utilize the available connector operations. A sample usecase is shown below. + +#### Get a paged list of active imports + +```ballerina +public function main() returns error? { + crmImport:CollectionResponsePublicImportResponse response = check baseClient->/.get({}); +} +``` + +## Examples -[//]: # (TODO: Add examples) +The `HubSpot.crm.imports` connector provides practical examples illustrating usage in various scenarios. Explore these [examples](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/examples), covering the following use cases: ## Build from the source diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index d3ec07a..f42ac3d 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -5,12 +5,12 @@ name = "hubspot.crm.import" version = "1.0.0" license = ["Apache-2.0"] authors = ["Ballerina"] -keywords = [] -# icon = "icon.png" # TODO: update icon.png +keywords = ["hubspot","crm","imports"] +icon = "icon.png" repository = "https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.import" [build-options] observabilityIncluded = true -[platform.java21] +[platform.java17] graalvmCompatible = true diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml new file mode 100644 index 0000000..71a6cb4 --- /dev/null +++ b/ballerina/Dependencies.toml @@ -0,0 +1,336 @@ +# AUTO-GENERATED FILE. DO NOT MODIFY. + +# This file is auto-generated by Ballerina for managing dependency versions. +# It should not be modified by hand. + +[ballerina] +dependencies-toml-version = "2" +distribution-version = "2201.10.0" + +[[package]] +org = "ballerina" +name = "auth" +version = "2.12.0" +dependencies = [ + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"} +] + +[[package]] +org = "ballerina" +name = "cache" +version = "3.8.0" +dependencies = [ + {org = "ballerina", name = "constraint"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "task"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "constraint" +version = "1.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "crypto" +version = "2.7.2" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "file" +version = "1.10.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "os"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "http" +version = "2.12.4" +dependencies = [ + {org = "ballerina", name = "auth"}, + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "constraint"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "file"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "jwt"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.decimal"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "lang.regexp"}, + {org = "ballerina", name = "lang.runtime"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "lang.value"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "mime"}, + {org = "ballerina", name = "oauth2"}, + {org = "ballerina", name = "observe"}, + {org = "ballerina", name = "time"}, + {org = "ballerina", name = "url"} +] +modules = [ + {org = "ballerina", packageName = "http", moduleName = "http"}, + {org = "ballerina", packageName = "http", moduleName = "http.httpscerr"} +] + +[[package]] +org = "ballerina" +name = "io" +version = "1.6.3" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] + +[[package]] +org = "ballerina" +name = "jballerina.java" +version = "0.0.0" + +[[package]] +org = "ballerina" +name = "jwt" +version = "2.13.0" +dependencies = [ + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "lang.__internal" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} +] + +[[package]] +org = "ballerina" +name = "lang.array" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.__internal"} +] + +[[package]] +org = "ballerina" +name = "lang.decimal" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.error" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.int" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.__internal"}, + {org = "ballerina", name = "lang.object"} +] + +[[package]] +org = "ballerina" +name = "lang.object" +version = "0.0.0" + +[[package]] +org = "ballerina" +name = "lang.regexp" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.runtime" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.string" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.regexp"} +] + +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "log" +version = "2.10.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"}, + {org = "ballerina", name = "observe"} +] + +[[package]] +org = "ballerina" +name = "mime" +version = "2.10.1" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "log"} +] +modules = [ + {org = "ballerina", packageName = "mime", moduleName = "mime"} +] + +[[package]] +org = "ballerina" +name = "oauth2" +version = "2.12.0" +dependencies = [ + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "time"}, + {org = "ballerina", name = "url"} +] +modules = [ + {org = "ballerina", packageName = "oauth2", moduleName = "oauth2"} +] + +[[package]] +org = "ballerina" +name = "observe" +version = "1.3.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "os" +version = "1.8.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "task" +version = "2.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "test" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.error"} +] +modules = [ + {org = "ballerina", packageName = "test", moduleName = "test"} +] + +[[package]] +org = "ballerina" +name = "time" +version = "2.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "url" +version = "2.4.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] +modules = [ + {org = "ballerina", packageName = "url", moduleName = "url"} +] + +[[package]] +org = "ballerinai" +name = "observe" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "observe"} +] +modules = [ + {org = "ballerinai", packageName = "observe", moduleName = "observe"} +] + +[[package]] +org = "ballerinax" +name = "hubspot.crm.import" +version = "1.0.0" +dependencies = [ + {org = "ballerina", name = "http"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "mime"}, + {org = "ballerina", name = "oauth2"}, + {org = "ballerina", name = "test"}, + {org = "ballerina", name = "url"}, + {org = "ballerinai", name = "observe"} +] +modules = [ + {org = "ballerinax", packageName = "hubspot.crm.import", moduleName = "hubspot.crm.import"} +] + diff --git a/ballerina/Module.md b/ballerina/Module.md index 0861857..0b4acdc 100644 --- a/ballerina/Module.md +++ b/ballerina/Module.md @@ -1,17 +1,172 @@ ## Overview -[//]: # (TODO: Add overview mentioning the purpose of the module, supported REST API versions, and other high-level details.) +[HubSpot](https://www.hubspot.com/our-story) is an AI-powered customer relationship management (CRM) platform. + +The `ballerinax/hubspot.crm.imports` offers APIs to connect and interact with the [HubSpot CRM Imports API](https://developers.hubspot.com/docs/api/crm/imports) endpoints, specifically based on the [HubSpot CRM Imports REST API](https://developers.hubspot.com/docs/reference/api/crm/imports) ## Setup guide -[//]: # (TODO: Add detailed steps to obtain credentials and configure the module.) +To use the HubSpot CRM imports connector, you must have access to the HubSpot API through a HubSpot developer account and a HubSpot App under it. Therefore you need to register for a developer account at HubSpot if you don't have one already. + +### Step 1: Create/Login to a HubSpot Developer Account + +If you have an account already, go to the [HubSpot developer portal](https://app.hubspot.com/) + +If you don't have a HubSpot Developer Account you can sign up to a free account [here](https://developers.hubspot.com/get-started) + +### Step 2 (Optional): Create a [Developer Test Account](https://developers.hubspot.com/beta-docs/getting-started/account-types#developer-test-accounts) under your account + +Within app developer accounts, you can create developer test accounts to test apps and integrations without affecting any real HubSpot data. + +> **Note:**These accounts are only for development and testing purposes. In production you should not use Developer Test Accounts. + +1. Go to Test Account section from the left sidebar. + + ![Hubspot developer portal](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/test_acc_1.png) + +2. Click Create developer test account. + + ![Hubspot developer test account](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/test_acc_2.png) + +3. In the dialogue box, give a name to your test account and click create. + + ![Create Hubspot developer test account](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/test_acc_3.png) + +### Step 3: Create a HubSpot App under your account. + +1. In your developer account, navigate to the "Apps" section. Click on "Create App" + + ![Hubspot apps](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/create_app_1.png) + +2. Provide the necessary details, including the app name and description. + +### Step 4: Configure the Authentication Flow. + +1. Move to the "Auth" Tab. + + ![Hubspot app auth tab](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/create_app_2.png) + +2. In the Scopes section, add the following scopes for your app using the "Add new scope" button. + + `crm.objects.import` + + ![Add new scope](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/scope_set.png) + +4. Add your Redirect URI in the relevant section. You can also use localhost addresses for local development purposes. Click Create App. + + ![Create app](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/create_app_final.png) + +### Step 5: Get your Client ID and Client Secret + +- Navigate to the Auth section of your app. Make sure to save the provided Client ID and Client Secret. + + ![Auth settings](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/get_credentials.png) + +### Step 6: Setup Authentication Flow + +Before proceeding with the Quickstart, ensure you have obtained the Access Token using the following steps: + +1. Create an authorization URL using the following format: + + ``` + https://app.hubspot.com/oauth/authorize?client_id=&scope=&redirect_uri= + ``` + + Replace the ``, `` and `` with your specific value. + +2. Paste it in the browser and select your developer test account to install the app when prompted. + + ![Choose account](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/install_app.png) + +3. A code will be displayed in the browser. Copy the code. + +4. Run the following curl command. Replace the ``, ` and `` with your specific value. Use the code you received in the above step 3 as the ``. + + - Linux/macOS + + ```bash + curl --request POST \ + --url https://api.hubapi.com/oauth/v1/token \ + --header 'content-type: application/x-www-form-urlencoded' \ + --data 'grant_type=authorization_code&code=&redirect_uri=&client_id=&client_secret=' + ``` + + - Windows + + ```bash + curl --request POST ^ + --url https://api.hubapi.com/oauth/v1/token ^ + --header 'content-type: application/x-www-form-urlencoded' ^ + --data 'grant_type=authorization_code&code=&redirect_uri=&client_id=&client_secret=' + ``` + + This command will return the access token necessary for API calls. + + ```json + { + "token_type": "bearer", + "refresh_token": "", + "access_token": "", + "expires_in": 1800 + } + ``` + +5. Store the access token securely for use in your application. ## Quickstart -[//]: # (TODO: Add a quickstart guide to demonstrate a basic functionality of the module, including sample code snippets.) +To use the `HubSpot CRM Imports` connector in your Ballerina application, update the `.bal` file as follows: -## Examples +### Step 1: Import the module + +Import the `hubspot.crm.import` module and `oauth2` module. + +```ballerina +import ballerina/oauth2; +import ballerinax/hubspot.crm.'import as crmImport; +``` + +### Step 2: Instantiate a new connector + +1. Create a `Config.toml` file and, configure the obtained credentials in the above steps as follows: + + ```toml + clientId = "" + clientSecret = "" + refreshToken = "" + ``` + +2. Instantiate a `crmImport:ConnectionConfig` with the obtained credentials and initialize the connector with it. -The `HubSpot CRM Import` connector provides practical examples illustrating usage in various scenarios. Explore these [examples](https://github.com/module-ballerinax-hubspot.crm.import/tree/main/examples/), covering the following use cases: + ```ballerina + configurable string clientId = ?; + configurable string clientSecret = ?; + configurable string refreshToken = ?; + + final crmImport:ConnectionConfig config = { + auth : { + clientId, + clientSecret, + refreshToken, + credentialBearer: oauth2:POST_BODY_BEARER + } + }; + + final crmImport:Client baseClient = check new(config); + ``` + +### Step 3: Invoke the connector operation + +Now, utilize the available connector operations. A sample usecase is shown below. + +#### Get a paged list of active imports + +```ballerina +public function main() returns error? { + crmImport:CollectionResponsePublicImportResponse response = check baseClient->/.get({}); +} +``` + +## Examples -[//]: # (TODO: Add examples) +The `HubSpot.crm.imports` connector provides practical examples illustrating usage in various scenarios. Explore these [examples](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/examples), covering the following use cases: diff --git a/ballerina/Package.md b/ballerina/Package.md index 0861857..0b4acdc 100644 --- a/ballerina/Package.md +++ b/ballerina/Package.md @@ -1,17 +1,172 @@ ## Overview -[//]: # (TODO: Add overview mentioning the purpose of the module, supported REST API versions, and other high-level details.) +[HubSpot](https://www.hubspot.com/our-story) is an AI-powered customer relationship management (CRM) platform. + +The `ballerinax/hubspot.crm.imports` offers APIs to connect and interact with the [HubSpot CRM Imports API](https://developers.hubspot.com/docs/api/crm/imports) endpoints, specifically based on the [HubSpot CRM Imports REST API](https://developers.hubspot.com/docs/reference/api/crm/imports) ## Setup guide -[//]: # (TODO: Add detailed steps to obtain credentials and configure the module.) +To use the HubSpot CRM imports connector, you must have access to the HubSpot API through a HubSpot developer account and a HubSpot App under it. Therefore you need to register for a developer account at HubSpot if you don't have one already. + +### Step 1: Create/Login to a HubSpot Developer Account + +If you have an account already, go to the [HubSpot developer portal](https://app.hubspot.com/) + +If you don't have a HubSpot Developer Account you can sign up to a free account [here](https://developers.hubspot.com/get-started) + +### Step 2 (Optional): Create a [Developer Test Account](https://developers.hubspot.com/beta-docs/getting-started/account-types#developer-test-accounts) under your account + +Within app developer accounts, you can create developer test accounts to test apps and integrations without affecting any real HubSpot data. + +> **Note:**These accounts are only for development and testing purposes. In production you should not use Developer Test Accounts. + +1. Go to Test Account section from the left sidebar. + + ![Hubspot developer portal](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/test_acc_1.png) + +2. Click Create developer test account. + + ![Hubspot developer test account](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/test_acc_2.png) + +3. In the dialogue box, give a name to your test account and click create. + + ![Create Hubspot developer test account](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/test_acc_3.png) + +### Step 3: Create a HubSpot App under your account. + +1. In your developer account, navigate to the "Apps" section. Click on "Create App" + + ![Hubspot apps](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/create_app_1.png) + +2. Provide the necessary details, including the app name and description. + +### Step 4: Configure the Authentication Flow. + +1. Move to the "Auth" Tab. + + ![Hubspot app auth tab](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/create_app_2.png) + +2. In the Scopes section, add the following scopes for your app using the "Add new scope" button. + + `crm.objects.import` + + ![Add new scope](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/scope_set.png) + +4. Add your Redirect URI in the relevant section. You can also use localhost addresses for local development purposes. Click Create App. + + ![Create app](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/create_app_final.png) + +### Step 5: Get your Client ID and Client Secret + +- Navigate to the Auth section of your app. Make sure to save the provided Client ID and Client Secret. + + ![Auth settings](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/get_credentials.png) + +### Step 6: Setup Authentication Flow + +Before proceeding with the Quickstart, ensure you have obtained the Access Token using the following steps: + +1. Create an authorization URL using the following format: + + ``` + https://app.hubspot.com/oauth/authorize?client_id=&scope=&redirect_uri= + ``` + + Replace the ``, `` and `` with your specific value. + +2. Paste it in the browser and select your developer test account to install the app when prompted. + + ![Choose account](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/docs/setup/resources/install_app.png) + +3. A code will be displayed in the browser. Copy the code. + +4. Run the following curl command. Replace the ``, ` and `` with your specific value. Use the code you received in the above step 3 as the ``. + + - Linux/macOS + + ```bash + curl --request POST \ + --url https://api.hubapi.com/oauth/v1/token \ + --header 'content-type: application/x-www-form-urlencoded' \ + --data 'grant_type=authorization_code&code=&redirect_uri=&client_id=&client_secret=' + ``` + + - Windows + + ```bash + curl --request POST ^ + --url https://api.hubapi.com/oauth/v1/token ^ + --header 'content-type: application/x-www-form-urlencoded' ^ + --data 'grant_type=authorization_code&code=&redirect_uri=&client_id=&client_secret=' + ``` + + This command will return the access token necessary for API calls. + + ```json + { + "token_type": "bearer", + "refresh_token": "", + "access_token": "", + "expires_in": 1800 + } + ``` + +5. Store the access token securely for use in your application. ## Quickstart -[//]: # (TODO: Add a quickstart guide to demonstrate a basic functionality of the module, including sample code snippets.) +To use the `HubSpot CRM Imports` connector in your Ballerina application, update the `.bal` file as follows: -## Examples +### Step 1: Import the module + +Import the `hubspot.crm.import` module and `oauth2` module. + +```ballerina +import ballerina/oauth2; +import ballerinax/hubspot.crm.'import as crmImport; +``` + +### Step 2: Instantiate a new connector + +1. Create a `Config.toml` file and, configure the obtained credentials in the above steps as follows: + + ```toml + clientId = "" + clientSecret = "" + refreshToken = "" + ``` + +2. Instantiate a `crmImport:ConnectionConfig` with the obtained credentials and initialize the connector with it. -The `HubSpot CRM Import` connector provides practical examples illustrating usage in various scenarios. Explore these [examples](https://github.com/module-ballerinax-hubspot.crm.import/tree/main/examples/), covering the following use cases: + ```ballerina + configurable string clientId = ?; + configurable string clientSecret = ?; + configurable string refreshToken = ?; + + final crmImport:ConnectionConfig config = { + auth : { + clientId, + clientSecret, + refreshToken, + credentialBearer: oauth2:POST_BODY_BEARER + } + }; + + final crmImport:Client baseClient = check new(config); + ``` + +### Step 3: Invoke the connector operation + +Now, utilize the available connector operations. A sample usecase is shown below. + +#### Get a paged list of active imports + +```ballerina +public function main() returns error? { + crmImport:CollectionResponsePublicImportResponse response = check baseClient->/.get({}); +} +``` + +## Examples -[//]: # (TODO: Add examples) +The `HubSpot.crm.imports` connector provides practical examples illustrating usage in various scenarios. Explore these [examples](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/examples), covering the following use cases: diff --git a/ballerina/client.bal b/ballerina/client.bal index 66cdc3f..49c5d6a 100644 --- a/ballerina/client.bal +++ b/ballerina/client.bal @@ -1,3 +1,6 @@ +// AUTO-GENERATED FILE. DO NOT MODIFY. +// This file is auto-generated by the Ballerina OpenAPI tool. + // Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). // // WSO2 LLC. licenses this file to you under the Apache License, @@ -13,3 +16,126 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. + +import ballerina/http; +import ballerina/mime; + +public isolated client class Client { + final http:Client clientEp; + final readonly & ApiKeysConfig? apiKeyConfig; + + # Gets invoked to initialize the `connector`. + # + # + config - The configurations to be used when initializing the `connector` + # + serviceUrl - URL of the target service + # + return - An error if connector initialization failed + public isolated function init(ConnectionConfig config, string serviceUrl = "https://api.hubapi.com/crm/v3/imports") returns error? { + http:ClientConfiguration httpClientConfig = {httpVersion: config.httpVersion, timeout: config.timeout, forwarded: config.forwarded, poolConfig: config.poolConfig, compression: config.compression, circuitBreaker: config.circuitBreaker, retryConfig: config.retryConfig, validation: config.validation}; + do { + if config.http1Settings is ClientHttp1Settings { + ClientHttp1Settings settings = check config.http1Settings.ensureType(ClientHttp1Settings); + httpClientConfig.http1Settings = {...settings}; + } + if config.http2Settings is http:ClientHttp2Settings { + httpClientConfig.http2Settings = check config.http2Settings.ensureType(http:ClientHttp2Settings); + } + if config.cache is http:CacheConfig { + httpClientConfig.cache = check config.cache.ensureType(http:CacheConfig); + } + if config.responseLimits is http:ResponseLimitConfigs { + httpClientConfig.responseLimits = check config.responseLimits.ensureType(http:ResponseLimitConfigs); + } + if config.secureSocket is http:ClientSecureSocket { + httpClientConfig.secureSocket = check config.secureSocket.ensureType(http:ClientSecureSocket); + } + if config.proxy is http:ProxyConfig { + httpClientConfig.proxy = check config.proxy.ensureType(http:ProxyConfig); + } + } + if config.auth is ApiKeysConfig { + self.apiKeyConfig = (config.auth).cloneReadOnly(); + } else { + httpClientConfig.auth = config.auth; + self.apiKeyConfig = (); + } + http:Client httpEp = check new (serviceUrl, httpClientConfig); + self.clientEp = httpEp; + return; + } + + # Get active imports + # + # + headers - Headers to be sent with the request + # + queries - Queries to be sent with the request + # + return - successful operation + resource isolated function get .(map headers = {}, *Get_getpageQueries queries) returns CollectionResponsePublicImportResponse|error { + string resourcePath = string `/`; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app-legacy"] = self.apiKeyConfig?.private\-app\-legacy; + } + resourcePath = resourcePath + check getPathForQueryParam(queries); + map httpHeaders = getMapForHeaders(headerValues); + return self.clientEp->get(resourcePath, httpHeaders); + } + + # Get the information on any import + # + # + headers - Headers to be sent with the request + # + return - successful operation + resource isolated function get [int importId](map headers = {}) returns PublicImportResponse|error { + string resourcePath = string `/${getEncodedUri(importId)}`; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app-legacy"] = self.apiKeyConfig?.private\-app\-legacy; + } + map httpHeaders = getMapForHeaders(headerValues); + return self.clientEp->get(resourcePath, httpHeaders); + } + + # + headers - Headers to be sent with the request + # + queries - Queries to be sent with the request + # + return - successful operation + resource isolated function get [int importId]/errors(map headers = {}, *GetImportidErrors_geterrorsQueries queries) returns CollectionResponsePublicImportErrorForwardPaging|error { + string resourcePath = string `/${getEncodedUri(importId)}/errors`; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app-legacy"] = self.apiKeyConfig?.private\-app\-legacy; + } + resourcePath = resourcePath + check getPathForQueryParam(queries); + map httpHeaders = getMapForHeaders(headerValues); + return self.clientEp->get(resourcePath, httpHeaders); + } + + # Start a new import + # + # + headers - Headers to be sent with the request + # + return - successful operation + resource isolated function post .(body payload, map headers = {}) returns PublicImportResponse|error { + string resourcePath = string `/`; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app-legacy"] = self.apiKeyConfig?.private\-app\-legacy; + } + map httpHeaders = getMapForHeaders(headerValues); + http:Request request = new; + mime:Entity[] bodyParts = check createBodyParts(payload); + request.setBodyParts(bodyParts); + return self.clientEp->post(resourcePath, request, httpHeaders); + } + + # Cancel an active import + # + # + headers - Headers to be sent with the request + # + return - successful operation + resource isolated function post [int importId]/cancel(map headers = {}) returns ActionResponse|error { + string resourcePath = string `/${getEncodedUri(importId)}/cancel`; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app-legacy"] = self.apiKeyConfig?.private\-app\-legacy; + } + map httpHeaders = getMapForHeaders(headerValues); + http:Request request = new; + return self.clientEp->post(resourcePath, request, httpHeaders); + } +} diff --git a/ballerina/icon.png b/ballerina/icon.png new file mode 100644 index 0000000..1f93076 Binary files /dev/null and b/ballerina/icon.png differ diff --git a/ballerina/tests/mock_service.bal b/ballerina/tests/mock_service.bal new file mode 100644 index 0000000..286eed5 --- /dev/null +++ b/ballerina/tests/mock_service.bal @@ -0,0 +1,100 @@ +// AUTO-GENERATED FILE. DO NOT MODIFY. +// This file is auto-generated by the Ballerina OpenAPI tool. + +// Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/http; + +service on new http:Listener(9090) { + resource function get .() returns CollectionResponsePublicImportResponse { + return { + results: [], + paging: { + next: { + after: "after", + link: "link" + } + } + }; + } + + resource function get [int importId]() returns PublicImportResponse { + return { + state: "DONE", + mappedObjectTypeIds: [ + "0-1" + ], + createdAt: "createdTime", + updatedAt: "updatedTime", + metadata: { + objectLists: [], + counters: { + "TOTAL_ROWS": 10, + "ERRORS": 3 + }, + fileIds: [ + "184448894240" + ] + }, + importName: "First Contact Data", + optOutImport: false, + id: importId.toString() + }; + } + + resource isolated function get [int importId]/errors() returns CollectionResponsePublicImportErrorForwardPaging { + return { + results: [], + paging: { + next: { + after: "after", + link: "link" + } + } + }; + } + + resource isolated function post .() returns PublicImportResponse { + return { + state: "STARTED", + importSource: "API", + mappedObjectTypeIds: [ + "0-1" + ], + createdAt: "createdTime", + updatedAt: "UpdatedTime", + metadata: { + objectLists: [], + counters: {}, + fileIds: [ + "184642347637" + ] + }, + importName: "First Contact Data", + optOutImport: false, + id: "55234002" + }; + } + + resource isolated function post [int importId]/cancel() returns ActionResponse { + return { + completedAt: "completedTime", + startedAt: "startedTime", + status: "COMPLETE" + }; + } +}; diff --git a/ballerina/tests/resources/dummy_file.csv b/ballerina/tests/resources/dummy_file.csv new file mode 100644 index 0000000..6e76c40 --- /dev/null +++ b/ballerina/tests/resources/dummy_file.csv @@ -0,0 +1,11 @@ +First Name,Last Name,Email +John,Doe,john.doe@example.com +Jane,Smith,jane.smith@example.com +Michael,Brown,michael.brown@example.com +Emily,Davis,emily.davis@example.com +Chris,Wilson,chris.wilson@example.com +Sarah,Johnson,sarah.johnson@example.com +Daniel,Garcia,daniel.garcia@example.com +Laura,Miller,laura.miller@example.com +David,Martinez,david.martinez@example.com +Anna,Taylor,anna.taylor@example.com diff --git a/ballerina/tests/resources/dummy_import_request.json b/ballerina/tests/resources/dummy_import_request.json new file mode 100644 index 0000000..97ddcb7 --- /dev/null +++ b/ballerina/tests/resources/dummy_import_request.json @@ -0,0 +1,35 @@ +{ + "name": "First Contact Data", + "importOperations": { + "0-1": "CREATE" + }, + "dateFormat": "DAY_MONTH_YEAR", + "files": [ + { + "fileName": "dummy_file.csv", + "fileFormat": "CSV", + "fileImportPage": { + "hasHeader": true, + "columnMappings": [ + { + "columnObjectTypeId": "0-1", + "columnName": "First Name", + "propertyName": "firstname" + }, + { + "columnObjectTypeId": "0-1", + "columnName": "Last Name", + "propertyName": "lastname" + }, + { + "columnObjectTypeId": "0-1", + "columnName": "Email", + "propertyName": "email", + "columnType": "HUBSPOT_ALTERNATE_ID" + } + ] + } + } + ] + } + \ No newline at end of file diff --git a/ballerina/tests/tests.bal b/ballerina/tests/tests.bal new file mode 100644 index 0000000..99cebef --- /dev/null +++ b/ballerina/tests/tests.bal @@ -0,0 +1,131 @@ +// AUTO-GENERATED FILE. DO NOT MODIFY. +// This file is auto-generated by the Ballerina OpenAPI tool. + +// Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/io; +import ballerina/oauth2; +import ballerina/test; + +configurable string clientId = ?; +configurable string clientSecret = ?; +configurable string refreshToken = ?; + +configurable boolean isLiveServer = false; +configurable string serviceUrl = isLiveServer ? "https://api.hubapi.com/crm/v3/imports" : "http://localhost:9090"; + +OAuth2RefreshTokenGrantConfig auth = { + clientId: clientId, + clientSecret: clientSecret, + refreshToken: refreshToken, + credentialBearer: oauth2:POST_BODY_BEARER +}; + +final Client baseClient = check new ({auth}); + +json readJson = check io:fileReadJson("tests/resources/dummy_import_request.json"); +string importRequestString = readJson.toString(); + +function createImport() returns int|error { + string csvFilePath = "tests/resources/dummy_file.csv"; + byte[] bytes = check io:fileReadBytes(csvFilePath); + + body requestBody = { + files: { + fileContent: bytes, + fileName: "dummy_file.csv" + }, + importRequest: importRequestString + }; + + PublicImportResponse buildupResponse = check baseClient->/.post(payload = requestBody); + int buildUpImportId = check int:fromString(buildupResponse.id ?: "0"); + + return buildUpImportId; +}; + +@test:Config { + groups: ["live_tests", "mock_tests"] +} +function testPost_() returns error? { + string csvFilePath = "tests/resources/dummy_file.csv"; + byte[] bytes = check io:fileReadBytes(csvFilePath); + + body requestBody = { + files: { + fileContent: bytes, + fileName: "dummy_file.csv" + }, + importRequest: importRequestString + }; + + PublicImportResponse response = check baseClient->/.post(payload = requestBody); + + test:assertNotEquals(response.id, (), "No id in response"); + test:assertEquals(response.state, "STARTED", "State should be in STARTED state"); +}; + +@test:Config { + groups: ["live_tests", "mock_tests"] +} +function testPost_importId_cancel() returns error? { + int buildUpImportId = check createImport(); + + ActionResponse response = check baseClient->/[buildUpImportId]/cancel.post(); + + test:assertNotEquals(response.startedAt, (), msg = "No startedAt timestamp in response"); + test:assertNotEquals(response.status, (), msg = "No status in response"); +} + +@test:Config { + groups: ["live_tests", "mock_tests"] +} +function testGet_importId() returns error? { + int buildUpImportId = check createImport(); + + PublicImportResponse response = check baseClient->/[buildUpImportId].get({}); + + test:assertNotEquals(response.createdAt, (), "No createdAt timestamp in response"); + test:assertNotEquals(response.id, (), "No id in response"); +} + +@test:Config { + groups: ["live_tests", "mock_tests"] +} +function testGet_() returns error? { + CollectionResponsePublicImportResponse response = check baseClient->/.get({}); + + test:assertNotEquals(response.results, null, msg = "Value should not be null"); + + PublicImportResponse[] results = response.results ?: []; + if (results.length() > 0) { + foreach var item in results { + test:assertNotEquals(item.id, (), "No id in response"); + } + } +} + +@test:Config { + groups: ["live_tests", "mock_tests"] +} +function testGet_importId_error() returns error? { + int buildUpImportId = check createImport(); + + CollectionResponsePublicImportErrorForwardPaging response = check baseClient->/[buildUpImportId]/errors.get({}); + + test:assertNotEquals(response.results, (), msg = "Results should not be null"); +} diff --git a/ballerina/types.bal b/ballerina/types.bal new file mode 100644 index 0000000..49d859e --- /dev/null +++ b/ballerina/types.bal @@ -0,0 +1,234 @@ +// AUTO-GENERATED FILE. DO NOT MODIFY. +// This file is auto-generated by the Ballerina OpenAPI tool. + +// Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/http; + +public type CollectionResponsePublicImportResponse record { + Paging? paging?; + PublicImportResponse[]? results; +}; + +public type PublicObjectListRecord record { + # The ID of the list containing the imported objects. + string? listId; + # The type of object contained in the list. + string? objectType; +}; + +public type Paging record { + NextPage? next?; + PreviousPage? prev?; +}; + +public type PropertyValue record { + string? sourceId; + boolean? selectedByUser; + string? sourceLabel; + "UNKNOWN"|"IMPORT"|"API"|"FORM"|"ANALYTICS"|"MIGRATION"|"SALESFORCE"|"INTEGRATION"|"CONTACTS_WEB"|"WAL_INCREMENTAL"|"TASK"|"EMAIL"|"WORKFLOWS"|"CALCULATED"|"SOCIAL"|"BATCH_UPDATE"|"SIGNALS"|"BIDEN"|"DEFAULT"|"COMPANIES"|"DEALS"|"ASSISTS"|"PRESENTATIONS"|"TALLY"|"SIDEKICK"|"CRM_UI"|"MERGE_CONTACTS"|"PORTAL_USER_ASSOCIATOR"|"INTEGRATIONS_PLATFORM"|"BCC_TO_CRM"|"FORWARD_TO_CRM"|"ENGAGEMENTS"|"SALES"|"HEISENBERG"|"LEADIN"|"GMAIL_INTEGRATION"|"ACADEMY"|"SALES_MESSAGES"|"AVATARS_SERVICE"|"MERGE_COMPANIES"|"SEQUENCES"|"COMPANY_FAMILIES"|"MOBILE_IOS"|"MOBILE_ANDROID"|"CONTACTS"|"ASSOCIATIONS"|"EXTENSION"|"SUCCESS"|"BOT"|"INTEGRATIONS_SYNC"|"AUTOMATION_PLATFORM"|"CONVERSATIONS"|"EMAIL_INTEGRATION"|"CONTENT_MEMBERSHIP"|"QUOTES"|"BET_ASSIGNMENT"|"QUOTAS"|"BET_CRM_CONNECTOR"|"MEETINGS"|"MERGE_OBJECTS"|"RECYCLING_BIN"|"ADS"|"AI_GROUP"|"COMMUNICATOR"|"SETTINGS"|"PROPERTY_SETTINGS"|"PIPELINE_SETTINGS"|"COMPANY_INSIGHTS"|"BEHAVIORAL_EVENTS"|"PAYMENTS"|"GOALS"|"PORTAL_OBJECT_SYNC"|"APPROVALS"|"FILE_MANAGER"|"MARKETPLACE"|"INTERNAL_PROCESSING"|"FORECASTING"|"SLACK_INTEGRATION"|"CRM_UI_BULK_ACTION"|"WORKFLOW_CONTACT_DELETE_ACTION"|"ACCEPTANCE_TEST"|"PLAYBOOKS"|"CHATSPOT"|"FLYWHEEL_PRODUCT_DATA_SYNC"|"HELP_DESK"|"BILLING"|"DATA_ENRICHMENT"|"AUTOMATION_JOURNEY"|"MICROAPPS"|"INTENT"|"PROSPECTING_AGENT" 'source; + int:Signed32? updatedByUserId; + int? persistenceTimestamp; + string? sourceMetadata; + "none"|"standard"|"high" dataSensitivity; + int[]? sourceVid; + string? unit; + string? requestId; + boolean? isEncrypted; + string? name; + boolean? useTimestampAsPersistenceTimestamp; + string? value; + int? selectedByUserTimestamp; + int? timestamp; + boolean? isLargeValue; +}; + +public type ImportTemplate record { + "admin_defined"|"previous_import"|"user_file" templateType; + int:Signed32? templateId; +}; + +public type ImportRowCore record { + string[]? rowData; + boolean? containsEncryptedProperties; + int:Signed32? lineNumber; + string? pageName?; + int:Signed32? fileId; +}; + +# Proxy server configurations to be used with the HTTP client endpoint. +public type ProxyConfig record {| + # Host name of the proxy server + string host = ""; + # Proxy server port + int port = 0; + # Proxy server username + string userName = ""; + # Proxy server password + @display {label: "", kind: "password"} + string password = ""; +|}; + +public type body record { + record {byte[] fileContent; string fileName;}? files?; + string? importRequest?; +}; + +public type ActionResponse record { + string? completedAt; + string? requestedAt?; + string? startedAt; + record {|string?...;|}? links?; + "PENDING"|"PROCESSING"|"CANCELED"|"COMPLETE" status; +}; + +# Represents the Queries record for the operation: get-/{importId}/errors_getErrors +public type GetImportidErrors_geterrorsQueries record { + # Set to True to receive the data values for the errored row. + boolean includeRowData?; + # The maximum number of results to display per page. + int:Signed32 'limit?; + # The paging cursor token of the last successfully read resource will be returned as the `paging.next.after` JSON property of a paged response containing more results. + string after?; + # Set to True to receive a message explaining the error. + boolean includeErrorMessage?; +}; + +public type ForwardPaging record { + NextPage? next?; +}; + +public type PublicImportError record { + int:Signed32? createdAt; + string? extraContext?; + string? objectTypeId?; + "INCORRECT_NUMBER_OF_COLUMNS"|"INVALID_OBJECT_ID"|"INVALID_ASSOCIATION_IDENTIFIER"|"NO_OBJECT_ID_FROM_ASSOCIATION_IDENTIFIER"|"MULTIPLE_COMPANIES_WITH_THIS_DOMAIN"|"PROPERTY_DEFINITION_NOT_FOUND"|"PROPERTY_VALUE_NOT_FOUND"|"COULD_NOT_FIND_OWNER"|"MULTIPLE_OWNERS_FOUND"|"COULD_NOT_FIND_BUSINESS_UNIT"|"COULD_NOT_PARSE_NUMBER"|"COULD_NOT_PARSE_DATE"|"COULD_NOT_PARSE_TERM"|"OUTSIDE_VALID_TIME_RANGE"|"OUTSIDE_VALID_TERM_RANGE"|"COULD_NOT_PARSE_ROW"|"INVALID_ENUMERATION_OPTION"|"AMBIGUOUS_ENUMERATION_OPTION"|"FAILED_VALIDATION"|"FAILED_TO_CREATE_ASSOCIATION"|"ASSOCIATION_LIMIT_EXCEEDED"|"FILE_NOT_FOUND"|"INVALID_COLUMN_CONFIGURATION"|"INVALID_FILE_TYPE"|"INVALID_SPREADSHEET"|"INVALID_SHEET_COUNT"|"FAILED_TO_PROCESS_OBJECT_WITH_EMPTY_PROPERTY_VALUES"|"UNKNOWN_BAD_REQUEST"|"GDPR_BLACKLISTED_EMAIL"|"DUPLICATE_ASSOCIATION_ID"|"LIMIT_EXCEEDED"|"PORTAL_WIDE_CUSTOM_OBJECT_LIMIT_EXCEEDED"|"INVALID_ALTERNATE_ID"|"INVALID_EMAIL"|"SECONDARY_EMAIL_WRITE_FAILURE"|"INVALID_DOMAIN"|"DUPLICATE_ROW_CONTENT"|"INVALID_NUMBER_SIZE"|"UNKNOWN_ERROR"|"FAILED_TO_OPT_OUT_CONTACT"|"INVALID_REQUIRED_PROPERTY"|"MISSING_REQUIRED_PROPERTY"|"DUPLICATE_ALTERNATE_ID"|"DUPLICATE_OBJECT_ID"|"DUPLICATE_UNIQUE_PROPERTY_VALUE"|"UNKNOWN_ASSOCIATION_RECORD_ID"|"INVALID_RECORD_ID"|"DUPLICATE_RECORD_ID"|"INVALID_CUSTOM_PROPERTY_VALIDATION"|"CREATE_ONLY_IMPORT"|"UPDATE_ONLY_IMPORT"|"COLUMN_TOO_LARGE"|"ROW_DATA_TOO_LARGE"|"MISSING_EVENT_TIMESTAMP"|"INVALID_EVENT_TIMESTAMP"|"INVALID_EVENT"|"DUPLICATE_EVENT"|"MISSING_EVENT_DEFINITION"|"INVALID_ASSOCIATION_KEY"|"ASSOCIATION_RECORD_NOT_FOUND"|"MISSING_OBJECT_DEFINITION"|"ASSOCIATION_LABEL_NOT_FOUND"|"MANY_ERRORS_IN_ROW" errorType; + PropertyValue? invalidPropertyValue?; + string? errorMessage?; + int:Signed32? knownColumnNumber?; + string? invalidValueToDisplay?; + string? id; + ImportRowCore? sourceData; + "CONTACT"|"COMPANY"|"DEAL"|"ENGAGEMENT"|"TICKET"|"OWNER"|"PRODUCT"|"LINE_ITEM"|"BET_DELIVERABLE_SERVICE"|"CONTENT"|"CONVERSATION"|"BET_ALERT"|"PORTAL"|"QUOTE"|"FORM_SUBMISSION_INBOUNDDB"|"QUOTA"|"UNSUBSCRIBE"|"COMMUNICATION"|"FEEDBACK_SUBMISSION"|"ATTRIBUTION"|"SALESFORCE_SYNC_ERROR"|"RESTORABLE_CRM_OBJECT"|"HUB"|"LANDING_PAGE"|"PRODUCT_OR_FOLDER"|"TASK"|"FORM"|"MARKETING_EMAIL"|"AD_ACCOUNT"|"AD_CAMPAIGN"|"AD_GROUP"|"AD"|"KEYWORD"|"CAMPAIGN"|"SOCIAL_CHANNEL"|"SOCIAL_POST"|"SITE_PAGE"|"BLOG_POST"|"IMPORT"|"EXPORT"|"CTA"|"TASK_TEMPLATE"|"AUTOMATION_PLATFORM_FLOW"|"OBJECT_LIST"|"NOTE"|"MEETING_EVENT"|"CALL"|"EMAIL"|"PUBLISHING_TASK"|"CONVERSATION_SESSION"|"CONTACT_CREATE_ATTRIBUTION"|"INVOICE"|"MARKETING_EVENT"|"CONVERSATION_INBOX"|"CHATFLOW"|"MEDIA_BRIDGE"|"SEQUENCE"|"SEQUENCE_STEP"|"FORECAST"|"SNIPPET"|"TEMPLATE"|"DEAL_CREATE_ATTRIBUTION"|"QUOTE_TEMPLATE"|"QUOTE_MODULE"|"QUOTE_MODULE_FIELD"|"QUOTE_FIELD"|"SEQUENCE_ENROLLMENT"|"SUBSCRIPTION"|"ACCEPTANCE_TEST"|"SOCIAL_BROADCAST"|"DEAL_SPLIT"|"DEAL_REGISTRATION"|"GOAL_TARGET"|"GOAL_TARGET_GROUP"|"PORTAL_OBJECT_SYNC_MESSAGE"|"FILE_MANAGER_FILE"|"FILE_MANAGER_FOLDER"|"SEQUENCE_STEP_ENROLLMENT"|"APPROVAL"|"APPROVAL_STEP"|"CTA_VARIANT"|"SALES_DOCUMENT"|"DISCOUNT"|"FEE"|"TAX"|"MARKETING_CALENDAR"|"PERMISSIONS_TESTING"|"PRIVACY_SCANNER_COOKIE"|"DATA_SYNC_STATE"|"WEB_INTERACTIVE"|"PLAYBOOK"|"FOLDER"|"PLAYBOOK_QUESTION"|"PLAYBOOK_SUBMISSION"|"PLAYBOOK_SUBMISSION_ANSWER"|"COMMERCE_PAYMENT"|"GSC_PROPERTY"|"SOX_PROTECTED_DUMMY_TYPE"|"BLOG_LISTING_PAGE"|"QUARANTINED_SUBMISSION"|"PAYMENT_SCHEDULE"|"PAYMENT_SCHEDULE_INSTALLMENT"|"MARKETING_CAMPAIGN_UTM"|"DISCOUNT_TEMPLATE"|"DISCOUNT_CODE"|"FEEDBACK_SURVEY"|"CMS_URL"|"SALES_TASK"|"SALES_WORKLOAD"|"USER"|"POSTAL_MAIL"|"SCHEMAS_BACKEND_TEST"|"PAYMENT_LINK"|"SUBMISSION_TAG"|"CAMPAIGN_STEP"|"SCHEDULING_PAGE"|"SOX_PROTECTED_TEST_TYPE"|"ORDER"|"MARKETING_SMS"|"PARTNER_ACCOUNT"|"CAMPAIGN_TEMPLATE"|"CAMPAIGN_TEMPLATE_STEP"|"PLAYLIST"|"CLIP"|"CAMPAIGN_BUDGET_ITEM"|"CAMPAIGN_SPEND_ITEM"|"MIC"|"CONTENT_AUDIT"|"CONTENT_AUDIT_PAGE"|"PLAYLIST_FOLDER"|"LEAD"|"ABANDONED_CART"|"EXTERNAL_WEB_URL"|"VIEW"|"VIEW_BLOCK"|"ROSTER"|"CART"|"AUTOMATION_PLATFORM_FLOW_ACTION"|"SOCIAL_PROFILE"|"PARTNER_CLIENT"|"ROSTER_MEMBER"|"MARKETING_EVENT_ATTENDANCE"|"ALL_PAGES"|"AI_FORECAST"|"CRM_PIPELINES_DUMMY_TYPE"|"KNOWLEDGE_ARTICLE"|"PROPERTY_INFO"|"DATA_PRIVACY_CONSENT"|"GOAL_TEMPLATE"|"SCORE_CONFIGURATION"|"AUDIENCE"|"PARTNER_CLIENT_REVENUE"|"AUTOMATION_JOURNEY"|"COMBO_EVENT_CONFIGURATION"|"CRM_OBJECTS_DUMMY_TYPE"|"CASE_STUDY"|"SERVICE"|"PODCAST_EPISODE"|"PARTNER_SERVICE"|"PROSPECTING_AGENT_CONTACT_ASSIGNMENT"|"UNKNOWN" objectType?; + string? invalidValue?; +}; + +# Provides settings related to HTTP/1.x protocol. +public type ClientHttp1Settings record {| + # Specifies whether to reuse a connection for multiple requests + http:KeepAlive keepAlive = http:KEEPALIVE_AUTO; + # The chunking behaviour of the request + http:Chunking chunking = http:CHUNKING_AUTO; + # Proxy server related options + ProxyConfig proxy?; +|}; + +public type PreviousPage record { + string? before; + string? link?; +}; + +public type PublicImportMetadata record { + # Summarized outcomes of each row a developer attempted to import into HubSpot. + record {|int:Signed32?...;|}? counters; + # The IDs of files uploaded in the File Manager API. + string[]? fileIds; + # The lists containing the imported objects. + PublicObjectListRecord[]? objectLists; +}; + +# OAuth2 Refresh Token Grant Configs +public type OAuth2RefreshTokenGrantConfig record {| + *http:OAuth2RefreshTokenGrantConfig; + # Refresh URL + string refreshUrl = "https://api.hubapi.com/oauth/v1/token"; +|}; + +public type CollectionResponsePublicImportErrorForwardPaging record { + ForwardPaging? paging?; + PublicImportError[]? results; +}; + +public type PublicImportResponse record { + ImportTemplate? importTemplate?; + string? createdAt; + PublicImportMetadata? metadata; + record {}? importRequestJson?; + "API"|"CRM_UI"|"IMPORT"|"MOBILE_ANDROID"|"MOBILE_IOS"|"SALESFORCE" importSource?; + string? importName?; + # The status of the import. + "STARTED"|"PROCESSING"|"DONE"|"FAILED"|"CANCELED"|"DEFERRED"|"REVERTED" state; + string? id; + # Whether or not the import is a list of people disqualified from receiving emails. + boolean? optOutImport; + string[]? mappedObjectTypeIds; + string? updatedAt; +}; + +# Represents the Queries record for the operation: get-/_getPage +public type Get_getpageQueries record { + string before?; + # The maximum number of results to display per page. + int:Signed32 'limit?; + # The paging cursor token of the last successfully read resource will be returned as the `paging.next.after` JSON property of a paged response containing more results. + string after?; +}; + +public type NextPage record { + string? link?; + string? after; +}; + +# Provides API key configurations needed when communicating with a remote HTTP endpoint. +public type ApiKeysConfig record {| + string private\-app\-legacy; +|}; + +# Provides a set of configurations for controlling the behaviours when communicating with a remote HTTP endpoint. +@display {label: "Connection Config"} +public type ConnectionConfig record {| + # Provides Auth configurations needed when communicating with a remote HTTP endpoint. + http:BearerTokenConfig|OAuth2RefreshTokenGrantConfig|ApiKeysConfig auth; + # The HTTP version understood by the client + http:HttpVersion httpVersion = http:HTTP_2_0; + # Configurations related to HTTP/1.x protocol + ClientHttp1Settings http1Settings?; + # Configurations related to HTTP/2 protocol + http:ClientHttp2Settings http2Settings?; + # The maximum time to wait (in seconds) for a response before closing the connection + decimal timeout = 60; + # The choice of setting `forwarded`/`x-forwarded` header + string forwarded = "disable"; + # Configurations associated with request pooling + http:PoolConfiguration poolConfig?; + # HTTP caching related configurations + http:CacheConfig cache?; + # Specifies the way of handling compression (`accept-encoding`) header + http:Compression compression = http:COMPRESSION_AUTO; + # Configurations associated with the behaviour of the Circuit Breaker + http:CircuitBreakerConfig circuitBreaker?; + # Configurations associated with retrying + http:RetryConfig retryConfig?; + # Configurations associated with inbound response size limits + http:ResponseLimitConfigs responseLimits?; + # SSL/TLS-related options + http:ClientSecureSocket secureSocket?; + # Proxy server related options + http:ProxyConfig proxy?; + # Enables the inbound payload validation functionality which provided by the constraint package. Enabled by default + boolean validation = true; +|}; diff --git a/ballerina/utils.bal b/ballerina/utils.bal new file mode 100644 index 0000000..cd3e056 --- /dev/null +++ b/ballerina/utils.bal @@ -0,0 +1,269 @@ +// AUTO-GENERATED FILE. DO NOT MODIFY. +// This file is auto-generated by the Ballerina OpenAPI tool. + +// Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/mime; +import ballerina/url; + +type SimpleBasicType string|boolean|int|float|decimal; + +# Represents encoding mechanism details. +type Encoding record { + # Defines how multiple values are delimited + string style = FORM; + # Specifies whether arrays and objects should generate as separate fields + boolean explode = true; + # Specifies the custom content type + string contentType?; + # Specifies the custom headers + map headers?; +}; + +enum EncodingStyle { + DEEPOBJECT, FORM, SPACEDELIMITED, PIPEDELIMITED +} + +final Encoding & readonly defaultEncoding = {}; + +# Serialize the record according to the deepObject style. +# +# + parent - Parent record name +# + anyRecord - Record to be serialized +# + return - Serialized record as a string +isolated function getDeepObjectStyleRequest(string parent, record {} anyRecord) returns string { + string[] recordArray = []; + foreach [string, anydata] [key, value] in anyRecord.entries() { + if value is SimpleBasicType { + recordArray.push(parent + "[" + key + "]" + "=" + getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + recordArray.push(getSerializedArray(parent + "[" + key + "]" + "[]", value, DEEPOBJECT, true)); + } else if value is record {} { + string nextParent = parent + "[" + key + "]"; + recordArray.push(getDeepObjectStyleRequest(nextParent, value)); + } else if value is record {}[] { + string nextParent = parent + "[" + key + "]"; + recordArray.push(getSerializedRecordArray(nextParent, value, DEEPOBJECT)); + } + recordArray.push("&"); + } + _ = recordArray.pop(); + return string:'join("", ...recordArray); +} + +# Serialize the record according to the form style. +# +# + parent - Parent record name +# + anyRecord - Record to be serialized +# + explode - Specifies whether arrays and objects should generate separate parameters +# + return - Serialized record as a string +isolated function getFormStyleRequest(string parent, record {} anyRecord, boolean explode = true) returns string { + string[] recordArray = []; + if explode { + foreach [string, anydata] [key, value] in anyRecord.entries() { + if value is SimpleBasicType { + recordArray.push(key, "=", getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + recordArray.push(getSerializedArray(key, value, explode = explode)); + } else if value is record {} { + recordArray.push(getFormStyleRequest(parent, value, explode)); + } + recordArray.push("&"); + } + _ = recordArray.pop(); + } else { + foreach [string, anydata] [key, value] in anyRecord.entries() { + if value is SimpleBasicType { + recordArray.push(key, ",", getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + recordArray.push(getSerializedArray(key, value, explode = false)); + } else if value is record {} { + recordArray.push(getFormStyleRequest(parent, value, explode)); + } + recordArray.push(","); + } + _ = recordArray.pop(); + } + return string:'join("", ...recordArray); +} + +# Serialize arrays. +# +# + arrayName - Name of the field with arrays +# + anyArray - Array to be serialized +# + style - Defines how multiple values are delimited +# + explode - Specifies whether arrays and objects should generate separate parameters +# + return - Serialized array as a string +isolated function getSerializedArray(string arrayName, anydata[] anyArray, string style = "form", boolean explode = true) returns string { + string key = arrayName; + string[] arrayValues = []; + if anyArray.length() > 0 { + if style == FORM && !explode { + arrayValues.push(key, "="); + foreach anydata i in anyArray { + arrayValues.push(getEncodedUri(i.toString()), ","); + } + } else if style == SPACEDELIMITED && !explode { + arrayValues.push(key, "="); + foreach anydata i in anyArray { + arrayValues.push(getEncodedUri(i.toString()), "%20"); + } + } else if style == PIPEDELIMITED && !explode { + arrayValues.push(key, "="); + foreach anydata i in anyArray { + arrayValues.push(getEncodedUri(i.toString()), "|"); + } + } else if style == DEEPOBJECT { + foreach anydata i in anyArray { + arrayValues.push(key, "[]", "=", getEncodedUri(i.toString()), "&"); + } + } else { + foreach anydata i in anyArray { + arrayValues.push(key, "=", getEncodedUri(i.toString()), "&"); + } + } + _ = arrayValues.pop(); + } + return string:'join("", ...arrayValues); +} + +# Serialize the array of records according to the form style. +# +# + parent - Parent record name +# + value - Array of records to be serialized +# + style - Defines how multiple values are delimited +# + explode - Specifies whether arrays and objects should generate separate parameters +# + return - Serialized record as a string +isolated function getSerializedRecordArray(string parent, record {}[] value, string style = FORM, boolean explode = true) returns string { + string[] serializedArray = []; + if style == DEEPOBJECT { + int arayIndex = 0; + foreach var recordItem in value { + serializedArray.push(getDeepObjectStyleRequest(parent + "[" + arayIndex.toString() + "]", recordItem), "&"); + arayIndex = arayIndex + 1; + } + } else { + if !explode { + serializedArray.push(parent, "="); + } + foreach var recordItem in value { + serializedArray.push(getFormStyleRequest(parent, recordItem, explode), ","); + } + } + _ = serializedArray.pop(); + return string:'join("", ...serializedArray); +} + +# Get Encoded URI for a given value. +# +# + value - Value to be encoded +# + return - Encoded string +isolated function getEncodedUri(anydata value) returns string { + string|error encoded = url:encode(value.toString(), "UTF8"); + if encoded is string { + return encoded; + } else { + return value.toString(); + } +} + +# Generate query path with query parameter. +# +# + queryParam - Query parameter map +# + encodingMap - Details on serialization mechanism +# + return - Returns generated Path or error at failure of client initialization +isolated function getPathForQueryParam(map queryParam, map encodingMap = {}) returns string|error { + string[] param = []; + if queryParam.length() > 0 { + param.push("?"); + foreach var [key, value] in queryParam.entries() { + if value is () { + _ = queryParam.remove(key); + continue; + } + Encoding encodingData = encodingMap.hasKey(key) ? encodingMap.get(key) : defaultEncoding; + if value is SimpleBasicType { + param.push(key, "=", getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + param.push(getSerializedArray(key, value, encodingData.style, encodingData.explode)); + } else if value is record {} { + if encodingData.style == DEEPOBJECT { + param.push(getDeepObjectStyleRequest(key, value)); + } else { + param.push(getFormStyleRequest(key, value, encodingData.explode)); + } + } else { + param.push(key, "=", value.toString()); + } + param.push("&"); + } + _ = param.pop(); + } + string restOfPath = string:'join("", ...param); + return restOfPath; +} + +# Generate header map for given header values. +# +# + headerParam - Headers map +# + return - Returns generated map or error at failure of client initialization +isolated function getMapForHeaders(map headerParam) returns map { + map headerMap = {}; + foreach var [key, value] in headerParam.entries() { + if value is SimpleBasicType[] { + headerMap[key] = from SimpleBasicType data in value + select data.toString(); + } else { + headerMap[key] = value.toString(); + } + } + return headerMap; +} + +isolated function createBodyParts(record {|anydata...;|} anyRecord, map encodingMap = {}) returns mime:Entity[]|error { + mime:Entity[] entities = []; + foreach [string, anydata] [key, value] in anyRecord.entries() { + Encoding encodingData = encodingMap.hasKey(key) ? encodingMap.get(key) : {}; + mime:Entity entity = new mime:Entity(); + if value is record {byte[] fileContent; string fileName;} { + entity.setContentDisposition(mime:getContentDispositionObject(string `form-data; name=${key}; filename=${value.fileName}`)); + entity.setByteArray(value.fileContent); + } else if value is byte[] { + entity.setContentDisposition(mime:getContentDispositionObject(string `form-data; name=${key};`)); + entity.setByteArray(value); + } else if value is SimpleBasicType|SimpleBasicType[] { + entity.setContentDisposition(mime:getContentDispositionObject(string `form-data; name=${key};`)); + entity.setText(value.toString()); + } else if value is record {}|record {}[] { + entity.setContentDisposition(mime:getContentDispositionObject(string `form-data; name=${key};`)); + entity.setJson(value.toJson()); + } + if encodingData?.contentType is string { + check entity.setContentType(encodingData?.contentType.toString()); + } + map? headers = encodingData?.headers; + if headers is map { + foreach var [headerName, headerValue] in headers.entries() { + if headerValue is SimpleBasicType { + entity.setHeader(headerName, headerValue.toString()); + } + } + } + entities.push(entity); + } + return entities; +} diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml index 01a3705..8799aae 100644 --- a/build-config/resources/Ballerina.toml +++ b/build-config/resources/Ballerina.toml @@ -5,12 +5,12 @@ name = "hubspot.crm.import" version = "@toml.version@" license = ["Apache-2.0"] authors = ["Ballerina"] -keywords = [] # TODO: Add keywords -# icon = "icon.png" # TODO: Add icon +keywords = ["hubspot","crm","imports"] +icon = "icon.png" repository = "https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.import" [build-options] observabilityIncluded = true -[platform.java21] +[platform.java17] graalvmCompatible = true diff --git a/docs/setup/resources/create_app_1.png b/docs/setup/resources/create_app_1.png new file mode 100644 index 0000000..dc6c160 Binary files /dev/null and b/docs/setup/resources/create_app_1.png differ diff --git a/docs/setup/resources/create_app_2.png b/docs/setup/resources/create_app_2.png new file mode 100644 index 0000000..29393b2 Binary files /dev/null and b/docs/setup/resources/create_app_2.png differ diff --git a/docs/setup/resources/create_app_final.png b/docs/setup/resources/create_app_final.png new file mode 100644 index 0000000..88713af Binary files /dev/null and b/docs/setup/resources/create_app_final.png differ diff --git a/docs/setup/resources/get_credentials.png b/docs/setup/resources/get_credentials.png new file mode 100644 index 0000000..0c587a9 Binary files /dev/null and b/docs/setup/resources/get_credentials.png differ diff --git a/docs/setup/resources/install_app.png b/docs/setup/resources/install_app.png new file mode 100644 index 0000000..6a77bec Binary files /dev/null and b/docs/setup/resources/install_app.png differ diff --git a/docs/setup/resources/scope_set.png b/docs/setup/resources/scope_set.png new file mode 100644 index 0000000..a246bbb Binary files /dev/null and b/docs/setup/resources/scope_set.png differ diff --git a/docs/setup/resources/test_acc_1.png b/docs/setup/resources/test_acc_1.png new file mode 100644 index 0000000..d9317bd Binary files /dev/null and b/docs/setup/resources/test_acc_1.png differ diff --git a/docs/setup/resources/test_acc_2.png b/docs/setup/resources/test_acc_2.png new file mode 100644 index 0000000..7d3acfc Binary files /dev/null and b/docs/setup/resources/test_acc_2.png differ diff --git a/docs/setup/resources/test_acc_3.png b/docs/setup/resources/test_acc_3.png new file mode 100644 index 0000000..5301aa7 Binary files /dev/null and b/docs/setup/resources/test_acc_3.png differ diff --git a/docs/spec/openapi.json b/docs/spec/openapi.json new file mode 100644 index 0000000..fb19515 --- /dev/null +++ b/docs/spec/openapi.json @@ -0,0 +1,800 @@ +{ + "openapi" : "3.0.1", + "info" : { + "title" : "CRM Imports", + "version" : "v3", + "x-hubspot-product-tier-requirements" : { + "marketing" : "FREE", + "sales" : "FREE", + "service" : "FREE", + "cms" : "FREE" + }, + "x-hubspot-documentation-banner" : "NONE", + "x-hubspot-api-use-case" : "After a successful in-person webinar, use this API to import a set of contacts based on who signed up during the event.", + "x-hubspot-related-documentation" : [ { + "name" : "Import CRM Guide", + "url" : "https://developers.hubspot.com/beta-docs/guides/api/crm/imports" + } ], + "x-hubspot-introduction" : "Use the imports API to import CRM records and activities, such as contacts, companies, and notes, into your HubSpot account." + }, + "servers" : [ { + "url" : "https://api.hubapi.com/crm/v3/imports" + } ], + "tags" : [ { + "name" : "Core" + }, { + "name" : "Public_Imports" + } ], + "paths" : { + "/{importId}/cancel" : { + "post" : { + "tags" : [ "Core" ], + "summary" : "Cancel an active import", + "description" : "This allows a developer to cancel an active import.", + "operationId" : "post-/{importId}/cancel_cancel", + "parameters" : [ { + "name" : "importId", + "in" : "path", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ActionResponse" + } + } + } + }, + "default" : { + "$ref" : "#/components/responses/Error" + } + }, + "security" : [ { + "oauth2_legacy" : [ "crm.import" ] + }, { + "private_apps_legacy" : [ "crm.import" ] + } ] + } + }, + "/{importId}" : { + "get" : { + "tags" : [ "Core" ], + "summary" : "Get the information on any import", + "description" : "A complete summary of an import record, including any updates.", + "operationId" : "get-/{importId}_getById", + "parameters" : [ { + "name" : "importId", + "in" : "path", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/PublicImportResponse" + } + } + } + }, + "default" : { + "$ref" : "#/components/responses/Error" + } + }, + "security" : [ { + "oauth2_legacy" : [ "crm.import" ] + }, { + "private_apps_legacy" : [ "crm.import" ] + } ] + } + }, + "/{importId}/errors" : { + "get" : { + "tags" : [ "Public_Imports" ], + "operationId" : "get-/{importId}/errors_getErrors", + "parameters" : [ { + "name" : "after", + "in" : "query", + "description" : "The paging cursor token of the last successfully read resource will be returned as the `paging.next.after` JSON property of a paged response containing more results.", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "limit", + "in" : "query", + "description" : "The maximum number of results to display per page.", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "importId", + "in" : "path", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "integer", + "format" : "int64" + } + }, { + "name" : "includeErrorMessage", + "in" : "query", + "description" : "Set to True to receive a message explaining the error.", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "boolean" + } + }, { + "name" : "includeRowData", + "in" : "query", + "description" : "Set to True to receive the data values for the errored row.", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "boolean" + } + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/CollectionResponsePublicImportErrorForwardPaging" + } + } + } + }, + "default" : { + "$ref" : "#/components/responses/Error" + } + }, + "security" : [ { + "oauth2_legacy" : [ "crm.import" ] + }, { + "private_apps_legacy" : [ "crm.import" ] + } ] + } + }, + "/" : { + "get" : { + "tags" : [ "Core" ], + "summary" : "Get active imports", + "description" : "Returns a paged list of active imports for this account.", + "operationId" : "get-/_getPage", + "parameters" : [ { + "name" : "after", + "in" : "query", + "description" : "The paging cursor token of the last successfully read resource will be returned as the `paging.next.after` JSON property of a paged response containing more results.", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "before", + "in" : "query", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "limit", + "in" : "query", + "description" : "The maximum number of results to display per page.", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/CollectionResponsePublicImportResponse" + } + } + } + }, + "default" : { + "$ref" : "#/components/responses/Error" + } + }, + "security" : [ { + "oauth2_legacy" : [ "crm.import" ] + }, { + "private_apps_legacy" : [ "crm.import" ] + } ] + }, + "post" : { + "tags" : [ "Core" ], + "summary" : "Start a new import", + "description" : "Begins importing data from the specified file resources. This uploads the corresponding file and uses the import request object to convert rows in the files to objects.", + "operationId" : "post-/_create", + "parameters" : [ ], + "requestBody" : { + "content" : { + "multipart/form-data" : { + "schema" : { + "type" : "object", + "properties" : { + "files" : { + "type" : "string", + "format" : "binary" + }, + "importRequest" : { + "type" : "string" + } + } + } + } + } + }, + "responses" : { + "200" : { + "description" : "successful operation", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/PublicImportResponse" + } + } + } + }, + "default" : { + "$ref" : "#/components/responses/Error" + } + }, + "security" : [ { + "oauth2_legacy" : [ "crm.import" ] + }, { + "private_apps_legacy" : [ "crm.import" ] + } ] + } + } + }, + "components" : { + "schemas" : { + "CollectionResponsePublicImportResponse" : { + "required" : [ "results" ], + "type" : "object", + "properties" : { + "paging" : { + "$ref" : "#/components/schemas/Paging" + }, + "results" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PublicImportResponse" + } + } + } + }, + "Paging" : { + "type" : "object", + "properties" : { + "next" : { + "$ref" : "#/components/schemas/NextPage" + }, + "prev" : { + "$ref" : "#/components/schemas/PreviousPage" + } + } + }, + "PublicObjectListRecord" : { + "required" : [ "listId", "objectType" ], + "type" : "object", + "properties" : { + "listId" : { + "type" : "string", + "description" : "The ID of the list containing the imported objects.", + "example" : "3" + }, + "objectType" : { + "type" : "string", + "description" : "The type of object contained in the list.", + "example" : "contacts" + } + } + }, + "Error" : { + "required" : [ "category", "correlationId", "message" ], + "type" : "object", + "properties" : { + "subCategory" : { + "type" : "string", + "description" : "A specific category that contains more specific detail about the error" + }, + "context" : { + "type" : "object", + "additionalProperties" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "description" : "Context about the error condition", + "example" : { + "missingScopes" : [ "scope1", "scope2" ], + "invalidPropertyName" : [ "propertyValue" ] + } + }, + "correlationId" : { + "type" : "string", + "description" : "A unique identifier for the request. Include this value with any error reports or support tickets", + "format" : "uuid", + "example" : "aeb5f871-7f07-4993-9211-075dc63e7cbf" + }, + "links" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + }, + "description" : "A map of link names to associated URIs containing documentation about the error or recommended remediation steps", + "example" : { + "knowledge-base" : "https://www.hubspot.com/products/service/knowledge-base" + } + }, + "message" : { + "type" : "string", + "description" : "A human readable message describing the error along with remediation steps where appropriate", + "example" : "Invalid input (details will vary based on the error)" + }, + "category" : { + "type" : "string", + "description" : "The error category", + "example" : "VALIDATION_ERROR" + }, + "errors" : { + "type" : "array", + "description" : "further information about the error", + "items" : { + "$ref" : "#/components/schemas/ErrorDetail" + } + } + }, + "example" : { + "message" : "Invalid input (details will vary based on the error)", + "correlationId" : "aeb5f871-7f07-4993-9211-075dc63e7cbf", + "category" : "VALIDATION_ERROR", + "links" : { + "knowledge-base" : "https://www.hubspot.com/products/service/knowledge-base" + } + } + }, + "PropertyValue" : { + "required" : [ "dataSensitivity", "isEncrypted", "isLargeValue", "name", "persistenceTimestamp", "requestId", "selectedByUser", "selectedByUserTimestamp", "source", "sourceId", "sourceLabel", "sourceMetadata", "sourceVid", "timestamp", "unit", "updatedByUserId", "useTimestampAsPersistenceTimestamp", "value" ], + "type" : "object", + "properties" : { + "sourceId" : { + "type" : "string" + }, + "selectedByUser" : { + "type" : "boolean" + }, + "sourceLabel" : { + "type" : "string" + }, + "source" : { + "type" : "string", + "enum" : [ "UNKNOWN", "IMPORT", "API", "FORM", "ANALYTICS", "MIGRATION", "SALESFORCE", "INTEGRATION", "CONTACTS_WEB", "WAL_INCREMENTAL", "TASK", "EMAIL", "WORKFLOWS", "CALCULATED", "SOCIAL", "BATCH_UPDATE", "SIGNALS", "BIDEN", "DEFAULT", "COMPANIES", "DEALS", "ASSISTS", "PRESENTATIONS", "TALLY", "SIDEKICK", "CRM_UI", "MERGE_CONTACTS", "PORTAL_USER_ASSOCIATOR", "INTEGRATIONS_PLATFORM", "BCC_TO_CRM", "FORWARD_TO_CRM", "ENGAGEMENTS", "SALES", "HEISENBERG", "LEADIN", "GMAIL_INTEGRATION", "ACADEMY", "SALES_MESSAGES", "AVATARS_SERVICE", "MERGE_COMPANIES", "SEQUENCES", "COMPANY_FAMILIES", "MOBILE_IOS", "MOBILE_ANDROID", "CONTACTS", "ASSOCIATIONS", "EXTENSION", "SUCCESS", "BOT", "INTEGRATIONS_SYNC", "AUTOMATION_PLATFORM", "CONVERSATIONS", "EMAIL_INTEGRATION", "CONTENT_MEMBERSHIP", "QUOTES", "BET_ASSIGNMENT", "QUOTAS", "BET_CRM_CONNECTOR", "MEETINGS", "MERGE_OBJECTS", "RECYCLING_BIN", "ADS", "AI_GROUP", "COMMUNICATOR", "SETTINGS", "PROPERTY_SETTINGS", "PIPELINE_SETTINGS", "COMPANY_INSIGHTS", "BEHAVIORAL_EVENTS", "PAYMENTS", "GOALS", "PORTAL_OBJECT_SYNC", "APPROVALS", "FILE_MANAGER", "MARKETPLACE", "INTERNAL_PROCESSING", "FORECASTING", "SLACK_INTEGRATION", "CRM_UI_BULK_ACTION", "WORKFLOW_CONTACT_DELETE_ACTION", "ACCEPTANCE_TEST", "PLAYBOOKS", "CHATSPOT", "FLYWHEEL_PRODUCT_DATA_SYNC", "HELP_DESK", "BILLING", "DATA_ENRICHMENT", "AUTOMATION_JOURNEY", "MICROAPPS", "INTENT", "PROSPECTING_AGENT" ] + }, + "updatedByUserId" : { + "type" : "integer", + "format" : "int32" + }, + "persistenceTimestamp" : { + "type" : "integer", + "format" : "int64" + }, + "sourceMetadata" : { + "type" : "string" + }, + "dataSensitivity" : { + "type" : "string", + "enum" : [ "none", "standard", "high" ] + }, + "sourceVid" : { + "type" : "array", + "items" : { + "type" : "integer", + "format" : "int64" + } + }, + "unit" : { + "type" : "string" + }, + "requestId" : { + "type" : "string" + }, + "isEncrypted" : { + "type" : "boolean" + }, + "name" : { + "type" : "string" + }, + "useTimestampAsPersistenceTimestamp" : { + "type" : "boolean" + }, + "value" : { + "type" : "string" + }, + "selectedByUserTimestamp" : { + "type" : "integer", + "format" : "int64" + }, + "timestamp" : { + "type" : "integer", + "format" : "int64" + }, + "isLargeValue" : { + "type" : "boolean" + } + } + }, + "ImportRowCore" : { + "required" : [ "containsEncryptedProperties", "fileId", "lineNumber", "rowData" ], + "type" : "object", + "properties" : { + "rowData" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "containsEncryptedProperties" : { + "type" : "boolean" + }, + "lineNumber" : { + "type" : "integer", + "format" : "int32" + }, + "pageName" : { + "type" : "string" + }, + "fileId" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "ImportTemplate" : { + "required" : [ "templateId", "templateType" ], + "type" : "object", + "properties" : { + "templateType" : { + "type" : "string", + "enum" : [ "admin_defined", "previous_import", "user_file" ] + }, + "templateId" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "ActionResponse" : { + "required" : [ "completedAt", "startedAt", "status" ], + "type" : "object", + "properties" : { + "completedAt" : { + "type" : "string", + "format" : "datetime" + }, + "requestedAt" : { + "type" : "string", + "format" : "datetime" + }, + "startedAt" : { + "type" : "string", + "format" : "datetime" + }, + "links" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "status" : { + "type" : "string", + "enum" : [ "PENDING", "PROCESSING", "CANCELED", "COMPLETE" ] + } + } + }, + "ErrorDetail" : { + "required" : [ "message" ], + "type" : "object", + "properties" : { + "subCategory" : { + "type" : "string", + "description" : "A specific category that contains more specific detail about the error" + }, + "code" : { + "type" : "string", + "description" : "The status code associated with the error detail" + }, + "in" : { + "type" : "string", + "description" : "The name of the field or parameter in which the error was found." + }, + "context" : { + "type" : "object", + "additionalProperties" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "description" : "Context about the error condition", + "example" : { + "missingScopes" : [ "scope1", "scope2" ] + } + }, + "message" : { + "type" : "string", + "description" : "A human readable message describing the error along with remediation steps where appropriate" + } + } + }, + "ForwardPaging" : { + "type" : "object", + "properties" : { + "next" : { + "$ref" : "#/components/schemas/NextPage" + } + } + }, + "PublicImportError" : { + "required" : [ "createdAt", "errorType", "id", "sourceData" ], + "type" : "object", + "properties" : { + "createdAt" : { + "type" : "integer", + "format" : "int32" + }, + "extraContext" : { + "type" : "string" + }, + "objectTypeId" : { + "type" : "string" + }, + "errorType" : { + "type" : "string", + "enum" : [ "INCORRECT_NUMBER_OF_COLUMNS", "INVALID_OBJECT_ID", "INVALID_ASSOCIATION_IDENTIFIER", "NO_OBJECT_ID_FROM_ASSOCIATION_IDENTIFIER", "MULTIPLE_COMPANIES_WITH_THIS_DOMAIN", "PROPERTY_DEFINITION_NOT_FOUND", "PROPERTY_VALUE_NOT_FOUND", "COULD_NOT_FIND_OWNER", "MULTIPLE_OWNERS_FOUND", "COULD_NOT_FIND_BUSINESS_UNIT", "COULD_NOT_PARSE_NUMBER", "COULD_NOT_PARSE_DATE", "COULD_NOT_PARSE_TERM", "OUTSIDE_VALID_TIME_RANGE", "OUTSIDE_VALID_TERM_RANGE", "COULD_NOT_PARSE_ROW", "INVALID_ENUMERATION_OPTION", "AMBIGUOUS_ENUMERATION_OPTION", "FAILED_VALIDATION", "FAILED_TO_CREATE_ASSOCIATION", "ASSOCIATION_LIMIT_EXCEEDED", "FILE_NOT_FOUND", "INVALID_COLUMN_CONFIGURATION", "INVALID_FILE_TYPE", "INVALID_SPREADSHEET", "INVALID_SHEET_COUNT", "FAILED_TO_PROCESS_OBJECT_WITH_EMPTY_PROPERTY_VALUES", "UNKNOWN_BAD_REQUEST", "GDPR_BLACKLISTED_EMAIL", "DUPLICATE_ASSOCIATION_ID", "LIMIT_EXCEEDED", "PORTAL_WIDE_CUSTOM_OBJECT_LIMIT_EXCEEDED", "INVALID_ALTERNATE_ID", "INVALID_EMAIL", "SECONDARY_EMAIL_WRITE_FAILURE", "INVALID_DOMAIN", "DUPLICATE_ROW_CONTENT", "INVALID_NUMBER_SIZE", "UNKNOWN_ERROR", "FAILED_TO_OPT_OUT_CONTACT", "INVALID_REQUIRED_PROPERTY", "MISSING_REQUIRED_PROPERTY", "DUPLICATE_ALTERNATE_ID", "DUPLICATE_OBJECT_ID", "DUPLICATE_UNIQUE_PROPERTY_VALUE", "UNKNOWN_ASSOCIATION_RECORD_ID", "INVALID_RECORD_ID", "DUPLICATE_RECORD_ID", "INVALID_CUSTOM_PROPERTY_VALIDATION", "CREATE_ONLY_IMPORT", "UPDATE_ONLY_IMPORT", "COLUMN_TOO_LARGE", "ROW_DATA_TOO_LARGE", "MISSING_EVENT_TIMESTAMP", "INVALID_EVENT_TIMESTAMP", "INVALID_EVENT", "DUPLICATE_EVENT", "MISSING_EVENT_DEFINITION", "INVALID_ASSOCIATION_KEY", "ASSOCIATION_RECORD_NOT_FOUND", "MISSING_OBJECT_DEFINITION", "ASSOCIATION_LABEL_NOT_FOUND", "MANY_ERRORS_IN_ROW" ] + }, + "invalidPropertyValue" : { + "$ref" : "#/components/schemas/PropertyValue" + }, + "errorMessage" : { + "type" : "string" + }, + "knownColumnNumber" : { + "type" : "integer", + "format" : "int32" + }, + "invalidValueToDisplay" : { + "type" : "string" + }, + "id" : { + "type" : "string" + }, + "sourceData" : { + "$ref" : "#/components/schemas/ImportRowCore" + }, + "objectType" : { + "type" : "string", + "enum" : [ "CONTACT", "COMPANY", "DEAL", "ENGAGEMENT", "TICKET", "OWNER", "PRODUCT", "LINE_ITEM", "BET_DELIVERABLE_SERVICE", "CONTENT", "CONVERSATION", "BET_ALERT", "PORTAL", "QUOTE", "FORM_SUBMISSION_INBOUNDDB", "QUOTA", "UNSUBSCRIBE", "COMMUNICATION", "FEEDBACK_SUBMISSION", "ATTRIBUTION", "SALESFORCE_SYNC_ERROR", "RESTORABLE_CRM_OBJECT", "HUB", "LANDING_PAGE", "PRODUCT_OR_FOLDER", "TASK", "FORM", "MARKETING_EMAIL", "AD_ACCOUNT", "AD_CAMPAIGN", "AD_GROUP", "AD", "KEYWORD", "CAMPAIGN", "SOCIAL_CHANNEL", "SOCIAL_POST", "SITE_PAGE", "BLOG_POST", "IMPORT", "EXPORT", "CTA", "TASK_TEMPLATE", "AUTOMATION_PLATFORM_FLOW", "OBJECT_LIST", "NOTE", "MEETING_EVENT", "CALL", "EMAIL", "PUBLISHING_TASK", "CONVERSATION_SESSION", "CONTACT_CREATE_ATTRIBUTION", "INVOICE", "MARKETING_EVENT", "CONVERSATION_INBOX", "CHATFLOW", "MEDIA_BRIDGE", "SEQUENCE", "SEQUENCE_STEP", "FORECAST", "SNIPPET", "TEMPLATE", "DEAL_CREATE_ATTRIBUTION", "QUOTE_TEMPLATE", "QUOTE_MODULE", "QUOTE_MODULE_FIELD", "QUOTE_FIELD", "SEQUENCE_ENROLLMENT", "SUBSCRIPTION", "ACCEPTANCE_TEST", "SOCIAL_BROADCAST", "DEAL_SPLIT", "DEAL_REGISTRATION", "GOAL_TARGET", "GOAL_TARGET_GROUP", "PORTAL_OBJECT_SYNC_MESSAGE", "FILE_MANAGER_FILE", "FILE_MANAGER_FOLDER", "SEQUENCE_STEP_ENROLLMENT", "APPROVAL", "APPROVAL_STEP", "CTA_VARIANT", "SALES_DOCUMENT", "DISCOUNT", "FEE", "TAX", "MARKETING_CALENDAR", "PERMISSIONS_TESTING", "PRIVACY_SCANNER_COOKIE", "DATA_SYNC_STATE", "WEB_INTERACTIVE", "PLAYBOOK", "FOLDER", "PLAYBOOK_QUESTION", "PLAYBOOK_SUBMISSION", "PLAYBOOK_SUBMISSION_ANSWER", "COMMERCE_PAYMENT", "GSC_PROPERTY", "SOX_PROTECTED_DUMMY_TYPE", "BLOG_LISTING_PAGE", "QUARANTINED_SUBMISSION", "PAYMENT_SCHEDULE", "PAYMENT_SCHEDULE_INSTALLMENT", "MARKETING_CAMPAIGN_UTM", "DISCOUNT_TEMPLATE", "DISCOUNT_CODE", "FEEDBACK_SURVEY", "CMS_URL", "SALES_TASK", "SALES_WORKLOAD", "USER", "POSTAL_MAIL", "SCHEMAS_BACKEND_TEST", "PAYMENT_LINK", "SUBMISSION_TAG", "CAMPAIGN_STEP", "SCHEDULING_PAGE", "SOX_PROTECTED_TEST_TYPE", "ORDER", "MARKETING_SMS", "PARTNER_ACCOUNT", "CAMPAIGN_TEMPLATE", "CAMPAIGN_TEMPLATE_STEP", "PLAYLIST", "CLIP", "CAMPAIGN_BUDGET_ITEM", "CAMPAIGN_SPEND_ITEM", "MIC", "CONTENT_AUDIT", "CONTENT_AUDIT_PAGE", "PLAYLIST_FOLDER", "LEAD", "ABANDONED_CART", "EXTERNAL_WEB_URL", "VIEW", "VIEW_BLOCK", "ROSTER", "CART", "AUTOMATION_PLATFORM_FLOW_ACTION", "SOCIAL_PROFILE", "PARTNER_CLIENT", "ROSTER_MEMBER", "MARKETING_EVENT_ATTENDANCE", "ALL_PAGES", "AI_FORECAST", "CRM_PIPELINES_DUMMY_TYPE", "KNOWLEDGE_ARTICLE", "PROPERTY_INFO", "DATA_PRIVACY_CONSENT", "GOAL_TEMPLATE", "SCORE_CONFIGURATION", "AUDIENCE", "PARTNER_CLIENT_REVENUE", "AUTOMATION_JOURNEY", "COMBO_EVENT_CONFIGURATION", "CRM_OBJECTS_DUMMY_TYPE", "CASE_STUDY", "SERVICE", "PODCAST_EPISODE", "PARTNER_SERVICE", "PROSPECTING_AGENT_CONTACT_ASSIGNMENT", "UNKNOWN" ] + }, + "invalidValue" : { + "type" : "string" + } + } + }, + "PreviousPage" : { + "required" : [ "before" ], + "type" : "object", + "properties" : { + "before" : { + "type" : "string" + }, + "link" : { + "type" : "string" + } + } + }, + "PublicImportMetadata" : { + "required" : [ "counters", "fileIds", "objectLists" ], + "type" : "object", + "properties" : { + "counters" : { + "type" : "object", + "additionalProperties" : { + "type" : "integer", + "format" : "int32" + }, + "description" : "Summarized outcomes of each row a developer attempted to import into HubSpot.", + "example" : { + "TOTAL_ROWS" : 2, + "CREATED_OBJECTS" : 1, + "UPDATED_OBJECTS" : 1, + "UNIQUE_OBJECTS_WRITTEN" : 2, + "PROPERTY_VALUES_EMITTED" : 2 + } + }, + "fileIds" : { + "type" : "array", + "description" : "The IDs of files uploaded in the File Manager API.", + "example" : [ "3579849" ], + "items" : { + "type" : "string" + } + }, + "objectLists" : { + "type" : "array", + "description" : "The lists containing the imported objects.", + "example" : [ { + "listId" : "3", + "objectType" : "contacts" + } ], + "items" : { + "$ref" : "#/components/schemas/PublicObjectListRecord" + } + } + } + }, + "CollectionResponsePublicImportErrorForwardPaging" : { + "required" : [ "results" ], + "type" : "object", + "properties" : { + "paging" : { + "$ref" : "#/components/schemas/ForwardPaging" + }, + "results" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PublicImportError" + } + } + } + }, + "PublicImportResponse" : { + "required" : [ "createdAt", "id", "mappedObjectTypeIds", "metadata", "optOutImport", "state", "updatedAt" ], + "type" : "object", + "properties" : { + "importTemplate" : { + "$ref" : "#/components/schemas/ImportTemplate" + }, + "createdAt" : { + "type" : "string", + "format" : "datetime", + "example" : "2020-01-14T22:45:00.153+00:00" + }, + "metadata" : { + "$ref" : "#/components/schemas/PublicImportMetadata" + }, + "importRequestJson" : { + "type" : "object", + "properties" : { } + }, + "importSource" : { + "type" : "string", + "enum" : [ "API", "CRM_UI", "IMPORT", "MOBILE_ANDROID", "MOBILE_IOS", "SALESFORCE" ] + }, + "importName" : { + "type" : "string" + }, + "state" : { + "type" : "string", + "description" : "The status of the import.", + "example" : "DONE", + "enum" : [ "STARTED", "PROCESSING", "DONE", "FAILED", "CANCELED", "DEFERRED", "REVERTED" ] + }, + "id" : { + "type" : "string", + "example" : "1471" + }, + "optOutImport" : { + "type" : "boolean", + "description" : "Whether or not the import is a list of people disqualified from receiving emails.", + "example" : false + }, + "mappedObjectTypeIds" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "updatedAt" : { + "type" : "string", + "format" : "datetime", + "example" : "2020-01-14T22:45:00.153+00:00" + } + } + }, + "NextPage" : { + "required" : [ "after" ], + "type" : "object", + "properties" : { + "link" : { + "type" : "string" + }, + "after" : { + "type" : "string" + } + } + } + }, + "responses" : { + "Error" : { + "description" : "An error occurred.", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/Error" + } + } + } + } + }, + "securitySchemes" : { + "oauth2_legacy" : { + "type" : "oauth2", + "flows" : { + "authorizationCode" : { + "authorizationUrl" : "https://app.hubspot.com/oauth/authorize", + "tokenUrl" : "https://api.hubapi.com/oauth/v1/token", + "scopes" : { + "crm.import" : "Create or modify all your CRM data through bulk imports" + } + } + } + }, + "private_apps_legacy" : { + "type" : "apiKey", + "name" : "private-app-legacy", + "in" : "header" + } + } + }, + "x-hubspot-available-client-libraries" : [ "PHP", "Node", "Ruby", "Python" ], + "x-hubspot-product-tier-requirements" : { + "marketing" : "FREE", + "sales" : "FREE", + "service" : "FREE", + "cms" : "FREE" + }, + "x-hubspot-documentation-banner" : "NONE" + } diff --git a/docs/spec/sanitations.md b/docs/spec/sanitations.md index daf9522..2d7b943 100644 --- a/docs/spec/sanitations.md +++ b/docs/spec/sanitations.md @@ -1,6 +1,6 @@ -_Author_: \ -_Created_: \ -_Updated_: \ +_Author_: @charanamanawathilake \ +_Created_: 2024/12/18 \ +_Updated_: 2025/01/07 \ _Edition_: Swan Lake # Sanitation for OpenAPI specification @@ -9,16 +9,25 @@ This document records the sanitation done on top of the official OpenAPI specifi The OpenAPI specification is obtained from (TODO: Add source link). These changes are done in order to improve the overall usability, and as workarounds for some known language limitations. -[//]: # (TODO: Add sanitation details) -1. -2. -3. +1. **Change the `url` property of the `servers` object**: + - **Original**: `https://api.hubapi.com` + - **Updated**: `https://api.hubapi.com/crm/v3/imports` + - **Reason**: This change is made to ensure that all API paths are relative to the URL (`/crm/v3/imports`), which improves the consistency and usability of the APIs. + + +2. **Update API Paths**: + - **Original**: Paths shared a common segment across all resource endpoints. + - **Updated**: Common paths segment is removed from the resource endpoints, as it is now included in the base URL. For example: + - **Original**: `/crm/v3/imports` + - **Updated**: `/` + - **Reason**: This modification simplifies the API paths, making them shorter and more readable. It also centralizes the versioning to the base URL, which is a common best practice. + +3. 'date-time' was changed to 'datetime' ## OpenAPI cli command The following command was used to generate the Ballerina client from the OpenAPI specification. The command should be executed from the repository root directory. ```bash -# TODO: Add OpenAPI CLI command used to generate the client +bal openapi -i docs/spec/openapi.json --mode client --license docs/license.txt -o ballerina/ --nullable ``` -Note: The license year is hardcoded to 2024, change if necessary. diff --git a/examples/README.md b/examples/README.md index abeb379..2433d1d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,16 +1,23 @@ # Examples -The `ballerinax/hubspot.crm.import` connector provides practical examples illustrating usage in various scenarios. +The `hubspot.crm.import` connector provides practical examples illustrating usage in various scenarios. Explore these [examples](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/examples), covering use cases like creating an import, fetching the status and cancelling an import. + +1. [Creating a contact import](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/examples/handle-import) - Integrate hubspot.crm.import API to create and manage a contact data import -[//]: # (TODO: Add examples) -1. -2. ## Prerequisites -[//]: # (TODO: Add prerequisites) +1. Generate Credentials to authenticate the connector as described in the [Setup Guide](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/README.md). + +2. For each example, create a `Config.toml` file the related configuration. Here's an example of how your `Config.toml` file should look: + + ```toml + clientId = "" + clientSecret = "" + refreshToken = "" + ``` -## Running an example +## Running an Example Execute the following commands to build an example from the source: @@ -25,21 +32,3 @@ Execute the following commands to build an example from the source: ```bash bal run ``` - -## Building the examples with the local module - -**Warning**: Due to the absence of support for reading local repositories for single Ballerina files, the Bala of the module is manually written to the central repository as a workaround. Consequently, the bash script may modify your local Ballerina repositories. - -Execute the following commands to build all the examples against the changes you have made to the module locally: - -* To build all the examples: - - ```bash - ./build.sh build - ``` - -* To run all the examples: - - ```bash - ./build.sh run - ``` diff --git a/examples/handle-import/Ballerina.toml b/examples/handle-import/Ballerina.toml new file mode 100644 index 0000000..66ea8e4 --- /dev/null +++ b/examples/handle-import/Ballerina.toml @@ -0,0 +1,10 @@ +[[dependency]] +org = "wso2" +name = "hubspot.crm.import" +version = "1.0.0" + +[package] +distribution = "2201.10.0" +org = "wso2" +name = "hubspot.crm.import" +version = "1.0.0" diff --git a/examples/handle-import/Dependencies.toml b/examples/handle-import/Dependencies.toml new file mode 100644 index 0000000..37d18a6 --- /dev/null +++ b/examples/handle-import/Dependencies.toml @@ -0,0 +1,313 @@ +# AUTO-GENERATED FILE. DO NOT MODIFY. + +# This file is auto-generated by Ballerina for managing dependency versions. +# It should not be modified by hand. + +[ballerina] +dependencies-toml-version = "2" +distribution-version = "2201.10.3" + +[[package]] +org = "ballerina" +name = "auth" +version = "2.12.0" +dependencies = [ + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"} +] + +[[package]] +org = "ballerina" +name = "cache" +version = "3.8.0" +dependencies = [ + {org = "ballerina", name = "constraint"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "task"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "constraint" +version = "1.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "crypto" +version = "2.7.2" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "file" +version = "1.10.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "os"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "http" +version = "2.12.4" +dependencies = [ + {org = "ballerina", name = "auth"}, + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "constraint"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "file"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "jwt"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.decimal"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "lang.regexp"}, + {org = "ballerina", name = "lang.runtime"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "lang.value"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "mime"}, + {org = "ballerina", name = "oauth2"}, + {org = "ballerina", name = "observe"}, + {org = "ballerina", name = "time"}, + {org = "ballerina", name = "url"} +] + +[[package]] +org = "ballerina" +name = "io" +version = "1.6.3" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] + +[[package]] +org = "ballerina" +name = "jballerina.java" +version = "0.0.0" + +[[package]] +org = "ballerina" +name = "jwt" +version = "2.13.0" +dependencies = [ + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "lang.__internal" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} +] + +[[package]] +org = "ballerina" +name = "lang.array" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.__internal"} +] + +[[package]] +org = "ballerina" +name = "lang.decimal" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.int" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.__internal"}, + {org = "ballerina", name = "lang.object"} +] + +[[package]] +org = "ballerina" +name = "lang.object" +version = "0.0.0" + +[[package]] +org = "ballerina" +name = "lang.regexp" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.runtime" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.string" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.regexp"} +] + +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "log" +version = "2.10.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"}, + {org = "ballerina", name = "observe"} +] + +[[package]] +org = "ballerina" +name = "mime" +version = "2.10.1" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "log"} +] + +[[package]] +org = "ballerina" +name = "oauth2" +version = "2.12.0" +dependencies = [ + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "time"}, + {org = "ballerina", name = "url"} +] +modules = [ + {org = "ballerina", packageName = "oauth2", moduleName = "oauth2"} +] + +[[package]] +org = "ballerina" +name = "observe" +version = "1.3.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "os" +version = "1.8.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "task" +version = "2.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "time" +version = "2.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "url" +version = "2.4.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerinai" +name = "observe" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "observe"} +] + +[[package]] +org = "ballerinax" +name = "hubspot.crm.import" +version = "1.0.0" +dependencies = [ + {org = "ballerina", name = "http"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "mime"}, + {org = "ballerina", name = "oauth2"}, + {org = "ballerina", name = "url"}, + {org = "ballerinai", name = "observe"} +] +modules = [ + {org = "ballerinax", packageName = "hubspot.crm.import", moduleName = "hubspot.crm.import"} +] + +[[package]] +org = "wso2" +name = "hubspot.crm.import" +version = "1.0.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "oauth2"}, + {org = "ballerinax", name = "hubspot.crm.import"} +] +modules = [ + {org = "wso2", packageName = "hubspot.crm.import", moduleName = "hubspot.crm.import"} +] + diff --git a/examples/handle-import/README.md b/examples/handle-import/README.md new file mode 100644 index 0000000..e6db6e5 --- /dev/null +++ b/examples/handle-import/README.md @@ -0,0 +1,27 @@ +## Customer Support tweets and Feedback Management + +This use case demonstrates how the HubSpot CRM Imports connector can be utilized to efficiently import CRM records and activities into HubSpot. The example involves a sequence of actions that leverage the HubSpot CRM Imports API to streamline the data import process, including creating an import, monitoring its progress by checking the status, and canceling the import if needed. + +## Prerequisites + +### 1. Setup Hubspot developer account + +Refer to the [Setup guide](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.import/tree/main/README.md) to obtain necessary credentials (client Id, client secret, tokens). + +### 2. Configuration + +Create a `Config.toml` file in the example's root directory and, provide your Twitter account related configurations as follows: + +```toml +clientId = "" +clientSecret = "" +refreshToken = "" +``` + +## Run the example + +Execute the following command to run the example: + +``` +bal run +``` diff --git a/examples/handle-import/main.bal b/examples/handle-import/main.bal new file mode 100644 index 0000000..692166c --- /dev/null +++ b/examples/handle-import/main.bal @@ -0,0 +1,93 @@ +// AUTO-GENERATED FILE. DO NOT MODIFY. +// This file is auto-generated by the Ballerina OpenAPI tool. + +// Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/io; +import ballerina/oauth2; +import ballerinax/hubspot.crm.'import as crmImport; + +// OAuth 2.0 Credentials +configurable string clientId = ?; +configurable string clientSecret = ?; +configurable string refreshToken = ?; + +crmImport:OAuth2RefreshTokenGrantConfig auth = { + clientId: clientId, + clientSecret: clientSecret, + refreshToken: refreshToken, + credentialBearer: oauth2:POST_BODY_BEARER +}; + +// Initialize the client +final crmImport:Client importClient = check new ({auth}); + +public function main() returns error? { + // Create an import request body + json readJson = check io:fileReadJson("resources/contact_import_request.json"); + string importRequestString = readJson.toString(); + + // Loading the data file + string filePath = "resources/contact_import_file.csv"; + byte[] bytes = check io:fileReadBytes(filePath); + + // Request body + crmImport:body requestBody = { + files: { + fileContent: bytes, + fileName: "contact_import_file.csv" + }, + importRequest: importRequestString + }; + + // Create an import + io:println("Creating an import..."); + crmImport:PublicImportResponse response = check importClient->/.post(payload = requestBody); + io:println("The import is in the state : " + response.state); + + // Check if the import is successful + if response.id == null { + return error("The import is not successful"); + } + + int responseId = check int:fromString(response.id ?: ""); + io:println("The import id is : " + responseId.toString()); + + // Fetching the status of this import + io:println("\nFetching the status of the import " + responseId.toString() + "..."); + crmImport:PublicImportResponse|error statusResponse = importClient->/[responseId].get({}); + if statusResponse is error { + io:println("Failed to fetch the status of the import"); + } else { + io:println("The import is in the state : " + statusResponse.state); + io:println("The import was created at : " + statusResponse.createdAt.toString()); + } + + // Cancel the import + io:println("\nCanceling the import " + responseId.toString() + "..."); + crmImport:ActionResponse|error cancelResponse = importClient->/[responseId]/cancel.post(); + if cancelResponse is error { + io:println("Failed to cancel the import"); + } else { + if cancelResponse.status == "COMPLETE" { + io:println("The import was cancelled successfully"); + } + else { + io:println("The import cancellation is in the state : " + cancelResponse.status); + } + } +} diff --git a/examples/handle-import/resources/contact_import_file.csv b/examples/handle-import/resources/contact_import_file.csv new file mode 100644 index 0000000..6e76c40 --- /dev/null +++ b/examples/handle-import/resources/contact_import_file.csv @@ -0,0 +1,11 @@ +First Name,Last Name,Email +John,Doe,john.doe@example.com +Jane,Smith,jane.smith@example.com +Michael,Brown,michael.brown@example.com +Emily,Davis,emily.davis@example.com +Chris,Wilson,chris.wilson@example.com +Sarah,Johnson,sarah.johnson@example.com +Daniel,Garcia,daniel.garcia@example.com +Laura,Miller,laura.miller@example.com +David,Martinez,david.martinez@example.com +Anna,Taylor,anna.taylor@example.com diff --git a/examples/handle-import/resources/contact_import_request.json b/examples/handle-import/resources/contact_import_request.json new file mode 100644 index 0000000..5a4a776 --- /dev/null +++ b/examples/handle-import/resources/contact_import_request.json @@ -0,0 +1,34 @@ +{ + "name": "First Contact Data", + "importOperations": { + "0-1": "CREATE" + }, + "dateFormat": "DAY_MONTH_YEAR", + "files": [ + { + "fileName": "contact_import_file.csv", + "fileFormat": "CSV", + "fileImportPage": { + "hasHeader": true, + "columnMappings": [ + { + "columnObjectTypeId": "0-1", + "columnName": "First Name", + "propertyName": "firstname" + }, + { + "columnObjectTypeId": "0-1", + "columnName": "Last Name", + "propertyName": "lastname" + }, + { + "columnObjectTypeId": "0-1", + "columnName": "Email", + "propertyName": "email", + "columnType": "HUBSPOT_ALTERNATE_ID" + } + ] + } + } + ] +}