From badd8f0d84397f01e5370f3c3bdc04df41af4aa8 Mon Sep 17 00:00:00 2001 From: aashikam Date: Wed, 17 Jul 2024 10:04:44 +0530 Subject: [PATCH 01/11] Add bulkv2 APIs to bulkv2 submodule --- ballerina/Ballerina.toml | 2 +- ballerina/modules/bulkv2/Module.md | 108 +++++ ballerina/modules/bulkv2/client.bal | 295 +++++++++++++ ballerina/modules/bulkv2/constants.bal | 144 ++++++ ballerina/modules/bulkv2/data_mappings.bal | 111 +++++ ballerina/modules/bulkv2/errors.bal | 42 ++ ballerina/modules/bulkv2/tests/README.md | 65 +++ .../modules/bulkv2/tests/bulk_csv_abort.bal | 89 ++++ .../modules/bulkv2/tests/bulk_csv_delete.bal | 76 ++++ .../modules/bulkv2/tests/bulk_csv_insert.bal | 289 ++++++++++++ .../modules/bulkv2/tests/bulk_csv_query.bal | 209 +++++++++ .../bulkv2/tests/resources/contacts1.csv | 3 + .../bulkv2/tests/resources/contacts2.csv | 3 + .../bulkv2/tests/resources/contacts3.csv | 3 + ballerina/modules/bulkv2/types.bal | 415 ++++++++++++++++++ ballerina/modules/bulkv2/utils.bal | 88 ++++ 16 files changed, 1941 insertions(+), 1 deletion(-) create mode 100644 ballerina/modules/bulkv2/Module.md create mode 100644 ballerina/modules/bulkv2/client.bal create mode 100644 ballerina/modules/bulkv2/constants.bal create mode 100644 ballerina/modules/bulkv2/data_mappings.bal create mode 100644 ballerina/modules/bulkv2/errors.bal create mode 100644 ballerina/modules/bulkv2/tests/README.md create mode 100644 ballerina/modules/bulkv2/tests/bulk_csv_abort.bal create mode 100644 ballerina/modules/bulkv2/tests/bulk_csv_delete.bal create mode 100644 ballerina/modules/bulkv2/tests/bulk_csv_insert.bal create mode 100644 ballerina/modules/bulkv2/tests/bulk_csv_query.bal create mode 100644 ballerina/modules/bulkv2/tests/resources/contacts1.csv create mode 100644 ballerina/modules/bulkv2/tests/resources/contacts2.csv create mode 100644 ballerina/modules/bulkv2/tests/resources/contacts3.csv create mode 100644 ballerina/modules/bulkv2/types.bal create mode 100644 ballerina/modules/bulkv2/utils.bal diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 31f5ab42..8748488e 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -3,7 +3,7 @@ distribution = "2201.8.0" org = "ballerinax" name = "salesforce" version = "8.0.2" -export = ["salesforce", "salesforce.bulk", "salesforce.soap"] +export = ["salesforce", "salesforce.bulk", "salesforce.soap","salesforce.bulkv2"] license= ["Apache-2.0"] authors = ["Ballerina"] keywords = ["Sales & CRM/Customer Relationship Management", "Cost/Freemium"] diff --git a/ballerina/modules/bulkv2/Module.md b/ballerina/modules/bulkv2/Module.md new file mode 100644 index 00000000..7d5e9c38 --- /dev/null +++ b/ballerina/modules/bulkv2/Module.md @@ -0,0 +1,108 @@ +## Overview + +Salesforce Sales Cloud is one of the leading Customer Relationship Management(CRM) software, provided by Salesforce.Inc. Salesforce enable users to efficiently manage sales and customer relationships through its APIs, robust and secure databases, and analytics services. Sales cloud provides serveral API packages to make operations on sObjects and metadata, execute queries and searches, and listen to change events through API calls using REST, SOAP, and CometD protocols. + +Ballerina Salesforce connector supports [Salesforce v59.0 REST API](https://developer.salesforce.com/docs/atlas.en-us.224.0.api_rest.meta/api_rest/intro_what_is_rest_api.htm), [Salesforce v59.0 SOAP API](https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_quickstart_intro.htm), [Salesforce v59.0 APEX REST API](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_rest_intro.htm), [Salesforce v59.0 BULK API](https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/api_asynch_introduction_bulk_api.htm), and [Salesforce v59.0 BULK V2 API](https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/bulk_api_2_0.htm). + +## Setup guide + +1. Create a Salesforce account with the REST capability. + +2. Go to Setup --> Apps --> App Manager + + Setup Side Panel + +3. Create a New Connected App. + + Create Connected Apps + + - Here we will be using https://test.salesforce.com as we are using sandbox environment. Users can use https://login.salesforce.com for normal usage. + + Create Connected Apps + +4. After the creation user can get consumer key and secret through clicking on the `Manage Consumer Details` button. + + Consumer Secrets + +5. Next step would be to get the token. + - Log in to salesforce in your preferred browser and enter the following url. + ``` + https://.salesforce.com/services/oauth2/authorize?response_type=code&client_id=&redirect_uri= + ``` + - Allow access if an alert pops up and the browser will be redirected to a Url like follows. + + ``` + https://login.salesforce.com/?code= + ``` + + - The code can be obtained after decoding the encoded code + +6. Get Access and Refresh tokens + - Following request can be sent to obtain the tokens. + + ``` + curl -X POST https://.salesforce.com/services/oauth2/token?code=&grant_type=authorization_code&client_id=&client_secret=&redirect_uri=https://test.salesforce.com/ + ``` + - Tokens can be obtained from the response. + +## Quickstart + +To use the Salesforce connector in your Ballerina application, modify the .bal file as follows: + +#### Step 1: Import connector + +Import the `ballerinax/salesforce` package into the Ballerina project. + +```ballerina +import ballerinax/salesforce; +``` + +#### Step 2: Create a new connector instance + +Create a `salesforce:ConnectionConfig` with the obtained OAuth2 tokens and initialize the connector with it. +```ballerina +salesforce:ConnectionConfig config = { + baseUrl: baseUrl, + auth: { + clientId: clientId, + clientSecret: clientSecret, + refreshToken: refreshToken, + refreshUrl: refreshUrl + } +}; + +salesforce:Client salesforce = check new (config); +``` + +#### Step 3: Invoke connector operation + +1. Now you can utilize the available operations. Note that they are in the form of remote operations. + +Following is an example on how to create a record using the connector. + + ```ballerina + salesforce:CreationResponse response = check + salesforce->create("Account", { + "Name": "IT World", + "BillingCity": "New York" + }); + + ``` + +2. Use following command to compile and run the Ballerina program. + +``` +bal run +```` + +## Examples + +The `salesforce` connector provides practical examples illustrating usage in various scenarios. Explore these examples below, covering use cases like creating sObjects, retrieving records, and executing bulk operations. + +1. [Salesforce REST API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/rest_api_usecases) - How to employ REST API of Salesforce to carryout various tasks. + +2. [Salesforce Bulk API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/bulk_api_usecases) - How to employ Bulk API of Salesforce to execute Bulk jobs. + +3. [Salesforce Bulk v2 API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/bulkv2_api_usecases) - How to employ Bulk v2 API to execute an ingest job. + +4. [Salesforce APEX REST API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/apex_rest_api_usecases) - How to employ APEX REST API to create a case in Salesforce. diff --git a/ballerina/modules/bulkv2/client.bal b/ballerina/modules/bulkv2/client.bal new file mode 100644 index 00000000..e8559885 --- /dev/null +++ b/ballerina/modules/bulkv2/client.bal @@ -0,0 +1,295 @@ +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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; +import ballerina/io; +import ballerina/lang.runtime; +import ballerinax/'client.config; +import ballerinax/salesforce.utils; + +# Ballerina Salesforce connector provides the capability to access Salesforce REST API. +# This connector lets you to perform operations for SObjects, query using SOQL, search using SOSL, and describe SObjects +# and organizational data. +public isolated client class Client { + + private final http:Client salesforceClient; + private map sfLocators = {}; + + # Initializes the connector. During initialization you can pass either http:BearerTokenConfig if you have a bearer + # token or http:OAuth2RefreshTokenGrantConfig if you have Oauth tokens. + # Create a Salesforce account and obtain tokens following + # [this guide](https://help.salesforce.com/articleView?id=remoteaccess_authenticate_overview.htm). + # + # + salesforceConfig - Salesforce Connector configuration + # + return - `sfdc:Error` on failure of initialization or else `()` + public isolated function init(ConnectionConfig config) returns error? { + http:Client|http:ClientError|error httpClientResult; + http:ClientConfiguration httpClientConfig = check config:constructHTTPClientConfig(config); + httpClientResult = trap new (config.baseUrl, httpClientConfig); + + if httpClientResult is http:Client { + self.salesforceClient = httpClientResult; + } else { + return error(INVALID_CLIENT_CONFIG); + } + } + + # Creates a bulkv2 ingest job. + # + # + payload - The payload for the bulk job + # + return - `BulkJob` if successful or else `error` + isolated remote function createIngestJob(BulkCreatePayload payload) returns BulkJob|error { + string path = utils:prepareUrl([API_BASE_PATH, JOBS, INGEST]); + return check self.salesforceClient->post(path, payload); + } + + # Creates a bulkv2 query job. + # + # + payload - The payload for the bulk job + # + return - `BulkJob` if successful or else `error` + isolated remote function createQueryJob(BulkCreatePayload payload) returns BulkJob|error { + string path = utils:prepareUrl([API_BASE_PATH, JOBS, QUERY]); + return check self.salesforceClient->post(path, payload); + } + + # Creates a bulkv2 query job and provide future value. + # + # + payload - The payload for the bulk job + # + return - `future` if successful else `error` + isolated remote function createQueryJobAndWait(BulkCreatePayload payload) returns future|error { + string path = utils:prepareUrl([API_BASE_PATH, JOBS, QUERY]); + http:Response response = check self.salesforceClient->post(path, payload); + if response.statusCode != 200 { + return error("Error occurred while closing the bulk job. ", httpCode = response.statusCode); + } + BulkJob bulkJob = check (check response.getJsonPayload()).fromJsonWithType(); + final string jobPath = utils:prepareUrl([API_BASE_PATH, JOBS, QUERY, bulkJob.id]); + worker A returns BulkJobInfo|error { + while true { + runtime:sleep(2); + http:Response jobStatus = check self.salesforceClient->get(jobPath); + if jobStatus.statusCode != 200 { + return error("Error occurred while checking the status of the bulk job. ", + httpCode = jobStatus.statusCode); + } else { + json responsePayload = check jobStatus.getJsonPayload(); + BulkJobInfo jobInfo = check responsePayload.cloneWithType(BulkJobInfo); + if jobInfo.state == JOB_COMPLETE || jobInfo.state == FAILED || jobInfo.state == ABORTED { + return jobInfo; + } + } + } + } + return A; + } + + # Retrieves detailed information about a job. + # + # + bulkJobId - Id of the bulk job + # + bulkOperation - The processing operation for the job + # + return - `BulkJobInfo` if successful or else `error` + isolated remote function getJobInfo(string bulkJobId, BulkOperation bulkOperation) returns BulkJobInfo|error { + string path = utils:prepareUrl([API_BASE_PATH, JOBS, bulkOperation, bulkJobId]); + return check self.salesforceClient->get(path); + }; + + # Uploads data for a job using CSV data. + # + # + bulkJobId - Id of the bulk job + # + content - CSV data to be added + # + return - `Nil` record if successful or `error` if unsuccessful + isolated remote function addBatch(string bulkJobId, string|string[][]|stream|io:ReadableByteChannel content) returns error? { + string payload = ""; + string path = utils:prepareUrl([API_BASE_PATH, JOBS, INGEST, bulkJobId, BATCHES]); + if content is io:ReadableByteChannel { + payload = check convertToString(content); + } else if content is string[][]|stream { + payload = check convertStringListToString(content); + } else { + payload = content; + } + http:Response response = check self.salesforceClient->put(path, payload, mediaType = "text/csv"); + if response.statusCode != 201 { + return error("Error occurred while adding the batch. ", httpCode = response.statusCode); + } + }; + + # Get details of all the jobs. + # + # + jobType - Type of the job + # + return - `AllJobs` record if successful or `error` if unsuccessful + isolated remote function getAllJobs(JobType? jobType = ()) returns error|AllJobs { + string path = utils:prepareUrl([API_BASE_PATH, JOBS, INGEST]) + + ((jobType is ()) ? "" : string `?jobType=${jobType}`); + return check self.salesforceClient->get(path); + } + + # Get details of all query jobs. + # + # + jobType - Type of the job + # + return - `AllJobs` if successful else `error` + isolated remote function getAllQueryJobs(JobType? jobType = ()) returns error|AllJobs { + string path = utils:prepareUrl([API_BASE_PATH, JOBS, INGEST]) + + ((jobType is ()) ? "" : string `?jobType=${jobType}`); + return check self.salesforceClient->get(path); + } + + # Get job status information. + # + # + status - Status of the job + # + bulkJobId - Id of the bulk job + # + return - `string[][]` if successful else `error` + isolated remote function getJobStatus(string bulkJobId, Status status) + returns string[][]|error { + string path = utils:prepareUrl([API_BASE_PATH, JOBS, INGEST, bulkJobId, status]); + http:Response response = check self.salesforceClient->get(path); + if response.statusCode == 200 { + string textPayload = check response.getTextPayload(); + if textPayload == "" { + return []; + } + string[][] result = check parseCsvString(textPayload); + return result; + } else { + json responsePayload = check response.getJsonPayload(); + return error("Error occurred while retrieving the bulk job status. ", + httpCode = response.statusCode, details = responsePayload); + } + + } + + # Get bulk query job results + # + # + bulkJobId - Id of the bulk job + # + maxRecords - The maximum number of records to retrieve per set of results for the query + # + return - The resulting string[][] if successful else `error` + isolated remote function getQueryResult(string bulkJobId, int? maxRecords = ()) returns string[][]|error { + + string path = ""; + string batchingParams = ""; + + if maxRecords != () { + lock { + if self.sfLocators.hasKey(bulkJobId) { + string locator = self.sfLocators.get(bulkJobId); + if locator is "null" { + return []; + } + batchingParams = string `results?maxRecords=${maxRecords}&locator=${locator}`; + } else { + batchingParams = string `results?maxRecords=${maxRecords}`; + } + } + path = utils:prepareUrl([API_BASE_PATH, JOBS, QUERY, bulkJobId, batchingParams]); + // Max records value default, we might not know when the locator comes + } else { + lock { + if self.sfLocators.hasKey(bulkJobId) { + string locator = self.sfLocators.get(bulkJobId); + if locator is "null" { + return []; + } + batchingParams = string `results?locator=${locator}`; + path = utils:prepareUrl([API_BASE_PATH, JOBS, QUERY, bulkJobId, batchingParams]); + } else { + path = utils:prepareUrl([API_BASE_PATH, JOBS, QUERY, bulkJobId, RESULT]); + } + } + } + + http:Response response = check self.salesforceClient->get(path); + if response.statusCode == 200 { + string textPayload = check response.getTextPayload(); + if textPayload == "" { + return []; + } + lock { + string|http:HeaderNotFoundError locatorValue = response.getHeader("sforce-locator"); + if locatorValue is string { + self.sfLocators[bulkJobId] = locatorValue; + } // header not found error ignored + } + string[][] result = check parseCsvString(textPayload); + return result; + } else { + json responsePayload = check response.getJsonPayload(); + return error("Error occurred while retrieving the query job results. ", + httpCode = response.statusCode, details = responsePayload); + } + + } + + # Abort the bulkv2 job. + # + # + bulkJobId - Id of the bulk job + # + bulkOperation - The processing operation for the job + # + return - `()` if successful else `error` + isolated remote function abortJob(string bulkJobId, BulkOperation bulkOperation) returns BulkJobInfo|error { + string path = utils:prepareUrl([API_BASE_PATH, JOBS, bulkOperation, bulkJobId]); + record {} payload = {"state": "Aborted"}; + return check self.salesforceClient->patch(path, payload); + } + + # Delete a bulkv2 job. + # + # + bulkJobId - Id of the bulk job + # + bulkOperation - The processing operation for the job + # + return - `()` if successful else `error` + isolated remote function deleteJob(string bulkJobId, BulkOperation bulkOperation) returns error? { + string path = utils:prepareUrl([API_BASE_PATH, JOBS, bulkOperation, bulkJobId]); + return check self.salesforceClient->delete(path); + } + + # Notifies Salesforce servers that the upload of job data is complete. + # + # + bulkJobId - Id of the bulk job + # + return - future if successful else `error` + isolated remote function closeIngestJobAndWait(string bulkJobId) returns error|future { + final string path = utils:prepareUrl([API_BASE_PATH, JOBS, INGEST, bulkJobId]); + record {} payload = {"state": "UploadComplete"}; + http:Response response = check self.salesforceClient->patch(path, payload); + if response.statusCode != 200 { + return error("Error occurred while closing the bulk job. ", httpCode = response.statusCode); + } + worker A returns BulkJobInfo|error { + while true { + runtime:sleep(2); + http:Response jobStatus = check self.salesforceClient->get(path); + if jobStatus.statusCode != 200 { + return error("Error occurred while checking the status of the bulk job. ", + httpCode = jobStatus.statusCode); + } else { + json responsePayload = check jobStatus.getJsonPayload(); + BulkJobInfo jobInfo = check responsePayload.cloneWithType(BulkJobInfo); + if jobInfo.state == JOB_COMPLETE || jobInfo.state == FAILED || jobInfo.state == ABORTED { + return jobInfo; + } + } + } + } + return A; + } + + # Notifies Salesforce servers that the upload of job data is complete. + # + # + bulkJobId - Id of the bulk job + # + return - BulkJobInfo if successful else `error` + isolated remote function closeIngestJob(string bulkJobId) returns error|BulkJobCloseInfo { + final string path = utils:prepareUrl([API_BASE_PATH, JOBS, INGEST, bulkJobId]); + record {} payload = {"state": "UploadComplete"}; + return check self.salesforceClient->patch(path, payload); + } +} diff --git a/ballerina/modules/bulkv2/constants.bal b/ballerina/modules/bulkv2/constants.bal new file mode 100644 index 00000000..1dd94fd0 --- /dev/null +++ b/ballerina/modules/bulkv2/constants.bal @@ -0,0 +1,144 @@ +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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. + +//Latest API Version +# Constant field `API_VERSION`. Holds the value for the Salesforce API version. +public const string API_VERSION = "v59.0"; + +// For URL encoding +# Constant field `ENCODING_CHARSET`. Holds the value for the encoding charset. +const string ENCODING_CHARSET = "utf-8"; + +//Salesforce endpoints +# Constant field `BASE_PATH`. Holds the value for the Salesforce base path/URL. +const string BASE_PATH = "/services/data"; + +# Constant field `API_BASE_PATH`. Holds the value for the Salesforce API base path/URL. +final string API_BASE_PATH = string `${BASE_PATH}/${API_VERSION}`; + +# Constant field `APEX_BASE_PATH`. Holds the value for the Salesforce Apex base path/URL. +final string APEX_BASE_PATH = string `/services/apexrest`; + +# Constant field `QUICK_ACTIONS`. Holds the value quickActions for quick actions resource prefix. +final string QUICK_ACTIONS = "quickActions"; + +# Constant field `ANALYTICS`. Holds the value analytics for analytics resource prefix. +const string ANALYTICS = "analytics"; + +# Constant field `ACTIONS`. Holds the value actions for actions resource prefix. +const string ACTIONS = "actions"; + +# Constant field `NAMED_LAYOUTS`. Holds the value namedlayouts for layout resource prefix. +const string NAMED_LAYOUTS = "namedLayouts"; + +# Constant field `COMPOSITE`. Holds the value composite for composite resource prefix. +const string COMPOSITE = "composite"; + +# Constant field `BATCH`. Holds the value batch for batch resource prefix. +const string BATCH = "batch"; + +# Constant field `BATCHES`. Holds the value batches for bulk resource prefix. +const string BATCHES = "batches"; + +# Constant field `JOBS`. Holds the value jobs for bulk resource prefix. +const string JOBS = "jobs"; + +# Constant field `INGEST`. Holds the value ingest for bulk resource prefix. +const string INGEST = "ingest"; + +# Constant field `INSTANCES`. Holds the value instances for instances resource prefix. +const string INSTANCES = "instances"; + +# Constant field `REPORTS`. Holds the value reports for reports resource prefix. +const string REPORTS = "reports"; + +# Constant field `SOBJECTS`. Holds the value sobjects for get sobject resource prefix. +const string SOBJECTS = "sobjects"; + +# Constant field `PASSWORD`. Holds the value sobjects for get password resource prefix. +const string PASSWORD = "password"; + +# Constant field `USER`. Holds the value sobjects for get USER resource prefix. +const string USER = "User"; + +# Constant field `DELETED` Holds the value deleted for get deleted resource prefix. +const string DELETED = "deleted"; + +# Constant field `UPDATED` Holds the value updated for get updated resource prefix. +const string UPDATED = "updated"; + +# Constant field `LIMITS`. Holds the value limits for get limits resource prefix. +const string LIMITS = "limits"; + +# Constant field `DESCRIBE`. Holds the value describe for describe resource prefix. +const string DESCRIBE = "describe"; + +# Constant field `search`. Holds the value search for SOSL search resource prefix. +const string SEARCH = "search"; + +# Constant field `PLATFORM_ACTION`. Holds the value PlatformAction for resource prefix. +const string PLATFORM_ACTION = "PlatformAction"; + +// Query param names +const string QUERY = "query"; + +// Result param names +const string RESULT = "results"; + +# Constant field `FIELDS`. Holds the value fields for resource prefix. +const string FIELDS = "fields"; + +# Constant field `q`. Holds the value q for query resource prefix. +const string Q = "q"; + +# Constant field `QUESTION_MARK`. Holds the value of "?". +const string QUESTION_MARK = "?"; + +# Constant field `EQUAL_SIGN`. Holds the value of "=". +const string EQUAL_SIGN = "="; + +# Constant field `EMPTY_STRING`. Holds the value of "". +public const string EMPTY_STRING = ""; + +# Constant field `AMPERSAND`. Holds the value of "&". +const string AMPERSAND = "&"; + +# Constant field `FORWARD_SLASH`. Holds the value of "/". +const string FORWARD_SLASH = "/"; + +# Next records URl +const NEXT_RECORDS_URL = "nextRecordsUrl"; + +const ATTRIBUTES = "attributes"; + +// SObjects +# Constant field `ACCOUNT`. Holds the value Account for account object. +const string ACCOUNT = "Account"; + +# Constant field `LEAD`. Holds the value Lead for lead object. +const string LEAD = "Lead"; + +# Constant field `CONTACT`. Holds the value Contact for contact object. +const string CONTACT = "Contact"; + +# Constant field `OPPORTUNITY`. Holds the value Opportunity for opportunity object. +const string OPPORTUNITY = "Opportunity"; + +# Constant field `PRODUCT`. Holds the value Product2 for product object. +const string PRODUCT = "Product2"; + +# Constant field `NEW_LINE`. Holds the value of "\n". +const string NEW_LINE = "\n"; diff --git a/ballerina/modules/bulkv2/data_mappings.bal b/ballerina/modules/bulkv2/data_mappings.bal new file mode 100644 index 00000000..e8271f1f --- /dev/null +++ b/ballerina/modules/bulkv2/data_mappings.bal @@ -0,0 +1,111 @@ +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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/log; + +isolated function toVersions(json payload) returns Version[]|Error { + Version[] versions = []; + json[] versionsArr = payload; + + foreach json ele in versionsArr { + Version|error ver = ele.cloneWithType(Version); + + if ver is Version { + versions[versions.length()] = ver; + } else { + string errMsg = "Error occurred while constructing Version record."; + log:printError(errMsg + " ele:" + ele.toJsonString(), 'error = ver); + return error Error(errMsg, ver); + } + } + return versions; +} + +type StringMap map; + +isolated function toMapOfStrings(json payload) returns map|Error { + map|error strMap = payload.cloneWithType(StringMap); + + if strMap is map { + return strMap; + } else { + string errMsg = "Error occurred while constructing map."; + log:printError(errMsg + " payload:" + payload.toJsonString(), 'error = strMap); + return error Error(errMsg, strMap); + } +} + +type JsonMap map; + +isolated function toMapOfLimits(json payload) returns map|Error { + map limits = {}; + map|error payloadMap = payload.cloneWithType(JsonMap); + + if payloadMap is error { + string errMsg = "Error occurred while constructing map using json payload."; + log:printError(errMsg + " payload:" + payload.toJsonString(), 'error = payloadMap); + return error Error(errMsg, payloadMap); + } else { + foreach var [key, value] in payloadMap.entries() { + Limit|error lim = value.cloneWithType(Limit); + if lim is Limit { + limits[key] = lim; + } else { + string errMsg = "Error occurred while constructing Limit record."; + log:printError(errMsg + " value:" + value.toJsonString(), 'error = lim); + return error Error(errMsg, lim); + } + } + } + return limits; +} + + +isolated function toSObjectMetaData(json payload) returns SObjectMetaData|Error { + SObjectMetaData|error res = payload.cloneWithType(SObjectMetaData); + + if res is SObjectMetaData { + return res; + } else { + string errMsg = "Error occurred while constructing SObjectMetaData record."; + log:printError(errMsg + " payload:" + payload.toJsonString(), 'error = res); + return error Error(errMsg, res); + } +} + +isolated function toOrganizationMetadata(json payload) returns OrganizationMetadata|Error { + OrganizationMetadata|error res = payload.cloneWithType(OrganizationMetadata); + + if res is OrganizationMetadata { + return res; + } else { + string errMsg = "Error occurred while constructing OrganizationMetadata record."; + log:printError(errMsg + " payload:" + payload.toJsonString(), 'error = res); + return error Error(errMsg, res); + } +} + +isolated function toSObjectBasicInfo(json payload) returns SObjectBasicInfo|Error { + SObjectBasicInfo|error res = payload.cloneWithType(SObjectBasicInfo); + + if res is SObjectBasicInfo { + return res; + } else { + string errMsg = "Error occurred while constructing SObjectBasicInfo record."; + log:printError(errMsg + " payload:" + payload.toJsonString(), 'error = res); + return error Error(errMsg, res); + } +} diff --git a/ballerina/modules/bulkv2/errors.bal b/ballerina/modules/bulkv2/errors.bal new file mode 100644 index 00000000..d3005413 --- /dev/null +++ b/ballerina/modules/bulkv2/errors.bal @@ -0,0 +1,42 @@ +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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. + +# Salesforce connector error. +public type Error error; + +# Additional details extracted from the Http error. +# +# + errorCode - Error code from Salesforce +# + message - Response body with extra information +# +public type ErrorDetails record { + string? errorCode?; + string? message?; +}; + +// Error constants +const string JSON_ACCESSING_ERROR_MSG = "Error occurred while accessing the JSON payload of the response."; +const string XML_ACCESSING_ERROR_MSG = "Error occurred while accessing the XML payload of the response."; +const string TEXT_ACCESSING_ERROR_MSG = "Error occurred while accessing the Text payload of the response."; +const string HTTP_CLIENT_ERROR = "Failed to establish the communication with the upstream server or a data binding failure. Refer error.cause() for more details"; +public const string HTTP_ERROR_MSG = "Error occurred while getting the HTTP response."; +const STATUS_CODE = "statusCode"; +const HEADERS = "headers"; +const BODY = "body"; + + +public const string ERR_EXTRACTING_ERROR_MSG = "Error occured while extracting errors from payload."; +public const string INVALID_CLIENT_CONFIG = "Invalid values provided for client configuration parameters."; diff --git a/ballerina/modules/bulkv2/tests/README.md b/ballerina/modules/bulkv2/tests/README.md new file mode 100644 index 00000000..ede4685d --- /dev/null +++ b/ballerina/modules/bulkv2/tests/README.md @@ -0,0 +1,65 @@ +# Testing Ballerina Salesforce module + +**Obtaining Tokens** + +1. Visit [Salesforce](https://www.salesforce.com) and create a Salesforce Account. +2. Create a connected app and obtain the following credentials: + * Base URL (Endpoint) + * Access Token + * Client ID + * Client Secret + * Refresh Token + * Refresh Token URL + +Note:- When you are setting up the connected app, select the following scopes under Selected OAuth Scopes: + +* Access and manage your data (api) +* Perform requests on your behalf at any time (refresh_token, offline_access) +* Provide access to your data via the Web (web) + +3. Provide the client ID and client secret to obtain the refresh token and access token. For more information on + obtaining OAuth2 credentials, go to + [Salesforce documentation](https://help.salesforce.com/articleView?id=remoteaccess_authenticate_overview.htm). + +**Create external ID field in Salesforce** + +Since External ID field called `My_External_Id__c` is used in the tests, follow below steps to create this external ID +field in the salesforce. + +1. Log in to your salesforce account and go to the `Setup` by clicking on the settings icon in the right side of the + menu. +2. Then in the left side panel, under Platform tools click on the `Objects and Fields` and the click on + `Object Manager`. +3. In the Object Manager page click on the `Contact` since we are going to create a external field for Contact SObject. +4. In the Contact page click on the `Fields & Relationships` and click `New` in the right hand side. +5. Then select `Text` as the Data type and click `Next`. +6. Add `My_External_Id` for "Field Label" and `255` for "Length" and click `Next`. +7. At the end click `Save` and see whether external field is added successfully by checking `Fields & Relationships` + fields. + +**Select Objects for Change Notifications** + +To receive notifications for record changes, select the custom objects and supported standard objects that you are +interested in. From Setup, enter Change Data Capture in the Quick Find box, and click Change Data Capture. Select the +SObject which you want to listen for changes + + +**Running Tests** + +1. Create a `ballerina.conf` inside project root directory and replace values inside quotes (eg: ) with + appropriate values. + ``` + EP_URL="" + ACCESS_TOKEN="" + CLIENT_ID="" + CLIENT_SECRET="" + REFRESH_TOKEN="" + REFRESH_URL="" + SF_USERNAME="" + SF_PASSWORD="" + ``` +2. Run the following command inside repo root folder. + ```bash + $ ballerina test -a --sourceroot sfdc-connector + ``` + \ No newline at end of file diff --git a/ballerina/modules/bulkv2/tests/bulk_csv_abort.bal b/ballerina/modules/bulkv2/tests/bulk_csv_abort.bal new file mode 100644 index 00000000..01b2d653 --- /dev/null +++ b/ballerina/modules/bulkv2/tests/bulk_csv_abort.bal @@ -0,0 +1,89 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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/log; +import ballerina/test; +import ballerina/os; + +// Create Salesforce client configuration by reading from environemnt. +configurable string clientId = os:getEnv("CLIENT_ID"); +configurable string clientSecret = os:getEnv("CLIENT_SECRET"); +configurable string refreshToken = os:getEnv("REFRESH_TOKEN"); +configurable string refreshUrl = os:getEnv("REFRESH_URL"); +configurable string baseUrl = os:getEnv("EP_URL"); +configurable string username = ""; +configurable string password = ""; + +string reportInstanceID = ""; + +// Using direct-token config for client configuration +ConnectionConfig sfConfigRefreshCodeFlow = { + baseUrl: baseUrl, + auth: { + clientId: clientId, + clientSecret: clientSecret, + refreshToken: refreshToken, + refreshUrl: refreshUrl + } +}; + +ConnectionConfig sfConfigPasswordFlow = { + baseUrl: baseUrl, + auth: { + password, + username, + tokenUrl: refreshUrl, + clientId: clientId, + clientSecret: clientSecret, + credentialBearer: "POST_BODY_BEARER" + } +}; + +ConnectionConfig sfConfigCredentialsFlow = { + baseUrl: baseUrl, + auth: { + clientId: clientId, + clientSecret: clientSecret, + tokenUrl: refreshUrl + } +}; + +Client baseClient = check new (sfConfigRefreshCodeFlow); + +@test:Config { + enable: true +} +function abortAndDeleteJob() returns error? { + log:printInfo("baseClient -> deleteCsv"); + //create job + BulkCreatePayload payload = { + 'object: "Contact", + contentType: "CSV", + operation: "delete", + lineEnding: "LF" + }; + BulkJob abortJob = check baseClient->createIngestJob(payload); + + log:printInfo("baseClient -> abortJob"); + BulkJobInfo|error abortJobInfo = baseClient->abortJob(abortJob.id, INGEST); + if abortJobInfo is error { + test:assertFail(msg = abortJobInfo.message()); + } + log:printInfo("baseClient -> deleteJob"); + error? deleteJobError = baseClient->deleteJob(abortJob.id, INGEST); + if deleteJobError is error { + test:assertFail(msg = deleteJobError.message()); + } +} diff --git a/ballerina/modules/bulkv2/tests/bulk_csv_delete.bal b/ballerina/modules/bulkv2/tests/bulk_csv_delete.bal new file mode 100644 index 00000000..bcaedcbb --- /dev/null +++ b/ballerina/modules/bulkv2/tests/bulk_csv_delete.bal @@ -0,0 +1,76 @@ +import ballerina/lang.runtime; +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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/log; +import ballerina/test; + +@test:AfterSuite {} +function deleteCSV() returns error? { + log:printInfo("baseClient -> deleteCsv"); + //create job + BulkCreatePayload payload = { + 'object: "Contact", + contentType: "CSV", + operation: "delete", + lineEnding: "LF" + }; + BulkJob insertJob = check baseClient->createIngestJob(payload); + + //add csv content + foreach int currentRetry in 1 ..< maxIterations + 1 { + error? response = baseClient->addBatch(insertJob.id, batchId); + if response is error { + if currentRetry == maxIterations { + log:printWarn("addBatch Operation Failed!"); + test:assertFail(msg = "Could not upload the contacts using CSV. " + response.message()); + } else { + log:printWarn("addBatch Operation Failed! Retrying..."); + runtime:sleep(delayInSecs); + } + } else { + break; + } + } + + //get job info + foreach int currentRetry in 1 ..< maxIterations + 1 { + error|BulkJobInfo jobInfo = baseClient->getJobInfo(insertJob.id, INGEST); + if jobInfo is BulkJobInfo { + test:assertTrue(jobInfo.id.length() > 0, msg = "Getting job info failed."); + break; + } else { + if currentRetry != maxIterations { + log:printWarn("getJobInfo Operation Failed! Retrying..."); + runtime:sleep(delayInSecs); + } else { + log:printWarn("getJobInfo Operation Failed! Giving up after 5 tries."); + test:assertFail(msg = jobInfo.message()); + } + } + } + + //close job + foreach int currentRetry in 1 ..< maxIterations + 1 { + future closedJob = check baseClient->closeIngestJobAndWait(insertJob.id); + BulkJobInfo|error closedJobInfo = wait closedJob; + if closedJobInfo is BulkJobInfo { + test:assertTrue(closedJobInfo.state == "JobComplete", msg = "Closing job failed."); + break; + } else { + test:assertFail(msg = closedJobInfo.message()); + } + } +} diff --git a/ballerina/modules/bulkv2/tests/bulk_csv_insert.bal b/ballerina/modules/bulkv2/tests/bulk_csv_insert.bal new file mode 100644 index 00000000..796778bf --- /dev/null +++ b/ballerina/modules/bulkv2/tests/bulk_csv_insert.bal @@ -0,0 +1,289 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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/lang.runtime; +import ballerina/log; +import ballerina/test; + +const int maxIterations = 5; +const decimal delayInSecs = 5.0; +string batchId = "id\n"; + +@test:Config { + enable: true +} +function insertCsv() returns error? { + log:printInfo("baseClient -> insertCsv"); + string contacts = "description,FirstName,LastName,Title,Phone,Email,My_External_Id__c\n" + + "Created_from_Ballerina_Sf_Bulk_API_V2,Cuthbert,Binns,Professor Level 02,0332236677,john434@gmail.com,845\n" + + "Created_from_Ballerina_Sf_Bulk_API_V2,Burbage,Shane,Professor Level 02,0332211777,peter77@gmail.com,846"; + + //create job + BulkCreatePayload payload = { + 'object: "Contact", + contentType: "CSV", + operation: "insert", + lineEnding: "LF" + }; + BulkJob insertJob = check baseClient->createIngestJob(payload); + + //add csv content + foreach int currentRetry in 1 ..< maxIterations + 1 { + error? response = baseClient->addBatch(insertJob.id, contacts); + if response is error { + if currentRetry == maxIterations { + log:printWarn("addBatch Operation Failed!"); + test:assertFail(msg = "Could not upload the contacts using CSV. " + response.message()); + } else { + log:printWarn("addBatch Operation Failed! Retrying..."); + runtime:sleep(delayInSecs); + } + } else { + break; + } + } + + //get job info + foreach int currentRetry in 1 ..< maxIterations + 1 { + error|BulkJobInfo jobInfo = baseClient->getJobInfo(insertJob.id, INGEST); + if jobInfo is BulkJobInfo { + test:assertTrue(jobInfo.id.length() > 0, msg = "Getting job info failed."); + break; + } else { + if currentRetry != maxIterations { + log:printWarn("getJobInfo Operation Failed! Retrying..."); + runtime:sleep(delayInSecs); + } else { + log:printWarn("getJobInfo Operation Failed! Giving up after 5 tries."); + test:assertFail(msg = jobInfo.message()); + } + } + } + + //close job + foreach int currentRetry in 1 ..< maxIterations + 1 { + future closedJob = check baseClient->closeIngestJobAndWait(insertJob.id); + BulkJobInfo|error closedJobInfo = wait closedJob; + if closedJobInfo is BulkJobInfo { + test:assertTrue(closedJobInfo.state == "JobComplete", msg = "Closing job failed."); + break; + } else { + test:assertFail(msg = closedJobInfo.message()); + } + } + string[][] jobstatus = check baseClient->getJobStatus(insertJob.id, "successfulResults"); + foreach string[] item in jobstatus { + batchId += item[0] + "\n"; + } +} + +@test:Config { + enable: true +} +function insertCsvFromFile() returns error? { + log:printInfo("baseClient -> insertCsvFromFile"); + string csvContactsFilePath = "tests/resources/contacts1.csv"; + + //create job + BulkCreatePayload payload = { + 'object: "Contact", + contentType: "CSV", + operation: "insert", + lineEnding: "LF" + }; + error|BulkJob insertJob = baseClient->createIngestJob(payload); + + if insertJob is BulkJob { + string[][] csvContent = check io:fileReadCsv(csvContactsFilePath); + foreach int currentRetry in 1 ..< maxIterations + 1 { + error? response = baseClient->addBatch(insertJob.id, csvContent); + if response is error { + test:assertFail(response.message()); + } else { + break; + } + } + + //get job info + foreach int currentRetry in 1 ..< maxIterations + 1 { + error|BulkJobInfo jobInfo = baseClient->getJobInfo(insertJob.id, INGEST); + if jobInfo is BulkJobInfo { + test:assertTrue(jobInfo.id.length() > 0, msg = "Getting job info failed."); + } else { + if currentRetry != maxIterations { + log:printWarn("getJobInfo Operation Failed! Retrying..."); + runtime:sleep(delayInSecs); + } else { + log:printWarn("getJobInfo Operation Failed! Giving up after 5 tries."); + test:assertFail(msg = jobInfo.message()); + } + } + } + runtime:sleep(10); + //close job + BulkJobCloseInfo closedJob = check baseClient->closeIngestJob(insertJob.id); + runtime:sleep(15); + BulkJobInfo|error closedJobInfo = baseClient->getJobInfo(insertJob.id, INGEST); + if closedJobInfo is BulkJobInfo { + test:assertTrue(closedJobInfo.state == "JobComplete", msg = "Closing job failed."); + } else { + test:assertFail(msg = closedJobInfo.message()); + } + string[][] jobstatus = check baseClient->getJobStatus(insertJob.id, "successfulResults"); + foreach string[] item in jobstatus { + batchId += item[0] + "\n"; + } + + } else { + test:assertFail(msg = insertJob.message()); + } +} + +@test:Config { + enable: true +} +function insertCsvStringArrayFromFile() returns error? { + log:printInfo("baseClient -> insertCsvStringArrayFromFile"); + + string csvContactsFilePath = "tests/resources/contacts2.csv"; + + //create job + BulkCreatePayload payload = { + 'object: "Contact", + contentType: "CSV", + operation: "insert", + lineEnding: "LF" + }; + error|BulkJob insertJob = baseClient->createIngestJob(payload); + + if insertJob is BulkJob { + io:ReadableByteChannel|io:Error rbc = io:openReadableFile(csvContactsFilePath); + if rbc is io:ReadableByteChannel { + foreach int currentRetry in 1 ..< maxIterations + 1 { + error? response = baseClient->addBatch(insertJob.id, rbc); + if response is error { + test:assertFail(response.message()); + } else { + break; + } + } + // close channel. + _ = check rbc.close(); + } else { + test:assertFail(msg = rbc.message()); + } + + //get job info + foreach int currentRetry in 1 ..< maxIterations + 1 { + error|BulkJobInfo jobInfo = baseClient->getJobInfo(insertJob.id, INGEST); + if jobInfo is BulkJobInfo { + test:assertTrue(jobInfo.id.length() > 0, msg = "Getting job info failed."); + } else { + if currentRetry != maxIterations { + log:printWarn("getJobInfo Operation Failed! Retrying..."); + runtime:sleep(delayInSecs); + } else { + log:printWarn("getJobInfo Operation Failed! Giving up after 5 tries."); + test:assertFail(msg = jobInfo.message()); + } + } + } + runtime:sleep(10); + //close job + future closedJob = check baseClient->closeIngestJobAndWait(insertJob.id); + BulkJobInfo|error closedJobInfo = wait closedJob; + if closedJobInfo is BulkJobInfo { + test:assertTrue(closedJobInfo.state == "JobComplete", msg = "Closing job failed."); + } else { + test:assertFail(msg = closedJobInfo.message()); + } + string[][] jobstatus = check baseClient->getJobStatus(insertJob.id, "successfulResults"); + foreach string[] item in jobstatus { + batchId += item[0] + "\n"; + } + + } else { + test:assertFail(msg = insertJob.message()); + } +} + +@test:Config { + enable: true +} +function insertCsvStreamFromFile() returns error? { + log:printInfo("baseClient -> insertCsvStreamFromFile"); + + string csvContactsFilePath = "tests/resources/contacts3.csv"; + + stream csvStream = check io:fileReadCsvAsStream(csvContactsFilePath); + //create job + BulkCreatePayload payload = { + 'object: "Contact", + contentType: "CSV", + operation: "insert", + lineEnding: "LF" + }; + BulkJob insertJob = check baseClient->createIngestJob(payload); + + //add csv content + foreach int currentRetry in 1 ..< maxIterations + 1 { + error? response = baseClient->addBatch(insertJob.id, csvStream); + if response is error { + if currentRetry == maxIterations { + log:printWarn("addBatch Operation Failed!"); + test:assertFail(msg = "Could not upload the contacts using CSV. " + response.message()); + } else { + log:printWarn("addBatch Operation Failed! Retrying..."); + runtime:sleep(delayInSecs); + } + } else { + break; + } + } + + //get job info + foreach int currentRetry in 1 ..< maxIterations + 1 { + error|BulkJobInfo jobInfo = baseClient->getJobInfo(insertJob.id, INGEST); + if jobInfo is BulkJobInfo { + test:assertTrue(jobInfo.id.length() > 0, msg = "Getting job info failed."); + break; + } else { + if currentRetry != maxIterations { + log:printWarn("getJobInfo Operation Failed! Retrying..."); + runtime:sleep(delayInSecs); + } else { + log:printWarn("getJobInfo Operation Failed! Giving up after 5 tries."); + test:assertFail(msg = jobInfo.message()); + } + } + } + runtime:sleep(10); + //close job + foreach int currentRetry in 1 ..< maxIterations + 1 { + future closedJob = check baseClient->closeIngestJobAndWait(insertJob.id); + BulkJobInfo|error closedJobInfo = wait closedJob; + if closedJobInfo is BulkJobInfo { + test:assertTrue(closedJobInfo.state == "JobComplete", msg = "Closing job failed."); + break; + } else { + test:assertFail(msg = closedJobInfo.message()); + } + } + string[][] jobstatus = check baseClient->getJobStatus(insertJob.id, "successfulResults"); + foreach string[] item in jobstatus { + batchId += item[0] + "\n"; + } +} diff --git a/ballerina/modules/bulkv2/tests/bulk_csv_query.bal b/ballerina/modules/bulkv2/tests/bulk_csv_query.bal new file mode 100644 index 00000000..58db85a3 --- /dev/null +++ b/ballerina/modules/bulkv2/tests/bulk_csv_query.bal @@ -0,0 +1,209 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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/log; +import ballerina/test; +import ballerina/lang.runtime; + +@test:Config { + enable: true, + dependsOn: [insertCsvFromFile, insertCsv, insertCsvStringArrayFromFile, insertCsvStreamFromFile] +} +function queryCsv() returns error? { + runtime:sleep(delayInSecs); + log:printInfo("baseClient -> queryCsv"); + string queryStr = "SELECT Id, Name FROM Contact WHERE Title='Professor Level 02'"; + + BulkCreatePayload payloadq = { + operation : "query", + query : queryStr + }; + + //create job + BulkJob queryJob = check baseClient->createQueryJob(payloadq); + + //get batch result + foreach int currentRetry in 1 ..< maxIterations + 1 { + string[][]|error batchResult = baseClient->getQueryResult(queryJob.id); + if batchResult is string[][] { + if batchResult.length() == 7 { + test:assertTrue(batchResult.length() == 7, msg = "Retrieving batch result failed."); + break; + } else { + if currentRetry != maxIterations { + log:printWarn("getBatchResult Operation Failed! Retrying..."); + runtime:sleep(delayInSecs); + } else { + log:printWarn("getBatchResult Operation Failed! Giving up after 5 tries."); + test:assertFail(msg = batchResult.toString()); + } + } + } else if batchResult is error { + if currentRetry != maxIterations { + log:printWarn("getBatchResult Operation Failed! Retrying..."); + runtime:sleep(delayInSecs); + } else { + log:printWarn("getBatchResult Operation Failed! Giving up after 5 tries."); + test:assertFail(msg = batchResult.message()); + } + } + } +} + + +@test:Config { + enable: true, + dependsOn: [insertCsvFromFile, insertCsv, insertCsvStringArrayFromFile, insertCsvStreamFromFile] +} +function queryWithLowerMaxRecordsValue() returns error? { + runtime:sleep(delayInSecs); + log:printInfo("baseClient -> queryCsv"); + string queryStr = "SELECT Id, Name FROM Contact WHERE Title='Professor Level 02'"; + + BulkCreatePayload payloadq = { + operation : "query", + query : queryStr + }; + + //create job + BulkJob queryJob = check baseClient->createQueryJob(payloadq); + int totalRecordsReceived = 0; + int totalIterationsOfGetResult = 0; + string[][]|error batchResult = []; + + //get batch result + foreach int currentRetry in 1 ..< maxIterations + 1 { + while true { + batchResult = baseClient->getQueryResult(queryJob.id, 5); + if batchResult is error || batchResult.length() == 0 { + break; + } else { + totalRecordsReceived += batchResult.length(); + totalIterationsOfGetResult += 1; + } + } + + if totalIterationsOfGetResult != 0 { + if totalRecordsReceived == 7 { + test:assertTrue(totalIterationsOfGetResult == 2, msg = "Retrieving batch result failed."); + break; + } else { + if currentRetry != maxIterations { + log:printWarn("getBatchResult Operation Failed! Retrying..."); + runtime:sleep(delayInSecs); + } else { + log:printWarn("getBatchResult Operation Failed! Giving up after 5 tries."); + } + } + } else if batchResult is error { + if currentRetry != maxIterations { + log:printWarn("getBatchResult Operation Failed! Retrying..."); + runtime:sleep(delayInSecs); + } else { + log:printWarn("getBatchResult Operation Failed! Giving up after 5 tries."); + test:assertFail(msg = batchResult.message()); + } + } + } +} + + +@test:Config { + enable: true, + dependsOn: [insertCsvFromFile, insertCsv, insertCsvStringArrayFromFile, insertCsvStreamFromFile] +} +function queryWithHigherMaxRecordsValue() returns error? { + runtime:sleep(delayInSecs); + log:printInfo("baseClient -> queryCsv"); + string queryStr = "SELECT Id, Name FROM Contact WHERE Title='Professor Level 02'"; + + BulkCreatePayload payloadq = { + operation : "query", + query : queryStr + }; + + //create job + BulkJob queryJob = check baseClient->createQueryJob(payloadq); + int totalRecordsReceived = 0; + int totalIterationsOfGetResult = 0; + string[][]|error batchResult = []; + + //get batch result + foreach int currentRetry in 1 ..< maxIterations + 1 { + while true { + batchResult = baseClient->getQueryResult(queryJob.id, 10); + if batchResult is error || batchResult.length() == 0 { + break; + } else { + totalRecordsReceived += batchResult.length(); + totalIterationsOfGetResult += 1; + } + } + + if totalIterationsOfGetResult != 0 { + if totalRecordsReceived == 7 { + test:assertTrue(totalIterationsOfGetResult == 1, msg = "Retrieving batch result failed."); + break; + } else { + if currentRetry != maxIterations { + log:printWarn("getBatchResult Operation Failed! Retrying..."); + runtime:sleep(delayInSecs); + } else { + log:printWarn("getBatchResult Operation Failed! Giving up after 5 tries."); + } + } + } else if batchResult is error { + if currentRetry != maxIterations { + log:printWarn("getBatchResult Operation Failed! Retrying..."); + runtime:sleep(delayInSecs); + } else { + log:printWarn("getBatchResult Operation Failed! Giving up after 5 tries."); + test:assertFail(msg = batchResult.message()); + } + } + } +} + +@test:Config { + enable: true, + dependsOn: [insertCsvFromFile, insertCsv, insertCsvStringArrayFromFile, insertCsvStreamFromFile] +} +function queryAndWaitCsv() returns error? { + runtime:sleep(delayInSecs); + log:printInfo("baseClient -> queryCsvWithWait"); + string queryStr = "SELECT Id, Name FROM Contact WHERE Title='Professor Level 03'"; + + BulkCreatePayload payloadq = { + operation : "query", + query : queryStr + }; + + //create job + future queryJob = check baseClient->createQueryJobAndWait(payloadq); + BulkJobInfo bulkJobInfo = check wait queryJob; + + //get batch result + string[][]|error batchResult = baseClient->getQueryResult(bulkJobInfo.id); + if batchResult is string[][] { + if batchResult.length() == 7 { + test:assertTrue(batchResult.length() == 7, msg = "Retrieving batch result failed."); + } + } else if batchResult is error { + log:printWarn("getBatchResult Operation Failed!"); + test:assertFail(msg = batchResult.message()); + } + +} diff --git a/ballerina/modules/bulkv2/tests/resources/contacts1.csv b/ballerina/modules/bulkv2/tests/resources/contacts1.csv new file mode 100644 index 00000000..533a896c --- /dev/null +++ b/ballerina/modules/bulkv2/tests/resources/contacts1.csv @@ -0,0 +1,3 @@ +description,FirstName,LastName,Title,Phone,Email +Created_from_Ballerina_Sf_Bulk_API,Albus,Dumbledore,Professor Level 02,466-883-9411,albusth@wsj.com +Created_from_Ballerina_Sf_Bulk_API,Rubeus,Hagrid,Professor Level 02,186-792-2989,hagridth@scientificamerican.com \ No newline at end of file diff --git a/ballerina/modules/bulkv2/tests/resources/contacts2.csv b/ballerina/modules/bulkv2/tests/resources/contacts2.csv new file mode 100644 index 00000000..2ab27fa4 --- /dev/null +++ b/ballerina/modules/bulkv2/tests/resources/contacts2.csv @@ -0,0 +1,3 @@ +description,FirstName,LastName,Title,Phone,Email +Created_from_Ballerina_Sf_Bulk_API,Silvanus,Kettleburn,Professor Level 01,344-776-9411,silvanus@wsj.com +Created_from_Ballerina_Sf_Bulk_API,Septima,Vector,Professor Level 02,566-889-345,vectorarithmatic@scientificamerican.com \ No newline at end of file diff --git a/ballerina/modules/bulkv2/tests/resources/contacts3.csv b/ballerina/modules/bulkv2/tests/resources/contacts3.csv new file mode 100644 index 00000000..0a2a4bc0 --- /dev/null +++ b/ballerina/modules/bulkv2/tests/resources/contacts3.csv @@ -0,0 +1,3 @@ +description,FirstName,LastName,Title,Phone,Email +Created_from_Ballerina_Sf_Bulk_API,Rolanda,Hooch,Professor Level 01,344-776-67577,hooch@wsj.com +Created_from_Ballerina_Sf_Bulk_API,Wilhelmina,Grubbly-Plank,Professor Level 02,566-656-445,plank@scientificamerican.com \ No newline at end of file diff --git a/ballerina/modules/bulkv2/types.bal b/ballerina/modules/bulkv2/types.bal new file mode 100644 index 00000000..974314cc --- /dev/null +++ b/ballerina/modules/bulkv2/types.bal @@ -0,0 +1,415 @@ +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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; +import ballerinax/'client.config; + +# Represents status of the bulk jobs +public enum Status { + SUCCESSFUL_RESULTS = "successfulResults", + FAILED_RESULTS = "failedResults" +}; + +public enum JobStateEnum { + OPEN = "Open", + UPLOAD_COMPLETE = "UploadComplete", + IN_PROGRESS = "InProgress", + JOB_COMPLETE = "JobComplete", + ABORTED = "Aborted", + FAILED = "Failed" +}; + +public enum JobType { + BIG_OBJECT_INGEST = "BigObjectIngest", + CLASSIC = "Classic", + V2_INGEST = "V2Ingest" +}; + +public enum BulkOperation { + QUERY = "query", + INGEST = "ingest" +}; + +# Operation type of the bulk job. +public enum Operation { + INSERT = "insert", + UPDATE = "update", + DELETE = "delete", + UPSERT = "upsert", + HARD_DELETE = "hardDelete", + QUERY = "query" +}; + +public enum LineEndingEnum { + LF = "LF", + CRLF = "CRLF" +}; + +public enum ColumnDelimiterEnum { + BACKQUOTE, + CARET, + COMMA, + PIPE, + SEMICOLON, + TAB +}; + +# Represents the Salesforce client configuration. +public type ConnectionConfig record {| + *config:ConnectionConfig; + # The Salesforce endpoint URL + string baseUrl; + # Configurations related to client authentication + http:BearerTokenConfig|config:OAuth2RefreshTokenGrantConfig| + config:OAuth2PasswordGrantConfig|config:OAuth2ClientCredentialsGrantConfig auth; +|}; + +# Defines the Salesforce version type. +public type Version record { + # Label of the Salesforce version + string label; + # URL of the Salesforce version + string url; + # Salesforce version number + string 'version; +}; + +# Defines the Limit type to list limits information for your org. +public type Limit record {| + # The limit total for the org + int Max; + # The total number of calls or events left for the org + int Remaining; + json...; +|}; + +# Defines the Attribute type. +# Contains the attribute information of the resultant record. +public type Attribute record {| + # Type of the resultant record + string 'type; + # URL of the resultant record + string url?; +|}; + +# Metadata for your organization and available to the logged-in user. +public type OrganizationMetadata record {| + # Encoding + string encoding; + # Maximum batch size + int maxBatchSize; + # Available SObjects + SObjectMetaData[] sobjects; + json...; +|}; + +# Metadata for an SObject, including information about each field, URLs, and child relationships. +public type SObjectMetaData record {| + # SObject name + string name; + # Is createable + boolean createable; + # Is deletable + boolean deletable; + # Is updateable + boolean updateable; + # Is queryable + boolean queryable; + # SObject label + string label; + # SObject URLs + map urls; + json...; +|}; + + +# Basic info of a SObject. +public type SObjectBasicInfo record {| + # Metadata related to the SObject + SObjectMetaData objectDescribe; + json...; +|}; + +# Represent the Attributes at SObjectBasicInfo. +public type Attributes record { + # Type of the resultant record + string 'type; + # URL of the resultant record + string url; +}; + + +# Response of object creation. +public type CreationResponse record { + # Created object ID + string id; + # Array of errors + anydata[] errors; + # Success flag + boolean success; +}; + +# Represents a Report. +public type Report record { + # Unique report ID + string id; + # Report display name + string name; + # URL that returns report data + string url; + # URL that retrieves report metadata + string describeUrl; + # Information for each instance of the report that was run asynchronously. + string instancesUrl; +}; + +# Represents an instance of a Report. +public type ReportInstance record { + # Unique ID for a report instance + string id; + # Status of the report run + string status; + # Date and time when an instance of the report run was requested + string requestDate; + # Date, time when the instance of the report run finished + string? completionDate; + # URL where results of the report run for that instance are stored + string url; + # API name of the user that created the instance + string ownerId; + # Indicates if it is queryable + boolean queryable; + # Indicates if it has detailed data + boolean hasDetailRows; +}; + +# Represents attributes of instance of an asynchronous report run. +public type AsyncReportAttributes record { + # Unique ID for an instance of a report that was run + string id; + # Unique report ID + string reportId; + # Display name of the report + string reportName; + # Status of the report run + string status; + # API name of the user that created the instance + string ownerId; + # Date and time when an instance of the report run was requested + string requestDate; + # Format of the resource + string 'type; + # Date, time when the instance of the report run finished + string? completionDate; + # Error message if the instance run failed + string? errorMessage; + # Indicates if it is queryable + boolean queryable; +}; + +# Represents attributes of instance of synchronous report run. +public type SyncReportAttributes record { + # Unique report ID + string reportId; + # Display name of the report + string reportName; + # Format of the resource + string 'type; + # Resource URL to get report metadata + string describeUrl; + # Resource URL to run a report asynchronously + string instancesUrl; +}; + +# Represents result of an asynchronous report run. +public type ReportInstanceResult record { + # Attributes for the instance of the report run + AsyncReportAttributes|SyncReportAttributes attributes; + # Indicates if all report results are returned + boolean allData; + # Collection of summary level data or both detailed and summary level data + map? factMap; + # Collection of column groupings + map? groupingsAcross; + # Collection of row groupings + map? groupingsDown; + # Information about the fields used to build the report + map? reportMetadata; + # Indicates if it has detailed data + boolean hasDetailRows; + # Information on report groupings, summary fields, and detailed data columns + map? reportExtendedMetadata; +}; + +# Represent the metadata of deleted records. +public type DeletedRecordsResult record { + # Array of deleted records + record {|string deletedDate; string id;|}[] deletedRecords; + # The earliest date covered by the results + string earliestDateAvailable; + # The latest date covered by the results + string latestDateCovered; +}; + +# Represent the metadata of updated records. +public type UpdatedRecordsResults record { + # Array of updated record IDs + string[] ids; + # The latest date covered by the results + string latestDateCovered; +}; + +# Represent the password status. +public type PasswordStatus record{ + # Indicates whether the password is expired + boolean isExpired; +}; + + +# Represent the Error response for password access. +public type ErrorResponse record { + # Error message + string message; + # Error code + string errorCode; +}; + +# Represent a quick action. +public type QuickAction record { + # Action enum or ID + string actionEnumOrId; + # Action label + string label; + # Action name + string name; + # Action type + string 'type; + # Action URLs + record{string defaultValues?; string quickAction?; string describe?; string defaultValuesTemplate?;} urls; +}; + + +# Represent a batch execution result. +public type SubRequestResult record { + # Status code of the batch execution + int statusCode; + # Result of the batch execution + json? result; +}; + +# Represent Subrequest of a batch. +public type Subrequest record {| + # Subrequest of a batch + string binaryPartName?; + # Binary part name alias + string binaryPartNameAlias?; + # Method of the subrequest + string method; + # Rich input of the subrequest + record{} richInput?; + # URL of the subrequest + string url; +|}; + +# Represent results of the batch request. +public type BatchResult record { + # Indicates whether the batch request has errors + boolean hasErrors; + # Results of the batch request + SubRequestResult[] results; +}; + + + +# Represents the bulk job creation request payload. +public type BulkCreatePayload record { + # the sObject type of the bulk job + string 'object?; + # the operation type of the bulk job + Operation operation; + # the column delimiter of the payload + ColumnDelimiterEnum columnDelimiter?; + # the content type of the payload + string contentType?; + # the line ending of the payload + LineEndingEnum lineEnding?; + # the external ID field name for upsert operations + string externalIdFieldName?; + # the SOQL query for query operations + string query?; +}; + +# Represents the bulk job creation response. +public type BulkJob record { + *BulkJobCloseInfo; + # The URL to use for uploading the CSV data for the job. + string contentUrl?; + # The line ending of the payload. + string lineEnding?; + # The column delimiter of the payload. + string columnDelimiter?; +}; + +# Represents bulk job related information. +public type BulkJobInfo record { + *BulkJob; + # The number of times that Salesforce attempted to process the job. + int retries?; + # The total time spent processing the job. + int totalProcessingTime?; + # The total time spent processing the job by API. + int apiActiveProcessingTime?; + # The total time spent to process triggers and other processes related to the job data; + int apexProcessingTime?; + # The number of records already processed by the job. + int numberRecordsProcessed?; +}; + + +# Represents bulk job related information when Closed. +public type BulkJobCloseInfo record { + # The ID of the job. + string id; + # The operation type of the job. + string operation; + # The sObject type of the job. + string 'object; + # The ID of the user who created the job. + string createdById; + # The date and time when the job was created. + string createdDate; + # The date and time when the job was finished. + string systemModstamp; + # The state of the job. + string state; + # The concurrency mode of the job. + string concurrencyMode; + # The content type of the payload. + string contentType; + # The API version. + float apiVersion; +}; + + +# Represents output for get all jobs request +public type AllJobs record { + # Indicates whether there are more records to retrieve. + boolean done; + # Array of job records. + BulkJobInfo[] records; + # URL to retrieve the next set of records. + string nextRecordsUrl; +}; diff --git a/ballerina/modules/bulkv2/utils.bal b/ballerina/modules/bulkv2/utils.bal new file mode 100644 index 00000000..6ae31cfc --- /dev/null +++ b/ballerina/modules/bulkv2/utils.bal @@ -0,0 +1,88 @@ +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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/log; +import ballerina/jballerina.java; +import ballerina/lang.'string as strings; + +isolated string csvContent = EMPTY_STRING; + +# Convert ReadableByteChannel to string. +# +# + rbc - ReadableByteChannel +# + return - converted string +isolated function convertToString(io:ReadableByteChannel rbc) returns string|error { + byte[] readContent; + string textContent = EMPTY_STRING; + while (true) { + byte[]|io:Error result = rbc.read(1000); + if result is io:EofError { + break; + } else if result is io:Error { + string errMsg = "Error occurred while reading from Readable Byte Channel."; + log:printError(errMsg, 'error = result); + return error(errMsg, result); + } else { + readContent = result; + string|error readContentStr = strings:fromBytes(readContent); + if readContentStr is string { + textContent = textContent + readContentStr; + } else { + string errMsg = "Error occurred while converting readContent byte array to string."; + log:printError(errMsg, 'error = readContentStr); + return error(errMsg, readContentStr); + } + } + } + return textContent; +} + +# Convert string[][] to string. +# +# + stringCsvInput - Multi dimentional array of strings +# + return - converted string +isolated function convertStringListToString(string[][]|stream stringCsvInput) returns string|error { + lock { + csvContent = EMPTY_STRING; + } + if stringCsvInput is string[][] { + foreach var row in stringCsvInput { + lock { + csvContent += row.reduce(isolated function(string s, string t) returns string { + return s.concat(",", t); + }, EMPTY_STRING).substring(1) + NEW_LINE; + } + } + } else { + check stringCsvInput.forEach(isolated function(string[] row) { + lock { + csvContent += row.reduce(isolated function(string s, string t) returns string { + return s.concat(",", t); + }, EMPTY_STRING).substring(1) + NEW_LINE; + + } + }); + } + lock { + return csvContent; + } +} + +isolated function parseCsvString(string stringContent) returns string[][]|error = @java:Method { + 'class: "io.ballerinax.salesforce.CsvParserUtils", + name: "parseCsvToStringArray" +} external; From 921a5b5e8b5ddae90bcbb43b56d85911d46fd5a3 Mon Sep 17 00:00:00 2001 From: aashikam Date: Wed, 17 Jul 2024 10:27:33 +0530 Subject: [PATCH 02/11] Add apex API to apex submodule --- ballerina/Ballerina.toml | 2 +- ballerina/modules/apex/Module.md | 108 ++++++ ballerina/modules/apex/client.bal | 97 ++++++ ballerina/modules/apex/constants.bal | 144 ++++++++ ballerina/modules/apex/data_mappings.bal | 111 ++++++ ballerina/modules/apex/errors.bal | 42 +++ ballerina/modules/apex/tests/README.md | 65 ++++ ballerina/modules/apex/tests/test.bal | 92 +++++ ballerina/modules/apex/types.bal | 417 +++++++++++++++++++++++ ballerina/modules/apex/utils.bal | 101 ++++++ 10 files changed, 1178 insertions(+), 1 deletion(-) create mode 100644 ballerina/modules/apex/Module.md create mode 100644 ballerina/modules/apex/client.bal create mode 100644 ballerina/modules/apex/constants.bal create mode 100644 ballerina/modules/apex/data_mappings.bal create mode 100644 ballerina/modules/apex/errors.bal create mode 100644 ballerina/modules/apex/tests/README.md create mode 100644 ballerina/modules/apex/tests/test.bal create mode 100644 ballerina/modules/apex/types.bal create mode 100644 ballerina/modules/apex/utils.bal diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 8748488e..19745435 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -3,7 +3,7 @@ distribution = "2201.8.0" org = "ballerinax" name = "salesforce" version = "8.0.2" -export = ["salesforce", "salesforce.bulk", "salesforce.soap","salesforce.bulkv2"] +export = ["salesforce", "salesforce.bulk", "salesforce.soap","salesforce.bulkv2", "salesforce.apex"] license= ["Apache-2.0"] authors = ["Ballerina"] keywords = ["Sales & CRM/Customer Relationship Management", "Cost/Freemium"] diff --git a/ballerina/modules/apex/Module.md b/ballerina/modules/apex/Module.md new file mode 100644 index 00000000..7d5e9c38 --- /dev/null +++ b/ballerina/modules/apex/Module.md @@ -0,0 +1,108 @@ +## Overview + +Salesforce Sales Cloud is one of the leading Customer Relationship Management(CRM) software, provided by Salesforce.Inc. Salesforce enable users to efficiently manage sales and customer relationships through its APIs, robust and secure databases, and analytics services. Sales cloud provides serveral API packages to make operations on sObjects and metadata, execute queries and searches, and listen to change events through API calls using REST, SOAP, and CometD protocols. + +Ballerina Salesforce connector supports [Salesforce v59.0 REST API](https://developer.salesforce.com/docs/atlas.en-us.224.0.api_rest.meta/api_rest/intro_what_is_rest_api.htm), [Salesforce v59.0 SOAP API](https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_quickstart_intro.htm), [Salesforce v59.0 APEX REST API](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_rest_intro.htm), [Salesforce v59.0 BULK API](https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/api_asynch_introduction_bulk_api.htm), and [Salesforce v59.0 BULK V2 API](https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/bulk_api_2_0.htm). + +## Setup guide + +1. Create a Salesforce account with the REST capability. + +2. Go to Setup --> Apps --> App Manager + + Setup Side Panel + +3. Create a New Connected App. + + Create Connected Apps + + - Here we will be using https://test.salesforce.com as we are using sandbox environment. Users can use https://login.salesforce.com for normal usage. + + Create Connected Apps + +4. After the creation user can get consumer key and secret through clicking on the `Manage Consumer Details` button. + + Consumer Secrets + +5. Next step would be to get the token. + - Log in to salesforce in your preferred browser and enter the following url. + ``` + https://.salesforce.com/services/oauth2/authorize?response_type=code&client_id=&redirect_uri= + ``` + - Allow access if an alert pops up and the browser will be redirected to a Url like follows. + + ``` + https://login.salesforce.com/?code= + ``` + + - The code can be obtained after decoding the encoded code + +6. Get Access and Refresh tokens + - Following request can be sent to obtain the tokens. + + ``` + curl -X POST https://.salesforce.com/services/oauth2/token?code=&grant_type=authorization_code&client_id=&client_secret=&redirect_uri=https://test.salesforce.com/ + ``` + - Tokens can be obtained from the response. + +## Quickstart + +To use the Salesforce connector in your Ballerina application, modify the .bal file as follows: + +#### Step 1: Import connector + +Import the `ballerinax/salesforce` package into the Ballerina project. + +```ballerina +import ballerinax/salesforce; +``` + +#### Step 2: Create a new connector instance + +Create a `salesforce:ConnectionConfig` with the obtained OAuth2 tokens and initialize the connector with it. +```ballerina +salesforce:ConnectionConfig config = { + baseUrl: baseUrl, + auth: { + clientId: clientId, + clientSecret: clientSecret, + refreshToken: refreshToken, + refreshUrl: refreshUrl + } +}; + +salesforce:Client salesforce = check new (config); +``` + +#### Step 3: Invoke connector operation + +1. Now you can utilize the available operations. Note that they are in the form of remote operations. + +Following is an example on how to create a record using the connector. + + ```ballerina + salesforce:CreationResponse response = check + salesforce->create("Account", { + "Name": "IT World", + "BillingCity": "New York" + }); + + ``` + +2. Use following command to compile and run the Ballerina program. + +``` +bal run +```` + +## Examples + +The `salesforce` connector provides practical examples illustrating usage in various scenarios. Explore these examples below, covering use cases like creating sObjects, retrieving records, and executing bulk operations. + +1. [Salesforce REST API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/rest_api_usecases) - How to employ REST API of Salesforce to carryout various tasks. + +2. [Salesforce Bulk API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/bulk_api_usecases) - How to employ Bulk API of Salesforce to execute Bulk jobs. + +3. [Salesforce Bulk v2 API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/bulkv2_api_usecases) - How to employ Bulk v2 API to execute an ingest job. + +4. [Salesforce APEX REST API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/apex_rest_api_usecases) - How to employ APEX REST API to create a case in Salesforce. diff --git a/ballerina/modules/apex/client.bal b/ballerina/modules/apex/client.bal new file mode 100644 index 00000000..25aa60c8 --- /dev/null +++ b/ballerina/modules/apex/client.bal @@ -0,0 +1,97 @@ +// Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 Inc. 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; +import ballerina/jballerina.java; +import ballerinax/'client.config; +import ballerinax/salesforce.utils; + +# Ballerina Salesforce connector provides the capability to access Salesforce REST API. +# This connector lets you to perform operations for SObjects, query using SOQL, search using SOSL, and describe SObjects +# and organizational data. + +public isolated client class Client { + private final http:Client salesforceClient; + private map sfLocators = {}; + + # Initializes the connector. During initialization you can pass either http:BearerTokenConfig if you have a bearer + # token or http:OAuth2RefreshTokenGrantConfig if you have Oauth tokens. + # Create a Salesforce account and obtain tokens following + # [this guide](https://help.salesforce.com/articleView?id=remoteaccess_authenticate_overview.htm). + # + # + salesforceConfig - Salesforce Connector configuration + # + return - `sfdc:Error` on failure of initialization or else `()` + public isolated function init(ConnectionConfig config) returns error? { + http:Client|http:ClientError|error httpClientResult; + http:ClientConfiguration httpClientConfig = check config:constructHTTPClientConfig(config); + httpClientResult = trap new (config.baseUrl, httpClientConfig); + + if httpClientResult is http:Client { + self.salesforceClient = httpClientResult; + } else { + return error(INVALID_CLIENT_CONFIG); + } + } + + # Access Salesforce APEX resource. + # + # + urlPath - URI path + # + methodType - HTTP method type + # + payload - Payload + # + returnType - The payload type, which is expected to be returned after data binding + # + return - `string|int|record{}` type if successful or else `error` + isolated remote function apexRestExecute(string urlPath, http:Method methodType, + record {} payload = {}, typedesc returnType = <>) + returns returnType|error = @java:Method { + 'class: "io.ballerinax.salesforce.ReadOperationExecutor", + name: "apexRestExecute" + } external; + + private isolated function processApexExecute(typedesc returnType, string urlPath, http:Method methodType, record {} payload) returns record {}|string|int|error? { + string path = utils:prepareUrl([APEX_BASE_PATH, urlPath]); + http:Response response = new; + match methodType { + "GET" => { + response = check self.salesforceClient->get(path); + } + "POST" => { + response = check self.salesforceClient->post(path, payload); + } + "DELETE" => { + response = check self.salesforceClient->delete(path); + } + "PUT" => { + response = check self.salesforceClient->put(path, payload); + } + "PATCH" => { + response = check self.salesforceClient->patch(path, payload); + } + _ => { + return error("Invalid Method"); + } + } + if response.statusCode == 200 || response.statusCode == 201 { + if response.getContentType() == "" { + return; + } + json responsePayload = check response.getJsonPayload(); + return check responsePayload.cloneWithType(returnType); + } else { + json responsePayload = check response.getJsonPayload(); + return error("Error occurred while executing the apex request. ", + httpCode = response.statusCode, details = responsePayload); + } + } +} diff --git a/ballerina/modules/apex/constants.bal b/ballerina/modules/apex/constants.bal new file mode 100644 index 00000000..a465dd5f --- /dev/null +++ b/ballerina/modules/apex/constants.bal @@ -0,0 +1,144 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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. + +//Latest API Version +# Constant field `API_VERSION`. Holds the value for the Salesforce API version. +public const string API_VERSION = "v59.0"; + +// For URL encoding +# Constant field `ENCODING_CHARSET`. Holds the value for the encoding charset. +const string ENCODING_CHARSET = "utf-8"; + +//Salesforce endpoints +# Constant field `BASE_PATH`. Holds the value for the Salesforce base path/URL. +const string BASE_PATH = "/services/data"; + +# Constant field `API_BASE_PATH`. Holds the value for the Salesforce API base path/URL. +final string API_BASE_PATH = string `${BASE_PATH}/${API_VERSION}`; + +# Constant field `APEX_BASE_PATH`. Holds the value for the Salesforce Apex base path/URL. +final string APEX_BASE_PATH = string `/services/apexrest`; + +# Constant field `QUICK_ACTIONS`. Holds the value quickActions for quick actions resource prefix. +final string QUICK_ACTIONS = "quickActions"; + +# Constant field `ANALYTICS`. Holds the value analytics for analytics resource prefix. +const string ANALYTICS = "analytics"; + +# Constant field `ACTIONS`. Holds the value actions for actions resource prefix. +const string ACTIONS = "actions"; + +# Constant field `NAMED_LAYOUTS`. Holds the value namedlayouts for layout resource prefix. +const string NAMED_LAYOUTS = "namedLayouts"; + +# Constant field `COMPOSITE`. Holds the value composite for composite resource prefix. +const string COMPOSITE = "composite"; + +# Constant field `BATCH`. Holds the value batch for batch resource prefix. +const string BATCH = "batch"; + +# Constant field `BATCHES`. Holds the value batches for bulk resource prefix. +const string BATCHES = "batches"; + +# Constant field `JOBS`. Holds the value jobs for bulk resource prefix. +const string JOBS = "jobs"; + +# Constant field `INGEST`. Holds the value ingest for bulk resource prefix. +const string INGEST = "ingest"; + +# Constant field `INSTANCES`. Holds the value instances for instances resource prefix. +const string INSTANCES = "instances"; + +# Constant field `REPORTS`. Holds the value reports for reports resource prefix. +const string REPORTS = "reports"; + +# Constant field `SOBJECTS`. Holds the value sobjects for get sobject resource prefix. +const string SOBJECTS = "sobjects"; + +# Constant field `PASSWORD`. Holds the value sobjects for get password resource prefix. +const string PASSWORD = "password"; + +# Constant field `USER`. Holds the value sobjects for get USER resource prefix. +const string USER = "User"; + +# Constant field `DELETED` Holds the value deleted for get deleted resource prefix. +const string DELETED = "deleted"; + +# Constant field `UPDATED` Holds the value updated for get updated resource prefix. +const string UPDATED = "updated"; + +# Constant field `LIMITS`. Holds the value limits for get limits resource prefix. +const string LIMITS = "limits"; + +# Constant field `DESCRIBE`. Holds the value describe for describe resource prefix. +const string DESCRIBE = "describe"; + +# Constant field `search`. Holds the value search for SOSL search resource prefix. +const string SEARCH = "search"; + +# Constant field `PLATFORM_ACTION`. Holds the value PlatformAction for resource prefix. +const string PLATFORM_ACTION = "PlatformAction"; + +// Query param names +const string QUERY = "query"; + +// Result param names +const string RESULT = "results"; + +# Constant field `FIELDS`. Holds the value fields for resource prefix. +const string FIELDS = "fields"; + +# Constant field `q`. Holds the value q for query resource prefix. +const string Q = "q"; + +# Constant field `QUESTION_MARK`. Holds the value of "?". +const string QUESTION_MARK = "?"; + +# Constant field `EQUAL_SIGN`. Holds the value of "=". +const string EQUAL_SIGN = "="; + +# Constant field `EMPTY_STRING`. Holds the value of "". +public const string EMPTY_STRING = ""; + +# Constant field `AMPERSAND`. Holds the value of "&". +const string AMPERSAND = "&"; + +# Constant field `FORWARD_SLASH`. Holds the value of "/". +const string FORWARD_SLASH = "/"; + +# Next records URl +const NEXT_RECORDS_URL = "nextRecordsUrl"; + +const ATTRIBUTES = "attributes"; + +// SObjects +# Constant field `ACCOUNT`. Holds the value Account for account object. +const string ACCOUNT = "Account"; + +# Constant field `LEAD`. Holds the value Lead for lead object. +const string LEAD = "Lead"; + +# Constant field `CONTACT`. Holds the value Contact for contact object. +const string CONTACT = "Contact"; + +# Constant field `OPPORTUNITY`. Holds the value Opportunity for opportunity object. +const string OPPORTUNITY = "Opportunity"; + +# Constant field `PRODUCT`. Holds the value Product2 for product object. +const string PRODUCT = "Product2"; + +# Constant field `NEW_LINE`. Holds the value of "\n". +const string NEW_LINE = "\n"; diff --git a/ballerina/modules/apex/data_mappings.bal b/ballerina/modules/apex/data_mappings.bal new file mode 100644 index 00000000..00aca270 --- /dev/null +++ b/ballerina/modules/apex/data_mappings.bal @@ -0,0 +1,111 @@ +// Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 Inc. 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/log; + +isolated function toVersions(json payload) returns Version[]|Error { + Version[] versions = []; + json[] versionsArr = payload; + + foreach json ele in versionsArr { + Version|error ver = ele.cloneWithType(Version); + + if ver is Version { + versions[versions.length()] = ver; + } else { + string errMsg = "Error occurred while constructing Version record."; + log:printError(errMsg + " ele:" + ele.toJsonString(), 'error = ver); + return error Error(errMsg, ver); + } + } + return versions; +} + +type StringMap map; + +isolated function toMapOfStrings(json payload) returns map|Error { + map|error strMap = payload.cloneWithType(StringMap); + + if strMap is map { + return strMap; + } else { + string errMsg = "Error occurred while constructing map."; + log:printError(errMsg + " payload:" + payload.toJsonString(), 'error = strMap); + return error Error(errMsg, strMap); + } +} + +type JsonMap map; + +isolated function toMapOfLimits(json payload) returns map|Error { + map limits = {}; + map|error payloadMap = payload.cloneWithType(JsonMap); + + if payloadMap is error { + string errMsg = "Error occurred while constructing map using json payload."; + log:printError(errMsg + " payload:" + payload.toJsonString(), 'error = payloadMap); + return error Error(errMsg, payloadMap); + } else { + foreach var [key, value] in payloadMap.entries() { + Limit|error lim = value.cloneWithType(Limit); + if lim is Limit { + limits[key] = lim; + } else { + string errMsg = "Error occurred while constructing Limit record."; + log:printError(errMsg + " value:" + value.toJsonString(), 'error = lim); + return error Error(errMsg, lim); + } + } + } + return limits; +} + + +isolated function toSObjectMetaData(json payload) returns SObjectMetaData|Error { + SObjectMetaData|error res = payload.cloneWithType(SObjectMetaData); + + if res is SObjectMetaData { + return res; + } else { + string errMsg = "Error occurred while constructing SObjectMetaData record."; + log:printError(errMsg + " payload:" + payload.toJsonString(), 'error = res); + return error Error(errMsg, res); + } +} + +isolated function toOrganizationMetadata(json payload) returns OrganizationMetadata|Error { + OrganizationMetadata|error res = payload.cloneWithType(OrganizationMetadata); + + if res is OrganizationMetadata { + return res; + } else { + string errMsg = "Error occurred while constructing OrganizationMetadata record."; + log:printError(errMsg + " payload:" + payload.toJsonString(), 'error = res); + return error Error(errMsg, res); + } +} + +isolated function toSObjectBasicInfo(json payload) returns SObjectBasicInfo|Error { + SObjectBasicInfo|error res = payload.cloneWithType(SObjectBasicInfo); + + if res is SObjectBasicInfo { + return res; + } else { + string errMsg = "Error occurred while constructing SObjectBasicInfo record."; + log:printError(errMsg + " payload:" + payload.toJsonString(), 'error = res); + return error Error(errMsg, res); + } +} diff --git a/ballerina/modules/apex/errors.bal b/ballerina/modules/apex/errors.bal new file mode 100644 index 00000000..f314a523 --- /dev/null +++ b/ballerina/modules/apex/errors.bal @@ -0,0 +1,42 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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. + +# Salesforce connector error. +public type Error error; + +# Additional details extracted from the Http error. +# +# + errorCode - Error code from Salesforce +# + message - Response body with extra information +# +public type ErrorDetails record { + string? errorCode?; + string? message?; +}; + +// Error constants +const string JSON_ACCESSING_ERROR_MSG = "Error occurred while accessing the JSON payload of the response."; +const string XML_ACCESSING_ERROR_MSG = "Error occurred while accessing the XML payload of the response."; +const string TEXT_ACCESSING_ERROR_MSG = "Error occurred while accessing the Text payload of the response."; +const string HTTP_CLIENT_ERROR = "Failed to establish the communication with the upstream server or a data binding failure. Refer error.cause() for more details"; +public const string HTTP_ERROR_MSG = "Error occurred while getting the HTTP response."; +const STATUS_CODE = "statusCode"; +const HEADERS = "headers"; +const BODY = "body"; + + +public const string ERR_EXTRACTING_ERROR_MSG = "Error occured while extracting errors from payload."; +public const string INVALID_CLIENT_CONFIG = "Invalid values provided for client configuration parameters."; diff --git a/ballerina/modules/apex/tests/README.md b/ballerina/modules/apex/tests/README.md new file mode 100644 index 00000000..ede4685d --- /dev/null +++ b/ballerina/modules/apex/tests/README.md @@ -0,0 +1,65 @@ +# Testing Ballerina Salesforce module + +**Obtaining Tokens** + +1. Visit [Salesforce](https://www.salesforce.com) and create a Salesforce Account. +2. Create a connected app and obtain the following credentials: + * Base URL (Endpoint) + * Access Token + * Client ID + * Client Secret + * Refresh Token + * Refresh Token URL + +Note:- When you are setting up the connected app, select the following scopes under Selected OAuth Scopes: + +* Access and manage your data (api) +* Perform requests on your behalf at any time (refresh_token, offline_access) +* Provide access to your data via the Web (web) + +3. Provide the client ID and client secret to obtain the refresh token and access token. For more information on + obtaining OAuth2 credentials, go to + [Salesforce documentation](https://help.salesforce.com/articleView?id=remoteaccess_authenticate_overview.htm). + +**Create external ID field in Salesforce** + +Since External ID field called `My_External_Id__c` is used in the tests, follow below steps to create this external ID +field in the salesforce. + +1. Log in to your salesforce account and go to the `Setup` by clicking on the settings icon in the right side of the + menu. +2. Then in the left side panel, under Platform tools click on the `Objects and Fields` and the click on + `Object Manager`. +3. In the Object Manager page click on the `Contact` since we are going to create a external field for Contact SObject. +4. In the Contact page click on the `Fields & Relationships` and click `New` in the right hand side. +5. Then select `Text` as the Data type and click `Next`. +6. Add `My_External_Id` for "Field Label" and `255` for "Length" and click `Next`. +7. At the end click `Save` and see whether external field is added successfully by checking `Fields & Relationships` + fields. + +**Select Objects for Change Notifications** + +To receive notifications for record changes, select the custom objects and supported standard objects that you are +interested in. From Setup, enter Change Data Capture in the Quick Find box, and click Change Data Capture. Select the +SObject which you want to listen for changes + + +**Running Tests** + +1. Create a `ballerina.conf` inside project root directory and replace values inside quotes (eg: ) with + appropriate values. + ``` + EP_URL="" + ACCESS_TOKEN="" + CLIENT_ID="" + CLIENT_SECRET="" + REFRESH_TOKEN="" + REFRESH_URL="" + SF_USERNAME="" + SF_PASSWORD="" + ``` +2. Run the following command inside repo root folder. + ```bash + $ ballerina test -a --sourceroot sfdc-connector + ``` + \ No newline at end of file diff --git a/ballerina/modules/apex/tests/test.bal b/ballerina/modules/apex/tests/test.bal new file mode 100644 index 00000000..229604d6 --- /dev/null +++ b/ballerina/modules/apex/tests/test.bal @@ -0,0 +1,92 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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/log; +import ballerina/os; +import ballerina/test; +import ballerina/lang.runtime; + +// Create Salesforce client configuration by reading from environemnt. +configurable string clientId = os:getEnv("CLIENT_ID"); +configurable string clientSecret = os:getEnv("CLIENT_SECRET"); +configurable string refreshToken = os:getEnv("REFRESH_TOKEN"); +configurable string refreshUrl = os:getEnv("REFRESH_URL"); +configurable string baseUrl = os:getEnv("EP_URL"); +configurable string username = ""; +configurable string password = ""; + +string reportInstanceID = ""; + +// Using direct-token config for client configuration +ConnectionConfig sfConfigRefreshCodeFlow = { + baseUrl: baseUrl, + auth: { + clientId: clientId, + clientSecret: clientSecret, + refreshToken: refreshToken, + refreshUrl: refreshUrl + } +}; + +ConnectionConfig sfConfigPasswordFlow = { + baseUrl: baseUrl, + auth: { + password, + username, + tokenUrl: refreshUrl, + clientId: clientId, + clientSecret: clientSecret, + credentialBearer: "POST_BODY_BEARER" + } +}; + +ConnectionConfig sfConfigCredentialsFlow = { + baseUrl: baseUrl, + auth: { + clientId: clientId, + clientSecret: clientSecret, + tokenUrl: refreshUrl + } +}; + +Client baseClient = check new (sfConfigRefreshCodeFlow); + +@test:Config { + enable: true +} +function testApex() returns error? { + log:printInfo("baseClient -> executeApex()"); + string|error caseId = baseClient->apexRestExecute("Cases", "POST", + {"subject" : "Bigfoot Sighting9!", + "status" : "New", + "origin" : "Phone", + "priority" : "Low"}); + if caseId is error { + test:assertFail(msg = caseId.message()); + } + runtime:sleep(5); + record{}|error case = baseClient->apexRestExecute(string `Cases/${caseId}`, "GET", {}); + if case is error { + test:assertFail(msg = case.message()); + } + runtime:sleep(5); + error? deleteResponse = baseClient->apexRestExecute(string `Cases/${caseId}`, "DELETE", {}); + if deleteResponse is error { + test:assertFail(msg = deleteResponse.message()); + } +} + diff --git a/ballerina/modules/apex/types.bal b/ballerina/modules/apex/types.bal new file mode 100644 index 00000000..4faba4a7 --- /dev/null +++ b/ballerina/modules/apex/types.bal @@ -0,0 +1,417 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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; +import ballerinax/'client.config; + + + +# Represents status of the bulk jobs +public enum Status { + SUCCESSFUL_RESULTS = "successfulResults", + FAILED_RESULTS = "failedResults" +}; + +public enum JobStateEnum { + OPEN = "Open", + UPLOAD_COMPLETE = "UploadComplete", + IN_PROGRESS = "InProgress", + JOB_COMPLETE = "JobComplete", + ABORTED = "Aborted", + FAILED = "Failed" +}; + +public enum JobType { + BIG_OBJECT_INGEST = "BigObjectIngest", + CLASSIC = "Classic", + V2_INGEST = "V2Ingest" +}; + +public enum BulkOperation { + QUERY = "query", + INGEST = "ingest" +}; + +# Operation type of the bulk job. +public enum Operation { + INSERT = "insert", + UPDATE = "update", + DELETE = "delete", + UPSERT = "upsert", + HARD_DELETE = "hardDelete", + QUERY = "query" +}; + +public enum LineEndingEnum { + LF = "LF", + CRLF = "CRLF" +}; + +public enum ColumnDelimiterEnum { + BACKQUOTE, + CARET, + COMMA, + PIPE, + SEMICOLON, + TAB +}; + +# Represents the Salesforce client configuration. +public type ConnectionConfig record {| + *config:ConnectionConfig; + # The Salesforce endpoint URL + string baseUrl; + # Configurations related to client authentication + http:BearerTokenConfig|config:OAuth2RefreshTokenGrantConfig| + config:OAuth2PasswordGrantConfig|config:OAuth2ClientCredentialsGrantConfig auth; +|}; + +# Defines the Salesforce version type. +public type Version record { + # Label of the Salesforce version + string label; + # URL of the Salesforce version + string url; + # Salesforce version number + string 'version; +}; + +# Defines the Limit type to list limits information for your org. +public type Limit record {| + # The limit total for the org + int Max; + # The total number of calls or events left for the org + int Remaining; + json...; +|}; + +# Defines the Attribute type. +# Contains the attribute information of the resultant record. +public type Attribute record {| + # Type of the resultant record + string 'type; + # URL of the resultant record + string url?; +|}; + +# Metadata for your organization and available to the logged-in user. +public type OrganizationMetadata record {| + # Encoding + string encoding; + # Maximum batch size + int maxBatchSize; + # Available SObjects + SObjectMetaData[] sobjects; + json...; +|}; + +# Metadata for an SObject, including information about each field, URLs, and child relationships. +public type SObjectMetaData record {| + # SObject name + string name; + # Is createable + boolean createable; + # Is deletable + boolean deletable; + # Is updateable + boolean updateable; + # Is queryable + boolean queryable; + # SObject label + string label; + # SObject URLs + map urls; + json...; +|}; + + +# Basic info of a SObject. +public type SObjectBasicInfo record {| + # Metadata related to the SObject + SObjectMetaData objectDescribe; + json...; +|}; + +# Represent the Attributes at SObjectBasicInfo. +public type Attributes record { + # Type of the resultant record + string 'type; + # URL of the resultant record + string url; +}; + + +# Response of object creation. +public type CreationResponse record { + # Created object ID + string id; + # Array of errors + anydata[] errors; + # Success flag + boolean success; +}; + +# Represents a Report. +public type Report record { + # Unique report ID + string id; + # Report display name + string name; + # URL that returns report data + string url; + # URL that retrieves report metadata + string describeUrl; + # Information for each instance of the report that was run asynchronously. + string instancesUrl; +}; + +# Represents an instance of a Report. +public type ReportInstance record { + # Unique ID for a report instance + string id; + # Status of the report run + string status; + # Date and time when an instance of the report run was requested + string requestDate; + # Date, time when the instance of the report run finished + string? completionDate; + # URL where results of the report run for that instance are stored + string url; + # API name of the user that created the instance + string ownerId; + # Indicates if it is queryable + boolean queryable; + # Indicates if it has detailed data + boolean hasDetailRows; +}; + +# Represents attributes of instance of an asynchronous report run. +public type AsyncReportAttributes record { + # Unique ID for an instance of a report that was run + string id; + # Unique report ID + string reportId; + # Display name of the report + string reportName; + # Status of the report run + string status; + # API name of the user that created the instance + string ownerId; + # Date and time when an instance of the report run was requested + string requestDate; + # Format of the resource + string 'type; + # Date, time when the instance of the report run finished + string? completionDate; + # Error message if the instance run failed + string? errorMessage; + # Indicates if it is queryable + boolean queryable; +}; + +# Represents attributes of instance of synchronous report run. +public type SyncReportAttributes record { + # Unique report ID + string reportId; + # Display name of the report + string reportName; + # Format of the resource + string 'type; + # Resource URL to get report metadata + string describeUrl; + # Resource URL to run a report asynchronously + string instancesUrl; +}; + +# Represents result of an asynchronous report run. +public type ReportInstanceResult record { + # Attributes for the instance of the report run + AsyncReportAttributes|SyncReportAttributes attributes; + # Indicates if all report results are returned + boolean allData; + # Collection of summary level data or both detailed and summary level data + map? factMap; + # Collection of column groupings + map? groupingsAcross; + # Collection of row groupings + map? groupingsDown; + # Information about the fields used to build the report + map? reportMetadata; + # Indicates if it has detailed data + boolean hasDetailRows; + # Information on report groupings, summary fields, and detailed data columns + map? reportExtendedMetadata; +}; + +# Represent the metadata of deleted records. +public type DeletedRecordsResult record { + # Array of deleted records + record {|string deletedDate; string id;|}[] deletedRecords; + # The earliest date covered by the results + string earliestDateAvailable; + # The latest date covered by the results + string latestDateCovered; +}; + +# Represent the metadata of updated records. +public type UpdatedRecordsResults record { + # Array of updated record IDs + string[] ids; + # The latest date covered by the results + string latestDateCovered; +}; + +# Represent the password status. +public type PasswordStatus record{ + # Indicates whether the password is expired + boolean isExpired; +}; + + +# Represent the Error response for password access. +public type ErrorResponse record { + # Error message + string message; + # Error code + string errorCode; +}; + +# Represent a quick action. +public type QuickAction record { + # Action enum or ID + string actionEnumOrId; + # Action label + string label; + # Action name + string name; + # Action type + string 'type; + # Action URLs + record{string defaultValues?; string quickAction?; string describe?; string defaultValuesTemplate?;} urls; +}; + + +# Represent a batch execution result. +public type SubRequestResult record { + # Status code of the batch execution + int statusCode; + # Result of the batch execution + json? result; +}; + +# Represent Subrequest of a batch. +public type Subrequest record {| + # Subrequest of a batch + string binaryPartName?; + # Binary part name alias + string binaryPartNameAlias?; + # Method of the subrequest + string method; + # Rich input of the subrequest + record{} richInput?; + # URL of the subrequest + string url; +|}; + +# Represent results of the batch request. +public type BatchResult record { + # Indicates whether the batch request has errors + boolean hasErrors; + # Results of the batch request + SubRequestResult[] results; +}; + + + +# Represents the bulk job creation request payload. +public type BulkCreatePayload record { + # the sObject type of the bulk job + string 'object?; + # the operation type of the bulk job + Operation operation; + # the column delimiter of the payload + ColumnDelimiterEnum columnDelimiter?; + # the content type of the payload + string contentType?; + # the line ending of the payload + LineEndingEnum lineEnding?; + # the external ID field name for upsert operations + string externalIdFieldName?; + # the SOQL query for query operations + string query?; +}; + +# Represents the bulk job creation response. +public type BulkJob record { + *BulkJobCloseInfo; + # The URL to use for uploading the CSV data for the job. + string contentUrl?; + # The line ending of the payload. + string lineEnding?; + # The column delimiter of the payload. + string columnDelimiter?; +}; + +# Represents bulk job related information. +public type BulkJobInfo record { + *BulkJob; + # The number of times that Salesforce attempted to process the job. + int retries?; + # The total time spent processing the job. + int totalProcessingTime?; + # The total time spent processing the job by API. + int apiActiveProcessingTime?; + # The total time spent to process triggers and other processes related to the job data; + int apexProcessingTime?; + # The number of records already processed by the job. + int numberRecordsProcessed?; +}; + + +# Represents bulk job related information when Closed. +public type BulkJobCloseInfo record { + # The ID of the job. + string id; + # The operation type of the job. + string operation; + # The sObject type of the job. + string 'object; + # The ID of the user who created the job. + string createdById; + # The date and time when the job was created. + string createdDate; + # The date and time when the job was finished. + string systemModstamp; + # The state of the job. + string state; + # The concurrency mode of the job. + string concurrencyMode; + # The content type of the payload. + string contentType; + # The API version. + float apiVersion; +}; + + +# Represents output for get all jobs request +public type AllJobs record { + # Indicates whether there are more records to retrieve. + boolean done; + # Array of job records. + BulkJobInfo[] records; + # URL to retrieve the next set of records. + string nextRecordsUrl; +}; diff --git a/ballerina/modules/apex/utils.bal b/ballerina/modules/apex/utils.bal new file mode 100644 index 00000000..370acf22 --- /dev/null +++ b/ballerina/modules/apex/utils.bal @@ -0,0 +1,101 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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/log; +import ballerina/time; +import ballerina/jballerina.java; +import ballerina/lang.'string as strings; + +isolated string csvContent = EMPTY_STRING; + +# Remove decimal places from a civil seconds value +# +# + civilTime - a time:civil record +# + return - a time:civil record with decimal places removed +# +isolated function removeDecimalPlaces(time:Civil civilTime) returns time:Civil { + time:Civil result = civilTime; + time:Seconds seconds = (result.second is ()) ? 0 : result.second; + decimal floor = decimal:floor(seconds); + result.second = floor; + return result; +} + +# Convert ReadableByteChannel to string. +# +# + rbc - ReadableByteChannel +# + return - converted string +isolated function convertToString(io:ReadableByteChannel rbc) returns string|error { + byte[] readContent; + string textContent = EMPTY_STRING; + while (true) { + byte[]|io:Error result = rbc.read(1000); + if result is io:EofError { + break; + } else if result is io:Error { + string errMsg = "Error occurred while reading from Readable Byte Channel."; + log:printError(errMsg, 'error = result); + return error(errMsg, result); + } else { + readContent = result; + string|error readContentStr = strings:fromBytes(readContent); + if readContentStr is string { + textContent = textContent + readContentStr; + } else { + string errMsg = "Error occurred while converting readContent byte array to string."; + log:printError(errMsg, 'error = readContentStr); + return error(errMsg, readContentStr); + } + } + } + return textContent; +} + +# Convert string[][] to string. +# +# + stringCsvInput - Multi dimentional array of strings +# + return - converted string +isolated function convertStringListToString(string[][]|stream stringCsvInput) returns string|error { + lock { + csvContent = EMPTY_STRING; + } + if stringCsvInput is string[][] { + foreach var row in stringCsvInput { + lock { + csvContent += row.reduce(isolated function(string s, string t) returns string { + return s.concat(",", t); + }, EMPTY_STRING).substring(1) + NEW_LINE; + } + } + } else { + check stringCsvInput.forEach(isolated function(string[] row) { + lock { + csvContent += row.reduce(isolated function(string s, string t) returns string { + return s.concat(",", t); + }, EMPTY_STRING).substring(1) + NEW_LINE; + + } + }); + } + lock { + return csvContent; + } +} + +isolated function parseCsvString(string stringContent) returns string[][]|error = @java:Method { + 'class: "io.ballerinax.salesforce.CsvParserUtils", + name: "parseCsvToStringArray" +} external; From 384126f502efceb2d8ff9a367d576e2e99e1bd0f Mon Sep 17 00:00:00 2001 From: aashikam Date: Wed, 17 Jul 2024 13:09:50 +0530 Subject: [PATCH 03/11] Deprecate bulkv2 and apex APIs in default package --- ballerina/client.bal | 58 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/ballerina/client.bal b/ballerina/client.bal index 07cb5776..2940678b 100644 --- a/ballerina/client.bal +++ b/ballerina/client.bal @@ -13,6 +13,7 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. + import ballerina/http; import ballerina/io; import ballerina/jballerina.java; @@ -24,7 +25,6 @@ import ballerinax/salesforce.utils; # Ballerina Salesforce connector provides the capability to access Salesforce REST API. # This connector lets you to perform operations for SObjects, query using SOQL, search using SOSL, and describe SObjects # and organizational data. - public isolated client class Client { private final http:Client salesforceClient; private map sfLocators = {}; @@ -483,6 +483,10 @@ public isolated client class Client { # + payload - Payload # + returnType - The payload type, which is expected to be returned after data binding # + return - `string|int|record{}` type if successful or else `error` + # # Deprecated + # This function is deprecated due to the introduction of the new submodule salesforce.apex, which supports this API. + # This API will be removed with the 9.0.0 release. + @deprecated isolated remote function apexRestExecute(string urlPath, http:Method methodType, record {} payload = {}, typedesc returnType = <>) returns returnType|error = @java:Method { @@ -532,6 +536,10 @@ public isolated client class Client { # # + payload - The payload for the bulk job # + return - `BulkJob` if successful or else `error` + # # Deprecated + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This API will be removed with the 9.0.0 release. + @deprecated isolated remote function createIngestJob(BulkCreatePayload payload) returns BulkJob|error { string path = utils:prepareUrl([API_BASE_PATH, JOBS, INGEST]); return check self.salesforceClient->post(path, payload); @@ -541,6 +549,10 @@ public isolated client class Client { # # + payload - The payload for the bulk job # + return - `BulkJob` if successful or else `error` + # # Deprecated + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This API will be removed with the 9.0.0 release. + @deprecated isolated remote function createQueryJob(BulkCreatePayload payload) returns BulkJob|error { string path = utils:prepareUrl([API_BASE_PATH, JOBS, QUERY]); return check self.salesforceClient->post(path, payload); @@ -550,6 +562,10 @@ public isolated client class Client { # # + payload - The payload for the bulk job # + return - `future` if successful else `error` + # # Deprecated + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This API will be removed with the 9.0.0 release. + @deprecated isolated remote function createQueryJobAndWait(BulkCreatePayload payload) returns future|error { string path = utils:prepareUrl([API_BASE_PATH, JOBS, QUERY]); http:Response response = check self.salesforceClient->post(path, payload); @@ -582,6 +598,10 @@ public isolated client class Client { # + bulkJobId - Id of the bulk job # + bulkOperation - The processing operation for the job # + return - `BulkJobInfo` if successful or else `error` + # # Deprecated + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This API will be removed with the 9.0.0 release. + @deprecated isolated remote function getJobInfo(string bulkJobId, BulkOperation bulkOperation) returns BulkJobInfo|error { string path = utils:prepareUrl([API_BASE_PATH, JOBS, bulkOperation, bulkJobId]); return check self.salesforceClient->get(path); @@ -592,6 +612,10 @@ public isolated client class Client { # + bulkJobId - Id of the bulk job # + content - CSV data to be added # + return - `Nil` record if successful or `error` if unsuccessful + # # Deprecated + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This API will be removed with the 9.0.0 release. + @deprecated isolated remote function addBatch(string bulkJobId, string|string[][]|stream|io:ReadableByteChannel content) returns error? { string payload = ""; string path = utils:prepareUrl([API_BASE_PATH, JOBS, INGEST, bulkJobId, BATCHES]); @@ -612,6 +636,10 @@ public isolated client class Client { # # + jobType - Type of the job # + return - `AllJobs` record if successful or `error` if unsuccessful + # # Deprecated + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This API will be removed with the 9.0.0 release. + @deprecated isolated remote function getAllJobs(JobType? jobType = ()) returns error|AllJobs { string path = utils:prepareUrl([API_BASE_PATH, JOBS, INGEST]) + ((jobType is ()) ? "" : string `?jobType=${jobType}`); @@ -622,6 +650,10 @@ public isolated client class Client { # # + jobType - Type of the job # + return - `AllJobs` if successful else `error` + # # Deprecated + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This API will be removed with the 9.0.0 release. + @deprecated isolated remote function getAllQueryJobs(JobType? jobType = ()) returns error|AllJobs { string path = utils:prepareUrl([API_BASE_PATH, JOBS, INGEST]) + ((jobType is ()) ? "" : string `?jobType=${jobType}`); @@ -633,6 +665,10 @@ public isolated client class Client { # + status - Status of the job # + bulkJobId - Id of the bulk job # + return - `string[][]` if successful else `error` + # # Deprecated + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This API will be removed with the 9.0.0 release. + @deprecated isolated remote function getJobStatus(string bulkJobId, Status status) returns string[][]|error { string path = utils:prepareUrl([API_BASE_PATH, JOBS, INGEST, bulkJobId, status]); @@ -657,6 +693,10 @@ public isolated client class Client { # + bulkJobId - Id of the bulk job # + maxRecords - The maximum number of records to retrieve per set of results for the query # + return - The resulting string[][] if successful else `error` + # # Deprecated + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This API will be removed with the 9.0.0 release. + @deprecated isolated remote function getQueryResult(string bulkJobId, int? maxRecords = ()) returns string[][]|error { string path = ""; @@ -718,6 +758,10 @@ public isolated client class Client { # + bulkJobId - Id of the bulk job # + bulkOperation - The processing operation for the job # + return - `()` if successful else `error` + # # Deprecated + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This API will be removed with the 9.0.0 release. + @deprecated isolated remote function abortJob(string bulkJobId, BulkOperation bulkOperation) returns BulkJobInfo|error { string path = utils:prepareUrl([API_BASE_PATH, JOBS, bulkOperation, bulkJobId]); record {} payload = {"state": "Aborted"}; @@ -729,6 +773,10 @@ public isolated client class Client { # + bulkJobId - Id of the bulk job # + bulkOperation - The processing operation for the job # + return - `()` if successful else `error` + # # Deprecated + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This API will be removed with the 9.0.0 release. + @deprecated isolated remote function deleteJob(string bulkJobId, BulkOperation bulkOperation) returns error? { string path = utils:prepareUrl([API_BASE_PATH, JOBS, bulkOperation, bulkJobId]); return check self.salesforceClient->delete(path); @@ -738,6 +786,10 @@ public isolated client class Client { # # + bulkJobId - Id of the bulk job # + return - future if successful else `error` + # # Deprecated + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This API will be removed with the 9.0.0 release. + @deprecated isolated remote function closeIngestJobAndWait(string bulkJobId) returns error|future { final string path = utils:prepareUrl([API_BASE_PATH, JOBS, INGEST, bulkJobId]); record {} payload = {"state": "UploadComplete"}; @@ -768,6 +820,10 @@ public isolated client class Client { # # + bulkJobId - Id of the bulk job # + return - BulkJobInfo if successful else `error` + # # Deprecated + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This API will be removed with the 9.0.0 release. + @deprecated isolated remote function closeIngestJob(string bulkJobId) returns error|BulkJobCloseInfo { final string path = utils:prepareUrl([API_BASE_PATH, JOBS, INGEST, bulkJobId]); record {} payload = {"state": "UploadComplete"}; From dd266a707f297a4a30ee7cba1ac53a3c953c0225 Mon Sep 17 00:00:00 2001 From: aashikam Date: Wed, 17 Jul 2024 15:05:12 +0530 Subject: [PATCH 04/11] Refactor bulkv2 and apex submodule code --- ballerina/modules/apex/client.bal | 6 +- ballerina/modules/apex/constants.bal | 120 +---------- ballerina/modules/apex/data_mappings.bal | 111 ---------- ballerina/modules/apex/errors.bal | 42 ---- ballerina/modules/apex/types.bal | 4 +- ballerina/modules/apex/utils.bal | 101 --------- ballerina/modules/bulkv2/constants.bal | 91 +------- ballerina/modules/bulkv2/data_mappings.bal | 111 ---------- ballerina/modules/bulkv2/errors.bal | 42 ---- ballerina/modules/bulkv2/types.bal | 238 --------------------- 10 files changed, 7 insertions(+), 859 deletions(-) delete mode 100644 ballerina/modules/apex/data_mappings.bal delete mode 100644 ballerina/modules/apex/errors.bal delete mode 100644 ballerina/modules/apex/utils.bal delete mode 100644 ballerina/modules/bulkv2/data_mappings.bal delete mode 100644 ballerina/modules/bulkv2/errors.bal diff --git a/ballerina/modules/apex/client.bal b/ballerina/modules/apex/client.bal index 25aa60c8..cfff7ad3 100644 --- a/ballerina/modules/apex/client.bal +++ b/ballerina/modules/apex/client.bal @@ -1,6 +1,6 @@ -// Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. // -// WSO2 Inc. licenses this file to you under the Apache License, +// 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 @@ -13,6 +13,7 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. + import ballerina/http; import ballerina/jballerina.java; import ballerinax/'client.config; @@ -21,7 +22,6 @@ import ballerinax/salesforce.utils; # Ballerina Salesforce connector provides the capability to access Salesforce REST API. # This connector lets you to perform operations for SObjects, query using SOQL, search using SOSL, and describe SObjects # and organizational data. - public isolated client class Client { private final http:Client salesforceClient; private map sfLocators = {}; diff --git a/ballerina/modules/apex/constants.bal b/ballerina/modules/apex/constants.bal index a465dd5f..7c907a49 100644 --- a/ballerina/modules/apex/constants.bal +++ b/ballerina/modules/apex/constants.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except @@ -18,127 +18,11 @@ # Constant field `API_VERSION`. Holds the value for the Salesforce API version. public const string API_VERSION = "v59.0"; -// For URL encoding -# Constant field `ENCODING_CHARSET`. Holds the value for the encoding charset. -const string ENCODING_CHARSET = "utf-8"; +public const string INVALID_CLIENT_CONFIG = "Invalid values provided for client configuration parameters."; //Salesforce endpoints # Constant field `BASE_PATH`. Holds the value for the Salesforce base path/URL. const string BASE_PATH = "/services/data"; -# Constant field `API_BASE_PATH`. Holds the value for the Salesforce API base path/URL. -final string API_BASE_PATH = string `${BASE_PATH}/${API_VERSION}`; - # Constant field `APEX_BASE_PATH`. Holds the value for the Salesforce Apex base path/URL. final string APEX_BASE_PATH = string `/services/apexrest`; - -# Constant field `QUICK_ACTIONS`. Holds the value quickActions for quick actions resource prefix. -final string QUICK_ACTIONS = "quickActions"; - -# Constant field `ANALYTICS`. Holds the value analytics for analytics resource prefix. -const string ANALYTICS = "analytics"; - -# Constant field `ACTIONS`. Holds the value actions for actions resource prefix. -const string ACTIONS = "actions"; - -# Constant field `NAMED_LAYOUTS`. Holds the value namedlayouts for layout resource prefix. -const string NAMED_LAYOUTS = "namedLayouts"; - -# Constant field `COMPOSITE`. Holds the value composite for composite resource prefix. -const string COMPOSITE = "composite"; - -# Constant field `BATCH`. Holds the value batch for batch resource prefix. -const string BATCH = "batch"; - -# Constant field `BATCHES`. Holds the value batches for bulk resource prefix. -const string BATCHES = "batches"; - -# Constant field `JOBS`. Holds the value jobs for bulk resource prefix. -const string JOBS = "jobs"; - -# Constant field `INGEST`. Holds the value ingest for bulk resource prefix. -const string INGEST = "ingest"; - -# Constant field `INSTANCES`. Holds the value instances for instances resource prefix. -const string INSTANCES = "instances"; - -# Constant field `REPORTS`. Holds the value reports for reports resource prefix. -const string REPORTS = "reports"; - -# Constant field `SOBJECTS`. Holds the value sobjects for get sobject resource prefix. -const string SOBJECTS = "sobjects"; - -# Constant field `PASSWORD`. Holds the value sobjects for get password resource prefix. -const string PASSWORD = "password"; - -# Constant field `USER`. Holds the value sobjects for get USER resource prefix. -const string USER = "User"; - -# Constant field `DELETED` Holds the value deleted for get deleted resource prefix. -const string DELETED = "deleted"; - -# Constant field `UPDATED` Holds the value updated for get updated resource prefix. -const string UPDATED = "updated"; - -# Constant field `LIMITS`. Holds the value limits for get limits resource prefix. -const string LIMITS = "limits"; - -# Constant field `DESCRIBE`. Holds the value describe for describe resource prefix. -const string DESCRIBE = "describe"; - -# Constant field `search`. Holds the value search for SOSL search resource prefix. -const string SEARCH = "search"; - -# Constant field `PLATFORM_ACTION`. Holds the value PlatformAction for resource prefix. -const string PLATFORM_ACTION = "PlatformAction"; - -// Query param names -const string QUERY = "query"; - -// Result param names -const string RESULT = "results"; - -# Constant field `FIELDS`. Holds the value fields for resource prefix. -const string FIELDS = "fields"; - -# Constant field `q`. Holds the value q for query resource prefix. -const string Q = "q"; - -# Constant field `QUESTION_MARK`. Holds the value of "?". -const string QUESTION_MARK = "?"; - -# Constant field `EQUAL_SIGN`. Holds the value of "=". -const string EQUAL_SIGN = "="; - -# Constant field `EMPTY_STRING`. Holds the value of "". -public const string EMPTY_STRING = ""; - -# Constant field `AMPERSAND`. Holds the value of "&". -const string AMPERSAND = "&"; - -# Constant field `FORWARD_SLASH`. Holds the value of "/". -const string FORWARD_SLASH = "/"; - -# Next records URl -const NEXT_RECORDS_URL = "nextRecordsUrl"; - -const ATTRIBUTES = "attributes"; - -// SObjects -# Constant field `ACCOUNT`. Holds the value Account for account object. -const string ACCOUNT = "Account"; - -# Constant field `LEAD`. Holds the value Lead for lead object. -const string LEAD = "Lead"; - -# Constant field `CONTACT`. Holds the value Contact for contact object. -const string CONTACT = "Contact"; - -# Constant field `OPPORTUNITY`. Holds the value Opportunity for opportunity object. -const string OPPORTUNITY = "Opportunity"; - -# Constant field `PRODUCT`. Holds the value Product2 for product object. -const string PRODUCT = "Product2"; - -# Constant field `NEW_LINE`. Holds the value of "\n". -const string NEW_LINE = "\n"; diff --git a/ballerina/modules/apex/data_mappings.bal b/ballerina/modules/apex/data_mappings.bal deleted file mode 100644 index 00aca270..00000000 --- a/ballerina/modules/apex/data_mappings.bal +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. -// -// WSO2 Inc. 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/log; - -isolated function toVersions(json payload) returns Version[]|Error { - Version[] versions = []; - json[] versionsArr = payload; - - foreach json ele in versionsArr { - Version|error ver = ele.cloneWithType(Version); - - if ver is Version { - versions[versions.length()] = ver; - } else { - string errMsg = "Error occurred while constructing Version record."; - log:printError(errMsg + " ele:" + ele.toJsonString(), 'error = ver); - return error Error(errMsg, ver); - } - } - return versions; -} - -type StringMap map; - -isolated function toMapOfStrings(json payload) returns map|Error { - map|error strMap = payload.cloneWithType(StringMap); - - if strMap is map { - return strMap; - } else { - string errMsg = "Error occurred while constructing map."; - log:printError(errMsg + " payload:" + payload.toJsonString(), 'error = strMap); - return error Error(errMsg, strMap); - } -} - -type JsonMap map; - -isolated function toMapOfLimits(json payload) returns map|Error { - map limits = {}; - map|error payloadMap = payload.cloneWithType(JsonMap); - - if payloadMap is error { - string errMsg = "Error occurred while constructing map using json payload."; - log:printError(errMsg + " payload:" + payload.toJsonString(), 'error = payloadMap); - return error Error(errMsg, payloadMap); - } else { - foreach var [key, value] in payloadMap.entries() { - Limit|error lim = value.cloneWithType(Limit); - if lim is Limit { - limits[key] = lim; - } else { - string errMsg = "Error occurred while constructing Limit record."; - log:printError(errMsg + " value:" + value.toJsonString(), 'error = lim); - return error Error(errMsg, lim); - } - } - } - return limits; -} - - -isolated function toSObjectMetaData(json payload) returns SObjectMetaData|Error { - SObjectMetaData|error res = payload.cloneWithType(SObjectMetaData); - - if res is SObjectMetaData { - return res; - } else { - string errMsg = "Error occurred while constructing SObjectMetaData record."; - log:printError(errMsg + " payload:" + payload.toJsonString(), 'error = res); - return error Error(errMsg, res); - } -} - -isolated function toOrganizationMetadata(json payload) returns OrganizationMetadata|Error { - OrganizationMetadata|error res = payload.cloneWithType(OrganizationMetadata); - - if res is OrganizationMetadata { - return res; - } else { - string errMsg = "Error occurred while constructing OrganizationMetadata record."; - log:printError(errMsg + " payload:" + payload.toJsonString(), 'error = res); - return error Error(errMsg, res); - } -} - -isolated function toSObjectBasicInfo(json payload) returns SObjectBasicInfo|Error { - SObjectBasicInfo|error res = payload.cloneWithType(SObjectBasicInfo); - - if res is SObjectBasicInfo { - return res; - } else { - string errMsg = "Error occurred while constructing SObjectBasicInfo record."; - log:printError(errMsg + " payload:" + payload.toJsonString(), 'error = res); - return error Error(errMsg, res); - } -} diff --git a/ballerina/modules/apex/errors.bal b/ballerina/modules/apex/errors.bal deleted file mode 100644 index f314a523..00000000 --- a/ballerina/modules/apex/errors.bal +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. -// -// 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. - -# Salesforce connector error. -public type Error error; - -# Additional details extracted from the Http error. -# -# + errorCode - Error code from Salesforce -# + message - Response body with extra information -# -public type ErrorDetails record { - string? errorCode?; - string? message?; -}; - -// Error constants -const string JSON_ACCESSING_ERROR_MSG = "Error occurred while accessing the JSON payload of the response."; -const string XML_ACCESSING_ERROR_MSG = "Error occurred while accessing the XML payload of the response."; -const string TEXT_ACCESSING_ERROR_MSG = "Error occurred while accessing the Text payload of the response."; -const string HTTP_CLIENT_ERROR = "Failed to establish the communication with the upstream server or a data binding failure. Refer error.cause() for more details"; -public const string HTTP_ERROR_MSG = "Error occurred while getting the HTTP response."; -const STATUS_CODE = "statusCode"; -const HEADERS = "headers"; -const BODY = "body"; - - -public const string ERR_EXTRACTING_ERROR_MSG = "Error occured while extracting errors from payload."; -public const string INVALID_CLIENT_CONFIG = "Invalid values provided for client configuration parameters."; diff --git a/ballerina/modules/apex/types.bal b/ballerina/modules/apex/types.bal index 4faba4a7..974314cc 100644 --- a/ballerina/modules/apex/types.bal +++ b/ballerina/modules/apex/types.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except @@ -17,8 +17,6 @@ import ballerina/http; import ballerinax/'client.config; - - # Represents status of the bulk jobs public enum Status { SUCCESSFUL_RESULTS = "successfulResults", diff --git a/ballerina/modules/apex/utils.bal b/ballerina/modules/apex/utils.bal deleted file mode 100644 index 370acf22..00000000 --- a/ballerina/modules/apex/utils.bal +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. -// -// 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/log; -import ballerina/time; -import ballerina/jballerina.java; -import ballerina/lang.'string as strings; - -isolated string csvContent = EMPTY_STRING; - -# Remove decimal places from a civil seconds value -# -# + civilTime - a time:civil record -# + return - a time:civil record with decimal places removed -# -isolated function removeDecimalPlaces(time:Civil civilTime) returns time:Civil { - time:Civil result = civilTime; - time:Seconds seconds = (result.second is ()) ? 0 : result.second; - decimal floor = decimal:floor(seconds); - result.second = floor; - return result; -} - -# Convert ReadableByteChannel to string. -# -# + rbc - ReadableByteChannel -# + return - converted string -isolated function convertToString(io:ReadableByteChannel rbc) returns string|error { - byte[] readContent; - string textContent = EMPTY_STRING; - while (true) { - byte[]|io:Error result = rbc.read(1000); - if result is io:EofError { - break; - } else if result is io:Error { - string errMsg = "Error occurred while reading from Readable Byte Channel."; - log:printError(errMsg, 'error = result); - return error(errMsg, result); - } else { - readContent = result; - string|error readContentStr = strings:fromBytes(readContent); - if readContentStr is string { - textContent = textContent + readContentStr; - } else { - string errMsg = "Error occurred while converting readContent byte array to string."; - log:printError(errMsg, 'error = readContentStr); - return error(errMsg, readContentStr); - } - } - } - return textContent; -} - -# Convert string[][] to string. -# -# + stringCsvInput - Multi dimentional array of strings -# + return - converted string -isolated function convertStringListToString(string[][]|stream stringCsvInput) returns string|error { - lock { - csvContent = EMPTY_STRING; - } - if stringCsvInput is string[][] { - foreach var row in stringCsvInput { - lock { - csvContent += row.reduce(isolated function(string s, string t) returns string { - return s.concat(",", t); - }, EMPTY_STRING).substring(1) + NEW_LINE; - } - } - } else { - check stringCsvInput.forEach(isolated function(string[] row) { - lock { - csvContent += row.reduce(isolated function(string s, string t) returns string { - return s.concat(",", t); - }, EMPTY_STRING).substring(1) + NEW_LINE; - - } - }); - } - lock { - return csvContent; - } -} - -isolated function parseCsvString(string stringContent) returns string[][]|error = @java:Method { - 'class: "io.ballerinax.salesforce.CsvParserUtils", - name: "parseCsvToStringArray" -} external; diff --git a/ballerina/modules/bulkv2/constants.bal b/ballerina/modules/bulkv2/constants.bal index 1dd94fd0..67ec7ba0 100644 --- a/ballerina/modules/bulkv2/constants.bal +++ b/ballerina/modules/bulkv2/constants.bal @@ -18,9 +18,7 @@ # Constant field `API_VERSION`. Holds the value for the Salesforce API version. public const string API_VERSION = "v59.0"; -// For URL encoding -# Constant field `ENCODING_CHARSET`. Holds the value for the encoding charset. -const string ENCODING_CHARSET = "utf-8"; +public const string INVALID_CLIENT_CONFIG = "Invalid values provided for client configuration parameters."; //Salesforce endpoints # Constant field `BASE_PATH`. Holds the value for the Salesforce base path/URL. @@ -29,27 +27,6 @@ const string BASE_PATH = "/services/data"; # Constant field `API_BASE_PATH`. Holds the value for the Salesforce API base path/URL. final string API_BASE_PATH = string `${BASE_PATH}/${API_VERSION}`; -# Constant field `APEX_BASE_PATH`. Holds the value for the Salesforce Apex base path/URL. -final string APEX_BASE_PATH = string `/services/apexrest`; - -# Constant field `QUICK_ACTIONS`. Holds the value quickActions for quick actions resource prefix. -final string QUICK_ACTIONS = "quickActions"; - -# Constant field `ANALYTICS`. Holds the value analytics for analytics resource prefix. -const string ANALYTICS = "analytics"; - -# Constant field `ACTIONS`. Holds the value actions for actions resource prefix. -const string ACTIONS = "actions"; - -# Constant field `NAMED_LAYOUTS`. Holds the value namedlayouts for layout resource prefix. -const string NAMED_LAYOUTS = "namedLayouts"; - -# Constant field `COMPOSITE`. Holds the value composite for composite resource prefix. -const string COMPOSITE = "composite"; - -# Constant field `BATCH`. Holds the value batch for batch resource prefix. -const string BATCH = "batch"; - # Constant field `BATCHES`. Holds the value batches for bulk resource prefix. const string BATCHES = "batches"; @@ -59,39 +36,6 @@ const string JOBS = "jobs"; # Constant field `INGEST`. Holds the value ingest for bulk resource prefix. const string INGEST = "ingest"; -# Constant field `INSTANCES`. Holds the value instances for instances resource prefix. -const string INSTANCES = "instances"; - -# Constant field `REPORTS`. Holds the value reports for reports resource prefix. -const string REPORTS = "reports"; - -# Constant field `SOBJECTS`. Holds the value sobjects for get sobject resource prefix. -const string SOBJECTS = "sobjects"; - -# Constant field `PASSWORD`. Holds the value sobjects for get password resource prefix. -const string PASSWORD = "password"; - -# Constant field `USER`. Holds the value sobjects for get USER resource prefix. -const string USER = "User"; - -# Constant field `DELETED` Holds the value deleted for get deleted resource prefix. -const string DELETED = "deleted"; - -# Constant field `UPDATED` Holds the value updated for get updated resource prefix. -const string UPDATED = "updated"; - -# Constant field `LIMITS`. Holds the value limits for get limits resource prefix. -const string LIMITS = "limits"; - -# Constant field `DESCRIBE`. Holds the value describe for describe resource prefix. -const string DESCRIBE = "describe"; - -# Constant field `search`. Holds the value search for SOSL search resource prefix. -const string SEARCH = "search"; - -# Constant field `PLATFORM_ACTION`. Holds the value PlatformAction for resource prefix. -const string PLATFORM_ACTION = "PlatformAction"; - // Query param names const string QUERY = "query"; @@ -104,41 +48,8 @@ const string FIELDS = "fields"; # Constant field `q`. Holds the value q for query resource prefix. const string Q = "q"; -# Constant field `QUESTION_MARK`. Holds the value of "?". -const string QUESTION_MARK = "?"; - -# Constant field `EQUAL_SIGN`. Holds the value of "=". -const string EQUAL_SIGN = "="; - # Constant field `EMPTY_STRING`. Holds the value of "". public const string EMPTY_STRING = ""; -# Constant field `AMPERSAND`. Holds the value of "&". -const string AMPERSAND = "&"; - -# Constant field `FORWARD_SLASH`. Holds the value of "/". -const string FORWARD_SLASH = "/"; - -# Next records URl -const NEXT_RECORDS_URL = "nextRecordsUrl"; - -const ATTRIBUTES = "attributes"; - -// SObjects -# Constant field `ACCOUNT`. Holds the value Account for account object. -const string ACCOUNT = "Account"; - -# Constant field `LEAD`. Holds the value Lead for lead object. -const string LEAD = "Lead"; - -# Constant field `CONTACT`. Holds the value Contact for contact object. -const string CONTACT = "Contact"; - -# Constant field `OPPORTUNITY`. Holds the value Opportunity for opportunity object. -const string OPPORTUNITY = "Opportunity"; - -# Constant field `PRODUCT`. Holds the value Product2 for product object. -const string PRODUCT = "Product2"; - # Constant field `NEW_LINE`. Holds the value of "\n". const string NEW_LINE = "\n"; diff --git a/ballerina/modules/bulkv2/data_mappings.bal b/ballerina/modules/bulkv2/data_mappings.bal deleted file mode 100644 index e8271f1f..00000000 --- a/ballerina/modules/bulkv2/data_mappings.bal +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. -// -// 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/log; - -isolated function toVersions(json payload) returns Version[]|Error { - Version[] versions = []; - json[] versionsArr = payload; - - foreach json ele in versionsArr { - Version|error ver = ele.cloneWithType(Version); - - if ver is Version { - versions[versions.length()] = ver; - } else { - string errMsg = "Error occurred while constructing Version record."; - log:printError(errMsg + " ele:" + ele.toJsonString(), 'error = ver); - return error Error(errMsg, ver); - } - } - return versions; -} - -type StringMap map; - -isolated function toMapOfStrings(json payload) returns map|Error { - map|error strMap = payload.cloneWithType(StringMap); - - if strMap is map { - return strMap; - } else { - string errMsg = "Error occurred while constructing map."; - log:printError(errMsg + " payload:" + payload.toJsonString(), 'error = strMap); - return error Error(errMsg, strMap); - } -} - -type JsonMap map; - -isolated function toMapOfLimits(json payload) returns map|Error { - map limits = {}; - map|error payloadMap = payload.cloneWithType(JsonMap); - - if payloadMap is error { - string errMsg = "Error occurred while constructing map using json payload."; - log:printError(errMsg + " payload:" + payload.toJsonString(), 'error = payloadMap); - return error Error(errMsg, payloadMap); - } else { - foreach var [key, value] in payloadMap.entries() { - Limit|error lim = value.cloneWithType(Limit); - if lim is Limit { - limits[key] = lim; - } else { - string errMsg = "Error occurred while constructing Limit record."; - log:printError(errMsg + " value:" + value.toJsonString(), 'error = lim); - return error Error(errMsg, lim); - } - } - } - return limits; -} - - -isolated function toSObjectMetaData(json payload) returns SObjectMetaData|Error { - SObjectMetaData|error res = payload.cloneWithType(SObjectMetaData); - - if res is SObjectMetaData { - return res; - } else { - string errMsg = "Error occurred while constructing SObjectMetaData record."; - log:printError(errMsg + " payload:" + payload.toJsonString(), 'error = res); - return error Error(errMsg, res); - } -} - -isolated function toOrganizationMetadata(json payload) returns OrganizationMetadata|Error { - OrganizationMetadata|error res = payload.cloneWithType(OrganizationMetadata); - - if res is OrganizationMetadata { - return res; - } else { - string errMsg = "Error occurred while constructing OrganizationMetadata record."; - log:printError(errMsg + " payload:" + payload.toJsonString(), 'error = res); - return error Error(errMsg, res); - } -} - -isolated function toSObjectBasicInfo(json payload) returns SObjectBasicInfo|Error { - SObjectBasicInfo|error res = payload.cloneWithType(SObjectBasicInfo); - - if res is SObjectBasicInfo { - return res; - } else { - string errMsg = "Error occurred while constructing SObjectBasicInfo record."; - log:printError(errMsg + " payload:" + payload.toJsonString(), 'error = res); - return error Error(errMsg, res); - } -} diff --git a/ballerina/modules/bulkv2/errors.bal b/ballerina/modules/bulkv2/errors.bal deleted file mode 100644 index d3005413..00000000 --- a/ballerina/modules/bulkv2/errors.bal +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. -// -// 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. - -# Salesforce connector error. -public type Error error; - -# Additional details extracted from the Http error. -# -# + errorCode - Error code from Salesforce -# + message - Response body with extra information -# -public type ErrorDetails record { - string? errorCode?; - string? message?; -}; - -// Error constants -const string JSON_ACCESSING_ERROR_MSG = "Error occurred while accessing the JSON payload of the response."; -const string XML_ACCESSING_ERROR_MSG = "Error occurred while accessing the XML payload of the response."; -const string TEXT_ACCESSING_ERROR_MSG = "Error occurred while accessing the Text payload of the response."; -const string HTTP_CLIENT_ERROR = "Failed to establish the communication with the upstream server or a data binding failure. Refer error.cause() for more details"; -public const string HTTP_ERROR_MSG = "Error occurred while getting the HTTP response."; -const STATUS_CODE = "statusCode"; -const HEADERS = "headers"; -const BODY = "body"; - - -public const string ERR_EXTRACTING_ERROR_MSG = "Error occured while extracting errors from payload."; -public const string INVALID_CLIENT_CONFIG = "Invalid values provided for client configuration parameters."; diff --git a/ballerina/modules/bulkv2/types.bal b/ballerina/modules/bulkv2/types.bal index 974314cc..b0628cfd 100644 --- a/ballerina/modules/bulkv2/types.bal +++ b/ballerina/modules/bulkv2/types.bal @@ -96,244 +96,6 @@ public type Limit record {| json...; |}; -# Defines the Attribute type. -# Contains the attribute information of the resultant record. -public type Attribute record {| - # Type of the resultant record - string 'type; - # URL of the resultant record - string url?; -|}; - -# Metadata for your organization and available to the logged-in user. -public type OrganizationMetadata record {| - # Encoding - string encoding; - # Maximum batch size - int maxBatchSize; - # Available SObjects - SObjectMetaData[] sobjects; - json...; -|}; - -# Metadata for an SObject, including information about each field, URLs, and child relationships. -public type SObjectMetaData record {| - # SObject name - string name; - # Is createable - boolean createable; - # Is deletable - boolean deletable; - # Is updateable - boolean updateable; - # Is queryable - boolean queryable; - # SObject label - string label; - # SObject URLs - map urls; - json...; -|}; - - -# Basic info of a SObject. -public type SObjectBasicInfo record {| - # Metadata related to the SObject - SObjectMetaData objectDescribe; - json...; -|}; - -# Represent the Attributes at SObjectBasicInfo. -public type Attributes record { - # Type of the resultant record - string 'type; - # URL of the resultant record - string url; -}; - - -# Response of object creation. -public type CreationResponse record { - # Created object ID - string id; - # Array of errors - anydata[] errors; - # Success flag - boolean success; -}; - -# Represents a Report. -public type Report record { - # Unique report ID - string id; - # Report display name - string name; - # URL that returns report data - string url; - # URL that retrieves report metadata - string describeUrl; - # Information for each instance of the report that was run asynchronously. - string instancesUrl; -}; - -# Represents an instance of a Report. -public type ReportInstance record { - # Unique ID for a report instance - string id; - # Status of the report run - string status; - # Date and time when an instance of the report run was requested - string requestDate; - # Date, time when the instance of the report run finished - string? completionDate; - # URL where results of the report run for that instance are stored - string url; - # API name of the user that created the instance - string ownerId; - # Indicates if it is queryable - boolean queryable; - # Indicates if it has detailed data - boolean hasDetailRows; -}; - -# Represents attributes of instance of an asynchronous report run. -public type AsyncReportAttributes record { - # Unique ID for an instance of a report that was run - string id; - # Unique report ID - string reportId; - # Display name of the report - string reportName; - # Status of the report run - string status; - # API name of the user that created the instance - string ownerId; - # Date and time when an instance of the report run was requested - string requestDate; - # Format of the resource - string 'type; - # Date, time when the instance of the report run finished - string? completionDate; - # Error message if the instance run failed - string? errorMessage; - # Indicates if it is queryable - boolean queryable; -}; - -# Represents attributes of instance of synchronous report run. -public type SyncReportAttributes record { - # Unique report ID - string reportId; - # Display name of the report - string reportName; - # Format of the resource - string 'type; - # Resource URL to get report metadata - string describeUrl; - # Resource URL to run a report asynchronously - string instancesUrl; -}; - -# Represents result of an asynchronous report run. -public type ReportInstanceResult record { - # Attributes for the instance of the report run - AsyncReportAttributes|SyncReportAttributes attributes; - # Indicates if all report results are returned - boolean allData; - # Collection of summary level data or both detailed and summary level data - map? factMap; - # Collection of column groupings - map? groupingsAcross; - # Collection of row groupings - map? groupingsDown; - # Information about the fields used to build the report - map? reportMetadata; - # Indicates if it has detailed data - boolean hasDetailRows; - # Information on report groupings, summary fields, and detailed data columns - map? reportExtendedMetadata; -}; - -# Represent the metadata of deleted records. -public type DeletedRecordsResult record { - # Array of deleted records - record {|string deletedDate; string id;|}[] deletedRecords; - # The earliest date covered by the results - string earliestDateAvailable; - # The latest date covered by the results - string latestDateCovered; -}; - -# Represent the metadata of updated records. -public type UpdatedRecordsResults record { - # Array of updated record IDs - string[] ids; - # The latest date covered by the results - string latestDateCovered; -}; - -# Represent the password status. -public type PasswordStatus record{ - # Indicates whether the password is expired - boolean isExpired; -}; - - -# Represent the Error response for password access. -public type ErrorResponse record { - # Error message - string message; - # Error code - string errorCode; -}; - -# Represent a quick action. -public type QuickAction record { - # Action enum or ID - string actionEnumOrId; - # Action label - string label; - # Action name - string name; - # Action type - string 'type; - # Action URLs - record{string defaultValues?; string quickAction?; string describe?; string defaultValuesTemplate?;} urls; -}; - - -# Represent a batch execution result. -public type SubRequestResult record { - # Status code of the batch execution - int statusCode; - # Result of the batch execution - json? result; -}; - -# Represent Subrequest of a batch. -public type Subrequest record {| - # Subrequest of a batch - string binaryPartName?; - # Binary part name alias - string binaryPartNameAlias?; - # Method of the subrequest - string method; - # Rich input of the subrequest - record{} richInput?; - # URL of the subrequest - string url; -|}; - -# Represent results of the batch request. -public type BatchResult record { - # Indicates whether the batch request has errors - boolean hasErrors; - # Results of the batch request - SubRequestResult[] results; -}; - - - # Represents the bulk job creation request payload. public type BulkCreatePayload record { # the sObject type of the bulk job From 79ca25e08d7b76d14bb72b2e5996b401e07fc97d Mon Sep 17 00:00:00 2001 From: aashikam Date: Thu, 18 Jul 2024 20:26:52 +0530 Subject: [PATCH 05/11] Update examples with new sub modules --- build-config/resources/Ballerina.toml | 2 +- .../create_case/invoke_apex_methods.bal | 6 +++--- .../bulkv2_insert.bal | 20 +++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml index d98e0620..29b9d4ab 100644 --- a/build-config/resources/Ballerina.toml +++ b/build-config/resources/Ballerina.toml @@ -3,7 +3,7 @@ distribution = "2201.8.0" org = "ballerinax" name = "salesforce" version = "@toml.version@" -export = ["salesforce", "salesforce.bulk", "salesforce.soap"] +export = ["salesforce", "salesforce.bulk", "salesforce.soap","salesforce.bulkv2", "salesforce.apex"] license= ["Apache-2.0"] authors = ["Ballerina"] keywords = ["Sales & CRM/Customer Relationship Management", "Cost/Freemium"] diff --git a/examples/apex_rest_api_usecases/create_case/invoke_apex_methods.bal b/examples/apex_rest_api_usecases/create_case/invoke_apex_methods.bal index 5467222c..dee9588a 100644 --- a/examples/apex_rest_api_usecases/create_case/invoke_apex_methods.bal +++ b/examples/apex_rest_api_usecases/create_case/invoke_apex_methods.bal @@ -15,7 +15,7 @@ // under the License. import ballerina/log; -import ballerinax/salesforce; +import ballerinax/salesforce.apex; import ballerina/lang.runtime; // Create Salesforce client configuration by reading from environment. @@ -26,7 +26,7 @@ configurable string refreshUrl = ?; configurable string baseUrl = ?; // Using direct-token config for client configuration -salesforce:ConnectionConfig sfConfig = { +apex:ConnectionConfig sfConfig = { baseUrl, auth: { clientId, @@ -38,7 +38,7 @@ salesforce:ConnectionConfig sfConfig = { public function main() returns error? { // Create Case using user defined APEX method - salesforce:Client baseClient = check new (sfConfig); + apex:Client baseClient = check new (sfConfig); string|error caseId = baseClient->apexRestExecute("Cases", "POST", {"subject" : "Item Fault!", diff --git a/examples/bulkv2_api_usecases/execute_bulkv2_ingest_job/bulkv2_insert.bal b/examples/bulkv2_api_usecases/execute_bulkv2_ingest_job/bulkv2_insert.bal index 1c1d9fe3..341b0b07 100644 --- a/examples/bulkv2_api_usecases/execute_bulkv2_ingest_job/bulkv2_insert.bal +++ b/examples/bulkv2_api_usecases/execute_bulkv2_ingest_job/bulkv2_insert.bal @@ -14,11 +14,11 @@ // specific language governing permissions and limitations // under the License. -import ballerinax/salesforce; +import ballerinax/salesforce.bulkv2; import ballerina/lang.runtime; import ballerina/io; -// Create Salesforce client configuration by reading from environment. +// Create Salesforce bulkv2 client configuration by reading from environment. configurable string clientId = ?; configurable string clientSecret = ?; configurable string refreshToken = ?; @@ -26,7 +26,7 @@ configurable string refreshUrl = ?; configurable string baseUrl = ?; // Using direct-token config for client configuration -salesforce:ConnectionConfig sfConfig = { +bulkv2:ConnectionConfig sfConfig = { baseUrl, auth: { clientId, @@ -38,19 +38,19 @@ salesforce:ConnectionConfig sfConfig = { public function main() returns error? { // Insert contacts using a CSV file - salesforce:Client baseClient = check new (sfConfig); + bulkv2:Client baseClient = check new (sfConfig); string csvContactsFilePath = "contacts1.csv"; //create job - salesforce:BulkCreatePayload payload = { + bulkv2:BulkCreatePayload payload = { 'object : "Contact", contentType : "CSV", operation : "insert", lineEnding : "LF" }; - error|salesforce:BulkJob insertJob = baseClient->createIngestJob(payload); + error|bulkv2:BulkJob insertJob = baseClient->createIngestJob(payload); - if insertJob is salesforce:BulkJob { + if insertJob is bulkv2:BulkJob { string[][] csvContent = check io:fileReadCsv(csvContactsFilePath); error? response = baseClient->addBatch(insertJob.id, csvContent); if response is error { @@ -58,14 +58,14 @@ public function main() returns error? { } runtime:sleep(5); //get job info - error|salesforce:BulkJobInfo jobInfo = baseClient->getJobInfo(insertJob.id, "ingest"); + error|bulkv2:BulkJobInfo jobInfo = baseClient->getJobInfo(insertJob.id, "ingest"); if jobInfo is error { io:println("Error occurred while getting job info: ", jobInfo.message()); } runtime:sleep(5); //close job - future closedJob = check baseClient->closeIngestJobAndWait(insertJob.id); - salesforce:BulkJobInfo|error closedJobInfo = wait closedJob; + future closedJob = check baseClient->closeIngestJobAndWait(insertJob.id); + bulkv2:BulkJobInfo|error closedJobInfo = wait closedJob; if closedJobInfo is error { io:println("Error occurred while closing job: ", closedJobInfo.message()); } From fa28106e436e7dcb33d851ad5a8f4753f48d733e Mon Sep 17 00:00:00 2001 From: aashikam Date: Thu, 18 Jul 2024 21:33:30 +0530 Subject: [PATCH 06/11] Update API docs of apex module --- ballerina/modules/apex/Module.md | 104 +++---- ballerina/modules/apex/client.bal | 26 +- ballerina/modules/apex/constants.bal | 13 +- ballerina/modules/apex/tests/README.md | 65 ---- ballerina/modules/apex/tests/test.bal | 29 +- ballerina/modules/apex/types.bal | 394 +------------------------ 6 files changed, 67 insertions(+), 564 deletions(-) delete mode 100644 ballerina/modules/apex/tests/README.md diff --git a/ballerina/modules/apex/Module.md b/ballerina/modules/apex/Module.md index 7d5e9c38..33cb00f5 100644 --- a/ballerina/modules/apex/Module.md +++ b/ballerina/modules/apex/Module.md @@ -1,67 +1,65 @@ ## Overview -Salesforce Sales Cloud is one of the leading Customer Relationship Management(CRM) software, provided by Salesforce.Inc. Salesforce enable users to efficiently manage sales and customer relationships through its APIs, robust and secure databases, and analytics services. Sales cloud provides serveral API packages to make operations on sObjects and metadata, execute queries and searches, and listen to change events through API calls using REST, SOAP, and CometD protocols. - -Ballerina Salesforce connector supports [Salesforce v59.0 REST API](https://developer.salesforce.com/docs/atlas.en-us.224.0.api_rest.meta/api_rest/intro_what_is_rest_api.htm), [Salesforce v59.0 SOAP API](https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_quickstart_intro.htm), [Salesforce v59.0 APEX REST API](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_rest_intro.htm), [Salesforce v59.0 BULK API](https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/api_asynch_introduction_bulk_api.htm), and [Salesforce v59.0 BULK V2 API](https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/bulk_api_2_0.htm). - +Salesforce Apex REST API enables you to expose your Apex classes and methods as RESTful web services. This module provides operations for executing custom Apex REST endpoints, allowing you to perform various HTTP operations on these endpoints and handle responses accordingly. + ## Setup guide 1. Create a Salesforce account with the REST capability. 2. Go to Setup --> Apps --> App Manager - Setup Side Panel + Setup Side Panel 3. Create a New Connected App. - Create Connected Apps + Create Connected Apps - Here we will be using https://test.salesforce.com as we are using sandbox environment. Users can use https://login.salesforce.com for normal usage. - Create Connected Apps + Create Connected Apps 4. After the creation user can get consumer key and secret through clicking on the `Manage Consumer Details` button. - Consumer Secrets + Consumer Secrets -5. Next step would be to get the token. - - Log in to salesforce in your preferred browser and enter the following url. - ``` - https://.salesforce.com/services/oauth2/authorize?response_type=code&client_id=&redirect_uri= - ``` - - Allow access if an alert pops up and the browser will be redirected to a Url like follows. - - ``` - https://login.salesforce.com/?code= - ``` +5. The next step is to get the token. + + - Log in to Salesforce in your preferred browser and enter the following URL: + `https://.salesforce.com/services/oauth2/authorize?response_type=code&client_id=&redirect_uri=` + - Allow access if an alert pops up, and the browser will be redirected to a URL like the following: + `https://login.salesforce.com/?code=` - The code can be obtained after decoding the encoded code 6. Get Access and Refresh tokens - - Following request can be sent to obtain the tokens. - - ``` - curl -X POST https://.salesforce.com/services/oauth2/token?code=&grant_type=authorization_code&client_id=&client_secret=&redirect_uri=https://test.salesforce.com/ - ``` + + - The following request can be sent to obtain the tokens. + ```curl -X POST https://.salesforce.com/services/oauth2/token?code=&grant_type=authorization_code&client_id=&client_secret=&redirect_uri=https://test.salesforce.com/``` - Tokens can be obtained from the response. ## Quickstart -To use the Salesforce connector in your Ballerina application, modify the .bal file as follows: +To use the Salesforce Apex client in your Ballerina application, update the .bal file as follows: -#### Step 1: Import connector +### Step 1: Import connector -Import the `ballerinax/salesforce` package into the Ballerina project. +Import the `ballerinax/salesforce.apex` module into the Ballerina project. ```ballerina -import ballerinax/salesforce; +import ballerinax/salesforce.apex; ``` -#### Step 2: Create a new connector instance +### Step 2: Create a new connector instance -Create a `salesforce:ConnectionConfig` with the obtained OAuth2 tokens and initialize the connector with it. +Create a `ConnectionConfig` with the OAuth2 tokens obtained, and initialize the connector with it. ```ballerina -salesforce:ConnectionConfig config = { +configurable string clientId = ?; +configurable string clientSecret = ?; +configurable string refreshToken = ?; +configurable string refreshUrl = ?; +configurable string baseUrl = ?; + +apex:ConnectionConfig sfConfig = { baseUrl: baseUrl, auth: { clientId: clientId, @@ -71,38 +69,28 @@ salesforce:ConnectionConfig config = { } }; -salesforce:Client salesforce = check new (config); +apex:Client apexClient = check new (sfConfig); ``` -#### Step 3: Invoke connector operation - -1. Now you can utilize the available operations. Note that they are in the form of remote operations. - -Following is an example on how to create a record using the connector. +### Step 3: Invoke connector operation - ```ballerina - salesforce:CreationResponse response = check - salesforce->create("Account", { - "Name": "IT World", - "BillingCity": "New York" - }); - - ``` - -2. Use following command to compile and run the Ballerina program. +1. Now you can use the operations available within the connector. Note that they are in the form of remote operations. +Following is an example of how to execute a custom Apex REST endpoint using the connector. +```ballerina +public function main() returns error? { + string|error caseId = apexClient->apexRestExecute("Cases", "POST", + {"subject" : "Item Fault!", + "status" : "New", + "priority" : "High"}); + if caseId is error { + log:printError("Error occurred while creating the case."); + return caseId; + } + return; +} ``` -bal run -```` - -## Examples - -The `salesforce` connector provides practical examples illustrating usage in various scenarios. Explore these examples below, covering use cases like creating sObjects, retrieving records, and executing bulk operations. - -1. [Salesforce REST API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/rest_api_usecases) - How to employ REST API of Salesforce to carryout various tasks. - -2. [Salesforce Bulk API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/bulk_api_usecases) - How to employ Bulk API of Salesforce to execute Bulk jobs. -3. [Salesforce Bulk v2 API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/bulkv2_api_usecases) - How to employ Bulk v2 API to execute an ingest job. +2. Use `bal run` command to compile and run the Ballerina program. -4. [Salesforce APEX REST API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/apex_rest_api_usecases) - How to employ APEX REST API to create a case in Salesforce. +**[You can find a list of samples here](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/apex_rest_api_usecases/create_case)** diff --git a/ballerina/modules/apex/client.bal b/ballerina/modules/apex/client.bal index cfff7ad3..44b83383 100644 --- a/ballerina/modules/apex/client.bal +++ b/ballerina/modules/apex/client.bal @@ -19,20 +19,20 @@ import ballerina/jballerina.java; import ballerinax/'client.config; import ballerinax/salesforce.utils; -# Ballerina Salesforce connector provides the capability to access Salesforce REST API. -# This connector lets you to perform operations for SObjects, query using SOQL, search using SOSL, and describe SObjects -# and organizational data. +# Ballerina Salesforce Apex Client provides the capability to access Salesforce Apex REST API. +# This client allows you to perform operations for custom Apex REST endpoints, execute HTTP methods on these endpoints, +# and handle responses appropriately. public isolated client class Client { private final http:Client salesforceClient; private map sfLocators = {}; - # Initializes the connector. During initialization you can pass either http:BearerTokenConfig if you have a bearer - # token or http:OAuth2RefreshTokenGrantConfig if you have Oauth tokens. + # Initializes the Salesforce Apex Client. During initialization, you can pass either `http:BearerTokenConfig` + # if you have a bearer token or `http:OAuth2RefreshTokenGrantConfig` if you have OAuth tokens. # Create a Salesforce account and obtain tokens following - # [this guide](https://help.salesforce.com/articleView?id=remoteaccess_authenticate_overview.htm). + # [this guide](https://help.salesforce.com/articleView?id=remoteaccess_authenticate_overview.htm). # - # + salesforceConfig - Salesforce Connector configuration - # + return - `sfdc:Error` on failure of initialization or else `()` + # + config - Salesforce connector configuration + # + return - `error` on failure of initialization or else `()` public isolated function init(ConnectionConfig config) returns error? { http:Client|http:ClientError|error httpClientResult; http:ClientConfiguration httpClientConfig = check config:constructHTTPClientConfig(config); @@ -45,12 +45,12 @@ public isolated client class Client { } } - # Access Salesforce APEX resource. + # Executes an HTTP request on a Salesforce Apex resource. # - # + urlPath - URI path - # + methodType - HTTP method type - # + payload - Payload - # + returnType - The payload type, which is expected to be returned after data binding + # + urlPath - URI path of the Apex resource + # + methodType - HTTP method type (GET, POST, DELETE, PUT, PATCH) + # + payload - Payload to be sent with the request + # + returnType - The type of data expected to be returned after data binding # + return - `string|int|record{}` type if successful or else `error` isolated remote function apexRestExecute(string urlPath, http:Method methodType, record {} payload = {}, typedesc returnType = <>) diff --git a/ballerina/modules/apex/constants.bal b/ballerina/modules/apex/constants.bal index 7c907a49..319ea0b9 100644 --- a/ballerina/modules/apex/constants.bal +++ b/ballerina/modules/apex/constants.bal @@ -14,15 +14,8 @@ // specific language governing permissions and limitations // under the License. -//Latest API Version -# Constant field `API_VERSION`. Holds the value for the Salesforce API version. -public const string API_VERSION = "v59.0"; +# Error message for invalid client configuration parameters. (not public) +final string INVALID_CLIENT_CONFIG = "Invalid values provided for client configuration parameters."; -public const string INVALID_CLIENT_CONFIG = "Invalid values provided for client configuration parameters."; - -//Salesforce endpoints -# Constant field `BASE_PATH`. Holds the value for the Salesforce base path/URL. -const string BASE_PATH = "/services/data"; - -# Constant field `APEX_BASE_PATH`. Holds the value for the Salesforce Apex base path/URL. +# Holds the value for the Salesforce Apex base path/URL. final string APEX_BASE_PATH = string `/services/apexrest`; diff --git a/ballerina/modules/apex/tests/README.md b/ballerina/modules/apex/tests/README.md deleted file mode 100644 index ede4685d..00000000 --- a/ballerina/modules/apex/tests/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# Testing Ballerina Salesforce module - -**Obtaining Tokens** - -1. Visit [Salesforce](https://www.salesforce.com) and create a Salesforce Account. -2. Create a connected app and obtain the following credentials: - * Base URL (Endpoint) - * Access Token - * Client ID - * Client Secret - * Refresh Token - * Refresh Token URL - -Note:- When you are setting up the connected app, select the following scopes under Selected OAuth Scopes: - -* Access and manage your data (api) -* Perform requests on your behalf at any time (refresh_token, offline_access) -* Provide access to your data via the Web (web) - -3. Provide the client ID and client secret to obtain the refresh token and access token. For more information on - obtaining OAuth2 credentials, go to - [Salesforce documentation](https://help.salesforce.com/articleView?id=remoteaccess_authenticate_overview.htm). - -**Create external ID field in Salesforce** - -Since External ID field called `My_External_Id__c` is used in the tests, follow below steps to create this external ID -field in the salesforce. - -1. Log in to your salesforce account and go to the `Setup` by clicking on the settings icon in the right side of the - menu. -2. Then in the left side panel, under Platform tools click on the `Objects and Fields` and the click on - `Object Manager`. -3. In the Object Manager page click on the `Contact` since we are going to create a external field for Contact SObject. -4. In the Contact page click on the `Fields & Relationships` and click `New` in the right hand side. -5. Then select `Text` as the Data type and click `Next`. -6. Add `My_External_Id` for "Field Label" and `255` for "Length" and click `Next`. -7. At the end click `Save` and see whether external field is added successfully by checking `Fields & Relationships` - fields. - -**Select Objects for Change Notifications** - -To receive notifications for record changes, select the custom objects and supported standard objects that you are -interested in. From Setup, enter Change Data Capture in the Quick Find box, and click Change Data Capture. Select the -SObject which you want to listen for changes - - -**Running Tests** - -1. Create a `ballerina.conf` inside project root directory and replace values inside quotes (eg: ) with - appropriate values. - ``` - EP_URL="" - ACCESS_TOKEN="" - CLIENT_ID="" - CLIENT_SECRET="" - REFRESH_TOKEN="" - REFRESH_URL="" - SF_USERNAME="" - SF_PASSWORD="" - ``` -2. Run the following command inside repo root folder. - ```bash - $ ballerina test -a --sourceroot sfdc-connector - ``` - \ No newline at end of file diff --git a/ballerina/modules/apex/tests/test.bal b/ballerina/modules/apex/tests/test.bal index 229604d6..e0b2d3a7 100644 --- a/ballerina/modules/apex/tests/test.bal +++ b/ballerina/modules/apex/tests/test.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except @@ -14,7 +14,6 @@ // specific language governing permissions and limitations // under the License. - import ballerina/log; import ballerina/os; import ballerina/test; @@ -26,10 +25,6 @@ configurable string clientSecret = os:getEnv("CLIENT_SECRET"); configurable string refreshToken = os:getEnv("REFRESH_TOKEN"); configurable string refreshUrl = os:getEnv("REFRESH_URL"); configurable string baseUrl = os:getEnv("EP_URL"); -configurable string username = ""; -configurable string password = ""; - -string reportInstanceID = ""; // Using direct-token config for client configuration ConnectionConfig sfConfigRefreshCodeFlow = { @@ -42,27 +37,6 @@ ConnectionConfig sfConfigRefreshCodeFlow = { } }; -ConnectionConfig sfConfigPasswordFlow = { - baseUrl: baseUrl, - auth: { - password, - username, - tokenUrl: refreshUrl, - clientId: clientId, - clientSecret: clientSecret, - credentialBearer: "POST_BODY_BEARER" - } -}; - -ConnectionConfig sfConfigCredentialsFlow = { - baseUrl: baseUrl, - auth: { - clientId: clientId, - clientSecret: clientSecret, - tokenUrl: refreshUrl - } -}; - Client baseClient = check new (sfConfigRefreshCodeFlow); @test:Config { @@ -89,4 +63,3 @@ function testApex() returns error? { test:assertFail(msg = deleteResponse.message()); } } - diff --git a/ballerina/modules/apex/types.bal b/ballerina/modules/apex/types.bal index 974314cc..ffde1f0c 100644 --- a/ballerina/modules/apex/types.bal +++ b/ballerina/modules/apex/types.bal @@ -17,399 +17,13 @@ import ballerina/http; import ballerinax/'client.config; -# Represents status of the bulk jobs -public enum Status { - SUCCESSFUL_RESULTS = "successfulResults", - FAILED_RESULTS = "failedResults" -}; - -public enum JobStateEnum { - OPEN = "Open", - UPLOAD_COMPLETE = "UploadComplete", - IN_PROGRESS = "InProgress", - JOB_COMPLETE = "JobComplete", - ABORTED = "Aborted", - FAILED = "Failed" -}; - -public enum JobType { - BIG_OBJECT_INGEST = "BigObjectIngest", - CLASSIC = "Classic", - V2_INGEST = "V2Ingest" -}; - -public enum BulkOperation { - QUERY = "query", - INGEST = "ingest" -}; - -# Operation type of the bulk job. -public enum Operation { - INSERT = "insert", - UPDATE = "update", - DELETE = "delete", - UPSERT = "upsert", - HARD_DELETE = "hardDelete", - QUERY = "query" -}; - -public enum LineEndingEnum { - LF = "LF", - CRLF = "CRLF" -}; - -public enum ColumnDelimiterEnum { - BACKQUOTE, - CARET, - COMMA, - PIPE, - SEMICOLON, - TAB -}; - -# Represents the Salesforce client configuration. +# Represents the Salesforce Apex client configurations. +# +# + baseUrl - The Salesforce endpoint URL +# + auth - Configurations related to client authentication public type ConnectionConfig record {| *config:ConnectionConfig; - # The Salesforce endpoint URL string baseUrl; - # Configurations related to client authentication http:BearerTokenConfig|config:OAuth2RefreshTokenGrantConfig| config:OAuth2PasswordGrantConfig|config:OAuth2ClientCredentialsGrantConfig auth; |}; - -# Defines the Salesforce version type. -public type Version record { - # Label of the Salesforce version - string label; - # URL of the Salesforce version - string url; - # Salesforce version number - string 'version; -}; - -# Defines the Limit type to list limits information for your org. -public type Limit record {| - # The limit total for the org - int Max; - # The total number of calls or events left for the org - int Remaining; - json...; -|}; - -# Defines the Attribute type. -# Contains the attribute information of the resultant record. -public type Attribute record {| - # Type of the resultant record - string 'type; - # URL of the resultant record - string url?; -|}; - -# Metadata for your organization and available to the logged-in user. -public type OrganizationMetadata record {| - # Encoding - string encoding; - # Maximum batch size - int maxBatchSize; - # Available SObjects - SObjectMetaData[] sobjects; - json...; -|}; - -# Metadata for an SObject, including information about each field, URLs, and child relationships. -public type SObjectMetaData record {| - # SObject name - string name; - # Is createable - boolean createable; - # Is deletable - boolean deletable; - # Is updateable - boolean updateable; - # Is queryable - boolean queryable; - # SObject label - string label; - # SObject URLs - map urls; - json...; -|}; - - -# Basic info of a SObject. -public type SObjectBasicInfo record {| - # Metadata related to the SObject - SObjectMetaData objectDescribe; - json...; -|}; - -# Represent the Attributes at SObjectBasicInfo. -public type Attributes record { - # Type of the resultant record - string 'type; - # URL of the resultant record - string url; -}; - - -# Response of object creation. -public type CreationResponse record { - # Created object ID - string id; - # Array of errors - anydata[] errors; - # Success flag - boolean success; -}; - -# Represents a Report. -public type Report record { - # Unique report ID - string id; - # Report display name - string name; - # URL that returns report data - string url; - # URL that retrieves report metadata - string describeUrl; - # Information for each instance of the report that was run asynchronously. - string instancesUrl; -}; - -# Represents an instance of a Report. -public type ReportInstance record { - # Unique ID for a report instance - string id; - # Status of the report run - string status; - # Date and time when an instance of the report run was requested - string requestDate; - # Date, time when the instance of the report run finished - string? completionDate; - # URL where results of the report run for that instance are stored - string url; - # API name of the user that created the instance - string ownerId; - # Indicates if it is queryable - boolean queryable; - # Indicates if it has detailed data - boolean hasDetailRows; -}; - -# Represents attributes of instance of an asynchronous report run. -public type AsyncReportAttributes record { - # Unique ID for an instance of a report that was run - string id; - # Unique report ID - string reportId; - # Display name of the report - string reportName; - # Status of the report run - string status; - # API name of the user that created the instance - string ownerId; - # Date and time when an instance of the report run was requested - string requestDate; - # Format of the resource - string 'type; - # Date, time when the instance of the report run finished - string? completionDate; - # Error message if the instance run failed - string? errorMessage; - # Indicates if it is queryable - boolean queryable; -}; - -# Represents attributes of instance of synchronous report run. -public type SyncReportAttributes record { - # Unique report ID - string reportId; - # Display name of the report - string reportName; - # Format of the resource - string 'type; - # Resource URL to get report metadata - string describeUrl; - # Resource URL to run a report asynchronously - string instancesUrl; -}; - -# Represents result of an asynchronous report run. -public type ReportInstanceResult record { - # Attributes for the instance of the report run - AsyncReportAttributes|SyncReportAttributes attributes; - # Indicates if all report results are returned - boolean allData; - # Collection of summary level data or both detailed and summary level data - map? factMap; - # Collection of column groupings - map? groupingsAcross; - # Collection of row groupings - map? groupingsDown; - # Information about the fields used to build the report - map? reportMetadata; - # Indicates if it has detailed data - boolean hasDetailRows; - # Information on report groupings, summary fields, and detailed data columns - map? reportExtendedMetadata; -}; - -# Represent the metadata of deleted records. -public type DeletedRecordsResult record { - # Array of deleted records - record {|string deletedDate; string id;|}[] deletedRecords; - # The earliest date covered by the results - string earliestDateAvailable; - # The latest date covered by the results - string latestDateCovered; -}; - -# Represent the metadata of updated records. -public type UpdatedRecordsResults record { - # Array of updated record IDs - string[] ids; - # The latest date covered by the results - string latestDateCovered; -}; - -# Represent the password status. -public type PasswordStatus record{ - # Indicates whether the password is expired - boolean isExpired; -}; - - -# Represent the Error response for password access. -public type ErrorResponse record { - # Error message - string message; - # Error code - string errorCode; -}; - -# Represent a quick action. -public type QuickAction record { - # Action enum or ID - string actionEnumOrId; - # Action label - string label; - # Action name - string name; - # Action type - string 'type; - # Action URLs - record{string defaultValues?; string quickAction?; string describe?; string defaultValuesTemplate?;} urls; -}; - - -# Represent a batch execution result. -public type SubRequestResult record { - # Status code of the batch execution - int statusCode; - # Result of the batch execution - json? result; -}; - -# Represent Subrequest of a batch. -public type Subrequest record {| - # Subrequest of a batch - string binaryPartName?; - # Binary part name alias - string binaryPartNameAlias?; - # Method of the subrequest - string method; - # Rich input of the subrequest - record{} richInput?; - # URL of the subrequest - string url; -|}; - -# Represent results of the batch request. -public type BatchResult record { - # Indicates whether the batch request has errors - boolean hasErrors; - # Results of the batch request - SubRequestResult[] results; -}; - - - -# Represents the bulk job creation request payload. -public type BulkCreatePayload record { - # the sObject type of the bulk job - string 'object?; - # the operation type of the bulk job - Operation operation; - # the column delimiter of the payload - ColumnDelimiterEnum columnDelimiter?; - # the content type of the payload - string contentType?; - # the line ending of the payload - LineEndingEnum lineEnding?; - # the external ID field name for upsert operations - string externalIdFieldName?; - # the SOQL query for query operations - string query?; -}; - -# Represents the bulk job creation response. -public type BulkJob record { - *BulkJobCloseInfo; - # The URL to use for uploading the CSV data for the job. - string contentUrl?; - # The line ending of the payload. - string lineEnding?; - # The column delimiter of the payload. - string columnDelimiter?; -}; - -# Represents bulk job related information. -public type BulkJobInfo record { - *BulkJob; - # The number of times that Salesforce attempted to process the job. - int retries?; - # The total time spent processing the job. - int totalProcessingTime?; - # The total time spent processing the job by API. - int apiActiveProcessingTime?; - # The total time spent to process triggers and other processes related to the job data; - int apexProcessingTime?; - # The number of records already processed by the job. - int numberRecordsProcessed?; -}; - - -# Represents bulk job related information when Closed. -public type BulkJobCloseInfo record { - # The ID of the job. - string id; - # The operation type of the job. - string operation; - # The sObject type of the job. - string 'object; - # The ID of the user who created the job. - string createdById; - # The date and time when the job was created. - string createdDate; - # The date and time when the job was finished. - string systemModstamp; - # The state of the job. - string state; - # The concurrency mode of the job. - string concurrencyMode; - # The content type of the payload. - string contentType; - # The API version. - float apiVersion; -}; - - -# Represents output for get all jobs request -public type AllJobs record { - # Indicates whether there are more records to retrieve. - boolean done; - # Array of job records. - BulkJobInfo[] records; - # URL to retrieve the next set of records. - string nextRecordsUrl; -}; From 491469137fb1e20db5ba55d07aed06125aeae4f6 Mon Sep 17 00:00:00 2001 From: aashikam Date: Mon, 22 Jul 2024 05:07:46 +0530 Subject: [PATCH 07/11] Update API docs of bulkv2 module --- ballerina/constants.bal | 6 + ballerina/errors.bal | 10 +- ballerina/modules/apex/Module.md | 4 +- ballerina/modules/bulkv2/Module.md | 89 +++++------- ballerina/modules/bulkv2/client.bal | 30 ++-- ballerina/modules/bulkv2/constants.bal | 10 +- .../modules/bulkv2/tests/bulk_csv_abort.bal | 28 +--- .../modules/bulkv2/tests/bulk_csv_delete.bal | 5 +- .../modules/bulkv2/tests/bulk_csv_insert.bal | 3 +- .../modules/bulkv2/tests/bulk_csv_query.bal | 4 +- ballerina/modules/bulkv2/types.bal | 129 ++++++++++++------ ballerina/modules/bulkv2/utils.bal | 6 +- 12 files changed, 173 insertions(+), 151 deletions(-) diff --git a/ballerina/constants.bal b/ballerina/constants.bal index a465dd5f..8f1eab0e 100644 --- a/ballerina/constants.bal +++ b/ballerina/constants.bal @@ -16,6 +16,9 @@ //Latest API Version # Constant field `API_VERSION`. Holds the value for the Salesforce API version. +# # Deprecated +# This will be removed with the 9.0.0 release. +@deprecated public const string API_VERSION = "v59.0"; // For URL encoding @@ -111,6 +114,9 @@ const string QUESTION_MARK = "?"; const string EQUAL_SIGN = "="; # Constant field `EMPTY_STRING`. Holds the value of "". +# # Deprecated +# This will be removed with the 9.0.0 release. +@deprecated public const string EMPTY_STRING = ""; # Constant field `AMPERSAND`. Holds the value of "&". diff --git a/ballerina/errors.bal b/ballerina/errors.bal index f314a523..7dfdaa32 100644 --- a/ballerina/errors.bal +++ b/ballerina/errors.bal @@ -32,11 +32,19 @@ const string JSON_ACCESSING_ERROR_MSG = "Error occurred while accessing the JSON const string XML_ACCESSING_ERROR_MSG = "Error occurred while accessing the XML payload of the response."; const string TEXT_ACCESSING_ERROR_MSG = "Error occurred while accessing the Text payload of the response."; const string HTTP_CLIENT_ERROR = "Failed to establish the communication with the upstream server or a data binding failure. Refer error.cause() for more details"; +# # Deprecated +# This will be removed with the 9.0.0 release. +@deprecated public const string HTTP_ERROR_MSG = "Error occurred while getting the HTTP response."; const STATUS_CODE = "statusCode"; const HEADERS = "headers"; const BODY = "body"; - +# # Deprecated +# This will be removed with the 9.0.0 release. +@deprecated public const string ERR_EXTRACTING_ERROR_MSG = "Error occured while extracting errors from payload."; +# # Deprecated +# This will be removed with the 9.0.0 release. +@deprecated public const string INVALID_CLIENT_CONFIG = "Invalid values provided for client configuration parameters."; diff --git a/ballerina/modules/apex/Module.md b/ballerina/modules/apex/Module.md index 33cb00f5..34e28e04 100644 --- a/ballerina/modules/apex/Module.md +++ b/ballerina/modules/apex/Module.md @@ -93,4 +93,6 @@ public function main() returns error? { 2. Use `bal run` command to compile and run the Ballerina program. -**[You can find a list of samples here](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/apex_rest_api_usecases/create_case)** +## Examples + +1. [Salesforce APEX REST API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/apex_rest_api_usecases) - How to employ APEX REST API to create a case in Salesforce. diff --git a/ballerina/modules/bulkv2/Module.md b/ballerina/modules/bulkv2/Module.md index 7d5e9c38..f9b0a5d2 100644 --- a/ballerina/modules/bulkv2/Module.md +++ b/ballerina/modules/bulkv2/Module.md @@ -1,67 +1,59 @@ ## Overview -Salesforce Sales Cloud is one of the leading Customer Relationship Management(CRM) software, provided by Salesforce.Inc. Salesforce enable users to efficiently manage sales and customer relationships through its APIs, robust and secure databases, and analytics services. Sales cloud provides serveral API packages to make operations on sObjects and metadata, execute queries and searches, and listen to change events through API calls using REST, SOAP, and CometD protocols. - -Ballerina Salesforce connector supports [Salesforce v59.0 REST API](https://developer.salesforce.com/docs/atlas.en-us.224.0.api_rest.meta/api_rest/intro_what_is_rest_api.htm), [Salesforce v59.0 SOAP API](https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_quickstart_intro.htm), [Salesforce v59.0 APEX REST API](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_rest_intro.htm), [Salesforce v59.0 BULK API](https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/api_asynch_introduction_bulk_api.htm), and [Salesforce v59.0 BULK V2 API](https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/bulk_api_2_0.htm). +Salesforce Bulk API 2.0 enables you to handle large data sets asynchronously, optimizing performance for high-volume data operations. This module provides operations for executing bulk jobs and batches, allowing you to perform various data operations efficiently. ## Setup guide -1. Create a Salesforce account with the REST capability. +1. Create a Salesforce Account with the Bulk API 2.0 Capability. -2. Go to Setup --> Apps --> App Manager +2. Go to Setup --> Apps --> App Manager - Setup Side Panel + Setup Side Panel 3. Create a New Connected App. - Create Connected Apps + Create Connected Apps - Here we will be using https://test.salesforce.com as we are using sandbox environment. Users can use https://login.salesforce.com for normal usage. - Create Connected Apps + Create Connected Apps 4. After the creation user can get consumer key and secret through clicking on the `Manage Consumer Details` button. - Consumer Secrets - -5. Next step would be to get the token. - - Log in to salesforce in your preferred browser and enter the following url. - ``` - https://.salesforce.com/services/oauth2/authorize?response_type=code&client_id=&redirect_uri= - ``` - - Allow access if an alert pops up and the browser will be redirected to a Url like follows. - - ``` - https://login.salesforce.com/?code= - ``` - - - The code can be obtained after decoding the encoded code + Consumer Secrets + +5. The next step is to get the token. + + - Log in to Salesforce in your preferred browser and enter the following URL: + `https://.salesforce.com/services/oauth2/authorize?response_type=code&client_id=&redirect_uri=` + - Allow access if an alert pops up, and the browser will be redirected to a URL like the following: + `https://login.salesforce.com/?code=` + + - The code can be obtained after decoding the encoded code 6. Get Access and Refresh tokens - - Following request can be sent to obtain the tokens. - - ``` - curl -X POST https://.salesforce.com/services/oauth2/token?code=&grant_type=authorization_code&client_id=&client_secret=&redirect_uri=https://test.salesforce.com/ - ``` - - Tokens can be obtained from the response. + + - The following request can be sent to obtain the tokens. + ```curl -X POST https://.salesforce.com/services/oauth2/token?code=&grant_type=authorization_code&client_id=&client_secret=&redirect_uri=https://test.salesforce.com/``` + - Tokens can be obtained from the response. ## Quickstart -To use the Salesforce connector in your Ballerina application, modify the .bal file as follows: +To use the Salesforce Bulk API client in your Ballerina application, update the .bal file as follows: #### Step 1: Import connector -Import the `ballerinax/salesforce` package into the Ballerina project. +Import the `ballerinax/salesforce.bulkv2` package into the Ballerina project. ```ballerina -import ballerinax/salesforce; +import ballerinax/salesforce.bulkv2; ``` #### Step 2: Create a new connector instance Create a `salesforce:ConnectionConfig` with the obtained OAuth2 tokens and initialize the connector with it. ```ballerina -salesforce:ConnectionConfig config = { +bulkv2:ConnectionConfig config = { baseUrl: baseUrl, auth: { clientId: clientId, @@ -71,23 +63,24 @@ salesforce:ConnectionConfig config = { } }; -salesforce:Client salesforce = check new (config); +bulkv2:Client bulkv2Client = check new (config); ``` #### Step 3: Invoke connector operation -1. Now you can utilize the available operations. Note that they are in the form of remote operations. - -Following is an example on how to create a record using the connector. +1. Now you can use the operations available within the connector. Note that they are in the form of remote operations. - ```ballerina - salesforce:CreationResponse response = check - salesforce->create("Account", { - "Name": "IT World", - "BillingCity": "New York" - }); +Following is an example of how to create a bulk job using the connector. - ``` +```ballerina +bulkv2:BulkCreatePayload payload = { + 'object : "Contact", + contentType : "CSV", + operation : "insert", + lineEnding : "LF" +}; +error|bulkv2:BulkJob insertJob = baseClient->createIngestJob(payload); +``` 2. Use following command to compile and run the Ballerina program. @@ -97,12 +90,4 @@ bal run ## Examples -The `salesforce` connector provides practical examples illustrating usage in various scenarios. Explore these examples below, covering use cases like creating sObjects, retrieving records, and executing bulk operations. - -1. [Salesforce REST API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/rest_api_usecases) - How to employ REST API of Salesforce to carryout various tasks. - -2. [Salesforce Bulk API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/bulk_api_usecases) - How to employ Bulk API of Salesforce to execute Bulk jobs. - -3. [Salesforce Bulk v2 API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/bulkv2_api_usecases) - How to employ Bulk v2 API to execute an ingest job. - -4. [Salesforce APEX REST API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/apex_rest_api_usecases) - How to employ APEX REST API to create a case in Salesforce. +1. [Salesforce Bulk v2 API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/bulkv2_api_usecases) - How to employ Bulk v2 API to execute an ingest job. diff --git a/ballerina/modules/bulkv2/client.bal b/ballerina/modules/bulkv2/client.bal index e8559885..603e357a 100644 --- a/ballerina/modules/bulkv2/client.bal +++ b/ballerina/modules/bulkv2/client.bal @@ -20,15 +20,15 @@ import ballerina/lang.runtime; import ballerinax/'client.config; import ballerinax/salesforce.utils; -# Ballerina Salesforce connector provides the capability to access Salesforce REST API. -# This connector lets you to perform operations for SObjects, query using SOQL, search using SOSL, and describe SObjects -# and organizational data. +# Ballerina Salesforce Bulk v2 Client provides the capability to access Salesforce Bulk API v2. +# This client allows you to perform bulk data operations such as creating, querying, updating, and deleting large volumes of data. +# You can create and manage bulk jobs, upload data, check job status, and retrieve job results efficiently. public isolated client class Client { private final http:Client salesforceClient; private map sfLocators = {}; - # Initializes the connector. During initialization you can pass either http:BearerTokenConfig if you have a bearer + # Initializes the Bulk V2 client. During initialization you can pass either http:BearerTokenConfig if you have a bearer # token or http:OAuth2RefreshTokenGrantConfig if you have Oauth tokens. # Create a Salesforce account and obtain tokens following # [this guide](https://help.salesforce.com/articleView?id=remoteaccess_authenticate_overview.htm). @@ -109,8 +109,8 @@ public isolated client class Client { # Uploads data for a job using CSV data. # # + bulkJobId - Id of the bulk job - # + content - CSV data to be added - # + return - `Nil` record if successful or `error` if unsuccessful + # + content - CSV data to be uploaded + # + return - `()` if successful or else `error` isolated remote function addBatch(string bulkJobId, string|string[][]|stream|io:ReadableByteChannel content) returns error? { string payload = ""; string path = utils:prepareUrl([API_BASE_PATH, JOBS, INGEST, bulkJobId, BATCHES]); @@ -130,7 +130,7 @@ public isolated client class Client { # Get details of all the jobs. # # + jobType - Type of the job - # + return - `AllJobs` record if successful or `error` if unsuccessful + # + return - `AllJobs` record if successful or else `error` isolated remote function getAllJobs(JobType? jobType = ()) returns error|AllJobs { string path = utils:prepareUrl([API_BASE_PATH, JOBS, INGEST]) + ((jobType is ()) ? "" : string `?jobType=${jobType}`); @@ -140,7 +140,7 @@ public isolated client class Client { # Get details of all query jobs. # # + jobType - Type of the job - # + return - `AllJobs` if successful else `error` + # + return - `AllJobs` if successful or else `error` isolated remote function getAllQueryJobs(JobType? jobType = ()) returns error|AllJobs { string path = utils:prepareUrl([API_BASE_PATH, JOBS, INGEST]) + ((jobType is ()) ? "" : string `?jobType=${jobType}`); @@ -151,7 +151,7 @@ public isolated client class Client { # # + status - Status of the job # + bulkJobId - Id of the bulk job - # + return - `string[][]` if successful else `error` + # + return - `string[][]` if successful or else `error` isolated remote function getJobStatus(string bulkJobId, Status status) returns string[][]|error { string path = utils:prepareUrl([API_BASE_PATH, JOBS, INGEST, bulkJobId, status]); @@ -171,11 +171,11 @@ public isolated client class Client { } - # Get bulk query job results + # Get bulk query job results. # # + bulkJobId - Id of the bulk job # + maxRecords - The maximum number of records to retrieve per set of results for the query - # + return - The resulting string[][] if successful else `error` + # + return - The resulting string[][] if successful or else `error` isolated remote function getQueryResult(string bulkJobId, int? maxRecords = ()) returns string[][]|error { string path = ""; @@ -236,7 +236,7 @@ public isolated client class Client { # # + bulkJobId - Id of the bulk job # + bulkOperation - The processing operation for the job - # + return - `()` if successful else `error` + # + return - `()` if successful or else `error` isolated remote function abortJob(string bulkJobId, BulkOperation bulkOperation) returns BulkJobInfo|error { string path = utils:prepareUrl([API_BASE_PATH, JOBS, bulkOperation, bulkJobId]); record {} payload = {"state": "Aborted"}; @@ -247,7 +247,7 @@ public isolated client class Client { # # + bulkJobId - Id of the bulk job # + bulkOperation - The processing operation for the job - # + return - `()` if successful else `error` + # + return - `()` if successful or else `error` isolated remote function deleteJob(string bulkJobId, BulkOperation bulkOperation) returns error? { string path = utils:prepareUrl([API_BASE_PATH, JOBS, bulkOperation, bulkJobId]); return check self.salesforceClient->delete(path); @@ -256,7 +256,7 @@ public isolated client class Client { # Notifies Salesforce servers that the upload of job data is complete. # # + bulkJobId - Id of the bulk job - # + return - future if successful else `error` + # + return - future if successful or else `error` isolated remote function closeIngestJobAndWait(string bulkJobId) returns error|future { final string path = utils:prepareUrl([API_BASE_PATH, JOBS, INGEST, bulkJobId]); record {} payload = {"state": "UploadComplete"}; @@ -286,7 +286,7 @@ public isolated client class Client { # Notifies Salesforce servers that the upload of job data is complete. # # + bulkJobId - Id of the bulk job - # + return - BulkJobInfo if successful else `error` + # + return - BulkJobInfo if successful or else `error` isolated remote function closeIngestJob(string bulkJobId) returns error|BulkJobCloseInfo { final string path = utils:prepareUrl([API_BASE_PATH, JOBS, INGEST, bulkJobId]); record {} payload = {"state": "UploadComplete"}; diff --git a/ballerina/modules/bulkv2/constants.bal b/ballerina/modules/bulkv2/constants.bal index 67ec7ba0..f5850780 100644 --- a/ballerina/modules/bulkv2/constants.bal +++ b/ballerina/modules/bulkv2/constants.bal @@ -14,11 +14,13 @@ // specific language governing permissions and limitations // under the License. -//Latest API Version -# Constant field `API_VERSION`. Holds the value for the Salesforce API version. -public const string API_VERSION = "v59.0"; +// None is public -public const string INVALID_CLIENT_CONFIG = "Invalid values provided for client configuration parameters."; +# Holds the value for the latest Salesforce API version used. +const string API_VERSION = "v59.0"; + +# Error message for invalid client configuration parameters. (not public) +const string INVALID_CLIENT_CONFIG = "Invalid values provided for client configuration parameters."; //Salesforce endpoints # Constant field `BASE_PATH`. Holds the value for the Salesforce base path/URL. diff --git a/ballerina/modules/bulkv2/tests/bulk_csv_abort.bal b/ballerina/modules/bulkv2/tests/bulk_csv_abort.bal index 01b2d653..4f6b158b 100644 --- a/ballerina/modules/bulkv2/tests/bulk_csv_abort.bal +++ b/ballerina/modules/bulkv2/tests/bulk_csv_abort.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except @@ -13,6 +13,7 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. + import ballerina/log; import ballerina/test; import ballerina/os; @@ -23,10 +24,6 @@ configurable string clientSecret = os:getEnv("CLIENT_SECRET"); configurable string refreshToken = os:getEnv("REFRESH_TOKEN"); configurable string refreshUrl = os:getEnv("REFRESH_URL"); configurable string baseUrl = os:getEnv("EP_URL"); -configurable string username = ""; -configurable string password = ""; - -string reportInstanceID = ""; // Using direct-token config for client configuration ConnectionConfig sfConfigRefreshCodeFlow = { @@ -39,27 +36,6 @@ ConnectionConfig sfConfigRefreshCodeFlow = { } }; -ConnectionConfig sfConfigPasswordFlow = { - baseUrl: baseUrl, - auth: { - password, - username, - tokenUrl: refreshUrl, - clientId: clientId, - clientSecret: clientSecret, - credentialBearer: "POST_BODY_BEARER" - } -}; - -ConnectionConfig sfConfigCredentialsFlow = { - baseUrl: baseUrl, - auth: { - clientId: clientId, - clientSecret: clientSecret, - tokenUrl: refreshUrl - } -}; - Client baseClient = check new (sfConfigRefreshCodeFlow); @test:Config { diff --git a/ballerina/modules/bulkv2/tests/bulk_csv_delete.bal b/ballerina/modules/bulkv2/tests/bulk_csv_delete.bal index bcaedcbb..528b3ea2 100644 --- a/ballerina/modules/bulkv2/tests/bulk_csv_delete.bal +++ b/ballerina/modules/bulkv2/tests/bulk_csv_delete.bal @@ -1,5 +1,4 @@ -import ballerina/lang.runtime; -// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except @@ -14,6 +13,8 @@ import ballerina/lang.runtime; // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. + +import ballerina/lang.runtime; import ballerina/log; import ballerina/test; diff --git a/ballerina/modules/bulkv2/tests/bulk_csv_insert.bal b/ballerina/modules/bulkv2/tests/bulk_csv_insert.bal index 796778bf..1e21fccd 100644 --- a/ballerina/modules/bulkv2/tests/bulk_csv_insert.bal +++ b/ballerina/modules/bulkv2/tests/bulk_csv_insert.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except @@ -13,6 +13,7 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. + import ballerina/io; import ballerina/lang.runtime; import ballerina/log; diff --git a/ballerina/modules/bulkv2/tests/bulk_csv_query.bal b/ballerina/modules/bulkv2/tests/bulk_csv_query.bal index 58db85a3..ff1a46a7 100644 --- a/ballerina/modules/bulkv2/tests/bulk_csv_query.bal +++ b/ballerina/modules/bulkv2/tests/bulk_csv_query.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except @@ -63,7 +63,6 @@ function queryCsv() returns error? { } } - @test:Config { enable: true, dependsOn: [insertCsvFromFile, insertCsv, insertCsvStringArrayFromFile, insertCsvStreamFromFile] @@ -120,7 +119,6 @@ function queryWithLowerMaxRecordsValue() returns error? { } } - @test:Config { enable: true, dependsOn: [insertCsvFromFile, insertCsv, insertCsvStringArrayFromFile, insertCsvStreamFromFile] diff --git a/ballerina/modules/bulkv2/types.bal b/ballerina/modules/bulkv2/types.bal index b0628cfd..e9313a2b 100644 --- a/ballerina/modules/bulkv2/types.bal +++ b/ballerina/modules/bulkv2/types.bal @@ -17,12 +17,23 @@ import ballerina/http; import ballerinax/'client.config; -# Represents status of the bulk jobs +# Represents the status of the bulk jobs. +# +# + SUCCESSFUL_RESULTS - Indicates successful results +# + FAILED_RESULTS - Indicates failed results public enum Status { SUCCESSFUL_RESULTS = "successfulResults", FAILED_RESULTS = "failedResults" }; +# Represents the state of the job. +# +# + OPEN - The job is open +# + UPLOAD_COMPLETE - The upload is complete +# + IN_PROGRESS - The job is in progress +# + JOB_COMPLETE - The job is complete +# + ABORTED - The job is aborted +# + FAILED - The job has failed public enum JobStateEnum { OPEN = "Open", UPLOAD_COMPLETE = "UploadComplete", @@ -32,18 +43,34 @@ public enum JobStateEnum { FAILED = "Failed" }; +# Represents the type of job. +# +# + BIG_OBJECT_INGEST - Big object ingest job type +# + CLASSIC - Classic job type +# + V2_INGEST - V2 ingest job type public enum JobType { BIG_OBJECT_INGEST = "BigObjectIngest", CLASSIC = "Classic", V2_INGEST = "V2Ingest" }; +# Represents the bulk operation type. +# +# + QUERY - Query operation +# + INGEST - Ingest operation public enum BulkOperation { QUERY = "query", INGEST = "ingest" }; -# Operation type of the bulk job. +# Represents the operation type of the bulk job. +# +# + INSERT - Insert operation +# + UPDATE - Update operation +# + DELETE - Delete operation +# + UPSERT - Upsert operation +# + HARD_DELETE - Hard delete operation +# + QUERY - Query operation public enum Operation { INSERT = "insert", UPDATE = "update", @@ -51,13 +78,25 @@ public enum Operation { UPSERT = "upsert", HARD_DELETE = "hardDelete", QUERY = "query" -}; +}; +# Represents the line ending type. +# +# + LF - Line feed +# + CRLF - Carriage return and line feed public enum LineEndingEnum { LF = "LF", CRLF = "CRLF" }; +# Represents the column delimiter type. +# +# + BACKQUOTE - Backquote delimiter +# + CARET - Caret delimiter +# + COMMA - Comma delimiter +# + PIPE - Pipe delimiter +# + SEMICOLON - Semicolon delimiter +# + TAB - Tab delimiter public enum ColumnDelimiterEnum { BACKQUOTE, CARET, @@ -68,110 +107,114 @@ public enum ColumnDelimiterEnum { }; # Represents the Salesforce client configuration. +# +# + baseUrl - The Salesforce endpoint URL +# + auth - Configurations related to client authentication public type ConnectionConfig record {| *config:ConnectionConfig; - # The Salesforce endpoint URL string baseUrl; - # Configurations related to client authentication http:BearerTokenConfig|config:OAuth2RefreshTokenGrantConfig| config:OAuth2PasswordGrantConfig|config:OAuth2ClientCredentialsGrantConfig auth; |}; # Defines the Salesforce version type. +# +# + label - Label of the Salesforce version +# + url - URL of the Salesforce version +# + version - Salesforce version number public type Version record { - # Label of the Salesforce version string label; - # URL of the Salesforce version string url; - # Salesforce version number string 'version; }; # Defines the Limit type to list limits information for your org. +# +# + Max - The limit total for the org +# + Remaining - The total number of calls or events left for the org public type Limit record {| - # The limit total for the org int Max; - # The total number of calls or events left for the org int Remaining; json...; |}; # Represents the bulk job creation request payload. +# +# + object - The sObject type of the bulk job +# + operation - The operation type of the bulk job +# + columnDelimiter - The column delimiter of the payload +# + contentType - The content type of the payload +# + lineEnding - The line ending of the payload +# + externalIdFieldName - The external ID field name for upsert operations +# + query - The SOQL query for query operations public type BulkCreatePayload record { - # the sObject type of the bulk job string 'object?; - # the operation type of the bulk job Operation operation; - # the column delimiter of the payload ColumnDelimiterEnum columnDelimiter?; - # the content type of the payload string contentType?; - # the line ending of the payload LineEndingEnum lineEnding?; - # the external ID field name for upsert operations string externalIdFieldName?; - # the SOQL query for query operations string query?; }; # Represents the bulk job creation response. +# +# + contentUrl - The URL to use for uploading the CSV data for the job +# + lineEnding - The line ending of the payload +# + columnDelimiter - The column delimiter of the payload public type BulkJob record { *BulkJobCloseInfo; - # The URL to use for uploading the CSV data for the job. string contentUrl?; - # The line ending of the payload. string lineEnding?; - # The column delimiter of the payload. string columnDelimiter?; }; # Represents bulk job related information. +# +# + retries - The number of times that Salesforce attempted to process the job +# + totalProcessingTime - The total time spent processing the job +# + apiActiveProcessingTime - The total time spent processing the job by API +# + apexProcessingTime - The total time spent to process triggers and other processes related to the job data +# + numberRecordsProcessed - The number of records already processed by the job public type BulkJobInfo record { *BulkJob; - # The number of times that Salesforce attempted to process the job. int retries?; - # The total time spent processing the job. int totalProcessingTime?; - # The total time spent processing the job by API. int apiActiveProcessingTime?; - # The total time spent to process triggers and other processes related to the job data; int apexProcessingTime?; - # The number of records already processed by the job. int numberRecordsProcessed?; }; - # Represents bulk job related information when Closed. +# +# + id - The ID of the job +# + operation - The operation type of the job +# + object - The sObject type of the job +# + createdById - The ID of the user who created the job +# + createdDate - The date and time when the job was created +# + systemModstamp - The date and time when the job was finished +# + state - The state of the job +# + concurrencyMode - The concurrency mode of the job +# + contentType - The content type of the payload +# + apiVersion - The API version public type BulkJobCloseInfo record { - # The ID of the job. string id; - # The operation type of the job. string operation; - # The sObject type of the job. string 'object; - # The ID of the user who created the job. string createdById; - # The date and time when the job was created. string createdDate; - # The date and time when the job was finished. string systemModstamp; - # The state of the job. string state; - # The concurrency mode of the job. string concurrencyMode; - # The content type of the payload. - string contentType; - # The API version. - float apiVersion; }; - -# Represents output for get all jobs request +# Represents the output of the get all jobs request. +# +# + done - Indicates whether there are more records to retrieve +# + records - Array of job records +# + nextRecordsUrl - URL to retrieve the next set of records public type AllJobs record { - # Indicates whether there are more records to retrieve. boolean done; - # Array of job records. BulkJobInfo[] records; - # URL to retrieve the next set of records. string nextRecordsUrl; }; diff --git a/ballerina/modules/bulkv2/utils.bal b/ballerina/modules/bulkv2/utils.bal index 6ae31cfc..466c4bb0 100644 --- a/ballerina/modules/bulkv2/utils.bal +++ b/ballerina/modules/bulkv2/utils.bal @@ -21,7 +21,7 @@ import ballerina/lang.'string as strings; isolated string csvContent = EMPTY_STRING; -# Convert ReadableByteChannel to string. +# Util method to convert ReadableByteChannel to string. # # + rbc - ReadableByteChannel # + return - converted string @@ -51,9 +51,9 @@ isolated function convertToString(io:ReadableByteChannel rbc) returns string|err return textContent; } -# Convert string[][] to string. +# Util method to convert string[][] to string. # -# + stringCsvInput - Multi dimentional array of strings +# + stringCsvInput - Multi-dimensional array of strings # + return - converted string isolated function convertStringListToString(string[][]|stream stringCsvInput) returns string|error { lock { From 64a83306796080d7ad557741233df5b24fd5225c Mon Sep 17 00:00:00 2001 From: aashikam Date: Mon, 22 Jul 2024 05:08:14 +0530 Subject: [PATCH 08/11] Update version to 8.1.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index bec2bc74..d8d09706 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.caching=true group=io.ballerina.lib -version=8.0.3-SNAPSHOT +version=8.1.0-SNAPSHOT checkstylePluginVersion=10.12.0 spotbugsPluginVersion=5.0.14 From 7cff030d499690ec9f2dada4fd08b07e27f9dfdf Mon Sep 17 00:00:00 2001 From: aashikam Date: Mon, 22 Jul 2024 05:08:55 +0530 Subject: [PATCH 09/11] [Automated] Update the toml files --- ballerina/Ballerina.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 19745435..9a673e4f 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -2,7 +2,7 @@ distribution = "2201.8.0" org = "ballerinax" name = "salesforce" -version = "8.0.2" +version = "8.1.0" export = ["salesforce", "salesforce.bulk", "salesforce.soap","salesforce.bulkv2", "salesforce.apex"] license= ["Apache-2.0"] authors = ["Ballerina"] @@ -17,10 +17,10 @@ observabilityIncluded = true graalvmCompatible = true [[platform.java17.dependency]] -path = "../native/build/libs/salesforce-native-8.0.2.jar" +path = "../native/build/libs/salesforce-native-8.1.0-SNAPSHOT.jar" groupId = "io.ballerinax" artifactId = "salesforce" -version = "8.0.2" +version = "8.1.0-SNAPSHOT" [[platform.java17.dependency]] groupId = "com.opencsv" From 37cae699cfb07b3111bb3600215937451f89d7a4 Mon Sep 17 00:00:00 2001 From: aashikam Date: Wed, 24 Jul 2024 07:57:05 +0530 Subject: [PATCH 10/11] Add changes from code review --- ballerina/client.bal | 28 +++++----- ballerina/modules/apex/Module.md | 24 ++++----- ballerina/modules/bulkv2/Module.md | 10 ++-- ballerina/modules/bulkv2/tests/README.md | 65 ------------------------ ballerina/modules/bulkv2/types.bal | 4 +- 5 files changed, 30 insertions(+), 101 deletions(-) delete mode 100644 ballerina/modules/bulkv2/tests/README.md diff --git a/ballerina/client.bal b/ballerina/client.bal index 2940678b..7884ac7d 100644 --- a/ballerina/client.bal +++ b/ballerina/client.bal @@ -484,7 +484,7 @@ public isolated client class Client { # + returnType - The payload type, which is expected to be returned after data binding # + return - `string|int|record{}` type if successful or else `error` # # Deprecated - # This function is deprecated due to the introduction of the new submodule salesforce.apex, which supports this API. + # This function is deprecated due to the introduction of the new submodule salesforce.apex, which supports the same functionality. # This API will be removed with the 9.0.0 release. @deprecated isolated remote function apexRestExecute(string urlPath, http:Method methodType, @@ -537,7 +537,7 @@ public isolated client class Client { # + payload - The payload for the bulk job # + return - `BulkJob` if successful or else `error` # # Deprecated - # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports the same functionality. # This API will be removed with the 9.0.0 release. @deprecated isolated remote function createIngestJob(BulkCreatePayload payload) returns BulkJob|error { @@ -550,7 +550,7 @@ public isolated client class Client { # + payload - The payload for the bulk job # + return - `BulkJob` if successful or else `error` # # Deprecated - # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports the same functionality. # This API will be removed with the 9.0.0 release. @deprecated isolated remote function createQueryJob(BulkCreatePayload payload) returns BulkJob|error { @@ -563,7 +563,7 @@ public isolated client class Client { # + payload - The payload for the bulk job # + return - `future` if successful else `error` # # Deprecated - # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports the same functionality. # This API will be removed with the 9.0.0 release. @deprecated isolated remote function createQueryJobAndWait(BulkCreatePayload payload) returns future|error { @@ -599,7 +599,7 @@ public isolated client class Client { # + bulkOperation - The processing operation for the job # + return - `BulkJobInfo` if successful or else `error` # # Deprecated - # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports the same functionality. # This API will be removed with the 9.0.0 release. @deprecated isolated remote function getJobInfo(string bulkJobId, BulkOperation bulkOperation) returns BulkJobInfo|error { @@ -613,7 +613,7 @@ public isolated client class Client { # + content - CSV data to be added # + return - `Nil` record if successful or `error` if unsuccessful # # Deprecated - # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports the same functionality. # This API will be removed with the 9.0.0 release. @deprecated isolated remote function addBatch(string bulkJobId, string|string[][]|stream|io:ReadableByteChannel content) returns error? { @@ -637,7 +637,7 @@ public isolated client class Client { # + jobType - Type of the job # + return - `AllJobs` record if successful or `error` if unsuccessful # # Deprecated - # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports the same functionality. # This API will be removed with the 9.0.0 release. @deprecated isolated remote function getAllJobs(JobType? jobType = ()) returns error|AllJobs { @@ -651,7 +651,7 @@ public isolated client class Client { # + jobType - Type of the job # + return - `AllJobs` if successful else `error` # # Deprecated - # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports the same functionality. # This API will be removed with the 9.0.0 release. @deprecated isolated remote function getAllQueryJobs(JobType? jobType = ()) returns error|AllJobs { @@ -666,7 +666,7 @@ public isolated client class Client { # + bulkJobId - Id of the bulk job # + return - `string[][]` if successful else `error` # # Deprecated - # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports the same functionality. # This API will be removed with the 9.0.0 release. @deprecated isolated remote function getJobStatus(string bulkJobId, Status status) @@ -694,7 +694,7 @@ public isolated client class Client { # + maxRecords - The maximum number of records to retrieve per set of results for the query # + return - The resulting string[][] if successful else `error` # # Deprecated - # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports the same functionality. # This API will be removed with the 9.0.0 release. @deprecated isolated remote function getQueryResult(string bulkJobId, int? maxRecords = ()) returns string[][]|error { @@ -759,7 +759,7 @@ public isolated client class Client { # + bulkOperation - The processing operation for the job # + return - `()` if successful else `error` # # Deprecated - # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports the same functionality. # This API will be removed with the 9.0.0 release. @deprecated isolated remote function abortJob(string bulkJobId, BulkOperation bulkOperation) returns BulkJobInfo|error { @@ -774,7 +774,7 @@ public isolated client class Client { # + bulkOperation - The processing operation for the job # + return - `()` if successful else `error` # # Deprecated - # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports the same functionality. # This API will be removed with the 9.0.0 release. @deprecated isolated remote function deleteJob(string bulkJobId, BulkOperation bulkOperation) returns error? { @@ -787,7 +787,7 @@ public isolated client class Client { # + bulkJobId - Id of the bulk job # + return - future if successful else `error` # # Deprecated - # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports the same functionality. # This API will be removed with the 9.0.0 release. @deprecated isolated remote function closeIngestJobAndWait(string bulkJobId) returns error|future { @@ -821,7 +821,7 @@ public isolated client class Client { # + bulkJobId - Id of the bulk job # + return - BulkJobInfo if successful else `error` # # Deprecated - # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports this API. + # This function is deprecated due to the introduction of the new submodule salesforce.bulkv2, which supports the same functionality. # This API will be removed with the 9.0.0 release. @deprecated isolated remote function closeIngestJob(string bulkJobId) returns error|BulkJobCloseInfo { diff --git a/ballerina/modules/apex/Module.md b/ballerina/modules/apex/Module.md index 34e28e04..fb427d4f 100644 --- a/ballerina/modules/apex/Module.md +++ b/ballerina/modules/apex/Module.md @@ -1,7 +1,9 @@ ## Overview Salesforce Apex REST API enables you to expose your Apex classes and methods as RESTful web services. This module provides operations for executing custom Apex REST endpoints, allowing you to perform various HTTP operations on these endpoints and handle responses accordingly. - + +Ballerina Salesforce Apex REST API client supports the [Salesforce v59.0 APEX REST API](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_rest_intro.htm). + ## Setup guide 1. Create a Salesforce account with the REST capability. @@ -53,12 +55,6 @@ import ballerinax/salesforce.apex; Create a `ConnectionConfig` with the OAuth2 tokens obtained, and initialize the connector with it. ```ballerina -configurable string clientId = ?; -configurable string clientSecret = ?; -configurable string refreshToken = ?; -configurable string refreshUrl = ?; -configurable string baseUrl = ?; - apex:ConnectionConfig sfConfig = { baseUrl: baseUrl, auth: { @@ -79,14 +75,12 @@ Following is an example of how to execute a custom Apex REST endpoint using the ```ballerina public function main() returns error? { - string|error caseId = apexClient->apexRestExecute("Cases", "POST", - {"subject" : "Item Fault!", - "status" : "New", - "priority" : "High"}); - if caseId is error { - log:printError("Error occurred while creating the case."); - return caseId; - } + string caseId = check apexClient->apexRestExecute("Cases", "POST", + { + "subject": "Item Fault!", + "status": "New", + "priority": "High" + }); return; } ``` diff --git a/ballerina/modules/bulkv2/Module.md b/ballerina/modules/bulkv2/Module.md index f9b0a5d2..b1389f73 100644 --- a/ballerina/modules/bulkv2/Module.md +++ b/ballerina/modules/bulkv2/Module.md @@ -74,12 +74,12 @@ Following is an example of how to create a bulk job using the connector. ```ballerina bulkv2:BulkCreatePayload payload = { - 'object : "Contact", - contentType : "CSV", - operation : "insert", - lineEnding : "LF" + 'object: "Contact", + contentType: "CSV", + operation: "insert", + lineEnding: "LF" }; -error|bulkv2:BulkJob insertJob = baseClient->createIngestJob(payload); +bulkv2:BulkJob insertJob = check baseClient->createIngestJob(payload); ``` 2. Use following command to compile and run the Ballerina program. diff --git a/ballerina/modules/bulkv2/tests/README.md b/ballerina/modules/bulkv2/tests/README.md deleted file mode 100644 index ede4685d..00000000 --- a/ballerina/modules/bulkv2/tests/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# Testing Ballerina Salesforce module - -**Obtaining Tokens** - -1. Visit [Salesforce](https://www.salesforce.com) and create a Salesforce Account. -2. Create a connected app and obtain the following credentials: - * Base URL (Endpoint) - * Access Token - * Client ID - * Client Secret - * Refresh Token - * Refresh Token URL - -Note:- When you are setting up the connected app, select the following scopes under Selected OAuth Scopes: - -* Access and manage your data (api) -* Perform requests on your behalf at any time (refresh_token, offline_access) -* Provide access to your data via the Web (web) - -3. Provide the client ID and client secret to obtain the refresh token and access token. For more information on - obtaining OAuth2 credentials, go to - [Salesforce documentation](https://help.salesforce.com/articleView?id=remoteaccess_authenticate_overview.htm). - -**Create external ID field in Salesforce** - -Since External ID field called `My_External_Id__c` is used in the tests, follow below steps to create this external ID -field in the salesforce. - -1. Log in to your salesforce account and go to the `Setup` by clicking on the settings icon in the right side of the - menu. -2. Then in the left side panel, under Platform tools click on the `Objects and Fields` and the click on - `Object Manager`. -3. In the Object Manager page click on the `Contact` since we are going to create a external field for Contact SObject. -4. In the Contact page click on the `Fields & Relationships` and click `New` in the right hand side. -5. Then select `Text` as the Data type and click `Next`. -6. Add `My_External_Id` for "Field Label" and `255` for "Length" and click `Next`. -7. At the end click `Save` and see whether external field is added successfully by checking `Fields & Relationships` - fields. - -**Select Objects for Change Notifications** - -To receive notifications for record changes, select the custom objects and supported standard objects that you are -interested in. From Setup, enter Change Data Capture in the Quick Find box, and click Change Data Capture. Select the -SObject which you want to listen for changes - - -**Running Tests** - -1. Create a `ballerina.conf` inside project root directory and replace values inside quotes (eg: ) with - appropriate values. - ``` - EP_URL="" - ACCESS_TOKEN="" - CLIENT_ID="" - CLIENT_SECRET="" - REFRESH_TOKEN="" - REFRESH_URL="" - SF_USERNAME="" - SF_PASSWORD="" - ``` -2. Run the following command inside repo root folder. - ```bash - $ ballerina test -a --sourceroot sfdc-connector - ``` - \ No newline at end of file diff --git a/ballerina/modules/bulkv2/types.bal b/ballerina/modules/bulkv2/types.bal index e9313a2b..ae9dbb06 100644 --- a/ballerina/modules/bulkv2/types.bal +++ b/ballerina/modules/bulkv2/types.bal @@ -186,7 +186,7 @@ public type BulkJobInfo record { }; # Represents bulk job related information when Closed. -# +# # + id - The ID of the job # + operation - The operation type of the job # + object - The sObject type of the job @@ -209,7 +209,7 @@ public type BulkJobCloseInfo record { }; # Represents the output of the get all jobs request. -# +# # + done - Indicates whether there are more records to retrieve # + records - Array of job records # + nextRecordsUrl - URL to retrieve the next set of records From 9628979737f775e8faf8cda0bcd4ee917dd063c8 Mon Sep 17 00:00:00 2001 From: aashikam Date: Thu, 25 Jul 2024 10:57:22 +0530 Subject: [PATCH 11/11] Add changes from code review --- ballerina/modules/apex/client.bal | 2 +- ballerina/modules/apex/constants.bal | 2 +- ballerina/modules/apex/tests/test.bal | 2 +- ballerina/modules/apex/types.bal | 2 +- ballerina/modules/bulkv2/client.bal | 2 +- ballerina/modules/bulkv2/constants.bal | 2 +- ballerina/modules/bulkv2/tests/bulk_csv_abort.bal | 2 +- ballerina/modules/bulkv2/tests/bulk_csv_delete.bal | 2 +- ballerina/modules/bulkv2/tests/bulk_csv_insert.bal | 2 +- ballerina/modules/bulkv2/tests/bulk_csv_query.bal | 2 +- ballerina/modules/bulkv2/types.bal | 2 +- ballerina/modules/bulkv2/utils.bal | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ballerina/modules/apex/client.bal b/ballerina/modules/apex/client.bal index 44b83383..49ef1b15 100644 --- a/ballerina/modules/apex/client.bal +++ b/ballerina/modules/apex/client.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org). // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/ballerina/modules/apex/constants.bal b/ballerina/modules/apex/constants.bal index 319ea0b9..7d5562ee 100644 --- a/ballerina/modules/apex/constants.bal +++ b/ballerina/modules/apex/constants.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org). // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/ballerina/modules/apex/tests/test.bal b/ballerina/modules/apex/tests/test.bal index e0b2d3a7..7d9f2294 100644 --- a/ballerina/modules/apex/tests/test.bal +++ b/ballerina/modules/apex/tests/test.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org). // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/ballerina/modules/apex/types.bal b/ballerina/modules/apex/types.bal index ffde1f0c..427db539 100644 --- a/ballerina/modules/apex/types.bal +++ b/ballerina/modules/apex/types.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org). // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/ballerina/modules/bulkv2/client.bal b/ballerina/modules/bulkv2/client.bal index 603e357a..908241f0 100644 --- a/ballerina/modules/bulkv2/client.bal +++ b/ballerina/modules/bulkv2/client.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org). // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/ballerina/modules/bulkv2/constants.bal b/ballerina/modules/bulkv2/constants.bal index f5850780..ced10907 100644 --- a/ballerina/modules/bulkv2/constants.bal +++ b/ballerina/modules/bulkv2/constants.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org). // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/ballerina/modules/bulkv2/tests/bulk_csv_abort.bal b/ballerina/modules/bulkv2/tests/bulk_csv_abort.bal index 4f6b158b..81a0d7ee 100644 --- a/ballerina/modules/bulkv2/tests/bulk_csv_abort.bal +++ b/ballerina/modules/bulkv2/tests/bulk_csv_abort.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org). // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/ballerina/modules/bulkv2/tests/bulk_csv_delete.bal b/ballerina/modules/bulkv2/tests/bulk_csv_delete.bal index 528b3ea2..27abed27 100644 --- a/ballerina/modules/bulkv2/tests/bulk_csv_delete.bal +++ b/ballerina/modules/bulkv2/tests/bulk_csv_delete.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org). // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/ballerina/modules/bulkv2/tests/bulk_csv_insert.bal b/ballerina/modules/bulkv2/tests/bulk_csv_insert.bal index 1e21fccd..96f4b586 100644 --- a/ballerina/modules/bulkv2/tests/bulk_csv_insert.bal +++ b/ballerina/modules/bulkv2/tests/bulk_csv_insert.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org). // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/ballerina/modules/bulkv2/tests/bulk_csv_query.bal b/ballerina/modules/bulkv2/tests/bulk_csv_query.bal index ff1a46a7..2a27d1cf 100644 --- a/ballerina/modules/bulkv2/tests/bulk_csv_query.bal +++ b/ballerina/modules/bulkv2/tests/bulk_csv_query.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org). // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/ballerina/modules/bulkv2/types.bal b/ballerina/modules/bulkv2/types.bal index ae9dbb06..d41af005 100644 --- a/ballerina/modules/bulkv2/types.bal +++ b/ballerina/modules/bulkv2/types.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org). // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/ballerina/modules/bulkv2/utils.bal b/ballerina/modules/bulkv2/utils.bal index 466c4bb0..9e155618 100644 --- a/ballerina/modules/bulkv2/utils.bal +++ b/ballerina/modules/bulkv2/utils.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org). // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except