Skip to content

Commit c554779

Browse files
authoredOct 4, 2023
fix: rescheduled value DB update on reschedule and insights view cancelleds (#11474)
1 parent 53655a4 commit c554779

File tree

5 files changed

+287
-4
lines changed

5 files changed

+287
-4
lines changed
 

‎apps/web/playwright/insights.e2e.ts

+239
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
import { expect } from "@playwright/test";
2+
3+
import { randomString } from "@calcom/lib/random";
4+
import prisma from "@calcom/prisma";
5+
6+
import { test } from "./lib/fixtures";
7+
8+
test.describe.configure({ mode: "parallel" });
9+
10+
const createTeamsAndMembership = async (userIdOne: number, userIdTwo: number) => {
11+
const teamOne = await prisma.team.create({
12+
data: {
13+
name: "test-insights",
14+
slug: `test-insights-${Date.now()}-${randomString(5)}}`,
15+
},
16+
});
17+
18+
const teamTwo = await prisma.team.create({
19+
data: {
20+
name: "test-insights-2",
21+
slug: `test-insights-2-${Date.now()}-${randomString(5)}}`,
22+
},
23+
});
24+
if (!userIdOne || !userIdTwo || !teamOne || !teamTwo) {
25+
throw new Error("Failed to create test data");
26+
}
27+
28+
// create memberships
29+
await prisma.membership.create({
30+
data: {
31+
userId: userIdOne,
32+
teamId: teamOne.id,
33+
accepted: true,
34+
role: "ADMIN",
35+
},
36+
});
37+
await prisma.membership.create({
38+
data: {
39+
teamId: teamTwo.id,
40+
userId: userIdOne,
41+
accepted: true,
42+
role: "ADMIN",
43+
},
44+
});
45+
await prisma.membership.create({
46+
data: {
47+
teamId: teamOne.id,
48+
userId: userIdTwo,
49+
accepted: true,
50+
role: "MEMBER",
51+
},
52+
});
53+
await prisma.membership.create({
54+
data: {
55+
teamId: teamTwo.id,
56+
userId: userIdTwo,
57+
accepted: true,
58+
role: "MEMBER",
59+
},
60+
});
61+
return { teamOne, teamTwo };
62+
};
63+
64+
test.afterAll(async ({ users }) => {
65+
await users.deleteAll();
66+
});
67+
68+
test.describe("Insights", async () => {
69+
test("should be able to go to insights as admins", async ({ page, users }) => {
70+
const user = await users.create();
71+
const userTwo = await users.create();
72+
await createTeamsAndMembership(user.id, userTwo.id);
73+
74+
await user.apiLogin();
75+
76+
// go to insights page
77+
await page.goto("/insights");
78+
await page.waitForLoadState("networkidle");
79+
80+
// expect url to have isAll and TeamId in query params
81+
expect(page.url()).toContain("isAll=false");
82+
expect(page.url()).toContain("teamId=");
83+
});
84+
85+
test("should be able to go to insights as members", async ({ page, users }) => {
86+
const user = await users.create();
87+
const userTwo = await users.create();
88+
89+
await userTwo.apiLogin();
90+
91+
await createTeamsAndMembership(user.id, userTwo.id);
92+
// go to insights page
93+
await page.goto("/insights");
94+
95+
await page.waitForLoadState("networkidle");
96+
97+
// expect url to have isAll and TeamId in query params
98+
99+
expect(page.url()).toContain("isAll=false");
100+
expect(page.url()).not.toContain("teamId=");
101+
});
102+
103+
test("team select filter should have 2 teams and your account option only as member", async ({
104+
page,
105+
users,
106+
}) => {
107+
const user = await users.create();
108+
const userTwo = await users.create();
109+
110+
await user.apiLogin();
111+
112+
await createTeamsAndMembership(user.id, userTwo.id);
113+
// go to insights page
114+
await page.goto("/insights");
115+
116+
await page.waitForLoadState("networkidle");
117+
118+
// get div from team select filter with this class flex flex-col gap-0.5 [&>*:first-child]:mt-1 [&>*:last-child]:mb-1
119+
await page.getByTestId("dashboard-shell").getByText("Team: test-insights").click();
120+
await page
121+
.locator('div[class="flex flex-col gap-0.5 [&>*:first-child]:mt-1 [&>*:last-child]:mb-1"]')
122+
.click();
123+
const teamSelectFilter = await page.locator(
124+
'div[class="hover:bg-muted flex items-center py-2 pl-3 pr-2.5 hover:cursor-pointer"]'
125+
);
126+
127+
await expect(teamSelectFilter).toHaveCount(3);
128+
});
129+
130+
test("Insights Organization should have isAll option true", async ({ users, page }) => {
131+
const owner = await users.create(undefined, {
132+
hasTeam: true,
133+
isUnpublished: true,
134+
isOrg: true,
135+
hasSubteam: true,
136+
});
137+
await owner.apiLogin();
138+
139+
await page.goto("/insights");
140+
await page.waitForLoadState("networkidle");
141+
142+
await page.getByTestId("dashboard-shell").getByText("All").nth(1).click();
143+
144+
const teamSelectFilter = await page.locator(
145+
'div[class="hover:bg-muted flex items-center py-2 pl-3 pr-2.5 hover:cursor-pointer"]'
146+
);
147+
148+
await expect(teamSelectFilter).toHaveCount(4);
149+
});
150+
151+
test("should have all option in team-and-self filter as admin", async ({ page, users }) => {
152+
const owner = await users.create();
153+
const member = await users.create();
154+
155+
await createTeamsAndMembership(owner.id, member.id);
156+
157+
await owner.apiLogin();
158+
159+
await page.goto("/insights");
160+
161+
// get div from team select filter with this class flex flex-col gap-0.5 [&>*:first-child]:mt-1 [&>*:last-child]:mb-1
162+
await page.getByTestId("dashboard-shell").getByText("Team: test-insights").click();
163+
await page
164+
.locator('div[class="flex flex-col gap-0.5 [&>*:first-child]:mt-1 [&>*:last-child]:mb-1"]')
165+
.click();
166+
const teamSelectFilter = await page.locator(
167+
'div[class="hover:bg-muted flex items-center py-2 pl-3 pr-2.5 hover:cursor-pointer"]'
168+
);
169+
170+
await expect(teamSelectFilter).toHaveCount(3);
171+
});
172+
173+
test("should be able to switch between teams and self profile for insights", async ({ page, users }) => {
174+
const owner = await users.create();
175+
const member = await users.create();
176+
177+
await createTeamsAndMembership(owner.id, member.id);
178+
179+
await owner.apiLogin();
180+
181+
await page.goto("/insights");
182+
183+
// get div from team select filter with this class flex flex-col gap-0.5 [&>*:first-child]:mt-1 [&>*:last-child]:mb-1
184+
await page.getByTestId("dashboard-shell").getByText("Team: test-insights").click();
185+
await page
186+
.locator('div[class="flex flex-col gap-0.5 [&>*:first-child]:mt-1 [&>*:last-child]:mb-1"]')
187+
.click();
188+
const teamSelectFilter = await page.locator(
189+
'div[class="hover:bg-muted flex items-center py-2 pl-3 pr-2.5 hover:cursor-pointer"]'
190+
);
191+
192+
await expect(teamSelectFilter).toHaveCount(3);
193+
194+
// switch to self profile
195+
await page.getByTestId("dashboard-shell").getByText("Your Account").click();
196+
197+
// switch to team 1
198+
await page.getByTestId("dashboard-shell").getByText("test-insights").nth(0).click();
199+
200+
// switch to team 2
201+
await page.getByTestId("dashboard-shell").getByText("test-insights-2").click();
202+
});
203+
204+
test("should be able to switch between memberUsers", async ({ page, users }) => {
205+
const owner = await users.create();
206+
const member = await users.create();
207+
208+
await createTeamsAndMembership(owner.id, member.id);
209+
210+
await owner.apiLogin();
211+
212+
await page.goto("/insights");
213+
214+
await page.getByText("Add filter").click();
215+
216+
await page.getByRole("button", { name: "User" }).click();
217+
// <div class="flex select-none truncate font-medium" data-state="closed">People</div>
218+
await page.locator('div[class="flex select-none truncate font-medium"]').getByText("People").click();
219+
220+
await page
221+
.locator('div[class="hover:bg-muted flex items-center py-2 pl-3 pr-2.5 hover:cursor-pointer"]')
222+
.nth(0)
223+
.click();
224+
await page.waitForLoadState("networkidle");
225+
226+
await page
227+
.locator('div[class="hover:bg-muted flex items-center py-2 pl-3 pr-2.5 hover:cursor-pointer"]')
228+
.nth(1)
229+
.click();
230+
await page.waitForLoadState("networkidle");
231+
// press escape button to close the filter
232+
await page.keyboard.press("Escape");
233+
234+
await page.getByRole("button", { name: "Clear" }).click();
235+
236+
// expect for "Team: test-insight" text in page
237+
expect(await page.locator("text=Team: test-insights").isVisible()).toBeTruthy();
238+
});
239+
});

‎packages/features/bookings/lib/handleNewBooking.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2148,6 +2148,7 @@ async function handler(
21482148
id: originalRescheduledBooking.id,
21492149
},
21502150
data: {
2151+
rescheduled: true,
21512152
status: BookingStatus.CANCELLED,
21522153
},
21532154
});

‎packages/features/insights/context/FiltersProvider.tsx

+6-4
Original file line numberDiff line numberDiff line change
@@ -114,17 +114,19 @@ export function FiltersProvider({ children }: { children: React.ReactNode }) {
114114
selectedFilter,
115115
isAll,
116116
dateRange,
117+
initialConfig,
117118
} = newConfigFilters;
118119
const [startTime, endTime] = dateRange || [null, null];
119-
const newSearchParams = new URLSearchParams(searchParams);
120+
const newSearchParams = new URLSearchParams(searchParams.toString());
120121
function setParamsIfDefined(key: string, value: string | number | boolean | null | undefined) {
121122
if (value !== undefined && value !== null) newSearchParams.set(key, value.toString());
122123
}
124+
123125
setParamsIfDefined("memberUserId", selectedMemberUserId);
124-
setParamsIfDefined("teamId", selectedTeamId);
125-
setParamsIfDefined("userId", selectedUserId);
126+
setParamsIfDefined("teamId", selectedTeamId || initialConfig?.teamId);
127+
setParamsIfDefined("userId", selectedUserId || initialConfig?.userId);
126128
setParamsIfDefined("eventTypeId", selectedEventTypeId);
127-
setParamsIfDefined("isAll", isAll);
129+
setParamsIfDefined("isAll", isAll || initialConfig?.isAll);
128130
setParamsIfDefined("startTime", startTime?.toISOString());
129131
setParamsIfDefined("endTime", endTime?.toISOString());
130132
setParamsIfDefined("filter", selectedFilter?.[0]);

‎packages/features/insights/filters/TeamAndSelfList.tsx

+6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ export const TeamAndSelfList = () => {
2222
const { data, isSuccess } = trpc.viewer.insights.teamListForUser.useQuery(undefined, {
2323
// Teams don't change that frequently
2424
refetchOnWindowFocus: false,
25+
trpc: {
26+
context: {
27+
skipBatch: true,
28+
},
29+
},
2530
});
2631

2732
useEffect(() => {
@@ -48,6 +53,7 @@ export const TeamAndSelfList = () => {
4853
} else if (session.data?.user.id) {
4954
// default to user
5055
setConfigFilters({
56+
selectedUserId: session.data?.user.id,
5157
initialConfig: {
5258
teamId: null,
5359
userId: session.data?.user.id,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
-- View: public.BookingsTimeStatus
2+
3+
-- DROP VIEW public."BookingsTimeStatus";
4+
5+
CREATE OR REPLACE VIEW public."BookingTimeStatus"
6+
AS
7+
SELECT "Booking".id,
8+
"Booking".uid,
9+
"Booking"."eventTypeId",
10+
"Booking".title,
11+
"Booking".description,
12+
"Booking"."startTime",
13+
"Booking"."endTime",
14+
"Booking"."createdAt",
15+
"Booking".location,
16+
"Booking".paid,
17+
"Booking".status,
18+
"Booking".rescheduled,
19+
"Booking"."userId",
20+
"et"."teamId",
21+
"et"."length" as "eventLength",
22+
CASE
23+
WHEN "Booking".rescheduled IS TRUE THEN 'rescheduled'::text
24+
WHEN "Booking".status = 'cancelled'::"BookingStatus" AND "Booking".rescheduled IS NULL THEN 'cancelled'::text
25+
WHEN "Booking"."endTime" < now() THEN 'completed'::text
26+
WHEN "Booking"."endTime" > now() THEN 'uncompleted'::text
27+
ELSE NULL::text
28+
END AS "timeStatus",
29+
"et"."parentId" as "eventParentId"
30+
FROM "Booking"
31+
LEFT JOIN "EventType" et ON "Booking"."eventTypeId" = et.id
32+
LEFT JOIN "Membership" mb ON "mb"."userId" = "Booking"."userId";
33+
34+
35+

0 commit comments

Comments
 (0)