Merge branch 'main' into test/google-calendar

test/google-calendar
Shivam Kalra 2023-10-05 14:39:41 +05:30 committed by GitHub
commit 16ff63fa4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 365 additions and 9 deletions

View File

@ -189,7 +189,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
value: 0,
},
...[5, 10, 15, 20, 30, 45, 60, 90, 120].map((minutes) => ({
label: `minutes ${t("minutes")}`,
label: `${minutes} ${t("minutes")}`,
value: minutes,
})),
];
@ -225,7 +225,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
value: 0,
},
...[5, 10, 15, 20, 30, 45, 60, 90, 120].map((minutes) => ({
label: `minutes ${t("minutes")}`,
label: `${minutes} ${t("minutes")}`,
value: minutes,
})),
];
@ -272,7 +272,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
value: -1,
},
...[5, 10, 15, 20, 30, 45, 60, 75, 90, 105, 120].map((minutes) => ({
label: `minutes ${t("minutes")}`,
label: `${minutes} ${t("minutes")}`,
value: minutes,
})),
];

View File

@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "3.3.4",
"version": "3.3.5",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",

View File

@ -0,0 +1,239 @@
import { expect } from "@playwright/test";
import { randomString } from "@calcom/lib/random";
import prisma from "@calcom/prisma";
import { test } from "./lib/fixtures";
test.describe.configure({ mode: "parallel" });
const createTeamsAndMembership = async (userIdOne: number, userIdTwo: number) => {
const teamOne = await prisma.team.create({
data: {
name: "test-insights",
slug: `test-insights-${Date.now()}-${randomString(5)}}`,
},
});
const teamTwo = await prisma.team.create({
data: {
name: "test-insights-2",
slug: `test-insights-2-${Date.now()}-${randomString(5)}}`,
},
});
if (!userIdOne || !userIdTwo || !teamOne || !teamTwo) {
throw new Error("Failed to create test data");
}
// create memberships
await prisma.membership.create({
data: {
userId: userIdOne,
teamId: teamOne.id,
accepted: true,
role: "ADMIN",
},
});
await prisma.membership.create({
data: {
teamId: teamTwo.id,
userId: userIdOne,
accepted: true,
role: "ADMIN",
},
});
await prisma.membership.create({
data: {
teamId: teamOne.id,
userId: userIdTwo,
accepted: true,
role: "MEMBER",
},
});
await prisma.membership.create({
data: {
teamId: teamTwo.id,
userId: userIdTwo,
accepted: true,
role: "MEMBER",
},
});
return { teamOne, teamTwo };
};
test.afterAll(async ({ users }) => {
await users.deleteAll();
});
test.describe("Insights", async () => {
test("should be able to go to insights as admins", async ({ page, users }) => {
const user = await users.create();
const userTwo = await users.create();
await createTeamsAndMembership(user.id, userTwo.id);
await user.apiLogin();
// go to insights page
await page.goto("/insights");
await page.waitForLoadState("networkidle");
// expect url to have isAll and TeamId in query params
expect(page.url()).toContain("isAll=false");
expect(page.url()).toContain("teamId=");
});
test("should be able to go to insights as members", async ({ page, users }) => {
const user = await users.create();
const userTwo = await users.create();
await userTwo.apiLogin();
await createTeamsAndMembership(user.id, userTwo.id);
// go to insights page
await page.goto("/insights");
await page.waitForLoadState("networkidle");
// expect url to have isAll and TeamId in query params
expect(page.url()).toContain("isAll=false");
expect(page.url()).not.toContain("teamId=");
});
test("team select filter should have 2 teams and your account option only as member", async ({
page,
users,
}) => {
const user = await users.create();
const userTwo = await users.create();
await user.apiLogin();
await createTeamsAndMembership(user.id, userTwo.id);
// go to insights page
await page.goto("/insights");
await page.waitForLoadState("networkidle");
// get div from team select filter with this class flex flex-col gap-0.5 [&>*:first-child]:mt-1 [&>*:last-child]:mb-1
await page.getByTestId("dashboard-shell").getByText("Team: test-insights").click();
await page
.locator('div[class="flex flex-col gap-0.5 [&>*:first-child]:mt-1 [&>*:last-child]:mb-1"]')
.click();
const teamSelectFilter = await page.locator(
'div[class="hover:bg-muted flex items-center py-2 pl-3 pr-2.5 hover:cursor-pointer"]'
);
await expect(teamSelectFilter).toHaveCount(3);
});
test("Insights Organization should have isAll option true", async ({ users, page }) => {
const owner = await users.create(undefined, {
hasTeam: true,
isUnpublished: true,
isOrg: true,
hasSubteam: true,
});
await owner.apiLogin();
await page.goto("/insights");
await page.waitForLoadState("networkidle");
await page.getByTestId("dashboard-shell").getByText("All").nth(1).click();
const teamSelectFilter = await page.locator(
'div[class="hover:bg-muted flex items-center py-2 pl-3 pr-2.5 hover:cursor-pointer"]'
);
await expect(teamSelectFilter).toHaveCount(4);
});
test("should have all option in team-and-self filter as admin", async ({ page, users }) => {
const owner = await users.create();
const member = await users.create();
await createTeamsAndMembership(owner.id, member.id);
await owner.apiLogin();
await page.goto("/insights");
// get div from team select filter with this class flex flex-col gap-0.5 [&>*:first-child]:mt-1 [&>*:last-child]:mb-1
await page.getByTestId("dashboard-shell").getByText("Team: test-insights").click();
await page
.locator('div[class="flex flex-col gap-0.5 [&>*:first-child]:mt-1 [&>*:last-child]:mb-1"]')
.click();
const teamSelectFilter = await page.locator(
'div[class="hover:bg-muted flex items-center py-2 pl-3 pr-2.5 hover:cursor-pointer"]'
);
await expect(teamSelectFilter).toHaveCount(3);
});
test("should be able to switch between teams and self profile for insights", async ({ page, users }) => {
const owner = await users.create();
const member = await users.create();
await createTeamsAndMembership(owner.id, member.id);
await owner.apiLogin();
await page.goto("/insights");
// get div from team select filter with this class flex flex-col gap-0.5 [&>*:first-child]:mt-1 [&>*:last-child]:mb-1
await page.getByTestId("dashboard-shell").getByText("Team: test-insights").click();
await page
.locator('div[class="flex flex-col gap-0.5 [&>*:first-child]:mt-1 [&>*:last-child]:mb-1"]')
.click();
const teamSelectFilter = await page.locator(
'div[class="hover:bg-muted flex items-center py-2 pl-3 pr-2.5 hover:cursor-pointer"]'
);
await expect(teamSelectFilter).toHaveCount(3);
// switch to self profile
await page.getByTestId("dashboard-shell").getByText("Your Account").click();
// switch to team 1
await page.getByTestId("dashboard-shell").getByText("test-insights").nth(0).click();
// switch to team 2
await page.getByTestId("dashboard-shell").getByText("test-insights-2").click();
});
test("should be able to switch between memberUsers", async ({ page, users }) => {
const owner = await users.create();
const member = await users.create();
await createTeamsAndMembership(owner.id, member.id);
await owner.apiLogin();
await page.goto("/insights");
await page.getByText("Add filter").click();
await page.getByRole("button", { name: "User" }).click();
// <div class="flex select-none truncate font-medium" data-state="closed">People</div>
await page.locator('div[class="flex select-none truncate font-medium"]').getByText("People").click();
await page
.locator('div[class="hover:bg-muted flex items-center py-2 pl-3 pr-2.5 hover:cursor-pointer"]')
.nth(0)
.click();
await page.waitForLoadState("networkidle");
await page
.locator('div[class="hover:bg-muted flex items-center py-2 pl-3 pr-2.5 hover:cursor-pointer"]')
.nth(1)
.click();
await page.waitForLoadState("networkidle");
// press escape button to close the filter
await page.keyboard.press("Escape");
await page.getByRole("button", { name: "Clear" }).click();
// expect for "Team: test-insight" text in page
expect(await page.locator("text=Team: test-insights").isVisible()).toBeTruthy();
});
});

View File

@ -1658,7 +1658,7 @@
"no_recordings_found": "No recordings found",
"new_workflow_subtitle": "New workflow for...",
"reporting": "Reporting",
"reporting_feature": "See all incoming from data and download it as a CSV",
"reporting_feature": "See all incoming form data and download it as a CSV",
"teams_plan_required": "Teams plan required",
"routing_forms_are_a_great_way": "Routing forms are a great way to route your incoming leads to the right person. Upgrade to a Teams plan to access this feature.",
"choose_a_license": "Choose a license",

View File

@ -7,17 +7,32 @@
"second_other": "{{count}} segundo",
"upgrade_now": "Eguneratu orain",
"accept_invitation": "Onartu gonbidapena",
"calcom_explained": "{{appName}}-ek bilerak programatzeko azpiegitura eskaintzen du guztiontzat.",
"calcom_explained_new_user": "Bukatu zure {{appName}} kontua konfiguratzen! Bileren programazio-arazo guztiak konpontzeko urrats gutxi batzuk besterik ez zaizkizu geratzen.",
"have_any_questions": "Galderarik? Laguntzeko gaude.",
"reset_password_subject": "{{appName}}: Pasahitza berrezartzeko argibideak",
"verify_email_subject": "{{appName}}: egiaztatu zure kontua",
"check_your_email": "Begiratu zure emaila",
"verify_email_page_body": "Email bat bidali dugu {{email}} helbidera. Garrantzitsua da zure email helbidea egiaztatzea, {{appName}}-tik mezuak eta egutegiko eguneratzeak ahalik eta hobekien jasoko dituzula bermatzeko.",
"verify_email_banner_body": "Egiaztatu zure email helbidea mezuak eta egutegiko eguneratzeak ahalik eta hobekien jasoko dituzula bermatzeko",
"verify_email_email_header": "Egiaztatu zure email helbidea",
"verify_email_email_button": "Egiaztatu emaila",
"verify_email_email_body": "Mesedez, egiaztatu zure email helbidea beheko botoia sakatuz.",
"verify_email_by_code_email_body": "Mesedez, egiaztatu zure email helbidea beheko kodea erabiliz.",
"verify_email_email_link_text": "Hemen duzu esteka, botoiak sakatzea gustuko ez baduzu:",
"email_verification_code": "Sartu egiaztatze-kodea",
"email_verification_code_placeholder": "Sartu zure email helbidera bidalitako egiaztatze-kodea",
"incorrect_email_verification_code": "Egiaztatze-kodea ez da zuzena.",
"email_sent": "Email mezua zuzen bidali da",
"email_not_sent": "Errore bat gertatu da email mezua bidaltzerakoan",
"event_declined_subject": "Baztertua: {{title}} {{date}}(e)an",
"event_cancelled_subject": "Bertan behera: {{title}} {{date}}(e)an",
"event_request_declined": "Zure gertaera-eskaera baztertua izan da",
"event_request_declined_recurring": "Zure gertaera errepikari-eskaera baztertua izan da",
"event_request_cancelled": "Zure programatutako gertaera bertan behera utzi da",
"organizer": "Antolatzailea",
"need_to_reschedule_or_cancel": "Programazioa aldatu edo bertan behera utzi behar duzu?",
"no_options_available": "Ez dago aukerarik eskuragarri",
"cancellation_reason": "Bertan behera uztearen arrazoia (aukerakoa)",
"cancellation_reason_placeholder": "Zergatik utzi duzu bertan behera?",
"rejection_reason": "Errefusatzeko arrazoia",
@ -25,7 +40,11 @@
"rejection_reason_description": "Ziur zaude erreserba errefusatu nahi duzula? Erreserba-eskaera egin duen pertsonari jakinaraziko zaio. Arrazoi bat adieraz dezakezu behean.",
"rejection_confirmation": "Errefusatu erreserba",
"manage_this_event": "Kudeatu gertaera hau",
"invite_team_member": "Gonbidatu taldekidea",
"invite_team_individual_segment": "Gonbidatu norbanakoa",
"invite_team_notifcation_badge": "Gon.",
"your_event_has_been_scheduled": "Zure gertaera programatu da",
"your_event_has_been_scheduled_recurring": "Zure gertaera errepikaria programatu da",
"error_message": "Errore-mezua honakoa ian da: '{{errorMessage}}'",
"refund_failed_subject": "Itzulketak huts egin du: {{name}} - {{date}} - {{eventType}}",
"refund_failed": "Huts egin du itzulketak {{eventType}} gertaerarako, {{userName}}(r)ekin {{date}}(e)an.",
@ -37,26 +56,79 @@
"refunded": "Itzulita",
"payment": "Ordainketa",
"pay_now": "Ordaindu orain",
"still_waiting_for_approval": "Gertaera bat onarpenaren zain dago",
"event_is_still_waiting": "Gertaera-eskaera oraindik zain dago: {{attendeeName}} - {{date}} - {{eventType}}",
"no_more_results": "Emaitza gehiagorik ez",
"no_results": "Emaitzarik ez",
"load_more_results": "Kargatu emaitza gehiago",
"integration_meeting_id": "{{integrationName}} bileraren IDa: {{meetingId}}",
"confirmed_event_type_subject": "Baieztatua: {{eventType}} {{name}}(r)ekin {{date}}(e)an",
"new_event_request": "Gertaera berriaren eskaera: {{attendeeName}} - {{date}} - {{eventType}}",
"confirm_or_reject_request": "Baieztatu edo errefusatu eskaera",
"check_bookings_page_to_confirm_or_reject": "Begiratu zure erreserba-orrialdea erreserba baieztatu edo errefusatzeko.",
"event_awaiting_approval": "Gertaera bat zure onarpenaren zain dago",
"event_awaiting_approval_recurring": "Gertaera errepikari bat zure onarpenaren zain dago",
"someone_requested_an_event": "Norbaitek zure egutegian gertaera bat programatzeko eskaera egin du.",
"someone_requested_password_reset": "Norbaitek zure pasahitza aldatzeko esteka bat eskatu du.",
"password_reset_email_sent": "Email helbide hau gure sisteman baldin badago, berrezartzeko email mezu bat jaso behar zenuke.",
"password_reset_instructions": "Ez baduzu eskaera hau egin, segurua da email mezu honi kasurik ez egitea, eta zure pasahitza ez da aldatuko.",
"event_awaiting_approval_subject": "Onarpenaren zain: {{title}} {{date}}(e)an",
"event_still_awaiting_approval": "Gertaera bat zure onarpenaren zain dago oraindik",
"booking_submitted_subject": "Erreserba bidalita: {{title}} {{date}}(e)an",
"download_recording_subject": "Deskargatu grabaketa: {{title}} {{date}}(e)an",
"download_your_recording": "Deskargatu zure grabaketa",
"your_meeting_has_been_booked": "Zure bileraren erreserba egin da",
"event_type_has_been_rescheduled_on_time_date": "Zure {{title}} getaeraren programazioa aldatu egin da {{date}}(e)ra.",
"event_has_been_rescheduled": "Eguneratuta - Zure gertaeraren programazioa aldatu egin da",
"request_reschedule_subtitle": "{{organizer}}(e)k erreserba bertan behera utzi du eta beste denbora-tarte bat hautatzeko eskatu dizu.",
"request_reschedule_title_organizer": "Beste denbora-tarte bat hautatzeko eskatu diozu {{attendee}}(r)i",
"hi_user_name": "Kaixo {{name}}",
"ics_event_title": "{{eventType}} {{name}}(r)ekin",
"notes": "Oharrak",
"manage_my_bookings": "Kudeatu nire erreserbak",
"rejected_event_type_with_organizer": "Errefusatua: {{eventType}} {{organizer}}(r)ekin {{date}}(e)an",
"hi": "Kaixo",
"use_link_to_reset_password": "Erabili beheko esteka pasahitza berrezartzeko",
"hey_there": "Kaixo,",
"forgot_your_password_calcom": "Pasahitza ahaztu duzu? - {{appName}}",
"dismiss": "Alde batera utzi",
"no_data_yet": "Ez dago daturik",
"ping_test": "Ping testa",
"upcoming": "Laster",
"recurring": "Errepikariak",
"past": "Iraganekoak",
"choose_a_file": "Hautatu fitxategi bat...",
"upload_image": "Igo irudia",
"upload_target": "Igo {{target}}",
"no_target": "Ez dago {{target}}(r)ik",
"view_notifications": "Ikusi jakinarazpenak",
"view_public_page": "Ikusi orrialde publikoa",
"copy_public_page_link": "Kopiatu orrialde publikoaren esteka",
"sign_out": "Saioa itxi",
"add_another": "Gehitu beste bat",
"install_another": "Instalatu beste bat",
"unavailable": "Ez eskuragarri",
"set_work_schedule": "Ezarri zure laneko ordutegia",
"change_bookings_availability": "Aldatu noiz zauden prest erreserbak jasotzeko",
"select": "Hautatu...",
"text": "Testua",
"multiline_text": "Lerro ugaritako testua",
"number": "Zenbakia",
"checkbox": "Kontrol-laukia",
"is_required": "Derrigorrezkoa da",
"required": "Derrigorrezkoa",
"optional": "Hautazkoa",
"input_type": "Sarrera-mota",
"rejected": "Baztertua",
"unconfirmed": "Baieztatu gabea",
"guests": "Gonbidatuak",
"create_account": "Sortu kontua",
"confirm_password": "Baieztatu pasahitza",
"create_booking_link_with_calcom": "Sor ezazu zeure erreserba-esteka {{appName}}(e)kin",
"user_needs_to_confirm_or_reject_booking": "{{user}}(e)k erreserba baieztatu edo errefusatu behar du oraindik.",
"booking_submitted": "Zure erreserba bidali da",
"booking_confirmed": "Zure erreserba baieztatu da",
"bookerlayout_column_view": "Zutabea",
"back_to_bookings": "Itzuli erreserbatara",
"really_cancel_booking": "Benetan bertan behera utzi nahi duzu zure erreserba?",
"cannot_cancel_booking": "Ezin duzu erreserba hau bertan behera utzi",

View File

@ -115,6 +115,7 @@ const BookerComponent = ({
columnViewExtraDays.current =
Math.abs(dayjs(selectedDate).diff(availableSlots[availableSlots.length - 2], "day")) + addonDays;
const prefetchNextMonth =
layout === BookerLayouts.COLUMN_VIEW &&
dayjs(date).month() !== dayjs(date).add(columnViewExtraDays.current, "day").month();
const monthCount =
dayjs(date).add(1, "month").month() !== dayjs(date).add(columnViewExtraDays.current, "day").month()

View File

@ -2148,6 +2148,7 @@ async function handler(
id: originalRescheduledBooking.id,
},
data: {
rescheduled: true,
status: BookingStatus.CANCELLED,
},
});

View File

@ -114,17 +114,19 @@ export function FiltersProvider({ children }: { children: React.ReactNode }) {
selectedFilter,
isAll,
dateRange,
initialConfig,
} = newConfigFilters;
const [startTime, endTime] = dateRange || [null, null];
const newSearchParams = new URLSearchParams(searchParams);
const newSearchParams = new URLSearchParams(searchParams.toString());
function setParamsIfDefined(key: string, value: string | number | boolean | null | undefined) {
if (value !== undefined && value !== null) newSearchParams.set(key, value.toString());
}
setParamsIfDefined("memberUserId", selectedMemberUserId);
setParamsIfDefined("teamId", selectedTeamId);
setParamsIfDefined("userId", selectedUserId);
setParamsIfDefined("teamId", selectedTeamId || initialConfig?.teamId);
setParamsIfDefined("userId", selectedUserId || initialConfig?.userId);
setParamsIfDefined("eventTypeId", selectedEventTypeId);
setParamsIfDefined("isAll", isAll);
setParamsIfDefined("isAll", isAll || initialConfig?.isAll);
setParamsIfDefined("startTime", startTime?.toISOString());
setParamsIfDefined("endTime", endTime?.toISOString());
setParamsIfDefined("filter", selectedFilter?.[0]);

View File

@ -22,6 +22,11 @@ export const TeamAndSelfList = () => {
const { data, isSuccess } = trpc.viewer.insights.teamListForUser.useQuery(undefined, {
// Teams don't change that frequently
refetchOnWindowFocus: false,
trpc: {
context: {
skipBatch: true,
},
},
});
useEffect(() => {
@ -48,6 +53,7 @@ export const TeamAndSelfList = () => {
} else if (session.data?.user.id) {
// default to user
setConfigFilters({
selectedUserId: session.data?.user.id,
initialConfig: {
teamId: null,
userId: session.data?.user.id,

View File

@ -0,0 +1,35 @@
-- View: public.BookingsTimeStatus
-- DROP VIEW public."BookingsTimeStatus";
CREATE OR REPLACE VIEW public."BookingTimeStatus"
AS
SELECT "Booking".id,
"Booking".uid,
"Booking"."eventTypeId",
"Booking".title,
"Booking".description,
"Booking"."startTime",
"Booking"."endTime",
"Booking"."createdAt",
"Booking".location,
"Booking".paid,
"Booking".status,
"Booking".rescheduled,
"Booking"."userId",
"et"."teamId",
"et"."length" as "eventLength",
CASE
WHEN "Booking".rescheduled IS TRUE THEN 'rescheduled'::text
WHEN "Booking".status = 'cancelled'::"BookingStatus" AND "Booking".rescheduled IS NULL THEN 'cancelled'::text
WHEN "Booking"."endTime" < now() THEN 'completed'::text
WHEN "Booking"."endTime" > now() THEN 'uncompleted'::text
ELSE NULL::text
END AS "timeStatus",
"et"."parentId" as "eventParentId"
FROM "Booking"
LEFT JOIN "EventType" et ON "Booking"."eventTypeId" = et.id
LEFT JOIN "Membership" mb ON "mb"."userId" = "Booking"."userId";