Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

948 | Fix: Error handling for malformed file uploads in testsets #977

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion agenta-backend/agenta_backend/routers/testset_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from fastapi import HTTPException, APIRouter, UploadFile, File, Form, Request
from fastapi.responses import JSONResponse
from pydantic import ValidationError

from agenta_backend.models.api.testset_model import (
TestSetSimpleResponse,
Expand Down Expand Up @@ -99,7 +100,10 @@ async def upload_file(
document["csvdata"].append(row)

user = await get_user(user_uid=user_org_data["uid"])
testset_instance = TestSetDB(**document, user=user)
try:
testset_instance = TestSetDB(**document, user=user)
except ValidationError as e:
raise HTTPException(status_code=403, detail=e.errors())
result = await engine.save(testset_instance)

if isinstance(result.id, ObjectId):
Expand Down
2 changes: 1 addition & 1 deletion agenta-web/src/components/TestSetTable/TestsetTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import useStateCallback from "@/hooks/useStateCallback"
import {AxiosResponse} from "axios"
import EditRowModal from "./EditRowModal"
import {getVariantInputParameters} from "@/lib/helpers/variantHelper"
import {convertToCsv, downloadCsv} from "@/lib/helpers/utils"
import {convertToCsv, downloadCsv} from "@/lib/helpers/fileManipulations"
import {NoticeType} from "antd/es/message/interface"
import {GenericObject, KeyValuePair} from "@/lib/Types"

Expand Down
2 changes: 1 addition & 1 deletion agenta-web/src/lib/helpers/evaluate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {HumanEvaluationListTableDataType} from "@/components/Evaluations/HumanEvaluationResult"
import {Evaluation, GenericObject, Variant} from "../Types"
import {convertToCsv, downloadCsv} from "./utils"
import {convertToCsv, downloadCsv} from "./fileManipulations"

export const exportExactEvaluationData = (evaluation: Evaluation, rows: GenericObject[]) => {
const exportRow = rows.map((data, ix) => {
Expand Down
46 changes: 46 additions & 0 deletions agenta-web/src/lib/helpers/fileManipulations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Papa from "papaparse"
import {GenericObject} from "../Types"

export const convertToCsv = (rows: GenericObject[], header: string[]) => {
return Papa.unparse({fields: header.filter((item) => !!item), data: rows})
}

export const downloadCsv = (csvContent: string, filename: string): void => {
if (typeof window === "undefined") return

const blob = new Blob([csvContent], {type: "text/csv"})
const link = document.createElement("a")
link.href = URL.createObjectURL(blob)
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}

export const isValidCSVFile = (file: File) => {
return new Promise((res) => {
Papa.parse(file, {
complete: (results) => {
res(results.errors.length === 0)
},
error: () => {
res(false)
},
})
})
}

export const isValidJSONFile = (file: File) => {
return new Promise((res) => {
const reader = new FileReader()
reader.onload = (e) => {
try {
JSON.parse(e.target?.result as string)
res(true)
} catch (e) {
res(false)
}
}
reader.readAsText(file)
})
}
19 changes: 0 additions & 19 deletions agenta-web/src/lib/helpers/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import dynamic from "next/dynamic"
import {EvaluationType} from "../enums"
import {GenericObject} from "../Types"
import Papa from "papaparse"

const llmAvailableProvidersToken = "llmAvailableProvidersToken"

Expand Down Expand Up @@ -139,24 +138,6 @@ export const isAppNameInputValid = (input: string) => {
return /^[a-zA-Z0-9_-]+$/.test(input)
}

type RowType = Record<string, any>

export const convertToCsv = (rows: RowType[], header: string[]) => {
return Papa.unparse({fields: header.filter((item) => !!item), data: rows})
}

export const downloadCsv = (csvContent: string, filename: string): void => {
if (typeof window === "undefined") return

const blob = new Blob([csvContent], {type: "text/csv"})
const link = document.createElement("a")
link.href = URL.createObjectURL(blob)
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}

export const delay = (ms: number) => new Promise((res) => setTimeout(res, ms))

export const snakeToCamel = (str: string) =>
Expand Down
33 changes: 26 additions & 7 deletions agenta-web/src/pages/apps/[app_id]/testsets/new/upload/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import {useState} from "react"
import axios from "@/lib/helpers/axiosConfig"
import {useRouter} from "next/router"
import {createUseStyles} from "react-jss"
import {isValidCSVFile, isValidJSONFile} from "@/lib/helpers/fileManipulations"
import {GenericObject} from "@/lib/Types"
import {globalErrorHandler} from "@/lib/helpers/errorHandler"

const useStyles = createUseStyles({
fileFormatBtn: {
Expand Down Expand Up @@ -32,16 +35,21 @@ export default function AddANewTestset() {

const onFinish = async (values: any) => {
const {file} = values

if (!values.file) {
message.error("Please select a file to upload")
return
}
const fileObj = file[0].originFileObj
const malformedFileError = `The file you uploaded is either malformed or is not a valid ${uploadType} file`

if (file && file.length > 0 && uploadType) {
const isValidFile = await (uploadType == "CSV"
? isValidCSVFile(fileObj)
: isValidJSONFile(fileObj))
if (!isValidFile) {
message.error(malformedFileError)
return
}

const formData = new FormData()
formData.append("upload_type", uploadType)
formData.append("file", file[0].originFileObj)
formData.append("file", fileObj)
if (values.testsetName && values.testsetName.trim() !== "") {
formData.append("testset_name", values.testsetName)
}
Expand All @@ -57,10 +65,20 @@ export default function AddANewTestset() {
headers: {
"Content-Type": "multipart/form-data",
},
//@ts-ignore
_ignoreError: true,
},
)
form.resetFields()
router.push(`/apps/${appId}/testsets`)
} catch (e: any) {
if (
e?.response?.data?.detail?.find(
(item: GenericObject) => item?.loc?.includes("csvdata"),
)
)
message.error(malformedFileError)
else globalErrorHandler(e)
} finally {
setUploadLoading(false)
}
Expand Down Expand Up @@ -156,6 +174,7 @@ export default function AddANewTestset() {
valuePropName="fileList"
getValueFromEvent={(e) => e.fileList}
label="Test set source"
rules={[{required: true}]}
>
<Upload.Dragger
name="file"
Expand All @@ -173,7 +192,7 @@ export default function AddANewTestset() {
</Form.Item>

<Form.Item {...tailLayout}>
<Button type="primary" htmlType="submit">
<Button type="primary" htmlType="submit" disabled={uploadLoading}>
Add test set
</Button>
</Form.Item>
Expand Down
Loading