Linking Child and Parent Cards on Trello – Part 2

Last week I published a bit of code that uses the Trello API to keep parent and child cards synced across a set of boards.  It was a little piece of research that has absolutely taken off around the office so I’ve been expanding on it and demoing it and talking about it and generally losing my mind.

The thing I expanded on most is a flaw that appeared in my original script whereby a user could create a child card outside of the normal workflow and it would never be linked to the parent card.  Obviously “outside of the normal workflow” means it’s already an edge case but that doesn’t mean it’s as uncommon as we’d like, so I came up with a way to handle it.  It does rely on the child card being tagged with the same card tag as the parent but it’s better than nothing.

#!/usr/bin/php -q
<?php
  $trello = new trello_api($GLOBALS['config']['key'], $GLOBALS['config']['secret'], $GLOBALS['config']['token']);

  // LOOP THROUGH EACH CARD ON THE WIP BOARD TO GET THE PARENT CARD TAGS
  $parent = array();

  $card_data = $trello->request('GET', ('/1/boards/' . $GLOBALS['config']['board']['wip_prototype']['id'] . '/cards'));
  foreach ($card_data AS $card) {
    $card_tag = '';
    if (preg_match('|[(.*)](.*)|is', $card->name, $matches)) {
      $card_tag = $matches[1];
    }

    if ($card_tag) {
      $parent[$card_tag] = array('id' => $card->id, 'url' => $card->shortUrl, 'label' => $card->labels[0]->color);
    }
  }

  // LOOP THROUGH EACH CHILD BOARD
  foreach ($GLOBALS['config']['child_boards'] AS $board_name) {
    // LOOP THROUGH ALL THE CARDS ON THE BOARD
    $card_data = $trello->request('GET', ('/1/boards/' . $GLOBALS['config']['board'][$board_name]['id'] . '/cards'));
    foreach ($card_data AS $card) {
      if (!preg_match('|Parent Card: https://trello.com/c/(.{8})|is', $card->desc)) {
        // NO PARENT CARD LINKED
        $card_tag = '';
        if (preg_match('|[(.*)](.*)|is', $card->name, $matches)) {
          $card_tag = $matches[1];
        }

        if ($card_tag) {
          // WE'VE GOT A TAG, LET'S USE IT
          if (is_array($parent[$card_tag])) {
            // WE KNOW THE PARENT THIS BELONGS TO

            // UPDATE THE CHILD CARD
            $trello->request('PUT', ('/1/cards/' . $card->id), array('desc' => (trim('Parent Card: ' . $parent[$card_tag]['url'] . ' ' . $card->desc))));

            // UPDATE THE PARENT CARD LABEL IF NECESSARY
            if (!$parent[$card_tag]['label']) {
              $trello->request('PUT', ('/1/cards/' . $parent[$card_tag]['id'] . '/labels'), array('value' => $GLOBALS['config']['label'][$board_name]));
            }

            // GET THE SLICES CHECKLIST
            $checklist_id = 0;
            $checklist_data = $trello->request('GET', ('/1/cards/' . $parent[$card_tag]['id'] . '/checklists'));
            foreach ($checklist_data AS $checklist) {
              if ($checklist->name == 'Slices') {
                $checklist_id = $checklist->id;
              }
            }

            if (!$checklist_id) {
              // NO SLICES CHECKLIST FOUND, MAKE ONE
              $new_checklist = $trello->request('POST', ('/1/cards/' . $parent[$card_tag]['id'] . '/checklists'), array('name' => 'Slices'));
              $checklist_id = $new_checklist->id;
            }

            // ADD THE CHECKLIST ITEM TO THE SLICES CHECKLIST
            $trello->request('POST', ('/1/cards/' . $parent[$card_tag]['id'] . '/checklist/' . $checklist_id . '/checkItem'), array('name' => $card->shortUrl));
          }
        }
      }
    }
  }

  unset($trello);
?>

As with my previous post, this uses my Trello API wrapper class and pulls in the $GLOBALS[‘config’] array of configuration values from another file.  Also as with my previous post I think it’s commented pretty well but we’re going through the code piece-by-piece anyway.

// LOOP THROUGH EACH CARD ON THE WIP BOARD TO GET THE PARENT CARD TAGS
$parent = array();

$card_data = $trello->request('GET', ('/1/boards/' . $GLOBALS['config']['board']['wip_prototype']['id'] . '/cards'));
foreach ($card_data AS $card) {
  $card_tag = '';
  if (preg_match('|[(.*)](.*)|is', $card->name, $matches)) {
    $card_tag = $matches[1];
  }

  if ($card_tag) {
    $parent[$card_tag] = array('id' => $card->id, 'url' => $card->shortUrl, 'label' => $card->labels[0]->color);
  }
}

We loop through all of the cards on our Work in Progress (“WIP”) board and use a regular expression to see if they have a card tag as a prefix (appearing in the pattern of “[TEST4] Test Project 4”). If the card does, we save an array of data about the parent to an array of parent cards for reference by card tag later.

// LOOP THROUGH EACH CHILD BOARD
foreach ($GLOBALS['config']['child_boards'] AS $board_name) {
  // LOOP THROUGH ALL THE CARDS ON THE BOARD
  $card_data = $trello->request('GET', ('/1/boards/' . $GLOBALS['config']['board'][$board_name]['id'] . '/cards'));
  foreach ($card_data AS $card) {
    if (!preg_match('|Parent Card: https://trello.com/c/(.{8})|is', $card->desc)) {

Then we loop through our list of child board names and get every card on that board using a GET request to /1/board/xxxxxx/cards (where xxxxxx is the board ID).  If the card’s description doesn’t match our convention for linking back to a parent, we know we’ve found a rogue card.

$card_tag = '';
if (preg_match('|[(.*)](.*)|is', $card->name, $matches)) {
  $card_tag = $matches[1];
}

if ($card_tag) {
  // WE'VE GOT A TAG, LET'S USE IT
  if (is_array($parent[$card_tag])) {
    // WE KNOW THE PARENT THIS BELONGS TO

We check to see if the card has a tag in its name, using the same regular expression as we did earlier.  If it does, we can use it to move forward. If we know the parent that tag belongs to, we can do even more.

// UPDATE THE CHILD CARD
$trello->request('PUT', ('/1/cards/' . $card->id), array('desc' => (trim('Parent Card: ' . $parent[$card_tag]['url'] . ' ' . $card->desc))));

The first thing we do is fire off a PUT request to /1/cards/yyyyyy (where yyyyyy is the ID of the rogue card) with desc set to the current description with our parent link prepended to it.  This gives our child card the necessary link to the parent.

// UPDATE THE PARENT CARD LABEL IF NECESSARY
if (!$parent[$card_tag]['label']) {
  $trello->request('PUT', ('/1/cards/' . $parent[$card_tag]['id'] . '/labels'), array('value' => $GLOBALS['config']['label'][$board_name]));
}

On the off chance that the parent card doesn’t have a label, we use the fact that we already know what board the child card is on to set one.  That involves a PUT request to /1/cards/zzzzzz/labels (where zzzzzz is the ID of the parent card) with value set to the color name of the label that corresponds to the board.

// GET THE SLICES CHECKLIST
$checklist_id = 0;
$checklist_data = $trello->request('GET', ('/1/cards/' . $parent[$card_tag]['id'] . '/checklists'));
foreach ($checklist_data AS $checklist) {
  if ($checklist->name == 'Slices') {
    $checklist_id = $checklist->id;
  }
}

Then we get ID of the parent card’s “Slices” checklist, as that’s where the parent card links to each of it’s children.  We make a GET request to /1/cards/zzzzzz/checklists and loop through each one until we find the one with the right name, then save that ID off for later.

if (!$checklist_id) {
  // NO SLICES CHECKLIST FOUND, MAKE ONE
  $new_checklist = $trello->request('POST', ('/1/cards/' . $parent[$card_tag]['id'] . '/checklists'), array('name' => 'Slices'));
  $checklist_id = $new_checklist->id;
}

What if we didn’t get a checklist ID?  Then we make one.  We fire off a POST request to /1/cards/zzzzzz/checklists with name set to “Slices” and that gives us back a bunch of data about a newly-created checklist.  We save off the new checklist ID so we can move forward.

// ADD THE CHECKLIST ITEM TO THE SLICES CHECKLIST
$trello->request('POST', ('/1/cards/' . $parent[$card_tag]['id'] . '/checklist/' . $checklist_id . '/checkItem'), array('name' => $card->shortUrl));

And our last step of the loop is to link the parent card to the rogue child.  We fire off a POST request to /1/cards/zzzzzz/checklist/cccccc/checkItem (where cccccc is the “Slices” checklist ID) with name set to the URL of the rogue child card.  Trello’s interface will convert that to the name of the child card when the parent card is viewed.

As I mentioned in my previous post, this is my first pass and I’m sure there’s a better way to do this.  This fixes a gap in that earlier implementation, though, so obviously iterating on it is working.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.