2
0

fix(results): 🐛 Loading rows

This commit is contained in:
Baptiste Arnaud
2022-02-11 15:30:02 +01:00
parent 901e2f39b0
commit 93fed893c0
13 changed files with 85 additions and 90 deletions

View File

@@ -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

View File

@@ -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>
))} ))}
</> </>
) )

View File

@@ -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>
) )
} }
) )

View File

@@ -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}

View File

@@ -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",

View File

@@ -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 }),
}, },
}) })
} }

View File

@@ -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,
}, },

View File

@@ -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 })

View File

@@ -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),
] ]
} }

View File

@@ -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,
} }
} }

View File

@@ -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

View File

@@ -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([

View File

@@ -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