<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Amit Jotwani</title>
    <description>The latest articles on DEV Community by Amit Jotwani (@ajot).</description>
    <link>https://dev.to/ajot</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F216219%2F7db1688a-a5d2-4c20-9151-459313d2894c.jpg</url>
      <title>DEV Community: Amit Jotwani</title>
      <link>https://dev.to/ajot</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ajot"/>
    <language>en</language>
    <item>
      <title>Dynamically Updating Your Tidbyt Display with REST API Data</title>
      <dc:creator>Amit Jotwani</dc:creator>
      <pubDate>Thu, 21 Sep 2023 16:40:19 +0000</pubDate>
      <link>https://dev.to/ajot/dynamically-updating-your-tidbyt-display-with-rest-api-data-4h05</link>
      <guid>https://dev.to/ajot/dynamically-updating-your-tidbyt-display-with-rest-api-data-4h05</guid>
      <description>&lt;p&gt;Last time, &lt;a href="https://dev.to/ajot/building-a-hello-world-app-for-tidbyt-retro-style-display-4e8k-temp-slug-4342514"&gt;I walked through building a simple "Hello World" app for my Tidbyt&lt;/a&gt;, the retro LED display screen that now sits &lt;del&gt;on my desk&lt;/del&gt; in our Living Room.&lt;/p&gt;

&lt;p&gt;This time, I was curious if I could use a REST API to dynamically update the Tidbyt's display.&lt;/p&gt;

&lt;p&gt;For example, it would be pretty cool if it could display a "Good Morning" greeting in different languages each day (using &lt;a href="http://greetingsapi.com"&gt;GreetingsAPI.com&lt;/a&gt;) or a joke (using &lt;a href="https://api.chucknorris.io"&gt;https://api.chucknorris.io&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  The Quest
&lt;/h3&gt;

&lt;p&gt;The mission is simple: Fetch a "Good Morning" greeting and its language from &lt;a href="http://GreetingsAPI.com"&gt;GreetingsAPI.com&lt;/a&gt; and display it on my Tidbyt using the &lt;a href="http://http.star"&gt;&lt;code&gt;http.star&lt;/code&gt;&lt;/a&gt; module for HTTP requests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VQjzzjZc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1695311558803/87b40a08-b408-47d8-8f40-343cc8d48b51.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VQjzzjZc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1695311558803/87b40a08-b408-47d8-8f40-343cc8d48b51.gif" alt="" width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Pre-requisite&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Before we dive in, make sure you've installed Pixlet on your machine. This tool is essential for &lt;strong&gt;serving&lt;/strong&gt; , &lt;strong&gt;rendering&lt;/strong&gt; , and &lt;strong&gt;pushing&lt;/strong&gt; the app to your Tidbyt device.&lt;/p&gt;

&lt;p&gt;If you're new to this or need a refresher, check out my previous post on &lt;a href="https://dev.to/ajot/building-a-hello-world-app-for-tidbyt-retro-style-display-4e8k-temp-slug-4342514"&gt;Building a Hello World App for Tidbyt Retro Style Display&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Lets Get Started&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;First, fire up your favorite IDE and create a new file called &lt;a href="http://greetings.star"&gt;&lt;code&gt;greetings.star&lt;/code&gt;&lt;/a&gt;. Then, paste the code below into it.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;greetings.star&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Import the required libraries
load("render.star", "render")
load("http.star", "http")

# Define API URLs for Chuck Norris Jokes and Greetings
GREETINGS_API_URL = "https://www.greetingsapi.com/random"
CHUCK_NORRIS_JOKE_URL = "https://api.chucknorris.io/jokes/random"

# Fetch a random greeting from Greetings API
def get_greeting():
    rep = http.get(GREETINGS_API_URL) # Make the HTTP GET request
    if rep.status_code != 200: # Check if the request was successful
        fail("Greetings API request failed with status %d", rep.status_code)
    print(rep.json()) # Debug print the response
    return rep.json() # Return the JSON response

# Fetch a random joke from Chuck Norris API
def get_joke():
    rep = http.get(CHUCK_NORRIS_JOKE_URL) # Make the HTTP GET request
    if rep.status_code != 200: # Check if the request was successful
        fail("Chuck Norris API request failed with status %d", rep.status_code)
    return rep.json() # Return the JSON response

# Main function to render the Tidbyt app
def main(config):
    greeting = get_greeting() # Get random greeting
    joke = get_joke() # Get random joke

    # Get font from config or default to "tb-8"
    font = config.get("font", "tb-8")
    print("Using font: '{}'".format(font)) # Debug print the font being used

    # Build the render tree
    return render.Root(
        child = render.Column(
            cross_align="center",
            children = [
                # Create a horizontal colored box
                render.Box(
                    width = 64,
                    height = 1,
                    color = "#78DECC",
                    padding = 10
                ),
                # Display the greeting text
                render.Text("%s" % greeting["greeting"], font = font, color = "#FF0000"),
                # Another horizontal colored box
                render.Box(
                    width = 64,
                    height = 1,
                    color = "#78DECC",
                ),
                # Scrolling marquee text to display the greeting in its native language
                render.Marquee(
                    width = 64,
                    child = render.Text("Good Morning in %s" % greeting["language"], font = font)
                ),
                # Final horizontal colored box
                render.Box(
                    width = 64,
                    height = 1,
                    color = "#78DECC",
                ),
            ],
        ),
    )

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡&lt;/p&gt;

&lt;p&gt;We're not using Chuck Norris this time, but hey, it's good to have options :-)&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Key points to remember&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://http.star"&gt;&lt;code&gt;http.star&lt;/code&gt;&lt;/a&gt; &lt;strong&gt;Module&lt;/strong&gt; : We use &lt;a href="http://http.star"&gt;&lt;code&gt;http.star&lt;/code&gt;&lt;/a&gt; module to interact with REST APIs. This module is part of the Starlark standard library and allows us to make HTTP requests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;render.Root&lt;/code&gt; &lt;strong&gt;and&lt;/strong&gt; &lt;code&gt;render.Column&lt;/code&gt;: To display the fetched data on the Tidbyt, we use &lt;code&gt;render.Root&lt;/code&gt; to set up the root layout and &lt;code&gt;render.Column&lt;/code&gt; to vertically arrange the display elements.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Serving the App
&lt;/h2&gt;

&lt;p&gt;To preview the app, open your terminal and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pixlet serve greetings.star

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will start a local server and you'll get a URL to preview your app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rendering the Content
&lt;/h2&gt;

&lt;p&gt;Run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pixlet render greetings.star

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a &lt;code&gt;.webp&lt;/code&gt; file named &lt;code&gt;greetings.webp&lt;/code&gt; in your current directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pushing to Your Tidbyt
&lt;/h2&gt;

&lt;p&gt;💡&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Make sure you have your Device ID handy for this step. See &lt;a href="https://curiousmints.com/building-a-hello-world-app-for-tidbyt-retro-style-display#heading-get-device-id"&gt;Get Device ID&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, to see your work come to life on your Tidbyt device, you'll need to push the generated &lt;code&gt;.webp&lt;/code&gt; file to it using its device id. Run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pixlet push &amp;lt;YOUR_DEVICE_ID&amp;gt; greetings.webp

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will display your app on your Tidbyt device.&lt;/p&gt;

&lt;p&gt;And there you have it! You should now be able to display greetings from around the world on your Tidbyt.&lt;/p&gt;

&lt;p&gt;💡&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro Tip:&lt;/strong&gt; To make your app a part of the regular app rotation and have it show up on your iOS app, you'll need to install it permanently. See &lt;a href="https://curiousmints.com/building-a-hello-world-app-for-tidbyt-retro-style-display#heading-installing-the-app-permanently"&gt;Installing the App Permanently&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building a Hello World App for Tidbyt Retro Style Display</title>
      <dc:creator>Amit Jotwani</dc:creator>
      <pubDate>Mon, 04 Sep 2023 18:38:55 +0000</pubDate>
      <link>https://dev.to/ajot/building-a-hello-world-app-for-tidbyt-retro-style-display-36mi</link>
      <guid>https://dev.to/ajot/building-a-hello-world-app-for-tidbyt-retro-style-display-36mi</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n6vHQ3j1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1694748400231/311e19e7-ee77-4262-8b00-f480e762187d.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n6vHQ3j1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1694748400231/311e19e7-ee77-4262-8b00-f480e762187d.jpeg" alt="" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I recently bought a &lt;a href="https://tidbyt.com/"&gt;Tidbyt&lt;/a&gt;, a fascinating retro pixelated display that can do everything from showing the weather to displaying memes (h/t to &lt;a href="https://twitter.com/greggyb"&gt;Greg Baugues&lt;/a&gt; for the recommendation).&lt;/p&gt;

&lt;p&gt;After setting it up and exploring some pre-built apps (&lt;em&gt;mine currently cycles through a Nokia Snake game, a Tetris clock I'm in love with, and even shows my upcoming Google Calendar meetings&lt;/em&gt;), I had to explore building my own apps for it. The classic "Hello World" seemed like the best starting point.&lt;/p&gt;

&lt;p&gt;Figuring out how to build an app for Tidbyt wasn't overly complex, but took me some time to wrap my head around the basics. Here's a quick guide to get you started with your first Tidbyt app.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you will learn
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Understanding the basic building blocks: Starlark, Pixlet, and so on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Writing a simple "Hello World" app.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Testing your app locally in a browser.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Testing the app on your Tidbyt device.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pushing the app so it shows up in the Tidbyt iOS app.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's get going!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Basics: Pixlet and Starlark
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The primary tool for building Tidbyt apps is &lt;strong&gt;Pixlet&lt;/strong&gt; - an app runtime and UX toolkit for highly-constrained displays.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You write your Pixlet applets for Tidbyt in &lt;strong&gt;Starlark&lt;/strong&gt; , a &lt;strong&gt;Python-like language.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Apps developed with Pixlet can be served in a &lt;strong&gt;browser&lt;/strong&gt; , rendered as &lt;strong&gt;WebP or GIF animations&lt;/strong&gt; , or pushed to a &lt;strong&gt;physical Tidbyt device&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Hello World!
&lt;/h2&gt;

&lt;p&gt;Here's how a simple "Hello World" code snippet looks, saved in a file called &lt;code&gt;hello_world.star&lt;/code&gt;.pythonstarpyth&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;hello_world.star&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;load("render.star", "render")

def main():
    return render.Root(
        child = render.Text("Hello World")
    )

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Preview Your App in a Web Browser
&lt;/h2&gt;

&lt;p&gt;You can &lt;strong&gt;serve&lt;/strong&gt; your applet on your local machine, essentially previewing it in a web browser much like you would with a Flask app.&lt;/p&gt;

&lt;p&gt;To start this preview, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pixlet serve hello_world.star

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then navigate to &lt;a href="http://localhost:8080"&gt;http://localhost:8080&lt;/a&gt; to see your app in action.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M2woFscN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1694748205435/9e154b64-da30-4d11-8ae4-4d813731f43c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M2woFscN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1694748205435/9e154b64-da30-4d11-8ae4-4d813731f43c.png" alt="" width="800" height="531"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Preview Your App on Your Tidbyt Device
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Preparing the files to be rendered (Gif/Webp format)
&lt;/h3&gt;

&lt;p&gt;This bit had me stumped for a moment. Tidbyt applets need to be in either gif or webp format. You can't just deploy the &lt;code&gt;.star&lt;/code&gt; files to Tidbyt. To generate the gif/webp formats, run the &lt;code&gt;render&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pixlet render hello_world.star

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will produce a &lt;code&gt;.webp&lt;/code&gt; file named &lt;code&gt;hello_world.webp&lt;/code&gt; in your current directory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Connecting Pixlet to Your Tidbyt Account
&lt;/h3&gt;

&lt;p&gt;Before pushing any apps to your Tidbyt device, you'll first need to connect Pixlet to your Tidbyt account. Run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pixlet login

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will trigger a browser window to open, guiding you through the Tidbyt authentication process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Push to Device
&lt;/h3&gt;

&lt;p&gt;At this point, you can push your app to your Tidbyt device temporarily for a quick preview:&lt;/p&gt;

&lt;h4&gt;
  
  
  Get Device ID
&lt;/h4&gt;

&lt;p&gt;To know which device to push your app to, you'll need to list the devices connected to your account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pixlet devices

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Push to the device using the Device ID
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pixlet push &amp;lt;YOUR_DEVICE_ID&amp;gt; hello_world.webp

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that this will only display the app temporarily on your Tidbyt and it won't appear on your iOS app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing the App Permanently
&lt;/h2&gt;

&lt;p&gt;To make your app a part of the regular app rotation and also have it show up on your iOS app, you'll need to install it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pixlet push --installation-id &amp;lt;INSTALLATION_ID&amp;gt; &amp;lt;YOUR_DEVICE_ID&amp;gt; hello_world.webp

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;&amp;lt;INSTALLATION_ID&amp;gt;&lt;/code&gt; with your chosen alphanumeric name and &lt;code&gt;&amp;lt;YOUR_DEVICE_ID&amp;gt;&lt;/code&gt; with the specific ID of your Tidbyt device.&lt;/p&gt;

&lt;p&gt;You can name the &lt;code&gt;INSTALLATION_ID&lt;/code&gt; anything alphanumeric that's meaningful to you. I initially thought you could use hyphens because the manual mentioned it, but I got an error. So, I kept it simple and used &lt;code&gt;helloworld&lt;/code&gt; as my installation ID.&lt;/p&gt;

&lt;p&gt;That should get your app to show up on your Tidbyt device and also on your list of installed apps on your Tidbyt iOS app.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;I really enjoyed getting this "Hello World" app up and running on my Tidbyt. It's incredibly gratifying to see your custom creation pop up on such a gorgeous retro display.&lt;/p&gt;

&lt;p&gt;My curiosity is even more piqued. Now I've got a bunch of questions I want to answer next:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;How do I pull info from a REST API? Maybe even a Greetings API?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How do I change the font?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How can I center the text?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Can I add emojis?&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Answers coming soon 🙂&lt;/p&gt;




&lt;p&gt;👋 If you enjoyed this, I promise I'm just as fun in 280 characters! Follow me on &lt;a href="https://twitter.com/amit"&gt;Twitter&lt;/a&gt; 🐦&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Quickly Cycling Through and Fixing Spelling Errors in VS Code Markdown Files</title>
      <dc:creator>Amit Jotwani</dc:creator>
      <pubDate>Tue, 22 Aug 2023 12:46:17 +0000</pubDate>
      <link>https://dev.to/ajot/quickly-cycling-through-and-fixing-spelling-errors-in-vs-code-markdown-files-5fdc</link>
      <guid>https://dev.to/ajot/quickly-cycling-through-and-fixing-spelling-errors-in-vs-code-markdown-files-5fdc</guid>
      <description>&lt;p&gt;I wanted to cycle through and fix all spelling errors in a VS Code markdown file. I already had the spell-checking extension installed but couldn't figure out how to see all the errors together and fix them quickly.&lt;/p&gt;

&lt;p&gt;Here's how -&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the markdown file in VS Code where you want to correct the errors.&lt;/li&gt;
&lt;li&gt;Install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker&amp;amp;ref=ajot.me"&gt;Code Spell Checker Extension&lt;/a&gt; if you haven't already.&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;CMD + SHIFT + M&lt;/code&gt; to see all spelling errors together in the 'Problems' pane.&lt;/li&gt;
&lt;li&gt;Click on an error or highlight the word and press &lt;code&gt;CMD + .&lt;/code&gt; to see quick fix suggestions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QGbf3EqO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ajot.me/content/images/2023/08/CleanShot-2023-08-22-08-42-19%402x-5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QGbf3EqO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ajot.me/content/images/2023/08/CleanShot-2023-08-22-08-42-19%402x-5.png" alt="" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Changing the Number of Posts Displayed Per Page in Ghost CMS</title>
      <dc:creator>Amit Jotwani</dc:creator>
      <pubDate>Thu, 17 Aug 2023 22:05:41 +0000</pubDate>
      <link>https://dev.to/ajot/changing-the-number-of-posts-displayed-per-page-in-ghost-cms-e96</link>
      <guid>https://dev.to/ajot/changing-the-number-of-posts-displayed-per-page-in-ghost-cms-e96</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FW_2ujQG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ajot.me/content/images/2023/08/ghost-tips-tricks-banner.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FW_2ujQG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ajot.me/content/images/2023/08/ghost-tips-tricks-banner.png" alt="Changing the Number of Posts Displayed Per Page in Ghost CMS" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, I wanted to customize the number of posts displayed per page on my Ghost CMS website using the Dawn theme. The default setting was showing only 5 posts, and I wanted to bump that up to 25.&lt;/p&gt;

&lt;p&gt;Here's how I did it, broken down into simple steps, so it's easy to remember and follow along.&lt;/p&gt;

&lt;h3&gt;
  
  
  Download the theme you are currently using
&lt;/h3&gt;

&lt;p&gt;First, we need to download the current theme to access the file containing the posts' settings.&lt;/p&gt;

&lt;p&gt;Go to **Settings -&amp;gt; Design** in your Ghost CMS admin panel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zr7KfLQ0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ajot.me/content/images/2023/08/CleanShot-2023-08-17-18-02-52%402x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zr7KfLQ0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ajot.me/content/images/2023/08/CleanShot-2023-08-17-18-02-52%402x.png" alt="Changing the Number of Posts Displayed Per Page in Ghost CMS" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Edit package.json File
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Open the downloaded theme directory and locate the &lt;code&gt;package.json&lt;/code&gt; file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Find the key &lt;code&gt;posts_per_page&lt;/code&gt; and change its value to your desired number (For Dawn theme, by default, this was set to 5. I  changed mine to 25).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save the changes to the file.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yhThwnHo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ajot.me/content/images/2023/08/image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yhThwnHo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ajot.me/content/images/2023/08/image.png" alt="Changing the Number of Posts Displayed Per Page in Ghost CMS" width="800" height="531"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Zip Up the Directory
&lt;/h3&gt;

&lt;p&gt;We need to prepare the modified theme for uploading back to Ghost CMS.&lt;/p&gt;

&lt;p&gt;Zip up the entire theme directory, ensuring all necessary files are included (on a Mac, you can right click on the directory, and click "Compress").&lt;/p&gt;

&lt;h3&gt;
  
  
  Upload the Modified Theme to Ghost CMS
&lt;/h3&gt;

&lt;p&gt;Finally, we need to upload the customized theme back to Ghost CMS.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Head back to the **Design** section in your Ghost CMS admin panel.&lt;/li&gt;
&lt;li&gt;Upload the zipped directory containing the modified theme.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fuuYFs86--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ajot.me/content/images/2023/08/CleanShot-2023-08-17-18-02-52%402x-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fuuYFs86--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ajot.me/content/images/2023/08/CleanShot-2023-08-17-18-02-52%402x-1.png" alt="Changing the Number of Posts Displayed Per Page in Ghost CMS" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fin!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>When, why, and how to upgrade spreadsheets to PostgreSQL</title>
      <dc:creator>Amit Jotwani</dc:creator>
      <pubDate>Mon, 01 May 2023 20:33:14 +0000</pubDate>
      <link>https://dev.to/retool/when-why-and-how-to-upgrade-spreadsheets-to-postgresql-2406</link>
      <guid>https://dev.to/retool/when-why-and-how-to-upgrade-spreadsheets-to-postgresql-2406</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally written by &lt;a href="https://twitter.comkevinwhinnery" rel="noopener noreferrer"&gt;Kevin Whinnery&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MptTWrLp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/spreadsheets-vs-postgres-blog-image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MptTWrLp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/spreadsheets-vs-postgres-blog-image.png" alt="When, why, and how to upgrade spreadsheets to PostgreSQL" width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Many businesses are run off of spreadsheets. From your local coffee shop to the &lt;a href="https://www.army.mil/article/128694/a_tool_to_assist_with_emplacing_support_units_on_the_battlefield?ref=retool.com" rel="noopener noreferrer"&gt;US military&lt;/a&gt;, spreadsheets handle a lot of heavy lifting in many workflows. More than &lt;a href="https://askwonder.com/research/number-google-sheets-users-worldwide-eoskdoxav?ref=retool.com" rel="noopener noreferrer"&gt;3 billion people use Excel and Google Sheets every month&lt;/a&gt;. Unfortunately, there comes a time when even this powerful and familiar tool becomes a liability. Spreadsheets can get out of sync when emailed back and forth, and are vulnerable to typos and human error. At a certain level of complexity, you should strongly consider moving from spreadsheets to a relational database, which provides more guarantees about the integrity of your data.&lt;/p&gt;

&lt;p&gt;Read on to learn about the warning signs of impending spreadsheet disaster, how to recognize them, and how to move to a database when the time is right.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Recognizing when spreadsheets are falling over&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;As your spreadsheets grow in complexity, here are some pains you might start to feel - if you read through these pain points and find yourself nodding along, it might be time to consider upgrading to a database.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;You’re struggling to manage large amounts of data&lt;/strong&gt;. Your key spreadsheet has dozens of columns and tens of thousands of rows of data. At around 40,000 rows, you’ve started to notice that scrolling and calculations are slowing down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You have too many editors, and you keep stepping on one another’s toes.&lt;/strong&gt; Spreadsheet software like Google Sheets supports multiple people editing at the same time (i.e. multiplayer collaboration), but they lack version and access controls. Your key spreadsheet is difficult to keep in a predictable state, because so many people need to change and update it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It’s hard to get the insights you want from data in spreadsheets.&lt;/strong&gt; Maybe your business uses several key spreadsheets, and you have a business intelligence question that needs data from all of them. You find it hard to use the relatively limited query and data modeling capabilities of spreadsheets to get all the insights you’d like from your data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Costly mistakes and bad data because of human error&lt;/strong&gt;. Spreadsheets are very malleable by default, and don’t have built-in mechanisms to ensure data integrity, like transactions, foreign key constraints, and data validation. As a result, sometimes your data is inaccurate, which impacts decision making and workflows that depend on spreadsheet data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These pain points typically appear when your use case evolves from data storage, analysis, and visualization. Spreadsheets actually do those things reasonably well. What spreadsheets don’t do well is manage large, complex data sets, and relationships between different data points. They also don’t work well to model business processes and operations, which often have multiple steps and validations that need to happen at different steps. When your use case shifts from analyzing static data to driving business processes, that’s when you’re likely to encounter trouble.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;No-code tools provide a partial solution&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Some teams might upgrade their spreadsheet use cases to no-code tools like Airtable. Airtable has become a popular alternative to spreadsheets by providing more structured data storage. And it’s easy to understand why it’s been so compelling - Airtable combines the simplicity of a spreadsheet interface with the basic functionality of a relational database. It also enables teams to create customizable views and simple apps, and integrate with other tools.&lt;/p&gt;

&lt;p&gt;This upgrade might be sufficient for some spreadsheet use cases, if your primary pain point is just modeling relationships between data points. There are a few spreadsheet problems that aren’t solved at this stage of evolution, however.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Performance issues and storage limits&lt;/strong&gt; : As the amount of data you store in Airtable bases grows, interacting with data through the GUI can start to slow down (similar to spreadsheet software). There are also limits to the size of your data set enforced by the product (250k rows in an Enterprise plan as of this writing).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modeling business workflows&lt;/strong&gt; : Airtable is a major upgrade over spreadsheets in data modeling, but it’s hard to model more complex, multi-step business processes. The ability to create forms and customized user-facing views is nice, but they are still tightly coupled to pushing data in and out of a spreadsheet-like data store.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inconsistent interfaces to data&lt;/strong&gt; : because views and schemas in Airtable are still quite malleable by default, data integrity can still become an issue without great care taken in configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limited query capabilities&lt;/strong&gt; : Airtable has a REST API to access data and resources, but its query capabilities are limited when compared to languages like SQL or GraphQL. So while this is probably a step up from a spreadsheet, it might still not be flexible enough.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No-code solutions such as Airtable provide a meaningful upgrade over spreadsheets, but still experience some of the problems at scale that you might be feeling in a spreadsheet. If you find yourself struggling still when using these tools - it’s probably time to consider moving your data to a relational database.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Building with a PostgreSQL database&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The final evolution from spreadsheets is a relational database - like our personal favorite &lt;a href="https://www.postgresql.org/?ref=retool.com" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt;. A relational database like Postgres can potentially address many issues in a spreadsheet that has ballooned into an unmaintainable state.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Performance and scale:&lt;/strong&gt; A database can store extremely large data sets, far beyond what’s reasonably possible in a spreadsheet-like tool. And because database software is designed to hold such large data sets, it is also designed to access that data fast, at any scale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Robust data modeling&lt;/strong&gt; : Relational databases are designed to support very complex data models - whatever types of data your business cares about, it should be possible to model it in a database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data integrity&lt;/strong&gt; : Relational databases are designed to promote data consistency and integrity. The format of data is strictly enforced, and prevents many classes of human error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advanced querying&lt;/strong&gt; : A database that supports SQL queries can enable you to answer just about any question from your data, joining individual data points across multiple tables within the database. SQL is the “lingua franca” of data access for a reason!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supports building application software on top&lt;/strong&gt; : A SQL database can be combined with a wide array of software development tools to build applications that interact with your data. Custom software enables you to model any kind of business workflow, rather than just pushing data in and out of a spreadsheet. And if you choose platforms like Retool, those applications can be almost as easy to create as customizing a spreadsheet or arranging a PowerPoint presentation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So let’s say you are sold on the utility of a relational database, but your data is still locked in Airtable or a spreadsheet. And maybe you’re pretty comfortable with a spreadsheet interface, but have never used a relational database before. If this sounds like you, &lt;a href="https://retool.com/products/database/?ref=retool.com" rel="noopener noreferrer"&gt;Retool Database&lt;/a&gt; might be the best first step into the world of managing data in a relational database. It offers both an optional graphical interface that is spreadsheet-like, and is integrated into Retool, which makes it much simpler to build software and visualizations on top of your data than typical app development tools. Let’s take a look, at a high level, how you could migrate from a spreadsheet to Retool Database.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;From spreadsheets to Retool Database&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Your first step in migrating from spreadsheets to a relational database will be exporting your data from either Airtable or your spreadsheet software as CSV files. This bare-bones text representation of your data can make it portable into different environments, like a relational database. Retool Database, through its graphical interface, allows you to import CSV files as new tables in your database.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BidZpS-r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-cc6a63ac-8e71-4bc6-87b4-a3acdecdf6df.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BidZpS-r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-cc6a63ac-8e71-4bc6-87b4-a3acdecdf6df.png" alt="When, why, and how to upgrade spreadsheets to PostgreSQL" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To migrate your spreadsheet or Airtable data to Retool Database, just upload your CSVs into Retool. Retool will automatically create PostgreSQL tables for you. There’s no setup or configuration required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RSMnjuTS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-51d90511-47a3-4881-9e3f-d4ae014717a3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RSMnjuTS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-51d90511-47a3-4881-9e3f-d4ae014717a3.png" alt="When, why, and how to upgrade spreadsheets to PostgreSQL" width="800" height="654"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Uploading a CSV into Retool Database&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once you’ve uploaded your CSV, Retool Database will create PostgreSQL data type columns and insert your data for you. Double-check that the names and field types match your preferences.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QSzIs_Bs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-1090abd6-0367-4697-beb0-8eaea6c8cd8e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QSzIs_Bs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-1090abd6-0367-4697-beb0-8eaea6c8cd8e.png" alt="When, why, and how to upgrade spreadsheets to PostgreSQL" width="800" height="460"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Schema mapping&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;From there, you can quickly build out your data model and schema with the Retool Database UI—add tables and configure fields in a few clicks.&lt;/p&gt;

&lt;p&gt;With Retool, you can start with a PostgreSQL database and build custom applications that work for you and your users. If you want to use Retool Database, you get up to 5GB of data storage free for one year. To get started, &lt;a href="https://login.retool.com/auth/signup?redirect=databaseSignup&amp;amp;ref=retool.com" rel="noopener noreferrer"&gt;create a free Retool account&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Custom apps and workflows&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;With your data now in a PostgreSQL database, you can use &lt;a href="https://retool.com/?ref=retool.com" rel="noopener noreferrer"&gt;Retool&lt;/a&gt;, a developer platform for business software, to build custom apps and workflows on top of your database much faster than coding from scratch. These apps can be complex and custom enough to handle many use cases, and can scale to thousands of users.&lt;/p&gt;

&lt;p&gt;Retool comes with several features to help you build faster.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Drag-and-drop interface&lt;/strong&gt; : Create user interfaces quickly and easily using Retool's visual editor, without needing to write extensive frontend code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-built components&lt;/strong&gt; : Leverage Retool's library of hundreds of pre-built components, such as tables, forms, and charts, to streamline your app development process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seamless integrations&lt;/strong&gt; : Connect your Retool app to your Postgres database and other third-party services using Retool's built-in integrations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collaboration and source control&lt;/strong&gt; : Work together with your team on app development and track changes with Retool's source control features.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automate ETL tasks and alerts&lt;/strong&gt; : Use Retool Workflows to schedule background tasks that operate on your databases, like periodically ingesting data from 3rd-party APIs or running GPT-4 against your database to answer questions with plain English.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;An optimal path forward&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;There’s a time and place for every tool in your toolkit, and spreadsheets are a valuable arrow in your quiver. But when your spreadsheet becomes the system of record for critical business data, and your business processes rely on manual efforts that modify the spreadsheet - I’m afraid you’re asking for trouble at some point down the line. At some point, human error or software limitations will create problems for your business.&lt;/p&gt;

&lt;p&gt;Relational databases provide an alternative to mega-spreadsheets that is scalable and reliable, and opens up the possibility of creating custom software on top of your database to run your business processes in a safe and repeatable way. If you’re coming from the world of spreadsheets, &lt;a href="https://retool.com/products/database?ref=retool.com" rel="noopener noreferrer"&gt;Retool Database&lt;/a&gt; can help ease the transition to relational databases with a graphical interface, and the power of the Retool platform to build business processes and data visualizations on top of your data.&lt;/p&gt;

&lt;p&gt;If you have questions or want to share your experience working between spreadsheets and PostgreSQL, &lt;a href="https://discord.com/invite/aEHwpVd7yF?ref=retool.com" rel="noopener noreferrer"&gt;join us on Discord&lt;/a&gt; to chat about it live​, or post about your experience in our &lt;a href="https://community.retool.com/c/retool-tips-and-tricks/25?ref=retool.com" rel="noopener noreferrer"&gt;community forums&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Analyzing LEGO set data with Retool Workflows</title>
      <dc:creator>Amit Jotwani</dc:creator>
      <pubDate>Fri, 28 Apr 2023 00:54:56 +0000</pubDate>
      <link>https://dev.to/retool/building-data-insights-brick-by-brick-with-retool-workflows-2nlk</link>
      <guid>https://dev.to/retool/building-data-insights-brick-by-brick-with-retool-workflows-2nlk</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0JEYLxzu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/blog-image_1024x400%402x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0JEYLxzu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/blog-image_1024x400%402x.png" alt="Building data insights brick-by-brick with Retool Workflows" width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Analyzing and visualizing data is a crucial part of any business or organization, but it can be tedious and time-consuming. As someone who likes finding ways to save time and effort, I’m game to explore anything that can make this kind of work faster and easier. That’s where Retool Workflows come in.&lt;/p&gt;

&lt;p&gt;Retool Workflows make it easy for developers to create automated processes that send custom alerts, and analyze and visualize data using a graphical interface—and to run those processes on a schedule.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://retool.com/blog/introducing-python-in-retool-workflows/" rel="noopener noreferrer"&gt;Python support recently added to Retool Workflows&lt;/a&gt;, I wanted to play around and do some fun data analysis with it. I thought about some datasets to use—IMDb movie ratings, global coffee production statistics, UFO sightings... But I kept coming back to something that I love even as an adult—something that felt a little poetic, given the parallels I recognized with Retool: LEGO!&lt;/p&gt;

&lt;p&gt;Just like how LEGO pieces of different shapes and sizes connect to build pretty much anything you can dream up, Retool Workflows provide a way to drag and drop blocks of code, customize them, and connect them to create a seamless automation for data analysis. Building with Retool Workflows is, I realized, like building a LEGO model, but instead of using plastic bricks, you’re using Python/JavaScript code blocks. Instead of creating a castle or a spaceship, you can build a data analysis workflow that streamlines your ETL and reporting tasks.&lt;/p&gt;

&lt;p&gt;So, with LEGO as inspiration, allow me to demonstrate how Retool Workflows can be used to analyze data and create stunning visualizations using Python libraries like &lt;a href="https://pypi.org/project/pandas/?ref=retool.com" rel="noopener noreferrer"&gt;pandas&lt;/a&gt;, &lt;a href="https://pypi.org/project/seaborn/?ref=retool.com" rel="noopener noreferrer"&gt;seaborn&lt;/a&gt;, and &lt;a href="https://pypi.org/project/matplotlib/?ref=retool.com" rel="noopener noreferrer"&gt;matplotlib&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We’ll use &lt;a href="https://www.kaggle.com/datasets/rtatman/lego-database?select=sets.csv&amp;amp;ref=retool.com" rel="noopener noreferrer"&gt;this dataset available on Kaggle.com&lt;/a&gt; to analyze and visualize the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Number of LEGO sets released over the years:&lt;/strong&gt; We’ll dive into the data to see how many LEGO sets were produced each year and track how that number has evolved over time, allowing us to gain insights into the growth of the LEGO world.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Average number of pieces per LEGO set over the years:&lt;/strong&gt; How many pieces does the average LEGO set contain? We’ll crunch the numbers to find out, and explore how this has changed over time as sets have become more complex and detailed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can download the JSON for these workflows here: &lt;a href="https://gist.github.com/ajot/d46a7a01f2cc6c50aa31c369a9f97ae3?ref=retool.com" rel="noopener noreferrer"&gt;Workflow 1: Number of sets produced over the years&lt;/a&gt;, and &lt;a href="https://gist.github.com/ajot/8252b64ec7f3b4601cf656acbfe9d704?ref=retool.com" rel="noopener noreferrer"&gt;Workflow 2: Average number of pieces per LEGO set over the years&lt;/a&gt;), and &lt;a href="https://docs.retool.com/docs/import-export-apps?ref=retool.com" rel="noopener noreferrer"&gt;import it into your Retool account&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ready? Let’s get building.&lt;/p&gt;




&lt;h2&gt;
  
  
  Analyzing LEGO set data with Retool Workflows
&lt;/h2&gt;

&lt;p&gt;Here’s the basic outline that we'll be following to build the workflows for each of these analyses:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Workflow Start Trigger:&lt;/strong&gt; This block will be the starting point of our Retool workflow. We can configure this block to &lt;a href="https://docs.retool.com/docs/retool-workflows-triggers?ref=retool.com" rel="noopener noreferrer"&gt;trigger our workflow&lt;/a&gt; at regular intervals using Schedule/Cron or Webhook.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fetch your data (getLEGOSetsData):&lt;/strong&gt; This block will use SQL to query &lt;a href="https://retool.com/products/database?ref=retool.com" rel="noopener noreferrer"&gt;Retool Database&lt;/a&gt; for the LEGO set data we want to analyze.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customize with code (LEGOSetsPerYear):&lt;/strong&gt; This is where the magic happens! We’ll use Python libraries like &lt;code&gt;pandas&lt;/code&gt;, &lt;code&gt;seaborn&lt;/code&gt;, and &lt;code&gt;matplotlib&lt;/code&gt; to analyze the data and create visualizations. For example, we can use &lt;code&gt;pandas&lt;/code&gt; to group our data by year and then use &lt;code&gt;seaborn&lt;/code&gt; to create a bar chart showing the number of sets produced each year.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fire an action (UploadToAmazonS3Bucket&lt;/strong&gt; and &lt;strong&gt;getS3URL):&lt;/strong&gt; Once we have our visualizations, we can save them to a file buffer and then use Retool’s native integration for &lt;a href="https://docs.retool.com/docs/s3-integration?ref=retool.com" rel="noopener noreferrer"&gt;Amazon S3&lt;/a&gt; to upload the files to our S3 bucket. This will allow us to easily share the images with others or include them in reports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SendEmail:&lt;/strong&gt; Finally, we’ll use Retool’s native integration for &lt;a href="https://docs.retool.com/docs/sendgrid-integration?ref=retool.com" rel="noopener noreferrer"&gt;SendGrid&lt;/a&gt; to send the visualizations in an email. This is a great way to share our analysis with others or to receive automated reports.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s what the final workflow will look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cAnVnWyb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-c8046541-6477-4dfd-8b2f-62df7422a0cc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cAnVnWyb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-c8046541-6477-4dfd-8b2f-62df7422a0cc.png" alt="Building data insights brick-by-brick with Retool Workflows" width="800" height="376"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Bird's eye view of the final Python workflow to visualize the number of LEGO sets released over the years&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Let’s build the workflow
&lt;/h3&gt;

&lt;p&gt;The StartTrigger block is the entry point for the workflow. You can select this to either be a &lt;strong&gt;Schedule/Cron job&lt;/strong&gt; or a &lt;strong&gt;Webhook&lt;/strong&gt;. We’ll just let it be at its default value, but feel free to play around with it if you like.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YkWDC_Ft--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-8d5f98e5-9b01-4527-95ee-3100619be7d8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YkWDC_Ft--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-8d5f98e5-9b01-4527-95ee-3100619be7d8.png" alt="Building data insights brick-by-brick with Retool Workflows" width="754" height="668"&gt;&lt;/a&gt;&lt;br&gt;
_You can select the Start Trigger to either be a Schedule/Cron job or a Webhook. _&lt;/p&gt;

&lt;p&gt;Click on the “+” button on the left side bar, and then click on “Resource Query”. Then click on the canvas to place it next to the StartTrigger block.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xCGyNxO7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-302aa182-69dc-47fc-b3f7-d4a45cc07ae5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xCGyNxO7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-302aa182-69dc-47fc-b3f7-d4a45cc07ae5.png" alt="Building data insights brick-by-brick with Retool Workflows" width="492" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inside the Resource Query block, choose the source for your database. This could be any of the &lt;a href="https://retool.com/integrations/?ref=retool.com" rel="noopener noreferrer"&gt;data integrations&lt;/a&gt; supported by Retool, including any REST/GraphQL API, or databases like PostgreSQL, DynamoDB, or MongoDB. In this case, I downloaded &lt;a href="https://www.kaggle.com/datasets/rtatman/lego-database?select=sets.csv&amp;amp;ref=retool.com" rel="noopener noreferrer"&gt;this LEGO dataset available on Kaggle.com&lt;/a&gt; in CSV format, and imported it data into Retool’s built-in database using the CSV import feature.&lt;/p&gt;

&lt;p&gt;So, we’ll choose &lt;code&gt;retool_db (postgresql)&lt;/code&gt; as the resource, and then type the SQL query to retrieve the LEGO data set from Retool Database.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VhIfx8LM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-b2731e53-5af0-41e8-b85a-79cd815250ae.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VhIfx8LM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-b2731e53-5af0-41e8-b85a-79cd815250ae.png" alt="Building data insights brick-by-brick with Retool Workflows" width="800" height="638"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can run each block individually to see the results of that block, like we did to run the SQL query.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Eu6Xrn-5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-735e216a-a214-478d-934c-60d86a66007c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Eu6Xrn-5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-735e216a-a214-478d-934c-60d86a66007c.png" alt="Building data insights brick-by-brick with Retool Workflows" width="800" height="1048"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Write SQL to get the data for LEGO sets&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now that we have our LEGO data available in our workflow, we’re ready to write some Python code to analyze this data.&lt;/p&gt;
&lt;h2&gt;
  
  
  Analysis 1: The number of sets produced over the years
&lt;/h2&gt;

&lt;p&gt;We’ll add a “Code” block to our workflow, and write some Python code to analyze the data using the Python libraries mentioned earlier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JadaPlA7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-cfab40d8-99ee-4b16-86bf-d84b0d95acd0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JadaPlA7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-cfab40d8-99ee-4b16-86bf-d84b0d95acd0.png" alt="Building data insights brick-by-brick with Retool Workflows" width="800" height="490"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Add a Code block to write some Python code to analyze the data&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import pandas as pd 
import seaborn as sns 
import matplotlib.pyplot as plt 
import io 
import uuid

# create a pandas dataframe called lego_sets using the lego set data retrieved in the previous block
lego_sets = pd.DataFrame(getLegoSetsData.data) 

# count the number of sets released per year and store it in a new dataframe called sets_per_year
sets_per_year = lego_sets["year"].value_counts().reset_index() 

# set the column names of the sets_per_year dataframe
sets_per_year.columns = ["year", "count"] 

# sort the data by year
sets_per_year = sets_per_year.sort_values("year") 

# create a bar plot
plt.figure(figsize=(14, 6)) # set the figure size
sns.barplot(x="year", y="count", data=sets_per_year) # set the x and y axis, and the data for the plot

# add title and labels to the plot
plt.title("Number of LEGO Sets Released per Year") 
plt.xlabel("Year") 
plt.ylabel("Number of Sets") 

# rotate x-axis labels for better readability
plt.xticks(rotation=90) 

# display the plot
plt.show() 

# save the plot as a PNG image in a file buffer
file_buffer = io.BytesIO() 
plt.savefig(file_buffer, format='png') 
file_buffer.seek(0) 

# get the image data from the file buffer
image_data = file_buffer.getvalue() 

# Generate a random UUID (Universally Unique Identifier)
file_name = str(uuid.uuid4())

# Append a file extension
file_name += ".png"

# return the image data
return {"file_name": file_name,"image_data": image_data}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What’s this Python code doing?
&lt;/h3&gt;

&lt;p&gt;To get started, I’ve imported three handy libraries: &lt;code&gt;pandas&lt;/code&gt;, &lt;code&gt;seaborn&lt;/code&gt;, and &lt;code&gt;matplotlib&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pandas&lt;/code&gt; is a library that helps us work with tables of data. It’s useful when we have a lot of data to work with or when we need to combine different types of data (like numbers and text), like we do with our LEGO dataset.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;seaborn&lt;/code&gt; helps us make pretty graphs and charts from data. We can use it to visualize data in different ways to better understand patterns and relationships.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;matplotlib&lt;/code&gt; helps us make all kinds of graphs and charts. It’s very versatile, in the sense that we can use it to make simple or complex visualizations. We can use it to make things like line graphs, scatter plots, and bar charts. We’ll use a combination of &lt;code&gt;seaborn&lt;/code&gt; and &lt;code&gt;matplotlib&lt;/code&gt; to generate this graph to find out &lt;strong&gt;the number of sets released over the years.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We also use the library &lt;code&gt;io&lt;/code&gt; which is a part of Python and helps you work with input and output, like reading and writing files, strings, or byte arrays. We use this to create a file buffer object that stores the image data generated by our plotting libraries in memory as bytes.&lt;/p&gt;

&lt;p&gt;We then get the image data from the file buffer object for further use—in this case, we’re storing it in an S3 bucket and using that to send an email.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rMcNq0th--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-1e1d4e3b-3edf-4fd1-83d0-11ccad91404b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rMcNq0th--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-1e1d4e3b-3edf-4fd1-83d0-11ccad91404b.png" alt="Building data insights brick-by-brick with Retool Workflows" width="800" height="343"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Visualization of the Number of LEGO Sets Released per Year&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;💡&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Side note:&lt;/strong&gt; Apart from the drop off we see in 2017, which is probably because the dataset only covers up until July of that year, it’s quite evident from this chart that LEGO is now producing more sets than in decades past, which is great news.&lt;/p&gt;

&lt;p&gt;Next, we’ll connect this to another resource query, this time for an S3 bucket.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LARyhflh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-6aa11fb1-fe7e-40bd-bcae-bb780f8ec681.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LARyhflh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-6aa11fb1-fe7e-40bd-bcae-bb780f8ec681.png" alt="Building data insights brick-by-brick with Retool Workflows" width="800" height="673"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Upload the image file to Amazon S3 Bucket using Retool's built-in Amazon S3 integration&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To make it easier to reference the URL in our email, we’ll add another little Python code block to clean up the S3 URL, and we’ll grab the link to our file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qwPNXm71--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-52890395-6b9a-4752-8b6e-8589df984efc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qwPNXm71--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-52890395-6b9a-4752-8b6e-8589df984efc.png" alt="Building data insights brick-by-brick with Retool Workflows" width="800" height="601"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Extract the S3 URL from the response sent by Amazon S3 using a Python code block&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Finally, we’ll reference that URL in our SendGrid email block.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KPS0QaZN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-0c456803-c8a6-4016-9426-4b19e8c82f99.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KPS0QaZN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-0c456803-c8a6-4016-9426-4b19e8c82f99.png" alt="Building data insights brick-by-brick with Retool Workflows" width="800" height="912"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Send an email using Retool’s built-in SendGrid integration&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here’s what the final workflow looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tBQUGQR3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-c8dd4c3a-8f27-4c65-806c-4a9740f3a2a5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tBQUGQR3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-c8dd4c3a-8f27-4c65-806c-4a9740f3a2a5.png" alt="Building data insights brick-by-brick with Retool Workflows" width="800" height="376"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Bird's eye view of the final Python workflow to visualize the number of LEGO sets released over the years&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Testing and deploying the workflow
&lt;/h3&gt;

&lt;p&gt;Now that we have the workflow setup, we can test it by clicking on the “Run” button at the top. This will manually run the entire workflow, so you can test if it’s functioning correctly. Once you’re satisfied, you can &lt;a href="https://docs.retool.com/docs/retool-workflows-deployment?ref=retool.com" rel="noopener noreferrer"&gt;enable and deploy it&lt;/a&gt;, so it will fire off using the settings in the StartTrigger block.&lt;/p&gt;


&lt;h2&gt;
  
  
  Analysis 2: Average number of pieces per LEGO set over the years
&lt;/h2&gt;

&lt;p&gt;We can follow the same process using this code to get the average number of pieces per LEGO set over the years.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import pandas as pd 
import seaborn as sns 
import matplotlib.pyplot as plt 
import io 

# create a pandas dataframe called lego_sets using the lego set data retrieved in the previous block
lego_sets = pd.DataFrame(getLegoSetsData.data) 

# group the dataset by year and calculate the mean of pieces per set for each year
pieces_per_set_per_year = lego_sets.groupby('year')['num_parts'].mean().reset_index()

# create a line plot of pieces per set over the years
plt.figure(figsize=(14, 6)) # set the figure size
sns.lineplot(x="year", y="num_parts", data=pieces_per_set_per_year) # set the x and y axis, and the data for the plot

# add title and labels to the plot
plt.title("Average Number of Pieces per Set over the Years") 
plt.xlabel("Year") 
plt.ylabel("Average Number of Pieces per Set") 

# rotate x-axis labels for better readability
plt.xticks(rotation=90) 

# display the plot
plt.show() 

# save the plot as a PNG image in a file buffer
file_buffer = io.BytesIO() 
plt.savefig(file_buffer, format='png') 
file_buffer.seek(0) 

# get the image data from the file buffer
image_data = file_buffer.getvalue() 

# return the image data
return image_data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--O9jwz5v4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-59f72c16-7814-4be1-bfbf-81c0445f4e29.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--O9jwz5v4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/04/data-src-image-59f72c16-7814-4be1-bfbf-81c0445f4e29.png" alt="Building data insights brick-by-brick with Retool Workflows" width="800" height="343"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Visualization of the Average Number of Pieces per LEGO Set over the Years&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We can see from this chart that the sets are becoming larger on average. More LEGOs!&lt;/p&gt;

&lt;p&gt;🧱&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fun Fact:&lt;/strong&gt; As of 2021 the largest LEGO set in terms of the number of pieces is the &lt;a href="https://www.lego.com/en-us/product/world-map-31203?ref=retool.com" rel="noopener noreferrer"&gt;LEGO Art World Map set&lt;/a&gt;, which contains 11,695 pieces.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting the pieces together
&lt;/h2&gt;

&lt;p&gt;To wrap up, with Retool Workflows’ support for Python, we were able to analyze a LEGO dataset using popular Python libraries like &lt;code&gt;pandas&lt;/code&gt;, &lt;code&gt;seaborn&lt;/code&gt;, and &lt;code&gt;matplotlib&lt;/code&gt; to create visualizations that helped us gather some pretty interesting insights.  &lt;/p&gt;

&lt;p&gt;With Retool’s native integrations for &lt;a href="https://docs.retool.com/docs/s3-integration?ref=retool.com" rel="noopener noreferrer"&gt;Amazon S3&lt;/a&gt; and &lt;a href="https://docs.retool.com/docs/sendgrid-integration?ref=retool.com" rel="noopener noreferrer"&gt;SendGrid&lt;/a&gt;, we were able to have the workflow upload the images to an S3 bucket and send the image in an email—all automatically.&lt;/p&gt;

&lt;p&gt;The libraries we used in this post are just the tip of the iceberg. Retool comes pre-installed with over &lt;a href="https://docs.retool.com/docs/retool-workflows-blocks?ref=retool.com#available-python-libraries" rel="noopener noreferrer"&gt;20 popular Python libraries and packages&lt;/a&gt;, including &lt;a href="https://pypi.org/project/openai/?ref=retool.com" rel="noopener noreferrer"&gt;openai&lt;/a&gt;, &lt;a href="https://pypi.org/project/beautifulsoup4/?ref=retool.com" rel="noopener noreferrer"&gt;beautifulsoup4&lt;/a&gt;, and &lt;a href="https://pypi.org/project/pillow/?ref=retool.com" rel="noopener noreferrer"&gt;pillow&lt;/a&gt;, making it a powerful tool for any data science project.&lt;/p&gt;

&lt;p&gt;You can download the JSON for these workflows here: &lt;a href="https://gist.github.com/ajot/d46a7a01f2cc6c50aa31c369a9f97ae3?ref=retool.com" rel="noopener noreferrer"&gt;Workflow 1: Number of sets produced over the years&lt;/a&gt;, and &lt;a href="https://gist.github.com/ajot/8252b64ec7f3b4601cf656acbfe9d704?ref=retool.com" rel="noopener noreferrer"&gt;Workflow 2: Average number of pieces per LEGO set over the years&lt;/a&gt;), and &lt;a href="https://docs.retool.com/docs/import-export-apps?ref=retool.com" rel="noopener noreferrer"&gt;import it into your Retool account&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I hope you found this post helpful in demonstrating how to use Retool Workflows to automate your data analysis projects. Questions, comments, or favorite LEGO sets? &lt;a href="https://twitter.com/amit?ref=retool.com" rel="noopener noreferrer"&gt;Feel free to reach out to me on Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. My favorite LEGO set is the&lt;/em&gt; &lt;a href="https://www.lego.com/en-us/product/seinfeld-21328?ref=retool.com" rel="noopener noreferrer"&gt;&lt;em&gt;Seinfeld set&lt;/em&gt;&lt;/a&gt;&lt;em&gt;—it has a total of 1,326 pieces!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>visualization</category>
      <category>datascience</category>
      <category>pandas</category>
    </item>
    <item>
      <title>Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool</title>
      <dc:creator>Amit Jotwani</dc:creator>
      <pubDate>Wed, 01 Mar 2023 20:41:00 +0000</pubDate>
      <link>https://dev.to/ajot/building-a-custom-amazon-ec2-instance-admin-panel-for-devops-with-retool-7ja</link>
      <guid>https://dev.to/ajot/building-a-custom-amazon-ec2-instance-admin-panel-for-devops-with-retool-7ja</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xz73BFm7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/03/amazon_ec2_instance_manager_retool_app-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xz73BFm7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/03/amazon_ec2_instance_manager_retool_app-1.png" alt="Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool" width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Custom scripts and tools are a common way for DevOps professionals to manage resources within their infrastructure.&lt;/p&gt;

&lt;p&gt;These scripts, however can become challenging to run, share, scale, and maintain, particularly as the complexity of the infrastructure grows and the team size expands.&lt;/p&gt;

&lt;p&gt;In this blog post, we'll explore how you can use Retool to build custom easy to use admin panels that can simplify your common DevOps processes.&lt;/p&gt;

&lt;p&gt;We will use an example of managing Amazon EC2 virtual servers to demonstrate how Retool can help you build a custom tool that simplifies common tasks, such as launching new instances, starting and stopping instances, and checking instance status.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R7ISIObL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://retool.com/blog/content/images/2023/03/ec2_demo_sped_up_AdobeExpress.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R7ISIObL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://retool.com/blog/content/images/2023/03/ec2_demo_sped_up_AdobeExpress.gif" alt="Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool" width="384" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By the end of this post, you'll understand how you can use Retool’s drag-and-drop UI components and built-in integrations to quickly convert common DevOps processes into flexible software accessible to your entire team.&lt;/p&gt;

&lt;p&gt;For the impatient - feel free to simply download the &lt;a href="https://gist.githubusercontent.com/ajot/612139711531e97915a40232775af8cc/raw/bae2a236288d7887605883f908f8ba961cad682b/aws_devops_ec2_instance_manager.json?ref=retool-blog"&gt;JSON&lt;/a&gt; definition of this Retool app, which you can &lt;a href="https://docs.retool.com/docs/import-export-apps?ref=retool-blog"&gt;import&lt;/a&gt; into your own free Retool instance.&lt;/p&gt;

&lt;p&gt;Let’s get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect Retool with Amazon EC2 API
&lt;/h2&gt;

&lt;p&gt;Log in to Retool and create a new Resource for &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/APIReference/making-api-requests.html?ref=retool-blog"&gt;Amazon EC2 API&lt;/a&gt; using Retool's built-in &lt;a href="https://docs.retool.com/docs/connect-api-resource?ref=retool-blog"&gt;REST API&lt;/a&gt; integration. We will be using &lt;a href="https://docs.retool.com/docs/api-authentication?ref=retool-blog#aws-v4-signature-based-authentication"&gt;Retool’s built-in AWS v4 authentication&lt;/a&gt;, so we wouldn’t need to worry about writing complex authentication code. Retool will handle that for us in a secure way.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name -&amp;gt; &lt;code&gt;Amazon EC2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Base URL -&amp;gt; &lt;code&gt;https://ec2.amazonaws.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Headers -&amp;gt; &lt;code&gt;Accept: application/json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Authentication -&amp;gt; &lt;code&gt;AWS v4&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;AWS Region -&amp;gt; &lt;code&gt;&amp;lt;YOUR AWS REGION&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;AWS Access Key ID -&amp;gt; &lt;code&gt;&amp;lt;YOUR AWS Access Key ID&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;AWS Secret Key ID -&amp;gt; &lt;code&gt;&amp;lt;YOUR AWS Secret Key ID&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡&lt;/p&gt;

&lt;p&gt;Note: You would need an AWS Access Key ID and AWS Secret Key ID for the IAM role or user with the &lt;code&gt;AmazonEC2FullAccess&lt;/code&gt; policy attached for this step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jxxlZOzl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/03/CleanShot-2023-02-26-at-18.16.17%402x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jxxlZOzl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/03/CleanShot-2023-02-26-at-18.16.17%402x.png" alt="Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool" width="800" height="831"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Setting up the Resource for Amazon EC2 API in the "Resources" tab&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Create a new Retool app
&lt;/h2&gt;

&lt;p&gt;Now that we have connected Retool with Amazon EC2 API, we are ready to build the actual user interface to display it. But before we add UI components to our canvas, let's look at the data we would need to build out the functionality for our EC2 Instance Manager, and how we will get it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;List of EC2 Instances - Send GET request to EC2 API's &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html?ref=retool-blog"&gt;DescribeInstances&lt;/a&gt; Action.&lt;/li&gt;
&lt;li&gt;Launch a new EC2 Instance - Send POST request to EC2 API's &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RunInstances.html?ref=retool-blog"&gt;RunInstances&lt;/a&gt; Action&lt;/li&gt;
&lt;li&gt;Stop an Instance by Instance ID - Send POST request to EC2 API's &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_StopInstances.html?ref=retool-blog"&gt;StopInstances&lt;/a&gt; Action, along with the Instance ID.&lt;/li&gt;
&lt;li&gt;Start an Instance by Instant ID - Send POST request to EC2 API's &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_StartInstances.html?ref=retool-blog"&gt;StartInstances&lt;/a&gt; Action, along with the Instance ID.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Part 1: Displaying the list of EC2 Instances in a table
&lt;/h2&gt;

&lt;p&gt;We will create a new &lt;strong&gt;GET&lt;/strong&gt; Resource Query for the EC2 API’s &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html?ref=retool-blog"&gt;DescribeInstances&lt;/a&gt; action.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--d8lzKXuW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh6.googleusercontent.com/bmt-7SPkk_oS9xhl1l0fqu04WhqlWIcL4w_RiR-I4VhgdmuCmfaCpZyIG4RTyZbzQRd80XM5-bL_fNJLpgP6OJmZuz0VWDjY8lYbvk7ErzaMUSMycwBjhz0fN5NvWNWVyOPCDxiNfUWSC8sWmVq-uVs" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--d8lzKXuW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh6.googleusercontent.com/bmt-7SPkk_oS9xhl1l0fqu04WhqlWIcL4w_RiR-I4VhgdmuCmfaCpZyIG4RTyZbzQRd80XM5-bL_fNJLpgP6OJmZuz0VWDjY8lYbvk7ErzaMUSMycwBjhz0fN5NvWNWVyOPCDxiNfUWSC8sWmVq-uVs" alt="Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool" width="800" height="316"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Setting up the getAllEC2Instances Resource Query&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let’s name it &lt;code&gt;getAllEC2Instances&lt;/code&gt;. We will choose the resource we created earlier for Amazon EC2 API as the Resource. Choose the Action Type as GET. The base URL for the Resource Query will automatically be set to &lt;code&gt;ec2.amazonaws.com&lt;/code&gt;. Add this as URL params -&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;?Action=DescribeInstances&amp;amp;Filter.1.Name=instance-type&amp;amp;Filter.1.Value.1=t2.micro&amp;amp;Filter.2.Name=architecture&amp;amp;Filter.2.Value.1=x86_64&amp;amp;Version=2016-11-15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this request, we are using two filters to search for instances with specific attributes -&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;instance-type:&lt;/strong&gt; Instance type refers to an EC2 template that specifies the amount of resources (such as CPUs and memory) that a virtual machine will have. In this case, the value for the instance-type filter is "t2.micro", which are instances that have 1 vCPU and 1GB of memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;architecture:&lt;/strong&gt; Architecture refers to the type of processor used by the instance. In this case, the value for the architecture filter is "x86_64", which refers to a 64-bit processor architecture commonly used in personal computers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s try out this query by hitting the &lt;strong&gt;Preview&lt;/strong&gt; button. You will see the Response of the query in the panel under the query.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parsing the XML returned by Amazon EC2 API
&lt;/h3&gt;

&lt;p&gt;The Amazon EC2 API returns results in XML format. Luckily, Retool parses that XML automatically for us, and presents that in a JSON format. You can view this data by clicking on the &lt;strong&gt;State&lt;/strong&gt; tab in the left sidebar. The instance data we are looking for is nested under &lt;code&gt;data -&amp;gt; parsedXML -&amp;gt; DescribeInstanceResponses -&amp;gt; reservationSet -&amp;gt; 0 -&amp;gt; item&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VJTw4cTc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh5.googleusercontent.com/SziRrksS03dUXDEbYYrf6oaR_DfLObMJZF2mE1R5og0N6g_wfmfXFXgd3JalCO273gpzmezyE44EJPkeLAiDOCBzyVp4mtsTR7o8_PPoE08bPTAsk3mYddQRXJ6PNSG69uvLNRsOZLjnvkl_EIw6ESU" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VJTw4cTc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh5.googleusercontent.com/SziRrksS03dUXDEbYYrf6oaR_DfLObMJZF2mE1R5og0N6g_wfmfXFXgd3JalCO273gpzmezyE44EJPkeLAiDOCBzyVp4mtsTR7o8_PPoE08bPTAsk3mYddQRXJ6PNSG69uvLNRsOZLjnvkl_EIw6ESU" alt="Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool" width="279" height="821"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Viewing the result of the EC2 API in the State tab&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Transforming the data to make it easy to navigate&lt;/p&gt;

&lt;p&gt;The XML data returned by the EC2 API is deeply nested, making it difficult to extract the relevant information for each instance. So, we will write a bit of custom JavaScript code to “transform” the data into an easily readable array of objects using a JavaScript &lt;a href="https://docs.retool.com/docs/transformers?ref=retool-blog"&gt;Transformer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The fields we want to display in our table are - &lt;em&gt;Architecture, DNS Name, Instant State, Image ID, Reservation ID, IP Address, Instance Name, Launch Time, Instance ID, Group Name, and Region&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So, let’s add a new Transformer with this JavaScript code to simplify the data into an array of objects that contain only the relevant information we need for each instance, making it much easier to read and work with. We will then use the output of this function to power other parts of our app, starting with the Table.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7NE5gOLk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh4.googleusercontent.com/P3Ft5J256z_bjhJRO1HrsvCSB1c6mdVZJd0DV4s4RaOA4NP6XqWnxv01I4SP2Lec3MYH4QSW_EXaG5CxH7kvVqyfnXkgpeL0o3QLYGRSBCrLjGE_QpGiSRkmFKpnPWdAe57XNyqyb5Ijukr0fbW44-M" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7NE5gOLk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh4.googleusercontent.com/P3Ft5J256z_bjhJRO1HrsvCSB1c6mdVZJd0DV4s4RaOA4NP6XqWnxv01I4SP2Lec3MYH4QSW_EXaG5CxH7kvVqyfnXkgpeL0o3QLYGRSBCrLjGE_QpGiSRkmFKpnPWdAe57XNyqyb5Ijukr0fbW44-M" alt="Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool" width="758" height="328"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Add a new Transformer to "transform" aka restructure the data returned by the EC2 API&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Store data from the EC2 API in the variable data
const data = {{getAllEC2Instances.data.parsedXml.DescribeInstancesResponse.reservationSet['0'].item}};

// Initialize an empty array to hold final data
let finalData = [];

// Function to capitalize the first letter of a string
function capitalizeFirstLetter(string) {
  return string.charAt(0).toUpperCase() + string.slice(1);
}

// Loop through each key in the data object
Object.keys(data).forEach(key =&amp;gt; {

  // Retrieve specific data points for each instance and assign to variables
  let architecture = data[key].instancesSet[0].item[0].architecture[0];
  let dnsName = data[key].instancesSet[0].item[0].dnsName[0];
  let instanceState = capitalizeFirstLetter(data[key].instancesSet[0].item[0].instanceState[0].name[0]);
  let imageId = data[key].instancesSet[0].item[0].imageId[0];
  let reservationId = data[key].reservationId[0];
  let ipAddress = typeof(data[key].instancesSet[0].item[0].ipAddress) !== 'undefined' ? data[key].instancesSet[0].item[0].ipAddress[0] : 'N/A';
  let instanceName = typeof(data[key].instancesSet[0].item[0].tagSet) !== 'undefined' ? data[key].instancesSet[0].item[0].tagSet[0].item[0].value[0] : 'N/A'; 
  let launchTime = data[key].instancesSet[0].item[0].launchTime[0];
  let instanceId = data[key].instancesSet[0].item[0].instanceId[0];
  let groupName = data[key].instancesSet[0].item[0].groupSet[0].item[0].groupName[0];
  let region = data[key].instancesSet[0].item[0].placement[0].availabilityZone[0];

  // Create a new object containing all relevant data points
  let newObj = {
    'architecture': architecture,
    'dnsName': dnsName,
    'instanceState': instanceState,
    'imageId': imageId,
    'reservationId': reservationId,
    'ipAddress': ipAddress,
    'instanceName': instanceName,
    'launchTime': launchTime,
    'instanceId': instanceId,
    'groupName': groupName,
    'region': region
  };

  // Add new object to the finalData array
  finalData.push(newObj);
});

// Log finalData to the console and return it
console.log(finalData);
return finalData;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click on &lt;strong&gt;Preview&lt;/strong&gt; to see the results of this transformer. As you can see, it’s much cleaner and easier to read and navigate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GtCkQ_t0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/02/CleanShot-2023-02-27-at-14.18.13%402x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GtCkQ_t0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/02/CleanShot-2023-02-27-at-14.18.13%402x.png" alt="Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool" width="800" height="402"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Click on Preview to see the results of this transformer as a JSON object&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Displaying the data in the table
&lt;/h3&gt;

&lt;p&gt;Drag the &lt;a href="https://retool.com/components/table?ref=retool-blog"&gt;Table Component&lt;/a&gt; on to the canvas, and change the &lt;strong&gt;Data&lt;/strong&gt; property to reference our formatted EC2 instance data using &lt;a href="https://docs.retool.com/docs/transformers?ref=retool-blog#include-a-variable-from-your-app"&gt;Retool's double curly brace data binding&lt;/a&gt; to &lt;code&gt;{{transformInstanceData.value}}&lt;/code&gt; .&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--A7jH4tkR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/02/CleanShot-2023-02-27-at-11.48.50%402x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A7jH4tkR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/02/CleanShot-2023-02-27-at-11.48.50%402x.png" alt="Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool" width="630" height="1490"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Changing the data property of the table to refer to the results of the JavaScript Transformer&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The table will be populated with the data for your EC2 instances. Nice, we are on our way now with the list of EC2 instances being listed. But, it's a bit hard to know the status of these instances at a quick glance. Let's fix that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fU94eCj7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh5.googleusercontent.com/dqSYjYoAwbLCsseaD2bxfHZRLgfz5HhCidVwfT_RPRRJMvT4xjgWZch7ufIyr4P4Dun7xQ2GM3C2JmAY7Itg8Aqqx_G3q41cXV1uB7PhpeDgIRkWVnUTz1EzLAN_S_PUFQcCZAnEf1VYCEfXt5mK6wQ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fU94eCj7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh5.googleusercontent.com/dqSYjYoAwbLCsseaD2bxfHZRLgfz5HhCidVwfT_RPRRJMvT4xjgWZch7ufIyr4P4Dun7xQ2GM3C2JmAY7Itg8Aqqx_G3q41cXV1uB7PhpeDgIRkWVnUTz1EzLAN_S_PUFQcCZAnEf1VYCEfXt5mK6wQ" alt="Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool" width="800" height="326"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Populate table data with the list of EC2 Instances&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Add background colors for “running”, and “stopped” instances
&lt;/h3&gt;

&lt;p&gt;To make it easier to see the state of the instances, let’s add a background color to the &lt;code&gt;Instance State&lt;/code&gt; column of our table. Change the &lt;strong&gt;Background&lt;/strong&gt; property of the Instance State column to this conditional -&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{self.toLowerCase() === "running" ? 'rgba(165, 255, 110, 0.5)':self.toLowerCase() === "stopped" ? 'rgba(255, 141, 150, 0.5)':'rgba(255, 188, 99, 0.5)'}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Add background colors for “running”, and “stopped” instances&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If the state is “running”, then the color code rgba(165, 255, 110, 0.5) is returned, representing a light &lt;strong&gt;green&lt;/strong&gt; color with a 50% transparency.&lt;/p&gt;

&lt;p&gt;If the state is “stopped”, then the color code rgba(255, 188, 99, 0.5) is returned, representing a light &lt;strong&gt;red&lt;/strong&gt; color with a 50% transparency.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--K1CPyc6h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh3.googleusercontent.com/LqaCyiagZ4y667zU5SFyq7FLPlk5t2Ysl1KfNCaL912Yz5vQuhUfvuseaaGQnSX_EoWgrIN0JSY90qJq0GDLVegUgvocXHPh_irozM8bZ_Oskl9Zhu6R19pGROchK8LMadmDojvW95lro6TyNs1Ee7A" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K1CPyc6h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh3.googleusercontent.com/LqaCyiagZ4y667zU5SFyq7FLPlk5t2Ysl1KfNCaL912Yz5vQuhUfvuseaaGQnSX_EoWgrIN0JSY90qJq0GDLVegUgvocXHPh_irozM8bZ_Oskl9Zhu6R19pGROchK8LMadmDojvW95lro6TyNs1Ee7A" alt="Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool" width="800" height="322"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Add background colors for “running”, and “stopped” instances&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Part 2: Launching a new EC2 Instance (POST RunInstances)
&lt;/h2&gt;

&lt;p&gt;Create a new &lt;strong&gt;POST&lt;/strong&gt; Resource Query for the EC2 API’s  &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RunInstances.html?ref=retool-blog"&gt;RunInstances&lt;/a&gt; action.&lt;/p&gt;

&lt;p&gt;We will eventually add a &lt;a href="https://retool.com/components/listbox?ref=retool-blog"&gt;Listbox&lt;/a&gt; component to select the image type, but for now, let's hardcode the image ID to &lt;code&gt;ami-0dfcb1ef8550277af&lt;/code&gt;, which is a Linux image provided by Amazon in the AWS Free Tier called &lt;code&gt;Amazon Linux 2 AMI (HVM) - Kernel 5.10&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;launchNewEC2Instance&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Action Type -&amp;gt; &lt;code&gt;POST&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Headers -&amp;gt; &lt;code&gt;application/x-www-form-urlencoded; charset=utf-8&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Body -&amp;gt; &lt;code&gt;x-www-form-urlencoded&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Body Params:&lt;/li&gt;
&lt;li&gt; Action -&amp;gt; &lt;code&gt;RunInstances&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt; ImageId -&amp;gt; &lt;code&gt;ami-0dfcb1ef8550277af&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt; InstanceType -&amp;gt; &lt;code&gt;t2.micro&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt; MinCount -&amp;gt; &lt;code&gt;1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt; MaxCount -&amp;gt; &lt;code&gt;1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt; Version -&amp;gt; &lt;code&gt;2016-11-15&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eVw3Fq29--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh4.googleusercontent.com/XzDsWqBvOzlVBbkxN_k28aJGIPn9KWceMOuPu9lUQJjW3VLGYX-HKPgrnMRdCKaWj0upsTsvTTyDVlOzymlur2aiippXRWGAMNkWHTZe5JzHceJOIL882hN3V6bQLxBr0eTf2WP7voZaiWkNY0O_e-0" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eVw3Fq29--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh4.googleusercontent.com/XzDsWqBvOzlVBbkxN_k28aJGIPn9KWceMOuPu9lUQJjW3VLGYX-HKPgrnMRdCKaWj0upsTsvTTyDVlOzymlur2aiippXRWGAMNkWHTZe5JzHceJOIL882hN3V6bQLxBr0eTf2WP7voZaiWkNY0O_e-0" alt="Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool" width="800" height="471"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Setting up the launchNewEC2Instance Resource Query&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now that we have the query for launching a new instance, we can drag a button component to the canvas, and connect this query to it by creating an event handler for the button, and setting its &lt;strong&gt;Click&lt;/strong&gt; event to fire off the &lt;code&gt;launchNewEC2Instance&lt;/code&gt; query.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zPxzijrZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh6.googleusercontent.com/B7iAhpQTrns32aUkhTfJiY9qE4pRpjGWGCRQkTtMfOW6Wp7-sY6EFTib-APCwcuDa0fLx_WsMjmuLDR7FUQkZFA9zqIqtJwM_cCgIkPPtGMPIQjL8hvQM689O2MjSN-2SyaopYliGTszhrI8Q-kxQ78" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zPxzijrZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh6.googleusercontent.com/B7iAhpQTrns32aUkhTfJiY9qE4pRpjGWGCRQkTtMfOW6Wp7-sY6EFTib-APCwcuDa0fLx_WsMjmuLDR7FUQkZFA9zqIqtJwM_cCgIkPPtGMPIQjL8hvQM689O2MjSN-2SyaopYliGTszhrI8Q-kxQ78" alt="Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool" width="800" height="390"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Connect the button's "Click" event handler to the query&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Refresh the table after launching a new instance&lt;/p&gt;

&lt;p&gt;To ensure that the table automatically displays the newly launched instance, we will add getAllEC2Instances query to the &lt;strong&gt;Success&lt;/strong&gt; section of the &lt;code&gt;launchNewEC2Instance&lt;/code&gt; query, so it’s fired off automatically when a new instance is created successfully.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QphowQ2m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh6.googleusercontent.com/Ls5fT4SfWqP_zm9cE8tAziz0rqHFArFRUa6CHwVtYJQ2W2m7w9csFv39a1yT6kADhRN4WLB7F4innTWZc_7ez_QTUs5hYXSI8lStjcztekCNxBKiwfqyYevzJUfn9sNHpesR22GrK3w_vqON-AaGgLs" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QphowQ2m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh6.googleusercontent.com/Ls5fT4SfWqP_zm9cE8tAziz0rqHFArFRUa6CHwVtYJQ2W2m7w9csFv39a1yT6kADhRN4WLB7F4innTWZc_7ez_QTUs5hYXSI8lStjcztekCNxBKiwfqyYevzJUfn9sNHpesR22GrK3w_vqON-AaGgLs" alt="Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool" width="800" height="371"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Get the list of EC2 Instances when a new instance is launched successfully&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's it. Let's test it. Clicking the &lt;strong&gt;Launch new EC2 Instance&lt;/strong&gt; button should launch a new &lt;code&gt;T2.Micro&lt;/code&gt; EC2 instance using the image ID - &lt;code&gt;ami-0dfcb1ef8550277af&lt;/code&gt;, and the table should automatically be updated with the status of the new instance set to "Pending" &lt;em&gt;(it can take a few seconds for the new EC2 instance to complete spinning up, at which point the status will automatically change to "Running")&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Part 3: Stop and Start an EC2 instance
&lt;/h2&gt;

&lt;p&gt;To stop and restart our existing instances, we will add two new Resource Queries, which will use EC2 API's &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_StopInstances.html?ref=retool-blog"&gt;StopInstances&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_StartInstances.html?ref=retool-blog"&gt;StartInstances&lt;/a&gt; actions. We will then connect these queries to two actions buttons in the table.ru&lt;/p&gt;
&lt;h3&gt;
  
  
  Stopping an EC2 Instance
&lt;/h3&gt;

&lt;p&gt;Let's create a query called &lt;strong&gt;stopEC2InstanceByInstanceID,&lt;/strong&gt; using the configuration below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Action Type -&amp;gt; &lt;code&gt;POST&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Headers -&amp;gt; &lt;code&gt;application/x-www-form-urlencoded; charset=utf-8&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Body -&amp;gt; &lt;code&gt;x-www-form-urlencoded&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Body Params:&lt;/li&gt;
&lt;li&gt; Action -&amp;gt; &lt;code&gt;StopInstances&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt; InstanceId.1 -&amp;gt; &lt;code&gt;{{ table1.selectedRow.data['Instance ID'] }}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt; Version -&amp;gt; &lt;code&gt;2016-11-15&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aS7MChn5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh3.googleusercontent.com/BhBmf28NdKvL2qyQQxnQ4_z8P_t1sI446iW-dVJx8Lp22s13oWDj4TdI7TARFkqNtad9pgKOdpjuM2RlbS6wBHYXiIGStRtpvWoKRjYFCg-y2g1JugAiqze3KTs45bYYd_ZnlzVtJ4FKb4j3fScRxpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aS7MChn5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh3.googleusercontent.com/BhBmf28NdKvL2qyQQxnQ4_z8P_t1sI446iW-dVJx8Lp22s13oWDj4TdI7TARFkqNtad9pgKOdpjuM2RlbS6wBHYXiIGStRtpvWoKRjYFCg-y2g1JugAiqze3KTs45bYYd_ZnlzVtJ4FKb4j3fScRxpg" alt="Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool" width="800" height="534"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Setting up the stopEC2InstanceByInstanceID Resource Query&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  (re)Starting an EC2 Instance
&lt;/h3&gt;

&lt;p&gt;Next, create a query called &lt;strong&gt;startEC2InstanceByInstanceID,&lt;/strong&gt; using the same configuration as above, except the Action, which should be &lt;code&gt;StartInstances&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2oMyLg11--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh6.googleusercontent.com/ZBC6fdLxnUOl4r9_Lj6OeuPeVwsu8zamlNnmzzfB-vwygqejOtf5ThxRfzjX-Ip-NJ4vjdxAvc9RTHwQ8PDPm4zOwgPWfp6v1-RUgiQ7PZ9Q1Np1MsglfVgF-L-M3cDVzYPdOUfL7KNSstBOt8EhEDY" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2oMyLg11--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh6.googleusercontent.com/ZBC6fdLxnUOl4r9_Lj6OeuPeVwsu8zamlNnmzzfB-vwygqejOtf5ThxRfzjX-Ip-NJ4vjdxAvc9RTHwQ8PDPm4zOwgPWfp6v1-RUgiQ7PZ9Q1Np1MsglfVgF-L-M3cDVzYPdOUfL7KNSstBOt8EhEDY" alt="Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool" width="800" height="534"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Setting up the startEC2InstanceByInstanceID Resource Query&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Adding Buttons to the table to stop/start instance&lt;/p&gt;

&lt;p&gt;In the Table’s inspector, navigate to the &lt;strong&gt;Actions&lt;/strong&gt; section, and add two buttons for &lt;strong&gt;Start&lt;/strong&gt; , and &lt;strong&gt;Stop&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CF2ZfdlE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh5.googleusercontent.com/mbG-i3yrOf7IL67qGqxib1vgr6WYjgbkGm4LJJ_c0JyJheAkoAXbhdYHkMGOLv1YYqpclCK7itgRv_u4Swet5lINLT_uHWFe0TL3gHhricA5s7k6RpAcKv7zKTJJc0JXtHrXU2JX6zrlzHSmsz9o9q4" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CF2ZfdlE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh5.googleusercontent.com/mbG-i3yrOf7IL67qGqxib1vgr6WYjgbkGm4LJJ_c0JyJheAkoAXbhdYHkMGOLv1YYqpclCK7itgRv_u4Swet5lINLT_uHWFe0TL3gHhricA5s7k6RpAcKv7zKTJJc0JXtHrXU2JX6zrlzHSmsz9o9q4" alt="Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool" width="800" height="197"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Adding Action Buttons for each row of the table for "Starting" and "Stopping" Instances&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then, set the &lt;strong&gt;Action query&lt;/strong&gt; property for these buttons to their respective queries we created earlier - Stop button (&lt;code&gt;stopEC2InstanceByInstanceID&lt;/code&gt;), Start button (&lt;code&gt;startEC2InstanceByInstanceID&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tNR60Jpm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh5.googleusercontent.com/bkemvI4wkKYf0PZ3J-83_s3CenGRqW3VojUx7i5Mki2tGQ17opZKgL5xDcoPKXrCKi1R-LfNiG0iAESIki5HIRbAUcPaemWf-cq8xxMs5O-BfV1WYkQITot3sPB4a71f8cxvtVRy9c5yvcXtOz9kvLA" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tNR60Jpm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh5.googleusercontent.com/bkemvI4wkKYf0PZ3J-83_s3CenGRqW3VojUx7i5Mki2tGQ17opZKgL5xDcoPKXrCKi1R-LfNiG0iAESIki5HIRbAUcPaemWf-cq8xxMs5O-BfV1WYkQITot3sPB4a71f8cxvtVRy9c5yvcXtOz9kvLA" alt="Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool" width="627" height="410"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Set Action query property for “Stop” action button to stopEC2InstanceByInstanceID query&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CJb4dPNS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh6.googleusercontent.com/5AWD-n51Y0xCrSA3p9w9tOzawWHztb5AYJYlIftFxmOzRL3vr9iPwUVo-hB7_EJEwkmbtcDWL0y2Q7eJZ697RW2lIUu5Rv244OQV5jhrPq6NSEKV_MTQC1DsfcFqyaxQ3p31F92HmNUPo3jcyAUD_bw" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CJb4dPNS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh6.googleusercontent.com/5AWD-n51Y0xCrSA3p9w9tOzawWHztb5AYJYlIftFxmOzRL3vr9iPwUVo-hB7_EJEwkmbtcDWL0y2Q7eJZ697RW2lIUu5Rv244OQV5jhrPq6NSEKV_MTQC1DsfcFqyaxQ3p31F92HmNUPo3jcyAUD_bw" alt="Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool" width="618" height="428"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Set Action query property for “Start” action button to startEC2InstanceByInstanceID query&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Refresh the table after starting or stopping an instance
&lt;/h3&gt;

&lt;p&gt;To ensure the table automatically displays the updated status of the instance after stopping/starting an instance, we will add &lt;code&gt;getAllEC2Instances&lt;/code&gt; query to the “Success” section of the &lt;code&gt;startEC2InstanceByInstanceID&lt;/code&gt; and &lt;code&gt;stopEC2InstanceByInstanceID&lt;/code&gt; queries, so it’s fired off automatically when an instance stops or starts successfully.&lt;/p&gt;
&lt;h2&gt;
  
  
  Part 4: Add a Listbox Component to choose the type of instance to launch
&lt;/h2&gt;

&lt;p&gt;Finally, let’s add a Listbox component to the canvas, so we can choose the type of image we would like to launch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JO3gvDuR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh5.googleusercontent.com/oHyo2-H_EEanxHOn_nhU5egty13fdU6jA-m0mXkWio4EBX-hoIBgt0HUnW5gNi3axbG7HFsHO3aPfNU-9ZC2wbO5mOtKXOYTMRKtk4cPyiBWGC4oLN6mMnlpT2zyx9vqkzIH_Bzxn30YxpHAP8c4edQ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JO3gvDuR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh5.googleusercontent.com/oHyo2-H_EEanxHOn_nhU5egty13fdU6jA-m0mXkWio4EBX-hoIBgt0HUnW5gNi3axbG7HFsHO3aPfNU-9ZC2wbO5mOtKXOYTMRKtk4cPyiBWGC4oLN6mMnlpT2zyx9vqkzIH_Bzxn30YxpHAP8c4edQ" alt="Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool" width="800" height="229"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Add a Listbox Component to allow choosing of instance type&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Getting the Image Data for the Listbox
&lt;/h3&gt;

&lt;p&gt;You can use Amazon EC2’s DescribeImages action to get details about the images. But since we only want to list the images available in the free tier, instead of calling an API, we will simply create a JSON object with the list of images we want to display.&lt;/p&gt;
&lt;h3&gt;
  
  
  Write a JavaScript Query that returns a simple JSON object for the images
&lt;/h3&gt;

&lt;p&gt;Create a new JavaScript query called imageDataJSON and paste this code into it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const imageData = [
  {
    "id": 1,
    "image_id": "ami-0c2b0d3fb02824d92",
    "image_title": "Microsoft Windows Server 2022 Base",
    "show_flag": true
  },
  {
    "id": 2,
    "image_id": "ami-0dfcb1ef8550277af",
    "image_title": "Amazon Linux 2 AMI (HVM) - Kernel 5.10",
    "show_flag": true
  },
  {
    "id": 3,
    "image_id": "ami-0c9978668f8d55984",
    "image_title": "Red Hat Enterprise Linux 9",
    "show_flag": true
  },
  {
    "id": 4,
    "image_id": "ami-0557a15b87f6559cf",
    "image_title": "Ubuntu Server 22.04 LTS",
    "show_flag": true
  }
];

return imageData;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pre-load the Image data when the app launches
&lt;/h3&gt;

&lt;p&gt;To ensure our Listbox is automatically populated when the app opens, we will check the box &lt;code&gt;Run this query on page load?&lt;/code&gt; in the advanced tab for the &lt;code&gt;imageDataJSON&lt;/code&gt; query.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bGqiGRyD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh5.googleusercontent.com/4ZRTkVnEuku1gUFoo2lUd1auwthh-6LtSz7Gi5L37R32JCbH-gtP4eBfaA2E1F0vmi8Ija6vHzHnzlC5Snyf3IBAjtYlp0TQsqyr7r_c6hRiDu_cV55LauwyNxasRg7P3vL2-2_bK5i5cmIwJaWeFbc" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bGqiGRyD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh5.googleusercontent.com/4ZRTkVnEuku1gUFoo2lUd1auwthh-6LtSz7Gi5L37R32JCbH-gtP4eBfaA2E1F0vmi8Ija6vHzHnzlC5Snyf3IBAjtYlp0TQsqyr7r_c6hRiDu_cV55LauwyNxasRg7P3vL2-2_bK5i5cmIwJaWeFbc" alt="Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool" width="800" height="410"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Enabling "Run this query on page load" ensure the data is available when the page loads&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Add the Listbox Component to show the list of images available for launch
&lt;/h3&gt;

&lt;p&gt;Now that we have the data ready for the EC2 images we want to be able to choose from, we are ready to build the UI for it.&lt;/p&gt;

&lt;p&gt;Drag a Listbox component to the canvas, and change the &lt;code&gt;Data Source&lt;/code&gt; property to reference the data returned by the &lt;code&gt;imageDataJSON&lt;/code&gt; JavaScript query we just created. Also, change the &lt;code&gt;value&lt;/code&gt; and &lt;code&gt;label&lt;/code&gt; properties of the listbox to &lt;code&gt;item.image_id&lt;/code&gt; (value that should be passed when the item is selected) and &lt;code&gt;item.image_title&lt;/code&gt; (label value that should be displayed in the Listbox) respectively.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ba3BcCax--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh5.googleusercontent.com/5sFQfDdm0Bz3zd1MrXuy8h2zpl-w7VoZFEgR_XdGQFRX32vA43OeKZ4HCTptzf98XLNc5mG9BZgY-8WkDmCMCK5CODogO634n1GroV8GRza8t2MXHfA6G-eK7rQH1gcxT2JCeMzTKpRPoFgQdLrqEFE" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ba3BcCax--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lh5.googleusercontent.com/5sFQfDdm0Bz3zd1MrXuy8h2zpl-w7VoZFEgR_XdGQFRX32vA43OeKZ4HCTptzf98XLNc5mG9BZgY-8WkDmCMCK5CODogO634n1GroV8GRza8t2MXHfA6G-eK7rQH1gcxT2JCeMzTKpRPoFgQdLrqEFE" alt="Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool" width="800" height="317"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Setup the Listbox Component to display EC2 Image Data returned by imageDataJSON query&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Update the launchNewEC2Instance query to reference the image selected in the Listbox Component
&lt;/h3&gt;

&lt;p&gt;Finally, we will update our &lt;code&gt;launchNewEC2Instance&lt;/code&gt; to reference the image selected in the Listbox Component by changing the &lt;code&gt;ImageId&lt;/code&gt; URL parameter to &lt;code&gt;{{ listbox1.selectedItem.image_id }}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MOPJk5EN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/03/CleanShot-2023-03-01-at-14.18.31%402x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MOPJk5EN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://retool.com/blog/content/images/2023/03/CleanShot-2023-03-01-at-14.18.31%402x.png" alt="Building a Custom Amazon EC2 Instance Admin Panel for DevOps with Retool" width="800" height="596"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Reference the image selected in the Listbox to launch a new EC2 instance&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Congratulations! After this long journey, you now have your very own Amazon EC2 Instance Manager. With the power to stop, start, and launch instances, and choose from a variety of EC2 images, you've become a DevOps superhero.&lt;/p&gt;

&lt;p&gt;To wrap up this post, we used the example of an EC2 Instance Manager to explore how Retool's drag-and-drop UI components and built-in integrations can help you quickly build custom admin panels that simplify common DevOps tasks.&lt;/p&gt;

&lt;p&gt;We went through how to add a Resource for Amazon EC2 API, launch and manage EC2 instances, how to change the background color of columns to reflect the instance state data using RGBA CSS color codes, and how to keep the table refreshed by firing off the list EC2 instances query as a success query trigger.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this post, and that it has inspired you to explore Retool further and use it to create your own custom admin panels for managing virtual servers, databases, or other infrastructure resources.&lt;/p&gt;

&lt;p&gt;If you’d like to build a similar app, here’s the &lt;a href="https://gist.githubusercontent.com/ajot/612139711531e97915a40232775af8cc/raw/bae2a236288d7887605883f908f8ba961cad682b/aws_devops_ec2_instance_manager.json?ref=retool-blog"&gt;JSON&lt;/a&gt; you can &lt;a href="https://docs.retool.com/docs/import-export-apps?ref=retool-blog"&gt;import&lt;/a&gt; into your own free Retool instance.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and happy Retooling!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>aws</category>
      <category>ec2</category>
      <category>retool</category>
    </item>
    <item>
      <title>How I Built a Dashboard to Visualize My Amazon Spending using Retool and Google Sheets</title>
      <dc:creator>Amit Jotwani</dc:creator>
      <pubDate>Wed, 01 Feb 2023 19:33:13 +0000</pubDate>
      <link>https://dev.to/ajot/visualize-csv-data-and-build-a-dashboard-to-track-your-amazon-spending-4bp6</link>
      <guid>https://dev.to/ajot/visualize-csv-data-and-build-a-dashboard-to-track-your-amazon-spending-4bp6</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2Fdashboard-4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2Fdashboard-4.png" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My wife and I became parents last year, and let me tell you - babies aren’t cheap. Amazon has profited handsomely from our baby business in recent months. As the little brown boxes piled up, we wondered how much we'd spent on Amazon in general, and on baby supplies in particular. What categories did we spend the most on? What was our biggest baby-related expense? (Spoiler - it was the “&lt;a href="https://www.amazon.com/SlumberPod-3-0-Portable-Blackout-Sleeping/dp/B0B7PBRZX5?th=1" rel="noopener noreferrer"&gt;&lt;em&gt;SlumberPod 3.0 Portable Privacy Pod Blackout Canopy Crib&lt;/em&gt;&lt;/a&gt;”)&lt;/p&gt;

&lt;p&gt;To answer these questions, I had some mystery-solving to do. I found that Amazon provides a download of your purchase history as a CSV file. With that data at my fingertips, I wondered if I could build a simple “Amazon Purchases Insights Dashboard,” which, at a glance, would provide insights like the total money we spent on Amazon last year, money spent on each category, most expensive month, and more. With Retool, it turned out to be pretty simple.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2FCleanShot-2023-01-29-at-23.01.03-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2FCleanShot-2023-01-29-at-23.01.03-1.png" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;An Amazon spending data dashboard, powered by Retool&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In this post, I'll walk you through how I built this dashboard using a combination of Google Sheets, Retool, and even a splash of ChatGPT. Together, we will build a dashboard that can help us answer the following questions.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How much money did we spend on Amazon in 2022?&lt;/li&gt;
&lt;li&gt;How much money did we spend each month in 2022?&lt;/li&gt;
&lt;li&gt;What were the reasons for the high expenses in June 2022?&lt;/li&gt;
&lt;li&gt;What category did we spend the most on in 2022?&lt;/li&gt;
&lt;li&gt;How many items did we return or refund in 2022?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Whether you are a curious parent or just a data nerd, buckle up for a wild ride through Amazon spending data and visualization in Retool. For the impatient - feel free to simply download the &lt;a href="https://gist.github.com/ajot/44cec830fe05e885669ca95fad12cff6" rel="noopener noreferrer"&gt;JSON definition of this Retool app&lt;/a&gt;, which you can &lt;a href="https://docs.retool.com/docs/import-export-apps" rel="noopener noreferrer"&gt;import&lt;/a&gt; into your own free Retool instance. Let’s get started!&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting purchase history data from Amazon
&lt;/h2&gt;

&lt;p&gt;This part was pretty easy. Amazon provides a way for you to download your purchase history in CSV format through the “&lt;a href="https://www.amazon.com/b2b/reports" rel="noopener noreferrer"&gt;Order History Report&lt;/a&gt;” page. For this analysis, I went with “Items" as the report type and chose the start date and end dates as Jan 1, 2022, to Dec 31, 2022. The reports usually take only a few seconds to generate, but if you have lots of orders, it may take longer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh6.googleusercontent.com%2FGIYPBM_mUPnyX2AricsCCNv7kuWRfIylrNNEmKuArpnT6cW1A_RzhnFPUrAA2bEEZxW_sR-OqkGzGlrGX8YxL2HD_72IXqCmLjnpet4aNvNujaTifDXVzVwb5otxSRoqAx4roZDRFTDA8IXQRxKJ48P8fnbVNZI50CRd1bycxbkTdUrZXGZRCp5iHu0VxA" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh6.googleusercontent.com%2FGIYPBM_mUPnyX2AricsCCNv7kuWRfIylrNNEmKuArpnT6cW1A_RzhnFPUrAA2bEEZxW_sR-OqkGzGlrGX8YxL2HD_72IXqCmLjnpet4aNvNujaTifDXVzVwb5otxSRoqAx4roZDRFTDA8IXQRxKJ48P8fnbVNZI50CRd1bycxbkTdUrZXGZRCp5iHu0VxA" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A cutting edge and gorgeous UI for generating Amazon spending data&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once I had the CSV file, I imported it into Google Sheets using Google’s &lt;a href="https://support.google.com/docs/answer/40608?hl=en&amp;amp;co=GENIE.Platform%3DDesktop" rel="noopener noreferrer"&gt;import feature&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh5.googleusercontent.com%2FBBowNTjlF6IQJmA1ShwgMbmQq3-yJC2XfMeINg7i3FIa0Xfvnd2FtWUoV_NSOTHrBtfNwkOcKPY3YSnaqVgcvZVxCpgK-Jb6ejZ4AmiasXOgfg8VdJWl7mOfzFALe5YfwGGNMcE0Ne8E7LJrrUswEe10J4J7nzpAB9nVbKvLOdNiZZXPnVTXvH6ByhI_cw" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh5.googleusercontent.com%2FBBowNTjlF6IQJmA1ShwgMbmQq3-yJC2XfMeINg7i3FIa0Xfvnd2FtWUoV_NSOTHrBtfNwkOcKPY3YSnaqVgcvZVxCpgK-Jb6ejZ4AmiasXOgfg8VdJWl7mOfzFALe5YfwGGNMcE0Ne8E7LJrrUswEe10J4J7nzpAB9nVbKvLOdNiZZXPnVTXvH6ByhI_cw" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Google Sheets allows you to import Sheet data from a CSV on your filesystem&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After you import the data, it should look something like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh4.googleusercontent.com%2FwXUZmGnkFjueLCIK7E7cL-kQAslbvyW5jnkvb5TV-dWeqo0xCM-L9UahR6q8S1jVsEqd7LroGu4rfRjQ0DmFVp7_57vg_w2JvZO-qAx18w7N3nFGDdxIcNHufEtG0NpffmFsYCcOF1xOILr918_V3D2VtwrsgdvEZJqfmgBa5b9TtCrlodAlAeTvttmogw" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh4.googleusercontent.com%2FwXUZmGnkFjueLCIK7E7cL-kQAslbvyW5jnkvb5TV-dWeqo0xCM-L9UahR6q8S1jVsEqd7LroGu4rfRjQ0DmFVp7_57vg_w2JvZO-qAx18w7N3nFGDdxIcNHufEtG0NpffmFsYCcOF1xOILr918_V3D2VtwrsgdvEZJqfmgBa5b9TtCrlodAlAeTvttmogw" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;We now have a Google Sheet with our Amazon purchase history&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Connect Retool with Google Sheets
&lt;/h2&gt;

&lt;p&gt;Now that the data is loaded into a Google Sheet, we are ready to connect it to Retool. Login to Retool and create a new &lt;a href="https://docs.retool.com/docs/resources" rel="noopener noreferrer"&gt;Resource&lt;/a&gt; using the built-in &lt;a href="https://docs.retool.com/docs/google-sheets-integration" rel="noopener noreferrer"&gt;Google Sheets integration&lt;/a&gt;. Follow the prompts to authenticate with Google. Here’s what the resource would look like once you have successfully authenticated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh5.googleusercontent.com%2FeD9mivdzbxR4TGOkRCKgotCOv3DsS0b1GlLXPLWcnD0SsvvsNLMdRVCHfvXIdprCRi6bJJl4jm9JCME-Qb2zO53K6uSPZ9G-L0EeA4C1YnFml9vUT83bknFz8SV6q5Zs5ocwjh_B2eVjDYWg_CDTxfoe0NCfZ12YEQ_dgM2qpwGymUT79paPQAQBobfH8A" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh5.googleusercontent.com%2FeD9mivdzbxR4TGOkRCKgotCOv3DsS0b1GlLXPLWcnD0SsvvsNLMdRVCHfvXIdprCRi6bJJl4jm9JCME-Qb2zO53K6uSPZ9G-L0EeA4C1YnFml9vUT83bknFz8SV6q5Zs5ocwjh_B2eVjDYWg_CDTxfoe0NCfZ12YEQ_dgM2qpwGymUT79paPQAQBobfH8A" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Creating a Retool resource for the Google Sheet that contains your Amazon purchases&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Create a new Retool app
&lt;/h2&gt;

&lt;p&gt;Now that our purchase data is ready in Retool, we need to build the actual user interface to display it. Our Retool app will need a JavaScript query to fetch and format the Amazon data, which we will then need to access in several user interface components. Let's grab the data first.&lt;/p&gt;
&lt;h2&gt;
  
  
  Writing a JavaScript query to fetch our data
&lt;/h2&gt;

&lt;p&gt;Before building the front-end, I did a quick mapping exercise to determine what data I would need for the insights I was trying to get. It turned out I could build the entire dashboard with a single &lt;a href="https://docs.retool.com/docs/google-sheets-integration#query-google-sheets" rel="noopener noreferrer"&gt;resource query&lt;/a&gt; to load the data from Google Sheets, and three &lt;a href="https://docs.retool.com/docs/transformers" rel="noopener noreferrer"&gt;JavaScript transformers&lt;/a&gt; to format the data conveniently for my dashboard.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;getDataFromGoogleSheets (query):&lt;/strong&gt; This is the master query that pulls all the data I need from Google Sheets. It will act as the base for my transformers, which will aggregate specific insights.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;getOrdersByMonth (transformer):&lt;/strong&gt; This transformer will help us answer the question - "How much money did we spend each month?" It will return the total amount spent and a list of orders for each &lt;strong&gt;month&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;getAnnualSpent (transformer):&lt;/strong&gt; This will help us answer the question - "What's the total amount of money we spent in 2022?". It will return the total amount spent each month. We can add them together to get the &lt;strong&gt;annual spending&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;getOrdersByCategories (transformer):&lt;/strong&gt; This will help us answer the question - "How much money did we spend on each category?". It will return the total amount spent and a list of orders for each &lt;strong&gt;category&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s create these four parts of the app.&lt;/p&gt;
&lt;h3&gt;
  
  
  Querying for all the data in our Google Sheet
&lt;/h3&gt;

&lt;p&gt;Create a new Retool app, and then create a new Resource query to kick things off.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh5.googleusercontent.com%2FYbul3nU3jUizx0MeoQIkzovKcdMA0fH4m6rbX0nd2WKDbHXlyw3vvCILeRTcFDHMfYTdEP194kOAD21mvKRAck7tGRlG3JTrBDlPQK-p6OlS6sCd783ZXKAJc0JuGGHsdvlfVcqjg2FqS3G0VCKysE2Bah6xV1Ex8RGdcIgXjZasMkpSrLnCAtZui61W1w" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh5.googleusercontent.com%2FYbul3nU3jUizx0MeoQIkzovKcdMA0fH4m6rbX0nd2WKDbHXlyw3vvCILeRTcFDHMfYTdEP194kOAD21mvKRAck7tGRlG3JTrBDlPQK-p6OlS6sCd783ZXKAJc0JuGGHsdvlfVcqjg2FqS3G0VCKysE2Bah6xV1Ex8RGdcIgXjZasMkpSrLnCAtZui61W1w" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Creating a new resource query for our Google Sheet data&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Next, select the Google Sheets resource you created earlier from the Resource dropdown, and then select the actual spreadsheet that contains your Amazon data. Name this query &lt;code&gt;getDataFromGoogleSheets&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh4.googleusercontent.com%2F81xvzkIodayXgRqptsaWK2TuMqp0uMqHT5jcPen7QkPNoKDniL7hnLUP7NQZwKfdfICA0GITzKikqCgU12j6HrerbBhi3snxH9sVb_dQwO4-VKQdBHJZZvWQfFV2bxqFopOeAj47YkxSeTathfwIwwYf0EbhXRVe0MO0KMnsZCFEOl5nUPCFA140TdhCkA" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh4.googleusercontent.com%2F81xvzkIodayXgRqptsaWK2TuMqp0uMqHT5jcPen7QkPNoKDniL7hnLUP7NQZwKfdfICA0GITzKikqCgU12j6HrerbBhi3snxH9sVb_dQwO4-VKQdBHJZZvWQfFV2bxqFopOeAj47YkxSeTathfwIwwYf0EbhXRVe0MO0KMnsZCFEOl5nUPCFA140TdhCkA" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Selecting the Google Sheets Resource created earlier&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's it for this query - you can click the "Preview" or "Run" button to see the data from Amazon flowing in.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create a transformer to group purchase data by month
&lt;/h3&gt;

&lt;p&gt;Next, create a transformer called &lt;code&gt;getOrdersByMonth&lt;/code&gt; - this transformer will reference the results of the &lt;code&gt;getDataFromGoogleSheets&lt;/code&gt; query, and then return a JavaScript object that contains data in a convenient format for our UI components.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2FCleanShot-2023-01-31-at-15.14.15%402x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2FCleanShot-2023-01-31-at-15.14.15%402x.png" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Creating a JavaScript transformer&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Use this code for the transformer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Declare and assign the results of getDataFromGoogleSheets query to variable 'sheetData'
let sheetData = {{getDataFromGoogleSheets.data}};

// Declare an array of month names for easy reference
const monthNamesShort = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

// Object to store final data
const finalData = {};

// Loop through each month
for (let i = 0; i &amp;lt; 12; i++) {
  // Filter orders for current month
  const monthOrders = sheetData.filter(function(order) {
    const date = new Date(order["Order Date"]);
    return date.getMonth() === i;
  });

  // Calculate total amount for current month
  const totalAmount = monthOrders.reduce(function(total, order) {
    return total + order["Item Total"];
  }, 0);

  // Add current month data to finalData object
  finalData[monthNamesShort[i]] = {
    orders: monthOrders,
    totalAmount: totalAmount
  };
}

// Log and return finalData
console.log(finalData);
return finalData;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s the shape of the object the transformer would return, containing all orders and their total grouped by month.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh4.googleusercontent.com%2Fdo8C2TQzGEUpX0WDp_r_9ImeP86SgGR9ruEThx2EKFStAIHcjbGQe2QDLDW4kCbhOv2_6lMTGhrkhdUCZ7n9e0nYWMMJUtL6JZjlU7TQ1VutPVlIFNIywZX_YHRHdxJ_pNF95-JaXeEqjZsUrEuabI2nripOUe1JQNbeJy-U48ei8FrClyDSMb034Tr8Wg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh4.googleusercontent.com%2Fdo8C2TQzGEUpX0WDp_r_9ImeP86SgGR9ruEThx2EKFStAIHcjbGQe2QDLDW4kCbhOv2_6lMTGhrkhdUCZ7n9e0nYWMMJUtL6JZjlU7TQ1VutPVlIFNIywZX_YHRHdxJ_pNF95-JaXeEqjZsUrEuabI2nripOUe1JQNbeJy-U48ei8FrClyDSMb034Tr8Wg" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Output of the getOrdersByMonth transformer&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Create a transformer to display total annual spend
&lt;/h3&gt;

&lt;p&gt;Next, we will create a transformer that calculates and returns our total annual spend. Use the same process as before to create a transformer called &lt;code&gt;getAnnualSpent&lt;/code&gt;, and use the following JavaScript code to define it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Assign the monthly orders to a variable
let monthlyOrders = {{getOrdersByMonth.value}};

// Initialize the total to zero
let annualTotal = 0;

// Loop through each month in monthlyOrders
for (let month in monthlyOrders) {
  // Log the total amount for the current month
  console.log(monthlyOrders[month]["totalAmount"]);
  // Add the total amount for the current month to the overall total
  annualTotal += parseFloat(monthlyOrders[month]["totalAmount"]);
}

// Return the total for the year
return annualTotal;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This transformer returns a single number rather than an object, which we can reference later in the UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a transformer to group spend by category
&lt;/h3&gt;

&lt;p&gt;Finally, create the &lt;code&gt;getOrdersByCategories&lt;/code&gt; query to group purchase data by category. Use this JavaScript code for the transformer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Declare and assign the results of getDataFromGoogleSheets query to variable 'sheetData'
let sheetData = {{getDataFromGoogleSheets.data}};

// Get an array of unique Parent Categories from sheetData
let uniqueParentCategories = [...new Set(sheetData.map(obj =&amp;gt; obj["Parent Category"]))];

// Object to store final data
const finalData = {};

// Loop through each unique Parent Category
for (let i = 0; i &amp;lt; uniqueParentCategories.length; i++) {
  // Filter data for current Parent Category
  const categoryData = sheetData.filter(function(data) {
    return data["Parent Category"] == uniqueParentCategories[i];
  });

  // Calculate total amount for current Parent Category
  const totalAmount = categoryData.reduce(function(total, data) {
    return total + data["Item Total"];
  }, 0);

  // Add current Parent Category data to finalData object
  finalData[uniqueParentCategories[i]] = {
    orders: categoryData,
    totalAmount: totalAmount
  };
}

// Log and return finalData
console.log(finalData);
return finalData;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you preview this transformer, the shape of the object returned should look something like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh5.googleusercontent.com%2FoEvfsG6BgQc2YhAnJOjnsSjepBojSRscR4jhmJMUUxEgAYfw8BFgKKr5TxQfRmeqI5b3ITLrzhGjmdeXuftI9ZftItE8Zkyohn6SzuwESNAFIK7CZ728eIc0WRdlwVKe-xe0UDtZhFnWs7TWKP3_Ko_Xz2lWs1dwd4LljbQzhCD1NASrPUQrS_6_fjomFQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh5.googleusercontent.com%2FoEvfsG6BgQc2YhAnJOjnsSjepBojSRscR4jhmJMUUxEgAYfw8BFgKKr5TxQfRmeqI5b3ITLrzhGjmdeXuftI9ZftItE8Zkyohn6SzuwESNAFIK7CZ728eIc0WRdlwVKe-xe0UDtZhFnWs7TWKP3_Ko_Xz2lWs1dwd4LljbQzhCD1NASrPUQrS_6_fjomFQ" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Output of the getOrdersByCategories transformer&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here’s the final set of queries and transformers we have now.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh5.googleusercontent.com%2Fx-vrImgx5QeuZFH7LWmj2VNIW2Wwi0TMIQpeqiS2Z1bf6_HDCJWD3u-qojAn6i_hdHhFCt7ZuU_LZMpMnyMdqPNADCetkWNd0JJenX3UbuCQ6ghW0R8wrUdT8qSNKiq_x81EKF_8SL67r95X-KIYlkHLlr3hK-Z7wvy_nyGoy3gFuTcV9IpvEzNka9_JCA" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh5.googleusercontent.com%2Fx-vrImgx5QeuZFH7LWmj2VNIW2Wwi0TMIQpeqiS2Z1bf6_HDCJWD3u-qojAn6i_hdHhFCt7ZuU_LZMpMnyMdqPNADCetkWNd0JJenX3UbuCQ6ghW0R8wrUdT8qSNKiq_x81EKF_8SL67r95X-KIYlkHLlr3hK-Z7wvy_nyGoy3gFuTcV9IpvEzNka9_JCA" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Our final list of one query and three transformers&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now that the data access tier of our application is in place, let's start building the UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting our data to UI components
&lt;/h2&gt;

&lt;p&gt;Now that we have the data queries and transformers in place, we can begin designing the dashboard and connecting the UI to this data. To build the UI, let's focus on the questions we posed earlier, and show an answer to each of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Question 1: How much money did we spend on Amazon in 2022?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh5.googleusercontent.com%2F_8WxNUeSKN9uWuZh2IkmNXdzlyMjSyd-Kfo9KxkEDwCrhb6NTSvBSTB-o0-8drlRs9vRfyJPoCUAzjrfr8kzIrBxNwgA0Rh_zsT1KLRe3mwj-UNMWn3NqZauYprRNI76F8X2i7zGZm_dDFfXhkuVhskbr5NXNv47448NOEjDR5S0iNBZeMntd4h_y65YDw" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh5.googleusercontent.com%2F_8WxNUeSKN9uWuZh2IkmNXdzlyMjSyd-Kfo9KxkEDwCrhb6NTSvBSTB-o0-8drlRs9vRfyJPoCUAzjrfr8kzIrBxNwgA0Rh_zsT1KLRe3mwj-UNMWn3NqZauYprRNI76F8X2i7zGZm_dDFfXhkuVhskbr5NXNv47448NOEjDR5S0iNBZeMntd4h_y65YDw" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The "quick insights" bar of our Amazon Shopping Analysis dashboard&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To build the "quick insights" bar at the top of this application, I used a combination of &lt;a href="https://retool.com/components/statistic" rel="noopener noreferrer"&gt;Statistic&lt;/a&gt; and &lt;a href="https://retool.com/components/icon" rel="noopener noreferrer"&gt;Icon&lt;/a&gt; UI components. To bind these Statistic components to the appropriate data sources, change their &lt;strong&gt;Primary Value&lt;/strong&gt; property to the following expressions.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Total Spend in 2022:&lt;/strong&gt; &lt;code&gt;{{getAnnualSpent.value}}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monthly Average:&lt;/strong&gt; &lt;code&gt;{{Math.round(getAnnualSpent.value/12)}}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Average Item Cost:&lt;/strong&gt; &lt;code&gt;{{getAnnualSpent.value/getDataFromGoogleSheets.data.length}}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Number of Items Ordered:&lt;/strong&gt; &lt;code&gt;{{getDataFromGoogleSheets.data.length}}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Average Item Cost:&lt;/strong&gt; &lt;code&gt;{{getAnnualSpent.value/getDataFromGoogleSheets.data.length}}&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;ℹ️&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analysis: How much money did we spend on Amazon in 2022?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
In 2022, we emptied our wallets and spent a whopping $12,000 on Amazon, an average of 42 items per month with an average cost of $25 per item. That’s a lot of brown boxes every month.&lt;/p&gt;

&lt;h3&gt;
  
  
  Question 2: How much money did we spend each month?
&lt;/h3&gt;

&lt;p&gt;Also, how many items did we order each month, and what was the average item cost? To answer these questions, I decided to build a section that shows the amount of money spent each month and a button that lists all the items for that month in the table below. It also shows a positive/negative trend compared to the previous month.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2FCleanShot-2023-01-29-at-23.01.03-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2FCleanShot-2023-01-29-at-23.01.03-2.png" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Monthly view of our Amazon Shopping Analysis Dashboard&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To build this section, I used the Statistic component again and made these changes to its properties.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Label:&lt;/strong&gt; January&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Primary Value:&lt;/strong&gt; &lt;code&gt;{{getOrdersByMonth.value.Jan.totalAmount}}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caption:&lt;/strong&gt; Average Item Cost: &lt;code&gt;${{Math.round(getOrdersByMonth.value.Jan.totalAmount/getOrdersByMonth.value.Jan.orders.length)}}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Number of Items Ordered:&lt;/strong&gt; &lt;code&gt;{{getDataFromGoogleSheets.data.length}}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Average Item Cost:&lt;/strong&gt; &lt;code&gt;{(getAnnualSpent.value/getDataFromGoogleSheets.data.length}}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable trend color:&lt;/strong&gt; On&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I then repeated this for the other months, replacing “Jan” with “Feb”, “Mar”, “Apr”, and so on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh5.googleusercontent.com%2FLaMWGp-QwHGgMhgdsMGayxjbcYhu_RKZWU7H1TooFY5i_q41okEdkvLqJWqLTG03rWLjKF5vxeD72GUgZ0UYd_fINiOQQe7W--i2osXw9L7qWhag1TvZOyAsPPooB2WCVQUeNMLrbWF0Zum_cp_yH5klXFgx9mQLNTkkFJJABJQXYrwP4K3Iad028tod9g" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh5.googleusercontent.com%2FLaMWGp-QwHGgMhgdsMGayxjbcYhu_RKZWU7H1TooFY5i_q41okEdkvLqJWqLTG03rWLjKF5vxeD72GUgZ0UYd_fINiOQQe7W--i2osXw9L7qWhag1TvZOyAsPPooB2WCVQUeNMLrbWF0Zum_cp_yH5klXFgx9mQLNTkkFJJABJQXYrwP4K3Iad028tod9g" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Setting the Primary value of the Statistics component to the total spend of the month&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To add the positive/negative trends for Statistics Components, I turned on &lt;code&gt;“Enable trend color”&lt;/code&gt; property for Secondary value, and set the &lt;code&gt;“Positive Trend”&lt;/code&gt; property for each of the Statistics components (except January, since it's the first month) to &lt;code&gt;{{self.secondaryValue &amp;lt; 0}}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh4.googleusercontent.com%2FFlh9vQHfeHtYCNkSrg76goBJ9uI51hwVAOvRAeV9Q3c5cNEnrTpw4vY-CHtfYsZ_ADRSB7dcBZ2NrcoVQPY6mSYf58y8RXy45QPEZyLu20sInm3yzViUnjxIipNUSR5riPZ7DXjTlwuYMYCyavgrfA4RTd4D626vOlJSiPKTx9iRGqpzr4fspLIRZkpf7g" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh4.googleusercontent.com%2FFlh9vQHfeHtYCNkSrg76goBJ9uI51hwVAOvRAeV9Q3c5cNEnrTpw4vY-CHtfYsZ_ADRSB7dcBZ2NrcoVQPY6mSYf58y8RXy45QPEZyLu20sInm3yzViUnjxIipNUSR5riPZ7DXjTlwuYMYCyavgrfA4RTd4D626vOlJSiPKTx9iRGqpzr4fspLIRZkpf7g" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
_Enabling the trend color property for Statistic Component's Secondary value _&lt;/p&gt;

&lt;p&gt;ℹ️&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analysis: Most expensive month&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The most expensive month for us turned out to be June, followed by December (which makes sense with the holiday shopping) - but what happened in June? We'll explore that further below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Question 3: Why were expenses so high in June?
&lt;/h3&gt;

&lt;p&gt;I decided to add a button for each month, which includes the number of items in the button text. When clicked, the button would filter and display the items in the table below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh6.googleusercontent.com%2FrElwA_vUKKAgw0zCKuFza6xxF0lywW6QQaUvhRvAa51XnUwoJeQ_g-f5W6zmLkgHXeev6DuHyr8UdSpQ1aFTEiQnNQQg1I6ChrWfIOQCnIbOmUQVn52F0hGnptB9BrZsqvBHvumUA0jF5aL9A7_gB9Mg9568XvKMz-dfoMEgGPbFHKYNlGWHyilHD1O64A" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh6.googleusercontent.com%2FrElwA_vUKKAgw0zCKuFza6xxF0lywW6QQaUvhRvAa51XnUwoJeQ_g-f5W6zmLkgHXeev6DuHyr8UdSpQ1aFTEiQnNQQg1I6ChrWfIOQCnIbOmUQVn52F0hGnptB9BrZsqvBHvumUA0jF5aL9A7_gB9Mg9568XvKMz-dfoMEgGPbFHKYNlGWHyilHD1O64A" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Clicking on the "XX Items" button in a month will change the contents of the table below.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To do this, add a Button component and change the text to this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{{getOrdersByMonth.value.Jan.orders.length}} Items&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then add an event handler for that button to control the table, setting its data property to this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{{getOrdersByMonth.value.Jan.orders}}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Duplicate this button for other months, replacing “Jan” in each of those snippets with “Feb”, “Mar”, and so on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh3.googleusercontent.com%2FeFbopACnYwT_3Spklwy1M8CaojmiQTud4BuJk7sQ67oBql5kUNroCwF1-OcGjhdETBBGCfIkLD88YUghNRAgHE-gfQj_l7EXMhMWdrUUDjiiPnivh_yTzj9LFBLdtlvPVGBT3A9XjsPQDO712CeByNJ-Okps7j4QOxcwG3xR1rxC9WW1CriGhNLP9OfZng" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh3.googleusercontent.com%2FeFbopACnYwT_3Spklwy1M8CaojmiQTud4BuJk7sQ67oBql5kUNroCwF1-OcGjhdETBBGCfIkLD88YUghNRAgHE-gfQj_l7EXMhMWdrUUDjiiPnivh_yTzj9LFBLdtlvPVGBT3A9XjsPQDO712CeByNJ-Okps7j4QOxcwG3xR1rxC9WW1CriGhNLP9OfZng" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Configuration for each of our Item buttons in Monthly view&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;ℹ️&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analysis: What happened in June?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Looking at the items ordered in June, I recalled that we traveled internationally with the baby for the first time, which explains the travel-related shopping. The relatively high amount in January also made sense, given that we were getting ready for the baby’s arrival.&lt;/p&gt;

&lt;h3&gt;
  
  
  Question 4: What category did we spend the most on?
&lt;/h3&gt;

&lt;p&gt;Also, how much did we spend on the baby category? To answer these questions, I built a “Category view” that shows the amount of money spent each month, along with a button that lists all the items for that month in the table below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using ChatGPT to group into broader categories
&lt;/h3&gt;

&lt;p&gt;Amazon includes a pretty wide and very niche set of categories. There were over 185 unique categories in my report, including things like &lt;em&gt;towel, toothbrush, lamp, table, etc&lt;/em&gt;. This makes the grouping by category pretty useless.&lt;/p&gt;

&lt;p&gt;I wanted to regroup the items into broader "parent" categories, which would be much easier to visualize. I decided on 12 parent categories - &lt;em&gt;Health, Baby, Kitchen, Food, Home, Electronics, Office, Clothes, Books, Toys and Games, Personal, Care, Clothing, Gift.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Instead of trying to do the frustrating task of regrouping 500+ records manually, I ran them through &lt;a href="https://chat.openai.com/chat" rel="noopener noreferrer"&gt;ChatGPT&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here are the steps I followed to do this for 500+ items:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create two new columns in your spreadsheet - &lt;code&gt;ChatGPT Output&lt;/code&gt;, and &lt;code&gt;Parent Category&lt;/code&gt;. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2FCleanShot-2023-01-31-at-17.21.40%402x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2FCleanShot-2023-01-31-at-17.21.40%402x.png" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log in to &lt;a href="https://chat.openai.com/chat" rel="noopener noreferrer"&gt;ChatGPT&lt;/a&gt;, and use the prompt below to pass the categories for regrouping. Since ChatGPT has character limits on the replies, I found it most effective to pass 50-60 items at a time, which is still much better than doing it manually.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;"Group the following items purchased from Amazon into broader categories such as "Baby", "Kitchen", and "Home". Repeat it for each item even if they're not unique. Use this format - Diaper: Baby"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2FCleanShot-2023-01-31-at-17.02.04%402x-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2FCleanShot-2023-01-31-at-17.02.04%402x-2.png" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2FCleanShot-2023-01-31-at-17.02.13%402x-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2FCleanShot-2023-01-31-at-17.02.13%402x-2.png" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy the output and paste it into a text editor (or &lt;a href="https://pastebin.com/" rel="noopener noreferrer"&gt;pastebin.com&lt;/a&gt;) to remove any extra bit of formatting and ensure each item is on a new line.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2FCleanShot-2023-01-31-at-17.58.50%402x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2FCleanShot-2023-01-31-at-17.58.50%402x.png" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Pasting output from ChatGPT into a simple text editor to remove any formatting&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Now paste this into the &lt;code&gt;ChatGPT Output&lt;/code&gt; column in the spreadsheet.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2FCleanShot-2023-01-31-at-17.39.41%402x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2FCleanShot-2023-01-31-at-17.39.41%402x.png" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Pasting the raw ChatGPT output into the spreadsheet in the ChatGPT Output column&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Now, use the "Split text to columns" feature of Google sheets to split by colon, to get the &lt;code&gt;Parent Category&lt;/code&gt; column filled out.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2FCleanShot-2023-01-31-at-17.56.24%402x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2FCleanShot-2023-01-31-at-17.56.24%402x.png" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2FCleanShot-2023-01-31-at-17.57.37%402x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2FCleanShot-2023-01-31-at-17.57.37%402x.png" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Using the "Split text to columns" feature to get the Parent Category&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Repeat this step for other items.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;💡&lt;/p&gt;

&lt;p&gt;Some items, like Gift Cards, may not have a category assigned in the data provided by Amazon. To avoid corrupting the order of the data returned by ChatGPT, you may want to filter these out before passing them to ChatGPT. &lt;/p&gt;

&lt;p&gt;We're now ready to build the category view of our dashboard using the new &lt;code&gt;Parent Category&lt;/code&gt; column.&lt;/p&gt;

&lt;h4&gt;
  
  
  Building the Category View
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2FCleanShot-2023-01-29-at-23.01.38-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fretool.com%2Fblog%2Fcontent%2Fimages%2F2023%2F01%2FCleanShot-2023-01-29-at-23.01.38-1.png" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Category view of our Amazon Shopping Analysis Dashboard&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To build this section, I used the Statistic Components again and changed the &lt;code&gt;Primary Value&lt;/code&gt; for the Statistic Component to &lt;code&gt;{{getOrdersByCategories.value.Baby.totalAmount}}&lt;/code&gt;, and Button Component's &lt;code&gt;Text Property&lt;/code&gt; to `&lt;code&gt;{getOrdersByCategories.value.Baby totalAmount}}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Similar to the monthly view, I added a button for each category and changed the text to &lt;code&gt;{{getOrdersByCategories.value.Baby.orders.length}} Items&lt;/code&gt;. I then added the event handler to control the table and set the data to &lt;code&gt;{{getOrdersByCategories.value.Baby.orders}}&lt;/code&gt; when clicked.&lt;/p&gt;

&lt;p&gt;I then repeated this for the other categories, replacing “Baby” with “Home”, “Kitchen”, and so on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh3.googleusercontent.com%2FbvgcjXKOFYJahwCfZeErkt9fah6gJzBD8XVAmDtoeMimBEaOuthsuXF6-x4OmEZhyV-ztyCjA9jtI9fQvEd-1O9pt1Kpq1wDbjrp8_Qej_oMhxYENqXWubPRvew1D7RmFKFWG_1ul0Hnl-ZXEhH1FCymEnxtIphEPMcNUN0v5T6JfFRwE5cW7hi6ouzV7w" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh3.googleusercontent.com%2FbvgcjXKOFYJahwCfZeErkt9fah6gJzBD8XVAmDtoeMimBEaOuthsuXF6-x4OmEZhyV-ztyCjA9jtI9fQvEd-1O9pt1Kpq1wDbjrp8_Qej_oMhxYENqXWubPRvew1D7RmFKFWG_1ul0Hnl-ZXEhH1FCymEnxtIphEPMcNUN0v5T6JfFRwE5cW7hi6ouzV7w" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Configuration for each of our Item buttons in Category View&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Question 5: How many items did we return/refund?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh5.googleusercontent.com%2F9AIlHVZWvrNCEw-csZKlqvtvSpx9oLZQhLeUwk_wn5uR-uB_uyjfiP5AyUKvWzSvEOgvUDFRJj4DvHE8duhw7Ay9KrSgDy4cQpXmvnQPLWSHLnn7KmSQCUW5ctEIXMlNRoVZwWoI3KjeCPy62VWatn3nnKaWwuZ9ytAcEOTirQiA_pHKsR1zUXqd99MOKQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh5.googleusercontent.com%2F9AIlHVZWvrNCEw-csZKlqvtvSpx9oLZQhLeUwk_wn5uR-uB_uyjfiP5AyUKvWzSvEOgvUDFRJj4DvHE8duhw7Ay9KrSgDy4cQpXmvnQPLWSHLnn7KmSQCUW5ctEIXMlNRoVZwWoI3KjeCPy62VWatn3nnKaWwuZ9ytAcEOTirQiA_pHKsR1zUXqd99MOKQ" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Refunds view using a new Resource query getRefundDataFromGoogleSheets&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To get the answer to this, I had to download the “Refunds” report from Amazon (instead of the “Items” report we downloaded earlier). I followed the same process to create a new Resource Query - &lt;code&gt;getRefundDataFromGoogleSheets&lt;/code&gt;, and two new transformers called &lt;code&gt;getRefundsByMonth&lt;/code&gt; and &lt;code&gt;getAnnualRefunds&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh3.googleusercontent.com%2F6RJjelrVRB66e8vlJxsgPMuiRghg1qW6ebDHi6d5FBHgG4SswA7TlVyQ7VWEEzr01dCuQgzsuoQ1Jjjh8vmi6nCN2Q-h7xydUTAY0hFyXuzS2SPdbKzkUpuGVzYX72Ek1nzrLnul18wzfTyHhXVAuzAGNxg0vr0GAETT2qtukdryyZZat_O4SQTGf_cUxQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh3.googleusercontent.com%2F6RJjelrVRB66e8vlJxsgPMuiRghg1qW6ebDHi6d5FBHgG4SswA7TlVyQ7VWEEzr01dCuQgzsuoQ1Jjjh8vmi6nCN2Q-h7xydUTAY0hFyXuzS2SPdbKzkUpuGVzYX72Ek1nzrLnul18wzfTyHhXVAuzAGNxg0vr0GAETT2qtukdryyZZat_O4SQTGf_cUxQ" alt="Visualize CSV Data and Build a Dashboard to Track Your Amazon Spending" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Adding a new query and two additional transformers for Refunds data&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The process to create these items is nearly identical to the process for the rest of the dashboard - to see the precise configuration I used, download the &lt;a href="https://gist.github.com/ajot/44cec830fe05e885669ca95fad12cff6" rel="noopener noreferrer"&gt;JSON definition of this Retool app&lt;/a&gt;, which you can &lt;a href="https://docs.retool.com/docs/import-export-apps" rel="noopener noreferrer"&gt;import&lt;/a&gt; into your own free Retool instance to inspect for yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final analysis
&lt;/h2&gt;

&lt;p&gt;I shared this dashboard with my wife, and we ran through it together to get some fascinating insights.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Overall, we spent an &lt;strong&gt;average of $838 per month&lt;/strong&gt; , with a return rate of 20%. That was a reminder to be careful of the “Buyer’s remorse,” especially with that easy-to-hit “Buy now” button.&lt;/li&gt;
&lt;li&gt;We ordered a total of &lt;strong&gt;506 items&lt;/strong&gt; at an average of &lt;strong&gt;42 items per month&lt;/strong&gt; , with an &lt;strong&gt;average per-item cost&lt;/strong&gt; of $24.88. That’s a lot of brown boxes. This was a bit of a shocker. With online shopping so easy, it’s easy to forget how much we’re ordering.&lt;/li&gt;
&lt;li&gt;Not surprisingly, the &lt;strong&gt;category we spent the most&lt;/strong&gt; on was baby-related. The category we spent the least on was books, which makes sense given that we had very little time to sleep, let alone read books. We would like to change that this year by reading a bit more.&lt;/li&gt;
&lt;li&gt;We agreed this could serve as a good starting point to budget more effectively across categories like home, kitchen, etc., in 2023.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;top 5 most expensive items&lt;/strong&gt; we bought in 2022 were travel bags, gift cards, and a Slumberpod tent for the baby girl!&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;most expensive month&lt;/strong&gt; for us was June (travel to see family), followed by December (holiday shopping).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Wrapping up and boxing our data visualization adventure
&lt;/h2&gt;

&lt;p&gt;We journeyed through the depths of Amazon purchases and emerged victorious with our "Amazon Purchases Insights Dashboard." We uncovered all sorts of juicy insights, like how much money we spent on baby gear (a lot). Along the way, we learned how to use Retool to create queries and connect them to UI components. If you’d like to build a similar dashboard, here’s the &lt;a href="https://gist.github.com/ajot/44cec830fe05e885669ca95fad12cff6" rel="noopener noreferrer"&gt;JSON&lt;/a&gt; you can &lt;a href="https://docs.retool.com/docs/import-export-apps" rel="noopener noreferrer"&gt;import&lt;/a&gt;. Also, here’s a &lt;a href="https://docs.google.com/spreadsheets/d/1vvDW9JQq10JRBpa8xz0p8c5WyLZTqJ4z-7oOcs8wsfA/edit?usp=sharing" rel="noopener noreferrer"&gt;sample Google Sheet&lt;/a&gt; you can use to start.&lt;/p&gt;

&lt;p&gt;I hope this has been helpful and has encouraged you to build your own dashboard. If you end up building it or have any questions, &lt;a href="https://twitter.com/amit" rel="noopener noreferrer"&gt;please reach out&lt;/a&gt; to me. I would love to help and would be curious to see what insights you uncover.&lt;/p&gt;

&lt;p&gt;Happy analyzing!&lt;/p&gt;

</description>
      <category>csv</category>
    </item>
    <item>
      <title>Did uploading a 6MB file bring down my Ghost site on Digital Ocean?</title>
      <dc:creator>Amit Jotwani</dc:creator>
      <pubDate>Tue, 26 Apr 2022 11:07:13 +0000</pubDate>
      <link>https://dev.to/ajot/did-uploading-a-6mb-file-bring-down-my-ghost-site-on-digital-ocean-433n</link>
      <guid>https://dev.to/ajot/did-uploading-a-6mb-file-bring-down-my-ghost-site-on-digital-ocean-433n</guid>
      <description>&lt;p&gt;Woke up to a 504 error this morning. It's hosted on Digital Ocean, so I reached out to support. While their response was prompt, the answer wasn't exactly helpful -&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As a self-managed infrastructure as a service (IaaS) provider, we do not have access to your servers so I cannot see your environment. The answer will depend on your webserver and/or database configuration. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They then includes a bunch of not-very-helpful-generic-504-articles from the internets.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what went wrong with my Ghost site overnight?
&lt;/h2&gt;

&lt;p&gt;I hadn't changed anything on my website, so I knew it probably had nothing to do with webserver/database configurations. A lot of the articles pointed me to the console, but I couldn't even access the console through digital ocean dashboard though. It threw an error of - "Timed out while waiting for handshake".&lt;/p&gt;

&lt;p&gt;I then ran into &lt;a href="https://gloathost.com/blog/how-to-restart-your-ghost-site-on-digital-ocean"&gt;this post&lt;/a&gt; (How to restart your Ghost site on Digital Ocean) from &lt;a href="https://twitter.com/dr"&gt;Dan Rowden&lt;/a&gt;. That gave me a hint that I should look into forcefully restarting my Ghost site. Another quick google search, and I ran into this post - &lt;a href="https://nuweb.blog/reboot-a-droplet-on-digitalocean/"&gt;2 Ways To Reboot a Droplet on DigitalOcean&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Restarting a Droplet on Digital Ocean
&lt;/h2&gt;

&lt;p&gt;To summarize - There are two ways to reboot a Droplet on DigitalOcean, these are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Gracefully&lt;/strong&gt;: Similar to that of restarting your machine through the Restart or Shut Down commands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forcefully&lt;/strong&gt;: Similar to holding down the power button on your machine and then powering it back on again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The graceful way wasn't working out in this case, since I couldn't even access the console. So, I went the forceful way.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Visit the Power tab in the Droplet.&lt;/li&gt;
&lt;li&gt;Click the Power Cycle button in the Power Cycle section&lt;/li&gt;
&lt;li&gt;Wait for confirmation message and wait a few minutes for the Droplet to reboot.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;That fixed it.&lt;/strong&gt; I could access the console, and the website came right back up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uhOmC2UM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q19quxl7t4jwvwzm0l35.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uhOmC2UM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q19quxl7t4jwvwzm0l35.png" alt="Restarting Digital Ocean Droplet" width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  So why did this happen?
&lt;/h2&gt;

&lt;p&gt;During my troubleshooting, I came across &lt;a href="https://www.digitalocean.com/community/questions/504-gateway-timeout-memory-error"&gt;this post&lt;/a&gt; on Digital Ocean community, pointing to memory issues being one of the causes for a 504 errors.&lt;/p&gt;

&lt;p&gt;This reminded me that I was trying to upload a large size image (&amp;gt;6MB) to my Ghost site yesterday. That was taking forever to load, and timed out each time I tried it. I now wonder if that process consumed most of the RAM, and the server essentially ran out of memory. Rebooting the server must have reset the memory. That makes sense to me. I will go with that argument, and vow never to upload a 6MB PNG to my Ghost site!&lt;/p&gt;

</description>
      <category>digitalocean</category>
      <category>ghost</category>
      <category>504</category>
    </item>
    <item>
      <title>How to deploy a Flask app to Digital Ocean's app platform</title>
      <dc:creator>Amit Jotwani</dc:creator>
      <pubDate>Tue, 19 Apr 2022 02:19:34 +0000</pubDate>
      <link>https://dev.to/ajot/how-to-deploy-a-flask-app-to-digital-oceans-app-platform-goc</link>
      <guid>https://dev.to/ajot/how-to-deploy-a-flask-app-to-digital-oceans-app-platform-goc</guid>
      <description>&lt;p&gt;I recently went through the process to put my side project &lt;a href="https://howbigisthebaby.io"&gt;howbigisthebaby.io&lt;/a&gt; on Digital Ocean's new app platform, and wanted to document it in case anyone else found it helpful (or atleast me revisiting this later).&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 1: Setup the environment for the Flask app
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Create the virtual environment on your machine&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ virtualenv venv -p Python3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Activate the virtual environment&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ source venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Install Flask and gunicorn&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip install Flask gunicorn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: Freeze the requirements in requirements.txt file&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now that you have the flask package installed, you will need to save this requirement and its dependencies so App Platform can install them later. Do this now using pip and then saving the information to a requirements.txt file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip freeze &amp;gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5: Write code for your Flask app in app.py. Here's a simple "hello world" app&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Part 2: Setting Up Your Gunicorn Configuration
&lt;/h2&gt;

&lt;p&gt;Since we cannot use Flask's built-in server in production environment, we will use Gunicron web server &lt;a href="https://www.reddit.com/r/Python/comments/68phcu/why_nginxgunicornflask/"&gt;Why Nginx/Gunicorn/Flask?&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;While lightweight and easy to use, Flask’s built-in server is not suitable for production as it doesn’t scale well and by default serves only one request at a time.&lt;br&gt;
&lt;a href="https://flask.palletsprojects.com/en/2.0.x/deploying/"&gt;Source: Flask's docs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Create the gunicorn config file&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ touch gunicorn_config.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Add this configuration to gunicorn_config.py file&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# inside gunicorn_config.py
bind = "0.0.0.0:8080"
workers = 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Part 3: Pushing the Site to GitHub
&lt;/h2&gt;

&lt;p&gt;The way the Digital Ocean app platform works is that it syncs with a GitHub repo. Any changes to this GitHub repo will automatically be pushed to your app living on Digital Ocean's app platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Create a new repo on GitHub.com&lt;/strong&gt;&lt;br&gt;
Open your browser and navigate to GitHub, log in with your profile, and create a new repository called flask-app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Then, create a .gitignore file, and add .pyc so these files are not pushed to GitHub.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git init
$ nano .gitignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# inside .gitignore
.pyc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Add files to your repository, and commit the changes&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git add app.py gunicorn_config.py requirements.txt .gitignore
$ git commit -m "Initial Flask App"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: Add the GitHub repo you created earlier as the remote repository&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git remote add origin https://github.com/your_username/flask-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;example: git remote add origin &lt;a href="https://github.com/ajot/hbb-flask-webhook"&gt;https://github.com/ajot/hbb-flask-webhook&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Push to GitHub&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git branch -M main
$ git push -u origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Part 4: Deploying to Digital Ocean's app platform
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Create a new "app" on Digital Ocean.&lt;/li&gt;
&lt;li&gt;Choose GitHub as source&lt;/li&gt;
&lt;li&gt;Choose the GitHub repo you just created&lt;/li&gt;
&lt;li&gt;Follow the promots to create the app&lt;/li&gt;
&lt;li&gt;Edit the run command to this - gunicorn --worker-tmp-dir /dev/shm app:app&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;strong&gt;Bonus Tip:&lt;/strong&gt; You can also set environment variables in Digital Ocean, and then access them in your code like this -&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;airtable_api_key = os.environ.get('airtable_api_key')
airtable_base = os.environ.get('airtable_base')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Resources:&lt;/strong&gt; &lt;br&gt;
&lt;a href="https://www.digitalocean.com/community/tutorials/how-to-deploy-a-flask-app-using-gunicorn-to-app-platform"&gt;How To Deploy a Flask App Using Gunicorn to App Platform | DigitalOcean&lt;/a&gt;&lt;/p&gt;

</description>
      <category>flask</category>
      <category>python</category>
      <category>tutorial</category>
      <category>digitalocean</category>
    </item>
    <item>
      <title>How to speed Alexa skill development using ASK Command Line Interface and Custom Templates</title>
      <dc:creator>Amit Jotwani</dc:creator>
      <pubDate>Mon, 11 May 2020 21:44:35 +0000</pubDate>
      <link>https://dev.to/ajot/how-to-speed-alexa-skill-development-using-ask-command-line-interface-and-custom-templates-16g1</link>
      <guid>https://dev.to/ajot/how-to-speed-alexa-skill-development-using-ask-command-line-interface-and-custom-templates-16g1</guid>
      <description>&lt;p&gt;As a developer, I spend a large amount of my time writing code everyday. The rest, of course is spent debugging that code and “Googling” the internet for answers. But, I digress. All to say, that I am always looking for ways to speed up the development workflows when it comes to “getting started” with something, and then making it easy to “run”, and “deploy” the code. &lt;/p&gt;

&lt;p&gt;One area  I spend a significant amount of time every week is creating new Alexa Skills from scratch from a learning and education point of view. This is where I’ve found the ASK CLI to be super helpful in speeding up the workflow to create and update Alexa Skills, using the Alexa Skill Kit CLI tool. &lt;/p&gt;

&lt;h3&gt;
  
  
  What is Alexa Skills Kit CLI?
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://developer.amazon.com/en-US/docs/alexa/smapi/quick-start-alexa-skills-kit-command-line-interface.html"&gt;Alexa Skills Kit Command Line Interface&lt;/a&gt;(ASK CLI) provides a quick way to perform a lot of Alexa skills tasks from the command line interface, like creating new skills, and deploying the code to Alexa Hosted or AWS. You can even simulate your skill from the command line. &lt;/p&gt;

&lt;h3&gt;
  
  
  Using Templates with ASK CLI to jump start development of a new Alexa Skill
&lt;/h3&gt;

&lt;p&gt;One feature that I like the most about ASK CLI is that it allows you to create a new skill by using your own custom skill templates. So, if you’re like me, you can create a starter template for all your Alexa Skill projects. For example, if there are certain helper functions you use in every skill, it would make sense to include those in the template, so they’re available in the new skill right off the bat. &lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a new skill using a custom template
&lt;/h3&gt;

&lt;p&gt;To create a new skill using a custom template, you provide the Git URL of the template with the flag &lt;code&gt;--template-url&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;The command to create a new skill using a custom template varies a bit based on the version of the ASK CLI. To check the version of ASK CLI, simply type &lt;code&gt;ask -v&lt;/code&gt; or &lt;code&gt;ask -V&lt;/code&gt; in the terminal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you’re using ASK CLI v2&lt;/strong&gt;&lt;br&gt;
If you’re using ASK CLI v2, you can create a new skill using a custom template using the command below -&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ask new --template-url https://example.com/example/example-skill.git --template-branch master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
Here’s a CLI v2 compatible  I use to jump start my Python based Alexa Skills - &lt;br&gt;
&lt;code&gt;https://github.com/ajot/python-alexa-skill-starter-template-cli-v2.git&lt;/code&gt;. If you like, you can use this to create a new skill by using the command below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ask new --template-url https://github.com/ajot/python-alexa-skill-starter-template-cli-v2.git --template-branch master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may get a warning, like the one below. Type "y" to continue.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"[Warn]: CLI is about to download the skill template from unofficial template. Please make sure you understand the source code to best protect yourself from malicious usage."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_wfaZVCc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/1qygrskh2p42amgawt8k.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_wfaZVCc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/1qygrskh2p42amgawt8k.gif" alt="Alt Text" width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will create the skill locally on your machine. You’re now ready to deploy the skill. See “Deploy” section below.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;If you’re using ASK CLI v1&lt;/strong&gt;&lt;br&gt;
If you’re using ASK CLI v1, you can create a new skill using a custom template using the command below -&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ask new --template-url https://example.com/example/example-skill.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
Here’s a CLI v1 compatible template I use to jump start my Python based Alexa Skills - &lt;br&gt;
&lt;code&gt;https://github.com/ajot/python-skill-basic-template.git&lt;/code&gt;. If you like, you can use this to create a new skill by using the command below, and optionally, providing a skill name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ask new --template-url https://github.com/ajot/python-skill-basic-template.git -n &amp;lt;optional-name-for-your-skill&amp;gt; -n &amp;lt;optional-skill-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may get a warning, like the one below. Type "yes"&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Downloading skill template from unofficial resource. Please make sure you understand what each script is doing to best protect yourself from malicious usage".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This will create the skill locally on your machine.&lt;/p&gt;




&lt;h3&gt;
  
  
  Deploying the code to Alexa Developer Console, and AWS Lambda
&lt;/h3&gt;

&lt;p&gt;At this point, we need to locally install any other modules your skill may need. In the case of the Python template I mentioned above, it needs  &lt;code&gt;ask-sdk-core&lt;/code&gt; package to be installed. &lt;/p&gt;

&lt;p&gt;As is generally a good practice, we first need to create/activate a &lt;a href="https://www.fullstackpython.com/virtual-environments-virtualenvs-venvs.html"&gt;virtual environment&lt;/a&gt;. There are several options available for managing virtual environments for Python. I personally use &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;virtualenv&lt;/a&gt;, but you can tailor the steps below to match your virtual environment of choice (like &lt;a href="https://docs.conda.io/projects/conda/en/latest/"&gt;Conda&lt;/a&gt;). &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create a new virtual environment&lt;/strong&gt;&lt;br&gt;
Create a new  virtual environment&lt;a href="https://www.fullstackpython.com/virtual-environments-virtualenvs-venvs.html"&gt;&lt;/a&gt; with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ virtualenv skill_env -p python3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our virtual environment is created. Now before we start using it, we need to activate:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Activating a virtual environment&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ source skill_env/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will know it worked if you see &lt;em&gt;(skill_env)&lt;/em&gt; in front of the command line, like this:&lt;br&gt;
[Image: Screen Shot 2020-05-07 at 8.01.34 PM.png]&lt;strong&gt;Install any modules your skill may need&lt;/strong&gt;&lt;br&gt;
This specific template, for example needs the &lt;code&gt;ask-sdk-core&lt;/code&gt; module. So let’s install that inside the virtual environment we created using the Python's pip command -&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip install ask-sdk-core
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are now ready to deploy our skill to the developer console, and AWS lambda -&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ask deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verify and test the skill deployment
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Verify the skill deployment on the Alexa Developer Console&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ft0ABXPd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/7t0b69kdndlj0cos4ulx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ft0ABXPd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/7t0b69kdndlj0cos4ulx.png" alt="Alt Text" width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Verify the code deployment on AWS Lambda Console&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4EnVCsYo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/ieimpx9f14zquxy92wwg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4EnVCsYo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/ieimpx9f14zquxy92wwg.png" alt="Alt Text" width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Test the skill in the Alexa Developer Console&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can now test your skill using the "Test" tab inside the Alexa Developer Console at &lt;a href="https://developer.amazon.com/alexa/console/ask/test"&gt;https://developer.amazon.com/alexa/console/ask/test&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QQXNqdtL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/0b6a5x5yczdpuxpxu8ik.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QQXNqdtL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/0b6a5x5yczdpuxpxu8ik.png" alt="Alt Text" width="384" height="643"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  More Resources for ASK CLI:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://developer.amazon.com/es-ES/docs/alexa/smapi/ask-cli-command-reference.html"&gt;ASK CLI Command Reference&lt;/a&gt;&lt;br&gt;
&lt;a href="https://developer.amazon.com/es-ES/docs/alexa/smapi/ask-cli-intro.html"&gt;Alexa Skills Kit Command Line Interface (ASK CLI) Overview&lt;/a&gt;&lt;br&gt;
&lt;a href="https://developer.amazon.com/en-US/docs/alexa/smapi/quick-start-alexa-skills-kit-command-line-interface.html"&gt;Quick Start: Alexa Skills Kit Command Line Interface (ASK CLI)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>alexa</category>
      <category>skills</category>
      <category>aws</category>
      <category>cli</category>
    </item>
    <item>
      <title>How to make HTTP Requests from an Alexa Skill to Get Data from an External API using Python 3</title>
      <dc:creator>Amit Jotwani</dc:creator>
      <pubDate>Fri, 17 Apr 2020 17:20:45 +0000</pubDate>
      <link>https://dev.to/ajot/how-to-make-http-requests-from-an-alexa-skill-to-get-data-from-an-external-api-using-python-3-45in</link>
      <guid>https://dev.to/ajot/how-to-make-http-requests-from-an-alexa-skill-to-get-data-from-an-external-api-using-python-3-45in</guid>
      <description>&lt;p&gt;As a developer, every day we interact with remote APIs and web servers. Almost all services we consume on the internet today are available in the form of an API: weather forecasts, geolocation services, Twitter feeds, and so on. &lt;/p&gt;

&lt;p&gt;It’s only fair to imagine, that you may run into situations in which you would want an Alexa Skill to get meaningful data from one of these remote sources by making HTTP requests to their APIs. HTTP requests allow you to fetch data from a remote source. It could be an API, a website, or something else.&lt;/p&gt;

&lt;p&gt;In this post, we will take a quick look how you can get external data from an external API from an Alexa Skill using Python 3.&lt;/p&gt;

&lt;p&gt;While there are many libraries to make an HTTP request in Python, including &lt;a href="https://docs.python.org/3/library/http.client.html" rel="noopener noreferrer"&gt;http.client&lt;/a&gt;  (formerly named &lt;a href="https://docs.python.org/2/library/httplib.html" rel="noopener noreferrer"&gt;httplib&lt;/a&gt; ),  &lt;a href="https://docs.python.org/2/library/urllib.html" rel="noopener noreferrer"&gt;urllib&lt;/a&gt; ,  &lt;a href="https://github.com/httplib2/httplib2" rel="noopener noreferrer"&gt;httplib2&lt;/a&gt; ,  &lt;a href="https://github.com/twisted/treq" rel="noopener noreferrer"&gt;treq&lt;/a&gt; , etc., the &lt;a href="https://requests.readthedocs.io/en/master/" rel="noopener noreferrer"&gt;Requests&lt;/a&gt; library is among the most popular Python libraries for making HTTP requests. The subtitle of “&lt;em&gt;HTTP for Humans&lt;/em&gt;” perfectly captures the philosophy behind it.  It abstracts the complexities of making requests behind a simple interface, and makes HTTP requests easy and simple.&lt;/p&gt;

&lt;p&gt;If you’re using &lt;a href="https://developer.amazon.com/en-US/docs/alexa/hosted-skills/build-a-skill-end-to-end-using-an-alexa-hosted-skill.html" rel="noopener noreferrer"&gt;Alexa Hosted&lt;/a&gt; as the backend for your skills, there’s an added advantage of the Requests library being built-in, so you don’t need to install or upload it manually. If you’re using AWS Lambda or any other service as the backend, you may need to install the &lt;code&gt;requests&lt;/code&gt; module locally, and then upload it to the service.&lt;/p&gt;

&lt;p&gt;So, with that out of the way, let’s jump into how you can use the Requests library to make an HTTP request to an API. For the purposes of this tutorial, we will be using the &lt;a href="https://api.chucknorris.io/" rel="noopener noreferrer"&gt;Chuck Norris API&lt;/a&gt;. You can obviously replace that with other APIs of your choice. &lt;/p&gt;

&lt;h2&gt;
  
  
  Using Requests library to make an API call from an Alexa Skill
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Import the &lt;code&gt;requests&lt;/code&gt; package by adding an import statement to the top of lambda_function.py:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Create a helper function to make the call to the API using the endpoint  &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;getResponseFromAPI&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://api.chucknorris.io/jokes/random&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

    &lt;span class="n"&gt;json_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;joke&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;joke&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here’s a sample of what the data that would get returned by the Chuck Norris API, and would be stored in the variable &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk941r6nh8dnuv1w310lc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk941r6nh8dnuv1w310lc.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Call the helper function from inside the LaunchRequestHandler (or any other intent handler that may be appropriate).&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LaunchRequestHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AbstractRequestHandler&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Handler for Skill Launch.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;can_handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler_input&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# type: (HandlerInput) -&amp;gt; bool
&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ask_utils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_request_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LaunchRequest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;handler_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;ask_utils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_intent_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AMAZON.YesIntent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;handler_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler_input&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# type: (HandlerInput) -&amp;gt; Response
&lt;/span&gt;        &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LaunchRequest was trigerred&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;joke&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getResponseFromAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;handler_input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response_builder&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;joke&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Would you like to hear another Chuck Norris joke?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Would you like to hear another joke?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Hopefully that makes it easy to make API requests from your Alexa Skills. If you're looking to learn more about building Alexa Skills, I’ll leave you with some resources if you’re looking to learn more about Alexa Skills, and the Python requests library in general. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://developer.amazon.com/en-US/alexa/alexa-skills-kit/get-deeper/tutorials-code-samples/build-an-engaging-alexa-skill/module-1" rel="noopener noreferrer"&gt;Create an Alexa Skill in Five Minutes - Python &amp;amp; Node&lt;/a&gt; - Step-by-step training course from the Amazon team to build your first Alexa Skill&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/alexa/alexa-skills-kit-sdk-for-python" rel="noopener noreferrer"&gt;Alexa Skills Kit SDK for Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs" rel="noopener noreferrer"&gt;Alexa Skills Kit SDK for Node.js&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>alexa</category>
      <category>python</category>
      <category>skill</category>
      <category>api</category>
    </item>
  </channel>
</rss>
