🚸 (results) Use header id as table accessor to allow duplicate names
This commit is contained in:
@@ -22,8 +22,7 @@ import { parseResultHeader } from '@typebot.io/lib/results'
|
||||
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'
|
||||
import { byId, isDefined } from '@typebot.io/lib'
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean
|
||||
@@ -90,40 +89,35 @@ export const ExportAllResultsModal = ({ isOpen, onClose }: Props) => {
|
||||
|
||||
const dataToUnparse = convertResultsToTableData(results, resultHeader)
|
||||
|
||||
const fields = parseColumnOrder(
|
||||
const headerIds = parseColumnOrder(
|
||||
typebot?.resultsTablePreferences?.columnsOrder,
|
||||
resultHeader
|
||||
).reduce<string[]>((currentHeaderLabels, columnId) => {
|
||||
).reduce<string[]>((currentHeaderIds, columnId) => {
|
||||
if (
|
||||
typebot?.resultsTablePreferences?.columnsVisibility[columnId] === false
|
||||
)
|
||||
return currentHeaderLabels
|
||||
return currentHeaderIds
|
||||
const columnLabel = resultHeader.find(
|
||||
(headerCell) => headerCell.id === columnId
|
||||
)?.label
|
||||
if (!columnLabel) return currentHeaderLabels
|
||||
return [...currentHeaderLabels, columnLabel]
|
||||
)?.id
|
||||
if (!columnLabel) return currentHeaderIds
|
||||
return [...currentHeaderIds, columnLabel]
|
||||
}, [])
|
||||
|
||||
const data = dataToUnparse.map<{ [key: string]: string }>((data) => {
|
||||
const newObject: { [key: string]: string } = {}
|
||||
fields?.forEach((field) => {
|
||||
newObject[field] = data[parseAccessor(field)]?.plainText
|
||||
headerIds?.forEach((headerId) => {
|
||||
const headerLabel = resultHeader.find(byId(headerId))?.label
|
||||
if (!headerLabel) return
|
||||
const newKey = parseUniqueKey(headerLabel, Object.keys(newObject))
|
||||
newObject[newKey] = data[headerId]?.plainText
|
||||
})
|
||||
return newObject
|
||||
})
|
||||
|
||||
const csvData = new Blob(
|
||||
[
|
||||
unparse({
|
||||
data,
|
||||
fields,
|
||||
}),
|
||||
],
|
||||
{
|
||||
type: 'text/csv;charset=utf-8;',
|
||||
}
|
||||
)
|
||||
const csvData = new Blob([unparse(data)], {
|
||||
type: 'text/csv;charset=utf-8;',
|
||||
})
|
||||
const fileName = `typebot-export_${new Date()
|
||||
.toLocaleDateString()
|
||||
.replaceAll('/', '-')}`
|
||||
@@ -166,3 +160,12 @@ export const ExportAllResultsModal = ({ isOpen, onClose }: Props) => {
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export const parseUniqueKey = (
|
||||
key: string,
|
||||
existingKeys: string[],
|
||||
count = 0
|
||||
): string => {
|
||||
if (!existingKeys.includes(key)) return key
|
||||
return parseUniqueKey(`${key} (${count + 1})`, existingKeys, count + 1)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import { CellValueType, TableData } from '../../types'
|
||||
import { IndeterminateCheckbox } from './IndeterminateCheckbox'
|
||||
import { colors } from '@/lib/theme'
|
||||
import { parseColumnOrder } from '../../helpers/parseColumnsOrder'
|
||||
import { parseAccessor } from '../../helpers/parseAccessor'
|
||||
import { HeaderIcon } from '../HeaderIcon'
|
||||
|
||||
type ResultsTableProps = {
|
||||
@@ -136,7 +135,7 @@ export const ResultsTable = ({
|
||||
},
|
||||
...resultHeader.map<ColumnDef<TableData>>((header) => ({
|
||||
id: header.id,
|
||||
accessorKey: parseAccessor(header.label),
|
||||
accessorKey: header.id,
|
||||
size: 200,
|
||||
header: () => (
|
||||
<HStack overflow="hidden" data-testid={`${header.label} header`}>
|
||||
|
||||
@@ -15,7 +15,8 @@ import { useToast } from '@/hooks/useToast'
|
||||
import { useResults } from '../../ResultsProvider'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
import { parseColumnOrder } from '../../helpers/parseColumnsOrder'
|
||||
import { parseAccessor } from '../../helpers/parseAccessor'
|
||||
import { byId } from '@typebot.io/lib/utils'
|
||||
import { parseUniqueKey } from './ExportAllResultsModal'
|
||||
|
||||
type Props = {
|
||||
selectedResultsId: string[]
|
||||
@@ -69,21 +70,21 @@ export const SelectionToolbar = ({
|
||||
selectedResultsId.includes(data.id.plainText)
|
||||
)
|
||||
|
||||
const fields = parseColumnOrder(
|
||||
const headerIds = parseColumnOrder(
|
||||
typebot?.resultsTablePreferences?.columnsOrder,
|
||||
resultHeader
|
||||
)
|
||||
.reduce<string[]>((currentHeaderLabels, columnId) => {
|
||||
.reduce<string[]>((currentHeaderIds, columnId) => {
|
||||
if (
|
||||
typebot?.resultsTablePreferences?.columnsVisibility[columnId] ===
|
||||
false
|
||||
)
|
||||
return currentHeaderLabels
|
||||
return currentHeaderIds
|
||||
const columnLabel = resultHeader.find(
|
||||
(headerCell) => headerCell.id === columnId
|
||||
)?.label
|
||||
if (!columnLabel) return currentHeaderLabels
|
||||
return [...currentHeaderLabels, columnLabel]
|
||||
)?.id
|
||||
if (!columnLabel) return currentHeaderIds
|
||||
return [...currentHeaderIds, columnLabel]
|
||||
}, [])
|
||||
.concat(
|
||||
typebot?.resultsTablePreferences?.columnsOrder
|
||||
@@ -94,29 +95,24 @@ export const SelectionToolbar = ({
|
||||
headerCell.id
|
||||
)
|
||||
)
|
||||
.map((headerCell) => headerCell.label)
|
||||
.map((headerCell) => headerCell.id)
|
||||
: []
|
||||
)
|
||||
|
||||
const data = dataToUnparse.map<{ [key: string]: string }>((data) => {
|
||||
const newObject: { [key: string]: string } = {}
|
||||
fields?.forEach((field) => {
|
||||
newObject[field] = data[parseAccessor(field)]?.plainText
|
||||
headerIds?.forEach((headerId) => {
|
||||
const headerLabel = resultHeader.find(byId(headerId))?.label
|
||||
if (!headerLabel) return
|
||||
const newKey = parseUniqueKey(headerLabel, Object.keys(newObject))
|
||||
newObject[newKey] = data[headerId]?.plainText
|
||||
})
|
||||
return newObject
|
||||
})
|
||||
|
||||
const csvData = new Blob(
|
||||
[
|
||||
unparse({
|
||||
data,
|
||||
fields,
|
||||
}),
|
||||
],
|
||||
{
|
||||
type: 'text/csv;charset=utf-8;',
|
||||
}
|
||||
)
|
||||
const csvData = new Blob([unparse(data)], {
|
||||
type: 'text/csv;charset=utf-8;',
|
||||
})
|
||||
const fileName = `typebot-export_${new Date()
|
||||
.toLocaleDateString()
|
||||
.replaceAll('/', '-')}`
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
import { FileLinks } from '../components/FileLinks'
|
||||
import { TableData } from '../types'
|
||||
import { convertDateToReadable } from './convertDateToReadable'
|
||||
import { parseAccessor } from './parseAccessor'
|
||||
|
||||
export const convertResultsToTableData = (
|
||||
results: ResultWithAnswers[] | undefined,
|
||||
@@ -18,7 +17,7 @@ export const convertResultsToTableData = (
|
||||
): TableData[] =>
|
||||
(results ?? []).map((result) => ({
|
||||
id: { plainText: result.id },
|
||||
'Submitted at': {
|
||||
date: {
|
||||
plainText: convertDateToReadable(result.createdAt),
|
||||
},
|
||||
...[...result.answers, ...result.variables].reduce<{
|
||||
@@ -40,22 +39,19 @@ export const convertResultsToTableData = (
|
||||
const content = variableValue ?? answer.content
|
||||
return {
|
||||
...tableData,
|
||||
[parseAccessor(header.label)]: parseCellContent(
|
||||
content,
|
||||
header.blockType
|
||||
),
|
||||
[header.id]: parseCellContent(content, header.blockType),
|
||||
}
|
||||
}
|
||||
const variable = answerOrVariable satisfies VariableWithValue
|
||||
if (variable.value === null) return tableData
|
||||
const key = headerCells.find((headerCell) =>
|
||||
const headerId = headerCells.find((headerCell) =>
|
||||
headerCell.variableIds?.includes(variable.id)
|
||||
)?.label
|
||||
if (!key) return tableData
|
||||
if (isDefined(tableData[key])) return tableData
|
||||
)?.id
|
||||
if (!headerId) return tableData
|
||||
if (isDefined(tableData[headerId])) return tableData
|
||||
return {
|
||||
...tableData,
|
||||
[parseAccessor(key)]: parseCellContent(variable.value),
|
||||
[headerId]: parseCellContent(variable.value),
|
||||
}
|
||||
}, {}),
|
||||
}))
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const parseAccessor = (label: string) => label.replaceAll('.', '')
|
||||
@@ -2,7 +2,6 @@ import { HStack, Text } from '@chakra-ui/react'
|
||||
import { ResultHeaderCell } from '@typebot.io/schemas'
|
||||
import { HeaderIcon } from '../components/HeaderIcon'
|
||||
import { HeaderCell } from '../types'
|
||||
import { parseAccessor } from './parseAccessor'
|
||||
|
||||
export const parseHeaderCells = (
|
||||
resultHeader: ResultHeaderCell[]
|
||||
@@ -14,5 +13,5 @@ export const parseHeaderCells = (
|
||||
<Text>{header.label}</Text>
|
||||
</HStack>
|
||||
),
|
||||
accessor: parseAccessor(header.label),
|
||||
accessor: header.id,
|
||||
}))
|
||||
|
||||
Reference in New Issue
Block a user