<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ante Sepic</title>
    <description>The latest articles on DEV Community by Ante Sepic (@originalexe).</description>
    <link>https://dev.to/originalexe</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F231307%2F97660f08-d1e0-4177-ae3a-bc11430069c4.jpeg</url>
      <title>DEV Community: Ante Sepic</title>
      <link>https://dev.to/originalexe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/originalexe"/>
    <language>en</language>
    <item>
      <title>Can you please refresh (Or how we version our Single-Page Application)</title>
      <dc:creator>Ante Sepic</dc:creator>
      <pubDate>Tue, 03 Dec 2019 20:32:01 +0000</pubDate>
      <link>https://dev.to/originalexe/can-you-please-refresh-or-how-we-version-our-single-page-application-335l</link>
      <guid>https://dev.to/originalexe/can-you-please-refresh-or-how-we-version-our-single-page-application-335l</guid>
      <description>&lt;p&gt;In this article, I outline our approach to solving the problem of people not "getting" the latest version of our SPA. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A single-page application (SPA) is a web application or web site that interacts with the user by dynamically rewriting the current page rather than loading entire new pages from a server.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At work, I am responsible for the development of our client-facing SPA. We use Vue.js as our frontend framework of choice, but the problem I will describe in this article is framework agnostic.&lt;/p&gt;

&lt;p&gt;Upon making changes and merging them to the &lt;code&gt;master&lt;/code&gt; branch on GitHub, Travis (not a real person) runs our deployment process which includes building the app via Vue CLI and then uploading the new build to Amazon S3. So far so good, right?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F4c3s9sm2qg5enxvrts07.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F4c3s9sm2qg5enxvrts07.gif" alt="What could go wrong"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The fundamental advantage of SPAs (people not having to load the whole HTML on route change) is also what was creating a problem. &lt;strong&gt;If people could technically never re-request the app fresh from your server, how do you deliver the latest version to them?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  New version detection
&lt;/h2&gt;

&lt;p&gt;The way we implemented a new version detection is pretty simple: We periodically do a fetch of the main &lt;code&gt;index.html&lt;/code&gt; file and compare it to the old version. If there is a difference, it means that a new version got deployed. The good thing is that we don't have to do any versioning manually. &lt;code&gt;index.html&lt;/code&gt; is guaranteed to be different on each build because Webpack generates a unique hash for each file during the build process, and hash is part of the file name embedded into the HTML. Since we only care whether the version is different or not (there is no concept of higher/lower version), this is enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Letting them know
&lt;/h2&gt;

&lt;p&gt;We knew that we somehow want to let the people know that there was a newer version available. As for how we "deliver the message", there were three versions that came to our mind:&lt;/p&gt;

&lt;p&gt;1.) Automatically refresh&lt;br&gt;
This was discarded immediately because it could interrupt and/or confuse users. Imagine that you are filling out some form and a website refreshes for no reason, losing your progress.&lt;/p&gt;

&lt;p&gt;2.) Intercept route change and reload from the server&lt;br&gt;
Users would not be disturbed by this one since they are navigating to another page anyway. The only reason we did not go for this one is that it would break some flows where we rely on information being preserved in the Vuex store in between route navigations.&lt;/p&gt;

&lt;p&gt;3.) Showing notification&lt;br&gt;
In the end, we decided to go for showing an in-app notification, with a link that would trigger a refresh. That way our users can finish what they were doing and update the app when they are ready.&lt;/p&gt;
&lt;h2&gt;
  
  
  Implementation details
&lt;/h2&gt;

&lt;p&gt;To periodically check for a new version, we decided to use AJAX polling since it will require no additional tooling, and other technologies like web sockets would be an overkill. The interval for the check is 10 minutes. A naive approach would be using a simple setInterval and firing a network request every 10 minutes. The drawback of that approach is that network requests are not free. Not in terms of bandwidth (HTML file is really small), but in terms of battery. You can read more about it &lt;a href="https://en.wikipedia.org/wiki/Fast_Dormancy" rel="noopener noreferrer"&gt;here&lt;/a&gt;. The gist is: if a person is not using the network for some time on their device, the in-device modem will go into the low-power mode. Getting it back to the "normal" state takes some energy. If we just fire network requests every 10 minutes, we run the risk of draining our users' battery more than we need to.&lt;/p&gt;

&lt;p&gt;Solution: &lt;strong&gt;Activity Based Timer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is the full code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ActivityBasedTimer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;globalTimerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maybeExecuteTimerCallback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;timerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;forced&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;timers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timerId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;forcedInterval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;forcedIntervalId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;lastExecution&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;intervalToCheckFor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;forced&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;forcedInterval&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;lastExecution&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;intervalToCheckFor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newTimer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;lastExecution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;forcedIntervalId&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;forcedIntervalId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;newTimer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forcedIntervalId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;maybeExecuteTimerCallback&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;timerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;forced&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;forcedInterval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;timers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newTimer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;forced&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timerId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;setInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;forcedInterval&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;globalTimerId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;lastExecution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;forcedInterval&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forcedInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;forcedInterval&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forcedIntervalId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;maybeExecuteTimerCallback&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;timerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;forced&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;forcedInterval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;timers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;globalTimerId&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;timerId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clearInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timerId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;timers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timerId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;forcedIntervalId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;forcedIntervalId&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;forcedIntervalId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;timers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timerId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runTimersCheck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;timers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_timer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timerId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;maybeExecuteTimerCallback&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;timerId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;runTimersCheck&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;ActivityBasedTimer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The timer exposes an interface for running the code in an interval (just like &lt;code&gt;setInterval&lt;/code&gt; does), but with no guarantee that the code will actually execute at that interval. It instead also exposes a function one should call to check for all timers and execute them as necessary. It basically loops through all the intervals, checks when they were last executed, and if more time than what is defined as an interval time has passed, it executes the callback. There is an additional, third parameter, in the &lt;code&gt;setInterval&lt;/code&gt; implementation that takes a "forced" interval. This interval uses a native &lt;code&gt;setInterval&lt;/code&gt; function so it more or less provides a guarantee of running every x milliseconds.&lt;/p&gt;

&lt;p&gt;We then used this interval to check for updates periodically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ActivityBasedTimer&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/services/activityBasedTimer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;versioningTimer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ActivityBasedTimer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;versioningTimer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newVersionAvailable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;isNewerVersionAvailable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;newVersionAvailable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;setNewVersionAvailable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// Normal interval is once every 10 minutes&lt;/span&gt;
  &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Forced interval is once per day&lt;/span&gt;
  &lt;span class="na"&gt;forcedInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember the function you need to call to check for the timers? We use that one in the router:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;versioningTimer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runTimersCheck&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We bound it to the router route change because that's a sign of people actively using the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;When we deploy a new version, after a few minutes, those people that did not close the tab in the meantime will get a notification telling them to click to update (which again is just a basic &lt;code&gt;window.location.reload(true)&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you approach this problem in your SPAs? I would like to hear from you. Feedback on our solution is also more than welcome.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Finally, if you want a chance to solve cool challenges like these, &lt;a href="https://grnh.se/65174d7f2" rel="noopener noreferrer"&gt;Homeday is hiring&lt;/a&gt;. Come join us in Berlin!&lt;/p&gt;

</description>
      <category>spa</category>
      <category>vue</category>
      <category>javascript</category>
      <category>ux</category>
    </item>
    <item>
      <title>Building my first desktop app: Electron initial impressions</title>
      <dc:creator>Ante Sepic</dc:creator>
      <pubDate>Sat, 16 Nov 2019 08:20:55 +0000</pubDate>
      <link>https://dev.to/originalexe/building-my-first-desktop-app-electron-initial-impressions-4cpi</link>
      <guid>https://dev.to/originalexe/building-my-first-desktop-app-electron-initial-impressions-4cpi</guid>
      <description>&lt;p&gt;TL;DR: I am building a desktop app with Electron. The app allows anyone to create widgets for their desktop with HTML, CSS and, optionally, JavaScript. Check it out &lt;a href="https://tryglitter.com" rel="noopener noreferrer"&gt;here&lt;/a&gt;. In this article, I am sharing my first impressions, but also some struggles I had to overcome while working with Electron for the first time.&lt;/p&gt;

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




&lt;p&gt;I have been using Ubuntu for most of my development career. What bothered me a lot was the lack of ways I could customize my desktop. On macOS, there exists Übersicht. On Windows, Rainmeter is very popular. Yet on Linux, I am stuck with pre-made widgets from various desktop managers that often look outdated. Even if you are lucky and use macOS/Windows, I found the experience with popular tools is not very user-friendly. I want to be able to write some HTML and CSS that I know how to use and create something beautiful on my desktop, without the steep learning curve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Be the change you wish to see in the world
&lt;/h2&gt;

&lt;p&gt;I decided to do something about this, and I think you'll like it. I am building &lt;a href="https://tryglitter.com" rel="noopener noreferrer"&gt;Glitter&lt;/a&gt;, a tool for almost any OS that allows you to create (or use others') widgets with the technologies you already know - HTML, CSS, and JavaScript. Glitter widgets will be hosted and reviewed on the Glitter platform, making it easy and secure to distribute all kinds of widgets with the world.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Electron
&lt;/h2&gt;

&lt;p&gt;I decided to use Electron for building the app. It's popular to hate on Electron in some circles because of the size of the final application (it includes both Node.js and Chromium runtimes) and memory usage. I think it's an invaluable tool that even further expands the reach of web technologies. It also empowers people like me, who are not versed in "desktop" languages, to create something for the platform. Popular apps powered by Electron include Slack, Discord, VS Code, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  First Electron impressions
&lt;/h2&gt;

&lt;p&gt;Getting started with Electron was fairly straightforward. I decided to make use of &lt;a href="https://www.electronforge.io" rel="noopener noreferrer"&gt;Electron Forge&lt;/a&gt;. They offer a CLI to kick-start the development and also provide handy tools for packaging your application. One other cool thing is the support for Webpack out of the box. If you are going to try it out, I suggest also joining the official Atom slack channel, which includes the #electron channel. The author of Electron Forge hangs out there (but also many other helpful people).&lt;/p&gt;

&lt;p&gt;In Electron, the entry point for your application is the main process (basically a Node.js script). From there, you create as many renderer processes (browser window instances) as you need. This was immediately clear to me and it made sense. It also allows for a standard separation of concerns I am used to, where I have a backend (in this case the main process), and a frontend (renderer). I am using React for powering the UI, but Glitter widgets will come with a widget generator that supports Vue.js and React (more after the MVP).&lt;/p&gt;

&lt;p&gt;Electron is being updated very often. The team is working hard and keeps introducing very handy features. For example, in version 7.x they introduced a much faster IPC method (basically a way for you to pass stuff between the main process and the browser window instances - it was already supported, but it had some issues).&lt;/p&gt;

&lt;h2&gt;
  
  
  It's not all roses.
&lt;/h2&gt;

&lt;p&gt;Even though Electron is fairly well documented, I have run into a lot of things that simply don't work as expected.&lt;/p&gt;

&lt;p&gt;For example, for my use case, I needed to spawn transparent, borderless windows for each widget. Doing so on Ubuntu by simply following the documentation and passing options to the &lt;code&gt;BrowserWindow&lt;/code&gt; instance did not work at all. I instead had to set an arbitrary timeout of 500ms after the start of the application, and only then the option worked.&lt;br&gt;
For some other options, setting them in the constructor never works, however calling the setter for that option after instantiating window, for some weird reason works&lt;/p&gt;

&lt;h2&gt;
  
  
  I am impressed with what's possible
&lt;/h2&gt;

&lt;p&gt;I really enjoyed my time with Electron so far. I am super thankful for the team maintaining it and I wish only the best for the platform. It brought us many nice apps and will hopefully continue to do so in the future.&lt;/p&gt;

&lt;p&gt;If you have any questions about development with Electron, feel free to drop a comment. I plan on writing about using TypeScript for Electron development, as well as how I decided to structure my code.&lt;/p&gt;




&lt;p&gt;Also, don't forget to check out &lt;a href="https://tryglitter.com" rel="noopener noreferrer"&gt;the app I am building&lt;/a&gt;. I welcome any feedback on that as well, of course. &lt;/p&gt;

</description>
      <category>electron</category>
      <category>javascript</category>
      <category>react</category>
    </item>
    <item>
      <title>Gaining insights into our GitHub repositories</title>
      <dc:creator>Ante Sepic</dc:creator>
      <pubDate>Mon, 23 Sep 2019 15:15:11 +0000</pubDate>
      <link>https://dev.to/originalexe/gaining-insights-into-our-github-repositories-3ee</link>
      <guid>https://dev.to/originalexe/gaining-insights-into-our-github-repositories-3ee</guid>
      <description>&lt;p&gt;&lt;em&gt;On how we learned about ourselves and had fun in the process&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Earlier this year, we have set an interesting key result (as part of our OKRs) - Average waiting time for two pull request reviews should be under 48 hours. The benefits of timely pull request reviews were clear to us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  not blocking your teammates (or yourself, if you depend on a feature inside the pull request)&lt;/li&gt;
&lt;li&gt;  checking the code while its context is still fresh&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We felt that 48 hours was a good balance between taking time to properly review the code and moving swiftly.&lt;/p&gt;

&lt;p&gt;What wasn't clear for us was how we will measure it. We also did not know our current "timing", but it felt like in some cases, it was taking longer than ideal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Action plan
&lt;/h3&gt;

&lt;p&gt;So we knew what we wanted, now was the time to figure out how to get it. After spending some time DuckDuckGo-ing, we were not able to find a tool that could help us. Luckily for us, we know how to code!&lt;/p&gt;

&lt;p&gt;Thanks to the availability of a GraphQL GitHub API, fetching the necessary data was fairly straightforward. Afterward, it was just a matter of doing some calculations and we had our first stats. I built the tool in JavaScript (Node.js), and the first version had to be run via command line.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fql40xeczgtdedt9tma0w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fql40xeczgtdedt9tma0w.png" alt="GitHub Stats displayed in a terminal"&gt;&lt;/a&gt;&lt;/p&gt;
First version displayed the results directly in the terminal



&lt;p&gt;This was very exciting, we could finally measure the times it takes us to review pull requests, among other things. But the output was not super friendly. I decided to give posting to Slack a chance. They make the process easy, you post a JSON to the webhook and it auto-posts to any channel for you. We now had the stats in Slack.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fmsg94r081zw3nc459cmd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fmsg94r081zw3nc459cmd.png" alt="GitHub Stats displayed in a Slack channel"&gt;&lt;/a&gt;&lt;/p&gt;
Displaying the results in a Slack channel



&lt;p&gt;Slack message looked much better than the terminal output we had previously, but it was taking quite a bit of space. Once we ran the analysis for multiple repositories, it felt kinda spam-y.&lt;/p&gt;

&lt;p&gt;We needed a better solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  If only we knew some frontend people
&lt;/h3&gt;

&lt;p&gt;Oh, wait.&lt;/p&gt;

&lt;p&gt;Here at Homeday, every quarter we have something called Product Engineering Week (PEW for short). It's a time where we have no meetings and each member of the product team gets to work on projects that we would not tackle in normal sprints. I decided to work on a UI that would communicate with the backend script we already had and &lt;em&gt;beautifully&lt;/em&gt; display the results.&lt;/p&gt;

&lt;p&gt;I did decide on some rules (which later proved challenging):&lt;/p&gt;

&lt;p&gt;For the backend, I wanted to use AWS Lambdas (I chose AWS since we already use AWS ecosystem for our whole infrastructure, it just made sense.)&lt;/p&gt;

&lt;p&gt;Instead of this being an app only for Homeday (with perhaps a predefined list of repositories and a GitHub token stored somewhere on the backend,) I decided to integrate GitHub authentication so that anyone with an account could use the tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS Lambdas
&lt;/h3&gt;

&lt;p&gt;I already had some previous adventures in the serverless world, but by no means would I call myself experienced with the technology. I chose &lt;a href="https://serverless.com/" rel="noopener noreferrer"&gt;Serverless&lt;/a&gt; because it makes it much easier to get started and develop your Lambdas (and serverless functions from many other providers), with a fairly quick feedback loop between making changes locally and them being reflected on Amazon servers.&lt;/p&gt;

&lt;p&gt;It took some time to get it right, but the first version went quite smoothly. I migrated the previous Node.js code into a Lambda and through Serverless config file connected it to the API gateway so I could simply send some GET requests and get the results back.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend
&lt;/h3&gt;

&lt;p&gt;Now that the backend was ready, it was time to do what I wanted in the first place - provide a nice interface for interacting with the analysis tool. I had limited time so I chose &lt;a href="https://vuetifyjs.com/en/" rel="noopener noreferrer"&gt;Vuetify&lt;/a&gt; as a base for my interface. We have very recently open-sourced our own components library "&lt;a href="https://blocks.homeday.dev" rel="noopener noreferrer"&gt;Homeday Blocks&lt;/a&gt;", but it's still missing some layout components that I wanted in my interface. It was also a nice opportunity to get to know the highly praised Vuetify and learn from it for our library.&lt;/p&gt;

&lt;p&gt;There are three parts to the flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Authenticating with a GitHub account (for this I used &lt;a href="https://auth0.com/" rel="noopener noreferrer"&gt;Auth0&lt;/a&gt; which made it fairly painless)&lt;/li&gt;
&lt;li&gt;  Providing the list of repositories that can be analyzed and also getting the date range&lt;/li&gt;
&lt;li&gt;  Displaying the results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are some images showing what it looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fv8dwmh16frovzac64r78.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fv8dwmh16frovzac64r78.png" alt="Login screen for the app"&gt;&lt;/a&gt;&lt;/p&gt;
Login screen



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F9y6y03ghf1abpi2m0zf3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F9y6y03ghf1abpi2m0zf3.png" alt="A welcome screen shown after authenticating"&gt;&lt;/a&gt;&lt;/p&gt;
Welcome screen



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fewq55brmuhd2v06vgysu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fewq55brmuhd2v06vgysu.png" alt="Repositories selected inside a modal"&gt;&lt;/a&gt;&lt;/p&gt;
Selecting repositories



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fjopki7y5jbvvnp2mei4n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fjopki7y5jbvvnp2mei4n.png" alt="Date range selection with often-used ranges available for a quick selection"&gt;&lt;/a&gt;&lt;/p&gt;
Selecting the date range



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fqzr7ynrc0coke8z7y2th.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fqzr7ynrc0coke8z7y2th.png" alt="Analysis results displayed in columns side-by-side"&gt;&lt;/a&gt;&lt;/p&gt;
Displaying the results



&lt;p&gt;I added some sugar on top like adding buttons for a quick selection of most often used date ranges and &lt;a href="https://vue-kawaii.now.sh/" rel="noopener noreferrer"&gt;Vue Kawaii&lt;/a&gt; to make the app a bit more friendly. Everything was fine and dandy in my universe, but then everything changed...&lt;/p&gt;

&lt;h3&gt;
  
  
  The Murphy awakens
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fndcaaevutftwzhuaheie.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fndcaaevutftwzhuaheie.png" alt="The fire nation attack - A scene from a popular animated series "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Luckily, no fire nation attacked me. It was AWS! The issue happened when I tried to run the analysis tool on one of our more active repositories. It kept timing out!&lt;/p&gt;

&lt;p&gt;Uh oh...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I'll simply increase the Lambda timeout" - an innocent child&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It made no difference, my Lambda was still timing out after exactly 29 seconds. Three and a half DuckDuckGo-s later, and I found out that it's a hard limit imposed by AWS for any Lambda triggered by the API gateway.  Sh*t.&lt;/p&gt;

&lt;h3&gt;
  
  
  Return of the Jedi
&lt;/h3&gt;

&lt;p&gt;It did not help that it was already Thursday and our PEW finishes on Friday. I had about 6 more work hours left before our "demo ceremony" and had to come up with a solution. Of course, I could have just picked some simpler repositories for the demo, and that was indeed my plan B. Luckily, I never had to use it.&lt;/p&gt;

&lt;p&gt;I decided to go with what I dubbed "The two Lambdas approach (Temporary Name)" ®.&lt;/p&gt;

&lt;p&gt;I implemented one Lambda that took a request, generated a unique identifier and replied with it. It also kicked off the second Lambda (and thus avoiding the hard limit) which did the calculation and stored it to an S3 instance as a JSON file (using the unique identifier we sent to the frontend as its name).&lt;/p&gt;

&lt;p&gt;On the frontend, after receiving the response, the code polls every 5 seconds for the expected file on S3, and once it finds it, displays the results.&lt;/p&gt;

&lt;p&gt;It's a hacky/dirty/whatever approach but it works, for the scale we expect, it should not crash AWS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;The app is live and kicking. You can try the hosted version here: &lt;a href="https://github-stats.homeday.dev/" rel="noopener noreferrer"&gt;Homeday GitHub Stats&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also find the code (and contribute to it) at the following links:&lt;br&gt;&lt;br&gt;
Backend: &lt;a href="https://github.com/homeday-de/homeday-github-stats-backend" rel="noopener noreferrer"&gt;https://github.com/homeday-de/homeday-github-stats-backend&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Frontend: &lt;a href="https://github.com/homeday-de/homeday-github-stats-frontend" rel="noopener noreferrer"&gt;https://github.com/homeday-de/homeday-github-stats-frontend&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All feedback is welcome (especially Lambda shaming).&lt;/p&gt;

&lt;p&gt;Finally, if you want a chance to work on cool projects like these, &lt;a href="https://grnh.se/65174d7f2" rel="noopener noreferrer"&gt;Homeday is hiring&lt;/a&gt;. Come to Berlin and take part in our next PEW!&lt;/p&gt;

</description>
      <category>github</category>
      <category>aws</category>
      <category>javascript</category>
      <category>graphql</category>
    </item>
  </channel>
</rss>
