Posting to Slack via Git Hooks

At my day job our codebase is kept in a handful of self-hosted Git repositories. We have a tool that runs nightly, emailing out a digest of all of the previous day’s commits.

It’s kinda cool but I have the tendency to ignore it as a wall of text. I prefer more granular messaging and since we’re also using Slack, I saw an opportunity to do something with a post-receive hook to get a message as changes came in.

Posting from Git to Slack isn’t revolutionary. There are a tons of solutions for this out there. In fact, my original attempt just used a modified version of Chris Eldredge’s shell script, which I grabbed off of GitHub.  However, my Bash-foo is weak and we’re a PHP shop so I decided to write a solution based in PHP (though heavily based on Eldredge as I had that code in front of me).

To fire off the PHP script, the post-receive hook looks like this:

#!/bin/bash
while read oldrev newrev refname
	php git-slack-post.php --oldrev="$oldrev" --newrev="$newrev" --refname="$refname"
done

That’s simplified a bit as the actual hooks use an absolute path to the script but you see that the script accepts the oldrev, newrev, and refname arguments.

As for the PHP script itself, it looks a bit like this (I’ve sanitized some things to remove references to our internal services).

#!/usr/bin/php -f
<?php
// expects to be called as follows:
// php git-slack-post.php --oldrev="1234567890abcdef" --newrev="0987654321fedcba" --refname="refs/foo/bar"

const WEBHOOK_URL = 'https://hooks.slack.com/services/ABC123FDG/456HIJ789/tWeNtYfOuRcHaRaCtErS0024';
const ZEROREV = '0000000000000000000000000000000000000000';

// pull the arguments from the command line
$newrev = $oldrev = $refname = '';
extract(getopt('', array('oldrev:', 'newrev:', 'refname:')));

// determine change type based on what revisions are available
if ($oldrev === ZEROREV) {
	$change_type = 'create';
} elseif ($newrev === ZEROREV) {
	$change_type = 'delete';
} else {
	$change_type = 'update';
}

$newrev_type = trim(`git cat-file -t {$newrev}`);
$oldrev_type = trim(`git cat-file -t {$oldrev}`);

$rev = $rev_type = '';
if (in_array($change_type, array('create', 'update'))) {
	$rev = $oldrev;
	$rev_type = $oldrev_type;
} elseif ($change_type === 'delete') {
	$rev = $newrev;
	$rev_type = $newrev_type;
}

if ((strpos($refname, 'refs/tags/') === 0) AND ($rev_type === 'commit')) {
	$refname_type = 'tag';
	$short_refname = str_replace('refs/tags/', '', $refname);
} elseif ((strpos($refname, 'refs/tags/') === 0) AND ($rev_type === 'tag')) {
	$refname_type = 'annotated tag';
	$short_refname = str_replace('refs/tags/', '', $refname);
} elseif ((strpos($refname, 'refs/heads/') === 0) AND ($rev_type === 'commit')) {
	$refname_type = 'branch';
	$short_refname = str_replace('refs/heads/', '', $refname);
} elseif ((strpos($refname, 'refs/remotes/') === 0) AND ($rev_type === 'commit')) {
	$refname_type = 'tracking branch';
	$short_refname = str_replace('refs/remotes/', '', $refname);

	echo '*** Push-update of tracking branch, ' . $refname;
	echo '***  - no notification generated.';

	exit;
} else {
	// this shouldn't be possible
	echo '*** Unknown type of update to ' . $refname . ' (' . $rev_type . ')';
	echo '***  - no notification generated.';

	exit;
}

// get the repo name
// only get the final directory name, cut ".git" off the end
$repo = substr(basename(realpath(getcwd())), 0, -4);

// get the user
$process_user = posix_getpwuid(posix_geteuid());
$user = $process_user['name'];

$header = '[' . $repo . '/' . $short_refname . '] ';
switch ($change_type) {
	case 'create':
		$header .= 'New ' . $refname_type . ' has been created';
		break;
	case 'delete':
		$header .= ucwords($refname_type) . ' has been deleted';
		break;
	case 'update':
		$commits = intval(trim(`git log --pretty=oneline {$oldrev}..{$newrev}|wc -l`));
		$header .= $commits . ' new commit' . (($commits > 1) ? 's' : '') . ' pushed by ' . $user;
		break;
	default:
		// this shouldn't be possible
		echo '*** Unknown type of update to ' . $refname . '(' . $rev_type . ')';
		echo '***  - notifications will probably screw up.';
		break;
}

$start = ($change_type === 'update') ? $oldrev : 'HEAD';
$end = $newrev;

// get git data
$data = `git log --pretty=format:"%an&&&&&%h&&&&&%s&&&&&%b@@@@@" {$start}..{$end}`;

$attachments = array();
foreach (explode('@@@@@', $data) AS $item) {
	if (!trim($item)) {
		continue;
	}
	list($author, $hash, $comment, $body) = explode('&&&&&', $item);

	$text = trim('`' . $hash . '` ' . $comment . ' - *' . trim($author) . '*' . "\n\n" . $body);
	$fallback = trim($hash . ' - ' . $comment . ' - ' . trim($author) . "\n\n" . $body);

	// link jira tasks
	$text = preg_replace('#ABC-(\d+)#is', '<https://jira.example.com/browse/ABC-$1|ABC-$1>', $text);

	$text = preg_replace('#\[ABC\][ ]?\((\d+)\)#is', '<https://jira.example.com/browse/ABC-$1|$0>', $text);

	// link support bugs/tickets
	if (preg_match('#(TICKET|BUG|SUPPORT)[\-| ](\d+)#is', $text, $match)) {
		$replace = '<https://support.example.com/passthru.php?item=' . $match[0] . '|' . $match[0] . '>';
		$text = str_replace($match[0], $replace, $text);
	}

	if (preg_match('#\[HOTFIX\][ ]?\((\d+)\)#is', $text, $match)) {
		$replace = '<https://support.example.com/passthru.mh?item=' . $match[0] . '|' . $match[0] . '>';
		$text = str_replace($match[0], $replace, $text);
	}

	$attachments[] = array(
		'text'      => $text,
		'fallback'  => $fallback,
		'color'     => '#1881d0',
		'mrkdwn_in' => array('text'),
	);
}

// build message
$msg = array('text' => $header, 'attachments' => $attachments);

// send message
$c = curl_init();
curl_setopt($c, CURLOPT_URL, WEBHOOK_URL);
curl_setopt($c, CURLOPT_POST, 1);
curl_setopt($c, CURLOPT_POSTFIELDS, json_encode($msg));
curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($c, CURLOPT_HEADER, 0);
curl_setopt($c, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
curl_exec($c);
curl_close($c);

We get details about what’s being pushed and build a message out of all of that. Simple enough. So lets break that down a little bit.

const WEBHOOK_URL = 'https://hooks.slack.com/services/ABC123FDG/456HIJ789/tWeNtYfOuRcHaRaCtErS0024';
const ZEROREV = '0000000000000000000000000000000000000000';

// pull the arguments from the command line
$newrev = $oldrev = $refname = '';
extract(getopt('', array('oldrev:', 'newrev:', 'refname:')));

We start by defining our Slack webhook URL (which can be set up at https://my.slack.com/services/new/incoming-webhook) and what an “empty” revision number looks like. Then we pull in the oldrev, newrev, and refname that should have been passed into the script as arguments.

// determine change type based on what revisions are available
if ($oldrev === ZEROREV) {
	$change_type = 'create';
} elseif ($newrev === ZEROREV) {
	$change_type = 'delete';
} else {
	$change_type = 'update';
}

$newrev_type = trim(`git cat-file -t {$newrev}`);
$oldrev_type = trim(`git cat-file -t {$oldrev}`);

If the old revision is empty, it means we’re creating something. If the new revision is empty, it means we’re deleting something. Otherwise it’s an update of something that already existed and continues to do so.

Whatever the change type, we get more information about the old and new revisions by using the backtick operator to run the get cat-file -t command for each revision number.

$rev = $rev_type = '';
if (in_array($change_type, array('create', 'update'))) {
	$rev = $oldrev;
	$rev_type = $oldrev_type;
} elseif ($change_type === 'delete') {
	$rev = $newrev;
	$rev_type = $newrev_type;
}

If the change type is a create or an update, we’ll use the old revision data to reference things going forward. If it’s a delete we’ll use the new revision data.

if ((strpos($refname, 'refs/tags/') === 0) AND ($rev_type === 'commit')) {
	$refname_type = 'tag';
	$short_refname = str_replace('refs/tags/', '', $refname);
} elseif ((strpos($refname, 'refs/tags/') === 0) AND ($rev_type === 'tag')) {
	$refname_type = 'annotated tag';
	$short_refname = str_replace('refs/tags/', '', $refname);
} elseif ((strpos($refname, 'refs/heads/') === 0) AND ($rev_type === 'commit')) {
	$refname_type = 'branch';
	$short_refname = str_replace('refs/heads/', '', $refname);
} elseif ((strpos($refname, 'refs/remotes/') === 0) AND ($rev_type === 'commit')) {
	$refname_type = 'tracking branch';
	$short_refname = str_replace('refs/remotes/', '', $refname);

	echo '*** Push-update of tracking branch, ' . $refname;
	echo '***  - no notification generated.';

	exit;
} else {
	// this shouldn't be possible
	echo '*** Unknown type of update to ' . $refname . ' (' . $rev_type . ')';
	echo '***  - no notification generated.';

	exit;
}

This is just a bunch of logic that looks at the refname and the revision type and determines exactly what you’ve pushed. If we can’t figure out what it is, we exit with an error.

// get the repo name
// only get the final directory name, cut ".git" off the end
$repo = substr(basename(realpath(getcwd())), 0, -4);

// get the user
$process_user = posix_getpwuid(posix_geteuid());
$user = $process_user['name'];

We determine the repo name based on the path the hook is running from and we get the user it’s running as so we know who did the push we’re about to notify people of.

$header = '[' . $repo . '/' . $short_refname . '] ';
switch ($change_type) {
	case 'create':
		$header .= 'New ' . $refname_type . ' has been created';
		break;
	case 'delete':
		$header .= ucwords($refname_type) . ' has been deleted';
		break;
	case 'update':
		$commits = intval(trim(`git log --pretty=oneline {$oldrev}..{$newrev}|wc -l`));
		$header .= $commits . ' new commit' . (($commits > 1) ? 's' : '') . ' pushed by ' . $user;
		break;
	default:
		// this shouldn't be possible
		echo '*** Unknown type of update to ' . $refname . '(' . $rev_type . ')';
		echo '***  - notifications will probably screw up.';
		break;
}

Now we start building the message that will be posted to Slack. The message begins in the form of “[reponame/branchname]. If it’s a create or delete, we then note what was created or deleted. If it’s a commit, we note the number of commits and who they were pushed by.

$start = ($change_type === 'update') ? $oldrev : 'HEAD';
$end = $newrev;

// get git data
$data = `git log --pretty=format:"%an&&&&&%h&&&&&%s&&&&&%b@@@@@" {$start}..{$end}`;

$attachments = array();
foreach (explode('@@@@@', $data) AS $item) {
	if (!trim($item)) {
		continue;
	}
	list($author, $hash, $comment, $body) = explode('&&&&&', $item);

We’re going to start building a series of messages (what Slack calls “attachments”) detailing items from the Git log pertinent to this push. We use the git log command and define our format. We get the author name with %an, the hash with %h, the commit message with %s and the commit body with %b. Those are all separated by five ampersands, with each item separated by five at signs. We use those goofy separators so we can split on them later, as it’s unlikely anyone enters those in text.

	$text = trim('`' . $hash . '` ' . $comment . ' - *' . trim($author) . '*' . "\n\n" . $body);
	$fallback = trim($hash . ' - ' . $comment . ' - ' . trim($author) . "\n\n" . $body);

Here we actually build our message. The text property of a Slack attachment can be markdown, so we pretty it up a little bit. The fallback property is plaintext so it doesn’t get that formatting. The result is the hash, then the commit message, then the author. If there is a longer commit message, it gets added after that.

	// link jira tasks
	$text = preg_replace('#ABC-(\d+)#is', '<https://jira.example.com/browse/ABC-$1|ABC-$1>', $text);

	$text = preg_replace('#\[ABC\][ ]?\((\d+)\)#is', '<https://jira.example.com/browse/ABC-$1|$0>', $text);

	// link support bugs/tickets
	if (preg_match('#(TICKET|BUG|SUPPORT)[\-| ](\d+)#is', $text, $match)) {
		$replace = '<https://support.example.com/passthru.php?item=' . $match[0] . '|' . $match[0] . '>';
		$text = str_replace($match[0], $replace, $text);
	}

	if (preg_match('#\[HOTFIX\][ ]?\((\d+)\)#is', $text, $match)) {
		$replace = '<https://support.example.com/passthru.mh?item=' . $match[0] . '|' . $match[0] . '>';
		$text = str_replace($match[0], $replace, $text);
	}

We’re not done with that text yet, though. We have a loosely-followed naming convention for our commits and we can use that to link back to other systems that might have more information about the commit. Anything that was a Jira task should start with the task number in the format “[ABC-1234]” but sometimes it’s “(ABC-1234)” or “[ABC] (1234)” or “[ABC](1234)” so we account for all of those. Similarly, references to our ticket system sometimes use “TICKET” or “BUG” or “SUPPORT” and sometimes have a space or a dash and sometimes use “HOTFIX” and… You get the idea. There are probably better regular expressions to use here but these work. So we find references to our Jira cards and link back to them, then find references to our support system and link back to it, where there’s a script that will do some additional parsing to figure out where to go.

	$attachments[] = array(
		'text'      => $text,
		'fallback'  => $fallback,
		'color'     => '#1881d0',
		'mrkdwn_in' => array('text'),
	);

With all of that done, we build an array for this attachment, defining our text, our fallback text, a color to display alongside the attachment, and confirming that there is markdown in our text field.

}

// build message
$msg = array('text' => $header, 'attachments' => $attachments);

// send message
$c = curl_init();
curl_setopt($c, CURLOPT_URL, WEBHOOK_URL);
curl_setopt($c, CURLOPT_POST, 1);
curl_setopt($c, CURLOPT_POSTFIELDS, json_encode($msg));
curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($c, CURLOPT_HEADER, 0);
curl_setopt($c, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
curl_exec($c);
curl_close($c);

Once we’re done looping through our data from the git log and building our attachments, we put together the message and send it off via Curl. The message is just an array, which then gets JSON-encoded and posted to our webhook URL.

It’s fire-and-forget, so we don’t make any note if the webhook doesn’t respond or anything like that.

For a short time we had an additional message attachment that included diff data but we decided we didn’t want our code getting posted to Slack so we removed it.

As I said, there are tons of solutions for this out there, this is just one more.

Reinventing the Wheel: Cache-Busting via Automated File Versioning

I have a tendency to like to reinvent the wheel in my development, just to prove that I can.

I’ve gotten better about it over the years.  I replaced my custom-built blog engine with WordPress on DetroitHockey.Net, for example.  I still roll my own classes for interacting with the Trello and Twitter APIs, though.

Lately I’ve been looking to solve asset caching issues.  If I make a change to main.js, for example, how do I make sure my users get the updated file?  This is something that I’m sure has been solved, but I wanted to give it a shot myself.

We can’t force a user to know that a file has changed and re-download it.  We can tell them how long to cache a file for, and the browser may or may not respect that, but we can’t interrupt that with a new file.

What we can do is version the file.  That’s not a huge deal.  Drop a suffix onto the file name (main.js becomes main-v2.js).  The problem is I’m lazy.  I don’t want to have to rename the file and update all of the places it’s used.  I want that to happen automatically if I happened to change the file.

Conveniently, my build process already had a point where it acted only if a CSS or Javascript file had changed.

I use BitBucket for my source control and BitBucket Pipelines for publishing.  There’s a step in my publish plan that sends a curl request to a listener on my server.  The listener takes a look at each CSS and JS file and minifies it, but it only saves the newly-minified file if it’s different from what’s already on the server.

So I’ve got a point where I know if the file has changed, what do I do with it?

I added a configuration file writable by that listener.  It’s an array stored as JSON tracking a version number for each asset file.  I then updated the references to those files in my site templates to make sure to use the version number from the configuration file is used.

The last step was to make the listener actually write to the configuration file.  The listener, as I said, checks to see if the newly-minified version of main.js (for example) is different from the existing minified version of main.js.  If it is, the existing file is deleted, the configuration file is updated with a new version number based on the time, and the new file is saved with that number.

I don’t have to rename anything.  I don’t have to change any links.  I don’t have to ask anyone to hard refresh.  So far, it works out pretty well.

Griffins Jersey Design Contest – 80s Edition

As I’ve noted in the past, I have a love-hate relationship with the Grand Rapids Griffins’ annual jersey design contest.  I have the tendency to put more thought than they probably intend into my own entries and, while I was a finalist last year, I don’t think I ever come close to winning.

This year I was going to skip out on the endeavor entirely until they added an impossible new wrinkle to the contest: Design a 1980s “fauxback” jersey.

The Griffins were founded in 1996, hence the “fauxback” requirement.  The idea is to come up with a look that represents a Griffins team that existed in that decade.  Which raises an interesting question: What makes an 80s hockey jersey?

The shoulder yokes of the Minnesota North Stars, New Jersey Devils, and Buffalo Sabres all jumped out at me, though all but New Jersey’s found their origins in the 70s.  Likewise the 70s birthed the over-the-shoulder stripes of the Winnipeg Jets, Toronto Maple Leafs, and a handful of All-Star teams.

I thought about modern teams that throwback to the 80s and how their identities have changed since then.  The Edmonton Oilers’ logo is virtually untouched but changed from royal blue and orange to navy blue and copper (then back to royal blue and orange, and now navy and orange).  The Calgary Flames were bright red and yellow in the 80s, darkening their red and adding black in the 90s.  The Los Angeles Kings went from purple and yellow to black and silver.  The Devils went from red and green to red and black.

What makes an 80s hockey jersey?  It’s not so much the striping pattern or the logo…  It’s the colors.  The bright colors of 1980s hockey design were virtually abandoned in the 1990s.  In addition to the previously-mentioned changes, the 90s saw the North Stars go from bright green and yellow to forest green and metallic gold.  Like the Oilers, the New York Islanders abandoned royal blue and orange (only to – also like the Oilers – eventually return to it).  The Hartford Whalers gave up green and royal blue for green, silver, and navy.

Which is a problem for this contest because the Griffins explicitly stated that entrants should use the team’s current colors, with the jersey base color being red or black.

By my count, in the 1980s across the National Hockey League, the American Hockey League, and the International Hockey League, only the Chicago Blackhawks (and some of their affiliates) used a red, black, and white combination.  The metallic silver the Griffins now use was unseen in the NHL until the 90s, though the Kings added grey in 1988.  Teams in the 80s used yellow, not the gold in Grand Rapids’ current identity.

As such, I don’t think it’s possible to have an “80s fauxback” that uses the Griffins’ current colors.  That didn’t stop me from trying, though.

My first sketch used the Winnipeg/Toronto-style over-the-shoulder striping and a Edmonton Oilers-like logo, with a griffin silhouette over the word “Griffins” in a circle.  I also dropped gold and silver from the color scheme to simplify things.  The logo felt forced, though, and the striping seemed too modern, so I scrapped that idea.

Next I ignored the logo and tried a striping pattern based on the Buffalo Sabres.  The shoulders featured a black yoke with white and gold outlines. The sleeves and waist had a black/white/gold/white/black stripe set.  I actually like this idea a bit but, again, there was nothing that made me think “80s” so I moved on.

Just to see if it led to anything, I cloned the North Stars’ 1988 jersey set, swapping green for red and yellow for gold.  This led me to believe that the gold just wasn’t going to work and I needed to go back to red, white, and black.

Starting with a red jersey, I added thick stripes in white and black – separated by a thin red stripe – to the sleeves and waist.  I then included a yoke in black with a white outline.  I realized I had something similar to the Devils’ 1982 home uniform and decided it was 80s enough to move forward.  I also decided that between the North Stars, the New York Rangers, various NHL All-Star teams, and some of the 1988 Olympic teams, a drop-shadowed number represented the 80s pretty well, too.

For the logo, the Moncton Hawks jumped out at me, with their bird head and abstract wings inside a circle.  I tried going in that direction with a stylized wing attached to a griffin head with an outstretched claw, all merged together with the team’s name as a wordmark.  It didn’t look like a griffin to me so I decided to go in a different direction.

Next I went simple.  A griffin silhouette in a circle with “Grand Rapids Griffins” around it.  Specifically, this was based off of an old Muskegon Mohawks jersey that I couldn’t figure out the exact year for.  The wing came from the Detroit Red Wings’ logo while the tail was from the Griffins’ original logo as a pair of homages.  It looked more 1960s than 1980s, though, so I went back to the abstract griffin idea.

I removed the circle and added in the bottom half of the griffin’s body. This gave me an opportunity to reuse the tail from the Griffins’ original logo, as with the silhouette logo. Then I switched up the griffin’s head to look more like an eagle and less like a dragon.  At this point, I had my crest, and I decided not to worry about shoulder logos because, by and large, they weren’t used in the 80s.

While I’ve submitted the red, black, and white design, I still don’t think it’s 80s enough.  The colors make it look 90s to me.

I tried simplifying the design to just red and white and, while I think it looks more 80s, I don’t think it looks as good.  In the end, the design has to look good enough to win.

But I come back to the idea that, by forcing the modern colors, the Griffins have unintentionally made any submission less 1980s.  I tried the team’s colors from before their rebrand two years ago – red, white, and blue – and feel that it’s a design that screams 80s.  I just can’t use it.

Wrong colors or not, as I said at the start, I don’t expect to win this thing anyway.  Based on last year, the voters seem swayed by submissions that look like they came out of a catalogue or a video game, not flat designs in the template I use.  I don’t get to submit all the thought I put into it. But it will be interesting to see what the voters think an 80s jersey is.


Update 8/21/2017, 8:50 PM: My design is up for vote today, which gets me thinking about my design more.  I now think I should have included a circle behind the logo, as I originally attempted.  It helps make it look more 80s than 90s.

Though it’s too late for the contest, I’ve updated the red jersey I submitted to include the circle, added it to the blue variant that I think the team should actually wear, and created white versions of each.


View All Images as Gallery

Rebranding FantasyHockeySim.com

I launched FantasyHockeySim.com as a spinoff of DetroitHockey.Net last summer and the visual elements of it were a rush job.  Getting the site out the door was my priority, so I stole design elements for FHS from DH.N and put together a logo that didn’t say “fantasy hockey” at all.

Awhile ago I ranted about the Detroit Red Wings’ “Hockeytown” logo and how the only way it said “Hockeytown” was literally, with the text splashed across it.  I’d done the same thing with the FantasyHockeySim.com logo, as crossed sticks said “hockey” but the only way it said “fantasy hockey” was via the FHS acronym across the front.  Even then, it looked more like the logo for a high school hockey team than for simulated fantasy hockey software.

While stuck on a development project, I decided to take a crack at a new FHS logo.

Because I like shield-based logos far too much, my first pass centered around different shields.  Eventually I put together one that I really liked the look of and started building alternate logos around it.

I showed the “final” set (the shield logo and “promotional” versions featuring additional text) around and realized I hadn’t solved the problem I was trying to handle in the first place.  The logos still only said “fantasy hockey” literally, and even then it was only the promotional ones.

I stepped back from it and didn’t think about it for awhile until an idea came to me during my drive into work a couple weeks later.

Representing hockey in a logo is easy.  Sticks, pucks, all sorts of imagery is available.  How do you represent “simulation” though?  Well, simulation means computers and code and, even to a layman, X/HTML’s angle brackets are recognizable as code.  So I wrapped a pair of crossed hockey sticks in angle brackets and went from there.

The first issue I hit with the logo was that the crossed sticks looked a bit like an X, so I changed their position and added lines representing tape to the blades.  After that, I decided to give up on my attempt at a monochrome logo, changing the color of the angle brackets to help separate them from the sticks (with the added effect of appearing as syntax coloring).

As I worked on a primary version of the logo, I also created an alternate version and a “promotional” version.  The promotional logo features the “bracket” logo inside a roundel containing the “Simulated Fantasy Hockey” descriptor and the site name while the alternate is simplified version of that, without the text.

The biggest issue with the alternate and promotional logos was making sure the broken inner circle of the roundel didn’t appear like it was supposed to be attached to the angle brackets.  To handle that, I shrank down the width of that inner circle and made the break in it wider.  Changing the color of the brackets completed the effect.

The FHS site still needs a redesign to get away from borrowing so much from DH.N but at least now the logo is original and descriptive.

Grand Rapids Griffins Alternate Jersey Concept 2016

Once again, the Grand Rapids Griffins are holding their annual jersey design contest and, even though I know I disagree with their design preferences, I’m throwing my hat into the ring.

Two years ago I entered a design featuring a griffin silhouette on a shield as the primary logo, with the jersey in “vintage” white, blue, and red. The shoulder logo was a roundel with an interlocking GR logo the team had previously used. Last year I tweaked the logo to make the griffin’s wing a little cleaner, switched up the shoulder logos, changed the number font, and updated the colors to go along with the Griffins’ color change, but the striping pattern stayed the same.

This year I thought for certain that I was going to enter another red jersey, so I started with my previous design. I swapped out the “vintage” colors and simplified the striping pattern. Rather than black numbers with a white outline, I went with white numbers outlined in black as they would be more legible.  I kept the player’s jersey number in the collar webbing because, as I’ve said before, I loved that feature of their old alternate jersey.  I also brought back the shoulder logo from my original entry as a 20th Season patch no longer made sense.  Finally, I broke down and put the Winged Wheel logo of the Detroit Red Wings on one shoulder, as the Griffins do that on their standard jerseys to denote their parent club, no matter how much I dislike the practice.

The first version of my concept for the Griffins' 2016 jersey design contest.
The first version of my concept for the Griffins’ 2016 jersey design contest.

I felt like that design was too simple, though, so I continued evolving the design. For the second generation, I switched the order of the sleeve colors and removed the shoulder yoke. I wanted the Griffins’ jersey to have an homage to the alternate colored sleeves of the Red Wings’ white jersey. I also brought back the black numbers outlined in white as I figured for a one-shot jersey, legibility is less of a concern (in fact, the Griffins wore dark red numbers on a dark blue jersey for one game two seasons ago).

The second version of my concept for the Griffins' 2016 jersey design contest.
The second version of my concept for the Griffins’ 2016 jersey design contest.

Unfotunately, I thought that looked far too close to the design of the Texas Stars. While the Griffins selected a design two seasons ago that was basically a color swapped Iowa Wild jersey, I wasn’t comfortable submitting something like that.

As such, I decided to fully embrace the alternate-colored sleeves. I made the jersey body red with a black stripe bounded by white and the sleeves black with a red stripe and white outline. I also changed up the shoulder logo, replacing the interlocking GR with the griffin silhouette I used on the crest as I didn’t want to re-use one of the team’s existing marks, even modified.

The third version of my concept for the Griffins' 2016 jersey design contest.
The third version of my concept for the Griffins’ 2016 jersey design contest.

At this point, I thought that I had my final design. I started showing it to a handful of people and near-universally the feedback was that they wanted to see a black version. Of course, I had started out trying to make a red jersey, so at first I ignored this.  Eventually I hit the point where I had to listen to what my informal focus group was saying and did a switch of the colors. While a quick Twitter poll showed 53% of fans would have preferred a red jersey, 100% of people who saw both the red and black jerseys picked the black one. As such, the black one was my final design.

My submission for the Griffins' 2016 jersey design contest.
My submission for the Griffins’ 2016 jersey design contest.

There are some coincidental homages in this design.  The Grand Rapids Owls, an International Hockey League team in the late 1970s, wore jerseys with red sleeves that had black stripes outlined in white.  Additionally, the Red Wings sold “fashion” jerseys (alternate jerseys that were never actually worn in-game) that had a black body with a red stripe at the waist, red sleeves with a black stripe, and numbers that match this design.  The stripes did not include a white outline.


The Griffins explicitly stated that they wanted a dark jersey from this year’s contest.  I imagine that’s because of the AHL’s new rule that will see light jerseys worn at home until Christmas and dark jerseys after that.  Previously there had been some flexibility with regards to alternates but I’d guess that’s out the window with these new rules.

At any rate, just for fun, I created a white version of my submission.

An alternate white version of my concept for the Griffins' 2016 jersey design contest.
An alternate white version of my concept for the Griffins’ 2016 jersey design contest.

As I mentioned, the primary logo is carried over directly from last season’s submission, aside from the color switch.  This is a logo full of homages.  The shape of the shield is that of the DetroitHockey.Net logo as a reference to my previous work.  The feathers on the griffin’s wing are those of the Winged Wheel.  The griffin’s tail is that of the original Grand Rapids logo.

The crest logo for my 2016 Griffins jersey contest submission.
The crest logo for my 2016 Griffins jersey contest submission.

The shoulder logo went through a number of revisions as I sorted out what color it would be placed on, how much detail should be included, and what element would be inside the roundel.

While I think that having a silhouetted griffin on both the crest and the shoulder is a bit repetitive, I see the different uses to be somewhat like how the Tampa Bay Lightning have a lightning bolt on both the crest and the shoulder.


As I’ve said every year, I don’t expect to win this contest.  This year is interesting because ten finalists will be determined by fan vote and then the Griffins staff will decide.  Additionally, this year submissions do not have to follow a standardized template.  If I had to guess, the vote will skew towards submissions that look like they come out of a video game, as they come across as the most impressive.  Whether or not those are actually the best designs will have to be seen.


Update: After posting this I noticed that the shoulder logos are incorrectly depicted on the view of the back of the jersey.  They should be switched so that the Winged Wheel is on the right shoulder and the roundel is on the left, as they appear in the view of the front of the jersey.  I’m not going to update the graphics, just use your imagination a little.

Lessons Learned: Slack API

I wrote last month about launching FantasyHockeySim.com and replacing the old DetroitHockey.Net Community Forums with a Slack team.  At the time I promised a future post about the work I did with the Slack API.

The Slack API uses OAuth for authentication, which isn’t really a surprise.  It does open up problems if you want to have actions taken on behalf of an app instead of a user.  Unlike the Twitter API, the concept of bearer tokens is somewhat limited, and bots can only post messages (not extend invites or kick users, for example), so I ended up creating a user to act on behalf of the site.

Speaking of extending invitations, you can invite people to join a channel/group but there’s no formal way to invite users to join a group programmatically.  There is a hack, however, that requires you to use a bearer token from the Slack API Tester application, which seems asinine to me.

Like the Facebook API – but unlike Twitter or Trello – the Slack API makes use of permission scopes.  Unlike Facebook, on the user’s first login, you can only ask for identity-related scopes.  As such, the signup/login workflow for FHS is redundant and somewhat annoying, but users only have to go through it once.

So I have a signup form on FHS that automatically sends an invite to the prospective user.  After they sign up in the FHS Slack channel, they come back to the mothersite and log in using the new Slack user.  As they log in, Slack asks if they want to give FHS identity permissions with their account.  The user confirms this and logs in and I auto-create a user for them in the FHS system, where I keep track of what permissions FHS has for each user.

After logging in, if the user has not yet granted sufficient permissions, s/he will be presented with a nag screen that asks for those to be granted.  After granting permissions, the screen won’t be shown again.

Now that FHS has all of the permissions it needs, the site can actually do some stuff with the user’s Slack account.  Mostly this is administrative.

When a user joins a specific fantasy hockey league, s/he is added to that league’s private channel via a call to groups.invite.  When s/he leaves the league, s/he is removed from the channel via groups.kick.  Both of these are run by the aforementioned site user that I created, which has extended permissions (compared to the rest of the site users).

General managers (users that are members of one or more leagues) can push messages from Slack to their fantasy team’s home page.  This allows them to post trade blocks or out-of-office messages – content that’s a little longer-lived than the standard Slack post.  The interface for that relies on the groups.history and channels.history methods so that all of the possible posts for importing can be found.  Once a message is selected, pins.add is used to make sure the message is pinned in channel.  I also use pins.remove to clean up anything no longer flagged as news and pins.list to audit what should be pinned against what actually is pinned.

The site user automatically posts transaction information from the league site to the league channel using the chat.postMessage method.

Probably the most important new functionality is a “Message Center” section of FHS that shows users their missed messages from the Slack channels.  This is where I feel like things get weird with the API.

In the Slack interface, there are channels and direct messages.  In the API, there are channels, groups, IMs, and MPIMs (multi-person IMs).  The interface obscures away the idea that private channels are actually groups.  MPIMs -a type of IM that contains more than two participants – are also a special subset of groups.

That means that to find the total number of unread messages, you have to check channels.history, groups.history, and im.history and look for the unread_count.  You could also check mpim.history for unread_count but groups.history gets you that.

Actually, as of when I wrote my Message Center code, mpim.history was broken.  I Tweeted about unread_count always being zero and the Slack API team replied apologizing for the bug.

So in order to get the total unread count, you have to make three calls, which means three different permission scopes have to be granted.

Technically, the Slack API is really easy to work with.  Make a request, pass the proper credentials, get data back.  That said, after using it for a little bit, I can’t help but feel like there’s something a little off.  Inconsistencies – like “channels” and “groups” being plural but “im” and “mpim” being singular – make it seem thrown together.  That it’s not RESTful is a little bit of a surprise.  None of that is a reason not to use it, though.

Another Odd Site Launch: DetroitHockey.Net 2016

After launching a new site that wasn’t all that new, my next project was a redevelopment of DetroitHockey.Net that removed features rather than adding them.

With the fantasy side of the site gone and no longer reliant on the rarely-used DH.N Community Forums, I reworked DetroitHockey.Net to remove the forums.  I also pulled out my custom blog software and replaced it with WordPress.

The idea behind going to WordPress was that, while I enjoyed writing my own blog software, it left me somewhat behind the times as far as what the site was capable of.  It’s a wheel I don’t want to reinvent, so instead I learned how to tap into native functionality of WordPress to do the things I wanted to do.

With the loss of the forums and the continued simplification of the site content, I was able to go with a more streamlined design.  The header features the site logo (I launched with the 20th Season logo in place but it will revert when that season ends), an advertisement, and one navigation bar.  This is in contrast to the old version of the site, which had a logo, three navigation menus, a login menu, an ad, and a set of links to forum discussions.

I also took the opportunity to implement the sub-navigation menu system that I developed for FantasyHockeySim.com.

The home page features seven recent headlines from the WordPress-driven news section, an embedded Twitter feed (replacing a custom developed version of the feed display), the score of the most recent Red Wings’ game, and a calendar showing the current month’s schedule (with games denoted by opponent logo).

The individual article pages use a modified version of the WordPress Twenty-sixteen theme, wrapped in the DetroitHockey.Net template.

The only other page that was heavily modified was the DH.N Contributors page, which pulls from the WordPress user system.

This version of the site is intended to be transitional, with further revisions coming after seeing how the forum-less site is used.

A New Site That Isn’t New: FantasyHockeySim.com

A few days ago I launched a new website. Kind of.

FantasyHockeySim.com went live last Thursday. The site plays host to a couple simulated fantasy hockey leagues using the FHLSim software. Through FHS, league members can manage their teams in real-time, as opposed to all kinds of manual data entry that is required by FHLSim out of the box.

And if that sounds familiar, it might be because up until FantasyHockeySim.com launched, all of that was a part of DetroitHockey.Net. FHS is simply that functionality spun off into its own site.

One of the leagues that had been hosted at DH.N, the National Hockey Association, is the official league of SportsLogos.Net. And some of its members were not happy about having to be members of a Red Wings site to do their fantasy hockey. So I decided to move things to a neutral site.

With the fantasy hockey side of things no longer reliant on DetroitHockey.Net infrastructure, this would also allow me to update the two sites more easily.

Originally, the site was going to be called FHLSite, as that’s what I had referred to my software as when it was a part of DH.N. I had a hard time coming up with a logo for that, though, so I changed the name to FantasyHockeySim.com, abbreviated FHS.

For the logo, I took the shield from DH.N’s primary logo and changed the colors to gray and blue, then put the letters FHS over the top of it. A pair of crossed hockey sticks – the sticks from the DH.N logo, appear behind the shield.

The logo for FantasyHockeySim.com on a blue background
The logo for FantasyHockeySim.com on a blue background

The basic site design was ported over directly from DetroitHockey.Net but the header and footer were updated. For the main site and each league, the basic elements of the template are the same, with branding images and an accent color swapped out to provide uniqueness. The main site is a blue-gray while the DFHL is red and the NHA is a dark blue.

The biggest change was the replacement of the DH.N Community Forums as a communication hub and an identity provider. DH.N’s Invision Power Board installation is integrated into the entire site, which included the fantasy hockey side of things. It would not be available upon moving to a new domain.

I decided to create a team on Slack and build a login system around it.  I’ll write more about this in a future post on lessons learned about the Slack API, but the short version is that their OAuth workflow and API combined to allow me to have users with accounts on the FHS Slack team log in to the main FHS site and see their unread message counts, effectively replicating the functionality of the forums.

The end result is something curious, where I’ve launched a new site but created virtually no new functionality.  I think it’s a good starting point, though.

Fixing the Joe Louis Arena Farewell Season Logo

Given my recent history, it would seem that I’m contractually obligated to dislike any graphics package any team in the Detroit Red Wings’ organization puts out.

I didn’t like the Red Wings’ Winter Classic jerseys in 2014 or their Stadium Series jerseys for this year.  I don’t like their Hockeytown logo.  I didn’t like the Grand Rapids Griffins’ 20th Season logo or their updated primary logo and I disagree with their choices in the fan-designed jersey contests.

So when they unveiled the logo celebrating the team’s final season at Joe Louis Arena, it should come as no surprise that I was unhappy with it.

The Red Wings' "Farewell Season" logo for Joe Louis Arena.
The Red Wings’ “Farewell Season” logo for Joe Louis Arena.

The primary problem I have with this is that the only way it says “Joe Louis Arena” is literally.  Much like my complaint about the Hockeytown logo.  Even then, they didn’t use the arena’s actual name but the nickname of “The Joe.”

The main elements of the logo are a big block of text, a Stanley Cup silhouette, and four stars representing Stanley Cup Championships.  Visually, this feels more like a logo celebrating those championships than the arena that hosted the team when they were won.

The Joe isn’t all of that interesting of a building but at least try to represent it visually.

I threw together a rough drawing of the direction I’d like to have seen them go (and I emphasize “rough”).

My idea for a Joe Louis Arena "Farewell Season" logo.
My idea for a Joe Louis Arena “Farewell Season” logo.

The primary element of this design is a drawing of the exterior of Joe Louis Arena.  It’s not a beautiful building by any means but it is recognizable and as such it makes sense to use it in the logo.

I kept the “Farewell Season” text across the top of the logo and the Winged Wheel at the bottom.  The four stars are kept, two on each side of the Wings’ logo.  A ribbon is added across the bottom of the arena rendering containing the remaining text from the original logo.  The bounding shape is changed because I think it works better as a patch.

The only element removed is the Stanley Cup silhouette, which I don’t think is necessary.

I should again emphasize that this is a rough drawing.  The rendering of the arena is pretty sloppy.  I think a better job would be done if it were actually being used (in fact, the Red Wings already have a pretty good drawing of the arena on their “JLA Insider” page of their web site, though I think it’s a little “comic” for use in a logo like this).  For anyone who thinks an arena can’t be rendered well on a patch, I give you the Maple Leafs’ patch for their final season at Maple Leaf Gardens, the Devils’ patch for their first year in Prudential Center, and the Hurricanes’ patch for when they moved to what was then Raleigh Entertainment and Sports Arena.

So change the shape, change the colors, change any of the other elements…  Just include Joe Louis Arena.  That’s what the logo is supposed to be for.  Show it, don’t just say it.


EDIT 2/15: I got some feedback that the bounding shape is unnecessary because it reduces the impact of the arena’s distinctive shape and I agree.  I mocked up a version that uses the drawing of the arena, the ribbon, and the Winged Wheel without losing any of the important elements.  Even the four stars are kept.

Revised concept for a Joe Louis Arena "Farewell Season" logo without a bounding shape.
Revised concept for a Joe Louis Arena “Farewell Season” logo without a bounding shape.

 

Martin Biron, John Scott, and Software Development

Martin Biron was the last player in the National Hockey League to wear #00.  It’s one of my favorite stories because it mixes hockey with the pitfalls of software development.

Biron, then a rookie goalie for the Buffalo Sabres, appeared in three games in the 1995-96 season wearing #00, which he had worn during his junior career as well.  He wasn’t the first to wear it – John Davidson had donned #00 for the New York Rangers in the 1970s and Ed Belfour wore it in an All-Star Game in the early 1990s – but by the time Biron made it back into the Buffalo lineup during the 1998-99 season he’d lost the number forever, switching to #43.

Between Biron’s final appearance in #00 and his first in #43, the NHL rolled out a new stat-tracking software package.  If I recall correctly, it was developed by IBM (though I’ve heard Compuware mentioned as well, I could be mis-remembering) to much fanfare.  Unfortunately there was a bug that only applied to Martin Biron: It assumed jersey numbers were non-zero.

When I wrote the software behind the short-lived Hockey Sweater Numbers web site I specifically made sure to handle this.  IBM – ridiculously, to me – did not, and rather than fix it the league just banned the numbers that the system didn’t account for.

Why is this story on my mind right now?  Because of John Scott and my own web site.

There used to be a listing of all of the winners of NHL awards on DetroitHockey.Net.  I just pulled it off the site because that data is all over the Internet, I don’t need to worry about it on DH.N.  But I still maintain the database for my own personal uses.

John Scott is a journeyman enforcer.  He started this season with the Arizona Coyotes, out of the lineup more often than he was in it.  When the NHL launched their web-based All-Star voting campaign, though, Scott quickly rocketed to the top of the voting.  When the dust settled, he had been elected as the captain of the Pacific Division team.

Almost immediately he was traded to the Montreal Canadiens of the Atlantic Division, then demoted to their American Hockey League affiliate.  The league never wanted him in the All-Star game and it appeared that their problem was solved.  In the end, with the support of many players but seemingly few front offices, Scott was allowed to play in the All-Star Game.  He would captain the Pacific Division but would represent neither the Coyotes nor the Canadiens.  His petition to represent the St. John IceCaps, his AHL team, was denied.  He was a man without a team.

Like something out of a movie, the journeyman now-minor-leaguer with five career goals scored twice and was named MVP of the event.

Within minutes of the announcement, I found myself staring at the administration system for the NHL awards on DH.N, entering Scott as the winner of the All-Star MVP award, and stumped because my system requires a player to represent a team and Scott doesn’t have one.

The story I’ve laughed at so much has come around to bite me in the ass a bit.  Software not written to handle the strange things hockey throws at it.

Unlike the NHL, I’m adapting my software.  I’m hacking in the St. John IceCaps as Scott’s team.

NHL All-Star MVP of the AHL's St. John's IceCaps
NHL All-Star MVP of the AHL’s St. John’s IceCaps