feat: adds last active timestamp (#17261)
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
This commit is contained in:
co-authored by
Anik Dhabal Babu
parent
6cfe9ff88d
commit
97b920744e
@@ -67,6 +67,7 @@ describe("generate Csv for Org Users Table @test", () => {
|
||||
completedOnboarding: true,
|
||||
teams: [],
|
||||
attributes: [],
|
||||
lastActiveAt: "",
|
||||
};
|
||||
|
||||
it("should throw if no headers", () => {
|
||||
|
||||
@@ -54,10 +54,11 @@ export async function getServerSession(options: {
|
||||
return cachedSession;
|
||||
}
|
||||
|
||||
const userFromDb = await prisma.user.findUnique({
|
||||
const userFromDb = await prisma.user.update({
|
||||
where: {
|
||||
email: token.email.toLowerCase(),
|
||||
},
|
||||
data: { lastActiveAt: new Date() },
|
||||
});
|
||||
|
||||
if (!userFromDb) {
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
} from "@calcom/features/ee/workflows/lib/allowDisablingStandardEmails";
|
||||
import { scheduleWorkflowReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler";
|
||||
import { getFullName } from "@calcom/features/form-builder/utils";
|
||||
import { UsersRepository } from "@calcom/features/users/users.repository";
|
||||
import type { GetSubscriberOptions } from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
import {
|
||||
@@ -986,6 +987,11 @@ async function handler(
|
||||
originalRescheduledBooking,
|
||||
});
|
||||
|
||||
if (booking?.userId) {
|
||||
const usersRepository = new UsersRepository();
|
||||
await usersRepository.updateLastActiveAt(booking.userId);
|
||||
}
|
||||
|
||||
evt.uid = booking?.uid ?? null;
|
||||
evt.oneTimePassword = booking?.oneTimePassword ?? null;
|
||||
|
||||
|
||||
@@ -378,6 +378,11 @@ export default function MemberList(props: Props) {
|
||||
return filterValue.includes(rows.getValue(id));
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "lastActiveAt",
|
||||
header: "Last Active",
|
||||
cell: ({ row }) => <div>{row.original.lastActiveAt}</div>,
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
|
||||
@@ -342,6 +342,11 @@ export function UserListTable() {
|
||||
},
|
||||
},
|
||||
...generateAttributeColumns(),
|
||||
{
|
||||
id: "lastActiveAt",
|
||||
header: "Last Active",
|
||||
cell: ({ row }) => <div>{row.original.lastActiveAt}</div>,
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
enableHiding: false,
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface UserTableUser {
|
||||
accepted: boolean;
|
||||
disableImpersonation: boolean;
|
||||
completedOnboarding: boolean;
|
||||
lastActiveAt: string;
|
||||
teams: {
|
||||
id: number;
|
||||
name: string;
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { User } from "@prisma/client";
|
||||
|
||||
export interface IUsersRepository {
|
||||
updateLastActiveAt(userId: number): Promise<User>;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { captureException } from "@sentry/nextjs";
|
||||
|
||||
import db from "@calcom/prisma";
|
||||
|
||||
import type { IUsersRepository } from "./users.repository.interface";
|
||||
|
||||
export class UsersRepository implements IUsersRepository {
|
||||
async updateLastActiveAt(userId: number) {
|
||||
try {
|
||||
const user = await db.user.update({
|
||||
where: { id: userId },
|
||||
data: { lastActiveAt: new Date() },
|
||||
});
|
||||
return user;
|
||||
} catch (err) {
|
||||
captureException(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" ADD COLUMN "lastActiveAt" TIMESTAMP(3);
|
||||
@@ -277,6 +277,7 @@ model User {
|
||||
appTheme String?
|
||||
createdDate DateTime @default(now()) @map(name: "created")
|
||||
trialEndsAt DateTime?
|
||||
lastActiveAt DateTime?
|
||||
eventTypes EventType[] @relation("user_eventtype")
|
||||
credentials Credential[]
|
||||
teams Membership[]
|
||||
|
||||
@@ -106,6 +106,7 @@ export const listMembersHandler = async ({ ctx, input }: GetOptions) => {
|
||||
timeZone: true,
|
||||
disableImpersonation: true,
|
||||
completedOnboarding: true,
|
||||
lastActiveAt: true,
|
||||
teams: {
|
||||
select: {
|
||||
team: {
|
||||
@@ -164,6 +165,13 @@ export const listMembersHandler = async ({ ctx, input }: GetOptions) => {
|
||||
accepted: membership.accepted,
|
||||
disableImpersonation: user.disableImpersonation,
|
||||
completedOnboarding: user.completedOnboarding,
|
||||
lastActiveAt: membership.user.lastActiveAt
|
||||
? new Intl.DateTimeFormat(ctx.user.locale, {
|
||||
timeZone: ctx.user.timeZone,
|
||||
})
|
||||
.format(membership.user.lastActiveAt)
|
||||
.toLowerCase()
|
||||
: null,
|
||||
avatarUrl: user.avatarUrl,
|
||||
teams: user.teams
|
||||
.filter((team) => team.team.id !== organizationId) // In this context we dont want to return the org team
|
||||
|
||||
@@ -25,6 +25,7 @@ const userSelect = Prisma.validator<Prisma.UserSelect>()({
|
||||
id: true,
|
||||
bio: true,
|
||||
disableImpersonation: true,
|
||||
lastActiveAt: true,
|
||||
});
|
||||
|
||||
export const listMembersHandler = async ({ ctx, input }: ListMembersHandlerOptions) => {
|
||||
@@ -92,6 +93,13 @@ export const listMembersHandler = async ({ ctx, input }: ListMembersHandlerOptio
|
||||
disableImpersonation: user.disableImpersonation,
|
||||
bookerUrl: getBookerBaseUrlSync(profile?.organization?.slug || ""),
|
||||
teamId: member.teamId,
|
||||
lastActiveAt: member.user.lastActiveAt
|
||||
? new Intl.DateTimeFormat(ctx.user.locale, {
|
||||
timeZone: ctx.user.timeZone,
|
||||
})
|
||||
.format(member.user.lastActiveAt)
|
||||
.toLowerCase()
|
||||
: null,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user