Skip to content

feat: add membership creation date to Organization Member List table (CAL-5406) #21008

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion apps/api/v1/pages/api/memberships/_post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ import { membershipCreateBodySchema, schemaMembershipPublic } from "~/lib/valida
*/
async function postHandler(req: NextApiRequest) {
const data = membershipCreateBodySchema.parse(req.body);
const args: Prisma.MembershipCreateArgs = { data };
const args: Prisma.MembershipCreateArgs = {
data: {
createdAt: new Date(),
...data,
},
};

await checkPermissions(req);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export class MembershipsRepository {
async createMembership(teamId: number, userId: number, role: MembershipRole, accepted: boolean) {
const membership = await this.dbRead.prisma.membership.create({
data: {
createdAt: new Date(),
role,
teamId,
userId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,11 @@ export class OrganizationsTeamsMembershipsRepository {

async createOrgTeamMembership(teamId: number, data: CreateOrgTeamMembershipDto) {
return this.dbWrite.prisma.membership.create({
data: { ...data, teamId: teamId },
data: {
createdAt: new Date(),
...data,
teamId: teamId,
},
include: { user: { select: MembershipUserSelect } },
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ export class TeamsMembershipsRepository {

async createTeamMembership(teamId: number, data: CreateTeamMembershipInput) {
return this.dbWrite.prisma.membership.create({
data: { ...data, teamId: teamId },
data: {
createdAt: new Date(),
...data,
teamId: teamId,
},
include: { user: { select: MembershipUserSelect } },
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export class MembershipRepositoryFixture {
}

async create(data: Prisma.MembershipCreateInput) {
return this.prismaWriteClient.membership.create({ data });
return this.prismaWriteClient.membership.create({ data: { createdAt: new Date(), ...data } });
}

async delete(membershipId: Membership["id"]) {
Expand All @@ -30,7 +30,7 @@ export class MembershipRepositoryFixture {

async addUserToOrg(user: User, org: Team, role: MembershipRole, accepted: boolean) {
const membership = await this.prismaWriteClient.membership.create({
data: { teamId: org.id, userId: user.id, role, accepted },
data: { createdAt: new Date(), teamId: org.id, userId: user.id, role, accepted },
});
await this.prismaWriteClient.user.update({ where: { id: user.id }, data: { organizationId: org.id } });
return membership;
Expand Down
2 changes: 2 additions & 0 deletions apps/web/playwright/fixtures/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ const createTeamAndAddUser = async (
const { role = MembershipRole.OWNER, id: userId } = user;
await prisma.membership.create({
data: {
createdAt: new Date(),
teamId: team.id,
userId,
role: role,
Expand Down Expand Up @@ -566,6 +567,7 @@ export const createUsersFixture = (
// Add teammates to the team
await prisma.membership.create({
data: {
createdAt: new Date(),
teamId: team.id,
userId: teamUser.id,
role: MembershipRole.MEMBER,
Expand Down
4 changes: 1 addition & 3 deletions apps/web/playwright/out-of-office.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -660,9 +660,7 @@ test.describe("Out of office", () => {
await page.locator('[data-testid="add-filter-button"]').click();
await page.locator('[data-testid="add-filter-item-dateRange"]').click();
await expect(
page
.locator('[data-testid="filter-popover-trigger-dateRange"] span', { hasText: "Last 7 Days" })
.nth(0)
page.locator('[data-testid="filter-popover-trigger-dateRange"]', { hasText: "Last 7 Days" }).first()
).toBeVisible();
});

Expand Down
3 changes: 3 additions & 0 deletions apps/web/public/static/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -3099,6 +3099,9 @@
"enable_delegation_credential_description": "Grant Cal.com automatic access to the calendars of all organization members by enabling delegation credential.",
"disable_delegation_credential": "Disable Delegation Credential",
"disable_delegation_credential_description": "Once delegation credential is disabled, organization members who haven’t connected their calendars will need to do so manually.",
"last_active": "Last Active",
"member_since": "Member Since",
"last_updated": "Last Updated",
"salesforce_on_cancel_write_to_event": "On cancelled booking, write to event record instead of deleting event",
"salesforce_on_every_cancellation": "On every cancellation",
"report_issue": "Report issue",
Expand Down
2 changes: 2 additions & 0 deletions apps/web/test/lib/generateCsv.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ describe("generate Csv for Org Users Table", () => {
teams: [],
attributes: [],
lastActiveAt: "",
createdAt: null,
updatedAt: null,
};

it("should throw if no headers", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export function ActiveFilters<TData>({ table }: ActiveFiltersProps<TData>) {
key={column.id}
column={column}
options={column.dateRangeOptions}
showColumnName
showClearButton
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useState, useEffect, useCallback } from "react";
import dayjs from "@calcom/dayjs";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import classNames from "@calcom/ui/classNames";
import { Badge } from "@calcom/ui/components/badge";
import { Button, buttonClasses } from "@calcom/ui/components/button";
import {
Command,
Expand Down Expand Up @@ -35,14 +36,21 @@ import { useFilterPopoverOpen } from "./useFilterPopoverOpen";
type DateRangeFilterProps = {
column: Extract<FilterableColumn, { type: ColumnFilterType.DATE_RANGE }>;
options?: DateRangeFilterOptions;
showColumnName?: boolean;
showClearButton?: boolean;
};

export const DateRangeFilter = ({ column, options, showClearButton = false }: DateRangeFilterProps) => {
export const DateRangeFilter = ({
column,
options,
showColumnName = false,
showClearButton = false,
}: DateRangeFilterProps) => {
const { open, onOpenChange } = useFilterPopoverOpen(column.id);
const filterValue = useFilterValue(column.id, ZDateRangeFilterValue);
const { updateFilter, removeFilter } = useDataTable();
const range = options?.range ?? "past";
const endOfDay = options?.endOfDay ?? false;
const forceCustom = range === "custom";
const forcePast = range === "past";

Expand Down Expand Up @@ -73,13 +81,13 @@ export const DateRangeFilter = ({ column, options, showClearButton = false }: Da
type: ColumnFilterType.DATE_RANGE,
data: {
startDate: startDate.toDate().toISOString(),
endDate: endDate.toDate().toISOString(),
endDate: (endOfDay ? endDate.endOf("day") : endDate).toDate().toISOString(),
preset: preset.value,
},
});
}
},
[column.id]
[column.id, endOfDay]
);

useEffect(() => {
Expand Down Expand Up @@ -137,6 +145,10 @@ export const DateRangeFilter = ({ column, options, showClearButton = false }: Da
customButtonLabel = `${format(startDate.toDate(), "LLL dd, y")} - ?`;
}

const selectedValue = isCustomPreset
? customButtonLabel
: t(selectedPreset.labelKey, selectedPreset.i18nOptions);

return (
<Popover open={open} onOpenChange={onOpenChange}>
<PopoverTrigger asChild>
Expand All @@ -146,8 +158,15 @@ export const DateRangeFilter = ({ column, options, showClearButton = false }: Da
StartIcon="calendar-range"
EndIcon="chevron-down"
data-testid={`filter-popover-trigger-${column.id}`}>
{!isCustomPreset && <span>{t(selectedPreset.labelKey, selectedPreset.i18nOptions)}</span>}
{isCustomPreset && <span>{customButtonLabel}</span>}
{showColumnName && (
<>
<span>{column.title}</span>
<Badge variant="gray" className="ml-2">
{selectedValue}
</Badge>
</>
)}
{!showColumnName && <span>{selectedValue}</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="flex w-fit p-0" align="end">
Expand Down
14 changes: 14 additions & 0 deletions packages/features/data-table/lib/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
isMultiSelectFilterValue,
isTextFilterValue,
isNumberFilterValue,
isDateRangeFilterValue,
} from "./utils";

type MakeWhereClauseProps = {
Expand Down Expand Up @@ -154,6 +155,19 @@ export function makeWhereClause(props: MakeWhereClauseProps) {
default:
throw new Error(`Invalid operator for number filter: ${operator}`);
}
} else if (isDateRangeFilterValue(filterValue)) {
const { startDate, endDate } = filterValue.data;
if (!startDate || !endDate) {
throw new Error(`Invalid date range filter: ${JSON.stringify({ columnName, startDate, endDate })}`);
}

return {
[columnName]: {
...jsonPathObj,
gte: startDate,
lte: endDate,
},
};
}
throw new Error(`Invalid filter type: ${JSON.stringify({ columnName, filterValue })}`);
}
3 changes: 2 additions & 1 deletion packages/features/data-table/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ export const ZFilterValue = z.union([
]);

export type DateRangeFilterOptions = {
range: "past" | "custom";
range?: "past" | "custom";
endOfDay?: boolean;
};

export type TextFilterOptions = {
Expand Down
3 changes: 3 additions & 0 deletions packages/features/ee/dsync/lib/handleGroupEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ const handleGroupEvents = async (event: DirectorySyncEvent, organizationId: numb
});
await prisma.membership.createMany({
data: newUsers.map((user) => ({
createdAt: new Date(),
userId: user.id,
teamId: group.teamId,
role: MembershipRole.MEMBER,
Expand Down Expand Up @@ -163,12 +164,14 @@ const handleGroupEvents = async (event: DirectorySyncEvent, organizationId: numb
.map((user) => {
return [
{
createdAt: new Date(),
userId: user.id,
teamId: group.teamId,
role: MembershipRole.MEMBER,
accepted: true,
},
{
createdAt: new Date(),
userId: user.id,
teamId: organizationId,
role: MembershipRole.MEMBER,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ async function createOnboardingEligibleUserAndOnboarding(data: {
async function createTestMembership(data: { userId: number; teamId: number; role?: MembershipRole }) {
return prismock.membership.create({
data: {
createdAt: new Date(),
userId: data.userId,
teamId: data.teamId,
role: data.role || MembershipRole.MEMBER,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ function UserListTableContent({ oAuthClientId }: PlatformManagedUsersTableProps)
enableHiding: false,
size: 200,
header: () => {
return `Managed Users`;
return t("managed_users");
},
cell: ({ row }) => {
if (isPending) {
Expand Down Expand Up @@ -166,7 +166,7 @@ function UserListTableContent({ oAuthClientId }: PlatformManagedUsersTableProps)
{
id: "role",
accessorFn: (data) => data.role,
header: "Role",
header: t("role"),
size: 100,
cell: ({ row, table }) => {
if (isPending) {
Expand All @@ -188,7 +188,7 @@ function UserListTableContent({ oAuthClientId }: PlatformManagedUsersTableProps)
{
id: "teams",
accessorFn: (data) => data.teams.map((team) => team.name),
header: "Teams",
header: t("teams"),
size: 140,
cell: ({ row, table }) => {
if (isPending) {
Expand Down
47 changes: 46 additions & 1 deletion packages/features/users/components/UserTable/UserListTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ const initalColumnVisibility = {
member: true,
role: true,
teams: true,
createdAt: false,
updatedAt: false,
actions: true,
};

Expand Down Expand Up @@ -366,9 +368,52 @@ function UserListTableContent() {
...generateAttributeColumns(),
{
id: "lastActiveAt",
header: "Last Active",
accessorKey: "lastActiveAt",
header: t("last_active"),
enableSorting: false,
enableColumnFilter: true,
meta: {
filter: {
type: ColumnFilterType.DATE_RANGE,
dateRangeOptions: {
endOfDay: true,
},
},
},
cell: ({ row }) => <div>{row.original.lastActiveAt}</div>,
},
{
id: "createdAt",
accessorKey: "createdAt",
header: t("member_since"),
enableSorting: false,
enableColumnFilter: true,
meta: {
filter: {
type: ColumnFilterType.DATE_RANGE,
dateRangeOptions: {
endOfDay: true,
},
},
},
cell: ({ row }) => <div>{row.original.createdAt || ""}</div>,
},
{
id: "updatedAt",
accessorKey: "updatedAt",
header: t("last_updated"),
enableSorting: false,
enableColumnFilter: true,
meta: {
filter: {
type: ColumnFilterType.DATE_RANGE,
dateRangeOptions: {
endOfDay: true,
},
},
},
cell: ({ row }) => <div>{row.original.updatedAt || ""}</div>,
},
{
id: "actions",
enableHiding: false,
Expand Down
11 changes: 9 additions & 2 deletions packages/lib/server/repository/membership.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type IMembership = {
userId: number;
accepted: boolean;
role: MembershipRole;
createdAt?: Date;
};

const membershipSelect = Prisma.validator<Prisma.MembershipSelect>()({
Expand Down Expand Up @@ -71,13 +72,19 @@ const getWhereForfindAllByUpId = async (upId: string, where?: Prisma.MembershipW
export class MembershipRepository {
static async create(data: IMembership) {
return await prisma.membership.create({
data,
data: {
createdAt: new Date(),
...data,
},
});
}

static async createMany(data: IMembership[]) {
return await prisma.membership.createMany({
data,
data: data.map((item) => ({
createdAt: new Date(),
...item,
})),
});
}

Expand Down
Loading
Loading