Realtime collaboration
This post describes how I built a realtime collaboration app that scrum teams can use for planning poker. Planning poker, also known as Scrum poker, is a gamified technique for estimating the size of user stories. This helps in deciding how many stories can be put into a sprint. Usually story points in the Fibonacci scale is used as a metric, but T-shirt sizing, like small, medium, and large, is also used.
I've worked in many different development teams that used planning poker. Each team had their own solution for doing this online while we're all working from home. These solutions ranged from chat messaging, plugins for their project management tools, to dedicated web apps. For me the chat messaging option was the least preferred since it was too easy to become biased once the first team member submits their vote. To have a good planning poker session it is important to be able to vote without being influenced by others. Realtime collaboration tools are essential these days, and I love using tools that make collaboration easy and intuitive.
Onboarding @ Ably
This project is part of my onboarding at Ably, where I recently started as Sr Developer Advocate. I was asked to build something with Ably, Azure and a JavaScript front-end framework to learn the Ably Realtime SDK. I wanted to build something fun but still practical, and that's when I came up with Agile Flush, an online planning poker app. This is mostly intended as a learning exercise. One with a useful end result though, that can be used here: agileflush.ably.dev. The source code can be found at GitHub and I've also recorded a YouTube video about this project that you can watch here.
Agile Flush
The best products are those that are very low friction to use. For Agile Flush this means no signup and login, and not asking users to provide any information about themselves or the planning session. The idea is that team members join their regular online planning call in their communication tool of choice, and in addition visit the Agile Flush website, and are able to start voting immediately.
Figure 1: The Agile Flush Application
In terms of functionality this application is quite basic:
- A new session can be started.
- A card can be selected & unselected.
- The visibility of the voting can be toggled on/off.
- The voting can be reset.
The flow diagram in Figure 2 shows the steps and conditions.
Figure 2: Agile Flush functionality
All actions, except starting a new session, will synchronise data across all the participants of a session. The Show/hide votes and Reset votes actions will trigger the same functionality for the other participants. Selecting a card will increase the (initially hidden) card count for the other participants, but the selection of the card is only visible for the participant who performs the action.
The Tech Stack: Vue.js, Node.js, Ably & Azure Static Web Apps
Figure 3: Main technical components
Agile Flush is built with Vue.js as the front-end framework, a Node.js Azure Function to do the authentication with Ably, Ably as the realtime messaging component, and Azure Static Web Apps to host the Vue.js app and the Node function.
Vue.js application
The front-end is built using Vue.js v3. In addition it uses Vue Router and Vuex. The Vue.js application consists of the following components:
- HomePage.vue; the main layout for the application.
- SessionSection.vue, the layout that is shown once a voting session has started.
- CardItem.vue; this component is used for each voting card in the Vuex store cards collection.
- FooterSection; one paragraph showing social links.
Figure 4 shows the placement of the components. A detailed description for the components is provided in the Building the application section.
Figure 4: Layout of the Vue Components
Node.js Function
The Vue.js application needs to communicate safely with Ably. An authentication token is required when creating a new Ably Realtime instance, but that token shouldn’t be hardcoded in the front-end since it could be read and misused by others. Instead, the authentication token is requested from Ably via the createTokenRequest function that is written in Node.js and is running as an Azure Function in the backend. That function uses an Ably API key that is retrieved from the application settings that no-one can see. The function creates an instance of the Ably Realtime client, and that instance is used to get a new authentication token from Ably. When the Vue.js app creates a new Ably Realtime instance, the url of the Node.js Azure Function is provided in the authUrl parameter to safely obtain the token.
Pub/Sub with Ably
Agile Flush is a realtime collaboration tool. Each participant submits their vote (or undo their vote) on their client application and all these actions are synchronised to the other participants applications in realtime, so everyone can see the same results and discuss it. A pub/sub (publish/subscribe) mechanism is ideal for this type of communication. Ably is a realtime messaging platform that does pub/sub at scale. Each client application is both a publisher and subscriber in this case. A client triggers the publication of a vote to a specific Ably channel (bound to the session) and all clients are subscribed to this channel and handle the incoming vote message. Each client has their own Vuex store and the state of this store is kept in sync by the messages that Ably broadcasts to the subscribers.
Azure Static Web Apps
The Vue application and the Node Function need to be hosted somewhere. I’ve chosen Azure Static Web Apps since I’m very familiar with the Azure platform in general, and Static Web Apps is a good offering for websites that require some backend functionality in the form of Azure Functions. When an Azure Static Web App service is created it requires a connection to theGitHub repository where the code is. During the creation process a GitHub workflow is automatically created and added to the repository. This workflow includes steps for building & deploying of the application & Azure Function to the Azure cloud.
Building the application
In this section I’ll cover the steps I took to build Agile Flush. I’ll provide enough instructions for you to follow along and create the application from a template repository. However, if you want to see (and run) the final solution immediately, please have a look at the GitHub repository, and follow the instructions in the README.
Using the GitHub template repository
Azure Static Web Apps is a great service to host static websites and functions. I’ve been working with Azure for years so I’m most familiar with it. The Azure docs do contain several quick starts on using Azure Static Web Apps with several popular front-end frameworks but I found their Vue.js example a bit outdated. I created a new GitHub repo template instead, staticwebapp-vue-vite, which can be found here.
This staticwebapp-vue-vite template contains a fully working Azure Static Web App that is preconfigured with:
- Vue.js v3; a well known progressive JavaScript framework.
- Vue Router v4; a routing plugin for Vue.
- Vuex v4; a state management pattern and library. Recommended by my colleague Srushtika.
- Node.js API; a plain JavaScript based Azure Function.
- Vite.js; a fast JavaScript build tool. Recommended by my colleague Jo, who had been using this for our biggest project so far: FFS Chat App.
I recommend using the template to create your own GitHub repository for the Static Web App to avoid writing boilerplate code.
I also recommend using VS Code as the editor since there are several extensions available for working with Azure Static Web Apps and Azure Functions straight from VS Code. When you’ve used the template repository and open the folder with VS Code you’ll be asked to install these extensions.
Folder structure
The staticwebapp-vue-vite template contains the following folders:
- api; containing the Node.js API based on a vanilla JavaScript Azure Function.
- public; for static files, such as the favicon.ico.
- src; containing the Vue.js v3 application. This includes these subfolders:
- components; for the individual .vue components.
- router; for the VueRouter configuration.
- store; for the Vuex configuration.
To install the dependencies of both the Vue.js app and the Node.js function run this in the root:
npm run init
Now that we have the basics in place, let’s start updating the Node.js function.
Updating the Node.js function
The Node.js function needs to communicate with Ably in order to retrieve an authentication token. The Ably JavaScript client library will be used for this.
- Inside the api folder, run:
npm install ably
This installs the Ably client library to be used with the Node function.
- Rename the folder from getTitleFromApi to createTokenRequest so the purpose of the function is clear.
- Now that the Ably library is available, the HTTP function can be updated to use the Ably Realtime client. Replace the existing code in createTokenRequest/index.js with the following implementation:
const Ably = require('ably/promises');
module.exports = async function (context) {
const id = `id- + ${Math.random().toString(36).substr(2, 16)}`;
const client = new Ably.Realtime(process.env.ABLY_API_KEY);
const tokenRequestData = await client.auth.createTokenRequest({ clientId: id });
context.res = {
headers: { "content-type": "application/json" },
body: JSON.stringify(tokenRequestData)
};
};
Notice that an environment variable, ABLY_API_KEY
, is used when creating a new instance of the Ably.Realtime client. We haven’t specified any API key yet, so let’s do that first.
Creating an Ably app and using the API key
The Node.js Azure Function is going to connect to Ably and requires an API key to do this. If you don’t have an Ably account yet, sign up for one.
- If you have an account, log in to ably.com, and create a new app and copy the API key.
- The API key should go in the local.settings.json file located in the api folder of the repository. Since this file is .gitignored, you need to create the file yourself and add the following content:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"FUNCTIONS_WORKER_RUNTIME": "node",
"ABLY_API_KEY": "YOUR_API_KEY"
}
}
- Replace
YOUR_API_KEY
with the actual Ably API key.
This local.settings.json file is only used on your local machine and should not be checked into git because it usually contains keys that are meant to be kept secret. Once the application is ready to be deployed, the Ably API key needs to be added to application settings of the Azure Static Web App resource. This will be covered in the Deploying to Azure Static Web Apps section of this post.
Creating the Vue.js components
This section lists the changes and additions to the Vue files that are already present in the template repository.
1. App.vue
The App.vue component located in the src folder is the root component of the application. At the moment it only contains an import of the HomePage.vue component.
- Replace the content of App.vue with this implementation.
The template
section in this file is still the same and uses HomePage component. The script
section now contains getters and actions from the Vuex store that are shown in the Vuex store section of this post. The style
section contains the css regarding fonts and colours that are applicable for the entire application.
2. HomePage.vue
The HomePage.vue component located in the_ src/components _folder contains the template for the home page.
Replace the content of the file with this implementation.
This component imports the SessionSection
and FooterSection
components. The script section contains the start method which starts a new voting session and initiates a new connection with Ably by calling the Vuex actions startSession
and instantiateAblyConnection
respectively. In addition this component contains buttons to toggle the visibility of the voting results and resetting the voting results using the toggleShowResults
and resetVoting
actions from the Vuex store.
3. SessionSection.vue
In the src/components folder create a new file named SessionSection.vue. This component will contain the template for the layout and behaviour once a session has been started.
Replace the content of the file with this implementation.
This is a very small component that only retrieves data from the Vuex store using these two getters: getNrOfParticipantsVoted
and getNrOfParticipantsJoined
.
4. CardItem.vue
In the src/components folder create a new file named CardItem.vue. This component will contain the template for a single voting card. This component will be used for all the cards present in the cards
collection in the Vuex store.
Replace the content of the file with this implementation.
The script
section contains the selectCard
method that is used for both selecting and deselecting a card. This method calls either the doVote
or undoVote
action in the Vuex store.
5. FooterSection.vue
In the src/components folder create a new file named FooterSection.vue. This component will contain the template for the footer of the page. It will show the social links that are present as static information.
Replace the content of the file with this implementation.
6. Vuex store
The store is the heart of the Vue application since it manages the state of the application and will handle the synchronisation of the state between the connected clients through an Ably channel. The main implementation of the Vuex store is located in src/store/index.js.
Replace the content of the file with this implementation.
Add two extra files in the src/store location:
- cardModule.js; The cardModule will handle the state for the voting cards. Replace the content of the file with this implementation.
- realtimeModule.js; The realtimeModule will handle the state for anything related to Ably Realtime. Replace the content of the file with this implementation.
The realtimeModule uses the Able Realtime client to communicate with Ably. To install the client library, run this in the root of the repository:
npm install ably
The Vuex store contains the following parts:
-
state
; a collection of properties that describe the state. For example, thecards
property in the cardModule that contains the definitions for all of the voting cards:
cards: [
{
number: '0',
count: [],
style: 'card0',
},
{
number: '0.5',
count: [],
style: 'card05',
},
...
-
getters
; a collection of methods to query the state. For example, thevoteCountForCard
getter that retrieves the vote count for the specified card number:
voteCountForCard: (state) => (cardNumber) =>
state.cards.filter((card) => card.number === cardNumber)[0].count.length
-
mutations
; a collection of methods to alter the state properties. For example, theaddParticipantVoted
mutation that adds a client vote to the cards state:
addParticipantVoted(state, clientVote) {
const index = this.getters.cardIndex(clientVote.cardNumber);
if (!state.cards[index].count.includes(clientVote.clientId)) {
state.cards[index].count.push(clientVote.clientId);
}
}
-
actions
; a collection of methods that combine mutations and the Ably API to manage and synchronise the state across the clients. For example, thedoVote
action that calls theaddParticipantVoted
mutation and thepublishVoteToAbly
action:
doVote({ dispatch, commit, getters }, cardNumber) {
const clientVote = {
clientId: getters.clientId,
cardNumber,
};
commit('addParticipantVoted', clientVote);
dispatch('publishVoteToAbly', clientVote);
}
The startSession
action in store/index.js depends on a generateSessionId
method which is not yet available.
- Inside the src folder create a new folder called util.
- Create a new file here and name it sessionIdGenerator.js.
- Replace the content of the file with this implementation.
The generateSessionId
method is used to create a randomised session ID based on two adjectives and a noun. This ID is put in the query string of the application so it can be easily shared with other participants.
For more information on how the components interact with the Vuex store have a look at the sequence diagrams located in the GitHub repository.
Now everything is in place and you can start the application locally by running:
npm run all
Deploying the realtime collaboration app to Azure Static Web Apps
Deployment of your application to Azure Static Web Apps is done via the Azure portal or the Azure CLI:
In both cases you can skip the repository creation step since you’re using the staticwebapp-vue-vite GitHub repository template.
For creating Azure Static Web Apps from scratch or managing existing apps from VSCode I highly recommend using the Azure Static Web Apps extension for VS Code. This extension should be a recommended install when you’re using the staticwebapp-vue-vite template repository.
Summarizing
Realtime collaboration tools are becoming increasingly more important on an everyday basis. Luckily creating realtime experiences is getting easier for developers thanks to modular front-end frameworks like Vue.js, realtime pub/sub services such as Ably, and an abundance of various cloud services.
Agile Flush is open source, you can have a look at the GitHub repository. Feel free to fork it, and extend it to make it your own (update the cards to use T-shirt sizing anyone?). The README explains how to build & run it locally or in GitHub Codespaces. I’ve also added CodeTours that explain all the important parts in the repository. If you plan to fork Agile Flush and to use it just for your team, you can use the free tier of Azure Static Web Apps, and the free tier of Ably, so you can host & run this app at zero cost!
Top comments (0)