fix: metadata batch edit silently fails due to split transactions and swallowed exceptions (#32041)

This commit is contained in:
Varun Chawla
2026-02-11 20:59:59 -08:00
committed by GitHub
parent 3fd1eea4d7
commit f233e2036f
5 changed files with 142 additions and 20 deletions

View File

@ -22,6 +22,7 @@ type MetadataItemWithEdit = {
type: DataType
value: string | number | null
isMultipleValue?: boolean
isUpdated?: boolean
updateType?: UpdateType
}
@ -615,6 +616,91 @@ describe('useBatchEditDocumentMetadata', () => {
})
})
describe('toCleanMetadataItem sanitization', () => {
it('should strip extra fields (isMultipleValue, updateType, isUpdated) from metadata items sent to backend', async () => {
const docListSingleDoc: DocListItem[] = [
{
id: 'doc-1',
doc_metadata: [
{ id: '1', name: 'field_one', type: DataType.string, value: 'Old Value' },
],
},
]
const { result } = renderHook(() =>
useBatchEditDocumentMetadata({
...defaultProps,
docList: docListSingleDoc as Parameters<typeof useBatchEditDocumentMetadata>[0]['docList'],
}),
)
const editedList: MetadataItemWithEdit[] = [
{
id: '1',
name: 'field_one',
type: DataType.string,
value: 'New Value',
isMultipleValue: true,
isUpdated: true,
updateType: UpdateType.changeValue,
},
]
await act(async () => {
await result.current.handleSave(editedList, [], false)
})
const callArgs = mockMutateAsync.mock.calls[0][0]
const sentItem = callArgs.metadata_list[0].metadata_list[0]
// Only id, name, type, value should be present
expect(Object.keys(sentItem).sort()).toEqual(['id', 'name', 'type', 'value'].sort())
expect(sentItem).not.toHaveProperty('isMultipleValue')
expect(sentItem).not.toHaveProperty('updateType')
expect(sentItem).not.toHaveProperty('isUpdated')
})
it('should coerce undefined value to null in metadata items sent to backend', async () => {
const docListSingleDoc: DocListItem[] = [
{
id: 'doc-1',
doc_metadata: [
{ id: '1', name: 'field_one', type: DataType.string, value: 'Value' },
],
},
]
const { result } = renderHook(() =>
useBatchEditDocumentMetadata({
...defaultProps,
docList: docListSingleDoc as Parameters<typeof useBatchEditDocumentMetadata>[0]['docList'],
}),
)
// Pass an item with value explicitly set to undefined (via cast)
const editedList: MetadataItemWithEdit[] = [
{
id: '1',
name: 'field_one',
type: DataType.string,
value: undefined as unknown as null,
updateType: UpdateType.changeValue,
},
]
await act(async () => {
await result.current.handleSave(editedList, [], false)
})
const callArgs = mockMutateAsync.mock.calls[0][0]
const sentItem = callArgs.metadata_list[0].metadata_list[0]
// value should be null, not undefined
expect(sentItem.value).toBeNull()
expect(sentItem.value).not.toBeUndefined()
})
})
describe('Edge Cases', () => {
it('should handle empty docList', () => {
const { result } = renderHook(() =>

View File

@ -71,6 +71,13 @@ const useBatchEditDocumentMetadata = ({
return res
}, [metaDataList])
const toCleanMetadataItem = (item: MetadataItemWithValue | MetadataItemWithEdit | MetadataItemInBatchEdit): MetadataItemWithValue => ({
id: item.id,
name: item.name,
type: item.type,
value: item.value ?? null,
})
const formateToBackendList = (editedList: MetadataItemWithEdit[], addedList: MetadataItemInBatchEdit[], isApplyToAllSelectDocument: boolean) => {
const updatedList = editedList.filter((editedItem) => {
return editedItem.updateType === UpdateType.changeValue
@ -92,24 +99,19 @@ const useBatchEditDocumentMetadata = ({
.filter((item) => {
return !removedList.find(removedItem => removedItem.id === item.id)
})
.map(item => ({
id: item.id,
name: item.name,
type: item.type,
value: item.value,
}))
.map(toCleanMetadataItem)
if (isApplyToAllSelectDocument) {
// add missing metadata item
updatedList.forEach((editedItem) => {
if (!newMetadataList.find(i => i.id === editedItem.id) && !editedItem.isMultipleValue)
newMetadataList.push(editedItem)
newMetadataList.push(toCleanMetadataItem(editedItem))
})
}
newMetadataList = newMetadataList.map((item) => {
const editedItem = updatedList.find(i => i.id === item.id)
if (editedItem)
return editedItem
return toCleanMetadataItem(editedItem)
return item
})