:ukraine: A self-hosted app for keeping track of employee wellbeing and dislocation during the Russo-Ukrainian war, with an interactive map.
On February 24th, 2022, the lives of the entire Ukrainian nation were disrupted and thrown into uncertainty because of the unprovoked, illegal invasion by the Russian Federation. As a result, millions have been dislocated and are at risk within war zones throughout Ukraine.
Ukrainian businesses have become key in the resistance – providing aid to employees, coordinating humanitarian aid nation-wide, and contributing to anti-war initiatives, all while trying to keep their organizations viable.
Together App aims to help organizations be tight-knit communities for their members. It’s a simple way to know where your people are, to know when and how to help each other, and to have moments of real human connection when they are most needed.
After all, we are in this together.
Deploying Together App
·
Customization
·
Open a GitHub Issue
In times of crisis, people move around quickly, connection gets disrupted, and safety conditions change rapidly. Every day, the Together App Slack bot asks members to check in – providing information about their whereabouts, wellbeing, and their ability to contribute to team efforts.
The check-in only takes seconds but it provides a clear, actionable overview of organization members for the wellbeing stakeholders. Nobody is left behind and everybody is on the same page.
Together App’s check-in requests are sent to all members via its Slack bot. We use Slack because it’s a trusted and reachable tool for companies worldwide.
Additionally, the application never stores a member’s exact location to avoid collecting sensitive data like a refugee camp or bomb shelter in a city under attack. It stores only the coordinates of the actual city in which the member is located.
These protective measures are crucial now that many Ukrainian companies are withstanding constant cyber threats coming from Russia. With Together App, your company’s and your people’s data is safe.
In addition to providing a dashboard accessible for wellbeing stakeholders, Together App puts every team member on an interactive map. Using the map, members can see who is staying in the same city, meet up, and help each other.
The map also became crucial for coordinating the logistics of humanitarian efforts and connecting members worldwide. Plus, it just feels good to see everybody at the same time, on the same map.
Together App has two key components:
Aside from automatically requesting check-ins, members can proactively check in using the /together
slash command and the Together App global shortcuts.
Upon deploying your self-hosted version of Together App, a dashboard will be available on the domain, where you and other organization members can view members and their check-in data, as well as the interactive map.
Actual check-in data is limited to Together App administrators (which can be added in the dashboard), while the map is visible to all members of the organization who have access through your SSO or OAuth provider.
Below is an in-depth guide on how you can deploy your own instance of Together App for your organization. This is intended to take no longer than an hour.
The following guide assumes that your organization already has:
If you do not, you will need to set these up in order to use Together App.
This guide will walk you through deploying Together App on Heroku. However, almost all the steps here are globally true for and easily applied to any hosting.
Below is the list of required environment variables. Depending on where you are deploying Together App, these may need to be set either in the .env
file or elsewhere (i.e. the Heroku dashboard).
This deployment guide will walk you through populating the values of the environment variables and will explain which each is used for.
NODE_ENV=
PORT=
HOST=
DATABASE_URL=
SLACK_TOGETHER_APP_TOKEN=
SLACK_TOGETHER_APP_SIGNING_SECRET=
SLACK_MONITORING_CHANNEL_ID=
SLACK_ORGANIZATION_CHANNEL_ID=
SLACK_WORKSPACE_ID=
TOGETHER_ADMINISTRATOR_SLACK_USER_ID=
GOOGLE_GEOCODING_API_TOKEN=
GOOGLE_PLACES_API_TOKEN=
MAPBOX_MAP_TOKEN=
OKTA_CLIENT_ID=
OKTA_CLIENT_SECRET=
OKTA_ISSUER=
NEXTAUTH_URL=
NEXTAUTH_SECRET=
ALLOWED_REFERRER_ID=
JOBS_API_TOKEN=
The easiest way to get started with Together App is to deploy it to Heroku.
HOST
and NEXTAUTH_URL
to your custom or Heroku domain.NODE_ENV
to production
.PORT
environment variable, as Heroku application ports are dynamic.Together App requires a PostgreSQL database.
DATABASE_URL
environment variable is set for you.DATABASE_URL
environment variable.Together App integrates with Slack. It is there that your organization's members will receive check in requests and where member wellbeing stakeholders will receive notifications. The reasoning behind using Slack is that it is the most popular collaboration tool for organizations and is generally a very safe, trusted environment, which is important in light of currently heightened worries of phishing and other cyber attacks.
To create your Slack application:
manifest.yml
file in the slack
folder.{{host}}
variable with the domain on which you'll be hosting Together App.slack
folder.SLACK_TOGETHER_APP_SIGNING_SECRET
.SLACK_TOGETHER_APP_TOKEN
environment variable.:warning: Note that Together App has been created to work in a single Slack workspace. If your organization is on the Enterprise Grid, you will need to create an instance of Together App for each workspace.
display_information:
name: Together App
description: An app to help us stay together during these tough times.
background_color: "#36373d"
features:
bot_user:
display_name: Together App
always_online: true
shortcuts:
- name: Check In Someone Else
type: global
callback_id: renderCheckInOtherMemberConfirmation
description: Check in another member of your organization with Together App.
- name: Check In
type: global
callback_id: renderCheckInSelfConfirmation
description: Check in using Together App.
- name: Repeat Last Check In
type: global
callback_id: renderRepeatCheckInConfirmation
description: Repeat your last check in with Together App.
slash_commands:
- command: /together
url: https://{{host}}/api/slack/command
description: Call the Together App menu to check in.
should_escape: false
oauth_config:
scopes:
bot:
- commands
- users:read
- users:read.email
- chat:write
settings:
interactivity:
is_enabled: true
request_url: https://{{host}}/api/slack/action
org_deploy_enabled: false
socket_mode_enabled: false
token_rotation_enabled: false
There is some other information from your Slack workspace that Together App needs in order to function.
Namely:
To get this information:
SLACK_WORKSPACE_ID
environment variable.TOGETHER_ADMINISTRATOR_SLACK_USER_ID
environment variable.SLACK_ORGANIZATION_CHANNEL_ID
environment variable.SLACK_MONITORING_CHANNEL_ID
environment variable.:warning: Make sure that Together App has been added to those channels.
Together App uses two Google APIs to make the check in process as easy as possible – the Geocoding API and the Places API. You need to create API tokens for both.
Follow these steps:
GOOGLE_GEOCODING_API_TOKEN
environment variable.GOOGLE_PLACES_API_TOKEN
environment variable.:warning: If you run into issues with detecting location while checking in, please go through the outlined restrictions above and double check that your tokens comply.
Together App uses MapBox to render the interactive map of an organization's members.
Follow these steps:
pk
) for MapBox according to their documentation.MAPBOX_MAP_TOKEN
environment variable.Out of the box, Together App supports signing in with Okta as the main SSO provider. However, the app is built with NextJS, and the package NextAuth supports multiple SSO and OAuth providers.
In this section are the instructions for setting up Okta authentication. View the section on customizations to learn how to set up a custom provider.
To set up Okta authentication:
https://{{host}}/api/auth/callback/okta
.OKTA_CLIENT_ID
environment variable.OKTA_CLIENT_SECRET
environment variable.OKTA_ISSUER
environment variable.There are additional secrets that are needed for Together App to function. Each one is a random string. You can use key generators to create them:
NEXTAUTH_SECRET
– a random string used to hash tokens, sign/encrypt cookies and generate cryptographic keys.ALLOWED_REFERRER_ID
– random string used to verify whether or not to render the check in page when redirected from Slack (other protection methods are in place, too).JOBS_API_TOKEN
– a random string used to verify whether or not a request to the https://{{host}}/api/jobs/*
endpoints are valid. These endpoints are used for scheduled jobs and do not return any data.Now that the environment has been populated, you're ready to deploy.
First, we need to deploy the code to the Heroku app:
To view the build and deploy progress, open your Terminal and type in:
heroku logs -t -a {{heroku-app-name}}
Before you can use Together App, you'll need to initiate the database scheme and seed data.
First, open the command line within your Heroku application:
heroku run bash -a {{heroku-app-name}}
Then, to update the schema, run the following command inside the application:
npm run migrate:production
Then, you need to seed the data. Please note that your server needs to be up and running before running this command:
npm run initialize
This command syncs the database with your Slack workspace and provides administrator permissions to the member whose Slack ID was set in the TOGETHER_ADMINISTRATOR_SLACK_USER_ID
environment variable.
Together App relies on scheduled jobs for syncing users with your Slack workspace, sending check in requests, reminding members to check in, and notifying stakeholders about members who haven't checked in recently.
These are invoked calling the following scripts:
npm run sync
– this syncs the Together App member base with your Slack workspace, importing newly added members and updating those with status changes (deleted, restricted, etc.).npm run request
– this sends a check-in request message to the organization-wide channel and as a direct message to each member.npm run remind
– this sends a check in reminder for members who haven't checked within a certain number of hours. This defaults to 24 hours, but is configurable.npm run notify
– this sends a notification to the monitoring channel for members who haven't checked in within a certain number of hours. This defaults to 24 hours, but is configurable.To set these up on Heroku:
Note that it is up to you how often and when to call these scripts. You might want to just request check-ins once per week. Aside from that, there are other customizations available, too.
An example algorithm for daily check-ins might be:
npm run sync
npm run request
npm run remind
npm run notify
npm run sync
This will:
But it's completely up to you and your organization. You will, however, want to call the npm run sync
script at least once on a daily basis, at a time when most of your members are not active.
Together App comes with a docker-compose.yml
file to make it easy to deploy locally. Overall, most of the steps above apply to deploying locally. There are just a few changes needed to do so.
.env
file in the project's root.NODE_ENV
environment variable to local
.DATABASE_URL
environment variable to postgres://admin:admin@db:5432/together
.PORT
environment variable to 3000
.HOST
and NEXTAUTH_URL
environment variables to the NGROK host.Once you've set up the environment, open the Terminal and run the following commands.
To install dependencies:
npm install
To build the project:
npm run build
To start up the server:
docker-compose up
To sync the database schema, you first need to navigate to the container:
docker exec -it together sh
Then run the migrations:
npm run migrate:local
Then seed data. Please note that your server needs to be up and running before running this command:
npm run initialize
The same goes with scheduled tasks – they need to run inside of the Docker container.
Together App has a few ways to customize the business logic for your instance. There are configuration files with type definitions location in config/custom
that you can directly edit – the content of these files will not change with further releases.
Authentication is implemented using NextAuth, so Together App supports any of the authentication providers supported by NextAuth.
To use a provider other than the default Okta, you can add it to config/custom/auth-provider-config.ts
:
import type { Nullable, AuthProviderConfig } from '../../types';
import SlackProvider from 'next-auth/providers/slack';
const SLACK_CLIENT_ID = process.env.SLACK_CLIENT_ID!;
const SLACK_CLIENT_SECRET = process.env.SLACK_CLIENT_SECRET!;
export const authProviderConfig: Nullable<AuthProviderConfig> = {
provider: SlackProvider({
clientId: SLACK_CLIENT_ID,
clientSecret: SLACK_CLIENT_SECRET,
}),
type: 'slack',
};
By default, members that are either restricted or ultra-restricted in your Slack workspace will not be included in check-in requests, the dashboard, or the map. They won't be able to check in or access data.
This can be configured by modifying config/custom/filter-slack-member-rule.ts
:
import type { Nullable, FilterSlackMemberRule } from '../../types';
export const filterSlackMemberRule: Nullable<FilterSlackMemberRule> = {
filterRestricted: false, // Restricted members will also be in Together App
filterUltraRestricted: true,
};
By default, when the scheduled job npm run request
is invoked, a request is sent to both the organization-wide channel and to each member as a direct message, from the Slack app.
This can be configured by modifying config/custom/check-in-request-rule.ts
:
import type { Nullable, CheckInRequestRule } from '../../types';
export const checkInRequestRule: Nullable<CheckInRequestRule> = {
requestCheckInDirectMessage: false, // The message will not be sent as a direct message
requestCheckInOrganizationChannel: true,
};
By default, when running the npm run notify
scheduled job, Together App notifies you in the monitoring channel about members that haven't checked in within the 24 hours previous to the job being invoked.
This can be configured by modifying config/custom/notify-if-not-checked-in-within-rule.ts
:
import type { Nullable, NotifyOfLateCheckInRule } from '../../types';
export const notifyIfNotCheckedInWithinRule: Nullable<NotifyOfLateCheckInRule> = {
hours: 12, // Will notify stakeholders of members not checked in within last 12 hours
};
By default, when running the npm run remind
scheduled job, Together App queries members who have not checked in within the last 24 hours and sends each one a reminder, requesting a check in.
This can be configured by modifying config/custom/remind-if-not-checked-in-within-rule.ts
:
import type { Nullable, RemindMemberOfLateCheckInRule } from '../../types';
export const remindIfNotCheckedInWithinRule: Nullable<RemindMemberOfLateCheckInRule> = {
hours: 48, // Will remind members not checked in within last 48 hours to check in
};
Every time a member checks in Together App determines whether or not the member is at risk or in need of assistance, and sends a notification to the monitoring channel.
By default, it considers a member at risk if they meet one of the following criteria:
This can be configured by declaring a function in config/custom/member-is-at-risk-rule.ts
:
import type { Nullable, MemberIsAtRiskRule } from '../../types';
import type { Member } from '../../entities';
export const memberIsAtRiskRule: Nullable<MemberIsAtRiskRule> = (member: Member): boolean => {
const isSafe = member.checkIn && member.checkIn.isSafe;
const isNotMobilized = !member.isMobilized;
return !Boolean(isSafe && isNotMobilized); // No check in within 24 hours is no longer considered at risk
};
By default, Together App uses console.log()
for errors. You can also declare your own logger that complies to the provided Logger
interface:
import type { Logger } from '@slack/logger';
import type { Nullable } from '../../types';
import SomeLogger from '../../logger';
export const logger: Nullable<Logger> = new SomeLogger();
Ray East (@raycharius)
Vadym Grabovyi (@hraboviyvadim)
Boris Zagorodniy (@BZahorodnii)
Taras Neporozhniy (@korywka)
Alexey Chernyshov (@ft502)