Looking back at the fertile environmental and strategic decisions that led to a successful support team at FullStory.
Goodbye Support, Hello Customer Experience Management
Forget web apps, unblock yourself with code
If you want to get to get a job writing code and you don’t have a computer science degree, what do you do? “Attend a Boot Camp!” seems to be the common refrain. Specifically, one to make web apps. Web app boot camps, baby, that will learn you some code.
I’m going to pick on web app boot camps for a bit. The vast majority of opportunities to use code do not involve web apps.
OKRs at Trello
At SUPCONF this week, Mercer Smith-Looper gave a talk about becoming a leader. At some point during her presentation, she shared about how when we were working together at Trello in the summer of 2016, we adopted OKRs as a way to provide the support team direction for upcoming year. This post is a deep dive into how we brought OKRs to the support team at Trello.
What are OKRs?
Before we jump into the story, let's get a working definition together for OKRs. OKRs are Objectives and Key Results. The Objective is the big visionary thing that you want to accomplish—it's usually qualitative. The Key Results are the handful of things that tell you if you've achieved your objective—they're measurable and quantitative. We'll look at an example in a bit.
Put another way, OKRs give you the ability to put a sense of why behind your work and a way to measure success.
"Why are we doing this work?"
"To meet the objective."
"How will we know we've met the objective?"
"By measuring the key results."
Stuck in Ambiguity
When I utter the sentence, "we introduced OKRs to the support team at Trello", it seems like a done deal, obviously existent, almost as if it we had always had OKRs. The truth is that to arrive at that point, I (and the support team along with me) had to swim through a lot of ambiguity, not knowing at all the best way to move forward, not sure if we were doing the right thing.
As the support team was growing in 2016, I started to have the sense of "the support team should be able to do more for Trello." We had the support inbox mostly under control and provided a high level of email-based support to all of our customers—what else could we handle? So I presented lots of project ideas to my manager.
Have you ever gotten frustrated because you feel like you're not getting traction to move forward on a project? That was happening quite frequently. I had lots of ideas for projects, but had trouble getting traction when I discussed them with my manager. It wasn't clear why we didn't see eye to eye, but without alignment, it didn't make sense to move forward on the projects. That was frustrating.
At the time, he suggested I come up with a vision for where support should be in a year. This was a great idea (thanks Justin!), so to address that, I decided to use OKRs. OKRs weren't a magic bullet. They were mainly a way for us to indicate our direction and to rally behind where we were going.
Nobody was doing OKRs at Trello at the time. We were going to have to figure this out on our own without much in the way of an example from our current working environment.
Researching OKRs
My search for a magical OKR spreadsheet was in vain. It doesn't exist. That's because working from a firm sense of why isn't something that happens overnight. A "system" can't teach that to you. There's no perfect OKR implementation that's going to give you a sense of purpose and create alignment within your team or organization. If you have a strong sense of purpose and alignment, OKRs will help immensely, but they can't give you those things. You have to struggle to find your why, and—at least within an organization—align your why with the why of the entire company.
But you still need a place to start, so let's look at some resources.
This video of Rick Klau from Google Ventures explaining OKRs is one of the most widely shared resources on the topic. It's good, but I struggled on knowing what to do with it when I first watched it. Still, its worth watching if you're interested in OKRs.
Justin, my manager at the time, recommended reading Radical Focus, which in hindsight is probably the best longform introduction to OKRs that I can recommend. I was expecting to have a magical OKR scoring spreadsheet at the end of it, which it didn't have, so I still struggled a bit on exactly what steps I needed to take to get them implemented. The foundation it provided, however, was solid.
Finding our Why
As this was our first time trying this brand new process, we had lots of fits and starts. I stumbled a lot as I tried to figure out what OKRs we should choose and how they should be structured. I had lots and lots of meetings that spanned two different managers (we moved from being under product to being under marketing) to get things finalized.
It was ugly. There was a lot of banging my head against the desk trying to figure out what to put down on paper. And then, when I had something that I thought was clear, it might not be clear when I talked it over with my manager. Or, if it was clear, it might not have been aligned with company goals, so it didn't make sense to work on it. Back to square one.
We even had one meeting with just Mercer and me where we spent pretty much an entire day on a Zoom call hammering them out. (If you ever find yourself banging your head on your desk, I highly recommend getting in a room with someone smart that you trust and asking them to think through the problem with you—it works wonders at unblocking your brain). Shortly after this meeting, we had something that my manager gave a thumbs up to and we were able to move forward and present the OKRs to the team.
An OKR Example
Here's a real-world example we came up with on the support team at Trello. Around the time we were crafting OKRs, we had a rough period where we weren't very responsive during a product outage. It took too long to post a status post, we were way too slow to respond on social media, and we weren't very coordinated in tackling the email inbox. Yuck. This seemed like a good thing to put into our OKRs. I don't remember the exact KRs we chose, but this should give you an idea:
Objective: Slay outages
Key Results:
- Support Team is aware of an outage within 30 seconds of it occurring
- Status Post up within 5 minutes of an outage.
- Average response time to tweets during an outage is <1 minute.
Let's look at the Objective first: "Slay outages." Without even getting into the key results, you should already have an idea of the type of work you're going to be doing. While no business wants an outage, if it does happen, you want to be able to look at your responsiveness and say "we slayed it." Craft your objectives so that they're motivational and give you a deep sense of why you're doing what you're doing.
Notice how it doesn't say "99.94% or better uptime for our product." That's because that's not an objective. That's a key result. To illustrate my point, you could say: "If we achieve 99.94% or better uptime for our product, we'll know that we've moved the needle on slaying outages." (This actually would have been a great KR for this kind of Objective, but as the KRs were focused on just the support team, it didn't quite fit since we didn't have control over product uptime). We'll dive into a KR example in a bit.
Divvying up the Work
When Mercer and I shared the OKRs with the Trello support team, we tried not to be too prescriptive in what people should work on. For the most part, we let individual team members choose what objective(s) they wanted to focus on and what work they would do which would lead to achieving the key results, which in turn would signify whether or not the objective was achieved.
Notice how none of the key results tell you what to do. They only tell you what to measure. They're also bold and challenging. When compared to the status quo, your KRs should be slightly scary. This forces you to be creative when thinking of how to achieve the key results.
OKRs leave a ton of freedom in how to solve a problem. By only defining the results, you leave it up to the people actually doing the work to figure out the best way to achieve those results. (Arguably—and this is the approach FullStory takes—you might also leave the defining of the Key Results up to the individual team members since they're closer to understanding what measurable results they may be able to achieve. Regardless of whether your leadership defines KRs or individual contributors define them, the actual work to achieve the KRs should be left up to the actual people doing the work).
Exploring How to Achieve a Key Result
Let's look at just one of the Key Results in depth: "Status Post up within 5 minutes of an outage". This one is tricky. During downtime, the engineers are focused on getting this back up and aren't focused on updating the status blog.
What if we had defined our "KR" as "Work with engineers to improve process for updating the status page"? That might have led to some shiny new Google docs, but how would we have known if we had achieved the objective of slaying outages? Just having docs doesn't mean people are following them.
Rather than iterate on how to interrupt the engineers to tell them to update the status page, we focused on creative ways the status blog could be updated. By switching from Tumblr to Statuspage, we were able to take advantage of an integration that automatically posted to the status page if Trello went down. Since that integration ran every minute, we were virtually guaranteed to hit our 5 minute goal if Trello went down. And we crafted a handful of common status page templates so the support team could update the status page with minimal help from the engineers. Win!
This is the magic of KRs. By defining what to measure and not what to do, we could get creative in our solution, ultimately leading to a better solution.
Scoring OKRs
Here's where my story with Trello diverges a little bit. Because I left Trello in September of 2016, I wasn't around to see the OKRs through, which means I wasn't around to be a part of their scoring. So for this part, I'll offer one perspective of how you might approach scoring OKRs.
Don't just do the work to achieve OKRs. Score them! It doesn't need to be complicated. Score it from 0 to 1 where 0 is total failure and 1 is "totally crushed it." A .7 should feel like you really moved the needle forward on a big, challenging problem. If you get all 1s, you're sandbagging.
One quick point about scoring: scoring OKRs is usually not about measuring individual performance, so be careful in trying to use it as a personal performance proxy.
A Satisfying End
Earlier this summer, Mercer messaged me to let me know they had scored the OKRs and reviewed them internally with her manager. (Remember, we had defined them for an entire year. Most companies implementing OKRs pick a much shorter time frame). Having been away from Trello for ~9 months, I had completely forgotten about them! I was blown away by how much Mercer and the support team had accomplished in that time period.
Think about how much had changed with the Trello support team: they changed managers when I left, changed companies when they were acquired, and their manager went on maternity leave at the end of the calendar year. Would they have been able to ratchet forward so much with so much change if they hadn't had a clear sense of why laid out in the OKRs for the year? It's possible, but it's much less likely. Having clearly defined OKRs provided structure for the team to know they were doing meaningful work that would move the business forward. (At least, I assume that's what happened—I wasn't around to witness it. I only heard about the amazing results when they were done!)
Pushing Through Ambiguity
I can't stress enough that so much of the work in getting started with OKRs was working through the discomfort of not having the slightest clue what I was doing. I leaned heavily on my managers, Mercer, and conversations with others on the Trello support team to try to find clarity and a path forward. It was messy, sometimes stressful, and a ton of hard work. But it was worth it.
If you're trying to implement OKRs for your team or organization for the first time, push through the ambiguity. Keep iterating until you're comfortable with the process. It's worth it.
FullStory Week 1
Week 1 at FullStory is in the books. Here are some sundry thoughts about the first week in the new gig.
Commuting
Commuting in Atlanta is awful. There's really no way for it not to suck. That being said, I've been able to shift my expectations a bit and just accept that driving over an hour one way isn't great but let's make the most of it.
The first couple days, I tried working from home starting really early, then getting to the office a bit later and leaving early to try to avoid traffic. The last two days I tried to simply work two complete days in the office at more or less normal hours, traffic be damned.
I actually preferred the latter two days. I was in the office longer and so could settle in a bit more. Perhaps it was just because I had settled into work more by the end of the week, but either way, I think I might prefer just getting to the office.
To make the most of the drive, I'm listening to a lot of podcasts. I'll probably try audiobooks pretty soon since I'm in the car for 10 hours per week.
I should add that FullStory is moving to a new office soon-ish which should have a nice positive effect on my commute. It will be about 10 minutes shorter in each direction and also have a shower. A shower? Yes, that's important. It means I can go from the gym straight to work, which will add somewhere between 30-60 minutes to my day while still allowing me to exercise.
Chrome Pixel
FullStory uses Chrome Pixels (Chromebooks) whenever they can. They challenged me to try to make it work but promised I could get a Macbook if I really was blocked. I really struggled for the first few days, but by the end of the week I started to warm up to it.
This might sound dumb, but I almost threw in the towel because I couldn't settle into a workflow to quickly take and post screenshots. I still haven't completely nailed it, but I'm a lot further along than I was at the beginning of the week.
As far as a piece of hardware goes, the Chrome Pixel is really quite amazing. It's blazingly fast and gets the basics of a solid keyboard and trackpad, which is crucial to actually wanting to use the machine. It also has a touchscreen.
I didn't really settle in until I got a Microsoft Sculpt keyboard and external monitor, which made a big difference in how productive I felt. The keyboard layout for the Sculpt keyboard on the Pixel, while not perfect, is surprisingly workable.
One of my original blockers with the Pixel was how I was going to write code. Most of my code falls into the category of "hacky script to apply leverage", so I really don't need much more than a terminal, text editor, and Python, but that's not included on a machine where everything is a web app. (Did I mention that everything on a Chromebook is a web app?)
To work around the Pixel limitations around writing code, I started a trial of Cloud9. I'm oversimplifying, but it's basically a development environment in the cloud. You get a great web text editor, an integrated terminal window, and the ability to store data "locally" in the cloud workspace. Aside from having to ramp up to a new way of working, I really can't find a fault with it so far.
The company
The company and people are great. It was a struggle to feel unproductive as I'm getting started, but by the end of the week I had a clear direction for where I want to go. That's an amazing feeling.
There's so much more that I want to write, but I also want to be done with this blog post, so that's all for now.
Goodbye Trello, Hello FullStory
Earlier today I shared with my coworkers that I'll be leaving Trello and joining FullStory here in Atlanta. Here's what I shared (edited slightly to keep specific names private):
Trellists,
I’m leaving Trello on Friday, September 9th to work for FullStory, a local tech company here in Atlanta. I’ll share more about that in a bit, but first, story time!
When I joined Fog Creek in 2011, I took a big bet—personally and professionally—that working as a support engineer for a first rate software shop would accelerate my career faster than working as a developer at HoneyBaked Ham (yes, HoneyBaked Ham). Even though my wife had just had Sophia a few months prior, we decided to move the family from the suburbs of Atlanta to New York, settling in Astoria (at the time, there were only two other Creekers with kids). We were terrified to move away from family but excited by the opportunity for me to work at Fog Creek.
Needless to say, I’m really glad I took that bet. For the past five years, I’ve worked with the most amazing people and have grown professionally in ways that I could never have imagined. The best thing about working here has always been the people. Perks are great, but nothing compares to working with amazing people. Fog Creek and Trello have truly given me a new baseline for what to expect from companies I plan to work for.
The people on the support team are the reason that you can trust that our customers receive exceptional support. Check out the #support-praise channel in Slack and you’ll understand why the future is bright and great in their hands.
One of my long term personal goals has been to work for a great tech company here in Atlanta. It was an incredibly difficult decision, but when an opportunity presented itself to work at FullStory, I decided this was the best move for me and my family.
Thanks to Fog Creek and Trello for taking a bet on me and to everyone here for making Trello such an amazing place to work. Always feel free to reach out to me if you need anything at all.
All the best,
Ben McCormack
I'd like to use this post to add a little more color as to why I'm making this change. This is going to be a rambling post. It's as much for me as it is for the reader interested in my career machinations.
When I started at Fog Creek in 2011, it was actually the second time I had applied for the Support Engineer role. I first heard of the role in late 2009, applied, took a trip to NYC, didn't get offered a job, and then went and found a job as a junior developer at HoneyBaked Ham. That's a fun story in and of itself and you can read about it here: Finding a Job the Unconventional Way.
The TL;DR of how I ended up back at Fog Creek is that they reached back out in late 2010, and after getting an offer similar to what I was making (more money, but had to move to NYC), I accepted because I wanted to work for a great company. I can't overstate this point. It's better to pinch hit for a major league team than to bat cleanup for a AA team. When you work for a great company, it completely changes how you think businesses should be run and how you should approach your career. If you don't work for a great company, do whatever you can to change that. It will change your world.
Let's skip all the way to the very recent past. If you want details of what happened at Fog Creek and Trello, my LinkedIn Profile is a good place to start (and don't forget I went remote and moved back to Atlanta in 2014). At this point in the very recent past, I've built out and managed the support team at Trello, we've set some goals for where we want to be by summer of 2017, and things are looking optimistic.
Enter FullStory.
It's at this point that I receive an offer from FullStory, a local tech company here in Atlanta. Do I stay with what I know or do I pursue this new opportunity?
This time, it wasn't just about working for a great company. Trello and FullStory are both great companies. Deciding to go with FullStory came down to two things: seeing my coworkers everyday and applying leverage. There's also a bonus that's all about empathy.
One of my long term goals has been to work for a local tech company in Atlanta. Don't get me wrong, working remotely for Trello has been great (and not commuting is awesome), but I would be lying if I said I don't miss seeing my coworkers every day. There's a lot of communication that happens outside of Slack and video chat when you see people in person, and I've been missing out on that. Working at FullStory offers an opportunity to be physically present with my coworkers again, something I'm not going to get at Trello without moving back to New York (and now that we have three kids, that's out of the picture).
The second thing is applying leverage. At Trello, I've been figuring out how to manage a support team each step of the way. It's been a rewarding process, but also a lot of trial and error as I figure out what works and what doesn't. Each time the team grows and takes on new challenges, I have to put one foot forward and hope I land on solid ground. At FullStory, I have the opportunity to take what I've learned at Trello and Fog Creek and apply it in a smaller setting. Put another way, my experience and skill set in support is likely going to have more leverage at FullStory than it is at Trello. That's exciting because it means my work should have a greater impact.
As a bonus, I'll share two more things about FullStory that I'm excited about from a product and culture perspective. Their product is all about empathy. When you watch how customers use (and misuse) your product, it generates a tremendous amount of empathy and a desire to make the product better. Their culture is also all about empathy. This blog post is a good place to start. Everyone at the company does support, but they also have a position called Hugger—yes, that's my new title; yes, I've shared a contrary opinion about "creative" titles in the past. Enjoy the dissonance— which is all about empathizing with the customer to improve customer experience. That resonates strongly with me.
Although I'm leaving Trello, I'm excited for the future of the support team there. They have great leadership in place and an enthusiastic team with a ton of potential energy to solve new and interesting problems for Trello's customers. My only regret is that I won't be there to participate in their success.
In a few weeks, I'll enter the doors (real, physical doors!) at FullStory and begin a new journey working in support for a great company. I'm excited about what the future will bring.
Values, Process, Automation
A year or so after I joined the Trello team, I was catching up with my former boss, Rich Armstrong, about how things were going in support at Fog Creek. He was surprised that the support team—which he no longer managed—was thinking about ditching the random assignment script that he had implemented years earlier. He realized that the why behind the script had become divorced from the implementation, making the process vulnerable to being removed or replaced.
If you work in the startup space, you probably have automated processes all over the place. Awesome! This article is about how values, process, and automation play together; how to document them; and why they’re useful when processes change.
Let’s start with why process is important.
Process takes understood values and turns them into a repeatable form. The natural evolution of process is automation. Put another way:
Values provide constraints for how to operate
Process reinforces and embeds values
Automation deeply embeds processes, and thus values, within the organization.
Thus, when we automate processes, we embed values into the everyday work of our team. Let’s take a look at an example first and then look at the importance of documenting our values, process, and automation.
Example: Random Assignment Script
I’m going to use the random assignment script as an example. The script assigns tickets from the support inbox to the various members of the support team as they come in. Let’s say you have 100 tickets and 4 members on your team. The script will assign 25 tickets to each person. Contrast this to the way most support teams operate, in which each team member is simply expected to be "in" the queue, usually starting in a shared "unassigned" folder and working oldest to newest.
Let’s look at the values, process, and automation behind the script. Since a lot of automated processes exist in the wild without documented values, we’ll start with automation and move backward:
Automation: The automation is a Python script that runs every 15 minutes to distribute cases. No one has to think about it. It just happens.
Process: The process that is being reinforced is dispassionately distributing cases as they come in, based on a specified load. If a human were doing this task, he or she would triage each case and round robin the cases to the various members on the team.
Values: There are multiple values, so let's list them out:
No Cherry Picking. Cherry picking, that is, eyeballing the support queue and picking the next case you work on, costs extra mental energy in having to decide what to work on.
Equal share of knowledge gain. Some agents are faster than others, so left to their own devices, they might work on more cases than other agents. While that's not in itself a bad thing, the fewer cases the other agents see, the less overall knowledge those agents will have about support and the product. It's not just about fairness (though that applies)—it's also about making sure everyone gets an even share of the knowledge that comes from doing support.
Distributed knowledge. In addition to the volume of knowledge, you also ensure that each agent gets a wide variety of exposure to the product and support. E.g. no one ends up with all the Windows Phone cases. You reduce the need for specialists (which aren't bad, but can unnecessarily affect your "bus factor" if you don’t need them).
Continuity of care. By assigning a case to an agent as it comes in, we ensure that the same agent is going to care for the customer throughout the life of the ticket. This avoids the customer feeling "passed off" and helps a single agent deliver a ticket to full resolution.
Now we know why the random assignment script exists. Now let’s look at how and why to write it all down.
Write it Down
In the case of random assignment, Rich had documented the underlying values years earlier on the Fog Creek blog, but the current support team didn’t know about it. When automating processes, document the values in a place where future custodians of the process can easily find them. Here’s how we do that on the support team at Trello.
We have a Trello board set up to document the automated processes. On the left, we have "possible ideas for improvement", which is everything we might work on if we had the time and it made sense. On the right, is everything that’s already in production, organized by where it lives.
When specing out possible ideas for improvement, we document the values, process, and automation first so we know why it makes sense to do it. Then, when it goes into production, we can look back and see why we did it.
A good next step—which I don’t believe we’re doing—would be to link back to the values from somewhere within the source code or wherever the automation piece happens to be implemented. That way, if for some reason the person reading the code doesn’t know about the underlying values for why it’s there, they don’t have to look far to find them.
Changing Process: Look at the Values
Why is all of this stuff important? If you’re a small startup and you can keep all of the values in your head, this may be overkill. You can do the mental math in your head without thinking about too much. "Let’s just change it" may suffice. If you’re getting beyond the “small startup” phase or if you’re not sure if a process should change, this part is for you.
If your values are documented, when someone suggests a change to process, ask what values underlie that process. Take the values from your current process and the values from the suggested process and prioritize them. Which are most important? If the values from the suggested process win out, you should go with the suggested process or modify the current process to prop up those values, assuming of course it makes sense to spend the time to do so (more on that in the next section).
For example, if the support team adds a new value of "provide a near instant response to paying customers," and if that’s more important than every other value associated with the current automation, the random assignment script would either need to change or make way for a different process that supports faster response times to paying customers.
Bonus! The Process Part
If you’re like me and you like automating things, it can be really easy to jump all the way to the end and start writing code. The automation part is probably easy to write down ("this code does this thing") and perhaps even the values are easy to write down (“this is why this hacky script is important”). It may be just as easy to skip over the understanding the un-automated process, but don’t do it. There’s a huge benefit to thinking about the process part by itself.
Try to answer the question: How would a human manually do this process? There are several reasons to use this as your starting point.
First, for a project that requires a lot of time writing code, you might want to test it manually before investing the time to automate it. When doing the process manually, are you getting the value of it that you expected? If not, it doesn’t make sense to automate it.
Second, if you’re trying to decide if it’s worth spending your time (and therefore the company’s money) automating a process, being able to understand the cost from a manual perspective let’s you understand when you’ll get a return on the company’s investment. E.g. if it costs $75/day to do it manually and $750 to automate it so the subsequent process costs virtually $0 per day, you’ll get a return on your investment after 10 days and save around $20,000 per year going forward. If that’s more valuable than other potential ways to spend your time, it’s time to start coding.
There are of course processes where you would say "if we had to do this manually, we’d never do it," but you can still do some back-of-napkin math to help you weigh whether it’s a good decision from a business perspective.
Et Cetera
On the support team at Trello, we don’t document the values behind every single thing we automate. We have a ton of stuff in Zapier that’s there "because it just makes sense", such as the language detection script. Nobody has to think very hard why it’s useful and the cost to implement and maintain is very low. For philosophical processes such as “how to automate the support queue,” writing down the values is more important.
I can’t remember who at SupConf (May 2016, San Francisco) suggested I write this post, but thanks for the suggestion!
Thanks also to Rich who planted the original seed for this post in my head (and appologies if I misremembered any part of our conversation).
Kate Heddleston’s blog post about The Null Process is excellent and a good counter argument to the comment "We don't want to introduce any unnecessary process."
Finding a New Pair of Work Headphones
"Work headphones" is a thing. After my MacBook Pro, there isn't a piece of hardware that I use more than my headphones. As a manager, I'm in a lot of meetings, and being remote, those meetings take place online. My headphones need to be great.
After much experimentation, I settled on the Sennheiser Game ONE headset connected to the Sound Blaster Omni Surround 5.1 external sound card. I tried a couple of different headsets that I didn't care for, including the Bose Quietcomfort 35, and I'll share my thinking there. First, how I got into this mess.
Fool me twice, time to find a new headset
I went searching for a new headset after the mic broke on my second pair of Sennheiser U320 headphones. This was the second pair that had broken in as many years, so it was clear I needed to go in a different direction, but there is a lot about the U320s I like.
First, I like the USB connection. I use this headset exclusively for work, so I like the super long cord and the USB connection. The USB connection frees up the headphone port, which I use to connect my speakers. That's nice—no unplugging a cord and plugging in another cord to change audio. Because I'm fancy, I have an Alfred keyword for headphones and speakers which automatically changes my audio settings on my Mac.
The U320s also has a "side tone" that lets you monitor your own voice. As you speak through the microphone, you ear yourself a little bit in the headset. Sometimes headsets make you feel a little disjointed and this helps with that.
In my quest, I needed two things: USB and the ability to hear myself while I'm talking.
Trouble Finding the Ideal Work Headphones
The challenge with finding great work headphones is that the market doesn't quite exist yet. On one hand, you have "office headsets" and on the other hand you have "gaming headsets." The office headsets belong in a cubicle and the gaming headsets come packaged with a Red Bull. There's nothing in between. I'm leaving out the entire category of "plain old headphones" because I'm assuming you want something with a built-in mic.
The thing is, you want something in between. You want a headset that provides a great conference call experience, but you also want a headset that's great for listening to music. And you want it to look like something you'd wear to work, not to a LAN party. For me, I also want to be able to plug it in via USB and I also want a boom mic. (I know there are plenty of people who use their webcam mic or their earbuds, but I think those are pretty crappy. If I'm spending so much time on video calls, I want to give the person I'm meeting the best possible audio experience. The mic can't suck).
First Attempt
I almost nailed it on my first attempt. I asked our IT department to order a pair of the Sennheiser Game ONE headphones and a cheap external sound card (to convert the mic and headphone cords to USB). Everything came in and I started testing right away. (Spoiler Alert: the cheap external sound card sucked and was the only problem).
When I plugged everything in, the headphone part worked, but the microphone did not. I thought it might be the cable but I wasn't sure. What was really frustrating was that I had no great way to isolate which part was the problem. I finally realized the headset was fine when I booted up a five year old Windows laptop. Ugh. Stupid external sound card.
The Sennheiser ONEs were okay, but I wasn't an instant fan. They were a little tight on my head and the mic was very hot and seemed to easily clip. I thought I might be able to do better.
Second Attempt, a Trip to Fry's
I'd already spent more time than I wanted testing a broken setup, so I decided to head to Fry's and pick out a few different pairs of headphones to try out. I also wanted to see about getting an external sound card to solve the USB problem.
I picked up the following headsets:
I also grabbed the Sound Blaster Omni Surround 5.1 external sound card to solve the USB problem. This ended up being a great find and solved my USB problem and even some of my mic problems.
The HyperX Cloud Revolver is pretty crappy. The microphone is much worse than either of the Sennheisers and the audio is just okay. The boom mic detaches but doesn't flip up, which I find annoying. Wirecutter has one of the HyperX headsets as the best choice for "most people", but honestly, if you're buying a gaming headset, get something good.
As a pair of headphones, the Bose Quietcomfort 35s are phenomenal. The noise canceling is amazing and the Bluetooth works perfectly. Oh, and they're so comfortable. They were easily the most comfortable pair of headphones I tested. I wanted to love these headphones so bad, but I couldn't do it, for two reasons. First, the mic isn't great. It's got a decent mic on the front of the headset, but it isn't any better than what you'd get from your webcam. It might even be worse. Second, I could not get used to talking in a meeting with the sound isolating noise cancellation. It was too jarring. I felt like I had ear plugs in or was swimming in my own head. I had to stop halfway through a meeting and switch headphones.
The Sennheiser Game ZERO headphones are great in so many ways, but they have the same closed back design as the Bose and so I ran into the same issues while talking during meetings. It felt stuffy and hard to process my own voice. What's frustrating is that there are several things I like better about the Game ZERO over the Game ONE: 1) The headphones lie flat. This is a really nice touch and makes the headphones easier to put away. 2) The mic is less sensitive and doesn't seem to clip as often as the Game ONE. 3) It comes with a case! While I would rarely use it, it's nice to have a case to stow your headphones in while traveling.
Why I Prefer the Sennheiser Game ONE Headset
In contrast to the closed back design of the Game ZERO headset, the Sennheiser Game ONE headset has an open back design. This is super important because it means I can hear my surroundings—and my own voice—while talking on a call. Remember the side tone that I liked about the U320s? The open back design solves the same problem. There's absolutely no feeling of "being inside my head" while talking with the Game ONE headphones on. That's what ultimately won me over, even though I liked the overall design of the Game ZERO headset better and loved the convenience and quality of the Bose.
I've spent a week with the Game ONE headset and so far it's working great. It's started to loosen up a bit on my head, which is nice because it was tight at first. I've even updated my Alfred script to know about the new headphones.
Doing the research was a pain, but hopefully I'm good for another year.
Coda: If you're thinking about getting the Game ONE headset for work, you should also look at the Sennheiser PC 363d headset. From what I can tell, it's very similar to the Game ONE and has the same open back design. However it also includes its own external sound card, allowing you to plug the 1/8" inputs into the USB sound card without having to buy additional hardware.
Language Detection in Help Scout with Zapier
I used Zapier to automatically tag the emails that come into Help Scout. For example, if we get an email in German, it will automatically be tagged language-de
. In this post, I'm going to write about why this is important at Trello, my love affair with Zapier, and how I went about setting it up. Skip to the end if you only care about the setup.
Why language detection is important for Trello
Trello is available in over 20 languages. When someone writes to support in their native language, we try to write back in their language. Sometimes we have someone on staff who can handle non-English emails. Most of the time we use Unbabel.
Because international support is so important, I've wanted to get more data on which languages we have coming into the inbox. I'm also interested in automatically routing cases to native speakers. That speeds up how fast we're able to get back to those customers.
I thought about manual tagging, but I avoid manual tagging whenever possible because it slows people down, so I save it for only the most critical things we want to track.
My love affair with Zapier
A while back, Zapier launched a Translate Zap. Since Zapier already had a Help Scout integration, this meant I might be able to detect the language and tag cases as they came in. Almost. It's a bit more complicated than that, but first I want to share why I love Zapier.
I love Zapier. For the uninitiated, Zapier is a service that connects apps together. For example, when a new card shows up in Trello with the word "Zanzibar" in it, copy the details of that Trello card to a Google Sheet. It's really easy to connect things together.
But the real reason I love Zapier is because it's such a solid platform. I can code—coding isn't the problem—the problem I always have is finding a place for that code to live. Servers. Version control. Uptime. Logging. Patches. Security. All of that stuff.
At Trello, we have two internal support boxes for scripting and automation. They're great. Mercer, one of the members of my team, even solved the language detection problem in one of our internal scripts, but because that script is so important for identifying our priority support customers, it got hung up in the testing phase and never shipped. That's not Mercer's fault. We have a complex infrastructure.
Zapier takes away the complexity of shipping code to solve tiny (or big) problems. It's a superb platform. I can connect services visually or I can write custom Python (or Javascript) code. You can test everything right in the browser. If it fails in production, you get logs for each task and can re-run the task right in the browser. This is all stuff you would want in a production system and Zapier just throws it in as part of the product.
If I can, I always choose to host my hacky solutions in Zapier.
</love-letter>
Setting up Zapier to tag cases in Help Scout
As I alluded to earlier, setting up Zapier to tag cases in Help Scout is almost automatic. You get a "New Conversation" trigger in Zapier, but you don't have a similar "Update Conversation" action. That means you have to write custom code that uses the Help Scout API. No worries. I'm going to give you the code!
When you first set up your Zap, your Trigger is going to be a Help Scout "New Conversation". We configure it to look at our "Trello Support" mailbox. PRO TIP: As you set up your Zap, be sure to test each phase separately. This will save you headaches from trying to test everything at once at the end.
In the screenshot above, I have a "Run Python" zap for the 2nd step. You can skip that. I'm just filtering out some cruft in our inbox unique to our setup.
Add an action for "Detect Language." Under "Edit Template" in the "Text" section, I'm passing in the Help Scout Subject and Preview on separate lines. I wish I could pass in the full first thread, but that's not available in the default trigger, so Subject and Preview it is.
Finally, the last Run Python Zap is where the real configuration lies. In the "Edit Template" section, you're going to need to set up inputs for id
, language
, and mailbox
, like so:
Here's the full code to paste into the code block. You'll need to replace the YOUR API KEY HERE
text with your API key from Help Scout.
import base64 from requests import Request, Session import json HELP_SCOUT_API_KEY = 'YOUR API KEY HERE' def query_helpscout(method, url, data=None, params=None): BASE_URL = "https://api.helpscout.net/v1/" auth = "Basic " + base64.b64encode(HELP_SCOUT_API_KEY + ":X") headers = {'Content-Type': 'application/json' , 'Accept' : 'application-json' , 'Authorization' : str(auth) , 'Accept-Encoding' : 'gzip, deflate' } s = Session() req = Request(method, BASE_URL + url, data=json.dumps(data), headers=headers, params=params ) prepped = s.prepare_request(req) resp = s.send(prepped ) return resp url = 'conversations/%s.json' % input.get('id') response = query_helpscout('GET', url) response.raise_for_status() # optional but good practice in case the call fails! conversation = response.json()['item'] data = {} data['tags'] = conversation['tags'] if conversation['tags'] else [] data['tags'].append('language-%s' % input.get('language')) response = query_helpscout('PUT', url, data) response.raise_for_status() return {'id': 1234, 'rawHTML': response.text} output = [{'id': 123, 'hello': 'world'}]
The return
and output
lines are from sample code and I didn't remove them, but they also don't matter. You're not doing anything with the output after this step.
If successful, every case that comes into Help Scout will automatically be tagged with the language in which it was written. Enjoy!
Reaction to "3 Ways to Amp Up Your Support Career"
Mercer Smith-Looper gave a great talk at SupConf in San Francisco this past week and just today she published a blog post version of her talk: 3 WAYS TO AMP UP YOUR SUPPORT CAREER; SUPCONF, MAY 2016. If you're getting started in support, go read it now. The high level points she's addressing are spot on. In this post, I'm going to take a different angle on some of the aspects in her post in attempt to further the discussion.
House Projects 1
We just bought a house and got moved in. This is the first time in our nearly seven years of marriage that we've been home owners. I've always enjoyed doing projects around the house, but there's something different about doing projects in a home that you own .
Yesterday after work and before bed, I knocked out several projects:
I turned the bonus room into my office, which previously had "hall and closet" doorknobs that didn't lock. I picked up a couple "bed and bath" doorknobs and installed them to prevent the small humans in my house from interrupting me during video calls.
At the back of our kitchen we have a set of stairs that lead up to the bonus room. We need to prevent Joshua from going up the stairs, so I added a gate with a door to replace the fixed gate we used to have at the base of the stairs. Since we don't have a proper mud room, I put up some coat hooks that were accessible to both kids and adults. Sophia should know exactly where to put her jacket and backpack when she gets home from school. I'm not entirely sold on the shelf for shoes, but that's what we're going with for now.
This home was built in 1986 and has sliding pocket doors between the kitchen and family room and between the master bed and bath. The doors had been sort of hopping along the rails, so I applied some Silicone WD-40, which helped a bunch. The silicone part is key. You don't want to use oil-based lubricants around your carpet or fabric (e.g. for a drapery traverse rod) since it can stain. Silicone spray is a touch more expensive but worth it.
After finishing my projects, I had some cake!
Visiting the Missionaries of the Poor in Jamaica
My father-in-law has been traveling to Jamaica to work with the Missionaries of the Poor in Jamaica since 1999. When I married Emily in 2009, he invited me to go on a trip with him, and I promised I would go when I had the vacation time. It took five and half years, but I finally had the opportunity to make my way down to Jamaica on January 6th for six days of service to the poorest of the poor. Before I begin, I should start by sharing a bit of the history behind the Missionaries of the Poor.
The Missionaries of the Poor was founded in 1981 (originally called the Brothers of the Poor) by Fr. Richard Ho Lung to serve the poorest of the poor in Jamaica. Fr. Ho Lung was born and raised in Jamaica to Chinese immigrants. He became a Jesuit priest, but later left the Jesuits to focus on serving the poor. In the late 70s, Fr. Ho Lung, along with two companions, Brian and Hayden (now both priests of M.O.P.), would regularly visit the abandoned and disabled in the government-run Eventide home, a hell hole where there were more rats taking residence than humans, and the men and women who did live there had to deal with the overbearing smell of feces and urine. These are the people Fr. Ho Lung and his companions sought to serve.
As the Missionaries of the Poor grew, they started their own "centers" to house the poor of Jamaica. They now run about half a dozen centers in Kingston alone. When I use the term "poorest of the poor," these are people who often have no family, no one to care for them, are often developmentally or physically disabled, and sometimes found literally abandoned in the gutters of the streets of Kingston. In the centers, the brothers, local volunteers, as well as volunteers from abroad, serve the residents by helping them bathe, cleaning their living quarters, changing their diapers, feeding them, singing with them, praying with them, and loving them in everything they do.
Meeting the residents of the centers was an immensely joy-filled experience. My first day I visited Bethlehem House, a residence for babies and young children. Many of the residents have severe symptoms of cerebral palsy and are confined to cribs in contorted positions. This was an emotionally challenging first assignment, but I very quickly learned to adopt an attitude of simply "I am here to serve" and did my best to do what the women asked me to do in terms of changing sheets, clothing the children after their bath, and feeding them at lunch time. In the afternoon, we got to take some of the boys outside to play, which was so much fun! Some of the boys just sat in their strollers and smiled, while others were able to walk around and get their feet in the grass.
I also spent time at Lord's Place and Jacob's Well, both of which are residences for older women. The residents at Jacob's Well were a bit more independent. One of the amazing things to witness was how they lived in community with one another. At one moment, everyone would seem to be sitting down, doing their own thing, and the next moment, one resident would be pushing another wheelchair-bound resident to the pavilion for prayer time. Some residents swept the floors and helped with dishes. Others needed to be fed their meals and relied more on the help of others.
What amazed me most about the women at Jacob's Well was the joy that they had. So many of them ran up to you when you came in and were so happy to have visitors join them. While they didn't have much in terms of possessions, they truly had so many of their needs taken care of that it wouldn't be an exaggeration to call them rich in many different aspects. They had community, work if they were able, shelter, food, water, and all of the care to live a peaceful life.
We ended up going back to Jacob's Well for a second day, which totally transformed the experience. Whereas on the first day we spent a lot of time getting acquainted and meeting the residents, on the second day, we already knew the women, so we could jump right in and start spending time with them. We had so much fun that second day and I already miss not being able to go back and see them.
As amazing as it was to spend time with and get to know the residents of the centers, that's only half of what I experienced during my time in Jamaica. The Missionaries of the Poor are not a purely humanitarian organization. They are a group of men devoted to Jesus Christ and His Church. They pray regularly. Their service to the poor is how they live out the Gospel. Everything they do comes back to their service of Jesus.
Each day, we rose very early (normally it would be 5:30, but they were on Christmas vacation for three days, so we rose at 6:30), and have mass, morning prayer, and adoration. This lasts about an hour and a half. At noon, while working in the centers, they pray daytime prayer together, and back in the residences in the evening they pray evening and night prayer together as well as the rosary. It's about three to four hours of prayer every day.
For me, entering into a disciplined life of communal prayer was a breath of fresh air. I had been a seminarian (studying to be a priest) for three years back in college, so this type of life was very familiar to me. I was surprised by just how familiar it was and how much I craved to be a part of that life. I was thankful that I left my iPhone at home and I looked forward to every time the bell rang to call us to prayer with the brothers.
I know for me, witnessing the disciplined lives of the brothers was a call to accountability, not just in prayer, but also in the other domestic activities to which I'm a part. In the monasteries, the brothers all pitch in to get things done. They take pride in having a clean home and enjoy their meals together. They put a lot of energy into simply having a nice place to live. I think of the vivid colors on the walls and how they have such beautiful landscaping, even in the midst of the ghettos of Kingston. That made a tremendous impact on me and called me into greater service in my own home and family life. I want my home to be a place that I'm excited to live in.
On Saturday, we only worked in the centers for half a day. While we were going to spend the afternoon visiting one of the local tourist trap markets in Kingston, at the last minute my group was offered the opportunity to visit Mt. Tabor monastery, which sits high on the hills above Kingston. We gladly accepted and this ended up being one of the highlights of my trip.
The monastery is almost self sustaining. They have pigs, cattle, chickens, rabbits, as well as a full garden of fruits and vegetables. It's noticeably cooler up there and the views of the surrounding hills are immensely peaceful.
The big highlight of the trip of the mountain was being able to meet Fr. Ho Lung in person, who was giving a retreat at the time to several of the brothers at the monastery. I had been reading a recent biography of Fr. Ho Lung, Candles in the Dark, and had become enamored with the story of how much he was able to accomplish through his zeal of serving the Lord and serving the poor. I asked him what his hopes were for the Missionaries of the Poor, and he said that he wants the Church to continue to grow, and to grow as a church for the poor (being "a church for the poor" is a common theme heard from Pope Francis) . When I told him how encouraged I was by the brothers and their regular life of prayer, he shared that we all need to be in constant renewal of our relationship with the Lord, even himself! It's hard to imagine that a nearly eighty-year-old man who is so firmly rooted in his faith would need to reevaluate his relationship to Jesus.
One of the things I thought about on my last day in Jamaica was just how much I realized that life is not disposable. So many of the people that the brothers care for had literally been thrown away in their previous life. Thrown away! When you get to know the residents, you realize just how much they have to give to you, the volunteer who came to serve them. You show up thinking you are going to serve them, but you realize when you leave that it is really you who has received the lion's share of the gift. You yearn to simplify your life and treasure the relationships with those near to you.
I think too, being a part of the disciplined lives of the brothers, which included regular prayer, service, and the removal of so many life's distractions, was an invitation to simplify and find discipline in my own life upon returning home. I most certainly will be back to serve with the Missionaries of the Poor in Jamaica.
Click the pictures in the gallery below to see more of my trip to Jamaica.
Top Photo: Sunset from the Mt. Tabor Monastery, Kingston, Jamaica. All images copyright Ben McCormack. All rights reserved.
My, don't you look familiar
I was recently shopping for a USB 3.0 Hub on Amazon and came across these two items that look eerily similar. For example, compare this Sabrent USB Hub to this ORICO USB Hub:
Support Specialist, What's in a Name?
A couple weeks ago, when we were getting ready to hire additional members for our support team at Trello, I realized I had to name the role. Our other positions at trello.com/jobs are all nouns a potential candidate can identify with, e.g. "Designer" or "Android Developer," so putting just "Support" doesn't fit, and "Support Team Member" feels clunky and awkward. What to do?
Plan Your Holiday Vacation with Trello
Getting ready for the holidays can be a stressful time of the year—travelling, buying gifts, meeting with friends, drinking eggnog—but it need not be. Emily and I have been using Trello to plan our upcoming Christmas vacation to see family. Hear why it’s been great for Emily (gratuitous toddler videos included):
Lift Heavier Things By Tracking Your Progress
If you work out on a regular basis, you need to track your progress. I remember learning to track bench press with a printed Excel sheet in a high school weight lifting class. For some reason, perhaps because CrossFit has so much variety, it took me a while to get serious about tracking my workouts.
When I started CrossFit in February of 2011, I would get into a routine where I would show up for a workout, try to remember what weight I lifted the last time, and add some weight for the current session. This worked out really well when I was beginning, mainly because I was jumping up so much in weight each time as I started to get in shape.
After a while, I noticed that my rate of improvement was diminishing and even leveling off in some areas. The problem—which I only noticed in hindsight—was mainly due to my not being able to remember how far I had pushed myself in a previous workout. Starting at a computer screen at 5:30 AM, I wasn’t doing much research on previous weights before heading into the gym.
So earlier this year, I started tracking my workouts in a couple of Google Docs.
Apart from actually showing up for the workouts, tracking my workouts has been the single biggest contributor to my improvements this year.
I started two spreadsheets in Google Docs, Named WODs and Ben’s Lifts. I check them accordingly before heading to the gym. I even have them saved as favorites on my smartphone in case I need to look them up while I’m there. Here’s a screenshot of my entries for Deadlift this year:
It’s not too complicated. All you really need is the movement, date, rep scheme, and weight, but I added my 1RM, 3RM, 5RM, and a Notes fields, which turns out to be quite helpful. You notice on June 29, it had been over three months since my last 5x5 Deadlift. I was quickly able to see that I had struggled at 275 for my last 5x5 but thought I could push myself to 285. If I hadn’t been tracking my progress, I probably would have guessed based on my last 5x3 and probably would have ended up at about 265.
There are a ton of different ways to track your workouts. A lot of CrossFit blogs encourage you to post your scores to the comments (I really like this, mainly because it encourages a positive community, but it can be hard to look up old scores). Our box recently started using SocialWod to automatically track our white board. Again, I really like this for the community aspect.
Even with all of these great methods, I still recommend coming up with your own simple tracking system. It’s the best way (in addition to showing up) that I know of to help yourself improve at the gym.
Have any thoughts? I’d love to hear them in the comments below!
Building HTML Files from Markdown and using MarkdownPad
Each month, I deliver a webinar for two of our Fog Creek products, FogBugz and Kiln (speaking of, want to sign up for the webinars? Do so here: FogBugz, Kiln). To help me deliver a consistent and polished message, I’ll keep a script open in a browser window on a separate monitor. This ensures that I stay on track and mention all of the things I want to say.
To create the outline for my scripts, I use MarkdownPad on Windows, which allows me to edit my outlines using the Markdown syntax and end up with an HTML file that’s viewable in any browser.
I really like using MarkdownPad, but one of the slower parts of my workflow involves remembering to go to File > Export HTML for each of the 7 outline files that I have. I really wanted to be able to convert my Markdown files from the command line to make this faster. Since MarkdownPad doesn’t offer this command line functionality out of the box, I decided to try to add it myself. While I was there, I added some additional features to help me out while I’m doing a live webinar. Here’s a list of features I decided to implement.
- Generate HTML using Markdown source from the command line
- Use the same CSS stylesheet and other user settings that I use in MarkdownPad when I export HTML.
- Add the ability to be able to navigate header tags using the keyboard arrow keys
- Dynamically stylize certain key words so that they stand out. For example, if I type - KILN: in MarkdownPad, I want to be able to see - KILN: in the resulting HTML so that it stands out.
Here’s a quick screencast that shows the result of my work:
Let’s break down how I set this up.
Converting Markdown to HTML
The biggest challenge I faced, at least initially, was figuring out how to take Markdown source and convert it to HTML from the command line. I decided to try using MarkdownSharp, .NET Markdown transformation library that happens to be the same was what’s used within MarkdownPad. Since you can easily call .NET classes from PowerShell, I figured this would be a relatively simple implementation, which was indeed the case:
[sourcecode language="powershell"] PS C:\> Import-Module MarkdownSharp.dll PS C:\> $m = New-Object MarkdownSharp.Markdown PS C:\> $m.Transform("- This is a bullet point") <ul> <li>This is a bullet point</li> </ul> [/sourcecode]
The actual implementation was a bit trickier, but still relatively straightforward. I created a function in my Powershell Profile called Markdown-ToHtml. It will take either a file name or plain text and also lets you specify standard options for MarkdownSharp. Notice how I’m importing MarkdownSharp.dll, which is stored in a lib directory right within my Powershell profile. I think I built the file from source code, but feel free to grab my compiled version here (click the Download link on the right side of the page).
[sourcecode language="powershell"] Import-Module .\lib\MarkdownSharp.dll function Markdown-ToHtml($item, $AutoHyperlink = $False, $AutoNewLines = $False, $LinkEmails = $False, $EncodeProblemUrlCharacters = $False){ $mo = New-Object MarkdownSharp.MarkdownOptions $mo.AutoHyperlink = $AutoHyperlink $mo.AutoNewLines = $AutoNewLines $mo.LinkEmails = $LinkEmails $mo.EncodeProblemUrlCharacters = $EncodeProblemUrlCharacters $m = New-Object MarkdownSharp.Markdown($mo) $toTransform = "" if (($item.GetType().Name -eq "FileInfo") -or (Test-Path $item -ErrorAction SilentlyContinue)) { $toTransform = (Get-Content $item) $toTransform = [string]::join("`r`n",$toTransform) } elseif ($item.GetType().Name -eq "String") { $toTransform = $item } else { # I don't know what to do with this } return $m.Transform($toTransform) } [/sourcecode]
Once you have the Markdown-ToHtml file in your profile (or within your build script; either way is fine), the next step is to grab MarkdownPad’s settings so our generated HTML is consistent with what you see in MarkdownPad.
Find MarkdownPad’s Settings
MarkdownPad is a ClickOnce application, so it’s settings are stored in a user.config file in a seemingly random folder in the user’s AppData directory. Thankfully, we can use a little Powershell Magic to make sure we get the correct file:
[sourcecode language="powershell"] PS C:\> ls $env:APPDATA\..\Local\Apps\2.0 -r -include user.config | %{if(cat $_ | ss "MarkdownPad" -quiet){return $_;}} | select -first 1
Directory: C:\Users\benm\AppData\Local\Apps\2.0\Data\0CZN9Q11.JVM\MNOR0348.10M\mark..tion_12329825c85e214b_0001.0003_8873814a9315382c\Data\1.3.1.1
Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 6/18/2012 3:02 PM 10492 user.config [/sourcecode]
Reading the XML file is also pretty simple:
[sourcecode language="powershell"] #Get the MarkdownPad config file $configFile = ls $env:APPDATA\..\Local\Apps\2.0 -r -include user.config | %{if(cat $_ | ss "MarkdownPad" -quiet){return $_;}} | select -first 1 #Parse the config file as XML, pulling out appropriate values [xml]$doc = Get-Content $configFile $Css = $doc.SelectSingleNode("/configuration/userSettings/MarkdownPad.Properties.Settings/setting[@name='HTML_CustomStylesheetSource']").Value $AutoHyperlink = [System.Convert]::ToBoolean($doc.SelectSingleNode("/configuration/userSettings/MarkdownPad.Properties.Settings/setting[@name='Markdown_AutoHyperlink']").Value) $AutoNewLines = [System.Convert]::ToBoolean($doc.SelectSingleNode("/configuration/userSettings/MarkdownPad.Properties.Settings/setting[@name='Markdown_AutoNewLines']").Value) $LinkEmails = $False $EncodeProblemUrlCharacters = [System.Convert]::ToBoolean($doc.SelectSingleNode("/configuration/userSettings/MarkdownPad.Properties.Settings/setting[@name='Markdown_EncodeProblemUrlCharacters']").Value) [/sourcecode]
We’ll use these settings later when we put together the complete build script.
Navigate HTML Headers Using Arrow Keys
I wanted to be able to use the arrow keys to navigate my outline files during the live webinar. I decided to include jQuery in my generated files and I ended up finding a cool library called jQuery.ScrollTo, which is included in my build script.
To wire up the ScrollTo plugin to keyboard commands, I used the following script, called scrollToArrow.js:
[sourcecode language="javascript"] (function($){ $(window).keyup(function(e){ window.ixTag = window.ixTag || 0; tagsH = $('h1,h2,h3,h4,h5'); var key = e.which | e.keyCode; if(key === 37){ // 37 is left arrow window.ixTag = window.ixTag - 1 < 0 ? 0 : window.ixTag - 1 console.log('left'); } else if(key === 39){ // 39 is right arrow window.ixTag = window.ixTag + 1 >= tagsH.length ? tagsH.length - 1 : window.ixTag + 1 console.log('right'); } $.scrollTo($(tagsH[window.ixTag]),{duration:250}); }); })(jQuery); [/sourcecode]
It’s a bit hacky, but it does the job.
Dynamically Stylize Keywords
The next challenge was to add styling to the resulting HTML page for certain keywords so that they would jump out to me during the webinar.
I had originally solved this using a PowerShell script to modify the original Markdown file, but I decided to use some javascript instead so that the original Markdown file isn’t littered with <span> tags. Here’s what the addStyles.js file looks like:
[sourcecode language="javascript"] $(document).ready(function(){ var toMatch = /^(PP|FB|PS|VS|KILN|NP|EXPLORER|THG):/i; var matches = $('p,li').filter(function(){ return $(this).html().match(toMatch); });
$(matches).each(function() { var html = $(this).html(); console.log(html); var match = html.match(toMatch)[1]; var replaceWith = html.replace(toMatch, '<span class="' + match + '">' + match + '</span>:'); //console.log(replaceWith); $(this).html(replaceWith); }); }); [/sourcecode]
Bringing It All Together into A Build Script
I use all of the above elements—putting Markdown-ToHtml in my profile, parsing the user config, the javascript files—to put together a simple build script. In short, the script will look for all Markdown files (.md) in the directory and then output the HTML to a folder called outline-html. The javascript files and the exported CSS are also placed in outline-html. Here is build.ps1:
[sourcecode language="powershell"] #Get the MarkdownPad config file $configFile = ls $env:APPDATA\..\Local\Apps\2.0 -r -include user.config | %{if(cat $_ | ss "MarkdownPad" -quiet){return $_;}} | select -first 1 #Parse the config file as XML, pulling out appropriate values [xml]$doc = Get-Content $configFile $Css = $doc.SelectSingleNode("/configuration/userSettings/MarkdownPad.Properties.Settings/setting[@name='HTML_CustomStylesheetSource']").Value $AutoHyperlink = [System.Convert]::ToBoolean($doc.SelectSingleNode("/configuration/userSettings/MarkdownPad.Properties.Settings/setting[@name='Markdown_AutoHyperlink']").Value) $AutoNewLines = [System.Convert]::ToBoolean($doc.SelectSingleNode("/configuration/userSettings/MarkdownPad.Properties.Settings/setting[@name='Markdown_AutoNewLines']").Value) $LinkEmails = $False $EncodeProblemUrlCharacters = [System.Convert]::ToBoolean($doc.SelectSingleNode("/configuration/userSettings/MarkdownPad.Properties.Settings/setting[@name='Markdown_EncodeProblemUrlCharacters']").Value)
#put the CSS in its own file $Css | out-file .\outline-html\markdownStyle.css -encoding "UTF8"
#for each Markdown file in the directory: # 1. use MarkdownSharp to convert the markdown to the HTML body # 2. build the full HTML file, adding in CSS and javascript references in the header # 3. create the file in outline-html $files = ls *.md | %{$_.Name}
$files | foreach { $htmlBody = Markdown-ToHtml $_ -AutoHyperlink $AutoHyperlink -AutoNewLines $AutoNewLines -LinkEmails $LinkEmails -EncodeProblemUrlCharacters $EncodeProblemUrlCharacters $sb = New-Object System.Text.StringBuilder [void]$sb.AppendLine('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">') [void]$sb.AppendLine('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">') [void]$sb.AppendLine('<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>') [void]$sb.AppendLine('<script src="jquery.scrollTo.js"></script>') [void]$sb.AppendLine('<script src="scrollToArrows.js"></script>') [void]$sb.AppendLine('<script src="addStyles.js"></script>') [void]$sb.AppendLine('<link rel="stylesheet" type="text/css" href="markdownStyle.css">') [void]$sb.AppendLine('<head>') [void]$sb.AppendLine("<title>$_</title>") [void]$sb.AppendLine('<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />') [void]$sb.AppendLine('</head>') [void]$sb.AppendLine('<body>') [void]$sb.AppendLine($htmlBody) [void]$sb.AppendLine('</body>') $htmlFileName = (($_ -split ".md")[0]) + ".html" $sb.ToString() | out-file ".\outline-html\$htmlFileName" -Encoding "UTF8" } [/sourcecode]
Once you have build.ps1, you can run it from the command line using .\build.ps1, which will generate HTML files for all markdown files in the current directory.
cURL for Powershell
OK, so the title of this post is a bit lot misleading. I haven’t implemented cURL in Powershell, but I did want a simple way to simply do an HTTP GET against a URL and download its contents. With cURL, it’s as simple as:
$ curl http://isitchristmas.com
Easy! This will download the URL as a string that can be piped into another command. In Powershell, it’s a bit more cumbersome:
PS > (New-Object net.webclient).DownloadString( 'http://isitchristmas.com')
It will get the job done, but that’s a lot to remember just to get a string from an HTTP GET. I’ve added the following bit of code to my Powershell Profile to make this task a bit easier:
function Get-URL([string] $url){ (New-Object net.webclient).DownloadString($url) } Set-Alias curl Get-URL
Those expecting the full functionality of cURL with the curl alias are going to be disappointed, but if you’re simply wanting to grab the contents of a URL, this will do the trick. Now I can get the contents of a website and pipe it into another command in Powershell, such as installing pip:
PS > curl https://raw.github.com/pypa/pip/master/contrib/get-pip.py | python
The Paleo Diet is Not a Fad Diet
I’ve written previously about how when I first heard about the Paleo Diet, I thought it was fairly extreme. No beans? Crazy! No dairy! Stupid! No grains? Actually, this one was already starting to make sense to me, but I was a bit surprised that for a strict Paleolithic approach, this wasn’t even allowed in moderation. Extreme!
The notion that the Paleo Diet is a fad is nothing new, and when a good friend of mine who is blogging about his own journey into weight-loss and healthy living wrote a piece called What Hasn’t Worked, I decided I’d write up a piece on why Paleo is not a fad. He’s had some great success, so even if his experience is anecdotal, he does have a firm leg to stand on when he shares what’s not working. One paragraph, though, caught my attention:
Fad diets. By the time I started sniffing around fad diets I'd already educated myself enough to just come up with something on my own. Fad dieting is, again, not something you can keep up forever so I wasn't too interested.
Now, he doesn’t specifically call out the Paleo Diet, but I had recommended it to him a while back and I know he tried it, but he later moved on to other dietary regimens. That’s fine. However, whether or not Paleo was meant to be included in the mix of fad diets, I wanted to cook up a response that hopefully explains why Paleo is certainly not a fad.
Most of the people who balk at Paleo do so because of the exclusion of certain foods. In fact, the elimination of entire groups of food because they are considered harmful is, according to a questionably-written Wikipedia entry on fad diets, one of the qualifications of being a fad diet. It’s true that a strict Paleo Diet excludes all dairy (milk, cheese, yogurt), legumes (beans, peas, peanuts), grains (bread, pasta), and – this should go without saying – processed foods.
I’m not going to go in to why these foods are excluded. If you want to read more, start with this article, and then consider reading Robb Wolf’s excellent The Paleo Solution or Loren Cordain’s The Paleo Diet.
After you remove a few food groups, what’s left?
We’re left with many varieties of beast, foul, fish, vegetables, fruits, nuts, and seeds. Oh Noes! What about my Twinkies! There are no processed foods here; you’re only going to find what our bodies were designed to eat. Let’s take a closer look.
Meat
Hands-down, animal meat is the best source of dietary protein for your body. Yes, quality does matter, which is why the protein that comes from meat is orders of magnitude superior to that found in, say, a candy bar or a head of broccoli. The nutrition label for your favorite bran muffin might indicate that it contains protein, but it doesn’t say what kind of protein it is, nor does it say how useful (or destructive) that protein will be once it enters your body.
Are some forms of meat better than others? Absolutely. For example, grass-fed beef is better than grain-fed (i.e. corn-fed) beef, both in that it is leaner and has a much better ratio of Omega-3 to Omega-6 fatty acids. This should come as no surprise. Cows were made to eat grass, not grain, so when we eat animals that are living out the fullness of their genetic potential, we also benefit from a dietary perspective. The same holds for wild-caught vs. farmed (i.e. corn-fed) fish and pastured chickens vs. confined (and exclusively grain-fed) chickens.
When it comes to meat on the Paleo Diet, we want to have variety and quality to get the best sources of protein and unprocessed fat in our diet.
Fruits & Vegetables
Let’s go ahead and throw vegetables and fruits into the same category and look at them together. If you’re doing Paleo to lose weight (which works, by the way), you’re going to want to stick with mostly vegetables and minimal fruit, but from a dietary perspective, both categories offer an excellent source of carbohydrates.
I cringe every time I hear, “Oh, you’re doing a caveman diet? So that means you’re cutting out carbs,” as if pasta were the only source of dietary carbohydrates. “Yeah, but if you want to get carbs from vegetables, you have to eat a lot!” Uh-huh. You can also get your carbs from a Butterfinger and a gallon of sweet tea, but are you really getting quality carbohydrates? Not a chance.
Even at the purely nutritional level, food is a lot more than just a source of calories. Of course, you’re going to get plenty of calories from eating your fruits and veggies, but you’re also going to get lots of fiber and a wealth of micro-nutrients (e.g. vitamins & minerals). When compared to grains, even whole grains, vegetables win when it comes to nutrient content.
Nuts, Seeds, & Oils
We round out the Paleo Diet with nuts, seeds, and an wonderful assortment of oils. These provide an excellent source of fat to compliment the meat and vegetables in our diet. Dietary fat also helps trigger our sense of satiety during a meal so that we know we’re full and should stop eating.
But What About Everything Else?
What do you mean “what else”? In addition to the food choices mentioned above, what other foods do you need in your diet to get the macro-nutrients (protein, fat, & carbs) and micro-nutrients (vitamins & minerals) for healthy living? Nothing. Everything you need for a full, balanced, healthy diet is there. Dairy, grains, and legumes aren’t offering you any nutritional benefits that don’t already exist by sticking to plants and animals.
You could eat like this for the rest of your life.
Not a Diet, But a Lifestyle
The thing is, not only is the Paleo Diet not a fad diet, it’s hardly even a diet. You can eat this way for your entire life, enjoying what you eat and staying healthy. If you’re trying to lose weight, you’ll probably want to be strict with Paleo until you reach a satisfactory maintenance weight, but most folks eat Paleo 80% of the time and get 95% of the benefits. Thus, even though you probably don’t have any ice cream in the fridge while you’re eating Paleo, you don’t really have a problem with going out for an occasional frozen treat.
But, But, But …
I gave some excellent resources above to point to why certain foods are excluded in a Paleo Diet, but the truth is, if you spend all of five minutes on the internet, you can find plenty of conflicting arguments for why Paleo is wrong in one area or another. One article will talk about how a caveman ate a taco one time and so grains are really OK while another article will talk about how beans are just fine. When you don’t have a PhD in molecular biology, you can get overwhelmed pretty quickly, but you know what? Nothing you read can contradict the fact that eating Paleo foods is healthy and sustainable for your entire life.
The objections people have to Paleo have nothing to do with whether or not you can actually be healthy just eating meat, vegetables, fruit, seeds, and nuts. When people balk at Paleo, it almost always has to do with not wanting to give up certain foods. “I can’t give up my cereal” or “there’s nothing wrong with dairy” of “that diet is just a fad.” Instead of explaining the billion reasons why you’re not doing Paleo, why not just try it for 30 days and see what’s different? Start with the quick-start guide found on Robb Wolf’s website.
At the end of the day, the objections don’t really matter because the foods you’re eating with a Paleo lifestyle will make you a healthier human person. You can eat this way, with great success, for the rest of your life, and that’s why Paleo is not a fad diet.