5 Steps how to track your Team's Mood

5 Steps how to track your Team's Mood

Personally, I’m a very structured person. And as a rule, I also try to back up everything possible with data and facts in my private life. That helps me personally a lot to draw the right conclusions for actions.

Measuring your own mood is a metric to determine over time if something is wrong. Often people don’t want to admit it to themselves, or judge the situation wrongly. Of course, the same measurement can be applied to entire teams. A team is only a strong team if the employees are satisfied and happy. Human mood pictures are an important asset, they should not only be treated anonymously but also primarily be in the data sovereignty of the teams (or at least the company). Of course, you can use external services for this, but you can also simply implement it yourself.

How about a self-built mood barometer for your team?

I have only simple requirements for it:

I will again use a Camunda Cloud workflow engine for this use case. Why? Because I don’t need to set up a cron job to poll the mood periodically, and because it allows me to easily extend the process.

A small backend is of course necessary

The backend should take care of sending and accepting the mood images. As channel I use a simple email. The email should contain five links that reflect the current mood. I chose email because everybody has an e-mail account. Be it in a private or business environment. No one has to register for a new service or install software for it.

template

I would like to use the following approach:

  1. A new process instance starts daily.
  2. A worker of a service task generates a random id for each participant and sends an email.
  3. Links in the email point to a web application that sends an HTTP request to a backend service with the mood.
  4. The backend service persists the data and checks beforehand if the request has already been submitted for the generated Id.

#1 Design the process

bpmn diagram

Hardly worth mentioning, so simple and so valuable! A special feature is the start event. It is a timer event with the following configuration:

With this configuration we tell the workflow engine to start a new instance every day.

#2 Implement the worker

The worker should do two things:

  1. Generate Id
  2. Send email

I’ve implemented the following controller to manage the moods:

import { v4 } from "uuid";
import { Document } from "../types/Document.type";
import { Mood, MoodRequest } from "../types/Mood.type";
import { User } from "../types/User.type";
import { Error, ErrorType } from "../utils/Error";
import { StorageController } from "./storage.controller";

export class MoodController {
  constructor(private store: StorageController) {}

  public createRequest(user: User) {
    const now = new Date().getTime();
    const moodRequest: MoodRequest = {
      uuid: v4(),
      team: user.team,
      ts: now,
      expiration: now + 1000 * 60 * 60 * 12,
    };
    return moodRequest;
  }

  public async saveMoodRequest(moodRequest: MoodRequest) {
    await this.store.set(Document.MOOD_REQUEST, moodRequest.uuid, moodRequest);
  }

  public async setMood(moodRequestId: string, moodValue: number) {
    const moodRequest: MoodRequest = await this.store.get(
      Document.MOOD_REQUEST,
      moodRequestId
    );
    if (!moodRequest) {
      throw new Error(ErrorType.NotFound, `Mood not found`);
    }
    const now = new Date().getTime();
    if (moodRequest.expiration < now) {
      this.store.delete(Document.MOOD_REQUEST, moodRequestId);
      throw new Error(ErrorType.BadRequest, `Request expired`);
    }
    const mood: Mood = {
      uuid: moodRequest.uuid,
      mood: moodValue,
      team: moodRequest.team,
      ts: now,
      requestTs: moodRequest.ts,
    };
    await Promise.all([
      this.store.delete(Document.MOOD_REQUEST, moodRequestId),
      this.store.set(Document.MOOD, mood.uuid, mood),
    ]);
  }
}

Via createRequest() a new record is created with a random Id and the associated team. Then the record is stored in a database.

The worker uses this controller to create the request. Afterwards the email is sent via an SMTP server. For the sake of simplicity, the name, the email address and the associated team are set as parameters.

const nodemailer = require("nodemailer");
import * as functions from "firebase-functions";
import { User } from "../../types/User.type";
import { MoodController } from "../mood.controller";
import { StorageController } from "../storage.controller";
import { ZeebeController } from "../zeebe.controller";

const PORT = 465;

export class MailWorkerController {
  constructor(
    private zeebeController: ZeebeController,
    private store: StorageController
  ) {}

  public createWorker(taskType: string) {
    this.zeebeController.getZeebeClient().createWorker({
      taskType,
      taskHandler: async (job: any, complete: any, worker: any) => {
        const user: User = {
          name: job.customHeaders.name,
          email: job.customHeaders.email,
          team: job.customHeaders.team,
        };
        try {
          await this.sendMail(user);
          complete.success();
        } catch (error) {
          complete.failure("Failed to send email");
        }
      },
    });
  }

  private async sendMail(user: User) {
    const smtpUser = functions.config().email.user;
    const smtpPass = functions.config().email.pass;
    const smtpHost = functions.config().email.host;
    const baseUrl = functions.config().email.baseurl;

    const transporter = nodemailer.createTransport({
      host: smtpHost,
      port: PORT,
      secure: true,
      auth: {
        user: smtpUser,
        pass: smtpPass,
      },
    });

    const subject = `How are you today?`;

    const moodController = new MoodController(this.store);
    const moodRequest = moodController.createRequest(user);

    await Promise.all([
      moodController.saveMoodRequest(moodRequest),
      transporter.sendMail({
        from: `"Adam" <[email protected]>`,
        to: user.email,
        subject,
        text: `Please consider a modern email client.`,
        html: this.createEmailHtml(user, baseUrl, moodRequest.uuid),
      }),
    ]);
  }

  private createEmailHtml(user: User, baseUrl: string, moodId: string) {
    let html = `<p>Hello ${user.name},</p>
    <p>How do you feel about your day today?</p>
    <p>Please click in the term which better describes your day:</p>
    <ul>
      <li><a href="${baseUrl}/mood/${moodId}/5">Excellent day</a></li>
      <li><a href="${baseUrl}/mood/${moodId}/4">Good day</a></li>
      <li><a href="${baseUrl}/mood/${moodId}/3">Average day</a></li>
      <li><a href="${baseUrl}/mood/${moodId}/2">Hard day</a></li>
      <li><a href="${baseUrl}/mood/${moodId}/1">Bad day</a></li>
    </ul>
    <p>By giving your today's feeling, you'll be able to see the average mood of your teammates.</p>
    <p>Cheers,</p>
    <p>Adam</p>
    `;
    return html;
  }
}

The implementation can be further optimized, for example, by taking the user from an (internal) user database and passing only a userId. In addition, an email service such as SendGrid or MailJet can be used for sending emails.

#3 Setup a simple Backend

The backend is very simple, it accepts the request, checks if there is already a result for the id and if not it is persisted:

const express = require("express");
import { NextFunction, Request, Response } from "express";
import { MoodController } from "../../controller/mood.controller";
import { StorageController } from "../../controller/storage.controller";
import { Error, ErrorType } from "../../utils/Error";

export class MoodRouter {
  public router = express.Router({ mergeParams: true });

  constructor(store: StorageController) {
    this.router.post(
      "/:moodRequestId/:moodValue",
      async (req: Request, res: Response, next: NextFunction) => {
        const moodRequestId: string = req.params.moodRequestId;
        const moodValue: number = Number(req.params.moodValue);

        try {
          if (!moodRequestId || !moodValue) {
            throw new Error(
              ErrorType.BadRequest,
              `Mandatory parameter missing`
            );
          }
          if (moodValue < 1 || moodValue > 5) {
            throw new Error(
              ErrorType.BadRequest,
              `Mood ${moodValue} is not in range [1,5]`
            );
          }

          const moodController = new MoodController(store);
          await moodController.setMood(moodRequestId, moodValue);

          res.send();
        } catch (error) {
          next(error);
        }
      }
    );
  }
}

#4 Use a Frontend to route your request to the Backend

Here I don’t want to go into too much detail, it’s just for routing the request ;)

#5 Start measuring!

With a few simple steps a service is created that can track your team’s mood. Since the orchestrating component is a process, you can of course add more steps, or customize the flow to send an email only on certain days.

bpmn diagram extended

And now: relax! :)

camunda zeebe automation mood
Published on 2020-12-12, last updated on 2024-05-03 by Adam
Comments or questions? Open a new discussion on github.
Adam Urban

Adam Urban is fullstack engineer, loves serverless and generative art, and is building side projects like weeklyfoo.com, flethy.com and diypunks.xyz in his free time.