echo /dev/null > here.php

The battle continues


So, if you’ve read rm -rf, you are aware of the situation I’ve been in. If you haven’t read it yet, please jump over there before continuing. Context is everything here. Anyways, last week was super productive after I had to recover from some death-wheeling, atomic scale stomach bug took me out over the weekend. Coming back after a recovery day and digging back into this codebase was oddly energizing, and really facilitated some big advancements across a lot of different areas of the code. I wanted to talk about bit more about how I’ve moved the HTML table generation from being done server-side to doing it client-side with jQuery instead.


While it’s still not ideal, below is a definite improvement. We’re still having to generate an array with a coupe nested loops, JSON encode that, and echo it out, then turn around and loop over that data again, but we’ve removed a ton of markup from the backend PHP that made the code difficult to reason about; not to mention the sheer violation of SOLID. Leaving off from the last article, here’s the jQuery responsible for making all this happen now.


function fetchPointsAndHours() {
  $.get(LOCAL_BASE_URL + "/fetchPointsAndHours.php?username=" + user)
    .done(function(resp) {
      r = $.parseJSON(resp);
      $(".agent-points").html(r.points);
      $(".tab1-hours-tables").html(generateTimeTables(r));
    })
    .fail(function() {
      console.error("Unable to fetch points and hours information right now.");
    });
}

function generateTimeTables(data) {
  var html = "";
  $.each(data, function(index, d) {
    html += '<span class="" id="msg-' + index + '">' + index + "</span>";
    if (index == "time") {
      $.each(d, function(k, v) {
        date = k;
        html +=
          '<strong>Date: </strong><span id="date-' +
          date +
          index +
          '">' +
          date +
          "</span>" +
          '<table class="table table-striped">' +
          "<thead>" +
          "<tr>" +
          '<th scope="col">Record Type</th>' +
          '<th scope="col">Hours</th>' +
          '<th scope="col">Actions</th>' +
          "</tr>" +
          "</thead>" +
          "<tbody>" +
          "<tr>";
        $.each(v, function(i, j) {
          if (i !== "dayStatus") {
            html +=
              "<td>" +
              '<span id="recordtype-' +
              date +
              index +
              '">' +
              i +
              "</span>" +
              "</td>" +
              "<td>" +
              '<span id="hours-' +
              date +
              index +
              '">' +
              j.hours +
              "</span>" +
              "</td>";
            switch (j.status) {
              case "Confirmed":
                html +=
                  "<td>" +
                  "<img src='" +
                  BASE_URL +
                  "/images/confirmed.png' style='height: 28px; width: 28px;'>" +
                  "</td>" +
                  "<td></td>";
                break;
              case "Refuted":
                html +=
                  "<td>" +
                  "<img src='" +
                  BASE_URL +
                  "/images/refuted.png' style='height: 28px; width: 28px;'>" +
                  "</td>" +
                  "<td></td>";
                break;
              default:
                html +=
                  "<td class='btn-group'>" +
                  "<input type='button' id='confirm-" +
                  date +
                  index +
                  "' class='btn btn-success' value='Confirm' />" +
                  "<input type='button' id='deny-" +
                  date +
                  index +
                  "' class='btn btn-danger' value='Refute' />" +
                  "</td>";
                break;
            }
            html += "</tr>";
          } else if (i === "dayStatus") {
            html += "</tbody></table>";
            html += '<h2 class="day-status';
            if (j === "All hours confirmed") {
              html += ' day-status__confirmed">';
            } else {
              html += ' day-status__refuted">';
            }
            html += j + "</h2>";
            html += "</div>";
          }
        });
      });
    }
  });

  return html;
}


In the next iteration, I’m going to simplify the object that is returned from the API more so that we can eliminate one of the nested loops on the frontend, but for now this is dynamically generating a table for each day, with each row having accurate totals, and controls for the user to manage the data with. A total status is also being presented below the table for each record, so all functionality that was once done with PHP is now done with jQuery and the user knows no difference. It’ll also be easier to maintain going forward!


Given the work above, the associated view now looks like this:

<div
  id="points-hours-tab"
  class="tab-pane fade show active"
  role="tabpanel"
  aria-labelledby="points-hours-tab"
>
  <h2>Your total points are <span class="agent-points"></span></h2>
  <br />
  <div class="tab1-hours-tables"></div>
</div>


Nice and simple now. The only problem is the component-like structure, where a new maintainer will be totally blind to what tab1-hours-tables is and what its content is supposed to be. That’s alright though, because a long-term goal is to scrap the jQuery and implement vue on the front end, so components will be everywhere instead of this current hogwash.


Lessons learned


I’ve definitely gained a lot of patience through this entire exercise. Having to reverse engineer a two decade old codebase that has been mistreated and outright abused by inexperienced or careless developers is no easy feat. Bringing this codebase into SOC2 compliance and ensuring future maintainability, scalability, and reliability is a whole other thing.


As a result of my work on the code, I’ve had the pleasure of experience what it means to update a production server across two major versions of the OS, along with a major and a half version kernel upgrade (and countless dependencies as well). This afforded me the ability to harden and optimize the nginx and php-fpm instances, clean up old cruft, and ensure the environment will serve as a good stepping stone to the eventual migration to Azure App Services in the coming months.


All in all, this has really be a test of my abilities, perseverance, and patience. Anyone who’s been in this position can relate, but I’m grateful for the experience as it’s forcing me to become a better engineer as a consequence.