diff --git a/.gitignore b/.gitignore index e71d7a9..249347e 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ Thumbs.db # Uploads uploads/* !uploads/.gitkeep + +*.tsv diff --git a/ui/src/api.js b/ui/src/api.js index 95aa969..19434c8 100644 --- a/ui/src/api.js +++ b/ui/src/api.js @@ -53,6 +53,12 @@ export const api = { // Mappings getMappings: (source, rule) => request('GET', `/mappings/source/${source}${rule ? `?rule_name=${rule}` : ''}`), getUnmapped: (source, rule) => request('GET', `/mappings/source/${source}/unmapped${rule ? `?rule_name=${rule}` : ''}`), + exportMappingsUrl: (source, rule) => `${BASE}/mappings/source/${source}/export.tsv${rule ? `?rule_name=${rule}` : ''}`, + importMappingsCSV: (source, file) => { + const fd = new FormData() + fd.append('file', file) + return request('POST', `/mappings/source/${source}/import-csv`, fd, true) + }, createMapping: (body) => request('POST', '/mappings', body), bulkMappings: (mappings) => request('POST', '/mappings/bulk', { mappings }), updateMapping: (id, body) => request('PUT', `/mappings/${id}`, body), diff --git a/ui/src/pages/Mappings.jsx b/ui/src/pages/Mappings.jsx index 47f1a0a..0cd5404 100644 --- a/ui/src/pages/Mappings.jsx +++ b/ui/src/pages/Mappings.jsx @@ -24,6 +24,7 @@ export default function Mappings({ source }) { const [loading, setLoading] = useState(false) const [editingId, setEditingId] = useState(null) const [editDrafts, setEditDrafts] = useState({}) + const [importing, setImporting] = useState(false) useEffect(() => { if (!source) return @@ -102,6 +103,30 @@ export default function Mappings({ source }) { } } + async function handleImportCSV(e) { + const file = e.target.files[0] + if (!file) return + e.target.value = '' + setImporting(true) + try { + const result = await api.importMappingsCSV(source, file) + alert(`Imported ${result.count} mapping${result.count !== 1 ? 's' : ''}.`) + // Refresh current tab + const rule = selectedRule || undefined + const [u, m] = await Promise.all([ + api.getUnmapped(source, rule), + tab === 'mapped' ? api.getMappings(source, rule) : Promise.resolve([]) + ]) + setUnmapped(u) + setMapped(m) + setDrafts({}) + } catch (err) { + alert(err.message) + } finally { + setImporting(false) + } + } + async function deleteMapping(id) { try { await api.deleteMapping(id) @@ -157,6 +182,19 @@ export default function Mappings({ source }) {

Mappings — {source}

+
+ + Export CSV + + +
{/* Rule filter */}