<?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: Massimiliano Pippi</title>
    <description>The latest articles on DEV Community by Massimiliano Pippi (@masci).</description>
    <link>https://dev.to/masci</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%2F53615%2F75a79496-e55f-4599-834e-dbce0d271b0f.jpeg</url>
      <title>DEV Community: Massimiliano Pippi</title>
      <link>https://dev.to/masci</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/masci"/>
    <language>en</language>
    <item>
      <title>Make a stale bot, learn GitHub Actions</title>
      <dc:creator>Massimiliano Pippi</dc:creator>
      <pubDate>Mon, 07 Oct 2019 18:11:23 +0000</pubDate>
      <link>https://dev.to/masci/make-a-stale-bot-learn-github-actions-581h</link>
      <guid>https://dev.to/masci/make-a-stale-bot-learn-github-actions-581h</guid>
      <description>&lt;p&gt;A few months ago I had the chance to play with the first Beta of &lt;a href="https://help.github.com/en/articles/about-github-actions"&gt;GitHub Actions&lt;/a&gt; but for some reasons it didn't click for me: the point-and-click interface, the HCL syntax and the confusion in the market place put me back to my comfort zone, in the good company of the usual CI/CD systems we all have been using for years now.&lt;/p&gt;

&lt;p&gt;Fast forward to a few weeks ago, when GitHub announced that Actions were finally approaching general availability; a couple of days later, my personal GitHub account was promptly enrolled in the Beta program. It was time to give it another try, and this is the story of my second attempt at learning Actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing the Stale Bot
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uMB6zba8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1508175800969-525c72a047dd%3Fixlib%3Drb-1.2.1%26auto%3Dformat%26fit%3Dcrop%26w%3D1467%26q%3D80" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uMB6zba8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1508175800969-525c72a047dd%3Fixlib%3Drb-1.2.1%26auto%3Dformat%26fit%3Dcrop%26w%3D1467%26q%3D80" alt="Photo by Dominik Scythe on Unsplash"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I learn something new, it's vital for me to have some sort of prototype I can change and evolve as I ramp up on the subject. That's why after few basic experiments and a couple of easy projects ported from other CI/CD platforms, I challenged myself to write a GitHub bot implemented in a single Workflow with minimum recourse to 3rd party Actions.&lt;/p&gt;

&lt;p&gt;The requirements are admittedly idiotic but they made a good excuse to learn how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use scheduled Workflows&lt;/li&gt;
&lt;li&gt;execute jobs conditionally&lt;/li&gt;
&lt;li&gt;interact with the GitHub API from a Workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;A stale bot is an automated mechanism responsible for marking issues and pull requests as "stale" when there's no activity around them after a given amount of time. For the sake of simplicity, we'll limit the scope of the bot to issues only.&lt;/p&gt;

&lt;p&gt;The requirements are the following. For each issue on a GitHub repository:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Apply the label &lt;code&gt;stale&lt;/code&gt; after 30 days without any kind of activity,&lt;/li&gt;
&lt;li&gt;remove the label &lt;code&gt;stale&lt;/code&gt; when there's activity on the issue,&lt;/li&gt;
&lt;li&gt;close the issue if it's been &lt;code&gt;stale&lt;/code&gt; for more than 15 days.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Create the Workflow
&lt;/h2&gt;

&lt;p&gt;First of all, let's create a GitHub Workflow. You can do it from the GitHub UI or by simply creating a yaml file under &lt;code&gt;.github/workflows/&lt;/code&gt; at the root of your repo. You might have different Workflows defined for a single repo and the name of the file can be whatever, in my case it is &lt;code&gt;stalebot.yaml&lt;/code&gt;. We also have to give our Workflow a name that will be displayed in GitHub UI: let's call it "Stale Bot". Our yaml file will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Stale Bot&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Determine when the Workflow has to run
&lt;/h2&gt;

&lt;p&gt;We have to tell GitHub when we want to run our Workflow and we can do it with the keyword &lt;code&gt;on&lt;/code&gt;. According to our requirements, the Workflow should be triggered by different events:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every night, to attach the &lt;code&gt;stale&lt;/code&gt; label and to close issues that've
been stale long enough.&lt;/li&gt;
&lt;li&gt;Every time the issue is edited, or a label or milestone is attached.&lt;/li&gt;
&lt;li&gt;Every time somebody leaves a comment on the issue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To run a Workflow periodically at a given time, we can use the &lt;code&gt;schedule&lt;/code&gt; keyword, followed by one or more intervals defined in &lt;a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html#tag_20_25_07"&gt;POSIX syntax&lt;/a&gt;, like in a &lt;code&gt;crontab&lt;/code&gt; file. For example, if we want our bot to run every night at midnight, we add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To also run the workflow every time an issue has activity, we add the &lt;code&gt;issues&lt;/code&gt; keyword, so that the previous snippet will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;
  &lt;span class="na"&gt;issues&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;GitHub API sends different events for different things that happen to issues: by leaving the &lt;code&gt;issues&lt;/code&gt; mapping empty, we accept the defaults. You can see what's the default for any value of the &lt;code&gt;on&lt;/code&gt; keyword in the docs &lt;a href="https://help.github.com/en/articles/events-that-trigger-workflows#about-workflow-events"&gt;page about events&lt;/a&gt;; in our case, the default value for &lt;code&gt;issues&lt;/code&gt; is &lt;a href="https://help.github.com/en/articles/events-that-trigger-workflows#issues-event-issues"&gt;"any possible kind of interaction"&lt;/a&gt; which is a bit too much.&lt;br&gt;
That's not a problem because we can limit the kind of events that trigger our bot by listing them explicitly in a sequence under the keyword &lt;code&gt;types&lt;/code&gt;, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;
  &lt;span class="na"&gt;issues&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;edited&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;milestoned&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;labeled&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Our bot will be then summoned whan an issue is edited, a milestone is added or a label is attached. Since we also want the bot to be activated when somebody leaves a comment on the issue, we need one more event on our &lt;code&gt;on&lt;/code&gt; mapping, specifically the &lt;code&gt;issue_added&lt;/code&gt; event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;
  &lt;span class="na"&gt;issues&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;edited&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;milestoned&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;labeled&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;issue_comment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The default &lt;code&gt;types&lt;/code&gt; for the &lt;code&gt;issue_comment&lt;/code&gt; event work for us, no need to add a &lt;code&gt;types&lt;/code&gt; keyword here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define the jobs
&lt;/h2&gt;

&lt;p&gt;A Workflow consists of a set of &lt;strong&gt;jobs&lt;/strong&gt; that will be run in parallel; each job consists of a sequence of &lt;strong&gt;steps&lt;/strong&gt; that will be run one after another.&lt;br&gt;
We're going to define a single job named &lt;code&gt;stale-bot-logic&lt;/code&gt; containing one step for each requirement we have, for a total of three steps. Since we can choose the platform on which our workflow will run, we pick Linux, specifically &lt;code&gt;ubuntu-latest&lt;/code&gt;. Let's add this to our workflow yaml definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;
  &lt;span class="na"&gt;issues&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;edited&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;milestoned&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;labeled&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;issue_comment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stale-bot-logic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;A step can either run a command on the host running the Workflow, or run a Docker container or execute a special kind of node.js applications called &lt;em&gt;Actions&lt;/em&gt;.&lt;br&gt;
There are a lot of Actions available, ready to be included in a Workflow; to have a better idea you can browse the GitHub &lt;a href="https://github.com/marketplace"&gt;market place&lt;/a&gt; - chances are that somebody else already solved a problem you're having and no wheels will be reinvented that day.&lt;/p&gt;
&lt;h3&gt;
  
  
  Mark issue stale
&lt;/h3&gt;

&lt;p&gt;The first step of the job will be marking an issue as stale after a certain amount of time: this means we need to query the GitHub API and guess what: there's an Action for that. The Action we need is called&lt;br&gt;
&lt;code&gt;actions/github-script&lt;/code&gt; and it'll be the only one we're going to use. It exposes a Javascript client that can make API calls straight from the yaml code. The Workflow will then look more or less like this (&lt;code&gt;on&lt;/code&gt; section omitted for brevity):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stale-bot&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Mark stale&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/github-script@0.2.0&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;github-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.token }}&lt;/span&gt;
          &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;// Here it goes some Javascript code that uses the GitHub API client&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;uses&lt;/code&gt; keyword will tell the CI system we want to use a certain action at the version &lt;code&gt;0.2.0&lt;/code&gt;. Actions may expose few parameters you can use to configure their behaviour (refer to each Action's docs for the details) and that's what the &lt;code&gt;with&lt;/code&gt; keyword is for: we'll pass in a valid GitHub token using the param &lt;code&gt;github-token&lt;/code&gt; and some Javascript code using the param &lt;code&gt;script&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The logic for this job will be the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get a list of all open issues.&lt;/li&gt;
&lt;li&gt;For each one, get the time and date of the last update.&lt;/li&gt;
&lt;li&gt;If the difference between now and the last update for an issue is above a certain threshold, attach a label called &lt;code&gt;stale&lt;/code&gt; to it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The code will look like this (I'll paste only the job definition for brevity):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Mark stale&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/github-script@0.2.0&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;github-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{github.token}}&lt;/span&gt;
    &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;// Fetch the list of all open issues&lt;/span&gt;
      &lt;span class="s"&gt;const opts = github.issues.listForRepo.endpoint.merge({&lt;/span&gt;
        &lt;span class="s"&gt;...context.repo,&lt;/span&gt;
        &lt;span class="s"&gt;state: 'open',&lt;/span&gt;
      &lt;span class="s"&gt;});&lt;/span&gt;
      &lt;span class="s"&gt;const issues = await github.paginate(opts);&lt;/span&gt;

      &lt;span class="s"&gt;// How many days of inactivity before we mark the issue stale&lt;/span&gt;
      &lt;span class="s"&gt;const elapsedDays = 30&lt;/span&gt;
      &lt;span class="s"&gt;// Get the time window in milliseconds&lt;/span&gt;
      &lt;span class="s"&gt;const elapsed = elapsedDays * 24 * 60 * 60 * 1000;&lt;/span&gt;

      &lt;span class="s"&gt;const now = new Date().getTime();&lt;/span&gt;
      &lt;span class="s"&gt;for (const issue of issues) {&lt;/span&gt;
        &lt;span class="s"&gt;// Ignore issues below the threshold&lt;/span&gt;
        &lt;span class="s"&gt;if (now - new Date(issue.updated_at).getTime() &amp;lt; elapsed) {&lt;/span&gt;
          &lt;span class="s"&gt;continue;&lt;/span&gt;
        &lt;span class="s"&gt;}&lt;/span&gt;

        &lt;span class="s"&gt;// If we got here, mark as stale.&lt;/span&gt;
        &lt;span class="s"&gt;github.issues.addLabels({&lt;/span&gt;
          &lt;span class="s"&gt;...context.repo,&lt;/span&gt;
          &lt;span class="s"&gt;issue_number: issue.number,&lt;/span&gt;
          &lt;span class="s"&gt;labels: ['stale']&lt;/span&gt;
        &lt;span class="s"&gt;});&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That's pretty much all the code we need but there's one detail we need to take care of: according to the &lt;code&gt;on&lt;/code&gt; configuration we defined earlier, the Workflow will run every day as a cron job and also every time an issue is updated. There's no need to have the "Mark Stale" job run every time an issue is updated (obviously an issue that gets updated is not stale) and we want to skip its execution in this case. In other words, we want to run the step only when the Workflow was kicked off by the cron scheduler. To do so, we can use the &lt;code&gt;if&lt;/code&gt; keyword on our job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Mark stale&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/github-script@0.2.0&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name == 'schedule'&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The job will be then skipped unless the statement in the &lt;code&gt;if&lt;/code&gt; value is true. That's Javascript code and you can access several objects called &lt;em&gt;contexts&lt;/em&gt; from there, in this case we're accessing the &lt;code&gt;event_name&lt;/code&gt; field of the &lt;a href="https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions#github-context"&gt;&lt;code&gt;github&lt;/code&gt; context&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Remove stale label
&lt;/h3&gt;

&lt;p&gt;Sometimes a stale issue gets noticed and some activity kicks off again. In this case, we need to remove the &lt;code&gt;stale&lt;/code&gt; label as soon as possible. We use the &lt;code&gt;github-script&lt;/code&gt; Action again and this time we'll ask the CI system to skip the job unless the source event is either an issue update or a new comment. Note how we use a slightly more complex &lt;code&gt;if&lt;/code&gt; clause here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Remove stale&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name == 'issues' || github.event_name == 'issue_comment'&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/github-script@0.2.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This time the logic will be the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get all the labels of the issue that triggered the Workflow.&lt;/li&gt;
&lt;li&gt;Search whether it has a &lt;code&gt;stale&lt;/code&gt; label and remove it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The final job definition will than be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Remove stale&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name == 'issues' || github.event_name == 'issue_comment'&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/github-script@0.2.0&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;github-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{github.token}}&lt;/span&gt;
    &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;// Fetch the list of labels attached to the issue that&lt;/span&gt;
      &lt;span class="s"&gt;// triggered the workflow&lt;/span&gt;
      &lt;span class="s"&gt;const opts = github.issues.listLabelsOnIssue.endpoint.merge({&lt;/span&gt;
        &lt;span class="s"&gt;...context.repo,&lt;/span&gt;
        &lt;span class="s"&gt;issue_number: context.issue.number&lt;/span&gt;
      &lt;span class="s"&gt;});&lt;/span&gt;
      &lt;span class="s"&gt;const labels = await github.paginate(opts);&lt;/span&gt;

      &lt;span class="s"&gt;for (const label of labels) {&lt;/span&gt;
        &lt;span class="s"&gt;// If the issue has a label named 'stale', remove it&lt;/span&gt;
        &lt;span class="s"&gt;if (label.name === 'stale') {&lt;/span&gt;
          &lt;span class="s"&gt;await github.issues.removeLabel({&lt;/span&gt;
            &lt;span class="s"&gt;...context.repo,&lt;/span&gt;
            &lt;span class="s"&gt;issue_number: context.issue.number,&lt;/span&gt;
            &lt;span class="s"&gt;name: 'stale'&lt;/span&gt;
          &lt;span class="s"&gt;})&lt;/span&gt;
          &lt;span class="s"&gt;return;&lt;/span&gt;
        &lt;span class="s"&gt;}&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Close stale
&lt;/h3&gt;

&lt;p&gt;We've one last job left to complete our stale bot: an issue should be closed in case it's been marked as stale for a certain amount of time. The logic is the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Load all the issues having the &lt;code&gt;stale&lt;/code&gt; label attached.&lt;/li&gt;
&lt;li&gt;For each one, compute the difference between now and the last update of the
issue (we assume it concides with the moment the &lt;code&gt;stale&lt;/code&gt; label was added)&lt;/li&gt;
&lt;li&gt;If the difference is above our threshold, close the issue.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The job definition looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Close stale&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name == 'schedule'&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/github-script@0.2.0&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;github-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{github.token}}&lt;/span&gt;
    &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;// Fetch the list of all open issues that have the 'stale' label&lt;/span&gt;
      &lt;span class="s"&gt;// attached&lt;/span&gt;
      &lt;span class="s"&gt;const opts = github.issues.listForRepo.endpoint.merge({&lt;/span&gt;
        &lt;span class="s"&gt;...context.repo,&lt;/span&gt;
        &lt;span class="s"&gt;state: 'open',&lt;/span&gt;
        &lt;span class="s"&gt;labels: ['stale'],&lt;/span&gt;
      &lt;span class="s"&gt;});&lt;/span&gt;
      &lt;span class="s"&gt;const issues = await github.paginate(opts);&lt;/span&gt;

      &lt;span class="s"&gt;// How many days of inactivity before we close a stale issue&lt;/span&gt;
      &lt;span class="s"&gt;const elapsedDays = 15&lt;/span&gt;
      &lt;span class="s"&gt;const elapsed = elapsedDays * 24 * 60 * 60 * 1000;&lt;/span&gt;

      &lt;span class="s"&gt;const now = new Date().getTime();&lt;/span&gt;
      &lt;span class="s"&gt;for (const issue of issues) {&lt;/span&gt;
        &lt;span class="s"&gt;// Ignore issues below the threshold&lt;/span&gt;
        &lt;span class="s"&gt;if (now - new Date(issue.updated_at).getTime() &amp;lt; elapsed) {&lt;/span&gt;
          &lt;span class="s"&gt;// This is how to print debug informations&lt;/span&gt;
          &lt;span class="s"&gt;console.log('skip issue ' + issue.number);&lt;/span&gt;
          &lt;span class="s"&gt;continue;&lt;/span&gt;
        &lt;span class="s"&gt;}&lt;/span&gt;

        &lt;span class="s"&gt;// If we arrive here, the issue has to be closed&lt;/span&gt;
        &lt;span class="s"&gt;console.log('closing issue ' + issue.number);&lt;/span&gt;
        &lt;span class="s"&gt;await github.issues.update({&lt;/span&gt;
          &lt;span class="s"&gt;...context.repo,&lt;/span&gt;
          &lt;span class="s"&gt;issue_number: issue.number,&lt;/span&gt;
          &lt;span class="s"&gt;state: 'closed'&lt;/span&gt;
        &lt;span class="s"&gt;});&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can find the complete workflow &lt;a href="https://github.com/masci/stalebot/blob/master/.github/workflows/stalebot.yml"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveat and final considerations
&lt;/h2&gt;

&lt;p&gt;First thing first, this bot shouldn't be considered feature complete by any mean, the goal was just ramping up up on a new technology without getting bored. Now that you're more familiar with GitHub Actions, a bunch of different and possibly more efficient workflows to solve the same problems might have popped into your mind already; if you need a stale bot for your project, I wouldn't see any problem on keep going this way and make the implementation better.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SP2StHcN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1563207153-f403bf289096%3Fixlib%3Drb-1.2.1%26ixid%3DeyJhcHBfaWQiOjEyMDd9%26auto%3Dformat%26fit%3Dcrop%26w%3D1351%26q%3D80" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SP2StHcN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1563207153-f403bf289096%3Fixlib%3Drb-1.2.1%26ixid%3DeyJhcHBfaWQiOjEyMDd9%26auto%3Dformat%26fit%3Dcrop%26w%3D1351%26q%3D80" alt="Photo by Lenin Estrada on Unsplash"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That said, there's a subtle problem with this approach that I think it's worth discussing: the code we wrote is just a piece of text defined in a yaml file that GitHub will happily pass to the entrypoint of the &lt;code&gt;actions/github-script&lt;/code&gt; action. This means few terrible things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No syntax highlighting&lt;/li&gt;
&lt;li&gt;No code validation&lt;/li&gt;
&lt;li&gt;No tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which can turn into respectively:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A miserable user experience for authors and contributors&lt;/li&gt;
&lt;li&gt;A frustrating development cycle&lt;/li&gt;
&lt;li&gt;Bugs and poor performance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is actually very interesting because this problem can help us to better define what the boundaries of a Workflow should be. I'd phrase like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If a workflow contains more than two lines of logic in plain text form, it's time to write a custom Action.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With a custom Action (whether Javascript or container based) you can dramatically simplify the workflow definition while having tests and the ability to exercise your code locally. You could also look at this problem symmetrically:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If your problem can be solved with two lines of logic, writing a custom Action is probably overkill&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'm a big fun of custom Actions and I enjoy Typescript, but I have to say they don't come for free: you basically need to write a full fledged Node application and whether this is a problem or not, it's up to you.&lt;/p&gt;

</description>
      <category>github</category>
      <category>actions</category>
      <category>bot</category>
      <category>automation</category>
    </item>
    <item>
      <title>How I manage a small team with the help of Trello</title>
      <dc:creator>Massimiliano Pippi</dc:creator>
      <pubDate>Sat, 03 Feb 2018 15:44:05 +0000</pubDate>
      <link>https://dev.to/masci/how-i-manage-a-small-team-with-the-help-of-trello-49d6</link>
      <guid>https://dev.to/masci/how-i-manage-a-small-team-with-the-help-of-trello-49d6</guid>
      <description>&lt;p&gt;&lt;a href="https://www.flickr.com/photos/gdsteam/14084166143/in/album-72157644412680192/" rel="noopener noreferrer"&gt;Cover courtesy of gdsteam from Flickr.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is not about agile management theory, something I know very little about; this is what I do to keep a small team of engineers on track without allocating 100% of my time to management. If you keep reading, you'll see how I didn't invent anything and how this is only "yet another way" of composing existing tools and practices in order to implement a process that works for me. So why another post?&lt;/p&gt;

&lt;p&gt;Well the problem is, it costed to me a lot of time and trial-and-error to get here and I decided to share my experience so that others can take inspiration, specially if some of the following apply: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You're part of a small team, let's say 8 people or less.&lt;/li&gt;
&lt;li&gt;Your team is responsible of delivering new projects and maintaining existing ones.&lt;/li&gt;
&lt;li&gt;You have formal deadlines, objectives and goals.&lt;/li&gt;
&lt;li&gt;You want some time to spend working on the product and not on management.&lt;/li&gt;
&lt;li&gt;You want to keep things simple.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;p&gt;My team is geographically distributed so we need remotely accessible tools to share informations. Beside the ubiquitous e-mail and instant messaging, the only management tool we use is Trello. &lt;/p&gt;

&lt;p&gt;Being general purpose, Trello offers a tiny fraction of the features one can find in specialised tools like Jira and Asana but this is actually a key point if you want to keep the management overhead at minimum for your team.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basics
&lt;/h2&gt;

&lt;p&gt;My approach is based on three building blocks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Backlog&lt;/li&gt;
&lt;li&gt;The Sprint&lt;/li&gt;
&lt;li&gt;The Roadmap&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's see what those mean and how such blocks can be implemented with Trello.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Backlog
&lt;/h4&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%2Frmsuypnd3poxy7vzhky8.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%2Frmsuypnd3poxy7vzhky8.png" alt="the Backlog in Trello"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a team, we don't usually work in isolation: interacting with Product Management, Customer Support, Documentation and other engineering teams is desirable and expected. The Backlog is the point of contact with the rest of the company and is the place where to put items like feature requests, bug reports or more in general any small, self contained task my team should work on - such items are represented with Trello cards. We sometimes put tasks ourselves on the Backlog, for example when priorities need to be discussed and evaluated.&lt;/p&gt;

&lt;p&gt;A Trello board is a perfect fit to implement a Backlog, see &lt;a href="https://trello.com/b/0A4qO3OV/backlog" rel="noopener noreferrer"&gt;this basic example&lt;/a&gt;. The number and type of lists can vary a lot depending on team activities but at least two columns should be there:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;Triage&lt;/em&gt;: is where new tasks are parked waiting for a priority to be assigned.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Next&lt;/em&gt;: is where tasks are sent after a priority is assigned, waiting to be assigned and scheduled during an upcoming sprint.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's fundamental to allocate some time to review the Backlog board, how often depends on the workload. A fixed schedule works better for me but there are really no rules here. The review should involve managers and product managers who can help deciding priorities and engineers who can provide help on better defining the scope of a card, or the amount of work that a card implies.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Sprint
&lt;/h4&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%2Fuzr6iot6nyegr8d1fvhe.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%2Fuzr6iot6nyegr8d1fvhe.png" alt="the Sprint in Trello"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The sprint board provides a picture of the current status of the team: what's done, who's working on what, what's waiting for a review and most important, what's blocked and why. For this to work, each card must have one or more owners, something we do on Trello by listing specific users in the "members" list. You can see how a Sprint board looks like in Trello &lt;a href="https://trello.com/b/zFlf1VII/sprint" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Most of the cards you see on the Sprint board come from the Backlog, specifically from the &lt;em&gt;Next&lt;/em&gt; column. This process is formalised with a planning meeting that usually happens right before a sprint starts but you can also let people pick tasks from there when needed, since cards are supposed to be already scheduled and have a priority assigned.&lt;/p&gt;

&lt;p&gt;There's not much to discuss about the Sprint Board but there are a few caveats:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The &lt;em&gt;TODO&lt;/em&gt; column should contain only cards that are supposed to be done within the current sprint. Cards tend to pile up there, specially low priority ones so when this happens, I remove the owner and send them back to the Backlog, even on &lt;em&gt;Triage&lt;/em&gt;: maybe the priority was wrong, the task poorly defined or even not needed in retrospective, that's ok.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;All the cards must have an owner! It might seem obvious but since most of the times you look at the sprint board with some filter active, orphan cards can become invisible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It's ok to skip the backlog board altogether and add a card directly on the sprint board: it's the case for bugfixes, or urgent tasks that pop up midpsprint but we try to keep this an exception.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  The Roadmap
&lt;/h4&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%2F38wlg3osr3dxpmbwip9z.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%2F38wlg3osr3dxpmbwip9z.png" alt="A roadmap in Trello"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Backlog and sprint boards are great to track the work of a team because it's extremely easy to answer questions like "What are you working on right now?", "What's next?", "Is there any blocker?" but what if you want to know the overall status of a specific project? On Trello you can use labels to link a card to a project but cards might be spread across Backlog and Sprint and while you can still filter, you can't avoid to go back and forth the two boards. Roadmaps can add another dimension you can look at your team's cards from.&lt;/p&gt;

&lt;p&gt;Each card in a roadmap board represents a macro feature, or an &lt;em&gt;Epic&lt;/em&gt;, and must contain a brief description along with one or more checklists. Each item in a checklist is a link to a card that might be in the backlog or the sprint board, see &lt;a href="https://trello.com/b/WTSoEcD1/project-mayhem-roadmap" rel="noopener noreferrer"&gt;this Trello board&lt;/a&gt; for an example.&lt;/p&gt;

&lt;p&gt;You'd need one Trello board for each project you want to track; as for the backlog board, you can go wild with the number of lists but the following should always be there:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;Now&lt;/em&gt;: epics that are currently under development.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Next&lt;/em&gt;: epics that should be done as soon as you have bandwidth.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Later&lt;/em&gt;: epics that have low priority and should be &lt;em&gt;eventually&lt;/em&gt; done.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Cards never leave a roadmap board, you're only supposed to move them across the lists, ideally from &lt;em&gt;Later&lt;/em&gt; to &lt;em&gt;Next&lt;/em&gt;, then to &lt;em&gt;Now&lt;/em&gt; and finally archived (or moved to a &lt;em&gt;Done&lt;/em&gt; list).&lt;/p&gt;

&lt;p&gt;Being a collection of tasks, epics represent a quite large chunk of work for a team but they might not be enough to track big features or long term goals; in this case you can group epics using a &lt;em&gt;Theme&lt;/em&gt; that in Trello words can be represented with a label. Examples of themes might be &lt;code&gt;version:1.0&lt;/code&gt; or &lt;code&gt;feature:SAML&lt;/code&gt;. You can then filter a roadmap board by label to see what's the status of the project in regard of one particular theme.&lt;/p&gt;

&lt;p&gt;Managing a roadmap board requires some extra time, specially composing the checklists since it's all manual work, but in exchange you get a perfect view over the status of a project, something that might be extremely useful when discussing long term goals that span over multiple sprints or even over multiple quarters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;I'm still evaluating the tradeoff between the extra work of maintaining roadmaps and the ability to answer questions about the big picture but overall it's working pretty good and proof is I could finally find the time to write this piece. Should you have any feedback, please reach out on Twitter @maxpippi.&lt;/p&gt;

</description>
      <category>agile</category>
      <category>management</category>
      <category>productivity</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
