<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>opensource &amp;mdash; Roundup Issue Tracker</title>
    <link>https://blog.rouilj.dynamic-dns.net/rouilj/tag:opensource</link>
    <description>A blog about developing and using the Roundup Issue Tracker. Covers web design/front end development, Python, security, user interface, customization, documentation, and community development.</description>
    <pubDate>Tue, 05 May 2026 10:26:07 -0400</pubDate>
    <item>
      <title>How does your customer help you?</title>
      <link>https://blog.rouilj.dynamic-dns.net/rouilj/how-does-your-customer-help-you</link>
      <description>&lt;![CDATA[Many companies are cutting back and delaying new projects after the recent election. As a new solution provider, how do you create a win-win situation for customers who might worry about:&#xA;&#xA;  quality of the solution&#xA;  satisfaction with the result&#xA;  cost&#xA;&#xA;How can you add value for the customer by reducing risk or cost?&#xA;&#xA;I propose 4 types of customers:&#xA;&#xA;  Regular customer: straight work for hire.&#xA;  Reference customer: provides a private reference for my work to a potential customer.&#xA;  Sponsor customer: the company is publically promoted as a user of the product.&#xA;  Partner customer: actively promotes the product within their market.&#xA;&#xA;Regular customer&#xA;&#xA;The customer receives a proposal for their project. Engages in discussions regarding the scope of work, pricing, and other details. Once the contract concludes, there is no continued relationship with the client.&#xA;&#xA;!--more--&#xA;&#xA;Reference Customer&#xA;&#xA;A reference customer starts as a regular customer. As part of the contract, they agree to talk with potential clients to discuss:&#xA;&#xA;  the project scope,&#xA;  the benefits of the solution,&#xA;  and the overall business impact of the work performed.&#xA;&#xA;Contracts are protected by a non-disclosure agreement to safeguard the confidentiality of references. This allows prospective clients to better understand the value proposition. But it prohibits them from sharing any proposal-related information outside their organization.&#xA;&#xA;In exchange for serving as a reference, these customers receive discounted rates on their contracts.&#xA;&#xA;Sponsor Customer&#xA;&#xA;A sponsor customer is a reference customer who has consented to publicly associate their company name with the solution/product. This association includes prominent visibility on websites, emails, and various marketing materials.&#xA;&#xA;Sponsor customers receive a greater discount on their contracts compared to reference customers. They may enjoy other benefits, such as enhanced support agreements.&#xA;&#xA;Partner Customer&#xA;&#xA;A partner customer can be in any of the other customer categories. Unlike the other customers, who take a passive approach to supporting the product, these customers actively advocate for the product.&#xA;&#xA;They share their positive experiences with others in the same industry. This strategy is particularly effective when the product offered is not a primary focus or distinguishing factor for the customer&#39;s business.&#xA;&#xA;Rather than receiving discounts on contracts, these customers benefit from additional revenue when a client they refer decides to purchase the solution.&#xA;&#xA;Conclusion&#xA;&#xA;Offering customers an ongoing role in the success of your business can reduce their concerns about your solution. This win-win approach to negotiating contracts can also reach new clients and help answer the concerns of new clients.&#xA;&#xA;What are your thoughts about these customer categories?&#xA;What tools/techniques do you use to help customers get&#xA;to yes?&#xA;&#xA;(A good reference on negotiating is https://www.amazon.com/Getting-Yes-Negotiating-Agreement-Without/dp/0143118757.)&#xA;&#xA;#opensource, #Business,  #DevTo]]&gt;</description>
      <content:encoded><![CDATA[<p>Many companies are cutting back and delaying new projects after the recent election. As a new solution provider, how do you create a win-win situation for customers who might worry about:</p>
<ul><li>quality of the solution</li>
<li>satisfaction with the result</li>
<li>cost</li></ul>

<p>How can you add value for the customer by reducing risk or cost?</p>

<p>I propose 4 types of customers:</p>
<ol><li>Regular customer: straight work for hire.</li>
<li>Reference customer: provides a private reference for my work to a potential customer.</li>
<li>Sponsor customer: the company is publically promoted as a user of the product.</li>
<li>Partner customer: actively promotes the product within their market.</li></ol>

<h2 id="regular-customer" id="regular-customer">Regular customer</h2>

<p>The customer receives a proposal for their project. Engages in discussions regarding the scope of work, pricing, and other details. Once the contract concludes, there is no continued relationship with the client.</p>



<h2 id="reference-customer" id="reference-customer">Reference Customer</h2>

<p>A reference customer starts as a regular customer. As part of the contract, they agree to talk with potential clients to discuss:</p>
<ul><li>the project scope,</li>
<li>the benefits of the solution,</li>
<li>and the overall business impact of the work performed.</li></ul>

<p>Contracts are protected by a non-disclosure agreement to safeguard the confidentiality of references. This allows prospective clients to better understand the value proposition. But it prohibits them from sharing any proposal-related information outside their organization.</p>

<p>In exchange for serving as a reference, these customers receive discounted rates on their contracts.</p>

<h2 id="sponsor-customer" id="sponsor-customer">Sponsor Customer</h2>

<p>A sponsor customer is a reference customer who has consented to publicly associate their company name with the solution/product. This association includes prominent visibility on websites, emails, and various marketing materials.</p>

<p>Sponsor customers receive a greater discount on their contracts compared to reference customers. They may enjoy other benefits, such as enhanced support agreements.</p>

<h2 id="partner-customer" id="partner-customer">Partner Customer</h2>

<p>A partner customer can be in any of the other customer categories. Unlike the other customers, who take a passive approach to supporting the product, these customers actively advocate for the product.</p>

<p>They share their positive experiences with others in the same industry. This strategy is particularly effective when the product offered is not a primary focus or distinguishing factor for the customer&#39;s business.</p>

<p>Rather than receiving discounts on contracts, these customers benefit from additional revenue when a client they refer decides to purchase the solution.</p>

<h2 id="conclusion" id="conclusion">Conclusion</h2>

<p>Offering customers an ongoing role in the success of your business can reduce their concerns about your solution. This win-win approach to negotiating contracts can also reach new clients and help answer the concerns of new clients.</p>

<p>What are your thoughts about these customer categories?
What tools/techniques do you use to help customers get
to yes?</p>

<p>(A good reference on negotiating is <a href="https://www.amazon.com/Getting-Yes-Negotiating-Agreement-Without/dp/0143118757." rel="nofollow">https://www.amazon.com/Getting-Yes-Negotiating-Agreement-Without/dp/0143118757.</a>)</p>

<p><a href="/rouilj/tag:opensource" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">opensource</span></a>, <a href="/rouilj/tag:Business" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">Business</span></a>,  <a href="/rouilj/tag:DevTo" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">DevTo</span></a></p>
]]></content:encoded>
      <guid>https://blog.rouilj.dynamic-dns.net/rouilj/how-does-your-customer-help-you</guid>
      <pubDate>Thu, 14 Nov 2024 01:28:38 +0000</pubDate>
    </item>
    <item>
      <title>Copy issue reference to clipboard enhancement</title>
      <link>https://blog.rouilj.dynamic-dns.net/rouilj/copy-issue-reference-to-clipboard-enhancement</link>
      <description>&lt;![CDATA[On the Roundup Issue Tracker mailing list a user asked for a mechanism to create a reference for the current issue and copy it to the clipboard.&#xA;&#xA;The request was for the reference to look like:&#xA;  issue2345: title of issue&#xA;In Roundup, object designators like issue2345 are automatically hyperlinked to the corresponding object.&#xA;&#xA;The code I recommended used a button to trigger this operation. It also used the clipboard API. When clicking on the button (or hitting space or return while focused on the button) the reference format above will be copied to the clipboard. Triggering the clipboard API must be done by a user interaction. For example: activating a button. If the browser does not support the clipboard API, a simple alert() is shown.&#xA;&#xA;In addition to copying to the clipboard, the user gets feedback when the text of the button changes to &#34;Reference copied&#34;. Then it resets to the original message after 2 seconds. Since clicking the button multiple times is idempotent, there is no sense in disabling or debouncing the button.&#xA;!--more--&#xA;Note, this is unlikely to be a11y compliant as I don&#39;t think the change of button text is announced. If anybody has some ideas on how to make this more compliant, leave them in the comments. Maybe using aria-live=&#34;polite&#34; or &#34;assertive&#34; on the button element would do the trick?&#xA;&#xA;Here is the code I suggested:&#xA;&lt;button id=&#34;copyreference&#34; type=&#34;button&#34;&#xA;            tal:condition=&#34;context/iseditok&#34;  Copy Reference&#xA;/button&#xA;&#xA;script tal:attributes=&#34;nonce request/client/clientnonce&#34;&#xA;  (function () {&#xA;    &#34;use strict&#34;;&#xA;&#xA;    let crb = document.querySelector(&#34;#copyreference&#34;);&#xA;    if ( ! crb ) return&#xA;    crb.addEventListener(&#34;click&#34;, async (e) =  {&#xA;        e.preventDefault()&#xA;        if ( ! navigator.clipboard ) {&#xA;            alert(&#34;Clipboard is not available&#34;)&#xA;            return&#xA;        }&#xA;&#xA;        let issueDesignator = new URL(document.URL)&#xA;            .pathname&#xA;            .split(&#34;/&#34;)&#xA;            .pop()&#xA;        let issueTitleText = document.querySelector(&#34;#title&#34;).value;&#xA;&#xA;        await navigator.clipboard.writeText(${issueDesignator}: ${issueTitleText})&#xA;&#xA;        let originalCrbInnerText = crb.innerText&#xA;        crb.innerText = &#34;Reference copied&#34;&#xA;        setTimeout(() =  {&#xA;            crb.innerText = originalCrbInnerText}, 2000)&#xA;        }&#xA;    );&#xA;  })();&#xA;&#xA;The flexibility of Roundup allows the administrator to rewrite all of the HTML used in the web interface. As a result, I based my guess of CSS selectors on the HTML generated by the classic issue.item.html TAL template.&#xA;&#xA;The button could be placed anywhere on the page and the script (including the nonce required by the CSP) would be placed anywhere after the button. Probably at the end of the page so it doesn&#39;t block rendering.&#xA;&#xA;The classic structure of the issue display page for users with editing capability included an input with the id title. This is retrieved using the id and included in the string written to the clipboard.&#xA;&#xA;A user without editing capability for the issue (or without editing capability for the title attribute of an issue) does not have an input with id=&#34;title&#34;.&#xA;In these cases, the admin would have to modify the issue.item.html template to add an id of title to the enclosing element. Then the code above would be modified to replace:&#xA;&#xA;let issueTitleText = document.querySelector(&#34;#title&#34;).value;&#xA;with:&#xA; let issueTitle = document.querySelector(&#34;input#title&#34;);&#xA;   if ( ! issueTitle ) {&#xA;     issueTitle = document.querySelector(&#34;#title&#34;);&#xA;     issueTitleText = issueTitle.innerText&#xA;   } else {&#xA;     issueTitleText = issueTitle.value&#xA;   }&#xA;&#xA;In my original example, the button element is generated only if the user can edit the issue. Removing the tal:condition attribute would always display the button.&#xA;&#xA;There is nothing with an id or CSS selector that contains the object designator. I use the final element of the path of the URL to get the designator.&#xA;&#xA;I chose to use the IIFE code structure. This allows me to use the early return pattern if the button is not found. If anybody knows how to do the equivalent of an early return without an IIFE or other function in a script tag, leave your trick in the comments.&#xA;&#xA;If you use the Roundup Issue Tracker and have your own enhancements, feel free to mention them in the comments.&#xA;&lt;!--&#xA;{% embed https://www.roundup-tracker.org/?ref=devto %}&#xA;--  #WebDev, #opensource #RoundupTracker  #DevTo&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>On the <a href="https://sourceforge.net/p/roundup/mailman/search/?q=Feature+Request+%3A+Copy+Issue+id+and+title+separated+by+%27%3B+%27+to+the+clipboard&amp;mail_list=roundup-users" rel="nofollow">Roundup Issue Tracker mailing list</a> a user asked for a mechanism to create a reference for the current issue and copy it to the clipboard.</p>

<p>The request was for the reference to look like:</p>

<pre><code>  issue2345: title of issue
</code></pre>

<p>In Roundup, object designators like <code>issue2345</code> are automatically hyperlinked to the corresponding object.</p>

<p>The code I recommended used a button to trigger this operation. It also used the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API" rel="nofollow">clipboard API</a>. When clicking on the button (or hitting <code>space</code> or <code>return</code> while focused on the button) the reference format above will be copied to the clipboard. Triggering the clipboard API must be done by a user interaction. For example: activating a button. If the browser does not support the clipboard API, a simple <code>alert()</code> is shown.</p>

<p>In addition to copying to the clipboard, the user gets feedback when the text of the button changes to “Reference copied”. Then it resets to the original message after 2 seconds. Since clicking the button multiple times is idempotent, there is no sense in disabling or debouncing the button.

Note, this is unlikely to be a11y compliant as I don&#39;t think the change of button text is announced. If anybody has some ideas on how to make this more compliant, leave them in the comments. Maybe using <code>aria-live=&#34;polite&#34;</code> or <code>&#34;assertive&#34;</code> on the <code>button</code> element would do the trick?</p>

<p>Here is the code I suggested:</p>

<pre><code class="language-html">&lt;button id=&#34;copyreference&#34; type=&#34;button&#34;
            tal:condition=&#34;context/is_edit_ok&#34;&gt;
   Copy Reference
&lt;/button&gt;

&lt;script tal:attributes=&#34;nonce request/client/client_nonce&#34;&gt;
  (function () {
    &#34;use strict&#34;;

    let crb = document.querySelector(&#34;#copyreference&#34;);
    if ( ! crb ) return
    crb.addEventListener(&#34;click&#34;, async (e) =&gt; {
        e.preventDefault()
        if ( ! navigator.clipboard ) {
            alert(&#34;Clipboard is not available&#34;)
            return
        }

        let issueDesignator = new URL(document.URL)
            .pathname
            .split(&#34;/&#34;)
            .pop()
        let issueTitleText = document.querySelector(&#34;#title&#34;).value;

        await navigator.clipboard.writeText(`${issueDesignator}: ${issueTitleText}`)

        let originalCrbInnerText = crb.innerText
        crb.innerText = &#34;Reference copied&#34;
        setTimeout(() =&gt; {
            crb.innerText = originalCrbInnerText}, 2000)
        }
    );
  })();
</code></pre>

<p>The <a href="https://www.roundup-tracker.org/docs/features.html" rel="nofollow">flexibility of Roundup</a> allows the administrator to rewrite all of the HTML used in the web interface. As a result, I based my guess of CSS selectors on the <a href="https://sourceforge.net/p/roundup/code/ci/default/tree/share/roundup/templates/classic/html/issue.item.html" rel="nofollow">HTML generated by the classic <code>issue.item.html</code> TAL template</a>.</p>

<p>The button could be placed anywhere on the page and the script (including the nonce required by the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP" rel="nofollow">CSP</a>) would be placed anywhere after the button. Probably at the end of the page so it doesn&#39;t block rendering.</p>

<p>The classic structure of the issue display page for users with editing capability included an input with the id <code>title</code>. This is retrieved using the id and included in the string written to the clipboard.</p>

<p>A user without editing capability for the issue (or without editing capability for the title attribute of an issue) does not have an input with <code>id=&#34;title&#34;</code>.
In these cases, the admin would have to modify the <code>issue.item.html</code> template to add an id of title to the enclosing element. Then the code above would be modified to replace:</p>

<pre><code class="language-js">let issueTitleText = document.querySelector(&#34;#title&#34;).value;
</code></pre>

<p>with:</p>

<pre><code class="language-js"> let issueTitle = document.querySelector(&#34;input#title&#34;);
   if ( ! issueTitle ) {
     issueTitle = document.querySelector(&#34;#title&#34;);
     issueTitleText = issueTitle.innerText
   } else {
     issueTitleText = issueTitle.value
   }
</code></pre>

<p>In my original example, the button element is generated only if the user can edit the issue. Removing the <code>tal:condition</code> attribute would always display the button.</p>

<p>There is nothing with an id or CSS selector that contains the object designator. I use the final element of the path of the URL to get the designator.</p>

<p>I chose to use the <a href="https://en.wikipedia.org/wiki/Immediately-invoked_function_expression" rel="nofollow">IIFE</a> code structure. This allows me to <a href="https://gomakethings.com/the-early-return-pattern-in-javascript/" rel="nofollow">use the early return pattern</a> if the button is not found. If anybody knows how to do the equivalent of an early return without an IIFE or other function in a script tag, leave your trick in the comments.</p>

<p>If you use the Roundup Issue Tracker and have your own enhancements, feel free to mention them in the comments.

<a href="/rouilj/tag:WebDev" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">WebDev</span></a>, <a href="/rouilj/tag:opensource" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">opensource</span></a> <a href="/rouilj/tag:RoundupTracker" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">RoundupTracker</span></a>  <a href="/rouilj/tag:DevTo" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">DevTo</span></a></p>
]]></content:encoded>
      <guid>https://blog.rouilj.dynamic-dns.net/rouilj/copy-issue-reference-to-clipboard-enhancement</guid>
      <pubDate>Thu, 09 Nov 2023 00:01:02 +0000</pubDate>
    </item>
    <item>
      <title>To Release or Not to Release: What&#39;s a Good Schedule</title>
      <link>https://blog.rouilj.dynamic-dns.net/rouilj/to-release-or-not-to-release-whats-a-good-schedule</link>
      <description>&lt;![CDATA[&#xA;&#xA;Shakespeare once said &#34;A rose by any other name would smell as sweet&#34;. I wonder if something similar can be said for software releases.&#xA;&#xA;I am planning a new release of the opensource Roundup Issue Tracker. Historically, release schedules were on an as needed basis. Before 2006 (when version 1.0.0 was released) there were multiple years with more than 10 releases (including alpha/beta and other pre releases).&#xA;&#xA;| Year |  Releases| Year |  Releases|&#xA;|:----:| --------:|:----:| --------:|&#xA;| 2022 |        2 | 2009 |        5 |&#xA;| 2021 |        2 | 2008 |        5 |&#xA;| 2020 |        2 | 2007 |        3 |&#xA;| 2019 |        2 | 2006 |       13 |&#xA;| 2018 |        1 | 2005 |        8 |&#xA;| 2016 |        1 | 2004 |       19 |&#xA;| 2013 |        1 | 2003 |       16 |&#xA;| 2012 |        2 | 2002 |       12 |&#xA;| 2011 |        3 | 2001 |        7 |&#xA;| 2010 |        5 |&#xA;&#xA;But there were no releases from 2013 until 2016. Also, there was a gap in 2017. There was significant development done during those years without releases. But only those willing to run code from the repository were able to benefit.  In 2018 I took over as release manager and chose to start doing yearly releases.&#xA;&#xA;Why Release&#xA;&#xA;With opensource projects, there are many reasons to release:&#xA;!--more--&#xA;get features/bug fixes to users&#xA;thank contributors&#xA;increase project visibility&#xA;establish expectations&#xA;provide deadlines&#xA;&#xA;As I mentioned above, producing a release makes the work of contributors (developers, documentation writers, people reporting issues) isn&#39;t generally available. Frequent releases align with my preference to reduce WIP (work in progress). Thanking the contributors by releasing their work helps drive enthusiasm and engagement.&#xA;&#xA;Announcements on multiple platforms accompany each release. When I see another issue tracking/bug reporting program release announcement, I experience FOMO over the marketing provided by their announcement.&#xA;&#xA;A regular release cadence helps users to schedule time for testing or upgrading the software. Also, regularly scheduled releases encourage  developers to finish their work and get it into a release. This deadline also drives me to go through all the steps to generate a beta and final release. This year (2023) will be my 6th yearly release.&#xA;&#xA;When to Release&#xA;&#xA;Although I do believe that reducing WIP is important, Roundup is not provided as a SAS (software as a service). It is deployed on-prem and meant to be customized. Some changes to Roundup require changes to a customized installation as part of the upgrade. For example:&#xA;&#xA;upgrade from Python 2 to Python 3 can require rewriting a tracker&#39;s business logic&#xA;support for newer databases versions can require table alteration (sometimes manual, sometimes programmatic using the roundup-admin migrate command)&#xA;security improvements and CSRF protection require changes to customized HTML templates.&#xA;&#xA;I mentioned my FOMO when seeing other products release multiple times a year. I often see releases with 5 or so changes.  Often these changes fix cosmetic issues or broken fields in the web interface.&#xA;&#xA;With Roundup, many users have redesigned/replaced part of the interface. A release only supplies changes to the reference interfaces. The Roundup administrator needs to merge the reference changes into their production interface. In Roundup, changes can be implemented in production without requiring a release. This separation of concerns between the Roundup core and a customizable tracker makes Roundup more flexible. But, increases the work required during an upgrade.&#xA;&#xA;Because of how customizable Roundup is, I try to balance between WIP and the churn caused by frequent releases. Yearly seems to be a sweet spot. There are occasions when a new core feature needs a change/bug fix. In that case the user has a choice to wait for a new release or use the most recent commit to the development repository.&#xA;&#xA;If Roundup was a SAS with more limited customizability, we could do canary deploys directly from the repository. This would positively affect:&#xA;&#xA;getting features/bug fixes to users&#xA;thank contributors&#xA;&#xA;but would also reduce the utility of Roundup. Setting up a Getting Things Done tracker for his/her family would find it very difficult in a SAS based model. Also, the tempo of releases in a SAS based model may not align with the user&#39;s needs for a stable platform.&#xA;&#xA;If Shakespeare were a software writer would he have penned &#34;Would software by another release schedule smell as sweet?&#34;. Leave your thoughts in the comments.&#xA;&#xA;(Cover Image by Glen Carrie)&#xA;&#xA;#webdev, #discuss, #opensource, #development&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mwyvykplf28yj7kweg70.jpg" alt="" title="Banner Image"></p>

<p>Shakespeare once said “A rose by any other name would smell as sweet”. I wonder if something similar can be said for software releases.</p>

<p>I am planning a new release of the opensource <a href="https://www.roundup-tracker.org" rel="nofollow">Roundup Issue Tracker</a>. Historically, release schedules were on an as needed basis. Before 2006 (when version 1.0.0 was released) there were multiple years with more than 10 releases (including alpha/beta and other pre releases).</p>

<table>
<thead>
<tr>
<th align="center">Year</th>
<th align="right">Releases</th>
<th align="center">Year</th>
<th align="right">Releases</th>
</tr>
</thead>

<tbody>
<tr>
<td align="center">2022</td>
<td align="right">2</td>
<td align="center">2009</td>
<td align="right">5</td>
</tr>

<tr>
<td align="center">2021</td>
<td align="right">2</td>
<td align="center">2008</td>
<td align="right">5</td>
</tr>

<tr>
<td align="center">2020</td>
<td align="right">2</td>
<td align="center">2007</td>
<td align="right">3</td>
</tr>

<tr>
<td align="center">2019</td>
<td align="right">2</td>
<td align="center">2006</td>
<td align="right">13</td>
</tr>

<tr>
<td align="center">2018</td>
<td align="right">1</td>
<td align="center">2005</td>
<td align="right">8</td>
</tr>

<tr>
<td align="center">2016</td>
<td align="right">1</td>
<td align="center">2004</td>
<td align="right">19</td>
</tr>

<tr>
<td align="center">2013</td>
<td align="right">1</td>
<td align="center">2003</td>
<td align="right">16</td>
</tr>

<tr>
<td align="center">2012</td>
<td align="right">2</td>
<td align="center">2002</td>
<td align="right">12</td>
</tr>

<tr>
<td align="center">2011</td>
<td align="right">3</td>
<td align="center">2001</td>
<td align="right">7</td>
</tr>

<tr>
<td align="center">2010</td>
<td align="right">5</td>
<td align="center"></td>
<td align="right"></td>
</tr>
</tbody>
</table>

<p>But there were no releases from 2013 until 2016. Also, there was a gap in 2017. There was significant development done during those years without releases. But only those willing to run code from the repository were able to benefit.  In 2018 I took over as release manager and chose to start doing yearly releases.</p>

<h2 id="why-release" id="why-release">Why Release</h2>

<p>With opensource projects, there are many reasons to release:

* get features/bug fixes to users
* thank contributors
* increase project visibility
* establish expectations
* provide deadlines</p>

<p>As I mentioned above, producing a release makes the work of contributors (developers, documentation writers, people reporting issues) isn&#39;t generally available. Frequent releases align with my preference to reduce <a href="https://en.wikipedia.org/wiki/Work_in_process" rel="nofollow">WIP (work in progress)</a>. Thanking the contributors by releasing their work helps drive enthusiasm and engagement.</p>

<p>Announcements on multiple platforms accompany each release. When I see another issue tracking/bug reporting program release announcement, I experience <a href="https://www.merriam-webster.com/dictionary/FOMO" rel="nofollow">FOMO</a> over the marketing provided by their announcement.</p>

<p>A regular release cadence helps users to schedule time for testing or upgrading the software. Also, regularly scheduled releases encourage  developers to finish their work and get it into a release. This deadline also drives me to go through all the steps to generate a beta and final release. This year (2023) will be my 6th yearly release.</p>

<h2 id="when-to-release" id="when-to-release">When to Release</h2>

<p>Although I do believe that reducing WIP is important, Roundup is not provided as a SAS (software as a service). It is deployed on-prem and meant to be customized. Some changes to Roundup require changes to a customized installation as part of the upgrade. For example:</p>
<ul><li>upgrade from Python 2 to Python 3 can require rewriting a tracker&#39;s business logic</li>
<li>support for newer databases versions can require table alteration (sometimes manual, sometimes programmatic using the <code>roundup-admin migrate</code> command)</li>
<li>security improvements and CSRF protection require changes to customized HTML templates.</li></ul>

<p>I mentioned my FOMO when seeing other products release multiple times a year. I often see releases with 5 or so changes.  Often these changes fix cosmetic issues or broken fields in the web interface.</p>

<p>With Roundup, many users have redesigned/replaced part of the interface. A release only supplies changes to the reference interfaces. The Roundup administrator needs to merge the reference changes into their production interface. In Roundup, changes can be implemented in production without requiring a release. This separation of concerns between the Roundup core and a customizable tracker makes Roundup more flexible. But, increases the work required during an upgrade.</p>

<p>Because of how customizable Roundup is, I try to balance between WIP and the churn caused by frequent releases. Yearly seems to be a sweet spot. There are occasions when a new core feature needs a change/bug fix. In that case the user has a choice to wait for a new release or use the most recent commit to the development repository.</p>

<p>If Roundup was a SAS with more limited customizability, we could do canary deploys directly from the repository. This would positively affect:</p>
<ul><li>getting features/bug fixes to users</li>
<li>thank contributors</li></ul>

<p>but would also reduce the utility of Roundup. Setting up a <a href="https://gettingthingsdone.com/" rel="nofollow">Getting Things Done</a> tracker for his/her family would find it very difficult in a SAS based model. Also, the tempo of releases in a SAS based model may not align with the user&#39;s needs for a stable platform.</p>

<p>If Shakespeare were a software writer would he have penned “Would software by another release schedule smell as sweet?”. Leave your thoughts in the comments.</p>

<p>(<a href="https://unsplash.com/photos/P1kP-BfIelI" rel="nofollow">Cover Image by Glen Carrie</a>)</p>

<p><a href="/rouilj/tag:webdev" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">webdev</span></a>, <a href="/rouilj/tag:discuss" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">discuss</span></a>, <a href="/rouilj/tag:opensource" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">opensource</span></a>, <a href="/rouilj/tag:development" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">development</span></a></p>
]]></content:encoded>
      <guid>https://blog.rouilj.dynamic-dns.net/rouilj/to-release-or-not-to-release-whats-a-good-schedule</guid>
      <pubDate>Mon, 12 Jun 2023 16:58:42 +0000</pubDate>
    </item>
    <item>
      <title>Mobile Web Page Debugging Without a Desktop or USB Cable</title>
      <link>https://blog.rouilj.dynamic-dns.net/rouilj/mobile-web-page-debugging-without-a-desktop-or-usb-cable</link>
      <description>&lt;![CDATA[img src=&#34;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bkoel2wt5oi2ia91atau.png&#34; height=520px title=&#34;Banner image&#34;&#xA;&#xA;One of the things I have to do when developing a new tracker for the Roundup Issue Tracker is test on mobile browsers. When I run into issues, I used to hook up a remote debugger. This usually requires finding a USB cable as wifi is less reliable.&#xA;&#xA;I really wish that mobile browsers had a native console that I could use to see console.log output and javascript errors. While not native, MobileConsole by hnldesign provides quite a bit of power by including one script and one style tag in my HTML page. At 45k uncompressed (17k gzipped) it&#39;s sized between the smallest and largest mobile consoles I found.&#xA;!--more--&#xA;It can be seen at the bottom of the web page:&#xA;&#xA;Phone screenshot showing the MobileConsole displayed on the bottom half of the screen. The top half displays a web page. There is a movable divider between the two. The console is showing log messages from the page along with the line number where they were logged&#xA;&#xA;It supports the usual console output functions (error, ... debug). Plus it supports console.time/timeEnd and console.trace along with the group family. It also displays objects collapsed and allows you to toggle them open. The console can be resized allowing you to reduce the screen real-estate used on a mobile device.&#xA;&#xA;It has some areas for improvement:&#xA;   it only opens objects one level deep. So an array in an object is displayed but you can&#39;t expand the array further to get access to its elements.&#xA;   grouping is not nested or collapsible. But this is noted as an area for improvement.&#xA;&#xA;If you do a lot of logging, you might want to download the library, open your editor, and apply a change that reduces redraws triggered by console output.&#xA;&#xA;One thing to note is that running commands in the console will not work if you are using a Content Security Policy (CSP). Any CSP that does not allow &#39;unsafe-eval&#39; will prevent console commands from running. But at least you will see the error in the console 8-).&#xA;&#xA;There are other mobile consoles as well:&#xA;&#xA;   mobileConsole is the smallest at 5k, but appears to only support console.log. Documentation is sparse and there is no demo.&#xA;   B1naryStudio js-mobile-console - is 6k in size. It has methods to show/hide, enable auto-opening on error, and create command aliases.&#xA;However, in its demo, I had issues with it displaying document.querySelect(&#39;body&#39;). All it showed was an empty object {}.&#xA;   Eruda is a console with a size of 130k gzipped. However, with that size, you get an element browser, network monitor, and other tools you know from your desktop browser&#39;s DevTools. It also has a plugin ecosystem. It properly handles console.group as a toggle. Also you can submit multi-line scripts. I think the only thing Eruda is missing is a debugger 8-).&#xA;&#xA;Using hnldesign&#39;s MobileBrowser I was able to figure out why my javascript was crashing. If your hardware can handle it though, Eruda has many more features to make debugging on mobile device easier. Now the only time I need to find a USB cable is when I have to debug using breakpoints.&#xA;&#xA;#WebDev #DevDotTo #OpenSource, #javascript&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bkoel2wt5oi2ia91atau.png" title="Banner image"></p>

<p>One of the things I have to do when developing a new tracker for the <a href="https://www.roundup-tracker.org?ref=dev_to" rel="nofollow">Roundup Issue Tracker</a> is test on mobile browsers. When I run into issues, I used to hook up a remote debugger. This usually requires finding a USB cable as wifi is less reliable.</p>

<p>I really wish that mobile browsers had a native console that I could use to see <code>console.log</code> output and javascript errors. While not native, <a href="https://github.com/c-kick/mobileConsole" rel="nofollow">MobileConsole by hnldesign</a> provides quite a bit of power by including one script and one style tag in my HTML page. At 45k uncompressed (17k gzipped) it&#39;s sized between the smallest and largest mobile consoles I found.

It can be seen at the bottom of the web page:</p>

<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jmkbsekrqfzsmuy1v75p.png" alt="Phone screenshot showing the MobileConsole displayed on the bottom half of the screen. The top half displays a web page. There is a movable divider between the two. The console is showing log messages from the page along with the line number where they were logged"></p>

<p>It supports the usual console output functions (<code>error</code>, ... <code>debug</code>). Plus it supports <code>console.time</code>/<code>timeEnd</code> and <code>console.trace</code> along with the <code>group</code> family. It also displays objects collapsed and allows you to toggle them open. The console can be resized allowing you to reduce the screen real-estate used on a mobile device.</p>

<p>It has some areas for improvement:
   * it only opens objects one level deep. So an array in an object is displayed but you can&#39;t expand the array further to get access to its elements.
   * grouping is not nested or collapsible. But this is noted as an area for improvement.</p>

<p>If you do a lot of logging, you might want to download the library, open your editor, and apply <a href="https://github.com/c-kick/mobileConsole/issues/2" rel="nofollow">a change that reduces redraws triggered by console output</a>.</p>

<p>One thing to note is that running commands in the console will not work if you are using a Content Security Policy (CSP). Any CSP that does not allow <code>&#39;unsafe-eval&#39;</code> will prevent console commands from running. But at least you will see the error in the console 8-).</p>

<p>There are other mobile consoles as well:</p>
<ul><li><a href="https://github.com/ljcucc/mobileConsole" rel="nofollow">mobileConsole</a> is the smallest at 5k, but appears to only support console.log. Documentation is sparse and there is no demo.</li>
<li><a href="https://github.com/B1naryStudio/js-mobile-console" rel="nofollow">B1naryStudio js-mobile-console</a> – is 6k in size. It has methods to show/hide, enable auto-opening on error, and create command aliases.
However, in its demo, I had issues with it displaying <code>document.querySelect(&#39;body&#39;)</code>. All it showed was an empty object <code>{}</code>.</li>
<li><a href="https://github.com/liriliri/eruda" rel="nofollow">Eruda</a> is a console with a size of 130k gzipped. However, with that size, you get an element browser, network monitor, and other tools you know from your desktop browser&#39;s DevTools. It also has a plugin ecosystem. It properly handles console.group as a toggle. Also you can submit multi-line scripts. I think the only thing Eruda is missing is a debugger 8-).</li></ul>

<p>Using hnldesign&#39;s MobileBrowser I was able to figure out why my javascript was crashing. If your hardware can handle it though, Eruda has many more features to make debugging on mobile device easier. Now the only time I need to find a USB cable is when I have to debug using breakpoints.</p>

<p><a href="/rouilj/tag:WebDev" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">WebDev</span></a> <a href="/rouilj/tag:DevDotTo" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">DevDotTo</span></a> <a href="/rouilj/tag:OpenSource" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">OpenSource</span></a>, <a href="/rouilj/tag:javascript" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">javascript</span></a></p>
]]></content:encoded>
      <guid>https://blog.rouilj.dynamic-dns.net/rouilj/mobile-web-page-debugging-without-a-desktop-or-usb-cable</guid>
      <pubDate>Mon, 20 Feb 2023 06:53:04 +0000</pubDate>
    </item>
    <item>
      <title>Implementing a Command Palette and Task Timer</title>
      <link>https://blog.rouilj.dynamic-dns.net/rouilj/implementing-a-command-palette-and-task-timer</link>
      <description>&lt;![CDATA[&#xA;I am a developer for the open source Roundup Issue Tracker. It has many use cases. One is to develop issue trackers like GitHub Issues, Bugzilla, or Request Tracker. I also develop a custom issue tracker for a help desk environment. This article describes the steps in adding a task-timing feature for that tracker.&#xA;&#xA;A user requested a task timer. The workflow:&#xA;&#xA;  open an issue page,&#xA;  start a timer,&#xA;  start the task associated with the issue,&#xA;  use the issue page to document the work.&#xA; &#xA;When done, the user would:&#xA;&#xA;  stop the timer,&#xA;  finish documentation, attach files, and make other changes to the issue,&#xA;  submit the time and other changes. &#xA;&#xA;There are a few decisions to make:&#xA;&#xA;  timer functions&#xA;     is start/stop enough?&#xA;     is pause/restart needed?&#xA;     does the user need to change/set/edit the timer?&#xA;     do we need to track seconds, minutes, and hours? Since the customer is billed for the time, recording seconds seems excessive. Is there a scenario where the timer would need to record seconds?&#xA;  timer controls&#xA;     do we use a button/buttons? Does checking a checkbox activate the timer?&#xA;     what does the UI look like for each timer function?&#xA;        Are the controls in the issue page?&#xA;        Are the controls in a floating popup? Does the popup need to be movable/collapsible&#xA;&#x9;so it doesn&#39;t block access to the underlying issue?&#xA;  notification feedback&#xA;     how is the user notified that the timer is running/paused/stopped?&#xA;     how does the user see the elapsed time?&#xA;  future planning&#xA;     how to add controls without cluttering an already complex interface&#xA;     what impact will this have on adding future feature requests in the same context&#xA;     does this guide us in implementing future workflows&#xA;&#xA;!--more--&#xA;Evaluating a Command Palette&#xA;&#xA;I decided to try to install a command palette. A command palette is a UI interface usually activated by a hotkey. It allows searching and selecting from a context-sensitive list of commands. You might be familiar with the VS Code command palette. Command palettes have many advantages for users:&#xA;&#xA;   discover useful commands (and their shortcuts)&#xA;   faster than scrolling down a long list of commands&#xA;   keyboard is faster than using the mouse&#xA;   invisible until activated&#xA;   make commands available that wouldn&#39;t be important enough to get a button or other UI element&#xA;&#xA;There are many command palette implementations in JavaScript. Some are used with specific frameworks. For example, kbar is a React component and spotlight is a Laravel component. I wanted one that would work with Vanilla JavaScript. I identified two candidates:&#xA;&#xA;  command-pal - &#34;The hackable command palette for the web, inspired by Visual Studio Code.&#34;&#xA;  Ninja Keys - &#34;Keyboard shortcut interface for your website that works with Vanilla JS, Vue, and React.&#34;&#xA;&#xA;Ninja Keys uses Lit while command-pal uses Svelte. I don&#39;t have any experience with either, so.... Both of them can bind any command on the palette to a hotkey thanks to hotkeys.js. Both are MIT licensed. Command-pal is larger in size, but it also bundles all the libraries it needs. It looks like Ninja Keys loads libraries/modules on demand from CDNs on the internet. Being able to use the library without internet access is a nice feature.&#xA;&#xA;Command-pal promotes itself as &#34;hackable&#34;. This usually means flexibility and sometimes simplicity. I like both. Command-pal&#39;s feature set wasn&#39;t as impressive as Ninja Keys. But it does include a floating button to trigger the command palette on mobile. This is a nice touch as moving the page to access UI elements can be tedious on mobile.&#xA;&#xA;Adding command-pal&#xA;&#xA;As a result, I chose command-pal. Integrating it was easy. I downloaded the file from the CDN. I also downloaded the dark theme from GitHub. I added a script tag and stylesheet link to the top-level page Roundup template file. This makes command-pal available on all the tracker&#39;s pages.&#xA;&#xA;To invoke it, I added:&#xA;&#xA; const c = new CommandPal({&#xA;   hotkey: &#34;ctrl+space&#34;,&#xA;   hotkeysGlobal: true,&#xA;   commands: commands,&#xA; });&#xA; c.start();&#xA;inside a script tag. The tracker is a web application. Sadly, the classic command palette hotkeys: &#34;ctrl+k&#34; or &#34;ctrl+shift+p&#34; are already used by the browser. I also wanted to activate the palette using the hotkey when focused on an input, select, or textarea. hotkeysGlobal: true should do that, but it didn&#39;t work for me in Firefox, Chrome, or Brave). I submitted a pull request to fix it.&#xA;&#xA;The commands array included:&#xA;&#xA;  [&#xA;    {&#xA;      name: &#34;Exit Command Palette&#34;,&#xA;      contexts: [ &#34;all&#34; ],&#xA;      weight: -10,&#xA;    },&#xA;    {&#xA;      name: &#34;Initial Screen&#34;,&#xA;      description: &#34;Screen shown after login.&#34;,&#xA;      contexts: [ &#34;all&#34; ],&#xA;      handler: () =  (window.location.href = &#34;.&#34;),&#xA;      shortcut: &#34;ctrl+i&#34;,&#xA;      weight: 5,&#xA;    }, ...&#xA;]&#xA;Besides the fields used by command-pal:&#xA;&#xA;  name,&#xA;  description,&#xA;  handler,&#xA;  shortcut&#xA;&#xA;I added extra fields:&#xA;&#xA;  weight&#xA;  contexts&#xA;&#xA;These were inspired by the Superhuman blog on building a remarkable command palette. Among the things they suggest are:&#xA;&#xA;   order commands by popularity/utility&#xA;   listed commands are context sensitive&#xA;&#xA;The commands are initially sorted by weight (using commands.sort()). This displays the most popular (highest weight) commands at the top of the menu. Displaying the search results sorted by weight is an ongoing project.&#xA;&#xA;The command is shown if its contexts property matches the current context. For example, the task timing commands only make sense when editing an issue. They should not be shown when viewing a list of issues, or a user&#39;s profile page. Inspecting the page&#39;s URL determines the current context. The code filters the command list, eliminating commands that are not appropriate for the context. Only then is CommandPal invoked.&#xA;&#xA;details&#xA;summary Originally written as a 2 part series. To see the transition click here or just continue reading...&#xA;/summary&#xA;I hope you have enjoyed learning about command palettes and command-pal in particular. In part 2, we will use command-pal to control the task timer and look at how it integrates with the tracker built using the Roundup Issue Tracker.&#xA;{% embed https://www.roundup-tracker.org %}&#xA;&#xA;!--article-break--&#xA;I am a developer for the open source Roundup Issue Tracker. It has many use cases. One is to develop issue trackers like GitHub Issues, Bugzilla, or Request Tracker. I also develop a custom issue tracker for a help desk environment. This article continues with the steps to add a task-timing feature for that tracker.&#xA;&#xA;In part 1 of this series I had just finished installing command-pal. Let&#39;s take a closer look at command-pal before we get to the timer.&#xA;/details&#xA;&#xA;Enhancing command-pal and Handling a Showstopper&#xA;&#xA;The  Superhuman blog post  lists other desirable features for a command palette:  &#xA;&#xA;  fuzzy search (for mispelings 8-)) - is included in command-pal using fuse.js. (It looks like there is a fork of Ninja Keys that has fuzzy search support.)&#xA;  icons - I created an issue and pull request to add support&#xA;  synonyms - the fuzzy search includes the description field. This helps broaden the matching terms. But a description shouldn&#39;t be a keyword/synonym list. fuse.js can search an array of strings that are part of an object. Adding this functionality is a work in progress.&#xA;&#xA;One interesting possibility is supporting multiple command palettes on a page. Each palette would have a different set of commands.  I am not sure that&#39;s a good idea. Superhuman suggests making the command palette omnipotent. Multiple palettes force the user to make a decision about which palette to activate. This breaks the idea of &#34;don&#39;t make me think&#34;.  I was able to create and activate multiple palettes with different hotkeys. However, more work on supporting multiple palettes on a page is needed.&#xA;&#xA;At its core, a command palette is a large select modal. Having the ability to activate the modal from Javascript could allow the palette to be used in more places without making the user think.&#xA;&#xA;I am pleased with command-pal. I have found it quite hackable even though I have never used Svelte before. However, I did have one potential showstopper.&#xA;&#xA;The tracker uses a Content Security Policy (CSP). Style blocks in the page include a nonce. If the nonce is missing or doesn&#39;t match the one in the CSP the style blocks are ignored. Svelte generates style blocks for each element that it creates/injects. These client-side blocks, don&#39;t have access to the server&#39;s CSP nonce. If they did have access to the nonce, the nonce would be useless for securing the page&#39;s assets. If the style blocks were in a file that could be fetched using a stylesheet link, everything would be fine. However, efforts to do this with Svelt have failed. Another alternative is to generate a secure hash (sha256, 384, or 512). How to get this generated at build time is unclear. However, I did find a way to calculate it at runtime that seems to work. I proposed a patch to allow an administrator to generate the hashes using the command-pal library.&#xA;&#xA;The mechanism for controlling the task timer is done. Now to turn my attention to actually timing tasks.&#xA;&#xA;Let&#39;s Time All The Things&#xA;&#xA;I chose the easytimer.js library. It supports:&#xA;&#xA;   setting an initial value to start counting time&#xA;   one minute timer granularity - to reduce CPU load&#xA;   pausing and restarting timers while keeping their accumulated time.&#xA; &#xA; The command palette allows the gross controls: stop/start/pause/resume. I still need to handle the other parts of the UI. The existing issue page provides a field for manually entering the task time. Rather than trying to create a new UI for the timer, I reused the existing field. The UI is relatively simple. There is a &#34;Time spent&#34; input element referred to as the &#34;time element&#34; below. &#xA;&#xA;  If the user prefills the time element with a number of minutes, the timer will start counting up from that time. This is helpful if you forgot to start the timer and start it after say 10 minutes.&#xA;  The use case only requires 1 minute precision. Since I am counting in minutes, I add one minute to the start time. This rounds the time up to the next minute.&#xA;  The time element is updated only once a minute. This is great for reducing CPU use, but poor for user feedback. There needs to be some way to notify the user that the timer is running. This needs to work without interfering with the ability to use the rest of the issue interface. Using a popup could work. But popups clutter the interface. If it can&#39;t be moved, it may hide something the user wants to use.&#xA;  Instead, I cycle the background color for the time element from yellow to goldenrod every 5 seconds. This is done using CSS rather than javascript. It should perform better than updating the input with a flashing indicator every second.&#xA;  The animation stops when the timer is paused. But the yellow background color is still shown in the time element.&#xA;  When the timer stops, the background of the time element returns to white.&#xA;  Besides the time element displaying state, other elements of the page change as well. Starting the timer makes a pending change to the issue. The issue page already has a mechanism for indicating a page with a pending change. Starting the timer triggers this mechanism. This results in:&#xA;    a change in the background color of the time element label (&#34;Time spent&#34;)&#xA;    a change in the background color for the H1 header on the page&#xA;    the H1 header on the page gets &#34;pending changes&#34; appended&#xA;    the title for the page has an exclamation mark prepended to it; allowing the page to be identified in a list of page titles&#xA;    the favicon for the page is overlayed with an exclamation mark inside a yellow dot; allowing the tab to be identified if the text can&#39;t be shown.&#xA;&#xA;The command-pal Javascript Entries&#xA;&#xA;Here are the three commands for working with the timer:&#xA;&#xA;// more commands&#xA;{&#xA;  name: &#34;Start or Unpause Timer&#34;,&#xA;  description: &#34;timer&#34;,&#xA;  contexts: [ &#34;issue.item&#34; ],  // only show timers on an issue page&#xA;  handler: () =  {&#xA;    if ( ! window.userTimer ) {&#xA;      try {&#xA;&#x9;// create the object&#xA;&#x9;window.userTimer = {&#xA;&#x9;  timer: new easytimer.Timer({precision: &#34;minutes&#34;}),&#xA;&#x9;  timeField: document.getElementById(&#34;time&#34;),&#xA;&#x9;  animation: null,&#xA;&#x9;}&#xA;      } catch (err) {&#xA;&#x9;alert(Error: ${err.name} - ${err.message})&#xA;      }&#xA;    }&#xA;&#xA;    let timer = window.userTimer.timer;&#xA;    let timeField = null;&#xA;    if ( window.userTimer.timeField ) {&#xA;      timeField = window.userTimer.timeField;&#xA;    } else {&#xA;      alert(&#34;Unable to find &#39;Time Spent&#39; field. Are you viewing an issue?&#34;);&#xA;      return;&#xA;    }&#xA;&#xA;    if ( timer.isRunning() ) {&#xA;      alert(&#34;Timer is running for: &#34; + &#xA;&#x9;    timer.getTimeValues())&#xA;      return;&#xA;    }&#xA;&#xA;    // restart timer&#xA;    if ( timer.isPaused() ) {&#xA;      timer.start();&#xA;      alert(&#34;Timer restarted at: &#34; + &#xA;&#x9;    timer.getTimeValues())&#xA;      window.userTimer.animation.play()&#xA;      return;&#xA;    }&#xA;&#xA;    // start timer instance&#xA;    let timeValue = parseInt(timeField.value);&#xA;    if (! timeValue || isNaN(timeValue)) { timeValue=0; }&#xA;    // round up time to next minute: 10 seconds -  1 minute&#xA;    let startValues = { minutes: timeValue + 1 }&#xA;&#xA;    timer.start({&#xA;      startValues: startValues,&#xA;      callback: function (timer) {&#xA;&#x9;let timeField = window.userTimer.timeField&#xA;&#x9;timeField.value = timer.getTotalTimeValues().minutes;&#xA;      }&#xA;    });&#xA;    alert(&#34;Timer started at: &#34; +&#xA;&#x9;  timer.getTimeValues())&#xA;    window.userTimer.animation = animatetimer(timeField);&#xA;&#xA;    timeField.value = timer.getTotalTimeValues().minutes;&#xA;&#xA;    // mark field/page with a pending change&#xA;    timeField.dispatchEvent(new Event(&#39;change&#39;,&#xA;&#x9;&#x9;&#x9;&#x9;      {bubbles: true}))&#xA;  },&#xA;  icon: &#39;span class=&#34;icon&#34;&amp;nbsp;⏱️ /span&#39;, // stopwatch emoji&#xA;  weight: 10, // sort this to the top of the list&#xA;},&#xA;{&#xA;  name: &#34;Pause Timer&#34;,&#xA;  contexts: [ &#34;issue.item&#34; ],&#xA;  description: &#34;Update time field and snooze the timer&#34;,&#xA;  handler: () =  {&#xA;    if (! (window.userTimer &amp;&amp; window.userTimer.timer) ) {&#xA;      alert(&#34;No timer was started.&#34;);&#xA;      return;&#xA;    }&#xA;&#xA;    let timer = window.userTimer.timer&#xA;    let timeField = window.userTimer.timeField&#xA;    let animation = window.userTimer.animation&#xA;&#xA;    if ( ! timer.isRunning() ) {&#xA;      alert(&#34;Timer is not running, time is: &#34; +&#xA;&#x9;    timer.getTimeValues());&#xA;      return;&#xA;    }&#xA;    timer.pause();&#xA;    animation.pause()&#xA;&#xA;    timeField.value = timer.getTotalTimeValues().minutes;&#xA;    alert(&#34;Timer paused at: &#34; +  timer.getTimeValues());&#xA;  },&#xA;},&#xA;{&#xA;  name: &#34;Stop Timer&#34;,&#xA;  contexts: [ &#34;issue.item&#34; ],&#xA;  handler: () =  {&#xA;    if (! (window.userTimer &amp;&amp; window.userTimer.timer) ) {&#xA;      alert(&#34;No timer was started.&#34;);&#xA;      return;&#xA;    }&#xA;&#xA;    let timer = window.userTimer.timer&#xA;&#xA;    if ( ! ( timer.isRunning() || timer.isPaused() )) {&#xA;      alert(&#34;Timer is not running, time is: &#34; +&#xA;&#x9;    timer.getTimeValues());&#xA;      return;&#xA;    }&#xA;&#xA;    let animation = window.userTimer.animation&#xA;    let timeField = window.userTimer.timeField&#xA;&#xA;    timer.pause(); // stop time update&#xA;    timeField.value = timer.getTotalTimeValues().minutes;&#xA;    timer.stop(); // also zero&#39;s timer.&#xA;    animation.cancel();&#xA;    window.userTimer.animation = null;&#xA;  },&#xA;},&#xA;// more commands&#xA;(Note: the version of command-pal that I am running has the change to support icons.)&#xA;&#xA;There is one helper function to set up the animation for the &#34;Time spent&#34; input field:&#xA;&#xA;function animatetimer(input) {&#xA;  return input.animate(&#xA;    [&#xA;      {backgroundColor: &#39;yellow&#39;, easing: &#39;linear&#39;},&#xA;      {backgroundColor: &#39;goldenrod&#39;, easing: &#39;linear&#39;},&#xA;      {backgroundColor: &#39;yellow&#39;, easing: &#39;linear&#39;}&#xA;    ],&#xA;    {duration: 5000, iterations: Infinity}&#xA;  );&#xA;}&#xA;&#xA;Working Example&#xA;&#xA;You can see this in action on the demo site. Use demo/demo for login. Activate the palette using ctrl+space. The code is also published.&#xA;&#xA;#javascript #opensource #DevDotTo #WebDev&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xejupqot8xa8g34r4iff.png" alt="" title="Banner image">
I am a developer for the open source <a href="https://www.roundup-tracker.org" rel="nofollow">Roundup Issue Tracker</a>. It has many use cases. One is to develop issue trackers like GitHub Issues, Bugzilla, or Request Tracker. I also develop a custom issue tracker for a help desk environment. This article describes the steps in adding a task-timing feature for that tracker.</p>

<p>A user requested a task timer. The workflow:</p>
<ul><li>open an issue page,</li>
<li>start a timer,</li>
<li>start the task associated with the issue,</li>
<li>use the issue page to document the work.</li></ul>

<p>When done, the user would:</p>
<ul><li>stop the timer,</li>
<li>finish documentation, attach files, and make other changes to the issue,</li>
<li>submit the time and other changes.</li></ul>

<p>There are a few decisions to make:</p>
<ul><li>timer functions
<ul><li>is start/stop enough?</li>
<li>is pause/restart needed?</li>
<li>does the user need to change/set/edit the timer?</li>
<li>do we need to track seconds, minutes, and hours? Since the customer is billed for the time, recording seconds seems excessive. Is there a scenario where the timer would need to record seconds?</li></ul></li>
<li>timer controls
<ul><li>do we use a button/buttons? Does checking a checkbox activate the timer?</li>
<li>what does the UI look like for each timer function?
Are the controls in the issue page?
Are the controls in a floating popup? Does the popup need to be movable/collapsible
so it doesn&#39;t block access to the underlying issue?</li></ul></li>
<li>notification feedback
<ul><li>how is the user notified that the timer is running/paused/stopped?</li>
<li>how does the user see the elapsed time?</li></ul></li>
<li>future planning
<ul><li>how to add controls without cluttering an already complex interface</li>
<li>what impact will this have on adding future feature requests in the same context</li>
<li>does this guide us in implementing future workflows</li></ul></li></ul>



<h2 id="evaluating-a-command-palette" id="evaluating-a-command-palette">Evaluating a Command Palette</h2>

<p>I decided to try to install a <a href="https://www.commandbar.com/blog/command-palette-past-present-and-future" rel="nofollow">command palette</a>. A command palette is a UI interface usually activated by a hotkey. It allows searching and selecting from a context-sensitive list of commands. You might be familiar with the <a href="https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette" rel="nofollow">VS Code command palette</a>. Command palettes have many advantages for users:</p>
<ul><li>discover useful commands (and their shortcuts)</li>
<li>faster than scrolling down a long list of commands</li>
<li>keyboard is faster than using the mouse</li>
<li>invisible until activated</li>
<li>make commands available that wouldn&#39;t be important enough to get a button or other UI element</li></ul>

<p>There are many command palette implementations in JavaScript. Some are used with specific frameworks. For example, <a href="https://github.com/timc1/kbar" rel="nofollow">kbar</a> is a React component and <a href="https://github.com/livewire-ui/spotlight" rel="nofollow">spotlight</a> is a Laravel component. I wanted one that would work with <a href="https://www.javatpoint.com/what-is-vanilla-javascript" rel="nofollow">Vanilla JavaScript</a>. I identified two candidates:</p>
<ul><li><a href="https://github.com/benwinding/command-pal" rel="nofollow">command-pal</a> – “The hackable command palette for the web, inspired by Visual Studio Code.”</li>
<li><a href="https://github.com/ssleptsov/ninja-keys" rel="nofollow">Ninja Keys</a> – “Keyboard shortcut interface for your website that works with Vanilla JS, Vue, and React.”</li></ul>

<p>Ninja Keys uses <a href="https://lit.dev/" rel="nofollow">Lit</a> while command-pal uses <a href="https://svelte.dev" rel="nofollow">Svelte</a>. I don&#39;t have any experience with either, so.... Both of them can bind any command on the palette to a hotkey thanks to <a href="https://github.com/jaywcjlove/hotkeys/" rel="nofollow">hotkeys.js</a>. Both are MIT licensed. Command-pal is larger in size, but it also bundles all the libraries it needs. It looks like Ninja Keys loads libraries/modules on demand from CDNs on the internet. Being able to use the library without internet access is a nice feature.</p>

<p>Command-pal promotes itself as “hackable”. This usually means flexibility and sometimes simplicity. I like both. Command-pal&#39;s feature set wasn&#39;t as impressive as Ninja Keys. But it does include a floating button to trigger the command palette on mobile. This is a nice touch as moving the page to access UI elements can be tedious on mobile.</p>

<h2 id="adding-command-pal" id="adding-command-pal">Adding command-pal</h2>

<p>As a result, I chose command-pal. Integrating it was easy. I downloaded the file from the <a href="https://cdn.jsdelivr.net/npm/command-pal" rel="nofollow">CDN</a>. I also downloaded the <a href="https://github.com/benwinding/command-pal/tree/master/public" rel="nofollow">dark theme</a> from GitHub. I added a script tag and stylesheet link to the top-level page Roundup template file. This makes command-pal available on all the tracker&#39;s pages.</p>

<p>To invoke it, I added:</p>

<pre><code class="language-js"> const c = new CommandPal({
   hotkey: &#34;ctrl+space&#34;,
   hotkeysGlobal: true,
   commands: commands,
 });
 c.start();
</code></pre>

<p>inside a <code>script</code> tag. The tracker is a web application. Sadly, the classic command palette hotkeys: “ctrl+k” or “ctrl+shift+p” are already used by the browser. I also wanted to activate the palette using the hotkey when focused on an input, select, or textarea. <code>hotkeysGlobal: true</code> should do that, but it didn&#39;t work for me in Firefox, Chrome, or Brave). I <a href="https://github.com/benwinding/command-pal/pull/18" rel="nofollow">submitted a pull request to fix it.</a></p>

<p>The commands array included:</p>

<pre><code>  [
    {
      name: &#34;Exit Command Palette&#34;,
      contexts: [ &#34;all&#34; ],
      weight: -10,
    },
    {
      name: &#34;Initial Screen&#34;,
      description: &#34;Screen shown after login.&#34;,
      contexts: [ &#34;all&#34; ],
      handler: () =&gt; (window.location.href = &#34;.&#34;),
      shortcut: &#34;ctrl+i&#34;,_
      weight: 5,
    }, ...
]
</code></pre>

<p>Besides the fields used by command-pal:</p>
<ul><li>name,</li>
<li>description,</li>
<li>handler,</li>
<li>shortcut</li></ul>

<p>I added extra fields:</p>
<ul><li>weight</li>
<li>contexts</li></ul>

<p>These were inspired by the <a href="https://blog.superhuman.com/how-to-build-a-remarkable-command-palette/" rel="nofollow">Superhuman blog on building a remarkable command palette</a>. Among the things they suggest are:</p>
<ul><li>order commands by popularity/utility</li>
<li>listed commands are context sensitive</li></ul>

<p>The commands are initially sorted by weight (using commands.sort()). This displays the most popular (highest weight) commands at the top of the menu. Displaying the <a href="https://github.com/benwinding/command-pal/pull/30#issuecomment-1418245061" rel="nofollow">search results sorted by weight</a> is an ongoing project.</p>

<p>The command is shown if its <code>contexts</code> property matches the current context. For example, the task timing commands only make sense when editing an issue. They should not be shown when viewing a list of issues, or a user&#39;s profile page. Inspecting the page&#39;s URL determines the current context. The code filters the command list, eliminating commands that are not appropriate for the context. Only then is CommandPal invoked.</p>

<p><details>
<summary> <strong>Originally written as a 2 part series. To see the transition click here or just continue reading...</strong>
</summary>
I hope you have enjoyed learning about command palettes and command-pal in particular. In part 2, we will use command-pal to control the task timer and look at how it integrates with the tracker built using the Roundup Issue Tracker.
{% embed <a href="https://www.roundup-tracker.org" rel="nofollow">https://www.roundup-tracker.org</a> %}</p>



<p>I am a developer for the open source <a href="https://www.roundup-tracker.org" rel="nofollow">Roundup Issue Tracker</a>. It has many use cases. One is to develop issue trackers like GitHub Issues, Bugzilla, or Request Tracker. I also develop a custom issue tracker for a help desk environment. This article continues with the steps to add a task-timing feature for that tracker.</p>

<p>In <a href="https://dev.to/rouilj/implementing-a-command-palette-and-task-timer-part-1-12j7" rel="nofollow">part 1 of this series</a> I had just finished installing command-pal. Let&#39;s take a closer look at command-pal before we get to the timer.
</details></p>

<h2 id="enhancing-command-pal-and-handling-a-showstopper" id="enhancing-command-pal-and-handling-a-showstopper">Enhancing command-pal and Handling a Showstopper</h2>

<p>The  <a href="https://blog.superhuman.com/how-to-build-a-remarkable-command-palette/" rel="nofollow">Superhuman blog post</a>  lists other desirable features for a command palette:</p>
<ul><li><strong>fuzzy search</strong> (for mispelings 8-)) – is included in command-pal using <a href="https://fusejs.io/" rel="nofollow">fuse.js</a>. (It looks like there is a <a href="https://www.npmjs.com/package/@deepdub/ninja-keys" rel="nofollow">fork of Ninja Keys that has fuzzy search support</a>.)</li>
<li><strong>icons</strong> – I created an <a href="https://github.com/benwinding/command-pal/issues/22" rel="nofollow">issue</a> and <a href="https://github.com/benwinding/command-pal/pull/23" rel="nofollow">pull request</a> to add support</li>
<li><strong>synonyms</strong> – the fuzzy search includes the description field. This helps broaden the matching terms. But a description shouldn&#39;t be a keyword/synonym list. fuse.js can search an array of strings that are part of an object. Adding this functionality is a <a href="https://github.com/benwinding/command-pal/issues/8" rel="nofollow">work in progress</a>.</li></ul>

<p>One interesting possibility is supporting multiple command palettes on a page. Each palette would have a different set of commands.  I am not sure that&#39;s a good idea. Superhuman suggests making the command palette omnipotent. Multiple palettes force the user to make a decision about which palette to activate. This breaks the idea of <a href="https://www.interaction-design.org/literature/article/don-t-make-me-think-key-learning-points-for-ux-design-for-the-web" rel="nofollow">“don&#39;t make me think”</a>.  I was able to create and activate multiple palettes with different hotkeys. However, more work on <a href="https://github.com/benwinding/command-pal/issues/24" rel="nofollow">supporting multiple palettes on a page</a> is needed.</p>

<p>At its core, a command palette is a large select modal. Having the ability to <a href="https://github.com/benwinding/command-pal/pull/28" rel="nofollow">activate the modal from Javascript</a> could allow the palette to be used in more places without making the user think.</p>

<p>I am pleased with command-pal. I have found it quite hackable even though I have never used Svelte before. However, I did have one potential showstopper.</p>

<p>The tracker uses a <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP" rel="nofollow">Content Security Policy (CSP)</a>. Style blocks in the page include a <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce" rel="nofollow">nonce</a>. If the nonce is missing or doesn&#39;t match the one in the CSP the style blocks are ignored. Svelte generates style blocks for each element that it creates/injects. These client-side blocks, don&#39;t have access to the server&#39;s CSP nonce. If they did have access to the nonce, the nonce would be useless for securing the page&#39;s assets. If the style blocks were in a file that could be fetched using a stylesheet link, everything would be fine. However, <a href="https://github.com/benwinding/command-pal/issues/13#issuecomment-1416721301" rel="nofollow">efforts to do this</a> with Svelt have failed. Another alternative is to generate a <a href="https://content-security-policy.com/hash/" rel="nofollow">secure hash (sha256, 384, or 512)</a>. How to get this generated at build time is unclear. However, <a href="https://github.com/benwinding/command-pal/issues/13" rel="nofollow">I did find a way</a> to calculate it at runtime that seems to work. I <a href="https://github.com/benwinding/command-pal/pull/21" rel="nofollow">proposed a patch to allow an administrator to generate the hashes</a> using the command-pal library.</p>

<p>The mechanism for controlling the task timer is done. Now to turn my attention to actually timing tasks.</p>

<h2 id="let-s-time-all-the-things" id="let-s-time-all-the-things">Let&#39;s Time All The Things</h2>

<p>I chose the <a href="http://albert-gonzalez.github.io/easytimer.js/" rel="nofollow">easytimer.js</a> library. It supports:</p>
<ul><li>setting an initial value to start counting time</li>
<li>one minute timer granularity – to reduce CPU load</li>
<li>pausing and restarting timers while keeping their accumulated time.</li></ul>

<p> The command palette allows the gross controls: stop/start/pause/resume. I still need to handle the other parts of the UI. The existing issue page provides a field for manually entering the task time. Rather than trying to create a new UI for the timer, I reused the existing field. The UI is relatively simple. There is a “Time spent” input element referred to as the “time element” below.</p>
<ol><li>If the user prefills the time element with a number of minutes, the timer will start counting up from that time. This is helpful if you forgot to start the timer and start it after say 10 minutes.</li>
<li>The use case only requires 1 minute precision. Since I am counting in minutes, I add one minute to the start time. This rounds the time up to the next minute.</li>
<li>The time element is updated only once a minute. This is great for reducing CPU use, but poor for user feedback. There needs to be some way to notify the user that the timer is running. This needs to work without interfering with the ability to use the rest of the issue interface. Using a popup could work. But popups clutter the interface. If it can&#39;t be moved, it may hide something the user wants to use.
Instead, I cycle the background color for the time element from yellow to goldenrod every 5 seconds. This is done using CSS rather than javascript. It should perform better than updating the input with a flashing indicator every second.</li>
<li>The animation stops when the timer is paused. But the yellow background color is still shown in the time element.</li>
<li>When the timer stops, the background of the time element returns to white.</li>
<li>Besides the time element displaying state, other elements of the page change as well. Starting the timer makes a pending change to the issue. The issue page already has a mechanism for indicating a page with a pending change. Starting the timer triggers this mechanism. This results in:
<ul><li>a change in the background color of the time element label (“Time spent”)</li>
<li>a change in the background color for the H1 header on the page</li>
<li>the H1 header on the page gets “pending changes” appended</li>
<li>the title for the page has an exclamation mark prepended to it; allowing the page to be identified in a list of page titles</li>
<li>the favicon for the page is overlayed with an exclamation mark inside a yellow dot; allowing the tab to be identified if the text can&#39;t be shown.</li></ul></li></ol>

<h3 id="the-command-pal-javascript-entries" id="the-command-pal-javascript-entries">The command-pal Javascript Entries</h3>

<p>Here are the three commands for working with the timer:</p>

<pre><code class="language-js">// more commands
{
  name: &#34;Start or Unpause Timer&#34;,
  description: &#34;timer&#34;,
  contexts: [ &#34;issue.item&#34; ],  // only show timers on an issue page
  handler: () =&gt; {
    if ( ! window.userTimer ) {
      try {
	// create the object
	window.userTimer = {
	  timer: new easytimer.Timer({precision: &#34;minutes&#34;}),
	  timeField: document.getElementById(&#34;time&#34;),
	  animation: null,
	}
      } catch (err) {
	alert(`Error: ${err.name} - ${err.message}`)
      }
    }

    let timer = window.userTimer.timer;
    let timeField = null;
    if ( window.userTimer.timeField ) {
      timeField = window.userTimer.timeField;
    } else {
      alert(&#34;Unable to find &#39;Time Spent&#39; field. Are you viewing an issue?&#34;);
      return;
    }

    if ( timer.isRunning() ) {
      alert(&#34;Timer is running for: &#34; + 
	    timer.getTimeValues())
      return;
    }

    // restart timer
    if ( timer.isPaused() ) {
      timer.start();
      alert(&#34;Timer restarted at: &#34; + 
	    timer.getTimeValues())
      window.userTimer.animation.play()
      return;
    }

    // start timer instance
    let timeValue = parseInt(timeField.value);
    if (! timeValue || isNaN(timeValue)) { timeValue=0; }
    // round up time to next minute: 10 seconds -&gt; 1 minute
    let startValues = { minutes: timeValue + 1 }

    timer.start({
      startValues: startValues,
      callback: function (timer) {
	let timeField = window.userTimer.timeField
	timeField.value = timer.getTotalTimeValues().minutes;
      }
    });
    alert(&#34;Timer started at: &#34; +
	  timer.getTimeValues())
    window.userTimer.animation = animate_timer(timeField);

    timeField.value = timer.getTotalTimeValues().minutes;

    // mark field/page with a pending change
    timeField.dispatchEvent(new Event(&#39;change&#39;,
				      {bubbles: true}))
  },
  icon: &#39;&lt;span class=&#34;icon&#34;&gt;&amp;nbsp;⏱️ &lt;/span&gt;&#39;, // stopwatch emoji
  weight: 10, // sort this to the top of the list
},
{
  name: &#34;Pause Timer&#34;,
  contexts: [ &#34;issue.item&#34; ],
  description: &#34;Update time field and snooze the timer&#34;,
  handler: () =&gt; {
    if (! (window.userTimer &amp;&amp; window.userTimer.timer) ) {
      alert(&#34;No timer was started.&#34;);
      return;
    }

    let timer = window.userTimer.timer
    let timeField = window.userTimer.timeField
    let animation = window.userTimer.animation

    if ( ! timer.isRunning() ) {
      alert(&#34;Timer is not running, time is: &#34; +
	    timer.getTimeValues());
      return;
    }
    timer.pause();
    animation.pause()

    timeField.value = timer.getTotalTimeValues().minutes;
    alert(&#34;Timer paused at: &#34; +  timer.getTimeValues());
  },
},
{
  name: &#34;Stop Timer&#34;,
  contexts: [ &#34;issue.item&#34; ],
  handler: () =&gt; {
    if (! (window.userTimer &amp;&amp; window.userTimer.timer) ) {
      alert(&#34;No timer was started.&#34;);
      return;
    }

    let timer = window.userTimer.timer

    if ( ! ( timer.isRunning() || timer.isPaused() )) {
      alert(&#34;Timer is not running, time is: &#34; +
	    timer.getTimeValues());
      return;
    }

    let animation = window.userTimer.animation
    let timeField = window.userTimer.timeField

    timer.pause(); // stop time update
    timeField.value = timer.getTotalTimeValues().minutes;
    timer.stop(); // also zero&#39;s timer.
    animation.cancel();
    window.userTimer.animation = null;
  },
},
// more commands
</code></pre>

<p>(Note: the version of command-pal that I am running has the change to support icons.)</p>

<p>There is one helper function to set up the animation for the “Time spent” input field:</p>

<pre><code class="language-js">function animate_timer(input) {
  return input.animate(
    [
      {backgroundColor: &#39;yellow&#39;, easing: &#39;linear&#39;},
      {backgroundColor: &#39;goldenrod&#39;, easing: &#39;linear&#39;},
      {backgroundColor: &#39;yellow&#39;, easing: &#39;linear&#39;}
    ],
    {duration: 5000, iterations: Infinity}
  );
}
</code></pre>

<h2 id="working-example" id="working-example">Working Example</h2>

<p>You can see this in action on the <a href="https://rouilj.dynamic-dns.net/dev_to_demo/issue34" rel="nofollow">demo site</a>. Use demo/demo for login. Activate the palette using <code>ctrl+space</code>. The <a href="https://rouilj.dynamic-dns.net/fossil/roundup_sysadmin/timeline?r=command_palette_and_timer&amp;c=2023-02-02+17%3A34%3A05" rel="nofollow">code is also published</a>.</p>

<p><a href="/rouilj/tag:javascript" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">javascript</span></a> <a href="/rouilj/tag:opensource" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">opensource</span></a> <a href="/rouilj/tag:DevDotTo" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">DevDotTo</span></a> <a href="/rouilj/tag:WebDev" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">WebDev</span></a></p>
]]></content:encoded>
      <guid>https://blog.rouilj.dynamic-dns.net/rouilj/implementing-a-command-palette-and-task-timer</guid>
      <pubDate>Sun, 19 Feb 2023 22:35:18 +0000</pubDate>
    </item>
    <item>
      <title>My Journey to Open-Source Development</title>
      <link>https://blog.rouilj.dynamic-dns.net/rouilj/my-journey-to-open-source-development</link>
      <description>&lt;![CDATA[&lt;!--&#xA;comment: front matter from dev.to&#xA;published: true&#xA;description: How I started as a developer of the Roundup Issue Tracker.&#xA;canonicalurl: &#xA;coverimage: https://www.roundup-tracker.org/images/indexloggedout.png?ref=dev.to1&#xA;tags: #python, #webdev, #opensource &#xA;series: Roundup Tracker - notes from an open-source developer&#xA;--  Welcome to the first article on the ongoing development of the Roundup Issue Tracker. Before we get started, a little about me.&#xA;&#xA;I have a B.S. in Physics which included some advanced computer science courses. But, my career ended up in system administration. I have taught and published a few peer-reviewed papers at conferences over the years. Trying to reduce toil and improve operations, lead to my interest in ticketing systems. Tools from LEAN and Six Sigma have proven valuable in analyzing ticketing and real-time logging data.&#xA;&#xA;Issues Need Tracking&#xA;In 2002, I ran a lab that installed a system monitoring solution on Sun computers. We needed to track requests that came to the lab. I evaluated ticketing systems across a few dimensions. The Roundup Issue Tracker scored the highest.&#xA;&#xA;Five minutes after downloading the software, I had a classic tracker running in demo mode. After entering a few tickets and adding a few keywords (tags), my co-worker and I decided it could work. It was flexible and looked like we could extend it to support our future growth. Little did I know that this was the start of a 20+ year relationship.&#xA;!--more--&#xA;Roundup Basics&#xA;Roundup tracker has two components: the Roundup core and the tracker instance. The core is written in Python. The tracker instance supports one of two HTML templating engines: (Zope Page Templates or Jinja2). It is programmed in Python.  The tracker instance controls the look/feel/functionality of the tracker. It:&#xA;&#xA;   defines the database schema/data model,&#xA;   provides the web interface (with customizable HTML and CSS),&#xA;   executes Python code to validate entered data and react to a change (e.g. by emailing notification messages),&#xA;   can hook into the core code to control how the core operates.&#xA;&#xA;One instance of the Roundup core can run many trackers. Each tracker can have a very different presentation and function.&#xA;&#xA;Getting My Feet Wet&#xA;When I started, Roundup was still very new. I started developing my customized tracker to learn Python and experiment with Roundup. My tracker implemented several ideas from Request Tracker (RT). I developed the key components, replies/comments, dependencies, and grouping (parent/child), in less than 40 hours of effort over a few weeks. Roundup&#39;s implementation and documentation made building these significant features easy.&#xA;&#xA;The other advantage was that Richard Jones, the developer, was in Australia. We set up a &#34;follow the sun&#34; development cycle. I emailed him a problem when I went to bed. By the time I got up the next morning, he would have a fix ready for me to use when I got home after work. This fast feedback is something I try to maintain in the Roundup community.&#xA;&#xA;I never deployed my custom tracker, but the classic tracker worked well for the lab. As a result, the sales engineering group requested (and got) a tracker. After I left the company in 2005, they upgraded the Roundup core and converted the database to MySQL (from a dbm key/value store).&#xA;&#xA;Back in a More Active Role&#xA;My next IT job used Request Tracker. I followed Roundup development but didn&#39;t contribute much. In 2012, there was some interest at work in setting up a customer-facing portal based on Roundup. I started development on my tracker again and re-joined the Roundup community. Over the next few years, I developed code to add:&#xA;&#xA;  a transaction source property for data changes. Makes changes from the web interface but prevents the same change if it originated from (unsigned) email.&#xA;  the filter command to the roundup-admin command line interface makes data searching easier.&#xA;  nofollow relationships to links which makes Roundup less useful to spammers.&#xA;  CSRF protection using synchronizer tokens makes Roundup more secure.&#xA;&#xA;Releasing Roundup&#xA;By 2018, Richard had stepped back, the last release was January 2016. I took over as a lead developer and release engineer. I released version 1.6.0 in July of 2018. This merged several existing patches, cleaning up Roundup&#39;s issue tracker.&#xA;&#xA;Since 2018, there were two major changes added by other developers:&#xA;&#xA;   support for both Python 2 and Python 3 (which required a 2.0.0 release)&#xA;   addition of a REST interface beside the XML-RPC interface&#xA;&#xA;On July 13th, 2022, I made my fifth annual release.&#xA;&#xA;Since Roundup deployments are on-prem, I established a yearly release cadence. Other issue trackers have more frequent releases. But, many of those releases address UI or workflow issues. In Roundup these are implemented in the (customized) tracker instance. So the user is not dependent on the fixes pushed from upstream. Each release includes upgrading instructions. These instructions can include changes for the tracker instance to bring it up to date.&#xA;&#xA;The slower release cadence has an advantage for the users. It reduces the need to update tracker instances to once a year rather than every few months. However, the yearly cadence results in less public visibility for Roundup.  &#xA;&#xA;I plan more articles on developing and maintaining an open-source application. Some future topics I am considering include (smallin no particular order/small):&#xA;&#xA;   The Customisability Trap&#xA;   Kitchen Sink Development&#xA;   Accessibility&#xA;   Responsive Design&#xA;   The Problem with Selects&#xA;   What?? No Javascript!&#xA;   Docker All the Things&#xA;   Colors and Themes&#xA;   The Bus Factor&#xA;   Evaluating Tracking Software&#xA;&#xA;The Roundup 20supth/sup anniversary article on lwn.net includes more information on Roundup.&#xA;&#xA;I hope you enjoyed this article. Thanks for reading.&#xA;&#xA;tags: #Python, #WebDev, #opensource #RoundupTracker #seriesRoundupTrackerNotesFromAnOpenSourceDeveloper&#xA;DevDotTo&#xA;]]&gt;</description>
      <content:encoded><![CDATA[

<p>Welcome to the first article on the ongoing development of the <a href="https://www.roundup-tracker.org?ref=dev.to_1" rel="nofollow">Roundup Issue Tracker</a>. Before we get started, a little about me.</p>

<p>I have a B.S. in Physics which included some advanced computer science courses. But, my career ended up in system administration. I have <a href="https://www.usenix.org/legacy/events/lisa09/training/tutonefile.html#s2" rel="nofollow">taught</a> and published a <a href="https://www.usenix.org/legacy/events/lisa04/tech/full_papers/rouillard/rouillard_html/index.html" rel="nofollow">few</a> peer-reviewed <a href="https://static.usenix.org/publications/library/proceedings/lisa94/rouillard.html" rel="nofollow">papers</a> at conferences over the years. Trying to reduce toil and improve operations, lead to my interest in ticketing systems. Tools from LEAN and Six Sigma have proven valuable in analyzing ticketing and real-time logging data.</p>

<h2 id="issues-need-tracking" id="issues-need-tracking">Issues Need Tracking</h2>

<p>In 2002, I ran a lab that installed a system monitoring solution on Sun computers. We needed to track requests that came to the lab. I evaluated ticketing systems across a few dimensions. The <a href="https://www.roundup-tracker.org?ref=dev.to_1" rel="nofollow">Roundup Issue Tracker</a> scored the highest.</p>

<p>Five minutes after downloading the software, I had a classic tracker running in demo mode. After entering a few tickets and adding a few keywords (tags), my co-worker and I decided it could work. It was flexible and looked like we could extend it to support our future growth. Little did I know that this was the start of a 20+ year relationship.
</p>

<h2 id="roundup-basics" id="roundup-basics">Roundup Basics</h2>

<p>Roundup tracker has two components: the Roundup core and the tracker instance. The core is written in Python. The tracker instance supports one of two HTML templating engines: (<a href="https://pagetemplates.readthedocs.io/en/latest/introduction.html" rel="nofollow">Zope Page Templates</a> or <a href="https://palletsprojects.com/p/jinja/" rel="nofollow">Jinja2</a>). It is programmed in Python.  The tracker instance controls the look/feel/functionality of the tracker. It:</p>
<ul><li>defines the database schema/data model,</li>
<li>provides the web interface (with customizable HTML and CSS),</li>
<li>executes Python code to <a href="https://www.roundup-tracker.org/docs/customizing.html?ref=dev.to_1#detectors-adding-behaviour-to-your-tracker" rel="nofollow">validate entered data and react</a> to a change (e.g. by emailing notification messages),</li>
<li>can <a href="https://www.roundup-tracker.org/docs/customizing.html?ref=dev.to_1#interfaces-py-hooking-into-the-core-of-roundup" rel="nofollow">hook into the core code</a> to control how the core operates.</li></ul>

<p>One instance of the Roundup core can run many trackers. Each tracker can have a very different presentation and function.</p>

<h2 id="getting-my-feet-wet" id="getting-my-feet-wet">Getting My Feet Wet</h2>

<p>When I started, Roundup was still very new. I started developing my customized tracker to learn Python and experiment with Roundup. My tracker implemented several ideas from <a href="https://bestpractical.com/request-tracker/" rel="nofollow">Request Tracker (RT)</a>. I developed the key components, <a href="https://rt-wiki.bestpractical.com/wiki/ManualUsingWebInterface#Replying_and_commenting" rel="nofollow">replies/comments</a>, <a href="https://rt-wiki.bestpractical.com/wiki/ManualUsingWebInterface#Links" rel="nofollow">dependencies, and grouping (parent/child)</a>, in less than 40 hours of effort over a few weeks. Roundup&#39;s implementation and documentation made building these significant features easy.</p>

<p>The other advantage was that Richard Jones, the developer, was in Australia. We set up a “follow the sun” development cycle. I emailed him a problem when I went to bed. By the time I got up the next morning, he would have a fix ready for me to use when I got home after work. This fast feedback is something I try to maintain in the Roundup community.</p>

<p>I never deployed my custom tracker, but the classic tracker worked well for the lab. As a result, the sales engineering group requested (and got) a tracker. After I left the company in 2005, they upgraded the Roundup core and converted the database to MySQL (from a dbm key/value store).</p>

<h2 id="back-in-a-more-active-role" id="back-in-a-more-active-role">Back in a More Active Role</h2>

<p>My next IT job used Request Tracker. I followed Roundup development but didn&#39;t contribute much. In 2012, there was some interest at work in setting up a customer-facing portal based on Roundup. I started development on my tracker again and re-joined the Roundup community. Over the next few years, I developed code to add:</p>
<ul><li>a transaction source property for data changes. Makes changes from the web interface but prevents the same change if it originated from (unsigned) email.</li>
<li>the filter command to the <code>roundup-admin</code> command line interface makes data searching easier.</li>
<li><code>nofollow</code> relationships to links which makes Roundup less useful to spammers.</li>
<li><a href="https://owasp.org/www-community/attacks/csrf" rel="nofollow">CSRF</a> protection using <a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#synchronizer-token-pattern" rel="nofollow">synchronizer tokens</a> makes Roundup more secure.</li></ul>

<h2 id="releasing-roundup" id="releasing-roundup">Releasing Roundup</h2>

<p>By 2018, Richard had stepped back, the last release was January 2016. I took over as a lead developer and release engineer. I released version 1.6.0 in July of 2018. This merged several existing patches, cleaning up <a href="https://issues.roundup-tracker.org?ref=dev.to_1" rel="nofollow">Roundup&#39;s issue tracker</a>.</p>

<p>Since 2018, there were two major changes added by other developers:</p>
<ul><li>support for both Python 2 and Python 3 (which required a 2.0.0 release)</li>
<li>addition of a <a href="https://www.roundup-tracker.org/docs/rest.html?ref=dev.to_1" rel="nofollow">REST interface</a> beside the <a href="https://www.roundup-tracker.org/docs/xmlrpc.html?ref=dev.to_1" rel="nofollow">XML-RPC</a> interface</li></ul>

<p>On July 13th, 2022, I made my <a href="https://sourceforge.net/p/roundup/mailman/roundup-users/thread/20210713043457.083CE6A0022%40pe15.cs.umb.edu/" rel="nofollow">fifth annual release</a>.</p>

<p>Since Roundup deployments are on-prem, I established a yearly release cadence. Other issue trackers have more frequent releases. But, many of those releases address UI or workflow issues. In Roundup these are implemented in the (customized) tracker instance. So the user is not dependent on the fixes pushed from upstream. Each release includes <a href="https://www.roundup-tracker.org/docs/upgrading.html?ref=dev.to_1" rel="nofollow">upgrading instructions</a>. These instructions can include changes for the tracker instance to bring it up to date.</p>

<p>The slower release cadence has an advantage for the users. It reduces the need to update tracker instances to once a year rather than every few months. However, the yearly cadence results in less public visibility for Roundup.</p>

<p>I plan more articles on developing and maintaining an open-source application. Some future topics I am considering include (<small>in no particular order</small>):</p>
<ul><li>The Customisability Trap</li>
<li>Kitchen Sink Development</li>
<li>Accessibility</li>
<li>Responsive Design</li>
<li>The Problem with Selects</li>
<li>What?? No Javascript!</li>
<li>Docker All the Things</li>
<li>Colors and Themes</li>
<li>The Bus Factor</li>
<li>Evaluating Tracking Software</li></ul>

<p>The <a href="https://lwn.net/Articles/869118/" rel="nofollow">Roundup 20<sup>th</sup> anniversary article on lwn.net</a> includes more information on Roundup.</p>

<p>I hope you enjoyed this article. Thanks for reading.</p>

<p>tags: <a href="/rouilj/tag:Python" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">Python</span></a>, <a href="/rouilj/tag:WebDev" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">WebDev</span></a>, <a href="/rouilj/tag:opensource" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">opensource</span></a> <a href="/rouilj/tag:RoundupTracker" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">RoundupTracker</span></a> <a href="/rouilj/tag:seriesRoundupTrackerNotesFromAnOpenSourceDeveloper" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">seriesRoundupTrackerNotesFromAnOpenSourceDeveloper</span></a>
<a href="/rouilj/tag:DevDotTo" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">DevDotTo</span></a></p>
]]></content:encoded>
      <guid>https://blog.rouilj.dynamic-dns.net/rouilj/my-journey-to-open-source-development</guid>
      <pubDate>Fri, 29 Jul 2022 05:49:19 +0000</pubDate>
    </item>
  </channel>
</rss>