Andrew Welch · Post-Mortems · #graphql #vuejs #post-mortem

Published , updated · 5 min read ·

Please consider 🎗 sponsoring me 🎗 to keep writing articles like this.

Post-Mortem: LinkedIn Talent Intelligence Experience

This project post-mortem cov­ers a unique use of Craft CMS 3, GraphQL, and Vue­JS to cre­ate an inter­ac­tive expe­ri­ence for Linked­In’s Tal­ent Intel­li­gence Experience

Unique Project Linkedin Talent Insights

I was recent­ly tasked to do an unusu­al project as part of the LinkedIn Tal­ent Intel­li­gence Expe­ri­ence. Tak­ing place in New York City, then Paris, and then Syd­ney, the live events high­lighted LinkedIn’s new Tal­ent Intel­li­gence plat­form which tar­gets enter­prise customers.

Enjoy short talks from global leaders, interactive sessions and networking opportunities. Immerse yourself in an interactive experience, infused with insights focused on recruitment agencies.

These series of events were spear­head­ed by Unit9 and pro­duced by dan­de­lion + bur­dock, and encom­passed a huge under­tak­ing that involved mul­ti­ple rooms with inter­ac­tive video, strat­e­gy ses­sions, pre­sen­ta­tions, cock­tails, and much more. I’m just going to focus here on the small part of the project that I worked on, because I think it’s an inter­est­ing use of tech­nolo­gies to pull off an inter­ac­tive experience.

I worked on the project under the direc­tion of Niall Thomp­son from dan­de­lion + bur­dock, and with my long-time part­ner in crime, Jonathan Melville from CodeMDD​.io to do the design and fron­tend tem­plate cod­ing. I was tasked with the plan­ning, archi­tec­ture, devops, and core app design, so that’s what I’ll focus on here.

What I hope you’ll take away from this post-mortem is start­ing to think about how you can lever­age web tech­nolo­gies and your skillset in non-tra­di­tion­al ways.

Link The Problem

The client want­ed an expe­ri­ence where peo­ple could inter­act with an app on mul­ti­ple tablets mount­ed on plinths in front of a large pro­jec­tion wall. Peo­ple approach­ing the tablets were giv­en infor­ma­tion on a fic­tion­al com­pa­ny that want­ed to relo­cate to anoth­er city, and it would be the their respon­si­bil­i­ty to pick where to relocate.

Linkedin Ti Unit9 Nyc 201806 00229

They’d be pre­sent­ed with a num­ber of ques­tions on avail­able tal­ent in a vari­ety of cities. They’d then be encour­aged to explore the pre­sent­ed mul­ti­ple-choice answers, and see insights from their choic­es dis­played on a large pro­jec­tion wall in front of them.

Then after gain­ing knowl­edge by inter­act­ing with the app, they made a choice about where to relo­cate the fic­tion­al com­pa­ny. There was no right or wrong answer to any­thing. The point was to be able to see the type of insights that Linked­In’s Tal­ent Intel­li­gence could offer them.

In addi­tion to this, the app need­ed to be able to:

  • Work with an arbi­trary num­ber of tablet/​projection screen pair­ings (“sta­tions”)
  • Work with an arbi­trary num­ber of cities that the event would trav­el to
  • Han­dle hav­ing a dif­fer­ent look, dif­fer­ent ques­tions, and dif­fer­ent insights for each city
  • When an answer was cho­sen on the tablet, dynam­i­cal­ly change the insight” shown on the pro­jec­tion screen
  • Have a way to allow both the client and our team to col­lab­o­ra­tive­ly edit the ques­tions, insights, etc. on the backend
  • Record each unique user ses­sion of answers in the data­base as entries
  • Present them with a final insights” screen that showed how their answer com­pared to every­one else’s from that city’s event
  • Cap­ture their name and email address at the end, so LinkedIn could fol­low up
  • Export all of the cap­tured data so that the LinkedIn data team could ana­lyze it

In addi­tion, we want­ed to design it so that if the client end­ed up want­i­ng to turn it into a web­site, it would­n’t be a com­plete rewrite.

Link The Result

Before we get into the nit­ty grit­ty of how we approached the project, let’s have a look at the final result. Then we can decon­struct how we got from here to there.

Linkedin Ti Unit9 Nyc 201806 00435

Look­ing through insights on the touch­screen app

Linkedin Ti Unit9 Nyc 201806 00003

The result­ing insight data that appears on the pro­jec­tion wall

Linkedin Ti Unit9 Nyc 201806 00351

Mak­ing a deci­sion on the touch­screen app

Linkedin Ti Unit9 Nyc 201806 00280

The phys­i­cal UX of inter­act­ing with the tablets and result­ing insights

It all came togeth­er in a way that blend­ed with the brand­ing and theme of the rest of the event. Here’s a YouTube video of it in action, if you want to see it all in motion.

Link The Solution

We did­n’t have long to bring it all togeth­er… and the scope was in flux right down to the wire.

Because of how quick­ly every­thing had to come togeth­er, and how flex­i­ble it all need­ed to be fac­ing the chang­ing spec, we opt­ed to put togeth­er some exist­ing tech­nolo­gies to get off the ground quickly.

  • Craft CMS 3 for the back­end, because we knew it well, and we need­ed a way to allow a vari­ety of con­tent authors to work togeth­er. We also need­ed a place to record and export the data, so it was a nat­ur­al fit.
  • Tail­wind CSS to style the web pages, because it allowed us to pro­to­type and iter­ate quick­ly as the project morphed
  • Vue­JS to do the app guts” because again, it allowed us to pro­to­type some­thing quick­ly, and the reac­tiv­i­ty was just a nat­ur­al for the type of app we were making
  • GraphQL via the CraftQL plu­g­in from Mark Huot to han­dle reading/​writing data from Craft CMS because it’s just so easy to use, and the data gets pack­aged up in a very nat­ur­al way for VueJS

We’ve talked about every sin­gle one of these tech­nolo­gies on the dev​Mode​.fm pod­cast, so per­haps it’s not sur­pris­ing that we chose them. But it’s inter­est­ing that these web” tech­nolo­gies worked so well togeth­er for a live inter­ac­tive app.

Link The Hardware

To make this all hap­pen, we need­ed hard­ware to run it all on. Here’s a rough dia­gram of what that looked like:

Linkedin Hardware

The hard­ware set­up used for the LinkedIn Tal­ent Intel­li­gence project

We opt­ed to go for a cloud serv­er pro­vi­sioned via Forge to host Craft CMS, so that the entire team could col­lab­o­rate from their dis­parate locations.

How­ev­er, for any live event, it’s not a great idea to depend on an Inter­net con­nec­tion being sta­ble enough to han­dle the job, or even be work­ing. And indeed, we end­ed up los­ing Inter­net access on the sec­ond day of the New York City event.

We chose to use a small Meerkat serv­er pro­vi­sioned with the same Ubun­tu 16.04 Lin­ux that was run­ning on our cloud serv­er. It then synced the data down from the cloud serv­er using the tech­nique described in the Data­base & Asset Sync­ing Between Envi­ron­ments in Craft CMS article.

We then had the wiz­ards at dan­de­lion + bur­dock hook the Sur­face Pro tablets and Meerkat up to the local pri­vate net­work, and away we went.

The Sur­face Pro tablets were con­fig­ured as dis­cussed in the Chrome Kiosk Mode arti­cle, so that they could be run­ning a mod­ern brows­er like Google Chrome, but could­n’t be tam­pered with by any of the users.

Link The Software

On the soft­ware side of things, the cen­ter of the uni­verse is Craft CMS 3. That’s where the data to dis­play comes from, and that’s where any result­ing answers from the user are stored:

Linkedin Software

The soft­ware set­up used for the LinkedIn Tal­ent Intel­li­gence project

We had two sep­a­rate Twig tem­plates for the tablet and display (pro­jec­tion wall) that have the HTML/​VueJS code for each. That way we could fix the tablets to load /tablet and using Touch Design­er, have it load the web view for /display to be com­pos­it­ed on top of live video.

Because we need­ed to be able to han­dle mul­ti­ple cities, and mul­ti­ple sta­tions for each city, we passed in the stationSlug URL para­me­ter to indi­cate which sta­tion the web page should load. e.g.: /tablet?stationSlug=new-york-green would load the tablet view for the New York Green station.

The cus­tom Vue­JS app would then load the appro­pri­ate Craft CMS 3 entry via GraphQL/​CraftQL in the Sta­tions chan­nel that cor­re­spond­ed to the giv­en stationSlug.

On the back­end in Craft CMS, the entries looked some­thing like this:

Linkedin Station Craft Cms Entry

Craft CMS 3 Sta­tion entry

This is where con­tent authors could choose the first ques­tion to ask, set the back­ground image, choose the sounds to be played as audi­to­ry clues (via howler.js), and so on.

Orig­i­nal­ly the sys­tem was designed to be a choose your own adven­ture” book style of ques­tions, where the answer to one ques­tion could lead to a dif­fer­ent ques­tion. That’s why the Sta­tions entry only has you set the first question.

The db schema is pret­ty sim­ple, and looks rough­ly like this:

Linkedin Db Schema

Rough overview of the db schema

…and each answer could link to the next ques­tion (if any).

In addi­tion to pro­vid­ing a way for the con­tent authors to cus­tomize things, this Sta­tions entry then also kept the per­sis­tent state of the app. The tablet allows peo­ple to change the state by chang­ing the entry with a GraphQL muta­tion, and the pro­jec­tion wall polls the state by doing a GraphQL query. I’d have pre­ferred to do GraphQL Sub­scrip­tions, but that isn’t a thing yet in CraftQL.

Want to add an addi­tion­al sta­tion? No prob­lem, just add a new entry. Want to add a new city? No prob­lem as well, just change the cat­e­go­ry the sta­tion is linked to.

This end­ed up being a good way to go, because the num­ber of sta­tions planned for use changed sev­er­al times as the project pro­gressed. And poten­tial­ly, each city might have had a vary­ing num­ber of sta­tions as well, depend­ing on the event space.

Never solve a problem for 2 things; always solve it for N things

This allowed great flex­i­bil­i­ty (maybe a bit more flex­i­bil­i­ty than was need­ed in the end). The ques­tions were in a sep­a­rate chan­nel, with the SuperTable plu­g­in used to pro­vide a nice UX for adding an arbi­trary num­ber of answers:

Linkedin Questions And Answers

An exam­ple ques­tion entry, with answers that link to the next question

For the data export, we used Fred Carlsen’s Beam plu­g­in along with a cus­tom tem­plate to allow easy export to a CSV file, on a per-city basis. This gives them access to each unique user ses­sion, with all of the answers they chose, as well as all of the lead cap­ture infor­ma­tion. All wrapped up in a neat lit­tle CSV bow.

Link Show me the App!

The cus­tom Vue­JS app itself is most­ly respon­si­ble for keep­ing track of state, and respond­ing to var­i­ous queries and input from the user. Here’s an exam­ple of what the stationsQuery GraphQL query looks like to retrieve the cur­rent state of a giv­en station:

Linkedin Stations Query

GraphQL query on the left, result­ing JSON data on the right

It’s real­ly awe­some how eas­i­ly the CraftQL plu­g­in lets you read (query) as well as write (mutate) data in Craft CMS 3. We end­ed up using GraphQL-Request to make the GraphQL requests easy. All we had to do was set up a client with a JSON Web Token (JWT):

const client = new GraphQLClient('/api', {
    headers: {
        Authorization: 'Bearer XXXXXXXXXXXXXXXXXXXXXXXXXXXXX',

Then we can declare a sim­ple GraphQL query like so:

const settingsQuery = `
      globals {
        settings {

In this case, all the query does is ask for the state of the recordAnswers lightswitch in the Set­tings Glob­als. We then can exe­cute it like this:

// Load in our global settings
            loadSettings: function() {
                    .then(data => {
                        this.recordAnswers = data.globals.settings.recordAnswers;
                    .catch(err => {

To do some­thing like writ­ing out the cap­tured lead infor­ma­tion at the end of a ses­sion, we sim­ply have a GraphQL muta­tion like this:

const writeLeadMutation = `
    mutation writeLead($firstName: String!, $lastName: String!, $email: String!, $cityIdentifier: String!, $stationIdentifier: String!, $userIdentifier: String!)
            authorId: 1
            title: "lead"
            firstName: $firstName
            lastName: $lastName
            email: $email
            cityIdentifier: $cityIdentifier
            stationIdentifier: $stationIdentifier
            userIdentifier: $userIdentifier
        ) {

In this case the chan­nel in Craft CMS is Leads, with upsert being CraftQL nomen­cla­ture for write”. Then each key/​value pair such as title, firstName, etc. all just cor­re­spond to fields in the Leads chan­nel that we want to save data to.

Due to Vue­JS’s reac­tive nature, as soon as we changed the prop­er­ties in our app, the changes were instant­ly reflect­ed to the user. This made for a real­ly nice inter­ac­tive expe­ri­ence for peo­ple using the tablets.

We also made good use of Vue­JS niceties such as com­put­ed prop­er­ties, watch­ers, tran­si­tions, and the like… but that’s a sto­ry for anoth­er day.

Link Wrapping up!

The cus­tom Vue­JS app is only 864 lines of code, so it’s noth­ing ter­ri­bly com­pli­cat­ed. But get­ting all the var­i­ous pieces that run asyn­chro­nous­ly to work in har­mo­ny took a bit of doing.

There also was quite a bit more work that went into the project as a whole in terms of the actu­al tem­plates, the CSS, etc. But a large chunk of the work was sim­ply fig­ur­ing out all of the var­i­ous pieces we’d need (both hard­ware and soft­ware), and bring­ing them all together.

I was one very small part of a huge pro­duc­tion, but I had a lot of fun try­ing to apply web tech­nolo­gies to a live inter­ac­tive event. Hope­ful­ly this might get you think­ing about some non-tra­di­tion­al appli­ca­tions of web technologies!

If you missed it the first time around, check out the YouTube video show­ing the project in action!