From 51c501665685d3f9f99b78b883076bc948b8c94b Mon Sep 17 00:00:00 2001 From: Luke Vella Date: Thu, 30 Mar 2023 14:10:23 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=8F=97=EF=B8=8F=20Update=20how=20we=20sto?= =?UTF-8?q?re=20date=20values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This also fixes a few bugs: - some values had end times that were before the start times - some values has end times a couple of days in the future It’s not entirely clear how users were able to set these values but this update fixes these values and helps avoid similar issues in the future. --- apps/web/src/components/create-poll.tsx | 7 +- .../month-calendar/month-calendar.tsx | 21 +-- .../month-calendar/time-picker.tsx | 27 ++-- apps/web/src/components/poll/manage-poll.tsx | 30 +++-- apps/web/src/pages/api/house-keeping.ts | 36 ++--- apps/web/src/server/routers/polls.ts | 127 +++++++++++------- apps/web/src/server/routers/polls/demo.ts | 5 +- apps/web/src/utils/date-time-utils.ts | 23 ++-- apps/web/tests/house-keeping.spec.ts | 23 +--- .../migration.sql | 35 +++++ packages/database/prisma/schema.prisma | 11 +- packages/database/prisma/seed.ts | 16 ++- 12 files changed, 203 insertions(+), 158 deletions(-) create mode 100644 packages/database/prisma/migrations/20230329173551_options_refactor/migration.sql diff --git a/apps/web/src/components/create-poll.tsx b/apps/web/src/components/create-poll.tsx index c0108d949..9bcd85817 100644 --- a/apps/web/src/components/create-poll.tsx +++ b/apps/web/src/components/create-poll.tsx @@ -4,7 +4,6 @@ import React from "react"; import { usePostHog } from "@/utils/posthog"; -import { encodeDateOption } from "../utils/date-time-utils"; import { trpc } from "../utils/trpc"; import { Button } from "./button"; import { @@ -102,7 +101,6 @@ const Page: React.FunctionComponent = () => { await createPoll.mutateAsync({ title: title, - type: "date", location: formData?.eventDetails?.location, description: formData?.eventDetails?.description, user: session.user.isGuest @@ -112,7 +110,10 @@ const Page: React.FunctionComponent = () => { } : undefined, timeZone: formData?.options?.timeZone, - options: required(formData?.options?.options).map(encodeDateOption), + options: required(formData?.options?.options).map((option) => ({ + startDate: option.type === "date" ? option.date : option.start, + endDate: option.type === "timeSlot" ? option.end : undefined, + })), }); } }; diff --git a/apps/web/src/components/forms/poll-options-form/month-calendar/month-calendar.tsx b/apps/web/src/components/forms/poll-options-form/month-calendar/month-calendar.tsx index e1deaa906..a3fd5733f 100644 --- a/apps/web/src/components/forms/poll-options-form/month-calendar/month-calendar.tsx +++ b/apps/web/src/components/forms/poll-options-form/month-calendar/month-calendar.tsx @@ -265,16 +265,11 @@ const MonthCalendar: React.FunctionComponent = ({ { - let newEnd = dayjs(newStart).add( + const newEnd = dayjs(newStart).add( duration, "minutes", ); - if (!newEnd.isSame(newStart, "day")) { - newEnd = newEnd - .set("hour", 23) - .set("minute", 45); - } // replace enter with updated start time onChange([ ...options.slice(0, index), @@ -293,9 +288,7 @@ const MonthCalendar: React.FunctionComponent = ({ /> { onChange([ ...options.slice(0, index), @@ -330,7 +323,15 @@ const MonthCalendar: React.FunctionComponent = ({ const lastOption = expectTimeOption( optionsForDay[optionsForDay.length - 1].option, ); - const startTime = lastOption.end; + + const startTime = dayjs(lastOption.end).isSame( + lastOption.start, + "day", + ) + ? // if the end time of the previous option is on the same day as the start time, use the end time + lastOption.end + : // otherwise use the start time + lastOption.start; onChange([ ...options, diff --git a/apps/web/src/components/forms/poll-options-form/month-calendar/time-picker.tsx b/apps/web/src/components/forms/poll-options-form/month-calendar/time-picker.tsx index 3957cdf90..afe77496c 100644 --- a/apps/web/src/components/forms/poll-options-form/month-calendar/time-picker.tsx +++ b/apps/web/src/components/forms/poll-options-form/month-calendar/time-picker.tsx @@ -9,6 +9,7 @@ import { Listbox } from "@headlessui/react"; import clsx from "clsx"; import * as React from "react"; +import { getDuration } from "@/utils/date-time-utils"; import { stopPropagation } from "@/utils/stop-propagation"; import { useDayjs } from "../../../../utils/dayjs"; @@ -17,7 +18,7 @@ import { styleMenuItem } from "../../../menu-styles"; export interface TimePickerProps { value: Date; - startFrom?: Date; + after?: Date; className?: string; onChange?: (value: Date) => void; } @@ -26,10 +27,11 @@ const TimePicker: React.FunctionComponent = ({ value, onChange, className, - startFrom, + after, }) => { const { dayjs } = useDayjs(); const { reference, floating, x, y, strategy, refs } = useFloating({ + placement: "bottom-start", strategy: "fixed", middleware: [ offset(5), @@ -38,7 +40,7 @@ const TimePicker: React.FunctionComponent = ({ apply: ({ rects }) => { if (refs.floating.current) { Object.assign(refs.floating.current.style, { - width: `${rects.reference.width}px`, + minWidth: `${rects.reference.width}px`, }); } }, @@ -47,15 +49,11 @@ const TimePicker: React.FunctionComponent = ({ }); const renderOptions = () => { - const startFromDate = startFrom - ? dayjs(startFrom) - : dayjs(value).startOf("day"); + const startFromDate = after ? dayjs(after) : dayjs(value).startOf("day"); const options: React.ReactNode[] = []; - const startMinute = - startFromDate.get("hour") * 60 + startFromDate.get("minute"); - const intervals = Math.floor((1440 - startMinute) / 15); - for (let i = 0; i < intervals; i++) { + + for (let i = 1; i <= 96; i++) { const optionValue = startFromDate.add(i * 15, "minutes"); options.push( = ({ className={styleMenuItem} value={optionValue.format("YYYY-MM-DDTHH:mm:ss")} > - {optionValue.format("LT")} +
+ {optionValue.format("LT")} + {after ? ( + + {getDuration(dayjs(after), optionValue)} + + ) : null} +
, ); } diff --git a/apps/web/src/components/poll/manage-poll.tsx b/apps/web/src/components/poll/manage-poll.tsx index d133ba720..0513cbd22 100644 --- a/apps/web/src/components/poll/manage-poll.tsx +++ b/apps/web/src/components/poll/manage-poll.tsx @@ -1,4 +1,5 @@ import { Placement } from "@floating-ui/react-dom-interactions"; +import dayjs from "dayjs"; import { Trans, useTranslation } from "next-i18next"; import * as React from "react"; @@ -23,6 +24,15 @@ import { useUpdatePollMutation } from "./mutations"; const PollOptionsForm = React.lazy(() => import("../forms/poll-options-form")); +const convertOptionToString = (option: { start: Date; duration: number }) => { + const start = dayjs(option.start); + return option.duration === 0 + ? start.format("YYYY-MM-DD") + : `${start.format("YYYY-MM-DDTHH:mm:ss")}/${start + .add(option.duration, "minute") + .format("YYYY-MM-DDTHH:mm:ss")}`; +}; + const ManagePoll: React.FunctionComponent<{ placement?: Placement; }> = ({ placement }) => { @@ -67,18 +77,20 @@ const ManagePoll: React.FunctionComponent<{ name="pollOptions" title={poll.title} defaultValues={{ - navigationDate: poll.options[0].value.split("/")[0], + navigationDate: poll.options[0].start.toString(), options: poll.options.map((option) => { - const [start, end] = option.value.split("/"); - return end + const start = dayjs(option.start); + return option.duration > 0 ? { type: "timeSlot", - start, - end, + start: start.format("YYYY-MM-DDTHH:mm:ss"), + end: start + .add(option.duration, "minute") + .format("YYYY-MM-DDTHH:mm:ss"), } : { type: "date", - date: start, + date: start.format("YYYY-MM-DD"), }; }), timeZone: poll.timeZone ?? "", @@ -86,12 +98,14 @@ const ManagePoll: React.FunctionComponent<{ onSubmit={(data) => { const encodedOptions = data.options.map(encodeDateOption); const optionsToDelete = poll.options.filter((option) => { - return !encodedOptions.includes(option.value); + return !encodedOptions.includes(convertOptionToString(option)); }); const optionsToAdd = encodedOptions.filter( (encodedOption) => - !poll.options.find((o) => o.value === encodedOption), + !poll.options.find( + (o) => convertOptionToString(o) === encodedOption, + ), ); const onOk = () => { diff --git a/apps/web/src/pages/api/house-keeping.ts b/apps/web/src/pages/api/house-keeping.ts index 8b4e096b1..8f1b6a73b 100644 --- a/apps/web/src/pages/api/house-keeping.ts +++ b/apps/web/src/pages/api/house-keeping.ts @@ -2,8 +2,6 @@ import { Prisma, prisma } from "@rallly/database"; import dayjs from "dayjs"; import { NextApiRequest, NextApiResponse } from "next"; -import { parseValue } from "../../utils/date-time-utils"; - /** * DANGER: This endpoint will permanently delete polls. */ @@ -25,32 +23,26 @@ export default async function handler( } // get polls that have not been accessed for over 30 days - const inactivePolls = await prisma.$queryRaw< - Array<{ id: string; max: string }> - >` - SELECT polls.id, MAX(options.value) FROM polls - JOIN options ON options.poll_id = polls.id - WHERE touched_at <= ${dayjs().add(-30, "days").toDate()} AND deleted = false - GROUP BY polls.id; - `; + const thirtyDaysAgo = dayjs().subtract(30, "days").toDate(); - const pollsToSoftDelete: string[] = []; - - // keep polls that have options that are in the future - inactivePolls.forEach(({ id, max: value }) => { - const parsedValue = parseValue(value); - const date = - parsedValue.type === "date" ? parsedValue.date : parsedValue.end; - - if (dayjs(date).isBefore(dayjs())) { - pollsToSoftDelete.push(id); - } + const oldPollsWithAllPastOptions = await prisma.poll.findMany({ + select: { + id: true, + }, + where: { + touchedAt: { lte: thirtyDaysAgo }, + options: { + every: { + start: { lt: new Date() }, + }, + }, + }, }); const softDeletedPolls = await prisma.poll.deleteMany({ where: { id: { - in: pollsToSoftDelete, + in: oldPollsWithAllPastOptions.map(({ id }) => id), }, }, }); diff --git a/apps/web/src/server/routers/polls.ts b/apps/web/src/server/routers/polls.ts index 24fec8b4e..205e47b82 100644 --- a/apps/web/src/server/routers/polls.ts +++ b/apps/web/src/server/routers/polls.ts @@ -1,6 +1,7 @@ import { prisma } from "@rallly/database"; import { sendEmail } from "@rallly/emails"; import { TRPCError } from "@trpc/server"; +import dayjs from "dayjs"; import { z } from "zod"; import { absoluteUrl } from "../../utils/absolute-url"; @@ -10,46 +11,6 @@ import { comments } from "./polls/comments"; import { demo } from "./polls/demo"; import { participants } from "./polls/participants"; -const defaultSelectFields: { - id: true; - timeZone: true; - title: true; - location: true; - description: true; - createdAt: true; - adminUrlId: true; - participantUrlId: true; - closed: true; - legacy: true; - demo: true; - options: { - orderBy: { - value: "asc"; - }; - }; - user: true; - deleted: true; -} = { - id: true, - timeZone: true, - title: true, - location: true, - description: true, - createdAt: true, - adminUrlId: true, - participantUrlId: true, - closed: true, - legacy: true, - demo: true, - options: { - orderBy: { - value: "asc", - }, - }, - user: true, - deleted: true, -}; - const getPollIdFromAdminUrlId = async (urlId: string) => { const res = await prisma.poll.findUnique({ select: { @@ -72,7 +33,6 @@ export const polls = router({ .input( z.object({ title: z.string(), - type: z.literal("date"), timeZone: z.string().optional(), location: z.string().optional(), description: z.string().optional(), @@ -82,7 +42,12 @@ export const polls = router({ email: z.string(), }) .optional(), - options: z.string().array(), + options: z + .object({ + startDate: z.string(), + endDate: z.string().optional(), + }) + .array(), demo: z.boolean().optional(), }), ) @@ -120,7 +85,6 @@ export const polls = router({ data: { id: await nanoid(), title: input.title, - type: input.type, timeZone: input.timeZone, location: input.location, description: input.description, @@ -137,8 +101,14 @@ export const polls = router({ : undefined, options: { createMany: { - data: input.options.map((value) => ({ - value, + data: input.options.map((option) => ({ + start: new Date(option.startDate), + duration: option.endDate + ? dayjs(option.endDate).diff( + dayjs(option.startDate), + "minute", + ) + : 0, })), }, }, @@ -193,15 +163,26 @@ export const polls = router({ if (input.optionsToAdd && input.optionsToAdd.length > 0) { await prisma.option.createMany({ - data: input.optionsToAdd.map((optionValue) => ({ - value: optionValue, - pollId, - })), + data: input.optionsToAdd.map((optionValue) => { + const [start, end] = optionValue.split("/"); + if (end) { + return { + start: new Date(start), + duration: dayjs(end).diff(dayjs(start), "minute"), + pollId, + }; + } else { + return { + start: new Date(start.substring(0, 10) + "T00:00:00"), + pollId, + }; + } + }), }); } await prisma.poll.update({ - select: defaultSelectFields, + select: { id: true }, where: { id: pollId, }, @@ -303,7 +284,24 @@ export const polls = router({ .query(async ({ input }) => { const res = await prisma.poll.findUnique({ select: { - ...defaultSelectFields, + id: true, + timeZone: true, + title: true, + location: true, + description: true, + createdAt: true, + adminUrlId: true, + participantUrlId: true, + closed: true, + legacy: true, + demo: true, + options: { + orderBy: { + start: "asc", + }, + }, + user: true, + deleted: true, watchers: { select: { userId: true, @@ -333,7 +331,32 @@ export const polls = router({ ) .query(async ({ input, ctx }) => { const res = await prisma.poll.findUnique({ - select: { userId: true, ...defaultSelectFields }, + select: { + id: true, + timeZone: true, + title: true, + location: true, + description: true, + createdAt: true, + adminUrlId: true, + participantUrlId: true, + closed: true, + legacy: true, + demo: true, + options: { + orderBy: { + start: "asc", + }, + }, + user: true, + userId: true, + deleted: true, + watchers: { + select: { + userId: true, + }, + }, + }, where: { participantUrlId: input.urlId, }, diff --git a/apps/web/src/server/routers/polls/demo.ts b/apps/web/src/server/routers/polls/demo.ts index 87dc81813..27775cd18 100644 --- a/apps/web/src/server/routers/polls/demo.ts +++ b/apps/web/src/server/routers/polls/demo.ts @@ -30,10 +30,10 @@ export const demo = router({ const adminUrlId = await nanoid(); const demoUser = { name: "John Example", email: "noreply@rallly.co" }; - const options: Array<{ value: string; id: string }> = []; + const options: Array<{ start: Date; id: string }> = []; for (let i = 0; i < optionValues.length; i++) { - options.push({ id: await nanoid(), value: optionValues[i] }); + options.push({ id: await nanoid(), start: new Date(optionValues[i]) }); } const participants: Array<{ @@ -74,7 +74,6 @@ export const demo = router({ data: { id: await nanoid(), title: "Lunch Meeting", - type: "date", location: "Starbucks, 901 New York Avenue", description: `Hey everyone, please choose the dates when you are available to meet for our monthly get together. Looking forward to see you all!`, demo: true, diff --git a/apps/web/src/utils/date-time-utils.ts b/apps/web/src/utils/date-time-utils.ts index c1af872b4..4b27388e3 100644 --- a/apps/web/src/utils/date-time-utils.ts +++ b/apps/web/src/utils/date-time-utils.ts @@ -65,7 +65,9 @@ export const decodeOptions = ( ): | { pollType: "date"; options: ParsedDateOption[] } | { pollType: "timeSlot"; options: ParsedTimeSlotOption[] } => { - const pollType = isTimeSlot(options[0].value) ? "timeSlot" : "date"; + const pollType = options.some(({ duration }) => duration > 0) + ? "timeSlot" + : "date"; if (pollType === "timeSlot") { return { @@ -83,12 +85,7 @@ export const decodeOptions = ( }; const parseDateOption = (option: Option): ParsedDateOption => { - const dateString = - option.value.indexOf("T") === -1 - ? // we add the time because otherwise Date will assume UTC time which might change the day for some time zones - option.value + "T00:00:00" - : option.value; - const date = dayjs(dateString); + const date = dayjs(option.start); return { type: "date", optionId: option.id, @@ -104,16 +101,12 @@ const parseTimeSlotOption = ( timeZone: string | null, targetTimeZone: string, ): ParsedTimeSlotOption => { - const [start, end] = option.value.split("/"); - const startDate = timeZone && targetTimeZone - ? dayjs(start).tz(timeZone, true).tz(targetTimeZone) - : dayjs(start); - const endDate = - timeZone && targetTimeZone - ? dayjs(end).tz(timeZone, true).tz(targetTimeZone) - : dayjs(end); + ? dayjs(option.start).tz(timeZone, true).tz(targetTimeZone) + : dayjs(option.start); + + const endDate = startDate.add(option.duration, "minute"); return { type: "timeSlot", diff --git a/apps/web/tests/house-keeping.spec.ts b/apps/web/tests/house-keeping.spec.ts index 36f6cb4d7..455fb7e58 100644 --- a/apps/web/tests/house-keeping.spec.ts +++ b/apps/web/tests/house-keeping.spec.ts @@ -176,7 +176,6 @@ const seedData = async () => { { title: "Active Poll", id: "active-poll", - type: "date", userId: "user1", participantUrlId: "p1", adminUrlId: "a1", @@ -185,7 +184,6 @@ const seedData = async () => { { title: "Deleted poll", id: "deleted-poll-6d", - type: "date", userId: "user1", deleted: true, deletedAt: dayjs().add(-6, "days").toDate(), @@ -196,7 +194,6 @@ const seedData = async () => { { title: "Deleted poll 7d", id: "deleted-poll-7d", - type: "date", userId: "user1", deleted: true, deletedAt: dayjs().add(-7, "days").toDate(), @@ -207,7 +204,6 @@ const seedData = async () => { { title: "Still active", id: "still-active-poll", - type: "date", userId: "user1", touchedAt: dayjs().add(-29, "days").toDate(), participantUrlId: "p4", @@ -217,7 +213,6 @@ const seedData = async () => { { title: "Inactive poll", id: "inactive-poll", - type: "date", userId: "user1", touchedAt: dayjs().add(-30, "days").toDate(), participantUrlId: "p5", @@ -228,7 +223,6 @@ const seedData = async () => { demo: true, title: "Demo poll", id: "demo-poll-new", - type: "date", userId: "user1", createdAt: new Date(), participantUrlId: "p6", @@ -238,7 +232,6 @@ const seedData = async () => { demo: true, title: "Old demo poll", id: "demo-poll-old", - type: "date", userId: "user1", createdAt: dayjs().add(-2, "days").toDate(), participantUrlId: "p7", @@ -247,7 +240,6 @@ const seedData = async () => { { title: "Inactive poll with future option", id: "inactive-poll-future-option", - type: "date", userId: "user1", touchedAt: dayjs().add(-30, "days").toDate(), participantUrlId: "p8", @@ -260,32 +252,27 @@ const seedData = async () => { data: [ { id: "option-1", - value: "2022-02-22", + start: new Date("2022-02-22T00:00:00"), pollId: "deleted-poll-7d", }, { id: "option-2", - value: "2022-02-23", + start: new Date("2022-02-23T00:00:00"), pollId: "deleted-poll-7d", }, { id: "option-3", - value: "2022-02-24", + start: new Date("2022-02-24T00:00:00"), pollId: "deleted-poll-7d", }, { id: "option-4", - value: `${dayjs() - .add(10, "days") - .format("YYYY-MM-DDTHH:mm:ss")}/${dayjs() - .add(10, "days") - .add(1, "hour") - .format("YYYY-MM-DDTHH:mm:ss")}`, + start: dayjs().add(10, "days").toDate(), pollId: "inactive-poll-future-option", }, { id: "option-5", - value: dayjs().add(-1, "days").format("YYYY-MM-DD"), + start: dayjs().add(-1, "days").toDate(), pollId: "inactive-poll", }, ], diff --git a/packages/database/prisma/migrations/20230329173551_options_refactor/migration.sql b/packages/database/prisma/migrations/20230329173551_options_refactor/migration.sql new file mode 100644 index 000000000..a728fddbe --- /dev/null +++ b/packages/database/prisma/migrations/20230329173551_options_refactor/migration.sql @@ -0,0 +1,35 @@ +-- AlterTable +ALTER TABLE "options" +ADD COLUMN "duration_minutes" INTEGER NOT NULL DEFAULT 0, +ADD COLUMN "start" TIMESTAMP(0); + +-- AlterTable +ALTER TABLE "polls" DROP COLUMN "type"; + +-- DropEnum +DROP TYPE "poll_type"; + +-- Reformat option value into new columns +UPDATE "options" +SET "start" = CASE + WHEN POSITION('/' IN "value") = 0 THEN (value || 'T00:00:00')::TIMESTAMP WITHOUT TIME ZONE + ELSE SPLIT_PART("value", '/', 1)::TIMESTAMP WITHOUT TIME ZONE + END, + "duration_minutes" = CASE + WHEN POSITION('/' IN "value") = 0 THEN 0 + ELSE + LEAST(EXTRACT(EPOCH FROM (split_part("value", '/', 2)::timestamp - split_part("value", '/', 1)::timestamp)) / 60, 1440) + END; + +-- Fix cases where we have a negative duration due to the end time being in the past +-- eg. Some polls have value 2023-03-29T23:00:00/2023-03-29T01:00:00 +UPDATE "options" +SET "duration_minutes" = "duration_minutes" + 1440 +WHERE "duration_minutes" < 0; + +-- Set start date to be not null now that we have all the data and drop the old value column +ALTER TABLE "options" +DROP COLUMN "value", +DROP COLUMN "updated_at", +ALTER COLUMN "start" SET NOT NULL; + diff --git a/packages/database/prisma/schema.prisma b/packages/database/prisma/schema.prisma index c0426d29c..0029b69fe 100644 --- a/packages/database/prisma/schema.prisma +++ b/packages/database/prisma/schema.prisma @@ -22,19 +22,12 @@ model User { @@map("users") } -enum PollType { - date - - @@map("poll_type") -} - model Poll { id String @id @unique @map("id") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") deadline DateTime? title String - type PollType description String? location String? user User? @relation(fields: [userId], references: [id]) @@ -88,11 +81,11 @@ model Participant { model Option { id String @id @default(cuid()) - value String + start DateTime @db.Timestamp(0) + duration Int @default(0) @map("duration_minutes") pollId String @map("poll_id") poll Poll @relation(fields: [pollId], references: [id]) createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime? @updatedAt @map("updated_at") @@index([pollId], type: Hash) @@map("options") diff --git a/packages/database/prisma/seed.ts b/packages/database/prisma/seed.ts index bce95ca7f..6638708f3 100644 --- a/packages/database/prisma/seed.ts +++ b/packages/database/prisma/seed.ts @@ -1,5 +1,5 @@ import { faker } from "@faker-js/faker"; -import { PrismaClient, VoteType } from "@prisma/client"; +import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); @@ -11,14 +11,16 @@ async function main() { // Create some users const user = await prisma.user.create({ data: { - name: faker.name.fullName(), - email: faker.internet.email(), + name: "Dev User", + email: "dev@rallly.co", }, }); // Create some polls const polls = await Promise.all( - Array.from({ length: 20 }).map(async () => { + Array.from({ length: 20 }).map(async (_, i) => { + // create some polls with no duration (all day) and some with a random duration. + const duration = i % 5 === 0 ? 15 * randInt(8) : 0; const poll = await prisma.poll.create({ include: { participants: true, @@ -30,7 +32,6 @@ async function main() { description: faker.lorem.paragraph(), location: faker.address.streetAddress(), deadline: faker.date.future(), - type: "date", user: { connect: { id: user.id, @@ -46,7 +47,8 @@ async function main() { ) // .map((date) => { return { - value: date.toISOString().substring(0, 10), + start: date, + duration, }; }), }, @@ -73,7 +75,7 @@ async function main() { data: poll.options.map((option) => { const randomNumber = randInt(100); const vote = - randomNumber > 90 ? "ifNeedBe" : randomNumber > 50 ? "yes" : "no"; + randomNumber > 95 ? "ifNeedBe" : randomNumber > 50 ? "yes" : "no"; return { participantId: participant.id, pollId: poll.id,