Post-Mortem: LinkedIn Talent Intelligence Experience
Tags: graphql, vue, postmortem
Published: 2020-02-21 01:47:32

---
title: Post-Mortem: LinkedIn Talent Intelligence Experience
published: true
date: 2018-06-20 13:37:00 UTC
tags: graphql,vuejs,post-mortem
canonical_url: https://nystudio107.com/blog/post-mortem-linkedin-talent-intelligence-experience
---

# 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

Andrew Welch / [nystudio107](https://nystudio107.com)

![Unique Project Linkedin Talent Insights](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x675_crop_center-center_82_line/unique-project-linkedin-talent-insights.jpg)

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](http://business.linkedin.com/talent-solutions/blog/talent-analytics/2018/3-insights-on-nycs-talent--where-they-come-from--where-they-go--), then [Paris](http://business.linkedin.com/talent-solutions/events/18/06/talent-intelligence-experience-french-guests-reg), and then [Syd­ney](http://business.linkedin.com/talent-solutions/events/18/06/new-era-of-data-and-insights-apac#), the live events high­lighted LinkedIn’s new [Tal­ent Intel­li­gence](http://business.linkedin.com/talent-solutions/blog/product-updates/2017/what-is-talent-intelligence) plat­form which tar­gets enter­prise customers.

<aside>
                                Enjoy short talks from glob­al lead­ers, inter­ac­tive ses­sions and net­work­ing oppor­tu­ni­ties. Immerse your­self in an inter­ac­tive expe­ri­ence, infused with insights focused on recruit­ment agencies.
                            </aside>

These series of events were spear­head­ed by [Unit9](http://www.unit9.com/) and pro­duced by [dan­de­lion + bur­dock](http://dandelion-burdock.com/), 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](http://dandelion-burdock.com/), and with my long-time part­ner in crime, Jonathan Melville from [CodeMDD​.io](http://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.

## The Prob­lem

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](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x675_crop_center-center_82_line/LinkedIn-TI-Unit9-NYC-201806-00229.jpg)

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.

## 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](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x675_crop_center-center_82_line/LinkedIn-TI-Unit9-NYC-201806-00435.jpg)

![Linkedin Ti Unit9 Nyc 201806 00003](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x675_crop_center-center_82_line/LinkedIn-TI-Unit9-NYC-201806-00003.jpg)

![Linkedin Ti Unit9 Nyc 201806 00351](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x675_crop_center-center_82_line/LinkedIn-TI_Unit9-NYC-201806-00351.jpg)

![Linkedin Ti Unit9 Nyc 201806 00280](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x675_crop_center-center_82_line/LinkedIn-TI-Unit9-NYC-201806-00280.jpg)

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](http://youtu.be/1_T1VDSasik) of it in action, if you want to see it all in motion.

## The Solu­tion

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](http://craftcms.com/) 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](http://tailwindcss.com/docs/what-is-tailwind/) to style the web pages, because it allowed us to pro­to­type and iter­ate quick­ly as the project morphed
- [Vue­JS](http://vuejs.org/) 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](http://graphql.org/) via the [CraftQL plu­g­in](http://github.com/markhuot/craftql) 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](http://devmode.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.

## The Hard­ware

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](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x811_crop_center-center_100_line/LinkedIn-hardware.png)

We opt­ed to go for a cloud serv­er pro­vi­sioned via [Forge](http://forge.laravel.com/) 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](http://system76.com/desktops/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](https://dev.to/gaijinity/database-asset-syncing-between-environments-in-craft-cms-3fca-temp-slug-480513) article.

We then had the wiz­ards at dan­de­lion + bur­dock hook the [Sur­face Pro](http://www.microsoft.com/en-us/store/b/surface) 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](http://dandelion-burdock.com/articles/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.

## The Soft­ware

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](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x941_crop_center-center_100_line/LinkedIn-software.png)

We had two sep­a­rate Twig tem­plates for the <kbd>tablet</kbd> and <kbd>display</kbd> (pro­jec­tion wall) that have the HTML/​VueJS code for each. That way we could fix the tablets to load <kbd>/tablet</kbd> and using [Touch Design­er](http://www.derivative.ca/), have it load the web view for <kbd>/display</kbd> 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 <kbd>stationSlug</kbd> URL para­me­ter to indi­cate which sta­tion the web page should load. e.g.: <kbd>/tablet?stationSlug=new-york-green</kbd> 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 <kbd>stationSlug</kbd>.

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

![Linkedin Station Craft Cms Entry](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x1352_crop_center-center_100_line/LinkedIn-station-craft-cms-entry.png)

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](http://howlerjs.com/)), 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](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x747_crop_center-center_100_line/LinkedIn-db-schema.png)

…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](http://graphql.org/blog/subscriptions-in-graphql-and-relay/), 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.

<aside>
                                Nev­er solve a prob­lem for <span>2</span> things; always solve it for N things
                            </aside>

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](http://github.com/verbb/super-table) used to pro­vide a nice UX for adding an arbi­trary num­ber of answers:

![Linkedin Questions And Answers](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x1386_crop_center-center_100_line/LinkedIn-questions-and-answers.png)

For the data export, we used Fred Carlsen’s [Beam plu­g­in](http://superbig.co/plugins/beam) 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.

## 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 <kbd>stationsQuery</kbd> GraphQL query looks like to retrieve the cur­rent state of a giv­en station:

![Linkedin Stations Query](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x745_crop_center-center_100_line/LinkedIn-stations-query.png)

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](http://github.com/prismagraphql/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 {
          recordAnswers
        }
      }
    }
    `;

```

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

```

// Load in our global settings
            loadSettings: function() {
                client.request(settingsQuery)
                    .then(data => {
                        this.recordAnswers = data.globals.settings.recordAnswers;
                        console.log(data);
                    })
                    .catch(err => {
                        console.log(err);
                        console.log(err.response.errors);
                        console.log(err.response.data);
                    });
            },

```

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!)
    {
        upsertLeads(
            authorId: 1
            title: "lead"
            firstName: $firstName
            lastName: $lastName
            email: $email
            cityIdentifier: $cityIdentifier
            stationIdentifier: $stationIdentifier
            userIdentifier: $userIdentifier
        ) {
        id
        }
    }
    `;

```

In this case the chan­nel in Craft CMS is Leads, with <kbd>upsert</kbd> being CraftQL nomen­cla­ture for ​“write”. Then each key/​value pair such as <kbd>title</kbd>, <kbd>firstName</kbd>, 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.

## Wrap­ping 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](http://youtu.be/1_T1VDSasik)!

## Further Reading

_If you want to be notified about new articles, follow [nystudio107](https://twitter.com/nystudio107) on Twitter._

<small>Copyright ©2020 nystudio107. Designed by nystudio107</small>