Alert users when another user is in an app

I have an app that allows users to adjust things in a database table. However, if more than one user is changing things, the 2nd user might not see the changes.

Is there a way to alert users when someone else is using it? (not editing, but actually just using it)

Hi there @mathfour,

Unfortunately there is no native way of doing this within retool. The only thing that comes to mind is with a custom component.

Brainstorming an approach.

  • Table to store "user check-ins"
  • Whenever a user loads the page, performs an action , or periodically: a timestamp and their name is written to the check-in table
  • Then when another loads the page/performs an action/periodically a query is triggered for any users who "checked-in" within the last 5 minutes or whatever interval and displays those users

You could reduce the noise by tagging the timestamps with any unique selections the user makes on a page. e.g. if the table on-screen is data for a particular company then only show users viewing that particular company record.

I'm loving this idea... And it seems SO fun!

I'll give it a shot and let you know how it goes. Thanks for the idea!

Got it!

Here's what I now have...

  1. I have a Retool database table xxxx_user_login_tracker:

  2. I have a js query set_user_and_datetime that automatically runs ever 60 seconds that captures the data and triggers the update query:

  3. I have the update query SAVE_this_user_login:

  4. I have the GET query GET_user_logins:

  5. And the get transformer get_users_logged_in:

  6. I have a fancy checkUsersButton button that changes words and colors depending on if someone else is logged in:


  7. And I have a modal that comes up when you click the checkUsersButton:

Thank you, @ferret141, for the idea!
~Bon

And if you want to copy and paste the long bits of code, here they are:
set_user_and_datetime

var user_email = current_user.email;
var user_name = current_user.fullName;

const date = new Date();

const formattedDate = date.toLocaleDateString('en-US', {
  year: 'numeric',
  month: 'long',
  day: 'numeric',
  timeZone: 'America/New_York'
});

const formattedTime = date.toLocaleTimeString('en-US', {
  hour: 'numeric',
  minute: 'numeric',
  timeZone: 'America/New_York',
  timeZoneName: 'short'
});

SAVE_this_user_login.trigger({
  additionalScope: {
    userEmail: user_email,
    userName: user_name,
    recentActivity: date,
    recentActivityDate: formattedDate,
    recentActivityTime: formattedTime
  }
});

GET_user_logins.trigger();

return {"user_email": user_email, 
        "user_name": user_name, 
        "recent_activity": date, 
        "recent_activity_date": formattedDate,
        "recent_activity_time": formattedTime};

SAVE_this_user_login

INSERT INTO xxxx_user_login_tracker (
  user_email, 
  user_name, 
  recent_activity, 
  recent_activity_date, 
  recent_activity_time
  ) 

VALUES (
  {{ userEmail }}, 
  {{ userName }}, 
  {{ recentActivity }}, 
  {{ recentActivityDate }}, 
  {{ recentActivityTime }}
)

ON CONFLICT(user_email) DO UPDATE 
SET user_name = EXCLUDED.user_name, 
    recent_activity = EXCLUDED.recent_activity,
    recent_activity_date = EXCLUDED.recent_activity_date,
    recent_activity_time = EXCLUDED.recent_activity_time

get_users_logged_in

const USER_LOGIN_DATA = {{ formatDataAsArray(GET_user_logins.data) }};
const NOW = new Date();
const ONE_MIN_BEFORE_NOW = new Date(NOW.getTime() - (1 * 60 * 1000));

const formatted_one_min_before_date = ONE_MIN_BEFORE_NOW.toLocaleDateString('en-US', {
  year: 'numeric',
  month: 'long',
  day: 'numeric',
  timeZone: 'America/New_York'
});

const formatted_one_min_before_time = ONE_MIN_BEFORE_NOW.toLocaleTimeString('en-US', {
  hour: 'numeric',
  minute: 'numeric',
  timeZone: 'America/New_York',
  timeZoneName: 'short'
});

const recent_logins = USER_LOGIN_DATA.map(one_user => {
  const RECENT_ACTIVITY_DATE = one_user.recent_activity_date;
  const RECENT_ACIVITY_TIME = one_user.recent_activity_time;
  const RECENT_ACTIVITY = new Date(one_user.recent_activity)
  return {
    ...one_user,
    other_user_recently_logged_in: (RECENT_ACTIVITY_DATE == formatted_one_min_before_date) && (RECENT_ACIVITY_TIME > formatted_one_min_before_time) && one_user.user_email !== {{ current_user.email }} ,
    minutes_since_last_checkin: Math.abs(NOW.getTime() - RECENT_ACTIVITY.getTime()) / 60000
  };
});

let sorted_logins = recent_logins.sort((a, b) => a.minutes_since_last_checkin - b.minutes_since_last_checkin);

const LOGIN_WARNING = sorted_logins.some(user => user.other_user_recently_logged_in);

return {
  recent_logins: sorted_logins,
  LOGIN_WARNING: LOGIN_WARNING
};

DISCLAIMER: I'm a python developer by nature, so I'm not so great with javascript. Feel free to make suggestions to improve my javascript - but don't make fun of me.

This is great! Thanks for sharing, @mathfour. :slightly_smiling_face: