Skip to content

Commit

Permalink
refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
fhennig committed Jan 14, 2025
1 parent 390e205 commit e3d14e8
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 46 deletions.
25 changes: 9 additions & 16 deletions website/src/components/Submission/FileUpload/ColumnMapping.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,9 @@ import { RawFile } from './fileProcessing';
describe('ColumnMapping', () => {
it('should create a mapping from columns', () => {
const sourceColumns = ['date', 'location', 'Foo Bar'];
const targetColumns = new Map([
['date', null],
['location', null],
['foo', 'Foo Bar'],
]);
const inputFields = [{ name: 'date' }, { name: 'location' }, { name: 'foo', displayName: 'Foo Bar' }];

const mapping = ColumnMapping.fromColumns(sourceColumns, targetColumns);
const mapping = ColumnMapping.fromColumns(sourceColumns, inputFields);
const entries = mapping.entries();

expect(entries).toEqual([
Expand All @@ -24,11 +20,11 @@ describe('ColumnMapping', () => {

it('should update a specific mapping', () => {
const sourceColumns = ['loc'];
const targetColumns = new Map([
['location', 'Location'],
['date', 'Date'],
]);
const mapping = ColumnMapping.fromColumns(sourceColumns, targetColumns);
const inputFields = [
{ name: 'location', displayName: 'Location' },
{ name: 'date', displayName: 'Date' },
];
const mapping = ColumnMapping.fromColumns(sourceColumns, inputFields);

const updatedMapping = mapping.updateWith('loc', 'date');

Expand All @@ -38,11 +34,8 @@ describe('ColumnMapping', () => {

it('should apply a mapping correctly', async () => {
const sourceColumns = ['loc', 'date'];
const targetColumns = new Map([
['date', null],
['location', 'Location'],
]);
const mapping = ColumnMapping.fromColumns(sourceColumns, targetColumns);
const inputFields = [{ name: 'date' }, { name: 'location', displayName: 'Location' }];
const mapping = ColumnMapping.fromColumns(sourceColumns, inputFields);
const updatedMapping = mapping.updateWith('loc', 'location');

const tsvContent = 'date\tloc\n' + '2023-01-01\tUSA\n' + '2023-01-02\tCanada';
Expand Down
39 changes: 18 additions & 21 deletions website/src/components/Submission/FileUpload/ColumnMapping.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,53 @@
import { stringSimilarity } from 'string-similarity-js';

import { type ProcessedFile } from './fileProcessing';
import type { InputField } from '../../../types/config';

export class ColumnMapping {
private readonly map: ReadonlyMap<string, string | null>;
private readonly displayNames: ReadonlyMap<string, string | null>;

private constructor(map: ReadonlyMap<string, string | null>, displayNames: ReadonlyMap<string, string | null>) {
private constructor(map: ReadonlyMap<string, string | null>) {
this.map = map;
this.displayNames = displayNames;
}

private static getBestMatchingTargetColumn(
sourceColumn: string,
targetColumns: ReadonlyMap<string, string | null>,
): string | null {
const [bestMatch, score] = Array.from(targetColumns.entries())
.map(([targetColName, targetColDisplayName]): [string, number] => {
private static getBestMatchingTargetColumn(sourceColumn: string, inputFields: InputField[]): string | null {
const [bestMatch, score] = inputFields
.map((field): [string, number] => {
const score = Math.max(
stringSimilarity(sourceColumn, targetColName),
stringSimilarity(sourceColumn, targetColDisplayName ?? ''),
stringSimilarity(sourceColumn, field.name),
stringSimilarity(sourceColumn, field.displayName ?? ''),
);
return [targetColName, score];
return [field.name, score];
})
.reduce((maxItem, currentItem) => (currentItem[1] > maxItem[1] ? currentItem : maxItem));
return score > 0.5 ? bestMatch : null;
}

/* Create a new mapping with the given columns, doing a best-effort to pre-match columns. */
public static fromColumns(sourceColumns: string[], targetColumns: Map<string, string | null>) {
public static fromColumns(sourceColumns: string[], inputFields: InputField[]) {
const mapping = new Map(
sourceColumns.map((sourceColumn) => [
sourceColumn,
this.getBestMatchingTargetColumn(sourceColumn, targetColumns),
this.getBestMatchingTargetColumn(sourceColumn, inputFields),
]),
);
return new ColumnMapping(mapping, targetColumns);
return new ColumnMapping(mapping);
}

/* Update the mapping with new source and target columns, trying to keep as much of the
mapping intact as possible. */
public update(newSourceColumns: string[], newTargetColumns: Map<string, string | null>): ColumnMapping {
public update(newSourceColumns: string[], inputFields: InputField[]): ColumnMapping {
const newMapping = new Map(
newSourceColumns.map((newSourceCol) => {
const prevTargetCol = this.map.get(newSourceCol);
if (prevTargetCol && Array.from(newTargetColumns.keys()).includes(prevTargetCol)) {
if (prevTargetCol && inputFields.map((f) => f.name).includes(prevTargetCol)) {
return [newSourceCol, prevTargetCol];
} else {
return [newSourceCol, ColumnMapping.getBestMatchingTargetColumn(newSourceCol, newTargetColumns)];
return [newSourceCol, ColumnMapping.getBestMatchingTargetColumn(newSourceCol, inputFields)];
}
}),
);
return new ColumnMapping(newMapping, newTargetColumns);
return new ColumnMapping(newMapping);
}

/* Returns the entries in the mapping as a list. Each item in the list has:
Expand All @@ -69,7 +65,7 @@ export class ColumnMapping {
public updateWith(sourceColumn: string, targetColumn: string | null): ColumnMapping {
const newMapping = new Map(this.map);
newMapping.set(sourceColumn, targetColumn);
return new ColumnMapping(newMapping, this.displayNames);
return new ColumnMapping(newMapping);
}

/* Apply this mapping to a TSV file, returning a new file with remapped columns. */
Expand All @@ -96,6 +92,7 @@ export class ColumnMapping {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mapsAreEqual = (m1: ReadonlyMap<any, any>, m2: ReadonlyMap<any, any>) =>
m1.size === m2.size && Array.from(m1.keys()).every((key) => m1.get(key) === m2.get(key));
return mapsAreEqual(this.displayNames, other.displayNames) && mapsAreEqual(this.map, other.map);

return mapsAreEqual(this.map, other.map);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,11 @@ export const ColumnMappingModal: FC<ColumnMappingModalProps> = ({

useEffect(() => {
if (inputColumns === null) return;
const targetColumnsWithDisplayNames = new Map<string, string | null>(
Array.from(possibleTargetColumns.values())
.flat()
.map((inputField) => [inputField.name, inputField.displayName ?? null]),
);
const inputFields = Array.from(possibleTargetColumns.values()).flat();
if (columnMapping !== null) {
setCurrentMapping(columnMapping.update(inputColumns, targetColumnsWithDisplayNames));
setCurrentMapping(columnMapping.update(inputColumns, inputFields));
} else {
setCurrentMapping(ColumnMapping.fromColumns(inputColumns, targetColumnsWithDisplayNames));
setCurrentMapping(ColumnMapping.fromColumns(inputColumns, inputFields));
}
}, [inputColumns, columnMapping, possibleTargetColumns, setCurrentMapping]);

Expand Down Expand Up @@ -168,11 +164,12 @@ export const ColumnSelectorRow: FC<ColumnSelectorRowProps> = ({
selectedOption,
setColumnMapping,
}) => {
const selectedOptionText = selectedOption
const selectedField = selectedOption
? Array.from(options.values())
.flat()
.find((o) => o.name === selectedOption)?.displayName
.find((o) => o.name === selectedOption)
: undefined;
const selectedOptionText = selectedField?.displayName ?? selectedField?.name;

return (
<tr key={selectingFor} className='border-gray-400 border-solid border-x-0 border-y'>
Expand Down

0 comments on commit e3d14e8

Please sign in to comment.