Sorting Harvest Timesheets and the Importance of UX Research

Large, baroque, analog clock with swirling gold metal bands and roman numerals.

Utilities like time-tracking apps must have the right feature set to succeed. Engaging with real users through UX research is the best way to hear what’s working, what’s missing, and what needs to change. If you’re looking for the walkthrough of how to enable alphabetical sorting for timesheets in Harvest, you can scroll to the Sorting Harvest Timesheets section.

Learning Over Time

We currently use Harvest for time-tracking at Savas. And we use it a lot. We log each task we work on throughout the day as a separate row in a table and update those rows each time we switch tasks. Surprisingly, there’s no way to sort or reorder these rows in either their desktop or web app.

Surprisingly, there’s no way to sort or reorder these rows, which makes it difficult to find and update entries quickly.

Lost Time

The lack of sorting makes it difficult to find and update entries quickly. It’s a handful of seconds wasted each time users interact with the app. But that time adds up quickly.
By the end of the day, those seconds have accumulated into minutes. By the end of the month, hours. Multiply that by the number of people on our team, and that’s a very tangible loss.

User Fatigue

But the bigger challenge is user fatigue. Any friction in a highly-trafficked interface will cause user fatigue over time, which has the potential to make a tool overall less effective. Logging time is a (necessary) pain to begin with. If time tracking software is tedious to use, employees may log time with less frequency or less accurately.

Adaptation and Iteration

I’ve used Harvest for many years. Their tools offer lots of great features for businesses to better plan, track, and utilize time. But in all that time, I haven’t noticed substantial updates to improve the usability of what’s at the heart of their offering—the time tracking interface.

Every company should aspire to make “easy, intuitive [products] that just work.” But doing that requires more than an initial, good idea.
 

Staying Ahead of the Curve

So how do organizations take a good idea and make it better? What separates successful products from those that fail? It’s the ability to focus and iterate on the features users want and need.

User Research

User research is the key to efficiently improving an existing app or website. The best user research combines large scale quantitative data (e.g. analytics, surveys, navigation tests) with smaller-scale qualitative methods, like interviews, focus groups, and moderated testing.

These smaller, face-to-face research methods always yield valuable, actionable anecdotes. For example, Maddy, a project manager here at Savas, said this when asked about her experience with Harvest:
 

I feel like there's an inverse relationship between the UX of a site and the number of times I have to use command + F to navigate it.

Focus groups and user interviews are great at identifying previously unknown issues. And we love to help organizations dig up these insights. For example, check out our A Study on Usability case study, which details our work for Duke’s Talent Identification Program.

(And Harvest, if you’re reading this, we’d love to work with you! Get in touch.)

Sorting Harvest Timesheets

All of this is to say that I was motivated enough to want to address the challenging aspects of the Harvest interface and make my daily experience better. So I wrote my first Chrome browser extension to add sorting functionality to the day-view pages of harvestapp.com.

Challenge and Approach

I knew it would be relatively simple to loop through the time entries on a page and sort them alphabetically by their text content. But the challenge was how to implement the new order without interfering with any of the JavaScript that powers Harvest.

Adding or removing entries from the DOM seemed like a recipe for trouble, and likely to break events bound to those elements. In the end, I decided to use CSS and flexbox’s order property to visually reorder the elements without making drastic changes to the DOM.

I also wanted to add a “sort” button to the interface, to put control in the hands of the user—at least in this first iteration. I plan to gather feedback from our team here at Savas once they’ve been using it for a while, to see what can be improved. It may be more beneficial to have the sort happen automatically, but we’ll see! These kinds of decisions shouldn’t be made in a vacuum, and benefit from user research and input.

The JavaScript

I initially experimented with executing JavaScript on the production site by using the Snippets functionality of Chrome DevTools. The site already included jQuery, so I ran with that, and tried to reuse as much of the site’s existing CSS and utility classes as possible.

I ended up with an approach that basically:

  1. Sorts an array of timesheet entries alphabetically
  2. Stores the new order
  3. Un-sorts the array of timesheet entries
  4. Uses the unsorted array to apply CSS enforcing the new order to the unsorted DOM

This functionality is bound to a click event on a new “Sort” button that is inserted into the DOM. You can check out the code below:

// sorts an array of objects by a key name
const sortBy = (arr, name) => {
  arr.sort(function(a,b) {
    let itemA = a[name];
    let itemB = b[name];
    if (itemA < itemB) return -1;
    if (itemA > itemB) return 1;
    return 0;
  });
};

// sorts the timesheet alphabetically
const sortTimesheet = () => {

  // jquery object of each entry
  let $listitems = $('.js-day-view-entry-list > li');

  // declaring array variables used later
  let items = [];
  let order = [];

  /**
   * for each time entry, add an object to the items array
   * index: initial index of the entry
   * task: normalized string of the project and task names
   * order: placeholder property that will be used to store the sorted order
   */
  $listitems.each(function(i) {
    let project = $(this).find('.project-client').text();
    let task = $(this).find('.task-notes').text();
    let combined = (project + task).replace(/\W/g, '').toUpperCase();
    items.push({
      index: i,
      task: combined,
      order: 0
    });
  });

  // sort the items array alphabetically by the task name
  sortBy(items, 'task');

  // store the current order of the array in the order property
  items.forEach(function(v, i) {
    items[i].order = i;
  });

  // sort the items array by the index property to restore the original order
  sortBy(items, 'index');

  // simplify the array into the order values
  order = items.map(item => item.order);

  // order the entries alphabetically
  $listitems.each(function(i) {
    $(this).attr('style',`order: ${order[i]};`);
  });

};

// sort button markup
const sortButton = '<div class="sort-timesheet-wrapper"><button type="button" class="sort-timesheet js-sort-timesheet"><div class="sort-timesheet-button hui-button hui-button-secondary w-100">Sort</div></button></div>';

// append sort button
$('.js-timesheet-view').prepend(sortButton);

// bind click event
$('.js-sort-timesheet').on('click', function() {
  sortTimesheet();
});

Creating the Chrome Extension

For this custom JavaScript to run automatically when loading the Harvest timesheet page, it has to be bundled as a Chrome extension.

Contrary to the first impression one might get when browsing the Chrome extension Developer Guide, it’s relatively simple to create an extension that just runs external JavaScript. All you need are the files you want to insert, and a manifest file in JSON format.

The simple way to insert scripts only on specific webpages is to use declarative injection, which allows you to specify match/exclusion patterns for URLs.

What would you do?

How would you have approached this problem differently? What would you improve or change about the code above? What other interfaces would you want to change if you could? Let us know by tweeting at @SavasLabs.