<?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: Dylan J. Sather</title>
    <description>The latest articles on DEV Community by Dylan J. Sather (@dylburger).</description>
    <link>https://dev.to/dylburger</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%2F234988%2F057b1fdb-03ed-4e08-a3e7-b477d8a6c3ef.jpeg</url>
      <title>DEV Community: Dylan J. Sather</title>
      <link>https://dev.to/dylburger</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dylburger"/>
    <language>en</language>
    <item>
      <title>Build your own chat bot with OpenAI and Pipedream</title>
      <dc:creator>Dylan J. Sather</dc:creator>
      <pubDate>Thu, 09 Nov 2023 16:54:23 +0000</pubDate>
      <link>https://dev.to/dylburger/build-your-own-chat-bot-with-openai-and-pipedream-299m</link>
      <guid>https://dev.to/dylburger/build-your-own-chat-bot-with-openai-and-pipedream-299m</guid>
      <description>&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F11%2Fdylburger_A_small_cute_childlike_robot_in_a_lush_fall_forest_th_0c56f04a-d49f-4975-989f-b0140ae9e11e.jpg" 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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F11%2Fdylburger_A_small_cute_childlike_robot_in_a_lush_fall_forest_th_0c56f04a-d49f-4975-989f-b0140ae9e11e.jpg" alt="Build your own chat bot with OpenAI and Pipedream"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Meet Pi, Pipedream's little AI helper. Pi is a support bot, trained on our docs, code, and integrated APIs. It answers questions in our &lt;a href="https://pipedream.com/support" rel="noopener noreferrer"&gt;Slack&lt;/a&gt; and &lt;a href="https://pipedream.com/community" rel="noopener noreferrer"&gt;Discourse&lt;/a&gt; communities. Like ChatGPT, it provides (mostly) human-level answers to challenging problems.&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F05%2FScreenshot-2023-05-30-at-7.15.29-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F05%2FScreenshot-2023-05-30-at-7.15.29-PM.png" alt="Build your own chat bot with OpenAI and Pipedream"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Pi, not affiliated with &lt;a href="https://pi.ai/" rel="noopener noreferrer"&gt;https://pi.ai/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It's fun to talk to, it's saving us hours a day, and it's providing support to our community that we never could without an artificial assistant.&lt;/p&gt;

&lt;p&gt;This week, we also updated Pi to use OpenAI's new &lt;a href="https://platform.openai.com/docs/guides/vision" rel="noopener noreferrer"&gt;Vision API&lt;/a&gt;, which lets us process screenshots and other media that Pi couldn't parse before.&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F05%2FScreenshot-2023-05-30-at-6.27.27-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F05%2FScreenshot-2023-05-30-at-6.27.27-PM.png" alt="Build your own chat bot with OpenAI and Pipedream"&gt;&lt;/a&gt;&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F05%2FScreenshot-2023-05-30-at-6.27.49-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F05%2FScreenshot-2023-05-30-at-6.27.49-PM.png" alt="Build your own chat bot with OpenAI and Pipedream"&gt;&lt;/a&gt;&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F11%2FScreenshot-2023-11-06-at-5.06.22-PM-2.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F11%2FScreenshot-2023-11-06-at-5.06.22-PM-2.png" alt="Build your own chat bot with OpenAI and Pipedream"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Pi has access to Pipedream integration data, pricing, product docs, and more&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Collectively, we've spent hundreds of hours researching, refining, and deploying Pi. We've made amazing progress, but since this tech is so new and moving fast, we still find issues and improve it incrementally every day.&lt;/p&gt;

&lt;p&gt;We're sharing everything we've learned so you can build your own.&lt;/p&gt;
&lt;h2&gt;
  
  
  Terms
&lt;/h2&gt;

&lt;p&gt;This article assumes you know how to code — or want to learn — and are curious about building AI bots. But we don't assume knowledge of language models like GPT-4. Here's a few terms you'll see used in this post and more generally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LLM&lt;/strong&gt; : Large language model. GPT-4 is an LLM, as is &lt;a href="https://ai.facebook.com/blog/large-language-model-llama-meta-ai/" rel="noopener noreferrer"&gt;Meta's LLaMA&lt;/a&gt;. LLMs are trained on websites, papers, and other vast amounts of text, often from the Internet. They learn how to predict the next most-likely token to appear after the text the user provides. This is why you often hear token prediction referred to as "completion". We use OpenAI's GPT-4 model for Pi. As of 2023, GPT-4 performs better than other models on most problems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token&lt;/strong&gt; : &lt;a href="https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them" rel="noopener noreferrer"&gt;A piece of a word&lt;/a&gt;, a unit of text that models like GPT-4 deal with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt&lt;/strong&gt; : The text you provide to the LLM. It can be a question, a set of instructions — any text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Completion&lt;/strong&gt; : Since LLMs are predicting the next most-likely token that appears after a user's prompt, it "completes" that text. This is why you'll see this verb used when describing LLMs. See e.g. OpenAI's &lt;a href="https://platform.openai.com/docs/guides/chat" rel="noopener noreferrer"&gt;chat completion API docs&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embeddings&lt;/strong&gt; : Embeddings are &lt;a href="https://en.wikipedia.org/wiki/Vector_(mathematics_and_physics)" rel="noopener noreferrer"&gt;vectors&lt;/a&gt; that store the &lt;a href="https://en.wikipedia.org/wiki/Semantics" rel="noopener noreferrer"&gt;semantic meaning&lt;/a&gt; of a piece of text. (&lt;a href="https://platform.openai.com/docs/guides/embeddings" rel="noopener noreferrer"&gt;see OpenAI reference&lt;/a&gt;). If you want to compare the semantic meaning of two strings, compare their embeddings using a measure like &lt;a href="https://en.wikipedia.org/wiki/Cosine_similarity" rel="noopener noreferrer"&gt;cosine distance&lt;/a&gt;. Embeddings that are "close" in distance are more related. We'll discuss this all in more detail below.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Designing your bot
&lt;/h2&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F11%2FScreenshot-2023-11-08-at-2.37.50-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F11%2FScreenshot-2023-11-08-at-2.37.50-PM.png" alt="Build your own chat bot with OpenAI and Pipedream"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;This is the high-level design of Pi. Your bot will probably look very different!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You should think through five key things when designing your bot:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What docs / content does it need to answer questions?&lt;/li&gt;
&lt;li&gt;How are users going to interact with it? On Slack, email, UI?&lt;/li&gt;
&lt;li&gt;Given a question, how do you process it and retrieve the most relevant docs? In our case, users are often asking about &lt;a href="https://pipedream.com/apps" rel="noopener noreferrer"&gt;app integrations&lt;/a&gt;, so we try to identify those apps in the question and retrieve relevant app metadata.&lt;/li&gt;
&lt;li&gt;What rules should it follow when communicating with users? How should it return its answers and links to docs? How should it respond when it doesn't know an answer? How should you include the docs from step #3 in the prompt to give the LLM more knowledge? This is where you'll develop the core instructions the LLM will use to answer questions.&lt;/li&gt;
&lt;li&gt;How do you evaluate and improve the accuracy of answers? You'll constantly find issues with your docs and your prompt. You need a way to evaluate and improve your bot's performance for all of your key use cases. You don't want to ship a subtle change to your prompt and introduce a major regression in its behavior in another area, as we've done many times.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We'll detail how we approached each of these below.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Your bot needs docs and knowledge
&lt;/h2&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F05%2FScreenshot-2023-05-22-at-2.15.39-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F05%2FScreenshot-2023-05-22-at-2.15.39-PM.png" alt="Build your own chat bot with OpenAI and Pipedream"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Step 1 — Collect, process, and save your docs somewhere&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you're building a bot to answer questions about your business, it's likely GPT-4 doesn't know everything about you. That was the case with Pipedream. Since we launched in 2019, it knows we exist. But we add new features, docs, and integrations daily, we don't know all the public docs the model is trained on, and &lt;a href="https://community.openai.com/t/what-is-the-actual-cutoff-date-for-gpt-4/394750" rel="noopener noreferrer"&gt;GPT-4 has a knowledge cutoff&lt;/a&gt; — a date where its knowledge ends. So today, it can't answer deep questions about the Pipedream product or API.&lt;/p&gt;

&lt;p&gt;If ChatGPT returns reliable answers for your use case, and you don't care about events or updates after a certain date, you may be able to skip this part. Everyone else should read on.&lt;/p&gt;
&lt;h3&gt;
  
  
  Retrieval augmented generation
&lt;/h3&gt;

&lt;p&gt;So how do we solve this problem? Enter &lt;a href="https://ai.meta.com/blog/retrieval-augmented-generation-streamlining-the-creation-of-intelligent-natural-language-processing-models/" rel="noopener noreferrer"&gt;Retrieval Augmented Generation&lt;/a&gt; ("RAG"):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval&lt;/strong&gt; : Given the user's question, retrieve relevant docs / content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generation&lt;/strong&gt; : Pass the docs, along with the original question, to the LLM&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A base model like GPT-4 is &lt;em&gt;generally&lt;/em&gt; very good at summarizing text, classifying input, writing code and more. But it lacks the specific information it needs to answer your users' questions. When you provide that context with the question, the LLM provides better answers.&lt;/p&gt;

&lt;p&gt;At this stage, you need to figure out what docs you need, how to process them, and where you're going to store them.&lt;/p&gt;
&lt;h3&gt;
  
  
  Choosing the right docs
&lt;/h3&gt;

&lt;p&gt;Only use trusted docs. For example, your pricing page, terms of service, and your product's docs can be considered trusted sources. But your community Q&amp;amp;A, blog posts from external authors, and other user-generated content are not &lt;em&gt;necessarily&lt;/em&gt; vetted.&lt;/p&gt;

&lt;p&gt;We originally included community Q&amp;amp;A, but it reduced the accuracy of answers. Some posts matched the user's question, but were unanswered or had wrong answers. Others referred to years' old features that had since been improved or deprecated. After a few attempts to separate good posts from bad, we removed them entirely and saw our accuracy improve.&lt;/p&gt;

&lt;p&gt;Today, we use our docs, pricing page, terms and privacy page, our blog, and our integrations data: information on the app, all of the sources and actions you can use on Pipedream, and the OpenAPI specs and docs of our partners.&lt;/p&gt;

&lt;p&gt;We run &lt;a href="https://github.com/PipedreamHQ/pipedream/blob/master/blog/pi/create_index.ipynb" rel="noopener noreferrer"&gt;this Jupyter notebook&lt;/a&gt; to fetch, process, and save docs into Supabase. We use Python because of the availability of text processing and AI client libraries. For example, we use &lt;a href="https://pytorch.org/" rel="noopener noreferrer"&gt;PyTorch&lt;/a&gt; and &lt;a href="https://scikit-learn.org/stable/" rel="noopener noreferrer"&gt;scikit-learn&lt;/a&gt; to customize our embeddings — Python is the de-facto language for this use case.&lt;/p&gt;

&lt;p&gt;Since the notebook processes Pipedream docs and custom Snowflake tables, you'll need to modify it for your use case. We'll walk through the core parts below so you see how this works.&lt;/p&gt;
&lt;h3&gt;
  
  
  Chunking
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Chunking&lt;/strong&gt; refers to the process of dividing a large document of text into smaller pieces. Typically, we need to do this for two reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;OpenAI and other LLM APIs have token limits, so to fit the user's question, docs, and answer, you can't pass every doc every time. These limits are rising — &lt;a href="https://openai.com/blog/new-models-and-developer-products-announced-at-devday" rel="noopener noreferrer"&gt;the newly-announced GPT 4 Turbo model&lt;/a&gt; supports &lt;code&gt;128K&lt;/code&gt; tokens per request — but since commercial LLMs charge per token, you'll want to minimize tokens to only the required information.&lt;/li&gt;
&lt;li&gt;You want your bot to retrieve docs similar to the user's question. If you store pages of docs with a range of different concepts in a single record, the embeddings created from that will encode meaning about the document as a whole, and not any individual sections. We like to think of chunks as a section of text that maps to a unique concept. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To separate our docs into chunks, we used two utilities from &lt;a href="https://python.langchain.com/en/latest/index.html" rel="noopener noreferrer"&gt;LangChain&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For Markdown docs, &lt;code&gt;MarkdownHeaderTextSplitter&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;For HTML docs, &lt;code&gt;HTMLHeaderTextSplitter&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When saving chunks, there should be some overlap between sections. For example, chunk 2 should contain the end of the text from chunk 1 and the start of the text from chunk 3. Since adjacent sections of docs are likely related, this keeps semantic context between these related chunks when they're saved as independent sections.&lt;/p&gt;

&lt;p&gt;I haven't seen a one-size-fits-all answer for how large your chunk size or chunk overlap should be. &lt;a href="https://dev.toChunking%20Strategies%20for%20LLM%20Applications"&gt;According to Pinecone&lt;/a&gt;, embeddings with the latest OpenAI models work best when chunks are multiples of 256 tokens. But these should be tested and optimized for your use case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## TEXT SPLITTERS ##
headers_to_split_on = [
    ("h1", "Header 1"),
    ("h2", "Header 2"),
]
text_splitter = HTMLHeaderTextSplitter(headers_to_split_on=headers_to_split_on)

## h2 and h3 sections of our Markdown docs separate logical sections
markdown_headers_to_split_on = [
    ("#", "Header 1"),
    ("##", "Header 2"),
    ("###", "Header 3"),
]
markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=markdown_headers_to_split_on)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Docs and data should be structured
&lt;/h3&gt;

&lt;p&gt;You should convert your docs to Markdown or another structured format as a part of the processing — if they are not already — with separate content in separate sections, delimited by headers like &lt;code&gt;#&lt;/code&gt; and &lt;code&gt;##&lt;/code&gt;. In practice, we've found that the inherent structure of headings, text, and other formatting communicates structure and meaning better than the same HTML. OpenAI also uses Markdown for their own prompts, so we think it's a safe bet.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Take note, OpenAI constructs prompts using markdown. I believe this is reflective of their instruction tuning datasets.   &lt;/p&gt;

&lt;p&gt;In our own work we’ve seen markdown increase model compliance far more than is otherwise reasonable. &lt;a href="https://t.co/VniaLFbO2B" rel="noopener noreferrer"&gt;https://t.co/VniaLFbO2B&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;— Mike Conover (@vagabondjack) &lt;a href="https://twitter.com/vagabondjack/status/1713602004969205804?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;October 15, 2023&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You'll see &lt;a href="https://github.com/PipedreamHQ/pipedream/blob/master/blog/pi/create_index.ipynb" rel="noopener noreferrer"&gt;in our notebook&lt;/a&gt; how we retrieve data from Snowflake and convert that to Markdown text, which details how users can use our integrations in the Pipedream UI. Specifically, we convert data about our integrations to &lt;em&gt;numbered instructions&lt;/em&gt; that tell users how to configure our &lt;a href="https://pipedream.com/docs/workflows/steps/triggers/" rel="noopener noreferrer"&gt;triggers&lt;/a&gt; and &lt;a href="https://pipedream.com/docs/workflows/steps/actions/" rel="noopener noreferrer"&gt;actions&lt;/a&gt;, which Pi can use directly when talking to users.&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F05%2FScreenshot-2023-05-27-at-6.47.56-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F05%2FScreenshot-2023-05-27-at-6.47.56-PM.png" alt="Build your own chat bot with OpenAI and Pipedream"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Example Markdown instructions for Pi&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/brexhq/prompt-engineering#strategies" rel="noopener noreferrer"&gt;Brex's prompt engineering guide&lt;/a&gt; and &lt;a href="https://www.anyscale.com/blog/a-comprehensive-guide-for-building-rag-based-llm-applications-part-1" rel="noopener noreferrer"&gt;Anyscale's RAG guide&lt;/a&gt; highlight other techniques for passing structured data to the LLM. Experiment with your own data and keep iterating.&lt;/p&gt;
&lt;h3&gt;
  
  
  Convert the docs to embeddings
&lt;/h3&gt;

&lt;p&gt;Once you have chunks of docs, you can convert them into embeddings.&lt;/p&gt;

&lt;p&gt;Embeddings are &lt;a href="https://en.wikipedia.org/wiki/Vector_(mathematics_and_physics)" rel="noopener noreferrer"&gt;vectors&lt;/a&gt; that store the &lt;a href="https://en.wikipedia.org/wiki/Semantics" rel="noopener noreferrer"&gt;semantic meaning&lt;/a&gt; of a piece of text. Embedding models accept text like "What's the temperature outside?" as input and converts it to a vector of numbers.&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F05%2FScreenshot-2023-05-30-at-7.54.56-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F05%2FScreenshot-2023-05-30-at-7.54.56-PM.png" alt="Build your own chat bot with OpenAI and Pipedream"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Embedding models convert text to numbers&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Since these numbers represent semantic meaning, if you want to compare the meaning of two strings, compare their embeddings using a measure like &lt;a href="https://en.wikipedia.org/wiki/Cosine_similarity" rel="noopener noreferrer"&gt;cosine distance&lt;/a&gt;. Embeddings that are "close" in distance are more related. You don't have to understand the math behind this — cosine distance is implemented in many libraries.&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F05%2FScreenshot-2023-05-30-at-7.49.55-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F05%2FScreenshot-2023-05-30-at-7.49.55-PM.png" alt="Build your own chat bot with OpenAI and Pipedream"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;How "close" are these two embeddings?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We use &lt;a href="https://python.langchain.com/en/latest/reference/modules/embeddings.html" rel="noopener noreferrer"&gt;LangChain's &lt;code&gt;OpenAIEmbeddings&lt;/code&gt; class&lt;/a&gt; to generate embeddings with OpenAI. You can use &lt;a href="https://platform.openai.com/docs/api-reference/embeddings" rel="noopener noreferrer"&gt;the OpenAI Embeddings API&lt;/a&gt; directly, or use an entirely different service like &lt;a href="https://docs.cohere.com/docs/embeddings" rel="noopener noreferrer"&gt;Cohere&lt;/a&gt; or your own embeddings model to generate embeddings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from langchain.embeddings import OpenAIEmbeddings

embeddings = OpenAIEmbeddings()
embedded_docs = embeddings.embed_documents(docs)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OpenAI embeddings that use the latest &lt;code&gt;text-embedding-ada-002&lt;/code&gt; model are vectors of 1536 numbers. &lt;code&gt;embedded_docs&lt;/code&gt; is a Python list of lists of embeddings. We'll store this alongside the docs content below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Store your docs and embeddings
&lt;/h3&gt;

&lt;p&gt;Now you need to save your sources and embeddings in a data store. We evaluated many different options — primarily &lt;a href="http://pinecone.io/" rel="noopener noreferrer"&gt;Pinecone&lt;/a&gt; and &lt;a href="https://www.postgresql.org/" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt; — and landed on Postgres for reasons I'll describe below.&lt;/p&gt;

&lt;p&gt;We quickly realized that we'd want to return a complex set of docs based on the user's query:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetch all docs that matched the embeddings (via vector similarity)&lt;/li&gt;
&lt;li&gt;Do a standard full-text search (people call #1 + #2 "&lt;a href="https://js.langchain.com/docs/modules/indexes/retrievers/supabase-hybrid" rel="noopener noreferrer"&gt;hybrid search&lt;/a&gt;")&lt;/li&gt;
&lt;li&gt;If we can identify apps in the user's question (e.g. "Slack", "Discord"), retrieve all integration docs tied to the app. This includes everything on &lt;a href="https://pipedream.com/apps" rel="noopener noreferrer"&gt;https://pipedream.com/apps&lt;/a&gt; — the OAuth configuration for apps, test code, and information on all triggers and actions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Docs are returned in order of descending similarity, so if we have to truncate content to fit into the &lt;code&gt;8K&lt;/code&gt; GPT-4 token limit, we'll (theoretically) get the best content at the top.&lt;/p&gt;

&lt;p&gt;Postgres is mature and &lt;a href="https://github.com/pgvector/pgvector" rel="noopener noreferrer"&gt;the &lt;code&gt;pgvector&lt;/code&gt; extension&lt;/a&gt; provides the vector operations we need to compare embeddings. It was also easy extend our Postgres queries to do the full-text search and integration docs retrieval:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT content
FROM docs
WHERE app = 'slack'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Retrieving all docs for the app is as simple as a WHERE clause, which is part of the reason we chose Postgres&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Like with chunking, consider your data and retrieval model, and test different providers. A different embedding model may work better for your use case, and Postgres may not be the optimal place to store your content. This is an important step to get right. Your bot will provide poor answers if it's given bad docs, so it's worth the time.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Interface with users
&lt;/h2&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F11%2FScreenshot-2023-11-08-at-4.30.45-PM-1.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F11%2FScreenshot-2023-11-08-at-4.30.45-PM-1.png" alt="Build your own chat bot with OpenAI and Pipedream"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We use Slack and Discourse for community support. Pipedream supports triggers and actions for these apps &lt;a href="https://pipedream.com/apps" rel="noopener noreferrer"&gt;and hundreds more&lt;/a&gt;. So we've built Pipedream workflows to handle incoming questions and pass them to Pi to answer.&lt;/p&gt;

&lt;p&gt;With Slack, Discourse, and other messaging apps, you don't need to manage your own UI. Even &lt;a href="https://www.midjourney.com/" rel="noopener noreferrer"&gt;Midjourney&lt;/a&gt; — with millions of users — still uses Discord for their core app.&lt;/p&gt;

&lt;p&gt;OpenAI's &lt;a href="https://platform.openai.com/docs/guides/gpt" rel="noopener noreferrer"&gt;Chat Completion API&lt;/a&gt; also accepts chat history. These platforms store chat history for you, so you don't need to maintain that state yourself.  The app-specific workflows just retrieve chat history directly from Slack or Discourse when a new question arrives.&lt;/p&gt;

&lt;p&gt;For example, we've configured the Pi Slack app to send &lt;a href="https://api.slack.com/events/app_mention" rel="noopener noreferrer"&gt;&lt;code&gt;app_mention&lt;/code&gt; events&lt;/a&gt; to &lt;a href="https://pipedream.com/new?h=tch_mypfj9" rel="noopener noreferrer"&gt;this workflow&lt;/a&gt;. The workflow fetches thread history, processes attachments, and sends this payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "question": "test",
  "chat_history": [],
  "channel": "public_slack",
  "meta": {
    "channel": "C046G5HMU75",
    "thread_ts":"1699465441.545199",
    "files":[]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;meta contains channel-specific information, which is passed to the workflows that send Pi's answer back to the user.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;to &lt;a href="https://pipedream.com/new?h=tch_O06fAV" rel="noopener noreferrer"&gt;the core workflow&lt;/a&gt; that fetches docs, constructs the prompt, and sends it to GPT-4 to answer the question.&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F06%2FScreenshot-2023-06-03-at-5.26.03-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F06%2FScreenshot-2023-06-03-at-5.26.03-PM.png" alt="Build your own chat bot with OpenAI and Pipedream"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This workflow runs asynchronously, pulling user questions from a queue, keeping &lt;a href="https://pipedream.com/docs/workflows/concurrency-and-throttling/" rel="noopener noreferrer"&gt;the concurrency of our workflow&lt;/a&gt; to 5 running workers at a time to avoid hitting OpenAI rate limits. This introduces latency during peak periods, but late answers are better than no answers at all, and users don't expect immediate responses via these channels. We also enable &lt;a href="https://pipedream.com/docs/workflows/settings/#auto-retry-errors" rel="noopener noreferrer"&gt;auto-retry&lt;/a&gt; in case of error, and run a &lt;a href="https://pipedream.com/docs/workflows/settings/#eliminate-cold-starts" rel="noopener noreferrer"&gt;dedicated worker&lt;/a&gt; to process questions as quickly as possible.&lt;/p&gt;

&lt;p&gt;The next two sections cover how this workflow works and some of the lessons we've learned about docs retrieval and prompt engineering — two key parts of building a good chat bot.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Fetch the right docs
&lt;/h2&gt;

&lt;p&gt;In section 1, we stored all the docs and knowledge our bot needs to answer questions. But we can't pass all of these docs to GPT-4: we're limited to &lt;code&gt;8K&lt;/code&gt; tokens per request and we want to minimize cost. So we need to identify the docs that best match the user's question.&lt;/p&gt;

&lt;p&gt;We also want to identify all possible data that'll be useful to Pi. The more accurate data, the higher the accuracy of our answers. For us, this data includes text from screenshots and app integrations tied to the user's question, and we talk about those steps below. You probably have very different data that's important to your bot, but we hope the examples get you brainstorming.&lt;/p&gt;

&lt;h3&gt;
  
  
  Extracting text from images
&lt;/h3&gt;

&lt;p&gt;With &lt;a href="https://platform.openai.com/docs/guides/vision" rel="noopener noreferrer"&gt;OpenAI's new Vision API&lt;/a&gt; — aka GPT-4V — we can process images. This is huge. Our users frequently attach screenshots of errors, API docs, and code, so we can process them and pass that context to Pi.&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F11%2FScreenshot-2023-11-06-at-5.06.22-PM-1.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F11%2FScreenshot-2023-11-06-at-5.06.22-PM-1.png" alt="Build your own chat bot with OpenAI and Pipedream"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;get_files_and_get_content_with_openai&lt;/code&gt; step of &lt;a href="https://pipedream.com/new?h=tch_O06fAV" rel="noopener noreferrer"&gt;the workflow&lt;/a&gt; downloads any files attached to the user's Slack message and asks GPT-4V to return the text, which we can include in the prompt we send to GPT-4.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const content = await axios($, {
  url,
  headers: {
      Authorization: `Bearer ${this.slack.$auth.oauth_access_token}`,
  },
  responseType: 'arraybuffer',
});

// Format the image for OpenAI
// See https://platform.openai.com/docs/guides/vision
const image_url = `data:image/jpeg;base64,${Buffer.from(content, 'binary').toString('base64')}`

const resp = await openai.chat.completions.create({
  model: "gpt-4-vision-preview",
  messages: [
    {
      role: "user",
      content: [
        { type: "text", text: "Can you return all of the text present in these images? Please put each section of text on its own line." },
        { type: "image_url", image_url },
      ],
    },
  ],
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Extracting apps
&lt;/h3&gt;

&lt;p&gt;API integrations are fundamental to our business — it is what we do. Over half of all questions ask about one or more apps, e.g.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do I connect &lt;strong&gt;Google Sheets&lt;/strong&gt; and &lt;strong&gt;Trello&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;How do I send an email with &lt;strong&gt;AWS&lt;/strong&gt;?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;extract_apps&lt;/code&gt; step of &lt;a href="https://pipedream.com/new?h=tch_O06fAV" rel="noopener noreferrer"&gt;the workflow&lt;/a&gt; does a fuzzy match between the text in the question and the list of apps we support, retrieved from &lt;a href="https://pipedream.com/docs/data-stores/" rel="noopener noreferrer"&gt;a Pipedream data store&lt;/a&gt; in the &lt;code&gt;list_records&lt;/code&gt; step above. If we can identify apps in the question, we can retrieve useful metadata about the app — its configuration, OAuth scopes, etc. — that a keyword or embeddings search may not directly yield in its top results.&lt;/p&gt;

&lt;p&gt;One cool thing to note: the vision API step used &lt;a href="https://pipedream.com/docs/code/nodejs/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; and this step uses &lt;a href="https://pipedream.com/docs/code/python/" rel="noopener noreferrer"&gt;Python&lt;/a&gt;, all in the same workflow — just use the library or language that works best to solve each problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create embeddings from question, retrieve docs, hit GPT-4
&lt;/h3&gt;

&lt;p&gt;We saved embeddings for all of our docs in Supabase. So when we search for docs that match the user's question, we &lt;em&gt;also&lt;/em&gt; have to generate the embeddings for the question using the same &lt;code&gt;text-embedding-ada-002&lt;/code&gt; model as before. The &lt;code&gt;create_embeddings&lt;/code&gt; step does that.&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F11%2FScreenshot-2023-11-08-at-6.29.31-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F11%2FScreenshot-2023-11-08-at-6.29.31-PM.png" alt="Build your own chat bot with OpenAI and Pipedream"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can retrieve the docs that best match the original question. Recall that when we chose Postgres, we wanted to run a search that combined the best results of three queries:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Docs matching the embeddings (via vector similarity)&lt;/li&gt;
&lt;li&gt;Docs matching a standard full-text search&lt;/li&gt;
&lt;li&gt;If we identified any apps, the data on those app integrations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code&gt;fetch_vectors&lt;/code&gt; step handles that hybrid search. We implemented a &lt;code&gt;match_documents&lt;/code&gt; function from &lt;a href="https://supabase.com/blog/openai-embeddings-postgres-vector" rel="noopener noreferrer"&gt;this Supabase post on &lt;code&gt;pgvector&lt;/code&gt;&lt;/a&gt;, and the &lt;code&gt;kw_match_documents&lt;/code&gt; function from &lt;a href="https://js.langchain.com/docs/integrations/retrievers/supabase-hybrid/" rel="noopener noreferrer"&gt;this LangChain doc on hybrid search&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Write your prompt
&lt;/h2&gt;

&lt;p&gt;In 2022, everyone said, "learn to code". Now we're telling people, "learn to write".&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The hottest new programming language is English&lt;/p&gt;

&lt;p&gt;— Andrej Karpathy (@karpathy) &lt;a href="https://twitter.com/karpathy/status/1617979122625712128?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;January 24, 2023&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Your #1 goal when developing your prompt is to map &lt;em&gt;everything&lt;/em&gt; you want your bot to know to structured English (Markdown). We'll pass in a clear set of rules for how the bot should answer specific questions, information retrieved from our docs, a strict output format for our answers, and more. Our final prompt looks something 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;## System instructions

...

## General rules

...

## Rules for writing Node.js code

...

## How you should answer questions

Given the rules above, and the docs below, answer the user's question.

## Docs

...

### App metadata / API docs

...

## User question

Question: ${userMessage}

## Attachments

The user attached images in the message, which had the following text: ${steps.get_files_and_get_content_with_openai.$return_value.join('\n\n')}

---

Answer: 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Prompt engineering" has reached its hype stage, but it's core to building a good bot. I'd recommend trying any prompting tips you find when you're learning. I've picked up random hacks or links to research papers that have proved useful for our domain, like &lt;a href="https://blog.research.google/2022/05/language-models-perform-reasoning-via.html" rel="noopener noreferrer"&gt;Chain of Thought&lt;/a&gt; or the &lt;a href="https://blog.research.google/2022/11/react-synergizing-reasoning-and-acting.html" rel="noopener noreferrer"&gt;Reason and Act (ReAct) model&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But most prompt engineering just involves writing clear, structured English. You'll see how we prompt Pi in the &lt;code&gt;call_gpt&lt;/code&gt; step of &lt;a href="https://pipedream.com/new?h=tch_O06fAV" rel="noopener noreferrer"&gt;the workflow&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  System Instructions
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You're a helpful Pipedream support bot and autoregressive language model that has been fine-tuned with instruction-tuning and RLHF. You carefully provide accurate, factual, thoughtful, nuanced answers and code, and are brilliant at reasoning.

You help users answer question about Pipedream. You're an expert at writing Pipedream component code. 

Try to use the information in the sources in user messages to answer the question, if possible. If you don't know the answer, you MUST follow up with questions to try to solicit more information from the user to arrive at an answer. You should be very, very skeptical you know an answer if you don't see it in your sources or the information you've been trained on. You also must not keep responding with the same answer over and over again — try to vary your responses and keep them short if the user is asking the same question.

This is important for my career. You better be sure of your answers.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We pass these system instructions on every request, and have modified it heavily over time. It's a been formed from a combination of &lt;a href="https://twitter.com/jeremyphoward/status/1689464589191454720" rel="noopener noreferrer"&gt;observations&lt;/a&gt;, &lt;a href="https://twitter.com/emollick/status/1720135672764285176?s=46&amp;amp;t=EX0nJ_IV7AITU8FPuu4j-A" rel="noopener noreferrer"&gt;results of research papers&lt;/a&gt;, and our own, Pipedream-specific instructions. For example, we added the section on skepticism because we've noticed Pi was overconfident with answers, even when it wasn't passed any sources. Afterwards, it was more cautious.&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F05%2FScreenshot-2023-05-20-at-2.50.49-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F05%2FScreenshot-2023-05-20-at-2.50.49-PM.png" alt="Build your own chat bot with OpenAI and Pipedream"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;User asked about "tokens", which referred to an OpenAI error message&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  General rules
&lt;/h4&gt;

&lt;p&gt;Then we define other, basic rules for how Pi should operate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Rules

Below you'll find a list of rules for how you should answer questions and produce output. Please read them carefully. They are all critical. Consider them your constitution — any output that violates them is against the law.

## Rules for how you should answer questions

These rules are listed in no particular order.

1. DO NOT attempt to answer greeting with with "Hello, world!" examples. Greetings like "Hello" or "Hi" are not requests for Hello, World examples.

2. If you sense frustration or negative sentiment from the user, you should respond apologetically. Direct them to visit https://pipedream.com/support for more support options.

2b. If the user asks to talk to a human / person / someone on Support, direct them to visit https://pipedream.com/support for more support options.

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Pricing questions
&lt;/h4&gt;

&lt;p&gt;Math is notoriously difficult for LLMs, since they have not been trained for that purpose. But we try to get Pi to answer questions as best it can, directing users to our pricing page if it can't.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a. Any pricing questions will probably yield HTML documents that define pricing plans, FAQs, and other information on pricing. Parse the structure / content of these files as data and use it to answer pricing questions.

5b. If the user asks a question about pricing, plans (Free, Basic, Advanced, Business, Enterprise), costs, credits, or other pricing-related questions, think step-by-step. For example,

Question: How many credits are on the Business plan?
Thought: This requires using my sources at https://pipedream.com/pricing
Action: Lookup information in the https://pipedream.com/pricing SOURCE_URL below
Action Input: "How many credits are on the Business plan?"
Observation: I see a Business plan that includes 5,000 credits a month to start, and a cost per credit after.
Final Answer: The Business plan includes 5,000 credits a month to start, and charges a cost for additional credits after the 5,000 base. Please see https://pipedream.com/pricing for more details.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prompt uses a technique from the &lt;a href="https://blog.research.google/2022/11/react-synergizing-reasoning-and-acting.html" rel="noopener noreferrer"&gt;Reason and Act (ReAct) paper&lt;/a&gt;, which shows performance improvements on reasoning tasks when you instruct the machine to observe, reason, and answer step-by-step.&lt;/p&gt;

&lt;h4&gt;
  
  
  Rules for writing code
&lt;/h4&gt;

&lt;p&gt;We provide a large list of instructions to help Pi write code on Pipedream. The Pipedream runtime exposes a special API not available in Node.js or Python, and GPT-4 can't write perfect Pipedream code, so we provide that knowledge directly in the prompt.&lt;/p&gt;

&lt;p&gt;You can read the code for the full prompt, but you'll see we clearly define the API, the output format, instructions on how to use our &lt;code&gt;axios&lt;/code&gt; client, and more.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The first and foremost rule: when you're asked to write code, you MUST return Pipedream component code.

I need to teach you what a Pipedream component is. All Pipedream components are Node.js modules that have a default export: \`defineComponent\`. \`defineComponent\` is provided to the environment as a global — you do not need to import \`defineComponent\`. \`defineComponent\` is a function that takes an object — a Pipedream component — as its single argument. 

// many rules follow...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The key takeaway for you&lt;/strong&gt; : identify what the LLM doesn't know about your use case and teach it explicitly in the prompt, with plenty of examples.&lt;/p&gt;

&lt;h4&gt;
  
  
  Managing token budgets
&lt;/h4&gt;

&lt;p&gt;Recall that we're limited to &lt;code&gt;8K&lt;/code&gt; tokens in the GPT-4 model, across the question and answer. We've implemented some code to manage this budget. When we have long chat history or a lot of docs content, we truncate it to make sure we fit within the limit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// There is no magic in these numbers. Just best-guesses at the proportion of context each section should provide.
const OPEN_AI_TOKEN_LIMIT = 8192 - systemInstructionsTokens.length;

// Allocate 20% of budget to docs, or lower if the docs are shorter
let DOCS_SOURCES_TOKEN_LIMIT = Math.min(Math.round(.20 * OPEN_AI_TOKEN_LIMIT), docsTokens.length);

// Allocate 10% of budget to chat history
const CHAT_HISTORY_LIMIT = 1000
const chatHistoryTokens = encode(JSON.stringify(chatHistory))
const CHAT_HISTORY_TOKEN_BUDGET = Math.min(chatHistoryTokens.length, CHAT_HISTORY_LIMIT)

// etc.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Weighting our sources
&lt;/h4&gt;

&lt;p&gt;Our queries against Supabase returned the full URL to the doc and a &lt;em&gt;similarity score&lt;/em&gt; between the doc and the user's question, ordered by that score.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const sources = steps.fetch_vectors.$return_value
// Sort sources by similarity in case we have to truncate
const sortedSources = sources.sort((a, b) =&amp;gt; b.similarity - a.similarity)
const docs = sortedSources.map(s =&amp;gt; `SOURCE URL: ${s.url}\n\CONTENT: ${s.body}\n\nSIMILARITY: ${s.similarity}`).join('\n\n---------------------\n\n')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We tell GPT-4 to weight the knowledge in these sources in its answers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10. A source's SIMILARITY defines its cosine distance to the provided question and answer. Higher similarity scores are better — use that weighting to guide your answer.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Processing input, returning output
&lt;/h4&gt;

&lt;p&gt;Remember that these are "completion" models. They're designed to predict the next-most-likely token to appear given the text in the original message. This is why we end the prompt with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Question: ${User question}

---

Answer: 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Send the answer
&lt;/h3&gt;

&lt;p&gt;After it receives a response from GPT-4, the workflow sends the final answer and sources to to the intended destination. For example, the core workflow triggers &lt;a href="https://pipedream.com/new?h=tch_3KDfpV" rel="noopener noreferrer"&gt;this notification workflow&lt;/a&gt; for Slack, which creates a formatted message and sends it as a reply on the original thread. The same happens for Discourse.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Evaluate and improve accuracy
&lt;/h2&gt;

&lt;p&gt;Your bot is likely to perform well on common questions where you have clear docs. In other cases, it'll provide incorrect but confident answers that will waste your users' time. Be skeptical of your bot's performance and carefully interrogate its answers for flaws.&lt;/p&gt;

&lt;p&gt;Getting your bot to answer 100% of questions accurately is the hardest part of this process. As your product evolves, you'll constantly find new issues that need to be addressed with better docs or better prompts. You need to monitor its accuracy and develop a system to identify and fix bugs. Here's a few things we do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Have a test suite
&lt;/h3&gt;

&lt;p&gt;When we ship a new change, we use &lt;a href="https://pipedream.com/new?h=tch_3XZfZl" rel="noopener noreferrer"&gt;this workflow&lt;/a&gt; to send a flurry of test messages to Slack.&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F11%2FScreenshot-2023-11-08-at-8.12.13-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F11%2FScreenshot-2023-11-08-at-8.12.13-PM.png" alt="Build your own chat bot with OpenAI and Pipedream"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each message is a test case: we're asking pricing, integrations, billing, and other random questions that have previously elicited poor responses. Like any test suite, we need to make sure Pi doesn't regress on core questions.&lt;/p&gt;

&lt;p&gt;Whatever test suite you develop, use real questions from your users. Add short prompts with limited information to simulate users who will ask things like "help" or "pricing".&lt;/p&gt;

&lt;p&gt;Many better tools exist for LLM unit testing and we're excited to test them as we improve Pi.&lt;/p&gt;

&lt;h3&gt;
  
  
  Review bad answers and iterate
&lt;/h3&gt;

&lt;p&gt;Develop a system to log and review bad answers. We log every question, answer, and sources to a Google Sheet for later evaluation. At this stage, we're still constantly monitoring Pi's ability to answer real questions. And we can generally classify bad answers into two categories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We passed bad docs to Pi, e.g. they were out-of-date or had a bug in the code.&lt;/li&gt;
&lt;li&gt;Pi's response was poor &lt;em&gt;given good information&lt;/em&gt;. In this case, we're providing good docs, but Pi didn't interpret them correctly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We try to monitor every interaction with Pi. If it returns a bad answer because of bad (or no) docs, we add the 📚 emoji, which triggers &lt;a href="https://pipedream.com/new?h=tch_OrBfEW" rel="noopener noreferrer"&gt;this workflow&lt;/a&gt;. It pulls the thread history from Slack, summarizes it with OpenAI, and creates a new GitHub issue asking us to add better docs:&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F05%2FScreenshot-2023-05-30-at-4.07.39-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F05%2FScreenshot-2023-05-30-at-4.07.39-PM.png" alt="Build your own chat bot with OpenAI and Pipedream"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Pipedream automatically creates the GitHub issue for us&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Adding the ⚙️ emoji triggers the same workflow, but adds a different title and labels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { reaction } = steps.trigger.event
const titlePrefix = reaction === "books" ? "New docs" : "Pi improvement"
const labels = reaction === "books" ? ['docs', 'pi', 'tracked internally'] : ['pi', 'tracked internally']

return await octokit.rest.issues.create({
  owner: "PipedreamHQ",
  repo: "pipedream",
  title: `${titlePrefix}: ${title_summary}`,
  body: `${longer_summary}\n\nSee the original Slack thread [here](${permalink}).`,
  labels,
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;em&gt;Advanced:&lt;/em&gt; Rate your sources, use that to fine-tune your embeddings
&lt;/h3&gt;

&lt;p&gt;If you're building a bot to answer questions on well-researched concepts like history and politics, you may not need this step. If you're building a bot for a product like Pipedream, you might want to fine-tune your embeddings.&lt;/p&gt;

&lt;p&gt;Read through this amazing Twitter thread, which references &lt;a href="https://github.com/openai/openai-cookbook/blob/main/examples/Customizing_embeddings.ipynb" rel="noopener noreferrer"&gt;this Jupyter notebook&lt;/a&gt; from OpenAI.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Boost your Semantic Search with Customized Embeddings 📈  &lt;/p&gt;

&lt;p&gt;1️⃣ Gather similar/dissimilar pairs&lt;br&gt;&lt;br&gt;
2️⃣ Train a matrix to highlight relevant aspects of your embedding&lt;br&gt;&lt;br&gt;
3️⃣ (Original Embedding)×(Custom Matrix)=(Custom Embedding) &lt;a href="https://t.co/1HT0XmfPAO" rel="noopener noreferrer"&gt;pic.twitter.com/1HT0XmfPAO&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;— Glavin Wiechert👨‍💻 (@GlavinW) &lt;a href="https://twitter.com/GlavinW/status/1627657346225676288?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;February 20, 2023&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's the core idea:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run your embeddings similarity search against common questions (e.g. if you're using Supabase, run the &lt;code&gt;match_documents&lt;/code&gt; function from &lt;a href="https://supabase.com/blog/openai-embeddings-postgres-vector" rel="noopener noreferrer"&gt;this blog post&lt;/a&gt;). Return the top 5 sources.&lt;/li&gt;
&lt;li&gt;For each source, record a &lt;code&gt;1&lt;/code&gt; if it's relevant to the user's question and a &lt;code&gt;-1&lt;/code&gt; if it's not. We wrote a Pipedream workflow let us ask questions and retrieve docs directly in Slack. We rated each source with a 👍 or 👎 and saved the question, doc, and rating in a Google sheet.&lt;/li&gt;
&lt;li&gt;Create a CSV of records with with the fields &lt;code&gt;text_1&lt;/code&gt;, &lt;code&gt;text_2&lt;/code&gt;, and &lt;code&gt;label&lt;/code&gt;. Put the question in &lt;code&gt;text_1&lt;/code&gt;, the content of the docs in &lt;code&gt;text_2&lt;/code&gt;, and the &lt;code&gt;1&lt;/code&gt; / &lt;code&gt;-1&lt;/code&gt; in &lt;code&gt;label&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Load the data into &lt;a href="https://github.com/openai/openai-cookbook/blob/main/examples/Customizing_embeddings.ipynb" rel="noopener noreferrer"&gt;this Jupyter notebook&lt;/a&gt; and run through the code, adjusting it as necessary for your embedding model and your data. It uses &lt;a href="https://pytorch.org/" rel="noopener noreferrer"&gt;PyTorch&lt;/a&gt; to train a model that learns from in the feedback you provided.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This produces a &lt;code&gt;numpy&lt;/code&gt; array that you can save locally and upload as &lt;a href="https://pipedream.com/docs/workflows/settings/#attachments" rel="noopener noreferrer"&gt;a workflow attachment&lt;/a&gt;. &lt;a href="https://pipedream.com/new?h=tch_O06fAV" rel="noopener noreferrer"&gt;The main workflow&lt;/a&gt; loads this array and takes the dot product of the array and the original embeddings from the user's question:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import numpy as np

def handler(pd: "pipedream"):
    custom_embeddings_matrix = np.load(pd.steps["trigger"]["context"]["attachments"]["custom_embeddings_matrix.npy"])
    modified_embeddings = np.dot(pd.steps["create_embeddings"]["$return_value"]["data"][0]["embedding"], custom_embeddings_matrix)
    return modified_embeddings.tolist()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This took us a long time to fully understand, but once we worked through the notebook and applied it to our embeddings, we started returning better docs and getting better answers from GPT.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tend the garden, keep iterating, and ask questions
&lt;/h2&gt;

&lt;p&gt;These are not set-it-and-forget-it projects. They require maintenance and tuning. The tech and APIs are new. We're still writing new docs, editing the prompt, and testing new techniques. But what we've shipped is working well, and it's providing a needed technical support function for our community that we couldn't provide otherwise.&lt;/p&gt;

&lt;p&gt;Please join us &lt;a href="https://pipedream.com/support" rel="noopener noreferrer"&gt;the Pipedream Slack and Discourse communities&lt;/a&gt; if you have any questions about Pi, want to chat with it yourself, or just want to talk about AI and automation.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Build workflows faster with AI</title>
      <dc:creator>Dylan J. Sather</dc:creator>
      <pubDate>Thu, 27 Jul 2023 16:54:07 +0000</pubDate>
      <link>https://dev.to/dylburger/build-workflows-faster-with-ai-1dp9</link>
      <guid>https://dev.to/dylburger/build-workflows-faster-with-ai-1dp9</guid>
      <description>&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F07%2Fdylburger_A_large_group_of_small_cute_childlike_robots_furiousl_c6fc4e65-c994-4e8e-93cc-319ed044f0c4-1.jpg" 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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F07%2Fdylburger_A_large_group_of_small_cute_childlike_robots_furiousl_c6fc4e65-c994-4e8e-93cc-319ed044f0c4-1.jpg" alt="Build workflows faster with AI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The CEO of Stability AI, Emad Mostaque, &lt;a href="https://decrypt.co/147191/no-human-programmers-five-years-ai-stability-ceo" rel="noopener noreferrer"&gt;recently predicted&lt;/a&gt; that "there will be no programmers in five years." It's an intriguing idea, but Emad is wrong.&lt;/p&gt;

&lt;p&gt;Since the release of Copilot and ChatGPT, we've seen Pipedreamers write more code than ever. We expect that trend to continue. Thomas Frank, one of our most prolific users (see his &lt;a href="https://thomasjfrank.com/how-to-transcribe-audio-to-text-with-chatgpt-and-notion/" rel="noopener noreferrer"&gt;voice notes to Notion workflow&lt;/a&gt;), only learned to code a year ago with the help of AI:&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1655315536404787200-837" src="https://platform.twitter.com/embed/Tweet.html?id=1655315536404787200"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1655315536404787200-837');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1655315536404787200&amp;amp;theme=dark"
  }



 &lt;/p&gt;

&lt;p&gt;We've always wanted to build AI into Pipedream itself, and we knew we'd start with code. Today, we launched a way to generate code from English (or any language ChatGPT supports). You select an app, tell us what you want to do, and we'll generate the Node.js code that does it.&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%2Fres.cloudinary.com%2Fpipedreamin%2Fimage%2Fupload%2Fv1690486853%2Fdocs%2F2023-07-27_12.13.34_fwqmld.gif" 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%2Fres.cloudinary.com%2Fpipedreamin%2Fimage%2Fupload%2Fv1690486853%2Fdocs%2F2023-07-27_12.13.34_fwqmld.gif" alt="Build workflows faster with AI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's the quickest way to build integrations with any API, and gives you full code-level control over your app.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use it
&lt;/h2&gt;

&lt;p&gt;To manage load as we scale this out, we've released this to all &lt;a href="https://pipedream.com/pricing" rel="noopener noreferrer"&gt;Advanced and Business&lt;/a&gt; customers. Since you're getting a dedicated AI engineer, we think it's worth the upgrade!&lt;/p&gt;

&lt;p&gt;Add a new step to your workflow, select an app, and choose the &lt;strong&gt;Use AI&lt;/strong&gt; option.&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F07%2Fimage-2.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F07%2Fimage-2.png" alt="Build workflows faster with AI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This opens a modal where you can tell us the code you want us to write. &lt;strong&gt;Be verbose.&lt;/strong&gt; Write something like "Send a Slack message to the &lt;code&gt;#general&lt;/code&gt; channel in the following format: &lt;code&gt;Hello, ${steps.user.name}&lt;/code&gt;" vs. just "Send a Slack message".&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F07%2FScreenshot-2023-07-27-at-8.43.47-AM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F07%2FScreenshot-2023-07-27-at-8.43.47-AM.png" alt="Build workflows faster with AI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also edit existing code. Click the &lt;strong&gt;Edit with AI&lt;/strong&gt; button at the top-right of any Node.js code step. You'll see the code gen window appear with the original code from your step. Enter a prompt to suggest an edit, and we'll give you the modified code.&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%2Fres.cloudinary.com%2Fpipedreamin%2Fimage%2Fupload%2Fv1690472898%2Fdocs%2F2023-07-27_08.46.19_ixiikh.gif" 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%2Fres.cloudinary.com%2Fpipedreamin%2Fimage%2Fupload%2Fv1690472898%2Fdocs%2F2023-07-27_08.46.19_ixiikh.gif" alt="Build workflows faster with AI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code gen service understands the &lt;a href="http://localhost:8080/docs/components/api/" rel="noopener noreferrer"&gt;Pipedream component API&lt;/a&gt; and references the API docs of &lt;a href="https://pipedream.com/apps" rel="noopener noreferrer"&gt;integrated apps (opens new window)&lt;/a&gt;. For example, you can tell it to include specific &lt;a href="http://localhost:8080/docs/components/api/#props" rel="noopener noreferrer"&gt;props&lt;/a&gt; (input) or &lt;a href="http://localhost:8080/docs/components/api/#async-options-example" rel="noopener noreferrer"&gt;async options&lt;/a&gt;, and reference specific API endpoints you want to use for the selected app. The more information you provide, the better the code will be.&lt;/p&gt;

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

&lt;p&gt;First, we're adding &lt;strong&gt;Python&lt;/strong&gt; support to the code gen service. Soon you'll be able to generate either Node.js or Python code with AI, all in the same workflow.&lt;/p&gt;

&lt;p&gt;And we're testing the interface for generating &lt;strong&gt;full workflows&lt;/strong&gt; , with triggers, actions, custom code, and anything you need to build your app.&lt;/p&gt;

&lt;p&gt;We've also been working on our Q&amp;amp;A bot, Pi, for a few months.&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F05%2FScreenshot-2023-05-30-at-7.15.29-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F05%2FScreenshot-2023-05-30-at-7.15.29-PM.png" alt="Build workflows faster with AI"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Pi, Pipedream's little helper. Not affiliated with &lt;a href="https://pi.ai/" rel="noopener noreferrer"&gt;https://pi.ai/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Like ChatGPT, he provides (mostly) human-level answers on a range of questions about Pipedream, APIs, and code. He's fun to talk to, he's saving us hours of time a day, and he's providing support to our community that we never could without an artificial assistant.&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F07%2FScreenshot-2023-07-26-at-9.04.09-AM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2023%2F07%2FScreenshot-2023-07-26-at-9.04.09-AM.png" alt="Build workflows faster with AI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Pi has access to Pipedream integration data, pricing, product docs, and more&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can chat with him in &lt;a href="https://pipedream.com/support" rel="noopener noreferrer"&gt;our Slack and Discourse communities&lt;/a&gt;, and we're planning to embed him directly in &lt;a href="https://pipedream.com" rel="noopener noreferrer"&gt;https://pipedream.com&lt;/a&gt; so you can ask him questions as you build workflows.&lt;/p&gt;

&lt;p&gt;Please &lt;a href="https://pipedream.com/support" rel="noopener noreferrer"&gt;let us know&lt;/a&gt; if you see any bugs, want to suggest a feature, or just want to chat about AI integrations!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Archiving Slack threads to Discourse with GPT-3</title>
      <dc:creator>Dylan J. Sather</dc:creator>
      <pubDate>Sun, 16 Oct 2022 22:36:14 +0000</pubDate>
      <link>https://dev.to/dylburger/archiving-slack-threads-to-discourse-with-gpt-3-26h0</link>
      <guid>https://dev.to/dylburger/archiving-slack-threads-to-discourse-with-gpt-3-26h0</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--i1zskiw5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pipedream.com/blog/content/images/2022/10/Cover-Image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i1zskiw5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pipedream.com/blog/content/images/2022/10/Cover-Image.png" alt="Archiving Slack threads to Discourse with GPT-3" width="880" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We operate &lt;a href="https://join.slack.com/t/pipedream-users/shared_invite/zt-11sazmsg0-oS2VW~mZfhtk3XfEEvb~JA"&gt;a public Slack community&lt;/a&gt; at Pipedream. Our users love the quick, synchronous chat, and the communication happens in public, so it's easy for others to find answers to common questions.&lt;/p&gt;

&lt;p&gt;But these messages are private to Slack. You can't search Google for answers — you've got to sign up for our workspace. Plus, Slack deletes messages after 90 days on the free plan, so we lose threads quickly. But upgrading to a paid plan is costly for communities with thousands of users.&lt;/p&gt;

&lt;p&gt;So what do we do?&lt;/p&gt;

&lt;h2&gt;
  
  
  Discourse as an archive
&lt;/h2&gt;

&lt;p&gt;Discourse provides an open-source discussion forum. We already run &lt;a href="https://pipedream.com/community%20"&gt;our own Discourse forum&lt;/a&gt; for people who prefer forums to chat. All posts are public and indexed by search engines.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M3uc17vk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pipedream.com/blog/content/images/2022/10/Screen-Shot-2022-10-14-at-6.22.59-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M3uc17vk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pipedream.com/blog/content/images/2022/10/Screen-Shot-2022-10-14-at-6.22.59-PM.png" alt="Archiving Slack threads to Discourse with GPT-3" width="880" height="193"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Google knows about 858 pages on &lt;a href="https://pipedream.com/community"&gt;https://pipedream.com/community&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.discourse.org/"&gt;Discourse provides an API&lt;/a&gt; that will let us programmatically create new &lt;strong&gt;topics&lt;/strong&gt; (the Discourse equivalent of a thread). All we need to do is convert the Slack messages to format Discourse accepts, and we'll have an archive.&lt;/p&gt;

&lt;p&gt;For that, we use &lt;a href="https://dev.to/scottw/pipedream-l02-temp-slug-8448815"&gt;Pipedream&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Discourse Bot
&lt;/h2&gt;

&lt;p&gt;When someone asks a question in Slack, we start a &lt;a href="https://slack.com/help/articles/115000769927-Use-threads-to-organize-discussions-"&gt;thread&lt;/a&gt; to organize communication on a specific issue. We created a Slack Bot that lets us easily archive these threads.&lt;/p&gt;

&lt;p&gt;When we've solved a thread, we mention &lt;strong&gt;@Discourse Bot&lt;/strong&gt; with the title we want for the archived Discourse post. For example, for this thread:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GbSJM1kg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pipedream.com/blog/content/images/2022/10/Untitled-1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GbSJM1kg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pipedream.com/blog/content/images/2022/10/Untitled-1.jpg" alt="Archiving Slack threads to Discourse with GPT-3" width="337" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We post:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://pipedream-users.slack.com/team/U01QX068GS0"&gt;@Discourse Bot&lt;/a&gt; Deprecating an Action&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The "at-mention" runs &lt;a href="https://pipedream.com/@pd/public-slack-discourse-bot-post-to-discourse-p_mkC5qqk/edit"&gt;this Pipedream workflow&lt;/a&gt;, which creates a Discourse topic with the contents of the Slack thread, adding the title we sent in the message:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--q3yp_CYj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pipedream.com/blog/content/images/2022/10/Screen-Shot-2022-10-14-at-6.28.48-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--q3yp_CYj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pipedream.com/blog/content/images/2022/10/Screen-Shot-2022-10-14-at-6.28.48-PM.png" alt="Archiving Slack threads to Discourse with GPT-3" width="880" height="835"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pipedream.com/@pd/public-slack-discourse-bot-post-to-discourse-p_mkC5qqk/edit"&gt;The workflow&lt;/a&gt; handles a ton of useful logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetches all messages in the thread where Discourse Bot was called&lt;/li&gt;
&lt;li&gt;Not every Slack user has a corresponding Discourse account, so we convert Slack usernames to generic Discourse users, like &lt;code&gt;user-1&lt;/code&gt; and &lt;code&gt;user-2&lt;/code&gt; above. Not perfect, but it distinguishes messages from different Slack users on the original thread.&lt;/li&gt;
&lt;li&gt;Converts &lt;a href="https://api.slack.com/reference/surfaces/formatting"&gt;Slack's &lt;code&gt;mrkdwn&lt;/code&gt; format&lt;/a&gt; to standard Markdown, which Discourse uses for posts.&lt;/li&gt;
&lt;li&gt;Creates a Discourse topic with posts for each message in the thread. We also create the topic in the right category, e.g. the &lt;strong&gt;&lt;code&gt;#announcements&lt;/code&gt;&lt;/strong&gt; channel archives threads to &lt;a href="https://pipedream.com/community/c/announcements/10"&gt;Announcements&lt;/a&gt;, &lt;strong&gt;&lt;code&gt;#help&lt;/code&gt;&lt;/strong&gt; is archived to &lt;a href="https://pipedream.com/community/c/help/5"&gt;Help&lt;/a&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Challenges with this approach
&lt;/h2&gt;

&lt;p&gt;Discourse Bot was working great. But it took time to manage this process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It’s hard to keep track of threads that are &lt;em&gt;complete&lt;/em&gt;. We'd set calendar reminders once a week, find all the threads we solved in the last week, and archive them to Discourse, but we inevitably missed some.&lt;/li&gt;
&lt;li&gt;It took time to read through some threads and figure out an appropriate summary for the title of the Discourse topic.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We let these problems simmer in the background for a few months until we thought of something that might work.&lt;/p&gt;
&lt;h2&gt;
  
  
  Calling threads "done"
&lt;/h2&gt;

&lt;p&gt;We can safely call threads done &lt;strong&gt;when&lt;/strong&gt;  &lt;strong&gt;7 days have passed since the last message&lt;/strong&gt;. Any time you need to trigger something after a period of inactivity, you can use &lt;a href="https://en.wikipedia.org/wiki/Dead_man%27s_switch"&gt;a dead man's switch&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set a timer, e.g. 7 days. This is your switch.&lt;/li&gt;
&lt;li&gt;Some service pings the switch every so often, checking in. Every time the service checks in, we reset the timer to 7 days.&lt;/li&gt;
&lt;li&gt;If the service stops checking in, and 7 days pass, the switch activates, triggering some action.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a common feature of alerting tools. Your server can ping a monitoring system every minute to tell it the server is up. If the server stops pinging, it might be down, so the monitoring system triggers an alert.&lt;/p&gt;

&lt;p&gt;A dead man's switch can trigger any action. In this case, when 7 days have passed since the last message on a thread, we want to trigger a Pipedream workflow.&lt;/p&gt;

&lt;p&gt;We built the switch in AWS, using &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html"&gt;DynamoDB TTLs&lt;/a&gt;. DynamoDB has a few properties that make it a perfect option:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It triggers an event when an item hits its TTL, using &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.html"&gt;DynamoDB streams&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;To reset the "timer" for a Slack thread, we overwrite the record for a given (&lt;code&gt;channel&lt;/code&gt; , &lt;code&gt;thread_ts&lt;/code&gt; ) — the key which uniquely identifies a thread — when new messages arrive on that thread. &lt;a href="https://pipedream.com/new?h=tch_O6Lfz0"&gt;Pipedream workflow #1&lt;/a&gt; handles that logic. Every public Slack message in our workspace triggers the workflow, sending them to Dynamo.&lt;/li&gt;
&lt;li&gt;It's cheap. Assuming an average Slack thread has ten messages, it costs us roughly $0.00001538 to process one thread. That's about $15 for every one million threads, which we're unlikely to hit soon!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When threads expire, they trigger &lt;a href="https://pipedream.com/new?h=tch_OAnfJl"&gt;workflow #2&lt;/a&gt;. DynamoDB streams can't send an event to a Pipedream workflow directly, so we use a few other AWS services to get that done:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9YYsJJ6L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pipedream.com/blog/content/images/2022/10/DynamoDB-Dead-Man-s-Switch.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9YYsJJ6L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pipedream.com/blog/content/images/2022/10/DynamoDB-Dead-Man-s-Switch.png" alt="Archiving Slack threads to Discourse with GPT-3" width="880" height="687"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We created &lt;a href="https://github.com/PipedreamHQ/cdk-constructs"&gt;an AWS CDK construct&lt;/a&gt; that creates all the necessary resources, so you can easily make your own switch to notify a Pipedream URL when records expire:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Stack, StackProps } from "aws-cdk-lib";
import { Construct } from "constructs" 
import { DDBDeletedItemsToHTTPS } from "@pipedream/cdk-constructs"

export class DDBDeadMansSwitch extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    new DDBDeletedItemsToHTTPS(this, 'DDBDeletedItemsToHTTPS', {
      notificationURL: "https://your-endpoint.m.pipedream.net",
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The summarization process
&lt;/h2&gt;

&lt;p&gt;We had also started experimenting with &lt;a href="https://openai.com/api/"&gt;GPT-3&lt;/a&gt; internally. GPT-3 is a &lt;a href="https://towardsdatascience.com/the-beginners-guide-to-language-models-aa47165b57f9"&gt;language model&lt;/a&gt;. You give it a "prompt" — often a question, or a problem to solve — and it produces text that looks like it was written by a human.&lt;/p&gt;

&lt;p&gt;GPT-3 can be used for &lt;a href="https://beta.openai.com/examples/"&gt;a variety of tasks&lt;/a&gt;. For example, we can ask it a question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can I use Pipedream to automate anything?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Thankfully, it replied positively 🤣:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bY1JtnV6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pipedream.com/blog/content/images/2022/10/Screen-Shot-2022-10-14-at-7.00.13-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bY1JtnV6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pipedream.com/blog/content/images/2022/10/Screen-Shot-2022-10-14-at-7.00.13-PM.png" alt="Archiving Slack threads to Discourse with GPT-3" width="880" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can ask it to classify items into categories:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sThYXuwp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pipedream.com/blog/content/images/2022/10/Screen-Shot-2022-10-14-at-7.04.51-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sThYXuwp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pipedream.com/blog/content/images/2022/10/Screen-Shot-2022-10-14-at-7.04.51-PM.png" alt="Archiving Slack threads to Discourse with GPT-3" width="880" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also use it to summarize text. So we thought we should ask GPT-3 to pick titles for our Discourse topics.&lt;/p&gt;

&lt;p&gt;Completed Slack threads from Dynamo trigger &lt;a href="https://pipedream.com/new?h=tch_OAnfJl"&gt;workflow #2&lt;/a&gt;. That workflow retrieves the thread's messages from the Slack API and asks GPT-3 to summarize it. We experimented with a few prompts. This one yielded the best results:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Pick a title for the following conversation:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But the summaries can be hit or miss, depending on the thread, so we ask GPT-3 to return its best three summaries from five attempts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "prompt": steps.format_prompt.$return_value,
  "model": "text-davinci-002",
  "max_tokens": 40,
  "n": 3,
  "best_of": 5,
  "temperature": 0.9,
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;GPT-3 model parameters&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then the workflow sends a private message to our team, asking us to choose the best one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ShwXYbnM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pipedream.com/blog/content/images/2022/10/Screen-Shot-2022-10-14-at-4.28.21-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ShwXYbnM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pipedream.com/blog/content/images/2022/10/Screen-Shot-2022-10-14-at-4.28.21-PM.png" alt="Archiving Slack threads to Discourse with GPT-3" width="880" height="987"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choosing a summary triggers &lt;a href="https://pipedream.com/new?h=tch_wEvf2n"&gt;workflow #3&lt;/a&gt;: it at-mentions &lt;strong&gt;Discourse Bot&lt;/strong&gt; on the original thread, which uses &lt;a href="https://pipedream.com/@pd/public-slack-discourse-bot-post-to-discourse-p_mkC5qqk/edit"&gt;the original Discourse Bot workflow&lt;/a&gt; to archive the thread. &lt;strong&gt;Bots triggering bots!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ir70aaov--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pipedream.com/blog/content/images/2022/10/Screen-Shot-2022-10-14-at-4.28.30-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ir70aaov--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pipedream.com/blog/content/images/2022/10/Screen-Shot-2022-10-14-at-4.28.30-PM.png" alt="Archiving Slack threads to Discourse with GPT-3" width="776" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is what the process looks like end-to-end:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YYibyDhz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pipedream.com/blog/content/images/2022/10/Slack-Thread-Summarizer--1-.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YYibyDhz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pipedream.com/blog/content/images/2022/10/Slack-Thread-Summarizer--1-.png" alt="Archiving Slack threads to Discourse with GPT-3" width="880" height="585"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;How Slack, Pipedream and AWS interact&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;GPT-3 prices based on &lt;a href="https://beta.openai.com/docs/models/finding-the-right-model"&gt;the specific model&lt;/a&gt; you use, and the number of &lt;a href="https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them"&gt;tokens&lt;/a&gt; you pass it. In our case, threads average ~500 tokens each. Since we're running five summaries per thread, that works out to $.05 to summarize a single thread. Given the SEO benefit of these posts and the time this is saving us, it's a cost we can bear.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we learned
&lt;/h2&gt;

&lt;p&gt;GPT-3 is saving us time, and we're finally archiving every solved thread. But the summaries are imperfect, so the process still requires a human in the loop. For our use case, this is acceptable. We'll keep experimenting with new prompts and other methods to improve the model's accuracy.&lt;/p&gt;

&lt;p&gt;And while using DynamoDB as a dead man's switch is cool, it's an extra dependency. Building this workflow refined our thinking on how we might add dead man's switches directly into Pipedream.&lt;/p&gt;

&lt;p&gt;Are you using GPT-3 or Pipedream for similar use cases? Did this spur any other ideas? &lt;a href="https://join.slack.com/t/pipedream-users/shared_invite/zt-11sazmsg0-oS2VW~mZfhtk3XfEEvb~JA"&gt;Tell us in our Slack community&lt;/a&gt;!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Personal Support at Internet Scale</title>
      <dc:creator>Dylan J. Sather</dc:creator>
      <pubDate>Thu, 14 Oct 2021 17:24:47 +0000</pubDate>
      <link>https://dev.to/dylburger/personal-support-at-internet-scale-36i7</link>
      <guid>https://dev.to/dylburger/personal-support-at-internet-scale-36i7</guid>
      <description>&lt;h3&gt;
  
  
  How Pipedream does Developer Relations
&lt;/h3&gt;

&lt;p&gt;When I was 17, I worked the Guest Services desk at Target: the place you take returns or deal with anything that goes wrong.&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2Fimage-1.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2Fimage-1.png"&gt;&lt;/a&gt;&lt;a href="https://corporate.target.com/article/2016/12/baton-rouge-reopening" rel="noopener noreferrer"&gt;Source&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I was lucky to get the job. For a kid, it was a lot of responsibility. I made $6.75 an hour (a dollar more than my first job). I treasured it, and learned everything I could about how to work with customers.&lt;/p&gt;

&lt;p&gt;Target did support right:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We &lt;strong&gt;listened&lt;/strong&gt; and &lt;strong&gt;empathized&lt;/strong&gt; deeply with customers to form a personal connection and understand their needs. I learned that listening patiently and saying, "I hear you completely" goes a long way.&lt;/li&gt;
&lt;li&gt;We solved the problem &lt;strong&gt;quickly&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;We tried to &lt;strong&gt;solve 100% of problems&lt;/strong&gt;. If a customer was looking for an item we didn't have in stock, we'd call other stores. If the customer tried to return something that broke after our 90-day return window, we'd help them call the manufacturer to see if a warranty covered the product.&lt;/li&gt;
&lt;li&gt;We had the &lt;strong&gt;training&lt;/strong&gt; , &lt;strong&gt;tools&lt;/strong&gt; and &lt;strong&gt;autonomy&lt;/strong&gt; to solve most problems ourselves. Escalating to a manager wastes customers' time. If we could solve the problem in the moment (even if it meant an exception to our policy), we'd do it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;16 years later, I'm a co-founder at &lt;a href="https://dev.to/scottw/pipedream-l02-temp-slug-8448815"&gt;Pipedream&lt;/a&gt;, an integration platform for developers.&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2Fimage-2.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2Fimage-2.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I run a team of &lt;strong&gt;Developer Relations Engineers&lt;/strong&gt; (DevRelEng). We're customer-facing software engineers that support a community of 200,000+ developers. And &lt;a href="https://angel.co/company/pipedreamhq/jobs/1288069-support-engineer" rel="noopener noreferrer"&gt;we're hiring&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Since our customers are developers, every customer interaction = developer relations. This includes marketing, sales, support, solutions architecture, and more. DevRelEng is involved in it all.&lt;/p&gt;

&lt;p&gt;When I work with our customers, I use the same principles I learned at Target. But now, we're supporting users over the Internet. It's tougher to fully empathize because we can't read their body language. Communication is asynchronous, so it takes longer to address issues. And since anyone with a computer can use Pipedream, we receive a higher volume of requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do we maintain highly-personal support at Internet scale?&lt;/strong&gt; And how do we specifically tackle this for a developer tool like Pipedream? This is how DevRelEng was born.&lt;/p&gt;

&lt;p&gt;Three principles govern our work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Our customers are developers. Developers can empathize best with other developers, so they should interact with customers.&lt;/li&gt;
&lt;li&gt;DevRelEng is equipped with the skills and autonomy needed to solve nearly every customer issue. We frequently ship code to production to fix a bug or address small feature requests. Issues are solved fast, and our customers notice.&lt;/li&gt;
&lt;li&gt;We approach developer relations like software engineers, building internal tools and automating everything we can to make us more efficient.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It works well for us, and I'm excited to tell you all about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Pipedream?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/scottw/pipedream-l02-temp-slug-8448815"&gt;Pipedream&lt;/a&gt; runs an integration platform for developers. What does that mean?&lt;/p&gt;

&lt;p&gt;Developers use Pipedream to connect different apps and APIs together using a visual programming environment. If you need to send a message to Slack or write a record to your database each time someone opens a new issue in your GitHub repo, you can develop a "workflow" without writing code.&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-01-at-10.18.07-AM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-01-at-10.18.07-AM.png"&gt;&lt;/a&gt;Here, the title of the GitHub issue is the text of the Slack message&lt;/p&gt;

&lt;p&gt;But you can run custom Node.js code anywhere you need it, extending any of the &lt;a href="https://github.com/PipedreamHQ/pipedream/tree/master/components" rel="noopener noreferrer"&gt;built-in actions&lt;/a&gt; or &lt;a href="https://pipedream.com/docs/components/guidelines/" rel="noopener noreferrer"&gt;adding your own&lt;/a&gt;. This is where Pipedream shines. You can quickly transition from a no-code workflow to a Node.js serverless function as you need more complex logic.&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-01-at-10.22.10-AM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-01-at-10.22.10-AM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can use Pipedream to automate almost anything, and &lt;a href="https://pipedream.com/pricing" rel="noopener noreferrer"&gt;it's free&lt;/a&gt;. Watch the video below, or &lt;a href="https://pipedream.com/docs/" rel="noopener noreferrer"&gt;read our docs to learn more&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does DevRelEng do at Pipedream?
&lt;/h2&gt;

&lt;p&gt;You can break down DevRelEng into two core functions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Supporting our customers&lt;/li&gt;
&lt;li&gt;Building software / tools to better support our customers&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Supporting our customers
&lt;/h3&gt;

&lt;p&gt;It's worth reiterating our core mantra:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Since our customers are developers, every customer interaction = developer relations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So when we say "support" in DevRelEng, we're talking about the whole customer lifecycle, from marketing to sales to technical support.&lt;/p&gt;

&lt;p&gt;Pipedream is &lt;a href="https://pipedream.com/pricing" rel="noopener noreferrer"&gt;free for low-volume workflows&lt;/a&gt;. Most users chat with us in our &lt;a href="https://pipedream.com/community" rel="noopener noreferrer"&gt;Discourse&lt;/a&gt;, &lt;a href="https://join.slack.com/t/pipedream-users/shared_invite/zt-ernlymsn-UHfPg~Dfp08uGkAd8dpkww" rel="noopener noreferrer"&gt;Slack&lt;/a&gt;, and &lt;a href="https://github.com/PipedreamHQ/pipedream" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; communities. Many of these users are evaluating Pipedream for use on their team, and later upgrade to one of our paid plans. We aim to answer 100% of our community questions because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It builds a public database of answers to common questions&lt;/strong&gt;. If we answer a question once, it's indexed by Google and we can refer future users to these answers. We acquire a lot of new users from search keywords for specific APIs, so each new page we add (e.g. "How do I connect Stripe and Discord?") contributes to that acquisition strategy. &lt;/li&gt;
&lt;li&gt;When someone gets a workflow working, they're hooked. They end up building more, and we convert a percentage of those users to a paid plan. &lt;strong&gt;We're doing Sales without explicitly selling anything&lt;/strong&gt;. We just helped them use the product.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Larger teams have access to dedicated, priority support. DevRelEng handles that, too. Most of these customers use Slack, so we setup dedicated Slack Connect channels where they can reach us quickly (from the customer's perspective, nothing is better than chat support). These teams are composed of highly-technical power users. With them, we're often wearing our Solutions Architect hat, answering workflow design questions or helping them build &lt;a href="https://pipedream.com/docs/components/" rel="noopener noreferrer"&gt;custom sources and actions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Like with our free customers, &lt;strong&gt;we try to answer questions in public&lt;/strong&gt;. If a Teams customer asks a general product question in a private channel, we'll write a doc or a Discourse post and share it with them so that &lt;em&gt;all&lt;/em&gt; users benefit.&lt;/p&gt;

&lt;p&gt;We also hear a lot of product feedback as we talk to customers. Here, we act as Product Manager, asking a lot of clarifying questions to get at the core of what the user's asking for. Every new request is assigned a GitHub issue, and we ask future users to +1 those issues so we can track the most popular asks. &lt;a href="https://dev.to/dylburger/analyzing-github-issue-reactions-13p4"&gt;We've developed a Pipedream workflow&lt;/a&gt; that collects this reaction data so we can analyze it:&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-01-at-2.22.25-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-01-at-2.22.25-PM.png"&gt;&lt;/a&gt;An API / CLI for workflows is our #1 ask by far, and we're working on it!&lt;/p&gt;

&lt;p&gt;This is a great example of how we're thinking like engineers when we solve support problems. It's a perfect segue to talk about the engineering side of DevRelEng.&lt;/p&gt;

&lt;h3&gt;
  
  
  DevRelEng software and tools
&lt;/h3&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-13-at-7.53.23-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-13-at-7.53.23-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We use standard tools to manage our community (GitHub, Slack, Discourse) and infrastructure (AWS, Vercel, Snowflake, etc.). I'll tell you how we leverage these services, and how we automate everything we can using Pipedream (DevRelEng alone runs 40+ Pipedream workflows internally).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;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;import axios from "axios"

export default {  
  key: "http-get-request",
  name: "GET Request",
  description: "Make an HTTP `GET` request to example.com",
  type: "action",
  version: "0.0.1",
  async run() {
    return await axios({
      method: "GET",
      url: "https://example.com",
    })
  },
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
A simple Pipedream action. &lt;a href="https://pipedream.com/docs/components/actions/#creating-your-own-actions" rel="noopener noreferrer"&gt;Learn more here&lt;/a&gt;.





&lt;p&gt;&lt;a href="https://github.com/PipedreamHQ/pipedream" rel="noopener noreferrer"&gt;Our GitHub repo&lt;/a&gt; is the center of our developer community. All &lt;a href="https://pipedream.com/docs/components/" rel="noopener noreferrer"&gt;Pipedream components&lt;/a&gt; (triggers and actions you can use in workflows) live on GitHub. Any user can contribute new components for any app, and we manage component development on &lt;a href="https://github.com/PipedreamHQ/pipedream/projects/1" rel="noopener noreferrer"&gt;this project board&lt;/a&gt;. &lt;a href="https://docs.pipedream.com/" rel="noopener noreferrer"&gt;Our docs&lt;/a&gt; are public, as is &lt;a href="https://github.com/PipedreamHQ/pipedream/issues" rel="noopener noreferrer"&gt;our roadmap&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We run 10+ Pipedream workflows on different events from GitHub. For example, we kick off a workflow &lt;a href="https://pipedream.com/@pd/github-pipedream-repo-label-action-mapping-p_yKCgy2z/edit" rel="noopener noreferrer"&gt;when specific labels are added to issues&lt;/a&gt;. When a user requests a new integration, the &lt;code&gt;app&lt;/code&gt; label is assigned, and it sends the app request to our integrations team. When a user requests &lt;a href="https://pipedream.com/docs/event-sources/" rel="noopener noreferrer"&gt;an event source&lt;/a&gt;, we add the &lt;code&gt;source-docs&lt;/code&gt; label, which sends a message to the user asking them if they'd like to develop the source themselves:&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-13-at-11.21.03-AM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-13-at-11.21.03-AM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a good example of how we automate highly-personal support. We write a kind, helpful note &lt;em&gt;once&lt;/em&gt;, and we develop an automation to send it to any customer with the same request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Discourse&lt;/strong&gt;&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-13-at-7.57.41-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-13-at-7.57.41-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.discourse.org/" rel="noopener noreferrer"&gt;Discourse&lt;/a&gt; provides an open-source community forum. All Discourse posts are indexed by Google, which helps existing users find answers to common questions, and attracts new users through search. We self-host our Discourse forum at &lt;a href="https://pipedream.com/community" rel="noopener noreferrer"&gt;https://pipedream.com/community&lt;/a&gt; as a Docker container using &lt;a href="https://github.com/discourse/discourse_docker" rel="noopener noreferrer"&gt;the discourse-docker project&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slack&lt;/strong&gt;&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-13-at-8.13.59-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-13-at-8.13.59-PM.png"&gt;&lt;/a&gt;A recent announcement. &lt;a href="https://join.slack.com/t/pipedream-users/shared_invite/zt-ernlymsn-UHfPg~Dfp08uGkAd8dpkww" rel="noopener noreferrer"&gt;Join our Slack for updates&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;We also operate &lt;a href="https://join.slack.com/t/pipedream-users/shared_invite/zt-ernlymsn-UHfPg~Dfp08uGkAd8dpkww" rel="noopener noreferrer"&gt;a public Slack&lt;/a&gt;, where people can ask questions about Pipedream, chat component development, or share workflows and tips.&lt;/p&gt;

&lt;p&gt;Some people prefer to chat on Slack, others live in Discourse. We maintain both to let users communicate over their preferred channel. The main drawbacks are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We split our community between Slack and Discourse (something I believe we can solve)&lt;/li&gt;
&lt;li&gt;Slack isn't indexed by Google. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To solve #2, we built &lt;strong&gt;Discourse Bot&lt;/strong&gt; : a Slack bot, &lt;a href="https://pipedream.com/@pd/public-slack-discourse-bot-post-to-discourse-p_mkC5qqk/edit" rel="noopener noreferrer"&gt;powered by a workflow&lt;/a&gt;, that converts Slack threads to public Discourse topics.&lt;/p&gt;

&lt;p&gt;Any time an issue is solved in Slack or a good discussion wraps up, we chat &lt;strong&gt;@Discourse Bot&lt;/strong&gt; with the desired title of the Discourse topic:&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-01-at-2.50.17-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-01-at-2.50.17-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That kicks off the workflow, converts Slack's message format to Markdown, uploads images, and creates a Discourse topic with posts for each message in the Slack thread:&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-01-at-2.49.54-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-01-at-2.49.54-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docs&lt;/strong&gt;&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-13-at-8.22.45-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-13-at-8.22.45-PM.png"&gt;&lt;/a&gt;Our docs deployed on Vercel&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pipedream.com/docs" rel="noopener noreferrer"&gt;https://pipedream.com/docs&lt;/a&gt; is managed with &lt;a href="https://vuepress.vuejs.org/" rel="noopener noreferrer"&gt;VuePress&lt;/a&gt; and deployed to &lt;a href="https://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt;. Docs are mostly written as Markdown, but VuePress lets us write custom Vue.js components when we need it. Anyone can edit our docs &lt;a href="https://github.com/PipedreamHQ/pipedream/tree/master/docs" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We try to write comprehensive docs. Any time a user has a question, we ask, "&lt;em&gt;is it documented?&lt;/em&gt;" If not, we'll either write a doc for it and share it with the user, or make a note to add a doc later. For this, we developed a Slack Bot called &lt;strong&gt;Docs Bot&lt;/strong&gt;. We chat the Docs Bot in Slack:&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-13-at-11.02.12-AM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-13-at-11.02.12-AM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That triggers &lt;a href="https://pipedream.com/@pd/new-docs-slack-bot-p_n1CoW5J/edit" rel="noopener noreferrer"&gt;this workflow&lt;/a&gt;, which creates a GitHub issue with the &lt;code&gt;docs&lt;/code&gt; label:&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-13-at-11.02.23-AM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-13-at-11.02.23-AM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every couple of weeks, we search for &lt;a href="https://github.com/PipedreamHQ/pipedream/issues?q=is%3Aissue+is%3Aopen+label%3Adocs" rel="noopener noreferrer"&gt;all open docs requests&lt;/a&gt; and write a batch of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We run Pipedream on &lt;a href="https://aws.amazon.com/" rel="noopener noreferrer"&gt;AWS&lt;/a&gt;, and the DevRelEng team is no different. A few example apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We operate &lt;a href="http://www.squid-cache.org/" rel="noopener noreferrer"&gt;a Squid proxy&lt;/a&gt; paid customers can use to route HTTP traffic through a fixed IP.&lt;/li&gt;
&lt;li&gt;We run an anomaly detection app powered by Facebook's &lt;a href="https://facebook.github.io/prophet/" rel="noopener noreferrer"&gt;Prophet forecasting library&lt;/a&gt;. It tells us if metrics dip or rise in unexpected ways ("&lt;em&gt;Did signups drop? Is something broken with that flow?&lt;/em&gt;"). We built the service because customers kept reaching out to tell us some feature broke before we noticed. Normally these issues show up in product data, so the app looks for these anomalies and tells us when they happen.&lt;/li&gt;
&lt;li&gt;We've also setup "&lt;a href="https://cloudonaut.io/dead-mans-switch-with-cloudwatch/" rel="noopener noreferrer"&gt;dead man's switches&lt;/a&gt;" that tell us when core integrations break. For example, we run a Pipedream workflow that sends a message to Discord. A different workflow runs on new Discord messages in the same channel. If both workflows run successfully, we were able to send and receive messages from Discord, so the integration works. We ping AWS CloudWatch to notify them that message was processed end-to-end. But if this process &lt;em&gt;breaks&lt;/em&gt;, the dead man's switch triggers since it hasn't received data, and we raise an alarm in Slack:&lt;/li&gt;
&lt;/ul&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-13-at-12.22.39-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-13-at-12.22.39-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Segment, Snowflake, Looker&lt;/strong&gt;&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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-14-at-12.08.51-PM.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%2Fpipedream.com%2Fblog%2Fcontent%2Fimages%2F2021%2F10%2FScreen-Shot-2021-10-14-at-12.08.51-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Product data is critical for our team. When we're troubleshooting a bug, we need to understand how many users might be impacted. Or we might want to look at the number of users who use a specific integration to assess the priority of developing new actions.&lt;/p&gt;

&lt;p&gt;For analytics, we use the &lt;strong&gt;SSL&lt;/strong&gt; stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://segment.com/" rel="noopener noreferrer"&gt;Segment&lt;/a&gt;: Usage data is recorded via &lt;a href="https://segment.com/docs/connections/spec/track/" rel="noopener noreferrer"&gt;Segment &lt;code&gt;track&lt;/code&gt; calls&lt;/a&gt;. Segment syncs these events to Snowflake, managing the schema for us.&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://snowflake.com/" rel="noopener noreferrer"&gt;Snowflake&lt;/a&gt;: Snowflake is fast, and works well as a product analytics database.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://looker.com/" rel="noopener noreferrer"&gt;Looker&lt;/a&gt;: Looker is essentially a &lt;a href="https://en.wikipedia.org/wiki/Pivot_table" rel="noopener noreferrer"&gt;pivot table&lt;/a&gt; for your database. We &lt;a href="https://looker.com/platform/data-modeling" rel="noopener noreferrer"&gt;create a model&lt;/a&gt; of our Snowflake schema that maps SQL to metrics and dimensions. When you use Looker's UI, you write no SQL. You just pull in metrics and dimensions (e.g., I want to see the count of users who signed up for Pipedream week over week), and Looker runs SQL behind the scenes. Looker is critical for answering complex questions quickly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Challenges ahead
&lt;/h2&gt;

&lt;p&gt;Pipedream is three years old. The product itself and the nature of how we interact with our developers will change drastically over the coming years. As we grow the business, DevRelEng is thinking about a few core things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Content / SEO&lt;/strong&gt; : Search traffic is critical for our user acquisition. If a developer asks Google how to integrate two apps, we want them to land on Pipedream. The whole company thinks about this problem, and DevRelEng tackles it from a community perspective. How do we develop more useful content for Pipedream and the integrations ecosystem at large? How do we encourage the community to generate content?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintaining high-quality support&lt;/strong&gt; : How do we continue to respond to 100% of requests in a personal, timely manner as we scale? This will involve a thoughtful mix of automation, technical writing, and cooperation with our community. We support 500+ apps, but we're not experts in them all. How do we leverage our community of experts, or the app providers themselves, to assist with bespoke integration questions?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling contributions&lt;/strong&gt; : Building event sources and actions requires expertise in the target API. We are experts at building integrations, but again, we lack deep expertise in any one specific API. Our team is focused on improving &lt;a href="https://pipedream.com/docs/components/api/" rel="noopener noreferrer"&gt;the core Pipedream component API&lt;/a&gt; and reducing the barrier to &lt;a href="https://pipedream.com/docs/components/guidelines/" rel="noopener noreferrer"&gt;contributions&lt;/a&gt; so that it's easy for the experts (app providers, community developers) to create components.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automating everything&lt;/strong&gt; : We have a backlog of 30+ workflows we'd like to develop to solve things we do frequently (and manually) today. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Join the team
&lt;/h2&gt;

&lt;p&gt;We're a small team with &lt;a href="https://pipedream.com/about" rel="noopener noreferrer"&gt;a lot of experience growing companies&lt;/a&gt;, but we need help solving these problems. We're looking for software engineers who like DevRel, DevRel who like writing code, teachers, solutions architects, and more. If you know how to write code and you've supported people in a technical role, you'll be a great fit. &lt;a href="https://angel.co/company/pipedreamhq/jobs/1288069-developer-relations-engineer?utm_campaign=startup_share&amp;amp;utm_content=startup_share_module&amp;amp;utm_medium=social&amp;amp;utm_term=pipedreamhq" rel="noopener noreferrer"&gt;Apply here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We're also hiring a &lt;a href="https://angel.co/company/pipedreamhq/jobs/1547046-ui-engineer?utm_campaign=startup_share&amp;amp;utm_content=startup_share_module&amp;amp;utm_medium=social&amp;amp;utm_term=pipedreamhq" rel="noopener noreferrer"&gt;UI Engineer&lt;/a&gt; and a &lt;a href="https://angel.co/company/pipedreamhq/jobs/1667565-devops-site-reliability-engineer?utm_campaign=startup_share&amp;amp;utm_content=startup_share_module&amp;amp;utm_medium=social&amp;amp;utm_term=pipedreamhq" rel="noopener noreferrer"&gt;Devops / Site Reliability Engineer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you have any questions about DevRelEng, Pipedream, or just want to say hi, I'm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dylan Sather (Pipedream)&lt;/strong&gt; on our &lt;a href="https://join.slack.com/t/pipedream-users/shared_invite/zt-ernlymsn-UHfPg~Dfp08uGkAd8dpkww" rel="noopener noreferrer"&gt;public Slack&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;dylburger&lt;/strong&gt; on &lt;a href="https://pipedream.com/community" rel="noopener noreferrer"&gt;Discourse&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;dylan at pipedream dot com&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Avoiding parking tickets with Pipedream</title>
      <dc:creator>Dylan J. Sather</dc:creator>
      <pubDate>Sun, 01 Nov 2020 19:44:27 +0000</pubDate>
      <link>https://dev.to/dylburger/avoiding-parking-tickets-with-pipedream-4fb6</link>
      <guid>https://dev.to/dylburger/avoiding-parking-tickets-with-pipedream-4fb6</guid>
      <description>&lt;p&gt;I grew up in Oklahoma City. In OKC, you drive everywhere. Public transportation is lacking and &lt;a href="https://www.google.com/search?q=okc+square+miles&amp;amp;oq=okc+square+miles&amp;amp;aqs=chrome.0.0i457j69i57.2618j0j9&amp;amp;sourceid=chrome&amp;amp;ie=UTF-8"&gt;it's a &lt;em&gt;huge&lt;/em&gt; city&lt;/a&gt; — over 100 square miles bigger than Los Angeles.&lt;/p&gt;

&lt;p&gt;Ten years ago, I moved to San Francisco and drove nowhere. My car sat on the street as I biked to work. But the Public Works department &lt;a href="https://www.sfpublicworks.org/services/mechanical-street-sweeping-and-street-cleaning-schedule"&gt;cleans most streets on a schedule&lt;/a&gt;, and if you don't move your car before cleaning begins, they give you a ticket.&lt;/p&gt;

&lt;p&gt;The schedule varies from street to street — one block might be Mondays, another Wednesdays — and I would always forget to move my car at the right time. Since I rarely drove it anyway, I ended up selling it.&lt;/p&gt;

&lt;p&gt;Fast forward to the present. My wife and I have a child:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IW8r_Ht1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1604088585/dev.to%2520posts/IMG_1501_cr82u6.jpg" class="article-body-image-wrapper"&gt;&lt;img alt="My child is a dog" src="https://res.cloudinary.com/practicaldev/image/fetch/s--IW8r_Ht1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1604088585/dev.to%2520posts/IMG_1501_cr82u6.jpg" width="400px"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And we bought a new car. The memories of those tickets rushed back. I needed something to remind me to move my car at the right time, no matter where I parked. I needed a way to turn this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IWGN7s3x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1603932358/dev.to%2520posts/Camera_2020-06-07_at_11.41.53_mzsz9w.jpg" class="article-body-image-wrapper"&gt;&lt;img alt="Example street cleaning sign" src="https://res.cloudinary.com/practicaldev/image/fetch/s--IWGN7s3x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1603932358/dev.to%2520posts/Camera_2020-06-07_at_11.41.53_mzsz9w.jpg" width="400px"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;into a Google Calendar reminder:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0m7UpDLM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1590802144/dev.to%2520posts/Screen_Shot_2020-05-29_at_6.28.27_PM_qs0cah.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0m7UpDLM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1590802144/dev.to%2520posts/Screen_Shot_2020-05-29_at_6.28.27_PM_qs0cah.png" alt="Calendar reminder for street cleaning"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I used &lt;a href="https://support.apple.com/guide/shortcuts/welcome/ios"&gt;iOS Shortcuts&lt;/a&gt; and &lt;a href="https://pipedream.com"&gt;Pipedream&lt;/a&gt; to automate this, with help from a handful of APIs. You might not ever park your car in San Francisco, but I hope what I learned about solving a real-world problem will be helpful for any automation you do.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What is Pipedream?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://pipedream.com"&gt;Pipedream&lt;/a&gt; is an integration platform for developers. You can use it to run &lt;strong&gt;workflows&lt;/strong&gt; - a set of steps that compose an automation. You can run workflows on HTTP requests, emails, on a schedule, or in response to app-based events like new tweets, new files uploaded to Google Drive, and more.&lt;/p&gt;

&lt;p&gt;Within a workflow, you can use pre-built actions that perform common operations across &lt;a href="https://docs.pipedream.com/apps/all-apps/"&gt;300+ apps&lt;/a&gt;, like creating a new calendar event, or sending an email. You can also write &lt;a href="https://docs.pipedream.com/workflows/steps/code/"&gt;any Node.js code&lt;/a&gt; when you need to solve a custom use case where code works best.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7RLJ4gwq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1604099472/dev.to%2520posts/Screen_Shot_2020-10-30_at_4.10.19_PM_lewsqp.png" class="article-body-image-wrapper"&gt;&lt;img alt="Pipedream" src="https://res.cloudinary.com/practicaldev/image/fetch/s--7RLJ4gwq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1604099472/dev.to%2520posts/Screen_Shot_2020-10-30_at_4.10.19_PM_lewsqp.png" width="400px"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can think of Pipedream as a mix of integration tools like &lt;a href="https://zapier.com/"&gt;Zapier&lt;/a&gt; / &lt;a href="https://ifttt.com/home"&gt;IFTTT&lt;/a&gt; and serverless platforms like &lt;a href="https://aws.amazon.com/lambda/"&gt;AWS Lambda&lt;/a&gt; / &lt;a href="https://cloud.google.com/functions"&gt;Google Cloud Functions&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;First attempts - extracting text from the signs&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I'll spare y'all the details of my many failed attempts. &lt;strong&gt;In short, the real world produces messy data&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Since the street signs include the cleaning schedule, I thought I could take a picture of the sign and use the text to create the reminders. I extracted that text using &lt;a href="https://cloud.google.com/vision/docs/ocr"&gt;Google Cloud's Vision API&lt;/a&gt;. Then, I parsed that text to get the day and time of the cleaning, creating a Google Calendar reminder. This worked great for a few signs around my neighborhood.&lt;/p&gt;

&lt;p&gt;Then I found this sign:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--x71mmkwK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1603932318/dev.to%2520posts/Camera_2020-06-07_at_11.53.10_rafx9h.jpg%3Ftest%3D1" class="article-body-image-wrapper"&gt;&lt;img alt="First bad sign" src="https://res.cloudinary.com/practicaldev/image/fetch/s--x71mmkwK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1603932318/dev.to%2520posts/Camera_2020-06-07_at_11.53.10_rafx9h.jpg%3Ftest%3D1" width="400px"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and this one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QloyYsKr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1603931501/dev.to%2520posts/Camera_2020-06-07_at_12.30.30_irqola.jpg%3Ftest%3D1" class="article-body-image-wrapper"&gt;&lt;img alt="Second bad sign" src="https://res.cloudinary.com/practicaldev/image/fetch/s--QloyYsKr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1603931501/dev.to%2520posts/Camera_2020-06-07_at_12.30.30_irqola.jpg%3Ftest%3D1" width="400px"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clearly, I couldn't rely on reading the text of every sign. I needed a different approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;DataSF to the rescue&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The City of San Francisco operates an "open data" portal called &lt;a href="https://datasf.org/opendata/"&gt;DataSF&lt;/a&gt;. The &lt;a href="https://datasf.org/about/#who-we-are"&gt;DataSF team&lt;/a&gt; helps source data from various city departments and aggregates it into a single platform.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dzhiXlRs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1604097600/dev.to%2520posts/Screen_Shot_2020-10-30_at_2.21.32_PM_ekzimj.png" class="article-body-image-wrapper"&gt;&lt;img alt="DataSF" src="https://res.cloudinary.com/practicaldev/image/fetch/s--dzhiXlRs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1604097600/dev.to%2520posts/Screen_Shot_2020-10-30_at_2.21.32_PM_ekzimj.png" width="600px"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anyone can download &lt;a href="https://data.sfgov.org/browse?category=COVID-19"&gt;COVID-19&lt;/a&gt; stats, &lt;a href="https://data.sfgov.org/City-Infrastructure/311-Cases/vw6y-z8j6"&gt;311 calls&lt;/a&gt;, a &lt;a href="https://data.sfgov.org/City-Infrastructure/Street-Tree-List/tkzw-k3nq"&gt;map of every tree maintained by the city&lt;/a&gt;, and hundreds of other public datasets. All the data are accessible via API.&lt;/p&gt;

&lt;p&gt;I'd used DataSF for past projects, so I figured I'd search their portal for "street cleaning". And what do you know - they provide &lt;a href="https://data.sfgov.org/City-Infrastructure/Street-Sweeping-Schedule/yhqp-riqs"&gt;the street cleaning schedule for all city streets&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Every record in this data set had the following structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Centerline Network Number - the city's unique ID for the street segment (roughly a few city blocks)&lt;/li&gt;
&lt;li&gt;The side of the street this cleaning schedule applies to (different sides have different schedules)&lt;/li&gt;
&lt;li&gt;The day and time of cleaning for that side of the street, e.g. Wednesdays from 12pm to 2pm&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry#Geometric_objects"&gt;Well-known text (WKT) representation&lt;/a&gt; of the geographic line representing the street segment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last value is a &lt;code&gt;LINESTRING&lt;/code&gt;: pairs of (longitude, latitude) values that compose a line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LINESTRING (
  -122.485667402435 37.778117247851,
  -122.485532329198 37.776252458259
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can use a WKT viewer like &lt;a href="https://arthur-e.github.io/Wicket/sandbox-gmaps3.html"&gt;Wicket&lt;/a&gt; to visualize these lines on a map:&lt;/p&gt;


&lt;img alt="Example street segment" src="https://res.cloudinary.com/practicaldev/image/fetch/s--JD7uQAZX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1604092677/dev.to%2520posts/Screen_Shot_2020-10-30_at_2.16.21_PM_knl6hu.png" width="400px"&gt;


&lt;p&gt;&lt;strong&gt;I wanted to find the street segment &lt;em&gt;closest&lt;/em&gt; to where I was parked so I could get its cleaning schedule&lt;/strong&gt;. I hadn't worked with geo data much, so it wasn't obvious how I'd solve that problem.&lt;/p&gt;

&lt;p&gt;DataSF is powered by a platform called &lt;a href="https://www.tylertech.com/products/socrata"&gt;Socrata&lt;/a&gt;. When you make API requests to DataSF, you're using &lt;a href="https://dev.socrata.com/"&gt;Socrata's API&lt;/a&gt;. They provide &lt;a href="https://dev.socrata.com/docs/functions/"&gt;a list of functions&lt;/a&gt; that can be used in API requests. I scanned this list and found the &lt;a href="https://dev.socrata.com/docs/functions/intersects.html"&gt;&lt;code&gt;intersects()&lt;/code&gt; function&lt;/a&gt;, which "allows you to compare two geospatial types to see if they intersect or overlap each other".&lt;/p&gt;

&lt;p&gt;Instead of finding the closest street segments to my car, I could reframe the problem: &lt;strong&gt;I want to find the street segment that overlaps with my current location&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I could use my phone to get my current location. But my location is a &lt;em&gt;point&lt;/em&gt;, and the street segment is a &lt;em&gt;line&lt;/em&gt; in the center of the street. Those geometries don't intersect.&lt;/p&gt;


&lt;img alt="Point and line street drawing" src="https://res.cloudinary.com/practicaldev/image/fetch/s--FmhX9qCO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1604096706/dev.to%2520posts/62578758561__DB85B347-DDF0-4BFF-B8BC-DEDCE04DCA77_w4htrp.jpg" width="400px"&gt;


&lt;p&gt;Instead, we need to draw a box around our current location a few meters in each direction. In WKT terms, this box is a &lt;code&gt;POLYGON&lt;/code&gt;, and &lt;strong&gt;we can ask DataSF whether that polygon intersects with any streets&lt;/strong&gt;. This also helps address &lt;a href="https://www.gps.gov/systems/gps/performance/accuracy/#:~:text=For%20example%2C%20GPS%2Denabled%20smartphones,receivers%20and%2For%20augmentation%20systems."&gt;the imperfect accuracy of GPS&lt;/a&gt; by broadening the area where we search for close streets.&lt;/p&gt;


&lt;img alt="Polygon and line drawing" src="https://res.cloudinary.com/practicaldev/image/fetch/s--8pEC15VO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1604096881/dev.to%2520posts/IMG_1509_cqysl4.jpg" width="400px"&gt;


&lt;p&gt;This worked great, but presented a new problem. Every street segment has &lt;em&gt;two&lt;/em&gt; cleaning records: one for the left side of the street, another for the right. The WKT &lt;code&gt;LINESTRING&lt;/code&gt; for both sides ran down the center of the street, so there's no way to tell whether you're on the right side or the left side by comparing the distance from your current location. And since GPS isn't perfectly accurate, we wouldn't want a program to pick — it might pick the wrong side.&lt;/p&gt;

&lt;p&gt;There's a non-obvious but simple solution to this problem: &lt;strong&gt;just let me choose the right time from a list&lt;/strong&gt;.&lt;/p&gt;


&lt;img alt="iOS Shortcut schedule" src="https://res.cloudinary.com/practicaldev/image/fetch/s--pNJYBmx3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1604102875/dev.to%2520posts/IMG_1510_wnesa4.jpg" width="400px"&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Glueing it all together with iOS Shortcuts&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Even in the early versions of this project, when I was taking pictures of street signs, I used &lt;a href="https://support.apple.com/guide/shortcuts/welcome/ios"&gt;iOS Shortcuts&lt;/a&gt; to kick off the automation from my iPhone.&lt;/p&gt;

&lt;p&gt;Shortcuts provides a "no code" programming environment for your iPhone. You can use it to retrieve your current location, take pictures, make HTTP requests, trigger actions in other iOS apps, and more. They even allow for basic flow control (if / then statements, for loops, and more). And they provide a way to prompt a user to select an option from a list. &lt;strong&gt;So I used the DataSF API to find the closest streets and their cleaning schedules, then let the user choose the right one&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Android users should check out &lt;a href="https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm"&gt;Tasker&lt;/a&gt;, which offers similar functionality).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;End-to-end, here's how the automation works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Shortcuts&lt;/em&gt; : Get current location&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Shortcuts&lt;/em&gt; : Send that location to Pipedream (HTTP Request)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Pipedream&lt;/em&gt; : &lt;a href="https://pipedream.com/@dylan/street-cleaning-1-get-street-cleaning-schedules-for-a-lat-long-p_7NCLjW/edit"&gt;Get street cleaning schedules for a given location&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Shortcuts&lt;/em&gt; : Present schedules in list, let user choose&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Shortcuts&lt;/em&gt; : Send chosen schedule and location to Pipedream (HTTP Request)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Pipedream&lt;/em&gt; : &lt;a href="https://pipedream.com/@dylan/street-cleaning-2-create-street-cleaning-calendar-reminder-p_o7Cxqr/edit"&gt;Delete old reminders, create Google Calendar reminder with current location&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So far, no tickets!&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Creating the calendar reminder&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This &lt;a href="(https://pipedream.com/@dylan/street-cleaning-2-create-street-cleaning-calendar-reminder-p_o7Cxqr/edit)"&gt;last workflow&lt;/a&gt; is neat, and I want to show you two things you might be able to use in other projects.&lt;/p&gt;

&lt;p&gt;SFData gave me the day and time of cleanings, but busy streets are cleaned multiple days: Tuesday &lt;em&gt;and&lt;/em&gt; Thursday, for example. I had to find the closest calendar date in the future given that schedule. If I parked on that Tue / Thu street on a Wednesday, I'd need to create a reminder to move my car by the next day (Thursday). If I parked there on Friday, the reminder would need to be for &lt;em&gt;next&lt;/em&gt; Tuesday.&lt;/p&gt;

&lt;p&gt;Enter &lt;a href="https://github.com/wanasit/chrono"&gt;Chrono&lt;/a&gt;, "a natural language date parser in Javascript". Chrono parses "Today", "this Tuesday", and other human-readable dates into a JavaScript Date object. They provide a function that did exactly what I needed:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Give me the Date object of the first 12PM Wed in the future&lt;/span&gt;
&lt;span class="c1"&gt;// https://github.com/wanasit/chrono#parsing-options&lt;/span&gt;
&lt;span class="nx"&gt;chrono&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parseDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;12PM Wed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;forwardDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// Wed Nov 4 2020 12:00:00 GMT+0000 (Coordinated Universal Time)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Once I had JavaScript Dates for streets with multiple cleanings, I compared them and found the closest time. Now we could create a calender reminder.&lt;/p&gt;

&lt;p&gt;I quickly found another problem: I might move the car days before street cleaning is scheduled. But that reminder is still on my calendar, and it'd tell me to move my car for the street I parked on days ago. I don't want to manually delete old reminders each time. I needed a way to automatically delete them when I moved my car.&lt;/p&gt;

&lt;p&gt;I ended up tagging new calendar events with metadata to identify them as "move car reminders". This way, the Pipedream workflow could find and delete all existing move car reminders before creating a new one. Google Calendar allows you to add this metadata as &lt;a href="https://developers.google.com/calendar/extended-properties"&gt;extended properties&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The fields of the Events resources cover the most common data associated with an event, such as location, start time, etc, but applications may want to store additional metadata specific to their use case. The Calendar API provides the ability to set hidden key-value pairs with an event, called extended properties. Extended properties make it easy to store application-specific data for an event without having to utilize an external database.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In code, you can just attach this metadata in the create event API request:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  extendedProperties: {
    private: {
      streetCleaning: true
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Finally, I add my wife to the invite, add reminders 24 and 12 hours ahead of the cleaning time, and add the location of the car, which you can view in Google Maps.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Wrapping up&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;These real-world data problems can be more challenging than typical automations, but they're rewarding to solve. I couldn't have done this without SFData - huge thanks to them for collecting and exposing the cleaning schedules.&lt;/p&gt;

&lt;p&gt;Let me know what problems you're automating in the comments!&lt;/p&gt;



&lt;p&gt;&lt;em&gt;I wrote this article with &lt;a href="https://code.visualstudio.com/"&gt;VS Code&lt;/a&gt;, on my Mac, and published it by running &lt;code&gt;git push&lt;/code&gt;. All my DEV posts are tracked by Git, and I can manage them from my local machine. Learn how to do this yourself!&lt;/em&gt;&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/dylburger" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SgtZRw8D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--iLjZNdrl--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/234988/057b1fdb-03ed-4e08-a3e7-b477d8a6c3ef.jpeg" alt="dylburger image"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/dylburger/publish-dev-articles-from-a-git-repo-with-github-pipedream-505j" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Publish DEV articles from a Git repo, with Github + Pipedream&lt;/h2&gt;
      &lt;h3&gt;Dylan J. Sather ・ Jun 10 ・ 7 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#pipedream&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#github&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#meta&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#git&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Show your most recent DEV post on your Github profile with Pipedream</title>
      <dc:creator>Dylan J. Sather</dc:creator>
      <pubDate>Sun, 12 Jul 2020 15:58:08 +0000</pubDate>
      <link>https://dev.to/dylburger/show-your-most-recent-dev-post-on-your-github-profile-with-pipedream-21h5</link>
      <guid>https://dev.to/dylburger/show-your-most-recent-dev-post-on-your-github-profile-with-pipedream-21h5</guid>
      <description>&lt;p&gt;Github just launched &lt;a href="https://dev.to/natterstefan/how-to-add-a-readme-to-your-github-profile-2bo9"&gt;READMEs for your Github profile&lt;/a&gt;. This is pretty powerful. Since you can add any &lt;a href="https://www.markdownguide.org/"&gt;Markdown&lt;/a&gt; to a &lt;code&gt;README&lt;/code&gt;, you can now add images, links, and all sorts of other content to your profile. Check out &lt;a href="https://github.com/M0nica"&gt;Monica Powell's profile&lt;/a&gt;, for example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_BRzNh-Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1594488579/dev.to%2520posts/Screen_Shot_2020-07-11_at_10.29.00_AM_f3ji3s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_BRzNh-Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1594488579/dev.to%2520posts/Screen_Shot_2020-07-11_at_10.29.00_AM_f3ji3s.png" alt="Monica Powell Github profile"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What if you could take this a step further, and &lt;strong&gt;update your profile &lt;em&gt;automatically&lt;/em&gt; in response to specific events&lt;/strong&gt;? For example, what if you could always display your most recent tweet on your profile? What if you could list the Spotify track you're listening to &lt;em&gt;right now&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;To test this idea, I'm going to show you how to display a link your newest DEV post on your profile:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0BCw5lFf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1594444679/dev.to%2520posts/Screen_Shot_2020-07-10_at_10.17.39_PM_rvedv0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0BCw5lFf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1594444679/dev.to%2520posts/Screen_Shot_2020-07-10_at_10.17.39_PM_rvedv0.png" alt="DEV post on profile" width="600px"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Each time you publish a new post, &lt;a href="https://pipedream.com/@dylan/update-github-profile-with-my-most-recent-dev-post-p_YyCV6y/edit"&gt;this Pipedream workflow&lt;/a&gt; will automatically update the link on that &lt;code&gt;README&lt;/code&gt;&lt;/strong&gt;. &lt;a href="https://pipedream.com"&gt;Pipedream&lt;/a&gt; is an integration platform for developers, and it's great for building serverless, event-driven workflows like this. Pipedream workflows are written using &lt;a href="https://docs.pipedream.com/workflows/steps/actions/#using-existing-actions"&gt;pre-built actions&lt;/a&gt; and &lt;a href="https://docs.pipedream.com/workflows/steps/code/"&gt;custom Node.js code&lt;/a&gt;, each of which can connect to hundreds of API integrations. Workflows run &lt;a href="https://docs.pipedream.com/pricing/"&gt;for free&lt;/a&gt;.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  &lt;strong&gt;How this works&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;First, add a &lt;code&gt;README&lt;/code&gt; to your Github profile. &lt;a href="https://dev.to/natterstefan"&gt;Stefan Natter&lt;/a&gt; published a great article on how to set that up:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/natterstefan" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ijiBs9S2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--AfdXs7wq--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/331744/3d794bbe-a226-4df2-907a-99300efc3c41.jpeg" alt="natterstefan image"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/natterstefan/how-to-add-a-readme-to-your-github-profile-2bo9" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;How to add a README to your GitHub profile&lt;/h2&gt;
      &lt;h3&gt;Stefan Natter 🇦🇹👨🏻‍💻 ・ Jul  9 ・ 1 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#github&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#tutorial&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;Pick a place in that &lt;code&gt;README&lt;/code&gt; where you'd like to display your most recent DEV posts. In &lt;a href="https://github.com/dylburger/dylburger"&gt;my &lt;code&gt;README&lt;/code&gt;&lt;/a&gt;, I've included this Markdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## DEV blog&lt;/span&gt;

I blog on &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;DEV&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;http://dev.to/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;. Check out my most recent post:

&lt;span class="c"&gt;&amp;lt;!-- dev --&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- devend --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notice the comments: &lt;code&gt;&amp;lt;!-- dev --&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;!-- devend --&amp;gt;&lt;/code&gt;. &lt;strong&gt;This is where the link to your newest DEV post will be added&lt;/strong&gt;. Put these comments wherever you'd like the link to live, and push that to your profile &lt;code&gt;README&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Visit &lt;a href="https://pipedream.com"&gt;https://pipedream.com&lt;/a&gt; and press the &lt;strong&gt;Sign In&lt;/strong&gt; button in the top-right to sign up for a Pipedream account:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2fWHhIi8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1591822072/dev.to%2520posts/Screen_Shot_2020-06-10_at_1.38.36_PM_zrnmrj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2fWHhIi8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1591822072/dev.to%2520posts/Screen_Shot_2020-06-10_at_1.38.36_PM_zrnmrj.png" alt="Sign up for Pipedream account"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you've signed up, open &lt;a href="https://pipedream.com/@dylan/update-github-profile-with-my-most-recent-dev-post-p_YyCV6y/edit"&gt;this Pipedream workflow&lt;/a&gt;. Click the &lt;strong&gt;Copy&lt;/strong&gt; button near the top-right to create a copy of the workflow in &lt;em&gt;your&lt;/em&gt; Pipedream account.&lt;/p&gt;

&lt;p&gt;Once you copy the workflow, you'll be asked to enter information specific to your DEV account and your Github profile. The first step of the workflow is the &lt;strong&gt;trigger&lt;/strong&gt; step. This workflow is triggered on new posts in an RSS feed. DEV generates an RSS feed for every user's posts at the URL &lt;code&gt;https://dev.to/feed/{username}&lt;/code&gt;, so we'll use this to track new posts.&lt;/p&gt;

&lt;p&gt;Replace &lt;code&gt;{username}&lt;/code&gt; with your DEV username, and enter your DEV RSS URL in the &lt;strong&gt;Feed URL&lt;/strong&gt; section of this step. By default, Pipedream will poll your RSS feed for new items every 15 minutes, which you can change:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lmj3-aop--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1594441766/dev.to%2520posts/Screen_Shot_2020-07-10_at_9.28.59_PM_tjldk4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lmj3-aop--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1594441766/dev.to%2520posts/Screen_Shot_2020-07-10_at_9.28.59_PM_tjldk4.png" alt="DEV RSS trigger step"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next step asks you to enter an &lt;strong&gt;Owner&lt;/strong&gt; and &lt;strong&gt;Repo&lt;/strong&gt; where your profile README lives. Enter your username in both fields:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AbLsd_vr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1594442522/dev.to%2520posts/Screen_Shot_2020-07-10_at_9.29.54_PM_qon31d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AbLsd_vr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1594442522/dev.to%2520posts/Screen_Shot_2020-07-10_at_9.29.54_PM_qon31d.png" alt="Profile Repo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The remaining steps fetch the current version of the &lt;code&gt;README&lt;/code&gt;, inserts the link for the newest post within the comments, and updates the &lt;code&gt;README&lt;/code&gt; with the newest content, pushing the newest link live. Since these steps interact with the Github API, you'll need to connect your Github account to the relevant steps. Scroll through the workflow and press the &lt;strong&gt;Connect Github&lt;/strong&gt; button in each step that requires it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XqjdGqC8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1594443047/dev.to%2520posts/Screen_Shot_2020-07-10_at_9.50.28_PM_jhzyr1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XqjdGqC8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1594443047/dev.to%2520posts/Screen_Shot_2020-07-10_at_9.50.28_PM_jhzyr1.png" alt="Connect Github button" width="200px"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, your workflow should be ready to run. Let's test it. At the top of your workflow, in the trigger step, you should see a sample event for the most recent post. You'll also see a &lt;strong&gt;Send Test Event&lt;/strong&gt; button. Press it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Psm3WsFs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1594443250/dev.to%2520posts/Screen_Shot_2020-07-10_at_9.52.40_PM_fbrbml.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Psm3WsFs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1594443250/dev.to%2520posts/Screen_Shot_2020-07-10_at_9.52.40_PM_fbrbml.png" alt="Send Test Event" width="600px"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This should run your workflow, adding the link to your profile:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--O_WJFmBs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1594433532/dev.to%2520posts/Screen_Shot_2020-07-10_at_7.11.45_PM_fpo5t2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--O_WJFmBs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1594433532/dev.to%2520posts/Screen_Shot_2020-07-10_at_7.11.45_PM_fpo5t2.png" alt="DEV post on profile" width="600px"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One final step - by default, the trigger step is turned &lt;strong&gt;Off&lt;/strong&gt; so you can test your workflow without events triggering your code until you're ready. Now that the workflow works, turn it &lt;strong&gt;On&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CGCg6Zsa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1594513887/dev.to%2520posts/Screen_Shot_2020-07-11_at_5.30.35_PM_uzguuf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CGCg6Zsa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1594513887/dev.to%2520posts/Screen_Shot_2020-07-11_at_5.30.35_PM_uzguuf.png" alt="DEV post on profile" width="600px"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that's it. New posts should trigger the workflow and update your profile automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can extend this idea to any data source, updating your profile when that data changes&lt;/strong&gt;. Pipedream &lt;a href="https://docs.pipedream.com/event-sources/"&gt;event sources&lt;/a&gt; allow you to trigger workflows on any event from any API, so you could trigger a profile rebuild on new tweets, new Github stars, changes to an Airtable table, and more. Try &lt;a href="https://pipedream.com/new"&gt;creating another workflow&lt;/a&gt; to see what else you can build, and share your workflows (or questions) in the comments!&lt;/p&gt;



&lt;p&gt;&lt;em&gt;I wrote this article with &lt;a href="https://code.visualstudio.com/"&gt;VS Code&lt;/a&gt;, on my Mac, and published it by running &lt;code&gt;git push&lt;/code&gt;. All my DEV posts are tracked by Git, and I can manage them from my local machine. Learn how to do this yourself!&lt;/em&gt;&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/dylburger" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SgtZRw8D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--iLjZNdrl--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/234988/057b1fdb-03ed-4e08-a3e7-b477d8a6c3ef.jpeg" alt="dylburger image"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/dylburger/publish-dev-articles-from-a-git-repo-with-github-pipedream-505j" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Publish DEV articles from a Git repo, with Github + Pipedream&lt;/h2&gt;
      &lt;h3&gt;Dylan J. Sather ・ Jun 10 ・ 7 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#pipedream&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#github&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#meta&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#git&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;



</description>
      <category>github</category>
      <category>pipedream</category>
      <category>meta</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Publish DEV articles from a Git repo, with Github + Pipedream</title>
      <dc:creator>Dylan J. Sather</dc:creator>
      <pubDate>Wed, 10 Jun 2020 21:18:35 +0000</pubDate>
      <link>https://dev.to/dylburger/publish-dev-articles-from-a-git-repo-with-github-pipedream-505j</link>
      <guid>https://dev.to/dylburger/publish-dev-articles-from-a-git-repo-with-github-pipedream-505j</guid>
      <description>&lt;p&gt;I wrote this article with &lt;a href="https://code.visualstudio.com/"&gt;VS Code&lt;/a&gt;, on my Mac, and published it by running &lt;code&gt;git push&lt;/code&gt;. All my DEV posts are tracked by Git, and I can manage them from my local machine.&lt;/p&gt;

&lt;p&gt;Below, I'll tell you why I set this up, and show you how easy it is to configure for your own posts.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How this works&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I'll show you the finished product first so you see how this works end-to-end.&lt;/p&gt;

&lt;p&gt;First, I create a new Markdown file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;My First DEV Post&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

Hello, world!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;and push it to Github:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;git add my-first-post.md
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Adding first post"&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As soon as I push, &lt;a href="https://pipedream.com/@dylan/publish-dev-articles-from-github-repo-p_gYCqpz/edit"&gt;this Pipedream workflow&lt;/a&gt; creates a draft of that post in my DEV account:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3W7jHHTu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1591819376/dev.to%2520posts/Screen_Shot_2020-06-10_at_1.01.56_PM_ovbijo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3W7jHHTu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1591819376/dev.to%2520posts/Screen_Shot_2020-06-10_at_1.01.56_PM_ovbijo.png" alt="My First Article Draft"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can edit and &lt;code&gt;git push&lt;/code&gt; again, and the draft will update. When I'm ready to publish, I add &lt;code&gt;published: true&lt;/code&gt; to the top of my YAML front matter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;My First DEV Post&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

Hello, world!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;git add my-first-post.md
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Publishing first post"&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;and it's published:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0dquGXbo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1591819622/dev.to%2520posts/Screen_Shot_2020-06-10_at_1.04.19_PM_qprplu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0dquGXbo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1591819622/dev.to%2520posts/Screen_Shot_2020-06-10_at_1.04.19_PM_qprplu.png" alt="My first post, published" width="600px"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How cool is that!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why manage DEV posts in a Git repo?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I like writing code in &lt;a href="https://code.visualstudio.com/"&gt;VS Code&lt;/a&gt;, with all its keyboard shortcuts, plugins, and other goodies I've added to make it my own. When I write Markdown in the DEV editor, I miss my local setup. That's the primary reason I set this up: &lt;strong&gt;I get to write articles in my own editor, with my own shortcuts and tools&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But storing your articles in a Github repo carries other benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every change you make to an article is tracked by Git. You can compare the changes you made between versions, or revert to an older version. If you make your repo public, anyone can open a pull request to fix typos, broken links, and more.&lt;/li&gt;
&lt;li&gt;You can run &lt;a href="https://git-scm.com/book/fa/v2/Customizing-Git-Git-Hooks"&gt;Git hooks&lt;/a&gt; or &lt;a href="https://help.github.com/en/actions"&gt;Github Actions&lt;/a&gt; to automate basic tasks: for example, you could run a script to validate your Markdown or spell check it before your change gets commited your repo.&lt;/li&gt;
&lt;li&gt;You can trigger other, more complex automations on every &lt;code&gt;git push&lt;/code&gt; using &lt;a href="https://pipedream.com"&gt;Pipedream&lt;/a&gt; workflows. For example, once you publish your article to DEV, you could automatically post its link to Twitter.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How to set this up&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;To get started, you'll need a &lt;a href="https://github.com"&gt;Github&lt;/a&gt; account and a &lt;a href="https://dev.to/settings/account"&gt;DEV API key&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First, &lt;a href="https://github.com/new"&gt;create a new Github repo&lt;/a&gt; to manage your DEV posts:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qnkdur56--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kgmyzh4vf6xmj1tbpuj1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qnkdur56--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kgmyzh4vf6xmj1tbpuj1.png" alt="DEV posts Github repo title"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then clone the repo locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:[YOUR_USERNAME]/dev-to-posts.git
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, you'll configure a &lt;a href="https://pipedream.com"&gt;Pipedream&lt;/a&gt; workflow to publish your articles using the &lt;a href="https://docs.dev.to/api/"&gt;DEV API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pipedream is an integration platform for developers&lt;/strong&gt;. For this flow, Pipedream works like &lt;a href="https://github.com/features/actions"&gt;Github Actions&lt;/a&gt;: each time you push new Markdown files to your repo, the workflow runs. Pipedream workflows are written using &lt;a href="https://docs.pipedream.com/workflows/steps/actions/#using-existing-actions"&gt;pre-built actions&lt;/a&gt; and &lt;a href="https://docs.pipedream.com/workflows/steps/code/"&gt;custom Node.js code&lt;/a&gt;, each of which can connect to hundreds of API integrations. You can run this workflow &lt;strong&gt;for free&lt;/strong&gt; on Pipedream's &lt;a href="https://docs.pipedream.com/pricing/"&gt;free tier&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Visit &lt;a href="https://pipedream.com"&gt;https://pipedream.com&lt;/a&gt; and press the &lt;strong&gt;Sign In&lt;/strong&gt; button in the top-right to sign up for a Pipedream account:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2fWHhIi8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1591822072/dev.to%2520posts/Screen_Shot_2020-06-10_at_1.38.36_PM_zrnmrj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2fWHhIi8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1591822072/dev.to%2520posts/Screen_Shot_2020-06-10_at_1.38.36_PM_zrnmrj.png" alt="Sign up for Pipedream account"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you've signed up, &lt;a href="https://pipedream.com/@dylan/publish-dev-articles-from-github-repo-p_gYCqpz/edit"&gt;&lt;strong&gt;open this DEV workflow&lt;/strong&gt;&lt;/a&gt; and click &lt;strong&gt;Copy&lt;/strong&gt; near the top-right. &lt;strong&gt;This creates a copy of my workflow in your account, that will run for &lt;em&gt;your&lt;/em&gt; DEV repo&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;First, you'll be asked to configure the &lt;strong&gt;Trigger&lt;/strong&gt; step. This workflow runs every time Markdown files are added or modified in your repo. You'll just need to connect your Github account and select your repo from the dropdown menu:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kpBBwwyo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1590435452/dev.to%2520posts/connect-github-account_vbugvb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kpBBwwyo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1590435452/dev.to%2520posts/connect-github-account_vbugvb.png" alt="Connect Github account" width="200px"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ot-Egz8Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1590435484/dev.to%2520posts/choose-dev-to-repo_sog0ux.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ot-Egz8Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1590435484/dev.to%2520posts/choose-dev-to-repo_sog0ux.png" alt="Choose DEV posts repo" width="300px"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then click &lt;strong&gt;Create Source&lt;/strong&gt; at the bottom right of the trigger step. This will configure a &lt;a href="https://requestbin.com/blog/working-with-webhooks/"&gt;webhook&lt;/a&gt; in your Github repo that notifies this workflow anytime a push happens.&lt;/p&gt;

&lt;p&gt;You'll also need to turn this trigger step &lt;strong&gt;On&lt;/strong&gt; to make sure it runs automatically on new pushes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GsiLO5wj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1591823665/dev.to%2520posts/Screen_Shot_2020-06-10_at_2.13.12_PM_q5e4jm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GsiLO5wj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1591823665/dev.to%2520posts/Screen_Shot_2020-06-10_at_2.13.12_PM_q5e4jm.png" alt="Turn trigger step on"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next &lt;a href="https://docs.pipedream.com/workflows/steps/"&gt;step&lt;/a&gt; of the workflow - &lt;code&gt;create_and_update_dev_posts&lt;/code&gt; - runs Node.js code to push this Markdown to the DEV API. If you add a &lt;em&gt;new&lt;/em&gt; article to your repo, the workflow creates a new DEV article. If you're pushing an update to an existing article, the workflow updates that DEV article.&lt;/p&gt;

&lt;p&gt;To get this step working, you'll just need to connect your DEV API key. &lt;strong&gt;Press the Connect Account button near the top of this step.&lt;/strong&gt; Like in the trigger step, this will prompt you to enter your DEV API key:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KA01uh-f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1591814490/dev.to%2520posts/Screen_Shot_2020-06-10_at_11.36.01_AM_zp6hko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KA01uh-f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1591814490/dev.to%2520posts/Screen_Shot_2020-06-10_at_11.36.01_AM_zp6hko.png" alt="Connect DEV API key"&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now you're ready to push your first article&lt;/strong&gt;. Create a new Markdown file in the Git repo you cloned above, adding the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;My First DEV Post&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

Hello, world!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Add, commit, and push this file to Github:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;git add my-first-post.md
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Adding first post"&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As soon as you push these changes, you should see a new event appear in your Pipedream workflow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dn0fske7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1591819326/dev.to%2520posts/Screen_Shot_2020-06-10_at_12.57.13_PM_qu31r1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dn0fske7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1591819326/dev.to%2520posts/Screen_Shot_2020-06-10_at_12.57.13_PM_qu31r1.png" alt="New Pipedream event"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This triggers the workflow, creating a new draft article in DEV:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3W7jHHTu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1591819376/dev.to%2520posts/Screen_Shot_2020-06-10_at_1.01.56_PM_ovbijo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3W7jHHTu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1591819376/dev.to%2520posts/Screen_Shot_2020-06-10_at_1.01.56_PM_ovbijo.png" alt="New DEV draft"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To publish the article, add &lt;code&gt;published: true&lt;/code&gt; to the &lt;a href="https://dev.to/p/editor_guide"&gt;YAML front matter&lt;/a&gt; section at the top of your file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;My First DEV Post&lt;/span&gt;
&lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;git add my-first-post.md
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Publishing first post"&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You should see your brand new article show up, published, in DEV:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0dquGXbo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1591819622/dev.to%2520posts/Screen_Shot_2020-06-10_at_1.04.19_PM_qprplu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0dquGXbo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/dkbxegavp/image/upload/v1591819622/dev.to%2520posts/Screen_Shot_2020-06-10_at_1.04.19_PM_qprplu.png" alt="My first post, published, again" width="600px"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To unpublish, set the &lt;code&gt;published&lt;/code&gt; flag to &lt;code&gt;false&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;My First DEV Post&lt;/span&gt;
&lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Since you own this copy of the Pipedream workflow, you can modify the code in any way you'd like&lt;/strong&gt;. You could update the DEV step to change how articles get published, or add a step to send a message to Slack, or post a tweet, any time you publish a new article. If you make any edits, share them in the comments below or publish your own article about it!&lt;/p&gt;

&lt;p&gt;The rest of this post addresses other details of the integration, like managing images in articles, and other &lt;a href="https://dev.to/p/editor_guide"&gt;YAML front matter&lt;/a&gt; you can use to change details of your posts.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Making changes to articles in the DEV UI&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Any change you make to articles in the DEV UI will get overwritten unless you also make those changes to the file in your repo&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How to include images in posts&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Currently, DEV doesn't have an API for uploading images, so I'm using &lt;a href="https://cloudinary.com/"&gt;Cloudinary&lt;/a&gt; to host mine, referencing the Cloudinary URL in my Markdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;![&lt;/span&gt;&lt;span class="nv"&gt;My image&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://res.cloudinary.com/dkbxegavp/image/upload/v1590355743/dev.to%20posts/dev-to-draft_bmqlgb.png&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In theory, this workflow could be changed to reference local images in your article Markdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;![&lt;/span&gt;&lt;span class="nv"&gt;My image&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;./images/my-image.png&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Once you commit the images to your repo, the Pipedream workflow could upload the images to Cloudinary directly, and change the article references to point to the hosted version, instead.&lt;/p&gt;

&lt;p&gt;Remember — the Pipedream workflow you copied above is yours to modify. &lt;strong&gt;You can add any logic you'd like to that workflow, uploading images to Cloudinary or another hosting provider, or include anything else custom to your publishing process&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Default branch only&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The workflow processes commits on the default repo branch only (&lt;code&gt;master&lt;/code&gt;, unless you've changed it). Commits to other branches are ignored, until they're merged to your default branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;One Markdown file : One post&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Each Markdown file you push to your DEV repo generates its own DEV post. Your files must have a &lt;code&gt;.md&lt;/code&gt; extension for them to be processed. Any non-Markdown files are ignored by the workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Front Matter&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The Pipedream workflow uses the same YAML front matter &lt;a href="https://dev.to/p/editor_guide"&gt;that DEV supports&lt;/a&gt; to update the published state, title, tags, and more.&lt;/p&gt;

&lt;p&gt;None of the front matter variables are required. If you include no front matter, your article will be saved to DEV as a draft, with a title based on the filename (see below).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Titles&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;title&lt;/code&gt; front matter variable is always used if present.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;title&lt;/code&gt; is absent, the workflow will use the filename of the article, converting hyphens and underscores as spaces. Case is preserved:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-first-post.md -&amp;gt; my first post
My_First_Post.md -&amp;gt; My First Post
My First Post.md -&amp;gt; My First Post
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If you rely on this convention and then later want to set the &lt;code&gt;title&lt;/code&gt; front matter variable, the workflow will update the title according to the variable's value, and use that instead of the filename moving forward.&lt;/p&gt;

</description>
      <category>pipedream</category>
      <category>github</category>
      <category>meta</category>
      <category>git</category>
    </item>
    <item>
      <title>The simplest way to run Node.js code on a schedule</title>
      <dc:creator>Dylan J. Sather</dc:creator>
      <pubDate>Fri, 03 Apr 2020 17:58:17 +0000</pubDate>
      <link>https://dev.to/dylburger/the-simplest-way-to-run-node-js-code-on-a-schedule-589i</link>
      <guid>https://dev.to/dylburger/the-simplest-way-to-run-node-js-code-on-a-schedule-589i</guid>
      <description>&lt;p&gt;I remember the first time I discovered &lt;code&gt;cron&lt;/code&gt;. I’ve been obsessed with automation my whole life, and as a fledgling developer &lt;code&gt;cron&lt;/code&gt; became my workhorse. &lt;/p&gt;

&lt;p&gt;At the time, I worked at an IT help desk. I used &lt;code&gt;cron&lt;/code&gt; for email reminders, automated reports, backups: anything I could schedule, I did.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1gXEZM5F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/c1J4hwTAE45ELFjJEXXvmYs9jFR_fVho6Jj3qPq7cop26H7L_rggwiNzdeuzjtigsZrqtwHE_UlyRvYAqhR5yGHuW9UF4od0mRO03E7gmKns76ygy_vesirU1kzUt3JoDZMppPaP" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1gXEZM5F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/c1J4hwTAE45ELFjJEXXvmYs9jFR_fVho6Jj3qPq7cop26H7L_rggwiNzdeuzjtigsZrqtwHE_UlyRvYAqhR5yGHuW9UF4od0mRO03E7gmKns76ygy_vesirU1kzUt3JoDZMppPaP" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cron&lt;/code&gt; is simple: you just tell it what you want to run, when you want to run it:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 0 * * *  node script.js  # run script.js once a day
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;But it comes with tradeoffs: primarily, you have to operate a server to run it, which costs money and time. If you don’t already have a server where you can run jobs, setting one up just for &lt;code&gt;cron&lt;/code&gt; is far from ideal. &lt;/p&gt;

&lt;p&gt;In the era of the cloud, it’s also outdated. Cron jobs are simple only if you cut your teeth on Linux and can effectively administer a server. This is an inaccessible option for many modern devs who operate far up the stack.&lt;/p&gt;

&lt;p&gt;So it’s no surprise that we have other choices for scheduling code in 2020:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/services-cloudwatchevents-expressions.html"&gt;AWS Lambda + CloudWatch Events&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/scheduler"&gt;Google Cloud Scheduler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://airflow.apache.org/"&gt;Airflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/job/automated-tasks-with-cron-jobs/"&gt;Kubernetes CronJobs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/scheduling_tasks.html"&gt;ECS Tasks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These options work well in their intended context: Lambda is great when you’re glueing together AWS resources. Kubernetes CronJobs or ECS Tasks are ideal when your code runs in a container. Airflow is great when you have complex dependencies between jobs, for example in data pipelines. &lt;/p&gt;

&lt;p&gt;But when you just want to run a script on a schedule, they’re overkill.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enter Pipedream
&lt;/h3&gt;

&lt;p&gt;&lt;a href="http://pipedream.com/"&gt;Pipedream&lt;/a&gt; is an integration platform for developers. You run serverless &lt;a href="https://docs.pipedream.com/workflows/"&gt;workflows&lt;/a&gt; - any Node.js code - triggered by HTTP requests, timers, emails, and more. You can also use &lt;a href="https://docs.pipedream.com/workflows/steps/actions/"&gt;pre-built actions&lt;/a&gt; to connect to hundreds of APIs and apps - actions are just Node functions that perform common operations against these APIs.&lt;/p&gt;

&lt;p&gt;You can sign up and run any Node code on a schedule in &lt;strong&gt;less than one minute&lt;/strong&gt;. Watch this video or follow the step-by-step instructions below:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/PwjVR0dj-Hk"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;First, install the Pipedream CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://cli.pipedream.com/install | sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cd&lt;/code&gt; into a directory with a Node script you’d like to run, or just create a simple one-line script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'console.log("Hello, world")'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cronjob.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;then deploy that script to Pipedream, running it every 15 seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;pd deploy &lt;span class="nt"&gt;--run&lt;/span&gt; cronjob.js &lt;span class="nt"&gt;--timer&lt;/span&gt; &lt;span class="nt"&gt;--frequency&lt;/span&gt; 15s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This step will prompt you to sign up for Pipedream if you haven’t already. The &lt;code&gt;pd&lt;/code&gt; CLI will deploy your code to Pipedream, and print logs as the script produces them. You can press &lt;code&gt;Ctrl-C&lt;/code&gt; to quit the real-time stream, and listen for new logs later by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;pd logs cronjob-js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can delete this job and all its logs by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;pd delete cronjob-js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can list all running jobs with &lt;code&gt;pd list&lt;/code&gt;, update their code or schedule with &lt;code&gt;pd update&lt;/code&gt;, and more. &lt;a href="https://github.com/PipedreamHQ/pipedream/tree/master/interfaces/timer"&gt;See the docs to learn more&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A practical example: send an HTTP request on a schedule
&lt;/h3&gt;

&lt;p&gt;If you already host code at some URL, and just want to trigger a job to run via HTTP request, you can use Pipedream to send that request on a schedule. In this example, we’ll hit the &lt;a href="https://swapi.co/"&gt;Star Wars API&lt;/a&gt; once a day.&lt;/p&gt;

&lt;p&gt;First, open your editor and create a file called &lt;code&gt;http.js&lt;/code&gt; with the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://swapi.co/api/films/`&lt;/span&gt;  &lt;span class="c1"&gt;// replace with your URL&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;pd deploy &lt;span class="nt"&gt;--run&lt;/span&gt; cronjob.js &lt;span class="nt"&gt;--timer&lt;/span&gt; &lt;span class="nt"&gt;--cron&lt;/span&gt; “0 0 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;”
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This will make a GET request to SWAPI, once a day at midnight, but you can modify this script to send any HTTP request, at any schedule. See our guide on &lt;a href="https://docs.pipedream.com/workflows/steps/code/nodejs/http-requests/"&gt;making HTTP requests in Node&lt;/a&gt; for more examples.&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://github.com/PipedreamHQ/pipedream/tree/master/interfaces/timer"&gt;the README on cron jobs&lt;/a&gt; or the &lt;a href="https://docs.pipedream.com/"&gt;Pipedream docs&lt;/a&gt; to learn more about the platform at large.&lt;/p&gt;

&lt;h3&gt;
  
  
  We ❤️ Feedback
&lt;/h3&gt;

&lt;p&gt;We're eager to hear your feedback. We're shipping new features every day - check out &lt;a href="https://github.com/PipedreamHQ/roadmap"&gt;our roadmap&lt;/a&gt; and &lt;a href="https://docs.pipedream.com/support/"&gt;reach out anytime via email or Slack&lt;/a&gt; to let us know what we can improve.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>cron</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Analyzing Github Issue Reactions</title>
      <dc:creator>Dylan J. Sather</dc:creator>
      <pubDate>Wed, 27 Nov 2019 19:45:18 +0000</pubDate>
      <link>https://dev.to/dylburger/analyzing-github-issue-reactions-13p4</link>
      <guid>https://dev.to/dylburger/analyzing-github-issue-reactions-13p4</guid>
      <description>&lt;p&gt;&lt;em&gt;Prioritizing issues with 👍s, ❤️s and 🎉s&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At &lt;a href="https://pipedream.com/"&gt;Pipedream&lt;/a&gt;, we use Github Issues to track feature requests, bugs, and new app integrations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lHYFzGIJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2296/1%2AiRV79H3q3LoLBH6JLy0oaA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lHYFzGIJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2296/1%2AiRV79H3q3LoLBH6JLy0oaA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anytime a user has a new idea, we send them &lt;a href="https://github.com/PipedreamHQ/roadmap"&gt;to the roadmap&lt;/a&gt; to make sure the idea gets captured.&lt;/p&gt;

&lt;p&gt;Anytime someone suggests an idea that’s already been captured, we send them to the roadmap to add a reaction (a 👍, ❤️ or 🎉) to the issue.&lt;/p&gt;

&lt;p&gt;We’re diligent about pushing people to the roadmap because we want to prioritize the most requested items. Reactions are the best way to collect that data.&lt;/p&gt;

&lt;p&gt;Unfortunately, Github doesn’t provide high-level dashboards on issue reactions. You can &lt;a href="https://help.github.com/en/github/searching-for-information-on-github/sorting-search-results#sort-by-reactions"&gt;sort issues&lt;/a&gt; by the total number of reactions in the Issues UI:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;is:issue is:open sort:reactions-desc&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;but you can’t see the reaction count without digging into the issue itself:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JwQRGlRz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AxVlofjt4KMhVV0kFwcSjjw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JwQRGlRz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AxVlofjt4KMhVV0kFwcSjjw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nor can you compare the count of reactions &lt;em&gt;across&lt;/em&gt; issues.&lt;/p&gt;

&lt;p&gt;To help us prioritize the right issues, we needed to answer question like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;What are the top issues this week?&lt;/strong&gt; (&lt;em&gt;What should we be focused on?&lt;/em&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;What issues are &lt;em&gt;trending&lt;/em&gt; this week?&lt;/strong&gt; (&lt;em&gt;Even if it’s not in the top 5, is there an issue getting lots of love this week we should be paying attention to?&lt;/em&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Who’s opening the most issues? Who’s reacting to the most issues?&lt;/strong&gt; (&lt;em&gt;Who are the most engaged users, and how can we prioritize their issues and get more feedback?)&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We built &lt;a href="https://pipedream.com/@dylburger/send-issue-reactions-for-a-repo-to-a-google-sheet-p_NMCar1/readme"&gt;a workflow&lt;/a&gt; to collect this data, and a Google sheet and Jupyter notebook to drive the analysis. &lt;strong&gt;I’ll show you how this works and how to use it for your own repo.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pulling Issue Reactions, saving to Google Sheets
&lt;/h2&gt;

&lt;p&gt;This &lt;a href="https://pipedream.com/@dylburger/send-issue-reactions-for-a-repo-to-a-google-sheet-p_NMCar1/readme"&gt;Pipedream workflow&lt;/a&gt; pulls reactions for all open issues in your repo once a day, saving them to a Google Sheet where can run more analysis:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--A9IB2NLT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4628/1%2A-zRE5r-SpEHWqUylf3Lusg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9IB2NLT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4628/1%2A-zRE5r-SpEHWqUylf3Lusg.png" alt="This gives us the basic data we need to run analysis on reactions by issue, author, and more."&gt;&lt;/a&gt;&lt;em&gt;This gives us the basic data we need to run analysis on reactions by issue, author, and more.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Follow the instructions in the workflow’s README&lt;/strong&gt; to connect your Github and Google Sheets accounts, and enter the necessary values in the fields of each step (for example, the Github repo and spreadsheet you’d like to save data to).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iN4JLuCc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2292/1%2AqouGcty4X2FY0dvaGjRuGA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iN4JLuCc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2292/1%2AqouGcty4X2FY0dvaGjRuGA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once that’s done, press the &lt;strong&gt;Run Now&lt;/strong&gt; button to collect your first set of reaction data:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PJ7BiTSB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AE19gVdzvTHI1sQVVB4PUCw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PJ7BiTSB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AE19gVdzvTHI1sQVVB4PUCw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This workflow uses some built-in Pipedream actions to save data to Google Sheets. But when you forked the workflow, you created a copy you can modify however you’d like.&lt;/p&gt;

&lt;p&gt;For example, you can swap out the Google Sheets steps if you’d like to save data to a database, Airtable, or any destination (you can use any &lt;a href="https://docs.pipedream.com/workflows/steps/actions/"&gt;pre-built actions&lt;/a&gt; or &lt;a href="https://docs.pipedream.com/workflows/steps/code/"&gt;run any Node.js code&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  How we make requests to the Github API
&lt;/h3&gt;

&lt;p&gt;I use the amazing &lt;a href="https://octokit.github.io/rest.js/"&gt;octokit/rest.js&lt;/a&gt; package to facilitate interaction with the Github API. This sets the necessary HTTP headers to enable the reactions API (&lt;a href="https://developer.github.com/changes/2016-05-12-reactions-api-preview/"&gt;it’s still in preview&lt;/a&gt;), and handles pagination and retries transparently.&lt;/p&gt;

&lt;p&gt;When the workflow runs the &lt;strong&gt;fetch_issues_reaction_data&lt;/strong&gt; step, Pipedream provides a fresh OAuth access token in the variable &lt;code&gt;auths.github.oauth_access_token&lt;/code&gt; that you can use to authorize requests (&lt;a href="https://docs.pipedream.com/connected-accounts/#connected-accounts"&gt;read more about connected accounts in the docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This all means you can fetch any data from the Github API with just a few lines of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Octokit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@octokit/rest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@octokit/plugin-retry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;octokit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Octokit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;auths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oauth_access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;previews&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;squirrel-girl-preview&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// See https://developer.github.com/v3/previews/#reactions&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;Retrieve&lt;/span&gt; &lt;span class="nx"&gt;owner&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="nx"&gt;passed&lt;/span&gt; &lt;span class="nx"&gt;by&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;octokit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listForRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;issues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;octokit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Exploratory Analysis in Google Sheets
&lt;/h2&gt;

&lt;p&gt;I like to explore my data with a &lt;a href="https://support.google.com/docs/answer/1272900?co=GENIE.Platform%3DDesktop&amp;amp;hl=en"&gt;pivot table&lt;/a&gt; before ever jumping into a more complex analysis with SQL or a Jupyter notebook.&lt;/p&gt;

&lt;p&gt;Pivot tables support grouping, aggregate functions, sorting, and more, all in a friendly GUI.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What issues have the most reactions?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;It’s nice to have a single, ordered table of the top issues.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7CCB_lYb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2748/1%2AaYnqXTTFrSZWRjVCEL_h5Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7CCB_lYb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2748/1%2AaYnqXTTFrSZWRjVCEL_h5Q.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What issues have seen the most reactions in the past week?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We need to see what issues are &lt;em&gt;trending&lt;/em&gt;. If it’s not a large project and can be tackled quickly, fixing a trending issue proves our responsiveness to users and helps build trust.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HzzZZ6v5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2588/1%2ADTjIsyJUMAC_XDlL4Kk6JQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HzzZZ6v5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2588/1%2ADTjIsyJUMAC_XDlL4Kk6JQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Reactions by User
&lt;/h3&gt;

&lt;p&gt;It’s nice to know who our top “reactors” are, so see if that correlates with engagement in Slack or other channels.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kMROj0zK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2728/1%2A213eRTvdU_oePkAiTuRhVg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kMROj0zK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2728/1%2A213eRTvdU_oePkAiTuRhVg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A deeper dive with a Jupyter notebook
&lt;/h2&gt;

&lt;p&gt;I wanted to observe how the cumulative sum of reactions for an issue move over time. This helps us understand whether the top issue recently moved to the top, or whether it’s been the top issue for weeks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DHxwnxCI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3728/1%2A4_8OrkbmjkqsuTPsiAprUw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DHxwnxCI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3728/1%2A4_8OrkbmjkqsuTPsiAprUw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I was also interested to analyze the number of total reactions over time, to see if it’s increasing, on average, or whether it correlates with dates when we’ve promoted our roadmap in our &lt;a href="https://pipedream.com/community"&gt;Slack community&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nDFfAmB0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AQ4_G79NFHWayln7fUlrteA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nDFfAmB0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AQ4_G79NFHWayln7fUlrteA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a new product, the number of reactions per day is small, so this doesn’t tell a rich story yet. You can help us by using Pipedream and 👍 your &lt;a href="https://github.com/PipedreamHQ/roadmap"&gt;favorite issues&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;I created these charts with pandas and matplotlib, using a a Jupyter notebook. You can see the code in &lt;a href="https://github.com/dylburger/github-reactions-analysis"&gt;this Github repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extending this analysis
&lt;/h2&gt;

&lt;p&gt;There are a number of other questions that would be interesting to analyze in the future:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Under what conditions are different types of emojis ( 👍, ❤️, 🎉, and more) used? Does the use of a specific emoji correlate with issue closure or activity?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Does an issue get attention on a project after a certain number of reactions?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Do issues with labels (or a specific label) get more reactions than issues with no labels?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Questions like these have been explored in &lt;a href="https://homepages.dcc.ufmg.br/~mtov/pub/2019-sbes.pdf"&gt;research papers&lt;/a&gt;, so there’s a lot of prior art you can read up on to get ideas to apply to your own repo.&lt;/p&gt;

&lt;p&gt;Let us know what kind of analyses you end up doing in the comments below!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was originally posted &lt;a href="https://medium.com/@dylan.sather/analyzing-github-issue-reactions-5281358839c1"&gt;on Medium&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>github</category>
      <category>pipedream</category>
      <category>node</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Airflow is overkill</title>
      <dc:creator>Dylan J. Sather</dc:creator>
      <pubDate>Tue, 29 Oct 2019 17:42:09 +0000</pubDate>
      <link>https://dev.to/dylburger/airflow-is-overkill-1ge</link>
      <guid>https://dev.to/dylburger/airflow-is-overkill-1ge</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published &lt;a href="https://medium.com/@dylan.sather/airflow-is-overkill-8c7239664cc5" rel="noopener noreferrer"&gt;on Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Last year, I taught a Data Analytics bootcamp at UC Berkeley Extension. In 6 months, students who'd never programmed before learned Excel, Python, &lt;code&gt;pandas&lt;/code&gt;, JavaScript, &lt;a href="https://d3js.org" rel="noopener noreferrer"&gt;D3&lt;/a&gt;, basic machine learning, and more.&lt;/p&gt;

&lt;p&gt;What would you guess was the toughest thing for most students to learn? Machine learning? JavaScript and D3?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deploying a Flask app to Heroku.&lt;/strong&gt;&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F6fmbfo6wll80owipqwjc.jpg" 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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F6fmbfo6wll80owipqwjc.jpg" alt="One Does Not Simply Deploy to Production"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I empathize. When I first learned to program, and got through the pain of setting up my environment, it was easy to iterate. I used a REPL to experiment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; name = "World"
&amp;gt;&amp;gt;&amp;gt; print(f'Hello, {name}')
Hello, World
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or wrote a script that I could quickly run and test on my local machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python hello.py
Hello, World
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I'm asked to deploy an app to a service like Heroku. Suddenly, I need to learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The Heroku CLI&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devcenter.heroku.com/articles/procfile" rel="noopener noreferrer"&gt;The &lt;code&gt;Procfile&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;and &lt;a href="https://gunicorn.org" rel="noopener noreferrer"&gt;Gunicorn&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What's more, each deploy can take minutes, so each mistake takes longer to troubleshoot and fix.&lt;/p&gt;

&lt;p&gt;I dealt with the pain of deploying apps before Heroku, so I appreciate its simplicity. But my students lacked that context, so Heroku seemed needlessly complex.&lt;/p&gt;

&lt;h2&gt;
  
  
  Airflow was my Heroku
&lt;/h2&gt;

&lt;p&gt;At Pipedream, we use Airflow to run scheduled jobs.&lt;/p&gt;

&lt;p&gt;When I first used Airflow, I needed to run a simple Python script on a schedule. I wrote that script in 15 minutes. Then I setup Airflow. This involved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reading the &lt;code&gt;README&lt;/code&gt; for the &lt;a href="https://github.com/helm/charts/tree/master/stable/airflow" rel="noopener noreferrer"&gt;Helm chart&lt;/a&gt; (we use Kubernetes)&lt;/li&gt;
&lt;li&gt;Realizing I failed to include some specific config, trying again&lt;/li&gt;
&lt;li&gt;Realizing there was a typo in the documented config, trying again&lt;/li&gt;
&lt;li&gt;Setting up some Kubernetes secrets&lt;/li&gt;
&lt;li&gt;Troubleshooting a &lt;a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-persistent-volume-storage/#create-a-persistentvolumeclaim" rel="noopener noreferrer"&gt;PersistentVolumeClaim&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Setting some env vars and &lt;a href="https://airflow.apache.org/howto/set-config.html" rel="noopener noreferrer"&gt;&lt;code&gt;airflow.cfg&lt;/code&gt; config&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;And lots of deep dives into the Airflow docs and StackOverflow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I felt a lot like my students did with Heroku. I just wanted it to work, but had to learn a handful of new concepts and tools all at once.&lt;/p&gt;

&lt;p&gt;Now, I love Airflow. I appreciate its dependency management, backfill, automatic retries, and all the things that make it a great job scheduler. But I didn't need any of that for my original use case. I just wanted to run a cron job.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cron&lt;/code&gt; has limitations. It doesn't have built-in error handling or retry, and has to be run on a machine that someone has to maintain. Modern job schedulers improve on it substantially, but lose its simplicity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep it simple
&lt;/h2&gt;

&lt;p&gt;As a teacher and developer, I care very deeply about improving developer tools. I joined &lt;a href="https://pipedream.com" rel="noopener noreferrer"&gt;Pipedream&lt;/a&gt; for this reason.&lt;/p&gt;

&lt;p&gt;When we built the Cron Scheduler, we tried to marry the simplicity of cron with a powerful programming environment. I believe it's the easiest way to run a job on a schedule. There's no infra or cloud resources to manage, and &lt;a href="https://docs.pipedream.com/pricing/" rel="noopener noreferrer"&gt;it's free&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When I schedule a job on Pipedream, I:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a workflow&lt;/li&gt;
&lt;li&gt;Set the schedule&lt;/li&gt;
&lt;li&gt;Write the code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I created this 1 minute video to show you how this works:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/wjIDvPy0aU0"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Every workflow on Pipedream begins with a &lt;a href="https://docs.pipedream.com/workflows/steps/triggers/" rel="noopener noreferrer"&gt;trigger&lt;/a&gt;: HTTP requests, emails, or a &lt;strong&gt;cron schedule&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;After I select my trigger, I add steps: &lt;a href="https://docs.pipedream.com/workflows/steps/code/" rel="noopener noreferrer"&gt;Run any Node.js code&lt;/a&gt; (&lt;a href="https://github.com/PipedreamHQ/roadmap/issues/1" rel="noopener noreferrer"&gt;Python coming soon&lt;/a&gt;), send an HTTP request or an email, or interact with the APIs of built-in apps like Slack, Github, Google, Reddit, Shopify, AWS, and more.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fogl49m7s8b9w5johs6nf.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fogl49m7s8b9w5johs6nf.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Standard output and error logs appear directly below the step that produced them. If a job fails, I'm notified via email, and I can replay that job in one click.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fw85xg1oq65g4pbtcbifg.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fw85xg1oq65g4pbtcbifg.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Workflow templates are also public. I can share them with anyone, and they can fork, modify, and run them their own accounts.&lt;/p&gt;

&lt;p&gt;Take a look at these examples and try running one on your own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pipedream.com/@dylburger/r-doggos-subreddit-top-posts-slack-p_6lCL1Z/readme" rel="noopener noreferrer"&gt;Send popular /r/doggos posts to a Slack channel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pipedream.com/@pravin/query-postgres-database-and-send-message-to-sms-email-and-slack-p_rvCe1k/readme" rel="noopener noreferrer"&gt;Query a PostgreSQL DB on a schedule, send results to Slack, email, or SMS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pipedream.com/@pravin/process-new-items-from-an-rss-feed-on-a-schedule-p_brC5qv/edit" rel="noopener noreferrer"&gt;Run Node.js code on each new item from an RSS feed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pipedream.com/@dylburger/google-alerts-for-hacker-news-p_ezCJ7p/readme" rel="noopener noreferrer"&gt;Google Alerts for Hacker News&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pipedream.com/@gilesdring/publish-ogt-github-pages-site-p_brC5aN/edit" rel="noopener noreferrer"&gt;Rebuild your Github pages site nightly&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  We ❤️ feedback
&lt;/h2&gt;

&lt;p&gt;Our &lt;a href="https://medium.com/@todsacerdoti/introducing-pipedream-bbca9dde0dc6" rel="noopener noreferrer"&gt;beta release&lt;/a&gt; is our first effort at making job scheduling and workflow management easy, but we want you to use the product and give us honest feedback about how it can improve.&lt;/p&gt;

&lt;p&gt;We'd love if you joined our &lt;a href="https://pipedream.com/community" rel="noopener noreferrer"&gt;Slack community&lt;/a&gt; and added new feature requests on &lt;a href="https://github.com/PipedreamHQ/roadmap" rel="noopener noreferrer"&gt;our backlog&lt;/a&gt;. And you can &lt;a href="https://docs.pipedream.com/support/" rel="noopener noreferrer"&gt;reach out to our team&lt;/a&gt; anytime. We'd love to hear from you.&lt;/p&gt;

</description>
      <category>airflow</category>
      <category>cron</category>
      <category>node</category>
      <category>pipedream</category>
    </item>
  </channel>
</rss>
