No More Forgotten Action Items: Our Journey with the Retrospective Reminder Bot
As the Trendyol Tech team, we’ve adopted the remote working model, allowing us to work efficiently. Our meetings, such as grooming, planning, and retrospectives, are also conducted online.
In this article, I want to talk about our retrospective meetings and our Retrospective Reminder Bot (RRB), which we developed to help us remember our action items.
As the Moderation & Fraud team within Dolap, we are a team of 10 individuals, including 7 Software Engineers (4 Back-end, 2 Front-end, and 1 Dev In Test), 2 Product Managers, and 1 Team Lead.
Our team carries out its work by adopting agile methodologies and organizing bi-weekly sprints using the Scrum method. At the end of each sprint, we conduct retrospective meetings with the aim of comprehensive evaluation. During these meetings, we discuss what we have successfully accomplished, what we could do better, and in which areas we need to improve.
👥 Online Retrospective Meetings
While conducting our meetings online, we opt for the Zoom application, which offers the capability for voice and video communication. During the card creation and discussion process, we use an online retrospective board tool named MetroRetro. This tool has been widely accepted and purchased within our company, allowing us to comfortably and securely benefit from its premium features. Many teams within Trendyol create and manage their retrospective boards with this tool.
In each of our retrospective meetings, a team member takes on the responsibility of moderating the meeting. To determine the moderator, we use alphabetical order, ensuring that everyone takes turns in leading the meeting regularly. Each team member, when their turn comes, can create a new board using their preferred retrospective template. This flexibility encourages everyone’s participation and allows for the sharing of various ideas.
Throughout our journey, we’ve experimented with a variety of retrospective templates such as Glad/Mad/Sad, Liked/Learned/Lacked, and Start/Stop/Continue. Among these, we found ourselves most satisfied and at ease with the Start/Stop/Continue format. It’s currently our preferred choice for retrospectives. However, we maintain flexibility. If a moderator decides to employ a different template, we respect that choice and adjust accordingly.
Before the retrospective meeting, the moderator of that week creates a new board and shares it with the team. This way, each member can add their comments about the relevant sprint as cards to the board until the retrospective meeting.
At the beginning of the meeting, we first review the status of ongoing action items and update the status of completed action items. Then, considering that everyone may not have completed their cards before the meeting, we allow the first 5 minutes for adding or editing cards. At the end of this time, everyone adds their final touches to their cards, and the retrospective meeting officially begins.
The moderator shares their screen and goes through the cards. The comments of each team member are collected and ideas are shared. At the end of the discussion, the decision is made on whether to create an action item for the related card. If it is decided to write an action item, it is added to the Retrospective Sheet that we have created to track these items. This way, the action items that everyone in the team needs to work on are identified, and progress is monitored.
📄 Retrospective Sheet
At Trendyol, we extensively use the Google Sheets platform to manage and track our data-related tasks and processes. To monitor and store our action items that arise during retrospective meetings, we have created a g-sheet called the ‘Retrospective Sheet’. Our Retrospective Sheet consists of four sections:
- Members: This section is where we identify the team members and their Slack Member IDs. By combining this information with the ‘Assignee’ column in the ‘Action Items’ section, we provide a selectable structure.
- Action Items: This section is where we write down the action items identified.
- Manifesto: In some retrospective meetings, the action items identified may express ongoing aspects that need continuous monitoring. For example, ensuring continuous knowledge sharing within the team is important to us. This section is where we identify such continuous and non-completable items.
- Retro Boards: This section is where we keep track of our past retrospective meetings. We store information about the date, the name of the retrospective, and the link to the retrospective board used for the meeting. By combining this information with the ‘Retro Board’ column in the ‘Action Items’ section, we provide a selectable structure.
📄 Action Items Sheet
This sheet contains the action items identified after the retrospective meetings. The sheet consists of five different columns:
- Action Items: The action items are written in this column.
- Assignee: This column indicates who is responsible for each action item. This ensures that the person in charge of each task is clearly identifiable.
- Status: This column shows the status of the respective action item, indicating whether it has been completed or not. This allows us to track the team’s progress and monitor the status of open action items.
- Notes: The action item’s extra information is written in this column. We write significant information or remarks about the action item.
- Retro Board: The action item’s retrospective meeting is indicated in this column. In the retrospectives held at the conclusion of each sprint, we may use this data to link the specific action item to the corresponding sprint. In this manner, we may review the pertinent events in the past and make sure that our responses to criticism are accurate.
Note: If you want to examine it in detail, you can go to the Example Retrospective Sheet.
🤖 Retrospective Reminder Bot
As you know, actionable items are determined at the end of retrospective meetings. These action items are sometimes assigned to an individual team member, while other times they are assigned to the entire team. It is crucial to effectively track these action items. However, remembering and consistently following up on assigned action items can be challenging. Each team member is responsible for tracking the action items assigned to them and those assigned to the whole team. However, we realized that individual tracking was negatively affecting our team’s productivity.
To solve this issue, we developed a ‘Retrospective Reminder Bot (RRB)’ on Slack. This bot reminds us about the action items assigned to the entire team in the general team channel, frontend-specific items in the frontend channel, and individually assigned items through personalized messages.
Operating every Wednesday between 10–11 AM, this reminder bot sends messages to the respective assignees or relevant channels about the unfinished action items. This way, everyone can remember and work on the action items without exerting extra effort. This reminder bot facilitates the team’s collaborative process and brings more organization to tracking action items.
The developed reminder bot has proven to be a valuable tool for improving our team’s productivity and ensuring that action items are not overlooked.
🛠️ Building the Bot: Integrating Slack API and Google Apps Script
As we use Slack as our internal communication application, receiving reminder messages through Slack was the most effective approach.
Let’s tell the process we went through using the Slack API to tell the story of how our bot came to be.
⚙️ Slack Settings
- We started by creating a new application by clicking the ‘Create New App’ button on the https://api.slack.com/apps page.
- On the ‘OAuth & Permissions’ page, under ‘Bot Token Scopes’, we defined the chat:write, im:write, mpim:write, and incoming-webhook scopes. You can find all the permissions that can be granted to a Slack bot here.
- On the ‘Incoming Webhooks’ page, we activated the ‘Activate Incoming Webhooks’ feature.
- We installed the Slack bot we created in our company workspace by clicking the ‘Install to Workspace’ button.
- Returning to the ‘Incoming Webhooks’ page, we clicked the ‘Add New Webhook to Workspace’ button to select the channels where the messages would be sent and included our bot in those channels. As a team, we selected 3 channels:
- dlp-moderation-team: the channel where the entire team is present.
- dlp-moderation-devs: the channel where only the developers on the team are present.
- dlp-frontend: the channel where only the frontend developers are present.
After selecting the channels, we made a note of the Webhook URLs for the chosen channels to use later.
Once we quickly completed these steps on the Slack side, we needed an application that would allow us to send requests for webhooks and APIs to send messages to channels and individuals. We mentioned that we store the action items from retrospective meetings in a Google Sheets document. Therefore, we chose to use Google Apps Script, which can work in integration with Google Sheets, to send webhook and API requests.
Google Apps Script allows you to interact with various Google applications and automate tasks. For example, you can automate data analysis in Google Sheets, gather form responses with Google Forms, create and edit documents in Google Docs, automate emails with Gmail, and perform file operations in Google Drive.
For more information, you can visit the Google Apps Script.
⚙️ Google Apps Script Settings
We’re about to delve into the Google Apps Script settings we’ve implemented for this bot. But before that, if you’re contemplating using this bot, you can swiftly set up your own by following the quick steps outlined below.
Go to the Example Retrospective Sheet and create a copy using the ‘File -> Make a copy’ method. This method creates a new Retrospective Sheet for you by copying the Retrospective sheet and the codes in Google Apps Script. Here are the changes you need to make when you use this method:
- Go to the Apps Script editor and update the ‘BOT-USER-OAUTH-TOKEN’ fields with the bot token of your own Slack application.
- In the Members sheet, add your own team channels, team members, and the Slack Member IDs of your team members.
- In the ‘Constants.gs’ file found within Apps Script, write the names of your teams and add the webhook URLs.
Note: The channel name here must be the same as the channel name in the Members sheet.
After completing these steps, you can prepare your bot for use by completing the settings in the Google Apps Script Triggers Settings section at the end of the article.
So, how did we embark on this process and what steps did we take? Now, we’re going to discuss these in detail.
- On the Retrospective Sheet, we created a new project by navigating to Extensions -> Apps Script in the top menu.
- Within this project, we can develop using the JavaScript language in files with the .gs extension. To send messages from Slack, we created four different files:
🗂️ Main.gs
In this file, there is a function called “sendActionItemsToAssignedMembers” that runs automatically every Wednesday between 10–11 AM. This function retrieves the member list and action items from the sheet that contains our action items. It filters the unfinished action items and initiates the process of sending reminder notifications by grouping the action items according to the assigned members.
In addition to the sendActionItemsToAssignedMembers function, we also have a function named “fetchSpreadsheetData”. This function allows us to read data from our respective Retrospective Sheet. You can examine its details below.
const ACTION_ITEM_MESSAGE_INDEX = 0;
const ACTION_ITEM_ASSIGNEE_INDEX = 1;
const ACTION_ITEM_STATUS_INDEX = 2;
const MEMBER_NAME_INDEX = 0;
const MEMBER_ID_INDEX = 1;
const MEMBER_IS_CHANNEL_INDEX = 2;
function sendActionItemsToAssignedMembers() {
const actionItemsData = fetchSpreadsheetData({
sheetName: "Action Items",
startRow: 2,
startColumn: 1,
endColumn: 3
});
const membersData = fetchSpreadsheetData({
sheetName: "Members",
startRow: 2,
startColumn: 1,
endColumn: 3
});
const actionItemsByMember = filterAndGroupActionItems(actionItemsData, membersData);
for (const [memberId, actionItems] of Object.entries(actionItemsByMember)) {
let notificationPayload;
if (actionItems.isChannel) {
notificationPayload = getActionItemReminderPayloadForChannel({
channelId: memberId,
messages: actionItems.messages.join('\n\n')
});
sendNotification(webhookUrls[memberId], notificationPayload);
} else {
notificationPayload = getActionItemReminderPayloadForMember({
memberId: memberId,
messages: actionItems.messages.join('\n\n')
});
sendNotification("https://slack.com/api/chat.postMessage", notificationPayload);
}
console.log(notificationPayload);
}
}
function fetchSpreadsheetData({
sheetName,
startRow,
startColumn,
endColumn
}) {
const sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
const lastRow = sheet.getLastRow() - 1;
const dataRange = sheet.getRange(startRow, startColumn, lastRow, endColumn);
return dataRange.getValues();
}
function filterAndGroupActionItems(actionItems, members) {
const filteredActionItems = actionItems
.filter(actionItem => !actionItem[ACTION_ITEM_STATUS_INDEX])
.map(actionItem => {
const assignedMember = members.find(member => member[MEMBER_NAME_INDEX] === actionItem[ACTION_ITEM_ASSIGNEE_INDEX]);
return {
memberId: assignedMember[MEMBER_ID_INDEX],
member: actionItem[ACTION_ITEM_ASSIGNEE_INDEX],
isChannel: assignedMember[MEMBER_IS_CHANNEL_INDEX],
message: actionItem[ACTION_ITEM_MESSAGE_INDEX]
};
});
return filteredActionItems.reduce((group, actionItem) => {
const { memberId } = actionItem;
group[memberId] = group[memberId] ?? {
memberId: actionItem.memberId,
isChannel: actionItem.isChannel,
messages: []
};
group[memberId].messages.push(`:arrow_right: ${actionItem.message}`);
return group;
}, {});
}
🗂️ Payload.gs
This file contains the templates for the messages to be sent to Slack. We have crafted separate templates for both channels and team members.
Note: The BOT-USER-OAUTH-TOKEN information can be found on the ‘OAuth & Permissions’ page under the application settings we created in Slack.
function getActionItemReminderPayloadForChannel({ channelId, messages }) {
return {
"channel": channelId,
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": `@here Hi folks :wave::skin-tone-3:`
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "I wanted to remind the team of the assigned retrospective items, see you at the retro meeting :blob-sunglasses:"
}
},
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": messages
}
}
]
};
}
function getActionItemReminderPayloadForMember({ memberId, messages }) {
return {
"as_user": true,
"token": "BOT-USER-OAUTH-TOKEN",
"channel": memberId,
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": `Hi <@${memberId}> :wave::skin-tone-3:`
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "I wanted to remind you of the retrospective items assigned to you, see you at the retro meeting :blob-sunglasses:"
}
},
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": messages
}
}
]
};
}
🗂️ Constants.gs
This file contains the Webhook URLs for the Slack channels where the reminder messages will be sent. We defined the URLs we noted earlier in this file.
const webhookUrls = {
"dlp-moderation-devs": "https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX",
"dlp-moderation-team": "https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX",
"dlp-frontend": "https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX"
}
🗂️ Slack.gs
This file contains the code used to send an HTTP request to Slack.
Note: The BOT-USER-OAUTH-TOKEN information can be found on the ‘OAuth & Permissions’ page under the application settings we created in Slack.
function sendNotification(url, payload) {
const options = {
"method": "post",
"contentType": "application/json",
"muteHttpExceptions": true,
"payload": JSON.stringify(payload),
"headers": {
"Authorization": "Bearer BOT-USER-OAUTH-TOKEN",
},
};
try {
UrlFetchApp.fetch(url, options);
} catch (e) {
Logger.log(e);
}
}
After preparing these source codes on Google Apps Script, the final step was to ensure that this script would send us notifications at regular intervals.
⚙️ Google Apps Script Triggers Settings
- Within the Google Apps Script page, we accessed the ‘Triggers’ page from the menu.
- Clicking the ‘Add Trigger’ button, we added a new trigger.
- On the opened page, we defined the following settings to make this bot run every Wednesday:
🚀 Mission Accomplished: Here are Example Messages from RRB
Now we have a powerful assistant that serves to remind us of the action items we discussed during our retrospective meetings, directing us towards proactive actions and maintaining our workflow in a sustainable and effective manner.
🎯 The Advantages of the RRB
- It ensures that action items are not forgotten.
- It increases the completion rate by reminding team members of their assigned tasks.
- It eliminates the need for manual reminder processes.
- It strengthens the sense of responsibility among team members.
- It minimizes missed or overlooked action items in retrospectives.
In addition to these benefits, other teams also have the opportunity to use this reminder bot. Apart from our team, the Dolap/Seller Delivery team is currently using this bot for their retrospective items.
At Trendyol, we strive to increase efficiency by reducing manual workload and automating anything that can be automated. In this article, we provided information about the tools we use in our team’s retrospective meetings and the reminder bot we developed.
Thank you for reading.
If you want to be part of a team that tries new technologies and want to experience a new challenge every day, come to us.