Skip to content
Open
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
53 changes: 41 additions & 12 deletions src/components/map-projects/AutoMatchDialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ import AIAssistantSelectorPanel from './AIAssistantSelectorPanel'
const AutoMatchDialog = ({
open,
onClose,
autoMatchUnmappedOnly,
setAutoMatchUnmappedOnly,
autoMatchScope,
setAutoMatchScope,
rowStatuses,
selectedRowCount,
autoRunAIAnalysis,
setAutoRunAIAnalysis,
AIModels,
Expand All @@ -47,11 +48,31 @@ const AutoMatchDialog = ({
}) => {
const { t } = useTranslation()
const [algos, setAlgos] = React.useState(true)
const selectedRows = autoMatchUnmappedOnly ? rowStatuses.unmapped.length : (rowStatuses.unmapped.length + rowStatuses.readyForReview.length)
const allRowsCount = rowStatuses.unmapped.length + rowStatuses.readyForReview.length
const rowsInSelectedScope = {
unmapped: rowStatuses.unmapped.length,
all: allRowsCount,
selected: selectedRowCount
}
const rowsToMatchCount = rowsInSelectedScope[autoMatchScope] || 0
const totalRows = rowStatuses.unmapped.length + rowStatuses.readyForReview.length + rowStatuses.reviewed.length
const hasSelectedRows = selectedRowCount > 0
const hasUnmappedRows = rowStatuses.unmapped.length > 0

React.useEffect(() => {
if (autoMatchScope === 'unmapped' && !hasUnmappedRows) {
setAutoMatchScope('all')
}
}, [autoMatchScope, hasUnmappedRows, setAutoMatchScope])

const getHelperTextForAutoMatchUnmapped = () => {
if (autoMatchUnmappedOnly) {
if (autoMatchScope === 'selected') {
if (hasSelectedRows) {
return t('map_project.auto_match_selected_rows_note', {count: selectedRowCount.toLocaleString()});
}
return '';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

auto_match_selected_rows_note_no_count was added to all 3 locales but is never referenced — this branch returns '', and it's effectively unreachable anyway (the selected radio is disabled without a selection). Either wire it here or drop the key from en/es/zh.

}
if (autoMatchScope === 'unmapped') {
const count = rowStatuses.unmapped.length;
if (count > 0) {
return t('map_project.auto_match_unmapped_only_note', {count: count.toLocaleString()});
Expand All @@ -69,7 +90,7 @@ const AutoMatchDialog = ({
return t('map_project.auto_match_note_no_counts');
};

const isDisabled = !repoVersion?.version_url || selectedRows === 0 || (!algos && !autoRunAIAnalysis)
const isDisabled = !repoVersion?.version_url || rowsToMatchCount === 0 || (!algos && !autoRunAIAnalysis)

return (
<Dialog
Expand Down Expand Up @@ -98,22 +119,30 @@ const AutoMatchDialog = ({
}
</div>
<FormControl sx={{marginTop: '16px'}}>
<FormLabel id="automatch-rows">{`${t('map_project.selected_rows')}: ${selectedRows.toLocaleString()} ${t('map_project.out_of')} ${totalRows.toLocaleString()}` }</FormLabel>
<FormLabel id="automatch-rows">{`${t('map_project.rows_to_match')}: ${rowsToMatchCount.toLocaleString()} ${t('map_project.out_of')} ${totalRows.toLocaleString()}` }</FormLabel>
<RadioGroup
row
aria-labelledby="automatch-rows"
name="automatch-rows"
onChange={() => setAutoMatchUnmappedOnly(!autoMatchUnmappedOnly)}
value={autoMatchScope}
onChange={event => setAutoMatchScope(event.target.value)}
>
<FormControlLabel
value="all"
control={<Radio />}
label={`${t('map_project.all_rows')} (${allRowsCount.toLocaleString()})`}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relabel (per review discussion): this option labeled "All Rows" actually targets Unmapped + Proposed (it skips Approved — see MapProject.jsx L1778), which collides with the main-page All bucket. Rename to "Unmapped & Proposed". map_project.all_rows is used only here, so add a dedicated key:

Suggested change
label={`${t('map_project.all_rows')} (${allRowsCount.toLocaleString()})`}
label={`${t('map_project.unmapped_and_proposed')} (${allRowsCount.toLocaleString()})`}

Then add the key to en/es/zh and remove the now-unused all_rows (please verify es/zh wording for consistency):

  • en: "unmapped_and_proposed": "Unmapped & Proposed"
  • es: "unmapped_and_proposed": "No mapeadas y propuestas"
  • zh: "unmapped_and_proposed": "未映射和已提议"

A true "All (incl. Approved)" / start-from-scratch scope (with a warning) is tracked separately in #2589.

/>
<FormControlLabel
value="unmapped"
control={<Radio checked={autoMatchUnmappedOnly} />}
label={t('map_project.unmapped_only')}
disabled={!hasUnmappedRows}
control={<Radio />}
label={`${t('map_project.unmapped_only')} (${rowStatuses.unmapped.length.toLocaleString()})`}
/>
<FormControlLabel
value="all"
control={<Radio checked={!autoMatchUnmappedOnly} />}
label={t('map_project.all_rows')}
value="selected"
disabled={!hasSelectedRows}
control={<Radio />}
label={`${t('map_project.selected_rows')} (${selectedRowCount.toLocaleString()})`}
/>
</RadioGroup>
</FormControl>
Expand Down
54 changes: 43 additions & 11 deletions src/components/map-projects/MapProject.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ const MapProject = () => {

const [matchDialog, setMatchDialog] = React.useState(false)
const [showItem, setShowItem] = React.useState(false)
const [autoMatchUnmappedOnly, setAutoMatchUnmappedOnly] = React.useState(true)
const [autoMatchScope, setAutoMatchScope] = React.useState('unmapped')
const [autoRunAIAnalysis, setAutoRunAIAnalysis] = React.useState(false)
const [alert, setAlert] = React.useState(false)
const [columnVisibilityModel, setColumnVisibilityModel] = React.useState({})
Expand Down Expand Up @@ -1008,7 +1008,7 @@ const MapProject = () => {
setDecisionTab('candidates')
setSearchText('')
setShowItem(false)
setAutoMatchUnmappedOnly(true)
setAutoMatchScope('unmapped')
setAlert(false)
setSelectedCandidatesScoreBucket(false)
setScoreBucketSortBy('desc')
Expand Down Expand Up @@ -1591,6 +1591,16 @@ const MapProject = () => {

const getRowsResults = async (rows, selectedAlgos) => {
abortRef.current = false;
const selectedRowIndexes = getSelectedRowIndexes(rows)
const selectedRowIndexSet = new Set(selectedRowIndexes)
const isAutoMatchUnmappedOnly = autoMatchScope === 'unmapped'
const isAutoMatchAllRows = autoMatchScope === 'all'
const isAutoMatchSelectedRows = autoMatchScope === 'selected'
const selectedRowsLogExtras = isAutoMatchSelectedRows ? {
selected_rows_count: selectedRowIndexes.length,
row_indexes: selectedRowIndexes,
selected_row_indexes: selectedRowIndexes
Comment on lines +1600 to +1602

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

row_indexes and selected_row_indexes are the same array, and this object is spread into both auto_match_started and auto_match_finished — which projectLog POSTs to …/logs/ (L1337). A large selection therefore persists the same index array up to 4× per run, while only selected_rows_count is read (in ProjectLogs). Drop the duplicate (keep whichever the analytics pipeline expects; if neither, selected_row_indexes is clearest) — and worth deciding whether the full array is needed at all vs. just the count:

Suggested change
selected_rows_count: selectedRowIndexes.length,
row_indexes: selectedRowIndexes,
selected_row_indexes: selectedRowIndexes
selected_rows_count: selectedRowIndexes.length,
selected_row_indexes: selectedRowIndexes

} : {}

// Function to process a single batch
const processBatch = async (_repo, rowBatch, algo) => {
Expand Down Expand Up @@ -1729,15 +1739,18 @@ const MapProject = () => {
let _selectedAlgos = filter(algosSelected, algo => selectedAlgos.includes(algo.id))
let subActions = [...map(_selectedAlgos, algo => algo.name || algo.id)]
subActions.push('reranker')
if(autoMatchUnmappedOnly)
if(isAutoMatchUnmappedOnly)
subActions.push('unmatched_only')
if(isAutoMatchSelectedRows)
subActions.push('selected_rows')
if(inAIAssistantGroup && autoRunAIAnalysis)
subActions.push('with_ai_analysis')

projectLog({
action: 'auto_match_started',
extras: {
sub_actions: subActions,
...selectedRowsLogExtras,
...(inAIAssistantGroup && autoRunAIAnalysis ? {
ai_assistant: {
model: getSelectedAIModel(),
Expand All @@ -1747,13 +1760,25 @@ const MapProject = () => {
}
})

if(!autoMatchUnmappedOnly)
if(isAutoMatchAllRows)
setRowStatuses(prev => ({...prev, readyForReview: []}))
if(isAutoMatchSelectedRows)
setRowStatuses(prev => ({
...prev,
readyForReview: without(prev.readyForReview, ...selectedRowIndexes),
reviewed: without(prev.reviewed, ...selectedRowIndexes)
}))

setTimeout(async () => {
const rowsToProcess = autoMatchUnmappedOnly
const rowsToProcess = isAutoMatchUnmappedOnly
? filter(rows, row => rowStatuses.unmapped.includes(row.__index))
: filter(rows, row => !rowStatuses.reviewed.includes(row.__index))
: filter(rows, row => {
if(isAutoMatchSelectedRows)
return selectedRowIndexSet.has(row.__index)
if(rowStatuses.reviewed.includes(row.__index))
return false
return true
})

// ocl_online#105 Phase 5: open the run record, then guarantee it is
// closed out (completed / partial / failed / cancelled) via the finally,
Expand Down Expand Up @@ -1805,6 +1830,7 @@ const MapProject = () => {
action: 'auto_match_finished',
extras: {
sub_actions: subActions,
...selectedRowsLogExtras,
...(inAIAssistantGroup && autoRunAIAnalysis ? {
ai_assistant: {
model: getSelectedAIModel(),
Expand Down Expand Up @@ -2043,6 +2069,7 @@ const MapProject = () => {
const onGetCandidates = event => {
event.stopPropagation()
event.preventDefault()
setAutoMatchScope(getSelectedRowIndexes().length ? 'selected' : (rowStatuses.unmapped.length ? 'unmapped' : 'all'))
setMatchDialog(true)
}

Expand Down Expand Up @@ -2677,7 +2704,10 @@ const MapProject = () => {
})
}

const getSelectedRowIndexes = () => selectedRowIds.map(id => parseInt(id)).filter(Number.isFinite)
const getSelectedRowIndexes = (_rows = data) => {
const selectedIds = new Set(selectedRowIds.map(id => id?.toString()))
return _rows.filter(_row => selectedIds.has(_row.__index?.toString())).map(_row => _row.__index)
}
Comment on lines +2707 to +2710

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Robustness regression: data initializes to false (L157), so when this runs with no args (e.g. onGetCandidates before the sheet finishes parsing — the button is gated on file, not data), _rows is false and .filter throws. The previous selectedRowIds.map(parseInt) version never touched data. Cheap guard (isArray is already imported):

Suggested change
const getSelectedRowIndexes = (_rows = data) => {
const selectedIds = new Set(selectedRowIds.map(id => id?.toString()))
return _rows.filter(_row => selectedIds.has(_row.__index?.toString())).map(_row => _row.__index)
}
const getSelectedRowIndexes = (_rows = data) => {
if(!isArray(_rows)) return []
const selectedIds = new Set(selectedRowIds.map(id => id?.toString()))
return _rows.filter(_row => selectedIds.has(_row.__index?.toString())).map(_row => _row.__index)
}


const openBulkConfirm = action => {
const indexes = getSelectedRowIndexes()
Expand Down Expand Up @@ -3934,10 +3964,11 @@ const MapProject = () => {
const rows = getRows()
const visibleRowIds = rows.map(_row => _row.__index)
const visibleRowIdKey = visibleRowIds.join(',')
const selectedRowsCount = selectedRowIds.length
const selectedRowsCount = getSelectedRowIndexes(rows).length
React.useEffect(() => {
setSelectedRowIds(prev => {
const next = prev.filter(id => visibleRowIds.includes(parseInt(id)))
const visibleRowIdSet = new Set(visibleRowIds.map(id => id?.toString()))
const next = prev.filter(id => visibleRowIdSet.has(id?.toString()))
return next.length === prev.length ? prev : next
})
}, [visibleRowIdKey])
Expand Down Expand Up @@ -4965,8 +4996,9 @@ const MapProject = () => {
onSubmit={onGetCandidatesSubmit}
{...{
rowStatuses,
autoMatchUnmappedOnly,
setAutoMatchUnmappedOnly,
autoMatchScope,
setAutoMatchScope,
selectedRowCount: selectedRowsCount,
autoRunAIAnalysis,
setAutoRunAIAnalysis,
AIModels,
Expand Down
7 changes: 5 additions & 2 deletions src/components/map-projects/ProjectLogs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,15 @@ const ProjectLogs = ({onClose, logs, project}) => {
</a>
</span>
if(['auto_match_started', 'auto_match_finished', 'auto_matched'].includes(log.action)) {
const subActions = map(log.extras?.sub_actions || [], formatSubAction).filter(subAction => log.extras?.selected_rows_count ? subAction !== 'Selected Rows' : true)
if(log.extras?.selected_rows_count)
subActions.push(`${log.extras.selected_rows_count.toLocaleString()} Selected Rows`)
return <span>
{startCase(log.action)}
{
log.extras?.sub_actions?.length ?
subActions.length ?
<span style={{marginLeft: '4px'}}>
{`(${map(log.extras.sub_actions, formatSubAction).join(', ')})`}
{`(${subActions.join(', ')})`}
</span> :
null
}
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/locales/en/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,8 @@
"auto_match_unmapped_only_note_no_count": "Note: Skip input rows that are already proposed",
"auto_match_note": "Note: This will not affect {{approvedCount}} approved input rows but will override {{proposedCount}} proposed input rows",
"auto_match_note_no_counts": "Note: This will not affect approved input rows but will override proposed input rows",
"auto_match_selected_rows_note": "Note: This will run Auto Match for {{count}} selected input rows",
"auto_match_selected_rows_note_no_count": "Note: Select rows in the left panel to enable this option",
"run_ai_analysis": "Run AI Analysis",
"run_ai_analysis_note": "Note: Enabling this feature will run AI Analysis on results of each row. This has direct cost implications.",
"decision": "Decision",
Expand Down Expand Up @@ -632,6 +634,7 @@
"select_an_algo": "Select an algorithm",
"selected_rows": "Selected Rows",
"all_rows": "All Rows",
"rows_to_match": "Rows to Match",
"out_of": "out of",
"retrieve_candidates": "Retrieve Candidates",
"retrieve_candidates_helper_text": "Your project is configured to use the following match algorithms:",
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/locales/es/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,8 @@
"auto_match_unmapped_only_note_no_count": "Nota: Omitir filas de entrada que ya están propuestas",
"auto_match_note": "Nota: Esto no afectará {{approvedCount}} filas de entrada aprobadas pero sobrescribirá {{proposedCount}} filas de entrada propuestas",
"auto_match_note_no_counts": "Nota: Esto no afectará las filas de entrada aprobadas pero sobrescribirá las filas de entrada propuestas",
"auto_match_selected_rows_note": "Nota: Esto ejecutará Auto Match para {{count}} fila(s) seleccionada(s)",
"auto_match_selected_rows_note_no_count": "Nota: Seleccione filas en el panel izquierdo para habilitar esta opción",
"run_ai_analysis": "Ejecutar Análisis de IA",
"run_ai_analysis_note": "Nota: Habilitar esta función ejecutará Análisis de IA en los resultados de cada fila. Esto tiene implicaciones de costo directas.",
"decision": "Decisión",
Expand Down Expand Up @@ -609,6 +611,7 @@
"select_an_algo": "Seleccione un algoritmo",
"selected_rows": "Filas seleccionadas",
"all_rows": "Todas las filas",
"rows_to_match": "Filas para coincidir",
"out_of": "de",
"retrieve_candidates": "Recuperar candidatos",
"retrieve_candidates_helper_text": "Su proyecto está configurado para usar los siguientes algoritmos de coincidencia:",
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/locales/zh/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,8 @@
"auto_match_unmapped_only_note_no_count": "注意:跳过已提议的输入行",
"auto_match_note": "注意:这不会影响 {{approvedCount}} 个已批准的输入行,但会覆盖 {{proposedCount}} 个已提议的输入行",
"auto_match_note_no_counts": "注意:这不会影响已批准的输入行,但会覆盖已提议的输入行",
"auto_match_selected_rows_note": "注意:这将对 {{count}} 个已选输入行运行自动匹配",
"auto_match_selected_rows_note_no_count": "注意:请在左侧面板选择行以启用此选项",
"run_ai_analysis": "运行 AI 分析",
"run_ai_analysis_note": "注意:启用此功能将对每行的结果运行 AI 分析。这会产生直接的成本影响。",
"decision": "决策",
Expand Down Expand Up @@ -634,6 +636,7 @@
"select_an_algo": "选择算法",
"selected_rows": "已选行",
"all_rows": "所有行",
"rows_to_match": "要匹配的行",
"out_of": "共",
"retrieve_candidates": "获取候选项",
"retrieve_candidates_helper_text": "你的项目已配置为使用以下匹配算法:",
Expand Down