@@ -180,7 +180,8 @@
|
||||
"type": "Typebot link",
|
||||
"options": {
|
||||
"typebotId": "current",
|
||||
"groupId": "vLUAPaxKwPF49iZhg4XZYa"
|
||||
"groupId": "vLUAPaxKwPF49iZhg4XZYa",
|
||||
"mergeResults": false
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -305,7 +306,8 @@
|
||||
"groupId": "1GvxCAAEysxJMxrVngud3X",
|
||||
"options": {
|
||||
"groupId": "vLUAPaxKwPF49iZhg4XZYa",
|
||||
"typebotId": "current"
|
||||
"typebotId": "current",
|
||||
"mergeResults": false
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@@ -282,7 +282,8 @@
|
||||
"type": "Typebot link",
|
||||
"options": {
|
||||
"typebotId": "current",
|
||||
"groupId": "cl96ns9qr00043b6ii07bo25o"
|
||||
"groupId": "cl96ns9qr00043b6ii07bo25o",
|
||||
"mergeResults": false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -618,7 +618,8 @@
|
||||
"groupId": "cl1r15f68005f2e6dvdtal7cp",
|
||||
"options": {
|
||||
"groupId": "cl1r09bc6000h2e6dqml18p4p",
|
||||
"typebotId": "current"
|
||||
"typebotId": "current",
|
||||
"mergeResults": false
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@@ -65,7 +65,8 @@ export const getLinkedTypebots = authenticatedProcedure
|
||||
(typebotIds, block) =>
|
||||
block.type === LogicBlockType.TYPEBOT_LINK &&
|
||||
isDefined(block.options.typebotId) &&
|
||||
!typebotIds.includes(block.options.typebotId)
|
||||
!typebotIds.includes(block.options.typebotId) &&
|
||||
block.options.mergeResults !== false
|
||||
? [...typebotIds, block.options.typebotId]
|
||||
: typebotIds,
|
||||
[]
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Stack } from '@chakra-ui/react'
|
||||
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
|
||||
import { TypebotLinkOptions } from '@typebot.io/schemas'
|
||||
import { byId } from '@typebot.io/lib'
|
||||
import { GroupsDropdown } from './GroupsDropdown'
|
||||
import { TypebotsDropdown } from './TypebotsDropdown'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
import { isNotEmpty } from '@typebot.io/lib'
|
||||
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
|
||||
|
||||
type Props = {
|
||||
options: TypebotLinkOptions
|
||||
@@ -12,21 +13,30 @@ type Props = {
|
||||
}
|
||||
|
||||
export const TypebotLinkForm = ({ options, onOptionsChange }: Props) => {
|
||||
const { linkedTypebots, typebot, save } = useTypebot()
|
||||
const [linkedTypebotId, setLinkedTypebotId] = useState(options.typebotId)
|
||||
const { typebot } = useTypebot()
|
||||
|
||||
const handleTypebotIdChange = async (
|
||||
typebotId: string | 'current' | undefined
|
||||
) => onOptionsChange({ ...options, typebotId })
|
||||
) => onOptionsChange({ ...options, typebotId, groupId: undefined })
|
||||
|
||||
const { data: linkedTypebotData } = trpc.typebot.getTypebot.useQuery(
|
||||
{
|
||||
typebotId: options.typebotId as string,
|
||||
},
|
||||
{
|
||||
enabled: isNotEmpty(options.typebotId) && options.typebotId !== 'current',
|
||||
}
|
||||
)
|
||||
|
||||
const handleGroupIdChange = (groupId: string | undefined) =>
|
||||
onOptionsChange({ ...options, groupId })
|
||||
|
||||
useEffect(() => {
|
||||
if (linkedTypebotId === options.typebotId) return
|
||||
setLinkedTypebotId(options.typebotId)
|
||||
save().then()
|
||||
}, [linkedTypebotId, options.typebotId, save])
|
||||
const updateMergeResults = (mergeResults: boolean) =>
|
||||
onOptionsChange({ ...options, mergeResults })
|
||||
|
||||
const isCurrentTypebotSelected =
|
||||
(typebot && options.typebotId === typebot.id) ||
|
||||
options.typebotId === 'current'
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
@@ -40,22 +50,30 @@ export const TypebotLinkForm = ({ options, onOptionsChange }: Props) => {
|
||||
)}
|
||||
{options.typebotId && (
|
||||
<GroupsDropdown
|
||||
key={options.typebotId}
|
||||
groups={
|
||||
typebot &&
|
||||
(options.typebotId === typebot.id ||
|
||||
options.typebotId === 'current')
|
||||
typebot && isCurrentTypebotSelected
|
||||
? typebot.groups
|
||||
: linkedTypebots?.find(byId(options.typebotId))?.groups ?? []
|
||||
: linkedTypebotData?.typebot?.groups ?? []
|
||||
}
|
||||
groupId={options.groupId}
|
||||
onGroupIdSelected={handleGroupIdChange}
|
||||
isLoading={
|
||||
linkedTypebots === undefined &&
|
||||
linkedTypebotData?.typebot === undefined &&
|
||||
options.typebotId !== 'current' &&
|
||||
typebot &&
|
||||
typebot.id !== options.typebotId
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{!isCurrentTypebotSelected && (
|
||||
<SwitchWithLabel
|
||||
label="Merge answers"
|
||||
moreInfoContent="If enabled, the answers collected in the linked typebot will be merged with the results of the current typebot."
|
||||
initialValue={options.mergeResults ?? true}
|
||||
onCheckChange={updateMergeResults}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,24 +2,36 @@ import { TypebotLinkBlock } from '@typebot.io/schemas'
|
||||
import React from 'react'
|
||||
import { Tag, Text } from '@chakra-ui/react'
|
||||
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
|
||||
import { byId } from '@typebot.io/lib'
|
||||
import { byId, isNotEmpty } from '@typebot.io/lib'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
|
||||
type Props = {
|
||||
block: TypebotLinkBlock
|
||||
}
|
||||
|
||||
export const TypebotLinkNode = ({ block }: Props) => {
|
||||
const { linkedTypebots, typebot } = useTypebot()
|
||||
const { typebot } = useTypebot()
|
||||
|
||||
const { data: linkedTypebotData } = trpc.typebot.getTypebot.useQuery(
|
||||
{
|
||||
typebotId: block.options.typebotId as string,
|
||||
},
|
||||
{
|
||||
enabled:
|
||||
isNotEmpty(block.options.typebotId) &&
|
||||
block.options.typebotId !== 'current',
|
||||
}
|
||||
)
|
||||
|
||||
const isCurrentTypebot =
|
||||
typebot &&
|
||||
(block.options.typebotId === typebot.id ||
|
||||
block.options.typebotId === 'current')
|
||||
const linkedTypebot = isCurrentTypebot
|
||||
? typebot
|
||||
: linkedTypebots?.find(byId(block.options.typebotId))
|
||||
const linkedTypebot = isCurrentTypebot ? typebot : linkedTypebotData?.typebot
|
||||
const blockTitle = linkedTypebot?.groups.find(
|
||||
byId(block.options.groupId)
|
||||
)?.title
|
||||
|
||||
if (!block.options.typebotId)
|
||||
return <Text color="gray.500">Configure...</Text>
|
||||
return (
|
||||
|
||||
@@ -61,7 +61,17 @@ export const TypebotsDropdown = ({
|
||||
aria-label="Navigate to typebot"
|
||||
icon={<ExternalLinkIcon />}
|
||||
as={Link}
|
||||
href={`/typebots/${typebotId}/edit?parentId=${query.typebotId}`}
|
||||
href={{
|
||||
pathname: '/typebots/[typebotId]/edit',
|
||||
query: {
|
||||
typebotId,
|
||||
parentId: query.parentId
|
||||
? Array.isArray(query.parentId)
|
||||
? query.parentId.concat(query.typebotId?.toString() ?? '')
|
||||
: [query.parentId, query.typebotId?.toString() ?? '']
|
||||
: query.typebotId ?? [],
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
@@ -156,13 +156,22 @@ export const TypebotHeader = () => {
|
||||
as={Link}
|
||||
aria-label="Navigate back"
|
||||
icon={<ChevronLeftIcon fontSize={25} />}
|
||||
href={
|
||||
router.query.parentId
|
||||
? `/typebots/${router.query.parentId}/edit`
|
||||
href={{
|
||||
pathname: router.query.parentId
|
||||
? '/typebots/[typebotId]/edit'
|
||||
: typebot?.folderId
|
||||
? `/typebots/folders/${typebot.folderId}`
|
||||
: '/typebots'
|
||||
}
|
||||
? '/typebots/folders/[folderId]'
|
||||
: '/typebots',
|
||||
query: {
|
||||
folderId: typebot?.folderId ?? [],
|
||||
parentId: Array.isArray(router.query.parentId)
|
||||
? router.query.parentId.slice(0, -1)
|
||||
: [],
|
||||
typebotId: Array.isArray(router.query.parentId)
|
||||
? [...router.query.parentId].pop()
|
||||
: router.query.parentId ?? [],
|
||||
},
|
||||
}}
|
||||
size="sm"
|
||||
/>
|
||||
<HStack spacing={1}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LogicBlockType, PublicTypebot, Typebot } from '@typebot.io/schemas'
|
||||
import { PublicTypebot, Typebot } from '@typebot.io/schemas'
|
||||
import { Router, useRouter } from 'next/router'
|
||||
import {
|
||||
createContext,
|
||||
@@ -49,7 +49,6 @@ const typebotContext = createContext<
|
||||
{
|
||||
typebot?: Typebot
|
||||
publishedTypebot?: PublicTypebot
|
||||
linkedTypebots?: Pick<Typebot, 'id' | 'groups' | 'variables' | 'name'>[]
|
||||
isReadOnly?: boolean
|
||||
isPublished: boolean
|
||||
isSavingLoading: boolean
|
||||
@@ -132,36 +131,6 @@ export const TypebotProvider = ({
|
||||
{ redo, undo, flush, canRedo, canUndo, set: setLocalTypebot },
|
||||
] = useUndo<Typebot>(undefined)
|
||||
|
||||
const linkedTypebotIds = useMemo(
|
||||
() =>
|
||||
typebot?.groups
|
||||
.flatMap((group) => group.blocks)
|
||||
.reduce<string[]>(
|
||||
(typebotIds, block) =>
|
||||
block.type === LogicBlockType.TYPEBOT_LINK &&
|
||||
isDefined(block.options.typebotId) &&
|
||||
!typebotIds.includes(block.options.typebotId)
|
||||
? [...typebotIds, block.options.typebotId]
|
||||
: typebotIds,
|
||||
[]
|
||||
) ?? [],
|
||||
[typebot?.groups]
|
||||
)
|
||||
|
||||
const { data: linkedTypebotsData } = trpc.getLinkedTypebots.useQuery(
|
||||
{
|
||||
typebotId: typebot?.id as string,
|
||||
},
|
||||
{
|
||||
enabled: isDefined(typebot?.id) && linkedTypebotIds.length > 0,
|
||||
onError: (error) =>
|
||||
showToast({
|
||||
title: 'Error while fetching linkedTypebots',
|
||||
description: error.message,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!typebot && isDefined(localTypebot)) setLocalTypebot(undefined)
|
||||
if (isFetchingTypebot) return
|
||||
@@ -270,7 +239,6 @@ export const TypebotProvider = ({
|
||||
value={{
|
||||
typebot: localTypebot,
|
||||
publishedTypebot,
|
||||
linkedTypebots: linkedTypebotsData?.typebots ?? [],
|
||||
isReadOnly: typebotData?.isReadOnly,
|
||||
isSavingLoading: isSaving,
|
||||
save: saveTypebot,
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { useToast } from '@/hooks/useToast'
|
||||
import { ResultHeaderCell, ResultWithAnswers } from '@typebot.io/schemas'
|
||||
import {
|
||||
LogicBlockType,
|
||||
ResultHeaderCell,
|
||||
ResultWithAnswers,
|
||||
} from '@typebot.io/schemas'
|
||||
import { createContext, ReactNode, useContext, useMemo } from 'react'
|
||||
import { parseResultHeader } from '@typebot.io/lib/results'
|
||||
import { useTypebot } from '../editor/providers/TypebotProvider'
|
||||
import { useResultsQuery } from './hooks/useResultsQuery'
|
||||
import { TableData } from './types'
|
||||
import { convertResultsToTableData } from './helpers/convertResultsToTableData'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
import { isDefined } from '@typebot.io/lib/utils'
|
||||
|
||||
const resultsContext = createContext<{
|
||||
resultsList: { results: ResultWithAnswers[] }[] | undefined
|
||||
@@ -32,7 +38,7 @@ export const ResultsProvider = ({
|
||||
totalResults: number
|
||||
onDeleteResults: (totalResultsDeleted: number) => void
|
||||
}) => {
|
||||
const { publishedTypebot, linkedTypebots } = useTypebot()
|
||||
const { publishedTypebot } = useTypebot()
|
||||
const { showToast } = useToast()
|
||||
const { data, fetchNextPage, hasNextPage, refetch } = useResultsQuery({
|
||||
typebotId,
|
||||
@@ -41,6 +47,29 @@ export const ResultsProvider = ({
|
||||
},
|
||||
})
|
||||
|
||||
const linkedTypebotIds =
|
||||
publishedTypebot?.groups
|
||||
.flatMap((group) => group.blocks)
|
||||
.reduce<string[]>(
|
||||
(typebotIds, block) =>
|
||||
block.type === LogicBlockType.TYPEBOT_LINK &&
|
||||
isDefined(block.options.typebotId) &&
|
||||
!typebotIds.includes(block.options.typebotId) &&
|
||||
block.options.mergeResults !== false
|
||||
? [...typebotIds, block.options.typebotId]
|
||||
: typebotIds,
|
||||
[]
|
||||
) ?? []
|
||||
|
||||
const { data: linkedTypebotsData } = trpc.getLinkedTypebots.useQuery(
|
||||
{
|
||||
typebotId,
|
||||
},
|
||||
{
|
||||
enabled: linkedTypebotIds.length > 0,
|
||||
}
|
||||
)
|
||||
|
||||
const flatResults = useMemo(
|
||||
() => data?.flatMap((d) => d.results) ?? [],
|
||||
[data]
|
||||
@@ -49,9 +78,9 @@ export const ResultsProvider = ({
|
||||
const resultHeader = useMemo(
|
||||
() =>
|
||||
publishedTypebot
|
||||
? parseResultHeader(publishedTypebot, linkedTypebots)
|
||||
? parseResultHeader(publishedTypebot, linkedTypebotsData?.typebots)
|
||||
: [],
|
||||
[linkedTypebots, publishedTypebot]
|
||||
[linkedTypebotsData?.typebots, publishedTypebot]
|
||||
)
|
||||
|
||||
const tableData = useMemo(
|
||||
|
||||
@@ -23,6 +23,7 @@ import { useResults } from '../../ResultsProvider'
|
||||
import { parseColumnOrder } from '../../helpers/parseColumnsOrder'
|
||||
import { convertResultsToTableData } from '../../helpers/convertResultsToTableData'
|
||||
import { parseAccessor } from '../../helpers/parseAccessor'
|
||||
import { isDefined } from '@typebot.io/lib'
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean
|
||||
@@ -30,7 +31,7 @@ type Props = {
|
||||
}
|
||||
|
||||
export const ExportAllResultsModal = ({ isOpen, onClose }: Props) => {
|
||||
const { typebot, publishedTypebot, linkedTypebots } = useTypebot()
|
||||
const { typebot, publishedTypebot } = useTypebot()
|
||||
const workspaceId = typebot?.workspaceId
|
||||
const typebotId = typebot?.id
|
||||
const { showToast } = useToast()
|
||||
@@ -41,6 +42,15 @@ export const ExportAllResultsModal = ({ isOpen, onClose }: Props) => {
|
||||
const [areDeletedBlocksIncluded, setAreDeletedBlocksIncluded] =
|
||||
useState(false)
|
||||
|
||||
const { data: linkedTypebotsData } = trpc.getLinkedTypebots.useQuery(
|
||||
{
|
||||
typebotId: typebotId as string,
|
||||
},
|
||||
{
|
||||
enabled: isDefined(typebotId),
|
||||
}
|
||||
)
|
||||
|
||||
const getAllResults = async () => {
|
||||
if (!workspaceId || !typebotId) return []
|
||||
const allResults = []
|
||||
@@ -71,7 +81,11 @@ export const ExportAllResultsModal = ({ isOpen, onClose }: Props) => {
|
||||
const results = await getAllResults()
|
||||
|
||||
const resultHeader = areDeletedBlocksIncluded
|
||||
? parseResultHeader(publishedTypebot, linkedTypebots, results)
|
||||
? parseResultHeader(
|
||||
publishedTypebot,
|
||||
linkedTypebotsData?.typebots,
|
||||
results
|
||||
)
|
||||
: existingResultHeader
|
||||
|
||||
const dataToUnparse = convertResultsToTableData(results, resultHeader)
|
||||
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
LogicBlockType,
|
||||
defaultAbTestOptions,
|
||||
BlockWithItems,
|
||||
defaultTypebotLinkOptions,
|
||||
} from '@typebot.io/schemas'
|
||||
import { defaultPictureChoiceOptions } from '@typebot.io/schemas/features/blocks/inputs/pictureChoice'
|
||||
|
||||
@@ -122,7 +123,7 @@ const parseDefaultBlockOptions = (type: BlockWithOptionsType): BlockOptions => {
|
||||
case LogicBlockType.JUMP:
|
||||
return {}
|
||||
case LogicBlockType.TYPEBOT_LINK:
|
||||
return {}
|
||||
return defaultTypebotLinkOptions
|
||||
case LogicBlockType.AB_TEST:
|
||||
return defaultAbTestOptions
|
||||
case IntegrationBlockType.GOOGLE_SHEETS:
|
||||
|
||||
Reference in New Issue
Block a user