Blog

Using the Harvest API to Build Custom Reports

Written by Metal Toad Staff | Feb 28, 2012 12:00:00 AM
Filed under:

We use Harvest for time-tracking, which is simple and easy to use. For most users the tools on the website will be enough, but if you need to create a custom report or want to share data between Harvest and another application, you can use the Harvest API. I'm primarily a PHP guy, so using the Harvest PHP library makes it even easier. This post will demonstrate how to use this to create a custom reporting tool.

In our case, we wanted to see how many hours a given user spent on weekends over a given time period.

<?php
/**
 * @file
 * This calculates the number of hours logged on weekends during a given period.
 */
 
/**
 * Set your credentials here. You must be an admin on the Harvest account for
 * this to work properly. You may want to store this in a separate file and include
 * that file here.
 *   include('settings.php');
 */
$harvest_user = ''; // Your Harvest username, usually an email address
$harvest_pass = ''; // Your Harvest password
$harvest_account = ''; // The {myaccount} portion of your Harvest url: {myaccount}.harvestapp.com
// date_default_timezone_set('America/Los_Angeles'); // Set your timezone if it is not set in your php.ini
 
// Include the HarvestAPI class
require_once 'HarvestAPI.php';
// Register the HarvestAPI autoloader
spl_autoload_register(array('HarvestAPI', 'autoload'));
 
$harvestAPI = new HarvestAPI();
$harvestAPI->setUser($harvest_user);
$harvestAPI->setPassword($harvest_pass);
$harvestAPI->setAccount($harvest_account);

That sets up our object, now we can make calls against it like so:

<?php
$result = $harvestAPI->getUsers();
// If Harvest returns successful
if ($result->code == 200) {
  $people = $result->data;
}
?>

Since we only wanted entries that were on a weekend, I made a convenience function to help keep the resulting html a little more tidy.

/**
 * @param int from
 *   A date as recongized by strtotime for report start date.
 * @param int to
 *   A date as recongized by strtotime for report end date.
 * @param int user_id
 *   The Harvest User's id.
 * @param object
 *   The Harvest API object, with user already logged in.
 * @return array
 *   The entries that were on a weekend during the time period.
 */
function getWeekendEntriesForPeriod($from, $to, $user_id, $harvestAPI) {
  $range = new Harvest_Range(date('Ymd', strtotime($from)), date('Ymd', strtotime($to)));
 
  $activity = $harvestAPI->getUserEntries($user_id, $range);
 
  $entries = array();
  $hours_per_week = array();
 
  foreach ($activity->data as $entry) {
    // We also want to calculate the total hours spent during that week
    $week_year = date('W-o', strtotime($entry->spent_at)); // Uses ISO-8601, weeks are Monday - Sunday
 
    if (!isset($hours_per_week[$week_year])) {
      $hours_per_week[$week_year] = 0;
    }
    $hours_per_week[$week_year] += $entry->hours;
 
    // If the entry is during a weekend
    $day_of_week = date('w', strtotime($entry->spent_at));
    // 0 = Sunday, 6 = Saturday
    if ($day_of_week == 0 || $day_of_week == 6) {
      $entries[] = $entry;
    }
  }
 
  // Loop through all the entries we are going to return and set our calculated hours_per_week
  foreach ($entries as $key => $entry) {
    $week_year = date('W-o', strtotime($entry->spent_at));
    $entries[$key]->hours_per_week = $hours_per_week[$week_year];
  }
 
  return $entries;
}

Finally, I built a simple persistent GET form to make a simple interface for our report. I'm also using the HTML5 date input type with a handy jQuery popup calendar.

<html>
<head>
<title>Weekend Hours Calculator</title>
<script type="text/javascript" language="javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="https://cdn.jquerytools.org/1.2.6/form/jquery.tools.min.js"></script>
<script type="text/javascript" language="javascript">
$(document).ready(function() {
  $(":date").dateinput();
});
</script>
<link type="text/css" rel="stylesheet" href="styles.css" />
</head>
<body>
<?php
// Display form for report
$from = isset($_GET['from']) ? $_GET['from'] : date('Y-m-d', time() - 86400);
$to = isset($_GET['to']) ? $_GET['to'] : date('Y-m-d');
 
$options = array();
foreach ($people as $person) {
  // Only show active people
  if ($person->is_active == 'true') {
    $option_html = '<option value="' . $person->id . '"';
    if (isset($_GET['user_id']) && $_GET['user_id'] == $person->id) {
      $option_html .= ' selected="selected"';
    }
    $option_html .= '>' . $person->first_name . ' ' . $person->last_name . '</option>';
    $options[] = $option_html;
  }
} ?>
<form name="report" method="GET">
<label>Search:</label> <select name="user_id">
<?php implode("\n", $options); ?>
</select> <input type="date" name="from" value="<?php echo $from; ?>" /> <input type="date" name="to" value="<?php echo $to; ?>" /> <input type="submit" value="Get Report" />
</form>
<?php
// If we are running a report
if (isset($_GET['user_id']) && isset($_GET['from']) && isset($_GET['to'])) {
  $entries = getWeekendEntriesForPeriod($_GET['from'], $_GET['to'], $_GET['user_id'], $harvestAPI);
  if (count($entries)) {
    echo '<table><tr><th>Hours</th><th>Date</th><th>Weekly Hours</th><th>Details</th>';
    $hours = 0;
 
    foreach ($entries as $entry) {
      echo '<tr><td class="right">' . $entry->hours . '</td><td>' . date('l, M j, Y', strtotime($entry->spent_at)) . '</td><td>' . $entry->hours_per_week . '</td><td>' . $entry->notes . '</td></tr>';
      $hours += $entry->hours;
    }
    echo '<tr><th class="right">' . $hours .'</th><th class="left" colspan="3">Total Weekend Hours</th></tr></table>';
  }
  //
  else {
    echo '<div class="noresults">No hours reported on a weekend for this user during the time period.</div>';
  }
}
?>
</body>
</html>

And the end result is a simple but informative table: