How I Created a Code Translator Using GPT-3 : Kwame Asante
by: Kwame Asante
blow post content copied from Finxter
click here to view original post
Hey, Folks! I will show you how I created a code translator using OpenAI’s GPT-3 models.
About Me: Before I do, I’d like to share a little about myself. My name is Kwame Asante, and I am a software engineer. I work for a financial institution during the day and teach others how to code outside of work.
Why BABL?
![](https://blog.finxter.com/wp-content/uploads/2023/02/babl_demo.gif)
Try It Yourself: https://www.bablcode.com/
Why the name BABL? because the tool is meant to translate code into more human-readable terms and vice versa. I figured why not take a slightly different approach to the wildly overused reference to the biblical tower of babel, at which the language of mankind first diverted. Plus, it’s more techy and sleek to omit vowels in a product’s name
Why create the tool? I’m looking to address two problems for two groups of people: engineers like myself, and my students.
For the engineer, reading through unfamiliar codebases and trying to understand what’s going on tends to be a taxing endeavor. To add insult to injury, a lot of code I’ve seen (and written, unfortunately) lacks proper documentation and sufficient commenting to help other developers understand what it actually does. It’s important to understand what a codebase you’re working with is doing, whether it’s an external module/library or code that you must modify. Given the difficulty and importance of understanding what code does, I decided to create something to make that process easier.
For the student, learning to code is difficult. The difficulty is two-fold: learning to think computationally and learning coding syntax.
Where I’ve often seen my students stumble in their learning journey is in getting a grip on the syntax of a programming language. They’re clear on what they want to do, and what steps to take to accomplish it. Still, the intimidating syntax of an unfamiliar programming language is often the barrier between ideation and implementation. The most important, often-overlooked aspect of programming is computational thinking: a set of de and processes that facilitate problem-solving.
I want my students to be able to code, but more importantly, I want them to be able to solve problems–all kinds of problems, whether or not they use code to solve the problem. With BABL, one can take a problem, break it down, recognize patterns, take a birds-eye view, and come up with the necessary steps to solve it, all without writing any code. Then, BABL does all of the work translating the instructions into code. This way, the student focuses more on the process of problem-solving while getting enough exposure to how a computer would interpret the solution they produce, such that they can eventually take a problem from end to end with little to no assistance.
The goal with BABL is to create a tool that serves both the seasoned developer and the student just starting out on their coding journey–essentially to create the Google translate for code!
ChatGPT Exploration Pre-BABL
My curiosity regarding Chat-GPT’s capabilities is what got me down the rabbit hole that is BABL. I would ask chatGPT to produce the code for apps like a mobile app that integrates the Eisenhower matrix (what can I say, I sometimes have trouble prioritizing the many things on my plate)
![](https://blog.finxter.com/wp-content/uploads/2023/02/image-353-1024x528.png)
Once I realized how much ChaptGPT could do in terms of writing code, my next thought was why not create a tool that can interpret code somewhere outside of the ChatGPT window.
Building BABL
In order to offer GPT services outside the chat window, I leveraged OpenAI. As I read through OpenAI’s documentation, I learned about all of the models one can use in their development, from davinci
, to ada
, to babbage
. After experimentation and further reading, I settled on text-davinci-003
for code-to-English translations.
Before adding functionality to call the OpenAI API, I created a new Github repository and built a simple UI therein. To accelerate this process, I enlisted the help of, you guessed it, ChatGPT
![](https://blog.finxter.com/wp-content/uploads/2023/02/image-354-1024x515.png)
With ChatGPT’s help, I created index.html
. It takes input in a textarea
box and sends the input in a rest call once the user clicks the submit button.
<!DOCTYPE html> <html> <head> <title>Babl</title> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <link href="prism.css" rel="stylesheet" /> <link href="style.css" rel="stylesheet"> <script src="https://kit.fontawesome.com/78718e074a.js" crossorigin="anonymous"></script> </head> <body> <h1>BABL</h1> <form> <label for="input-box">Insert Code Here:</label> <br> <textarea id="input-box" rows="10" cols="50"></textarea> <br><br> <button type="button" id="submit-button">Transpile</button> </form> <div id="error"> <i class="fas fa-exclamation-triangle fa-10x"></i><br/> <label id="error-message"></label> </div> <img id="loading-gif" src="loading.gif"> <div id="output"></div> <button id="copy-button">Copy</button> <script src="prism.js"></script> <script src="script.js"></script> </body> </html>
I then added a JavaScript file called script.js
to handle user submission and call the OpenAI API. In order to ensure I was properly using the API, I read some documentation but mostly used Chat-GPT to generate sample API calls in javascript that I could copy/paste.
I retrieved an API Key and generated/wrote the code to generate a rest call to OpenAI.
![](https://blog.finxter.com/wp-content/uploads/2023/02/image-355-1024x432.png)
To test out and refine my prompts for the models, I used the OpenAI Playground
![](https://blog.finxter.com/wp-content/uploads/2023/02/image-356-1024x503.png)
Once I felt comfortable with the responses I was receiving for my prompts, I plugged them into my API calls.
In script.js
, I handle the click event and send whatever is in the textarea
as the request body for the API call to OpenAI. In order to control the elements on the webpage, I use JQuery, a JavaScript library that simplifies the process of writing client-side JavaScript code for manipulating HTML documents.
const auth = "Bearer " + your_api_key_here $(document).ready(function() { $("#submit-button").click(function() { $("#loading-gif").show(); $("#output").hide() $("#copy-button").hide() $("#error").hide() var inputValue = $("#input-box").val(); if (inputValue) { $.ajax({ type: "POST", url: "https://api.openai.com/v1/engines/text-davinci-003/completions", headers: { "Content-Type": "application/json", "Authorization": auth }, data: JSON.stringify({ prompt: "Can I execute this code as-is without error: " + inputValue, max_tokens: 2036, temperature: 0.7, top_p: 1, frequency_penalty: 0, presence_penalty: 0 }), success: function(response) { if(!response.choices[0].text.toUpperCase().slice(0, 5).includes("\n\nYES")){ $("#loading-gif").hide(); $("#error-message").html("Not Valid Code. Please Try Again.") $("#error").show() } else { $.ajax({ type: "POST", url: "https://api.openai.com/v1/engines/text-davinci-003/completions", headers: { "Content-Type": "application/json", "Authorization": auth }, data: JSON.stringify({ prompt: "explain the following code as a comment in the language the code is written in: " + inputValue, max_tokens: 2036 }), success: function(response) { $("#loading-gif").hide(); var output = response.choices[0].text; $("#output").html(output); $("#output").show(); $("#copy-button").css("display", "block") }, error: function(jqXHR, textStatus, errorThrown) { $("#loading-gif").hide(); $("#error-message").html("Error Loading Explanation. Please Try Again.") $("#error").show() } }); } }, error: function(jqXHR, textStatus, errorThrown) { $("#loading-gif").hide(); $("#error-message").html("Error Loading Explanation. Please Try Again.") $("#error").show() } }); } else { $("#loading-gif").hide(); $("#error-message").html("Please Enter Code Above.") $("#error").show() } }); }); const copyButton = document.getElementById('copy-button'); const textArea = document.getElementById('output'); copyButton.addEventListener('click', async () => { try { await navigator.clipboard.writeText(textArea.innerText); alert('Copied!'); } catch (err) { console.error('Failed to copy text: ', err); } });
If you take a closer look at the above code, you’ll see two successive API calls using ajax–one call to validate the code that I want to interpret, and then another call to interpret the code. If the textarea
is blank or the code is invalid, BABL will flash an alert letting the user know. I also flash an error when the API call fails.
![](https://blog.finxter.com/wp-content/uploads/2023/02/image-357-1024x841.png)
![](https://blog.finxter.com/wp-content/uploads/2023/02/image-358-1024x899.png)
ChatGPT even helped me generate the styles to create a minimalist, modern feel.
The result was style.css
body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f0f0f0; } h1 { text-align: center; margin: 20px 0; color: #3e8e41; } form { width: 60%; margin: 0 auto; text-align: center; } #error { width: 50%; margin: 0 auto; text-align: center; display: none; } img { margin: 0 auto; text-align: center; display: block; } label { display: block; margin-bottom: 10px; font-size: 16px; color: #666; } i { color: #666; } textarea { width: 100%; padding: 12px 20px; margin: 8px 0; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px; background-color: #f8f8f8; font-size: 16px; resize: none; } button { width: 25%; background-color: #4CAF50; color: white; padding: 14px 20px; margin: 8px 0; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; } button:hover { background-color: #3e8e41; } #copy-button { width: 10%; margin: 0 auto; display: none; } #output { width: 60%; margin: 20px auto; padding: 20px; background-color: #fff; border-radius: 4px; box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.2); display: none; } #loading-gif { display: none; }
Using the above code, I had a simple HTML-CSS-Javascript application that translated code.
![](https://blog.finxter.com/wp-content/uploads/2023/02/image-359-1024x553.png)
Running Locally
To run the application locally, I simply opened index.html
in my browser by putting file:///path/to/index.html
in the address bar. From there, I could run the app and translate whatever code I gave as input.
Initial Deployment
To get BABL hosted on the World Wide Web, I used Amazon Web Services (AWS) Amplify, a development platform provided by AWS that allows developers to quickly build and deploy mobile and web applications. You can read more about AWS Amplify here.
After pushing my code to Github, I went into the AWS console and:
- Selected Amplify
- Selected New app → Host web app
![](https://blog.finxter.com/wp-content/uploads/2023/02/image-360.png)
- Under From your existing code, selected Github, and then clicked continue
![](https://blog.finxter.com/wp-content/uploads/2023/02/image-361-1024x509.png)
- Authroized AWS amplify to access my GitHub account
- Selected the repository containing the code I want to deploy
- Set the default deployment branch to
Main
- Voila! My app was deployed
![](https://blog.finxter.com/wp-content/uploads/2023/02/image-362-1024x533.png)
I could access BABL from anywhere, but the URL that AWS generated for me was long, and hard to remember. To change the web address for BABL, I connected the domain I purchased via AWS Route53, AWS’s DNS, (www.bablcode.com) to the application in Amplify.
![](https://blog.finxter.com/wp-content/uploads/2023/02/image-363-1024x636.png)
Secrets Management
Soon after deploying BABL, I did some quick testing of the deployed version, and saw that none of the code I tried was getting translated. I then saw an email from OpenAI telling me that my secret had been exposed in a public GitHub repository.
![](https://blog.finxter.com/wp-content/uploads/2023/02/image-364-1024x990.png)
To address this problem, I generated a new API Key, and after exploring options to prevent secret exposure, I landed on storing it as an environment variable for my Amplify application.
I went to the AWS console, into Amplify, selected my application, and then went to Environment variables. I created an environment variable called API_KEY
and set it equal to my new OpenAI API key.
![](https://blog.finxter.com/wp-content/uploads/2023/02/image-365-1024x435.png)
Conversion to ReactJS
To access my new API key, and to make the application more official, I decided to convert BABL into a ReactJS application. ReactJS (or React for short) is a popular open-source JavaScript library for building user interfaces. It’s widely used for developing mobile apps, single-page applications, as well as complex web apps
To create a new React application, I ensured that I installed Node.js, the JavaScript runtime environment that is often used to build React apps, and ran the following command:
npx create-react-app babl cd babl npm start
I managed dependencies in the package.json
file below:
{ "name": "babl", "version": "0.1.0", "private": true, "dependencies": { "@fortawesome/free-solid-svg-icons": "^6.2.1", "@fortawesome/react-fontawesome": "^0.2.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "jquery": "^3.6.3", "openai": "^3.1.0", "react": "^18.2.0", "react-analytics": "^1.0.3", "react-dom": "^18.2.0", "react-dotenv": "^0.1.3", "react-ga": "^3.3.1", "react-ga4": "^2.0.0", "react-helmet": "^6.1.0", "react-helmet-async": "^1.3.0", "react-router-dom": "^6.8.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, "scripts": { "start": "webpack-dev-server --port 8080", "build": "webpack", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": [ "react-app", "react-app/jest" ] }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "devDependencies": { "@babel/core": "^7.20.12", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-object-rest-spread": "^7.20.7", "@babel/preset-env": "^7.20.2", "@babel/preset-react": "^7.18.6", "css-loader": "^6.7.3", "dotenv-webpack": "^8.0.1", "file-loader": "^6.2.0", "html-webpack-plugin": "^5.5.0", "image-webpack-loader": "^8.1.0", "mini-css-extract-plugin": "^2.7.2", "node-polyfill-webpack-plugin": "^2.0.1", "style-loader": "^3.3.1", "webpack": "^5.75.0", "webpack-cli": "^5.0.1", "webpack-pwa-manifest": "^4.3.0" } }
Just like with a pip
file in Python, I can define and manage dependencies in my NodeJS application using package.json
.
I converted index.html
and script.js
into one React Component. A React Component is a JavaScript function or class that accepts input in the form of properties, and returns a React element, which is somewhat like HTML.
Below is babl.js
, the component I created:
import React, { useState, useRef } from 'react'; import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Link } from "react-router-dom"; import $ from 'jquery'; import './App.css'; import loadGif from './loading.gif'; import { HelmetProvider, Helmet } from 'react-helmet-async'; import ReactGA from 'react-ga4'; ReactGA.initialize('G-MRJNYMVTTC'); function Babl() { // ReactGA.ga('set', 'page', window.location.pathname + window.location.search); ReactGA.send({ hitType: "pageview", page: "/" }); const [input, setInput] = useState(''); const [errorMessage, setErrorMessage] = useState(''); const [loading, setLoading] = useState(false); const [explanationLoaded, setExplanationLoaded] = useState(false) const [output, setOutput] = useState(''); const auth = "Bearer " + process.env.REACT_APP_API_KEY; const textAreaRef = useRef(null); const copyButtonRef = useRef(null); function Transpile() { ReactGA.event({category: 'Button', action: 'Click', label: 'Trasnpile'}) setLoading(true); setExplanationLoaded(false); setOutput(''); setErrorMessage(''); if (input.trim()) { $.ajax({ type: 'POST', url: 'https://api.openai.com/v1/engines/text-davinci-003/completions', headers: { 'Content-Type': 'application/json', Authorization: auth, }, data: JSON.stringify({ prompt: `Can I execute this code as-is without error: ${input}`, max_tokens: 2036, temperature: 0.7, top_p: 1, frequency_penalty: 0, presence_penalty: 0, }), success: function (response) { if (!response.choices[0].text.toUpperCase().slice(0, 5).includes('\n\nYES')) { setLoading(false); setErrorMessage('Not Valid Code. Please Try Again.'); } else { $.ajax({ type: 'POST', url: 'https://api.openai.com/v1/engines/text-davinci-003/completions', headers: { 'Content-Type': 'application/json', Authorization: auth, }, data: JSON.stringify({ prompt: `explain the following code as a comment in the language the code is written in: ${input}`, max_tokens: 2036, }), success: function(response) { setLoading(false); var output_exp = response.choices[0].text; setOutput(output_exp); setExplanationLoaded(true); }, error: function() { setErrorMessage('Error Loading Explanation. Please Try Again.'); } }); }}, error: function() { setLoading(false) setErrorMessage('Error Loading Explanation. Please Try Again.'); }}); } else { setLoading(false) setErrorMessage('Please Enter Code Above.'); } } const handleCopy = async () => { try { await navigator.clipboard.writeText(textAreaRef.current.innerText); alert('Copied!'); } catch (err) { console.error('Failed to copy text: ', err); } } return ( <> <HelmetProvider> <Helmet> <link href='https://fonts.googleapis.com/css?family=Quicksand' rel='stylesheet' /> </Helmet> </HelmetProvider> <h1>BABL</h1> <form> <label htmlFor="input-box">Insert Code Here:</label> <br /> <textarea id="input-box" rows={10} cols={50} value={input} onChange={(e) => setInput(e.target.value)} /> <br /><br /> <button type="button" id="submit-button" onClick={Transpile}>Transpile</button> </form> <div id="error" style=> <FontAwesomeIcon icon={faExclamationTriangle} size="10x" color="#666"/> <br /> <label id="error-message">{errorMessage}</label> </div> {loading && <img id="loading-gif" src={loadGif} alt="loading" />} { explanationLoaded && <div ref={textAreaRef} id="output" dangerouslySetInnerHTML=></div> } { explanationLoaded && <button ref={copyButtonRef} id="copy-button" onClick={handleCopy}>Copy</button> } <form> <Link to="/comp" className='link'> <button> COMP </button> </Link> </form> </> ); } export default Babl;
Babl.js
imports the dependencies React
, useState
, useRef
, FontAwesomeIcon
, Link
, and others, all of which are defined in the package.json
file.
Babl.js
also renders (via the return
statement) a form that includes an input text area and a “Transpile
” button, just like the HTML/JavaScript version of the application.
The handleCopy()
function is called when the “Copy
” button is clicked. This function uses the navigator.clipboard.writeText()
method to copy the output text to the clipboard.
The HelmetProvider
and Helmet
components are used for setting the <head>
section of the HTML document. In this case, I used Helmet
to import the Google Font “QuickSand
” in the head section of the rendered HTML.
I replaced the plaintext API key that was in my code with: process.env.API_KEY
. In Node.js, process.env
is an object that represents the environment in which the Node.js process is running. It contains key-value pairs of environment variables that are available to the process.
In order to load the environment variable containing my API key into BABL from Amplify, I went to Amplify > My app > Build settings > and edited the amplify.yml
file, the configuration file used by the AWS Amplify CLI (Command Line Interface) tool to define the settings and resources required to build, deploy, and manage AWS Amplify applications. Below is what my amplify.yml
file contained:
version: 1.0
frontend:
phases:
preBuild:
commands:
- npm install
build:
commands:
- echo "REACT_APP_API_KEY=$API_KEY" >> .env
- npm run build
artifacts:
baseDirectory: dist
files:
- '**/*'
framework: react
start:
commands:
- npm start
Running Locally – ReactJS
To get the app running on my local server, I run the following command in the terminal: npm start
. Then, I visit http://localhost:8080/
[PICTURES OF LOCALHOST W/ and W/O WARNING MESSAGES]
What you see on the right is what you first get when the app loads. You can disregard the warnings and click on the x in the upper right corner. Then you should get the image on the left.
Generating a Build
Rather than simply deploy the React code as-is, I decided to bundle it using Webpack, an open-source module bundler for JavaScript apps. It helps with dependency and asset management by bundling them together into a single package.
With Webpack, I consolidated my application and created a degree of abstraction as to what goes on under the hood, just like with many web apps I see nowadays.
To do this, I configured how I would manage my assets and dependencies in my webpack.config.js
file:
const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const Dotenv = require("dotenv-webpack"); const WebpackPwaManifest = require('webpack-pwa-manifest'); module.exports = { entry: './src/index.js', output: { path: path.join(__dirname, '/dist'), filename: 'index_bundle.js' },
This section sets the entry point of the application, which is the starting point for Webpack to build the dependency graph. It also specifies the output location and filename for the bundled files.
mode: 'production',
This section sets the mode of Webpack to production, which will enable various optimizations and code minification.
module: { rules: [ { test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ }, { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.(png|svg|jpg|gif)$/, use: 'file-loader' } ] },
This section defines the rules for how webpack should handle different types of files in the project. For example, it specifies that JavaScript files should be processed using the babel-loader and that CSS files should be processed using both style-loader and css-loader.
plugins: [ new Dotenv({ systemvars: true }), new HtmlWebpackPlugin({ template: './public/index.html' }), new MiniCssExtractPlugin({ filename: 'App.css' }), new webpack.ProvidePlugin({ process: 'process/browser', }), new WebpackPwaManifest({ filename: 'manifest.json', name: 'My PWA', short_name: 'My PWA', description: 'My Progressive Web App', background_color: '#ffffff', theme_color: '#ffffff', icons: [ { src: path.resolve('public/apple-touch-icon.png'), sizes: [128] } ] }) ] }
This section includes a set of plugins that extend the functionality of Webpack.
For example, the Dotenv plugin loads environment variables from a .env
file, the HtmlWebpackPlugin
generates an HTML file that includes the bundled JavaScript and CSS files, the MiniCssExtractPlugin
extracts CSS into separate files, and the WebpackPwaManifest
generates a manifest file for a progressive web app. The ProvidePlugin
provides the ability to use a global process variable in the browser.
Deployment – ReactJS
Using AWS amplify to deploy a ReactJS application vs a simple html-js
application required some configuration changes. I first went into Amplify and updated the App build specification under Build settings.
![](https://blog.finxter.com/wp-content/uploads/2023/02/image-366-1024x263.png)
The amplify.yml
file I created was:
version: 1.0
frontend:
phases:
preBuild:
commands:
- npm install
build:
commands:
- echo "REACT_APP_API_KEY=$API_KEY" >> .env
- npm run build
artifacts:
baseDirectory: dist
files:
- '**/*'
framework: react
start:
commands:
- npm start
Rollout and Beta-Testing
After deploying and attaching a domain to BABL, I sent it out to friends to try out and share their opinion.
The feedback was mostly positive, but I began thinking of ways I could make this tool especially useful for the students I teach. That is when I decided to add the English-to-code functionality on a separate web page within the application.
To accomplish this, I leveraged the code-davinci-002
model.
After adding the ability to convert human text into Python code, I held a small pop-up class focused on computational thinking. The goal of the class was to come up with a step-by-step solution to coding interview-type questions and then convert the steps to code using BABL.
This way, students focus more on their computational thinking skills than on coding skills. The class was mostly a success until it was time to come up with step-by-step instructions to code a rock-paper-scissors game. The issue was producing a game that restarted if there was a tie. After some time and refinement, I was able to produce a self-restarting game.
I also decided to provide the option to translate to or from code on the same web page, making Babl a single-page application once again. The final babl.js
now looks like this:
function Convert() { ReactGA.event({category: 'Button', action: 'Click', label: 'Convert'}) setLoading(true); setExplanationLoaded(false); setOutput(''); setErrorMessage(''); if (input.trim()) { $.ajax({ type: 'POST', url: 'https://api.openai.com/v1/engines/code-davinci-002/completions', headers: { 'Content-Type': 'application/json', Authorization: auth, }, data: JSON.stringify({ prompt: `# Python 3\n"""\n ${input}\n"""`, temperature: 0, max_tokens: 1780, top_p: 1, frequency_penalty: 0, presence_penalty: 0, }), success: function(response) { setLoading(false); var output_exp = response.choices[0].text; setOutput(`<pre>${output_exp}</pre>`); setExplanationLoaded(true); }, error: function() { setErrorMessage('Error Loading Explanation. Please Try Again.'); } }) } else { setLoading(false) setErrorMessage('Please Enter Code Above.'); } }
It’s similar to the original Babl.js, except now I have a couple of radio buttons for the two options a user has between producing code or translating code. Each selection is mapped to a function that will call the OpenAI API for either purpose.
![](https://blog.finxter.com/wp-content/uploads/2023/02/image-368-1024x553.png)
![](https://blog.finxter.com/wp-content/uploads/2023/02/image-367-1024x552.png)
Takeaways
Effectively prompting an AI model is going to become a valuable skill.
As I built the code translator, the main challenge wasn’t in creating the UI or coding the API calls, but rather in getting what I wanted from the AI model and presenting the information I received in a manner helpful to my end users.
Next Steps
Leveraging AWS Amplify and Cognito’s functionalities will enable users to create an account with BABL and perform as many translations as they want.
I also plan to add extra capabilities to make BABL a more effective educational tool for folks on their coding journey.
February 27, 2023 at 03:03PM
Click here for more details...
=============================
The original post is available in Finxter by Kwame Asante
this post has been published as it is through automation. Automation script brings all the top bloggers post under a single umbrella.
The purpose of this blog, Follow the top Salesforce bloggers and collect all blogs in a single place through automation.
============================
![Salesforce Salesforce](https://1.bp.blogspot.com/-7PAEudGm-vA/X5xmpBCB6-I/AAAAAAAAACU/4l5vI_EgYqYlEXFHs1xEeyiYP9Dyl89IgCLcBGAsYHQ/s600/1.jpg)
Post a Comment