-
Vacancy: senior frontend developer in Amsterdam
We want Springest to become the largest source for learning in the world. From books and e-learning courses to on-site trainings, we help our users discover, compare, and book everything they need to reach their personal and professional learning goals.
We have a strong product focus in which everything revolves around the users of our Dutch, German, Belgian, UK, and US sites. Next to that, more and more organisations are using our SaaS tools to stimulate and manage learning for their employees.
Working at Springest
We are looking for a senior Frontend Developer to join our growing engineering team. We don’t have managers at Springest, but processes, and we feel that individuals taking responsibility is very important. At Springest, you will work in close collaboration with product owners, marketing, and sales colleagues. You are also a member of our development team where we discuss architecture, infrastructure, and keep a close watch on security and performance.
Our main application runs on Ruby on Rails backed by Postgres, Redis and Memcached. Next to that we rely on Elasticsearch to power our search which is a big part of our product. We also have some smaller Go and Elixir projects in production.
We are hosted on AWS and make heavy use of their offerings like RDS, CloudWatch, Elastic Beanstalk and EC2 Container Service.
For our frontend stack, we work with Slim, Sass & CoffeeScript. Some libraries we use are jQuery, Bourbon/Neat and Backbone (but mainly we care about using the right tool for the job!).
In our product development flow we often use tools like Balsamiq and Sketch for designing features. As a Frontend Developer, both your creative & technical input can help to refine the output.
Next to our main application we build internal tools whenever necessary to help ourselves and our growing Learning Advisor team. It is extremely rewarding to see small development projects make a big impact for other Springeteers, which in turn can result in more of our visitors being helped of course!
We also do a regular internal hackday where we drop everything and work on something completely different. We might give a new programming language a shot, try out an idea someone’s been toying with for a while or hack on some actual hardware. A lot of hackday ideas actually became super important for our business. Check this recap of a recent hackday to read more about why we organise them.
Your New Colleagues
At Springest you will work with around 50 colleagues (that’s not all of us and no we’re not always in full cowboy gear, this was our Western themed SpringFest!) who all are very skilled at what they do and all of them have a healthy dose of nerd skills that we really value. Our Product Owners also regularly ship code!
Springeteers are a happy bunch and we often get together outside work to enjoy free time as well. Our office is a cosy place where anything goes and where we all take good care of together. If you’re not good at ping-pong yet, you have a good chance of becoming good here as well! 🏓.
We are all active organisers and members of Meetups and other forms of knowledge exchange (learning is our hobby!) and we participate quite actively in the Amsterdam startup ecosystem. In addition to that we get a lot of attention for Springest being the poster boy of how Holacracy and GTD can work for an organisation, which in turn is due to our organisational structure without managers and other unnecessary overhead.
Some of your challenges
- helping to finish implementing our redesign (example before-after)
- helping to craft the user experience for our big but inspiring challenge: building the ultimate product for 21st century Learning. We’re currently one of the best-known platforms where people book their classroom and e-learning courses. But in our quickly-evolving society, people continuously supplement their learning profile with personal & social learning experiences. Because you learn every day: from articles, books, meetings & inspiring conversations. For this, we need the best possible backend + frontend + AI force we can gather. We’re well on our way, but we can’t wait to strengthen our team with extra frontend power.
- bringing our internal styleguides to the next level to help the whole Product Development team of full-stackers become full-stackier
What we expect from you
- You work well in a team, and want to make the team greater than a sum of its parts.
- Engagement in what it is that you are building and who you are building it for. We want you to feel involved and come up with ways to make Springest better.
- The drive to improve yourself and our organisation and deliver high quality work.
- You have a broad interest and deep knowledge. We are all about learning and sharing knowledge.
What You Get From Us
Apart from Springest being the coolest company you will ever work for, there are a few extras:
- We are very remote friendly.
- At least a € 1,000 education budget per year to spend on training, courses, and conferences.
- Stock options after 2 years.
- A cool workplace in the center of Amsterdam with height adjustable desks that you can sit and stand at, table tennis, a massage chair, and balcony with a barbecue.
- A Macbook.
- All software and hardware you need to do your job and have an optimal workflow.
Contact Rik Matena (rik@springest.com) for questions and applications. Please include links to your Github and LinkedIn profiles.
Checkout these links to get to know more about us:
Read more -
Hubot: Get notified in Slack on merge conflicts
This is the final out of three posts where I will explain how to hook up Hubot to Github’s API. The goal of these posts is to get notified in Slack when your pull request becomes unmergeable.
Read “Hubot: get it running locally in Slack” and “Hubot: listen to Github’s pull request events” to get up to speed and get the code that this post builds upon.
Connect to Github’s API to check the merge status of a pull request
Is your pull request event set up and your bot listening to your webhook? Great! Let’s do something with that data.
Here you can find an example of the data you’ll receive on pull request events. As you can see there’s a
mergeable
key. This is eithernull
,true
, orfalse
. You want to send a notification to Slack when the value isfalse
.Hubot has built-in event listener methods:
emit
andon
. With these methods, you can create your own event that Hubot will listen to. You define an event withemit
and you listen to it withon
.You can use these methods to handle errors. Wrap your data assignment in a
try/catch
block and create anerror
event if there is one:robot.router.post '/hubot/github/:room', (req, res) -> room = req.params.room try data = req.body catch error robot.emit 'error', error
Then somewhere in your script you can add:
robot.on 'error', (error) -> #process the error
Now that you have that sorted, you can set the values you need to work with. In this case: the Slack room name to post in and the pull request url, id and status. Add the following above the
catch error
line:pull_request = { room: room, url: data.pull_request.url pullId: data.pull_request.number pullState: data.pull_request.state }
The room is needed for the Slack notification. The
url
andnumber
are needed for theGET
request you’ll send to Github’s API in order to check the pull request’smerge status
. The pull request’sstate
is needed to filter events on pull requests that are not open. Let’s do that first, because there’s no point in checking for a possible merge conflict in closed ones.if (pull_request.pullState == 'open' || pull_request.pullState == 'reopened') # do stuff
Tip: throw a
console.log()
in your code to see what data you have available and know what you’re working with.When someone pushes to a branch that has a pull request, the payload is sent immediately. The result is that the initial value of
mergeable
in the data object isunknown
. The main reason is that it takes a few seconds until the merge status is known because the code has to be compared to the master branch. In order to know if the pull request has a merge conflict, therefore unable to merge with the master branch, you have to check its merge status via the Github API.You can use Hubot’s
http
call methods, that enable you to sendGET
requests. In this case to the Github API. Let’s create a method that takes care of this and add it at the top ingithub.coffee
, above theexport
function:getRequest = (robot, data, callback) -> url = "#{url}/YourRepoName/#{data.repository}/pulls/#{data.pullId}?access_token=#{token}" robot.http(url) .headers('Accept': 'application/rubyon') .get() (err, res, body) -> callback(err, res, body) module.exports = (robot) ->
The method accepts 3 arguments: the
robot
instance,data
and acallback
function.Notice that the
url
variable usesdata.repository
anddata.pullId
. These come from thepull_request
object that you pass as thedata
argument. There’s also anaccess_token
variable at the end. This is needed in case of a private repository. You can set one here. If your repo is public, you do not have to include the token in the url. What follows is theGET
request to that url.Create another method underneath called
checkMergeStatus
:checkMergeStatus = (robot, data) -> getRequest robot, data, (err, res, body) -> try response = JSON.parse body mergeStatus = response.mergeable catch error robot.emit 'error', error
This method uses your
getRequest
method and stores themergeable
value. Now, ifmergeStatus
isfalse
, you want to send a notification to your Slack channel. If it istrue
you do not have to do anything. If it is stillunknown
, you want to re-check. Add the following to thecheckMergeStatus
method, inside thetry
block:if (mergeStatus == false) # send notification else if (mergeStatus == 'unknown') setTimeout -> checkMergeStatus(robot, data) , 1000 else # do something?
As you can see, if the second condition evaluates to
true
, thecheckMergeStatus
will keep calling itself with 1000ms (1sec) intervals, until it changes.Send a notification to your Slack channel on a merge conflict
Here’s where the previously mentioned
emit
method comes in handy again. You can create amerge_conflict
event for when the status isfalse
. Make sure it has the values you need in your Slack notification:if (mergeStatus == false) robot.emit 'merge_conflict', { room: data.room, pullTitle: response.title, author: response.user.login, pullUrl: response.html_url, pullId: response.number }
All that you have to do now, is to get Hubot to take action as soon as the
merge_conflict
event happens. You can let Hubot ‘listen’ to it by using theon
method. I moved the code for this to a separate script calledmerge_conflict_notifier.coffee
in thescripts
folder:module.exports = (robot) -> robot.on 'merge_conflict', (merge_conflict) -> room_id = robot.adapter.client.rtm.dataStore.getChannelByName(merge_conflict.room).id message = { "text": ":no_entry_sign: Merge conflict: <#{merge_conflict.pullUrl}|##{merge_conflict.number} #{merge_conflict.pullTitle}> by #{merge_conflict.author}" } robot.messageRoom room_id, message
In order to post a message to a room via Slack’s API, you need to have the room’s id.
robot.adapter.client.rtm.dataStore.getChannelByName(<your room name>).id
will get that for you.The message is formatted according to Slack’s requirements. More information about message formatting can be found on their website.
Keeping track
But what happens if someone is going to commit several fixes to their branch but the merge conflict does not get resolved right away? You do not want to spam your Slack channel because that is super annoying. Therefore it would be a good idea to keep a list of the pull requests that have a merge conflict and remove them if they are resolved.
At the top of the
github.coffee
, add an empty array calledunmergeablePulls
. Now you can add some code to keep track of them. Add the following above thecheckMergeStatus
method:unmergeablePulls = [] addPullIdToList = (pullId) -> return false if pullId in unmergeablePulls unmergeablePulls.push pullId return true removePullIdFromList = (pullId) -> return if pullId == null indexOfPullId = unmergeablePulls.indexOf(pullId) unmergeablePulls.splice(indexOfPullId, pullId)
Now you can use these methods in the
if/else
block within thecheckMergeStatus
method and add theid
of the pull request when there’s a merge conflict. When theid
has already been added, the notification is skipped. When there is no longer a merge conflict, theid
is removed from the list:if (mergeStatus == false) if addPullIdToList(robot, data.pullId) robot.emit 'merge_conflict', { room: data.room, pullTitle: response.title, author: response.user.login, pullUrl: response.html_url, pullId: response.number } else if (mergeStatus == 'unknown') setTimeout -> checkMergeStatus(robot, data) , 1000 else removePullIdFromList(data.pullId)
Hubot has a
robot.brain
method that lets you store key-values to Redis. This could also be used to keep track of the id’s, but for now this is how I made it work.You can deploy your Hubot to Heroku. Hubot provides documentation on how to do that.
I hope these 3 posts have helped you get an idea of how Hubot works, the advantages of a chatbot in general and how it can enhance your productivity/workflow.
Happy hacking :)
Read more -
Efficient and effective working: a Product Tank meetup with special guest David Allen
We were proud to host the ProductTank AMS June 22nd meetup about efficient and effective working at Springest.
As a product-y person, you are usually required to communicate often and with many people. How do you make sure you still have time for “deep work”?
None other than David Allen, creator of the Getting Things Done Time Management method, opened the evening. He made an entertaining plea for you to not attempt to store all your new ideas in your brain. That thing is already busy enough dealing with the 50.000 conscious and subconscious decisions you make each day. “Your mind is a crappy office.” Tough to argue with.
David also gave the crowd a sense of how his company has been running on Holacracy –an operating system for organizations– for the past 6 years.
This served as a nice bridge to Jaap-Willem Mol from VergaderVerkalking (“if you’re not Dutch and you manage to pronounce that, I’ll buy you a beer”). Jaap-Willem talked about the three basic principles that lie central to effective meetings: rules, roles and feedback. He had a good tip: find inspiration for more effective meetings in the Holacracy Tactical & Governance meeting at Springest video produced by VergaderVerkalking.
Dennis Paagman, Product Owner & Developer at Springest, closed the round of presentations. He showed how GTD & Holacracy help us be productive and aligned on our goals, from the whole company down to each circle and individual role. Some concrete examples included quick peeks at our publicly visible & continuously evolving Holacratic structure at roles.springest.com, and an example of our actual current OKRs (Objectives and Key Results).
ProductTank organizer Iris van de Kieft closed the event by hosting a nice panel session with the three speakers and an engaged audience.
Thanks to the audience, the speakers and ProductTank. We love hosting meetups at our Springest HQ (with a view over the IJ river!). For example, we’re actively involved in the Amsterdam & Netherlands communities for Product, Holacracy and Ruby. So, reach out to Dennis if you’re interested! We’re also hiring backend/frontend/full-stack developers, spread the word!
Photo’s taken by Mark Mulder, Developer at Springest.
Read more -
Hubot: listen to Github's pull request events
This is the second out of three posts where I will explain how to hook up Hubot to Github’s API. The goal of these posts is to get notified in Slack when your pull request becomes unmergeable.
If you have never worked with Hubot in Slack before, read the first post: “Hubot: get it running locally in Slack”
Github’s API
Github offers an extensive API to connect to your repositories and automate your daily development workflow. Their documentation is very detailed and extensive and they have excellent guides to help you get started. For the goal of this post, I’ll focus on Pull Request events.
Enable Github’s Pull Request events
As said above, the goal of this project is to get notified in Slack when a merge-conflict occurs. Luckily, Github supports webhooks. That means you can get them to notify you whenever an event, like a push or a commit, happens, which in turn enables you to check the status of your pull request. Read more about all the events at https://developer.github.com/webhooks/
Send events to a webhook.
When an event happens on a pull request, Github gathers all relevant data and sends that event (a payload) to the URL of your choosing. This is called a webhook.
To set this up, go to the settings page of your repository. Select
Webhooks
. Then click theadd webhook
button. You have to provide a so called payload URL which is the webhook URL. Since you are running everything locally, you do not yet have a public URL.Set up ngrok
Ngrok is a tool that enables you to expose your local webserver to the internet. Download it here. Unzip it from your bot’s directory. This will install a small executable in your bot’s root directory:
$ unzip /path/to/ngrok.zip
Run it on port 8080, which is Hubot’s default port:
$ ./ngrok http 8080
You should see something like this:
ngrok by @inconshreveable Tunnel Status online Version 2.1.3 Region United States (us) Web Interface http://127.0.0.1:4040 Forwarding http://a3b9e4e6.ngrok.io -> localhost:8080 Forwarding https://a3b9e4e6.ngrok.io -> localhost:8080 Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00
You can use the
http://
URL for now, you can open that in your browser to see a nifty web interface.Set up the webhook.
Copy and paste the entire URL, ending with
.io
into the Payload URL field of the form. Append/hubot/github/general
to the URL. The/hubot
part is important because it is a requirement. The next part, in this example/github
, can be anything you like and/general
is the room’s name in Slack (which you are free to change as well).Make sure the content type is set to
application/json
and skip the secret field for now. The secret is used to protect your endpoint so we know that messages are coming from Github, but for brevity we’ll leave this up to you, the reader.Select
Let me select individual events
. In this case you only want to receivepull request
events. ClickAdd webhook
.Note: Every time you restart ngrok, you’ll get a new URL. Make sure to update it in your Github repo settings.
Test the webhook.
You will see a list of recent payload deliveries underneath your webhook form right after you save it. There is also a red error mark next to the first delivery.
Now check ngrok in your console. You should see this:
HTTP Requests ------------- POST / 404 Not Found
This means 2 things:
- The request was received by your local server. Yay!
- The endpoint it’s routed to, doesn’t exist yet.
Note: If you do not have your bot running, you’ll see a 502 Bad Gateway error.
Make Hubot listen to the webhook
In order to get rid of the 404 error, you have to setup an endpoint in your bot that will execute your code when a payload is received. In your favorite editor (I use Vim), create a new coffeescript file called
github.coffee
inside your bot’sscripts
directory.First things first, export a function:
module.exports = (robot) ->
This is a requirement and part of the anatomy of a Hubot script. The robot parameter is an instance of your bot.
Create the endpoint:
robot.router.post '/hubot/github/:room', (req, res) -> room = req.params.room data = req.body console.log("== Pull request data received: #{data.pull_request.number}")
robot.router
is a built-in Hubot method. By adding.post
you create a route for POST requests. Naturally, if you want to create a route for GET request, you simply call.get
.The string
'/hubot/github/:room'
is the webhook URL you set up in your Github repo, where:room
is a variable you have to define. The callback,(req, res)
is called when a POST request comes in. Save the room name and the JSON object that contains all data in 2 variables:room
anddata
. Then, to check that it works, the pull request’s number is printed out and will show up in your terminal.Finally, you want to tell Github you’ve successfully processed the request. You can do that by sending a success response:
res.send 'OK'
That’s it! Your Hubot is now listening to pull request events from Github. You can now move on to do stuff with the data you receive
Read more -
Hubot: get it running locally in Slack
This is the first out of three posts where I will explain how we hooked up Hubot to Github’s API. The goal of this is to get notified in Slack when your pull request becomes unmergeable.
A Springest Hackday idea
At Springest we hold regular hackdays. The purpose of these days is to learn, have fun together and work on something that is related to Springest, but isn’t part of our daily work-routine. It can be anything from hanging a swing in the office to building the company’s internal Facebook app called Sputr.
Starting the day, we all share our ideas during stand-up and we get to work. Either solo or in a team.
‘Get slack notifications when a pull request becomes unmergeable’ was one of the ideas on our hackday-list that I thought was interesting to work on.
In this post, I’ll explain how to run Hubot locally in Slack, so you can test-drive your code.
So what is Hubot, anyway?
Hubot is a chat bot by Github. It is open source and written in CoffeeScript and Node.js. You can automate processes with Hubot through scripts, or just add some flavor to your team’s culture. At Springest, our bot is called ‘Ingrid’. She notifies us about all sorts of things that happen on the Springest website and we can tell her to do stuff for us, like deploying, share support ticket info or give someone karma. All by messaging in Slack.
Slack is a messaging app for teams. Messaging is categorized in channels that everyone is free to follow or not. It enables us to easily communicate with each other.
To install Hubot, you need to follow these instructions:
Node.js and npm
Open your console and check if you have installed node.js and npm:
$ node -v # should output node.js version $ npm -v # should output npm version
Node.js is a server-side JavaScript environment and npm is a package manager for node programs.
If nothing exists, install node.js by downloading it here. This will also install npm.
Make sure you have the latest version of npm installed, by running:
$ npm install npm -g
If you have never installed hubot before, you need to install its generator. Otherwise you can skip this step.
$ npm install -g yo generator-hubot
Let’s run it!
Now that you have the generator, add a directory for your bot and create it. I asked my six-year-old son what name he would give a robot, if he would have one. He said ‘Stone’ because robots are hard like stone. So I called it Stone for this example, no questions asked.
Create your bot (just hit ‘enter’ for all questions) :
$ mkdir stone $ cd stone $ yo hubot ? Owner irisbune <xxxxxxx@gmail.com> ? Bot name stone ? Description A simple helpful robot for your Company ? Bot adapter campfire create bin/hubot create bin/hubot.cmd create Procfile create README.md create external-scripts.json create hubot-scripts.json create .gitignore create package.json create scripts/example.coffee create .editorconfig _____________________________ _____ / \ \ \ | Self-replication process | | | _____ | complete... | |__\\| /_____\ \ Good luck with that. / |//+ |[^_/\_]| /---------------------------- | | _|___@@__|__ +===+/ /// \_\ | |_\ /// HUBOT/\\ |___/\// / \\ \ / +---+ \____/ | | | //| +===+ \// |xx| loadDep:mime-db → request ▄ ╢████████████
Check if it is alive and kicking by running:
$ bin/hubot
If all went well, you should now be able to chat with your bot in the console. Type ‘your-bot-name the rules’ and see if you get a reply.
Move from console to Slack
Create an account on Slack if you do not have one yet. I’ve created my own personal channel, to test this bot before implementing the code in Ingrid, our company bot. Once you have your own channel set up, go to
https:// yourslackchannel .slack.com/apps/A0F7XDU93-hubot
and click on the green button, ‘Add Configuration’.Add your bot’s username and hit ‘Add Hubot Configuration’.
Keep this page open, because you need to copy the environment variable in order to run your bot in Slack.
Slack adapter
Now that you’ve configured your bot in Slack, and got your token, you have to install the Slack adapter and your bot is setup to run locally:
$ npm install hubot-slack --save
Open Slack on your computer and restart your bot with Slack’s environment variable, followed by a call to hubot’s scripts, plus the adapter flag:
$ HUBOT_SLACK_TOKEN=your-hubot-api-token ./bin/hubot --adapter slack
Invite your bot to your #random Slack channel (
/invite @botname
) and test it out by typing@botname pug me
Congratulations! You are now ready to hook up Github’s pull request events.
Read more
subscribe via RSS