Spaces:
Running
Running
/** | |
* | |
* Copyright 2023-2025 InspectorRAGet Team | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
* | |
**/ | |
'use client'; | |
import { isEmpty } from 'lodash'; | |
import { memo, useState, useMemo, useEffect } from 'react'; | |
import { | |
Button, | |
DataTable, | |
TableContainer, | |
Table, | |
TableHead, | |
TableRow, | |
TableHeader, | |
TableBody, | |
TableCell, | |
Pagination, | |
} from '@carbon/react'; | |
import { ArrowLeft, ArrowRight, WarningAlt } from '@carbon/icons-react'; | |
import { RawData } from '@/src/types'; | |
import { DataErrorKinds, processData } from '@/src/processor'; | |
import { useNotification } from '@/src/components/notification/Notification'; | |
import ExampleTile from '@/src/components/example-tile/ExampleTile'; | |
import classes from './DataVerification.module.scss'; | |
interface Props { | |
data: RawData; | |
onNext: Function; | |
onPrev: Function; | |
} | |
interface disqualifiedTaskRow { | |
id: string; | |
taskId: string; | |
models: Set<string>; | |
metrics: Set<string>; | |
reasons: string[]; | |
} | |
export default memo(function DataVerificationView({ | |
data, | |
onNext, | |
onPrev, | |
}: Props) { | |
const [page, setPage] = useState(1); | |
const [pageSize, setPageSize] = useState(10); | |
const [visibleRows, setVisibleRows] = useState<disqualifiedTaskRow[]>([]); | |
const { createNotification } = useNotification(); | |
// Process and validate data | |
const [exampleData, disqualifiedTasks, notifications] = useMemo( | |
() => processData(data), | |
[data], | |
); | |
useEffect(() => { | |
if (notifications) { | |
notifications.forEach((notification) => { | |
createNotification(notification); | |
}); | |
} | |
}, [notifications]); | |
// Create headers, rows and visible rows | |
const headers = [ | |
{ | |
key: 'taskId', | |
header: 'Task ID', | |
}, | |
{ | |
key: 'models', | |
header: 'Missing Models', | |
}, | |
{ | |
key: 'metrics', | |
header: 'Missing Metrics', | |
}, | |
{ | |
key: 'reasons', | |
header: 'Reasons', | |
}, | |
]; | |
const rows = useMemo(() => { | |
return Object.keys(disqualifiedTasks).map((taskId) => { | |
const missingModels = new Set<string>(); | |
const missingMetrics = new Set<string>(); | |
const reasons = new Set<string>(); | |
disqualifiedTasks[taskId].reasons.forEach((reason) => { | |
reasons.add(reason.kind); | |
if (reason.kind === DataErrorKinds.MISSING_METRIC) { | |
missingMetrics.add(reason.data); | |
} else if (reason.kind === DataErrorKinds.MISSING_MODEL) { | |
missingModels.add(reason.data); | |
} | |
}); | |
return { | |
id: taskId, | |
taskId: taskId, | |
models: missingModels, | |
metrics: missingMetrics, | |
reasons: [...reasons], | |
}; | |
}); | |
}, [disqualifiedTasks]); | |
useEffect( | |
() => | |
setVisibleRows( | |
rows.slice((page - 1) * pageSize, (page - 1) * pageSize + pageSize), | |
), | |
[rows, page, pageSize], | |
); | |
return ( | |
<div className={classes.root}> | |
{exampleData ? ( | |
<> | |
<ExampleTile | |
data={exampleData} | |
disableNavigation={true} | |
></ExampleTile> | |
{isEmpty(rows) ? ( | |
<div className={classes.successMessageContainer}> | |
<h3>Everything looks good. Enjoy analyzing!</h3> | |
</div> | |
) : ( | |
<> | |
<h4>Disqualified Tasks</h4> | |
{headers && rows && ( | |
<div className={classes.disqualifiedTasksContainer}> | |
<DataTable rows={visibleRows} headers={headers}> | |
{({ | |
rows, | |
headers, | |
getTableProps, | |
getHeaderProps, | |
getRowProps, | |
}) => ( | |
<TableContainer className={classes.table}> | |
<Table {...getTableProps()}> | |
<TableHead> | |
<TableRow> | |
{headers.map((header, index) => ( | |
<TableHeader | |
key={'header--' + index} | |
{...getHeaderProps({ header })} | |
> | |
{header.header} | |
</TableHeader> | |
))} | |
</TableRow> | |
</TableHead> | |
<TableBody> | |
{rows.map((row, index) => ( | |
<TableRow | |
key={'row--' + index} | |
{...getRowProps({ row })} | |
> | |
{row.cells.map((cell) => ( | |
<TableCell key={cell.id}> | |
{cell.info.header != 'taskId' | |
? !isEmpty(cell.value) | |
? [...cell.value].join(', ') | |
: '-' | |
: cell.value} | |
</TableCell> | |
))} | |
</TableRow> | |
))} | |
</TableBody> | |
</Table> | |
</TableContainer> | |
)} | |
</DataTable> | |
<Pagination | |
pageSizes={[10]} | |
totalItems={rows.length} | |
onChange={(event: any) => { | |
// Step 1: Update page size | |
setPageSize(event.pageSize); | |
// Step 2: Update page | |
setPage(event.page); | |
}} | |
></Pagination> | |
</div> | |
)} | |
</> | |
)} | |
<div className={classes.navigationContainer}> | |
{exampleData.tasks.length > 500 ? ( | |
<div className={classes.warningContainer}> | |
<WarningAlt | |
className={classes.warningContainerIcon} | |
size={18} | |
/> | |
<span className={classes.warningContainerText}> | |
{`Due to high number of data points, it might take us a moment to get everything ready once you click the "visualize" button.`} | |
</span> | |
</div> | |
) : null} | |
<div className={classes.navigationButtons}> | |
<Button | |
kind="secondary" | |
renderIcon={ArrowLeft} | |
iconDescription="Return to uploading data" | |
onClick={onPrev} | |
> | |
Return to uploading data | |
</Button> | |
<Button | |
disabled={!exampleData} | |
renderIcon={ArrowRight} | |
iconDescription="Proceed" | |
onClick={() => { | |
onNext(exampleData); | |
}} | |
> | |
Visualize | |
</Button> | |
</div> | |
</div> | |
</> | |
) : null} | |
</div> | |
); | |
}); | |