fix(results): 🐛 Loading rows
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
DATABASE_URL=postgresql://postgres:@localhost:5432/typebot
|
DATABASE_URL=postgresql://postgres:@localhost:5432/typebot
|
||||||
|
|
||||||
SECRET=q3t6v9y$B&E)H@McQfTjWnZr4u7x!z%C #256-bits secret (can be generated here: https://www.allkeysgenerator.com/Random/Security-Encryption-Key-Generator.aspx)
|
ENCRYPTION_SECRET=q3t6v9y$B&E)H@McQfTjWnZr4u7x!z%C #256-bits secret (can be generated here: https://www.allkeysgenerator.com/Random/Security-Encryption-Key-Generator.aspx)
|
||||||
NEXTAUTH_URL=http://localhost:3000
|
NEXTAUTH_URL=http://localhost:3000
|
||||||
|
|
||||||
# Used for email auth and email notifications
|
# Used for email auth and email notifications
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Checkbox, Flex, Skeleton } from '@chakra-ui/react'
|
import { chakra, Checkbox, Flex, Skeleton } from '@chakra-ui/react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
type LoadingRowsProps = {
|
type LoadingRowsProps = {
|
||||||
@@ -9,35 +9,32 @@ export const LoadingRows = ({ totalColumns }: LoadingRowsProps) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{Array.from(Array(3)).map((row, idx) => (
|
{Array.from(Array(3)).map((row, idx) => (
|
||||||
<Flex as="tr" key={idx}>
|
<tr key={idx}>
|
||||||
<Flex
|
<chakra.td
|
||||||
key={idx}
|
px="4"
|
||||||
py={2}
|
py="2"
|
||||||
px={4}
|
|
||||||
border="1px"
|
border="1px"
|
||||||
as="td"
|
|
||||||
borderColor="gray.200"
|
borderColor="gray.200"
|
||||||
width="50px"
|
width="50px"
|
||||||
>
|
>
|
||||||
<Checkbox isDisabled />
|
<Flex>
|
||||||
</Flex>
|
<Checkbox isDisabled />
|
||||||
|
</Flex>
|
||||||
|
</chakra.td>
|
||||||
{Array.from(Array(totalColumns)).map((cell, idx) => {
|
{Array.from(Array(totalColumns)).map((cell, idx) => {
|
||||||
return (
|
return (
|
||||||
<Flex
|
<chakra.td
|
||||||
key={idx}
|
key={idx}
|
||||||
py={2}
|
px="4"
|
||||||
px={4}
|
py="2"
|
||||||
border="1px"
|
border="1px"
|
||||||
as="td"
|
|
||||||
borderColor="gray.200"
|
borderColor="gray.200"
|
||||||
width="180px"
|
|
||||||
align="center"
|
|
||||||
>
|
>
|
||||||
<Skeleton height="5px" w="full" />
|
<Skeleton height="5px" w="full" />
|
||||||
</Flex>
|
</chakra.td>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</Flex>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
/* eslint-disable react/jsx-key */
|
/* eslint-disable react/jsx-key */
|
||||||
import { Box, Checkbox, Flex } from '@chakra-ui/react'
|
import { chakra, Checkbox, Flex } from '@chakra-ui/react'
|
||||||
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
||||||
import React, { useEffect, useMemo, useRef } from 'react'
|
import React, { useEffect, useMemo, useRef } from 'react'
|
||||||
import { Hooks, useFlexLayout, useRowSelect, useTable } from 'react-table'
|
import { Hooks, useRowSelect, useTable } from 'react-table'
|
||||||
import { parseSubmissionsColumns } from 'services/publicTypebot'
|
import { parseSubmissionsColumns } from 'services/publicTypebot'
|
||||||
import { LoadingRows } from './LoadingRows'
|
import { LoadingRows } from './LoadingRows'
|
||||||
|
|
||||||
const defaultCellWidth = 180
|
|
||||||
|
|
||||||
type SubmissionsTableProps = {
|
type SubmissionsTableProps = {
|
||||||
data?: any
|
data?: any
|
||||||
hasMore?: boolean
|
hasMore?: boolean
|
||||||
@@ -38,12 +36,7 @@ export const SubmissionsTable = ({
|
|||||||
prepareRow,
|
prepareRow,
|
||||||
getTableBodyProps,
|
getTableBodyProps,
|
||||||
selectedFlatRows,
|
selectedFlatRows,
|
||||||
} = useTable(
|
} = useTable({ columns, data }, useRowSelect, checkboxColumnHook) as any
|
||||||
{ columns, data, defaultColumn: { width: defaultCellWidth } },
|
|
||||||
useRowSelect,
|
|
||||||
checkboxColumnHook,
|
|
||||||
useFlexLayout
|
|
||||||
) as any
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onNewSelection(selectedFlatRows.map((row: any) => row.index))
|
onNewSelection(selectedFlatRows.map((row: any) => row.index))
|
||||||
@@ -68,81 +61,69 @@ export const SubmissionsTable = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
overflow="scroll"
|
|
||||||
maxW="full"
|
maxW="full"
|
||||||
maxH="full"
|
overflow="scroll"
|
||||||
|
ref={tableWrapper}
|
||||||
className="table-wrapper"
|
className="table-wrapper"
|
||||||
rounded="md"
|
rounded="md"
|
||||||
data-testid="table-wrapper"
|
|
||||||
pb="20"
|
|
||||||
ref={tableWrapper}
|
|
||||||
>
|
>
|
||||||
<Box as="table" rounded="md" {...getTableProps()} w="full" h="full">
|
<chakra.table rounded="md" {...getTableProps()}>
|
||||||
<Box as="thead" pos="sticky" top="0" zIndex={2}>
|
<thead>
|
||||||
{headerGroups.map((headerGroup: any) => {
|
{headerGroups.map((headerGroup: any) => {
|
||||||
return (
|
return (
|
||||||
<Flex as="tr" {...headerGroup.getHeaderGroupProps()}>
|
<tr {...headerGroup.getHeaderGroupProps()}>
|
||||||
{headerGroup.headers.map((column: any, idx: number) => {
|
{headerGroup.headers.map((column: any) => {
|
||||||
return (
|
return (
|
||||||
<Flex
|
<chakra.th
|
||||||
py={2}
|
px="4"
|
||||||
px={4}
|
py="2"
|
||||||
border="1px"
|
border="1px"
|
||||||
borderColor="gray.200"
|
borderColor="gray.200"
|
||||||
as="th"
|
|
||||||
color="gray.500"
|
|
||||||
fontWeight="normal"
|
fontWeight="normal"
|
||||||
textAlign="left"
|
whiteSpace="nowrap"
|
||||||
bgColor={'white'}
|
|
||||||
{...column.getHeaderProps()}
|
{...column.getHeaderProps()}
|
||||||
style={{
|
|
||||||
width: idx === 0 ? '50px' : `${defaultCellWidth}px`,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{column.render('Header')}
|
{column.render('Header')}
|
||||||
</Flex>
|
</chakra.th>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</Flex>
|
</tr>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</Box>
|
</thead>
|
||||||
|
|
||||||
<Box as="tbody" {...getTableBodyProps()}>
|
<tbody {...getTableBodyProps()}>
|
||||||
{rows.map((row: any, idx: number) => {
|
{rows.map((row: any, idx: number) => {
|
||||||
prepareRow(row)
|
prepareRow(row)
|
||||||
return (
|
return (
|
||||||
<Flex
|
<tr
|
||||||
as="tr"
|
|
||||||
{...row.getRowProps()}
|
{...row.getRowProps()}
|
||||||
ref={(ref) => {
|
ref={(ref) => {
|
||||||
if (idx === data.length - 10) bottomElement.current = ref
|
if (idx === data.length - 10) bottomElement.current = ref
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{row.cells.map((cell: any, idx: number) => {
|
{row.cells.map((cell: any) => {
|
||||||
return (
|
return (
|
||||||
<Flex
|
<chakra.td
|
||||||
py={2}
|
px="4"
|
||||||
px={4}
|
py="2"
|
||||||
border="1px"
|
border="1px"
|
||||||
as="td"
|
|
||||||
borderColor="gray.200"
|
borderColor="gray.200"
|
||||||
bgColor={'white'}
|
whiteSpace={
|
||||||
|
cell?.value?.length > 100 ? 'normal' : 'nowrap'
|
||||||
|
}
|
||||||
{...cell.getCellProps()}
|
{...cell.getCellProps()}
|
||||||
style={{
|
|
||||||
width: idx === 0 ? '50px' : `${defaultCellWidth}px`,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{cell.render('Cell')}
|
{cell.render('Cell')}
|
||||||
</Flex>
|
</chakra.td>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</Flex>
|
</tr>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
{hasMore === true && <LoadingRows totalColumns={columns.length} />}
|
{hasMore === true && <LoadingRows totalColumns={columns.length} />}
|
||||||
</Box>
|
</tbody>
|
||||||
</Box>
|
</chakra.table>
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -168,12 +149,14 @@ const IndeterminateCheckbox = React.forwardRef(
|
|||||||
const resolvedRef: any = ref || defaultRef
|
const resolvedRef: any = ref || defaultRef
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Checkbox
|
<Flex justify="center">
|
||||||
ref={resolvedRef}
|
<Checkbox
|
||||||
{...rest}
|
ref={resolvedRef}
|
||||||
isIndeterminate={indeterminate}
|
{...rest}
|
||||||
isChecked={checked}
|
isIndeterminate={indeterminate}
|
||||||
/>
|
isChecked={checked}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -106,11 +106,11 @@ export const SubmissionsContent = ({
|
|||||||
const tableData: { [key: string]: string }[] = useMemo(
|
const tableData: { [key: string]: string }[] = useMemo(
|
||||||
() => convertResultsToTableData(results),
|
() => convertResultsToTableData(results),
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[results?.length]
|
[results]
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack maxW="1200px" w="full">
|
<Stack maxW="1200px" w="full" pb="28">
|
||||||
<Flex w="full" justifyContent="flex-end">
|
<Flex w="full" justifyContent="flex-end">
|
||||||
<ResultsActionButtons
|
<ResultsActionButtons
|
||||||
isDeleteLoading={isDeleteLoading}
|
isDeleteLoading={isDeleteLoading}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"test": "dotenv -e ./playwright/.env -- yarn playwright test",
|
"test": "dotenv -e ./playwright/.env -e .env.local -- yarn playwright test",
|
||||||
"test:open": "dotenv -e ./playwright/.env -v PWDEBUG=1 -- yarn playwright test"
|
"test:open": "dotenv -e ./playwright/.env -e .env.local -v PWDEBUG=1 -- yarn playwright test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/css-reset": "^1.1.1",
|
"@chakra-ui/css-reset": "^1.1.1",
|
||||||
|
|||||||
@@ -49,13 +49,13 @@ if (process.env.FACEBOOK_CLIENT_ID && process.env.FACEBOOK_CLIENT_SECRET)
|
|||||||
const handler = (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
NextAuth(req, res, {
|
NextAuth(req, res, {
|
||||||
adapter: PrismaAdapter(prisma),
|
adapter: PrismaAdapter(prisma),
|
||||||
secret: process.env.SECRET,
|
secret: process.env.ENCRYPTION_SECRET,
|
||||||
providers,
|
providers,
|
||||||
session: {
|
session: {
|
||||||
strategy: 'database',
|
strategy: 'database',
|
||||||
},
|
},
|
||||||
callbacks: {
|
callbacks: {
|
||||||
session: async ({ session, user }) => ({ ...session, user }),
|
session: ({ session, user }) => ({ ...session, user }),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import path from 'path'
|
|||||||
const config: PlaywrightTestConfig = {
|
const config: PlaywrightTestConfig = {
|
||||||
globalSetup: require.resolve(path.join(__dirname, 'playwright/global-setup')),
|
globalSetup: require.resolve(path.join(__dirname, 'playwright/global-setup')),
|
||||||
testDir: path.join(__dirname, 'playwright/tests'),
|
testDir: path.join(__dirname, 'playwright/tests'),
|
||||||
timeout: 10 * 1000,
|
timeout: 10 * 2000,
|
||||||
expect: {
|
expect: {
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import test, { expect, Page } from '@playwright/test'
|
import test, { expect, Page } from '@playwright/test'
|
||||||
import { readFileSync } from 'fs'
|
import { readFileSync } from 'fs'
|
||||||
import { InputStepType } from 'models'
|
import { defaultTextInputOptions, InputStepType } from 'models'
|
||||||
import { parse } from 'papaparse'
|
import { parse } from 'papaparse'
|
||||||
import { generate } from 'short-uuid'
|
import { generate } from 'short-uuid'
|
||||||
import {
|
import {
|
||||||
@@ -17,7 +17,10 @@ test.describe('Results page', () => {
|
|||||||
await createTypebots([
|
await createTypebots([
|
||||||
{
|
{
|
||||||
id: typebotId,
|
id: typebotId,
|
||||||
...parseDefaultBlockWithStep({ type: InputStepType.TEXT }),
|
...parseDefaultBlockWithStep({
|
||||||
|
type: InputStepType.TEXT,
|
||||||
|
options: defaultTextInputOptions,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
await createResults({ typebotId })
|
await createResults({ typebotId })
|
||||||
|
|||||||
@@ -78,18 +78,25 @@ export const parseSubmissionsColumns = (
|
|||||||
(block) => typebot && block.steps.some((step) => isInputStep(step))
|
(block) => typebot && block.steps.some((step) => isInputStep(step))
|
||||||
)
|
)
|
||||||
.map((block) => {
|
.map((block) => {
|
||||||
const inputStep = block.steps.find((step) =>
|
const inputStep = block.steps.find((step) => isInputStep(step))
|
||||||
isInputStep(step)
|
if (!inputStep || !isInputStep(inputStep)) return
|
||||||
) as InputStep
|
|
||||||
return {
|
return {
|
||||||
Header: (
|
Header: (
|
||||||
<HStack>
|
<HStack
|
||||||
|
minW={
|
||||||
|
'isLong' in inputStep.options && inputStep.options.isLong
|
||||||
|
? '400px'
|
||||||
|
: '150px'
|
||||||
|
}
|
||||||
|
maxW="500px"
|
||||||
|
>
|
||||||
<StepIcon type={inputStep.type} />
|
<StepIcon type={inputStep.type} />
|
||||||
<Text>{block.title}</Text>
|
<Text>{block.title}</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
),
|
),
|
||||||
accessor: block.id,
|
accessor: block.id,
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
|
.filter(isDefined),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { stringify } from 'qs'
|
|||||||
import { Answer } from 'db'
|
import { Answer } from 'db'
|
||||||
import { sendRequest } from 'utils'
|
import { sendRequest } from 'utils'
|
||||||
|
|
||||||
|
const paginationLimit = 50
|
||||||
|
|
||||||
const getKey = (
|
const getKey = (
|
||||||
typebotId: string,
|
typebotId: string,
|
||||||
pageIndex: number,
|
pageIndex: number,
|
||||||
@@ -16,7 +18,7 @@ const getKey = (
|
|||||||
if (pageIndex === 0) return `/api/typebots/${typebotId}/results?limit=50`
|
if (pageIndex === 0) return `/api/typebots/${typebotId}/results?limit=50`
|
||||||
return `/api/typebots/${typebotId}/results?lastResultId=${
|
return `/api/typebots/${typebotId}/results?lastResultId=${
|
||||||
previousPageData.results[previousPageData.results.length - 1].id
|
previousPageData.results[previousPageData.results.length - 1].id
|
||||||
}&limit=50`
|
}&limit=${paginationLimit}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ResultWithAnswers = Result & { answers: Answer[] }
|
export type ResultWithAnswers = Result & { answers: Answer[] }
|
||||||
@@ -49,7 +51,10 @@ export const useResults = ({
|
|||||||
setSize,
|
setSize,
|
||||||
size,
|
size,
|
||||||
hasMore:
|
hasMore:
|
||||||
data && data.length > 0 && data[data.length - 1].results.length > 0,
|
data &&
|
||||||
|
data.length > 0 &&
|
||||||
|
data[data.length - 1].results.length > 0 &&
|
||||||
|
data.length === paginationLimit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
ENCRYPTION_SECRET=
|
||||||
NEXT_PUBLIC_VIEWER_HOST=http://localhost:3001
|
NEXT_PUBLIC_VIEWER_HOST=http://localhost:3001
|
||||||
DATABASE_URL=postgresql://postgres:@localhost:5432/typebot
|
DATABASE_URL=postgresql://postgres:@localhost:5432/typebot
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ export const ChatBlock = ({
|
|||||||
|
|
||||||
const displayNextStep = (answerContent?: string, isRetry?: boolean) => {
|
const displayNextStep = (answerContent?: string, isRetry?: boolean) => {
|
||||||
const currentStep = [...displayedSteps].pop()
|
const currentStep = [...displayedSteps].pop()
|
||||||
console.log(currentStep)
|
|
||||||
if (currentStep) {
|
if (currentStep) {
|
||||||
if (isRetry && stepCanBeRetried(currentStep))
|
if (isRetry && stepCanBeRetried(currentStep))
|
||||||
return setDisplayedSteps([
|
return setDisplayedSteps([
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { randomBytes, createCipheriv, createDecipheriv } from 'crypto'
|
import { randomBytes, createCipheriv, createDecipheriv } from 'crypto'
|
||||||
|
|
||||||
const algorithm = 'aes-256-gcm'
|
const algorithm = 'aes-256-gcm'
|
||||||
const secretKey = process.env.SECRET
|
const secretKey = process.env.ENCRYPTION_SECRET
|
||||||
|
|
||||||
export const encrypt = (
|
export const encrypt = (
|
||||||
data: object
|
data: object
|
||||||
|
|||||||
Reference in New Issue
Block a user