Coding a Planning Poker app

lukegarrigan

Luke Garrigan

Posted on February 27, 2021

Coding a Planning Poker app

The 2 week sprint is over, the scope for the next sprint has been established by management, it's time to estimate. Many programmers will likely have to go through the process of estimating the next chunk of work to get a feel for how much of the chunk can be done in the next couple weeks.

There are many ways to go about estimating, using the Fibonacci sequence, the modified Fibonacci sequence, t-shirt sizing esimating, etc. My team tends to go for the Fibonnaci sequence to measure the complexity of tickets in order to story point.

Once upon a time in a not-so-digital world, one might use physical playing cards to do the estimation. But now, we have no choice but to use software to fill the void! My team tend to go for planning poker apps like planningpokeronline.com

Image of planningpokeronline

Where members of the team choose the card they think represents the complexity of the ticket in question and then at the end - when everybody has voted - the cards are turned and the estimations shown, followed by a discussion on why people chose said card.

Anyway, planningpokeronline.com is indeed a great app, but it only allows you to play a certain amount of games before trying to sell you the premium version, which does my bloody head in.

So this week, I decided to create my own, free version.

My planning poker app

At the time of writing this blog, I've got the app deployed at lukegarrigan.github.io. You should be greeted with a button and nothing else, no fluff.

When you click Start new game a socket connection is established with the server - currently hosted on heroku. When the socket connection is made the server makes use of socket.io's rooms, which is a server-only concept that allows us to split sockets into an arbitrary channel. I generate an id using short-uuid to represent a room, this id is then passed back to the front-end which then handles the routing.

io.on('connection', (socket) => {
console.log('a user connected', socket.id);

let roomId = socket.handshake.query['roomId'];
if (!roomId) {
  roomId = short.generate();
  socket.emit('room', roomId); 
}
socket.join(roomId);
Enter fullscreen mode Exit fullscreen mode

As shown above the server emits a room event socket.emit('room', roomId); passing along the generated id back to the front-end. The front-end then stores the socket connection to state, which is just vue.js state management library. Then we route the user to the path game/${roomId}.

const socket = io(process.env.VUE_APP_SERVER);
store.commit("setSocket", socket);
store.state.socket.on("room", (roomId: string) => {
   router.push({ path: `game/${roomId}`});
});
Enter fullscreen mode Exit fullscreen mode

Something like this:

Then you're asked to enter your name:

When you hit enter the front-end emits a name event for the server to pick up:



  public enteredName(name: string) {
    store.state.socket.emit('name', name);
    this.modal = false;
  }


Enter fullscreen mode Exit fullscreen mode

The server then emits this information to all players in your room:



function updateClientsInRoom(roomId) {
  const roomPlayers = players.filter(p => p.roomId == roomId);
  io.to(roomId).emit('update', roomPlayers);
}


Enter fullscreen mode Exit fullscreen mode

But of course, in our scenario, there are no other players to send the information to as we've created a new game with the intention of inviting our team to play!

Let's invite our team by clicking the Invite players button:

A little bit of hacking was needed to get this to work on the front-end. In order to copy something to the clipboard the value that is to be copied must be within an input tag. My button is quite clearly not an input so I had to create a temporary input which gets appended to the body, the value of the current URL is put into that input - window.location.href - we then copy the value to the clipboard and finally remove the element.



public copyToClipboard() {
    const tempInput = document.createElement("input");
    tempInput.value = window.location.href;
    document.body.appendChild(tempInput);
    tempInput.select();
    document.execCommand("copy");
    document.body.removeChild(tempInput);
    this.showCopiedToClipboard = true;
    setTimeout(() => this.showCopiedToClipboard = false, 3000);
  }


Enter fullscreen mode Exit fullscreen mode

So, go ahead and send the link to your team!

So Bill is quite eager and dished out a vote quite early:

That vote is sent to the server through our socket connection:



  public performVote(vote: string) {
    store.state.socket.emit('vote', vote);
  }


Enter fullscreen mode Exit fullscreen mode

And then subsequently sent to all the clients in the room so they can see that Bill has voted:

socket.on('vote', (vote) => {
  let player = players.find(p => p.id == socket.id);
  if (player) {
    player.vote = vote;
  }
  updateClientsInRoom(roomId);
});
Enter fullscreen mode Exit fullscreen mode

When we've all voted, it's time to show the votes. Let's click Show Votes!

This follows the same process as above, sending a 'show' event which gets sent to all clients and upon receiving the event on the front-end we set showVotes to true which just makes the votes visible:

store.state.socket.on('show', () => {
  this.showVotes = true;
})
Enter fullscreen mode Exit fullscreen mode

Jeez, turns out Elon is pretty optimistic when it comes to estimating, who'd have guessed?

And that's pretty much it for this week's hack, it's been fun to make something I might actually use more than once! Hope you've enjoyed the blog, follow me on Twitter https://twitter.com/luke_garrigan where I just talk even more dribble!

If you enjoyed this blog I write quite a bit more on http://codeheir.com/ 🚀

💖 💪 🙅 🚩
lukegarrigan
Luke Garrigan

Posted on February 27, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

What was your win this week?
weeklyretro What was your win this week?

November 29, 2024

Where GitOps Meets ClickOps
devops Where GitOps Meets ClickOps

November 29, 2024

How to Use KitOps with MLflow
beginners How to Use KitOps with MLflow

November 29, 2024