Context
I'm putting together a volunteer sign up sheet (an Interface). The sheet goes live at a specific time, and people will be waiting by their computers so they can get the more desirable shifts. Once a shift is assigned, it no longer appears on the sheet (it is removed from the View underlying the Interface). However, it's not immediate. This means multiple people can click a shift's "Pick Shift" button before the record is removed from the view and button inaccessible.
I need the first person to click the button — or at least the first click we hear about because of latency — to be the one who gets the shift. (This would be trivial in SQL, including in Supabase on which Airtable is built, but, alas…).
Believe-it-or-not, I figured out how to do this (or at least get extremely close) with a Script in an automation. It's low-key bonkers, but it works! The problem I'm hitting now is how to notify the correct winner.
The setup: My schema (simplified)
Table: Volunteers
- Information about the volunteers
Table: Shifts
- «Various fields about the shift»
- Is Available (Checkbox) which the signup Interface's View filters on
- Assignment Status (Single select: Untouched, Assigned, Swapped, Processing, Released, etc.)
- Assignee (Link to single Volunteers record)
- Last successful transaction (Link to a single Shift Registration Activities record)
Table: Shift Registration Activities
- Shift (Link to a single Shift record)
- Volunteer (Link to a single Volunteer record)
- Created date (Date)
- Activity (Single select) indicates the type of activity being logged, e.g., Assign, Drop, Swap, etc.
The setup: How I'm handling race conditions (simplified)
I'll just focus on the registration automations since they're the most likely to collide. Clicking the Pick Shift button kicks off an automation that does the following:
- Update Record: un-check the Is Available field for the record whose button was clicked. (Doing this immediately minimizes the amount of time the record lingers in the View.)
- Create record: Create a record in the Shift Registration Activities table to log the activity
- Find records: Get all of the Shift Registration Activities records for the shift whose button was clicked.
- Run script: Assign the shift to the Volunteer linked to in the record with the earliest Created date. (More complicated, but that's the idea.)
This means that later concurrent instances of the Automation will draw the same or better conclusion about who to award the shift to. I say "or better" because I don't know if I can trust that the a record appearing later will always have a later Created date. So if a record with an earlier Created date appears after an automation finishes (or, rather, after an automation retrieves the data it bases its decision on), later Automations are more likely to have a complete picture and, thus, draw a better result about who was first.
In other words, the winner of the race might change when later Automations re-examine more up-to-date data, but it changes for the better.
The problem: How to notify the correct volunteer/assignee
- Having a notification (Slack message) immediately run at the end of the Script or in a subsequent Action of the same Automation won't work because the winner might change.
- Having a separate Automation triggered by the Shifts > Assignee field changing results in the same problem.
- Having a separate Automation that runs periodically won't work because they need to be notified in a few seconds and the fastest frequency for the Specific Time trigger is once every 15 minutes.
The best solution I can think of is to have a slight delay (5 seconds, maybe 10) The record will have disappeared from the view, so additional button clicks will stop, and all the automations should finish by then. Unfortunately, await new Promise(resolve => setTimeout(resolve, 5000)); doesn't work in the Automation Script action because setTimeout isn't implemented.
So now I'm faced with this awful approach:
// Function to simulate a delay
function delay(milliseconds) {
let start = new Date().getTime();
while (new Date().getTime() - start < milliseconds) {
// Busy-wait loop
}
}
// Simulate a 5-second delay
delay(5000);
Does anyone have a better solution? I hate to go whole hog on processors like that.