Skip to content

Commit

Permalink
fuzzy matching to dfc pairs when fuzzy search is enabled
Browse files Browse the repository at this point in the history
resolves #165
  • Loading branch information
ndepaola committed Aug 17, 2023
1 parent 64ae475 commit 7b5164f
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 33 deletions.
77 changes: 63 additions & 14 deletions frontend/src/common/processing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ test("empty prefix is processed correctly", () => {
});

test("line that doesn't specify quantity is processed correctly", () => {
expect(processLine("opt", dfcPairs)).toStrictEqual([
expect(processLine("opt", dfcPairs, false)).toStrictEqual([
1,
{
query: { card_type: Card, query: "opt" },
Expand All @@ -80,7 +80,7 @@ test("line that doesn't specify quantity is processed correctly", () => {
});

test("non-dfc line is processed correctly", () => {
expect(processLine("3x Lightning Bolt", dfcPairs)).toStrictEqual([
expect(processLine("3x Lightning Bolt", dfcPairs, false)).toStrictEqual([
3,
{
query: { card_type: Card, query: "lightning bolt" },
Expand All @@ -92,7 +92,7 @@ test("non-dfc line is processed correctly", () => {
});

test("non-dfc cardback line is processed correctly", () => {
expect(processLine("3x b:Black Lotus", dfcPairs)).toStrictEqual([
expect(processLine("3x b:Black Lotus", dfcPairs, false)).toStrictEqual([
3,
{
query: { card_type: Cardback, query: "black lotus" },
Expand All @@ -104,7 +104,7 @@ test("non-dfc cardback line is processed correctly", () => {
});

test("manually specified front and back line is processed correctly", () => {
expect(processLine("5 Opt | Char", dfcPairs)).toStrictEqual([
expect(processLine("5 Opt | Char", dfcPairs, false)).toStrictEqual([
5,
{
query: { card_type: Card, query: "opt" },
Expand All @@ -120,7 +120,9 @@ test("manually specified front and back line is processed correctly", () => {
});

test("line that matches to dfc pair is processed correctly", () => {
expect(processLine("2 Huntmaster of the Fells", dfcPairs)).toStrictEqual([
expect(
processLine("2 Huntmaster of the Fells", dfcPairs, false)
).toStrictEqual([
2,
{
query: { card_type: Card, query: "huntmaster of the fells" },
Expand All @@ -135,9 +137,51 @@ test("line that matches to dfc pair is processed correctly", () => {
]);
});

test("line that fuzzy matches to dfc pair is processed correctly", () => {
expect(processLine("2 bat", { batman: "ratman" }, true)).toStrictEqual([
2,
{
query: { card_type: Card, query: "bat" },
selectedImage: undefined,
selected: false,
},
{
query: { card_type: Card, query: "ratman" },
selectedImage: undefined,
selected: false,
},
]);
});

test("line that doesn't fuzzy match to dfc pair is processed correctly", () => {
expect(processLine("2 cat", { batman: "ratman" }, true)).toStrictEqual([
2,
{
query: { card_type: Card, query: "cat" },
selectedImage: undefined,
selected: false,
},
null,
]);
});

test("line that fuzzy matches ambiguously to dfc pair is processed correctly", () => {
expect(
processLine("2 bat", { batman: "ratman", batwoman: "ratwoman" }, true)
).toStrictEqual([
2,
{
query: { card_type: Card, query: "bat" },
selectedImage: undefined,
selected: false,
},
null, // ambiguous -> no match
]);
});

test("line that matches to dfc pair but a back is also manually specified is processed correctly", () => {
expect(
processLine("2 Huntmaster of the Fells | t:Goblin", dfcPairs)
processLine("2 Huntmaster of the Fells | t:Goblin", dfcPairs, false)
).toStrictEqual([
2,
{
Expand All @@ -155,9 +199,13 @@ test("line that matches to dfc pair but a back is also manually specified is pro

test("a card name that's a subset of a DFC pair's front is not matched", () => {
expect(
processStringAsMultipleLines("1 elesh norn\n1 elesh norn, grand cenobite", {
"elesh norn": "the argent etchings",
})
processStringAsMultipleLines(
"1 elesh norn\n1 elesh norn, grand cenobite",
{
"elesh norn": "the argent etchings",
},
false
)
).toStrictEqual([
[
1,
Expand Down Expand Up @@ -185,7 +233,7 @@ test("a card name that's a subset of a DFC pair's front is not matched", () => {
});

test("line that requests 0 of a card is processed correctly", () => {
expect(processLine("0 opt", dfcPairs)).toStrictEqual([
expect(processLine("0 opt", dfcPairs, false)).toStrictEqual([
0,
{
query: { card_type: Card, query: "opt" },
Expand All @@ -197,7 +245,7 @@ test("line that requests 0 of a card is processed correctly", () => {
});

test("line that requests -1 of a card is processed correctly", () => {
expect(processLine("-1 opt", dfcPairs)).toStrictEqual([
expect(processLine("-1 opt", dfcPairs, false)).toStrictEqual([
1,
{
query: { card_type: Card, query: "-1 opt" },
Expand All @@ -212,7 +260,8 @@ test("multiple lines processed correctly", () => {
expect(
processStringAsMultipleLines(
"char\n0 lightning bolt\n2x delver of secrets",
dfcPairs
dfcPairs,
false
)
).toStrictEqual([
[
Expand Down Expand Up @@ -241,7 +290,7 @@ test("multiple lines processed correctly", () => {
});

test("a line specifying the selected image ID for the front is processed correctly", () => {
expect(processLine("opt@xyz", dfcPairs)).toStrictEqual([
expect(processLine("opt@xyz", dfcPairs, false)).toStrictEqual([
1,
{
query: { card_type: Card, query: "opt" },
Expand All @@ -253,7 +302,7 @@ test("a line specifying the selected image ID for the front is processed correct
});

test("a line specifying the selected image ID for both faces is processed correctly", () => {
expect(processLine("2 opt@xyz | char@abcd", dfcPairs)).toStrictEqual([
expect(processLine("2 opt@xyz | char@abcd", dfcPairs, false)).toStrictEqual([
2,
{
query: { card_type: Card, query: "opt" },
Expand Down
52 changes: 37 additions & 15 deletions frontend/src/common/processing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
ProcessedLine,
ProjectMember,
SearchQuery,
SearchSettings,
SlotProjectMembers,
} from "@/common/types";

Expand Down Expand Up @@ -115,7 +116,11 @@ function unpackLine(
];
}

export function processLine(line: string, dfcPairs: DFCPairs): ProcessedLine {
export function processLine(
line: string,
dfcPairs: DFCPairs,
fuzzySearch: boolean
): ProcessedLine {
/**
* Process `line` to identify the search query and the number of instances requested for each face.
* If no back query is specified, attempt to match the front query to a DFC pair.
Expand Down Expand Up @@ -143,16 +148,27 @@ export function processLine(line: string, dfcPairs: DFCPairs): ProcessedLine {
if (backRawQuery != null && (backRawQuery[0] ?? "").length > 0) {
backQuery = processPrefix(backRawQuery[0]);
backSelectedImage = backRawQuery[1] ?? undefined;
} else if (
frontQuery != null &&
frontQuery?.query != null &&
frontQuery.query in dfcPairs
) {
// match to the card's DFC pair. assume the back is the same card type as the front.
backQuery = {
query: dfcPairs[frontQuery.query],
card_type: frontQuery.card_type,
};
} else if (frontQuery != null && frontQuery?.query != null) {
// typescript isn't smart enough to know that frontQuery.query is not null, so we have to do this
const frontQueryQuery = frontQuery.query;
let dfcPairMatchFront: string | null = null;
if (fuzzySearch) {
const matches = Object.keys(dfcPairs).filter((dfcPairFront) =>
dfcPairFront.startsWith(frontQueryQuery)
);
if (matches.length === 1) {
dfcPairMatchFront = matches[0];
}
} else if (frontQueryQuery in dfcPairs) {
dfcPairMatchFront = frontQueryQuery;
}
if (dfcPairMatchFront != null) {
// match to the card's DFC pair. assume the back is the same card type as the front.
backQuery = {
query: dfcPairs[dfcPairMatchFront],
card_type: frontQuery.card_type,
};
}
}

return [
Expand All @@ -172,7 +188,8 @@ export function processLine(line: string, dfcPairs: DFCPairs): ProcessedLine {

export function processLines(
lines: Array<string>,
dfcPairs: DFCPairs
dfcPairs: DFCPairs,
fuzzySearch: boolean
): Array<ProcessedLine> {
/**
* Process each line in `lines`, ignoring any lines which don't contain relevant information.
Expand All @@ -182,7 +199,11 @@ export function processLines(
[];
lines.forEach((line: string) => {
if (line != null && line.trim().length > 0) {
const [quantity, frontMember, backMember] = processLine(line, dfcPairs);
const [quantity, frontMember, backMember] = processLine(
line,
dfcPairs,
fuzzySearch
);
if (quantity > 0 && (frontMember != null || backMember != null)) {
queries.push([quantity, frontMember, backMember]);
}
Expand All @@ -193,9 +214,10 @@ export function processLines(

export function processStringAsMultipleLines(
lines: string,
dfcPairs: DFCPairs
dfcPairs: DFCPairs,
fuzzySearch: boolean
): Array<ProcessedLine> {
return processLines(lines.split(/\r?\n|\r|\n/g), dfcPairs);
return processLines(lines.split(/\r?\n|\r|\n/g), dfcPairs, fuzzySearch);
}

export function convertLinesIntoSlotProjectMembers(
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/features/import/importCSV.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from "@/common/processing";
import { useAppDispatch, useAppSelector } from "@/common/types";
import { addMembers, selectProjectSize } from "@/features/project/projectSlice";
import { selectFuzzySearch } from "@/features/searchSettings/searchSettingsSlice";
import { setError } from "@/features/toasts/toastsSlice";
import { RightPaddedIcon, TableWrapper } from "@/features/ui/styledComponents";

Expand Down Expand Up @@ -149,6 +150,8 @@ export function ImportCSV() {
const handleCloseCSVModal = () => setShowCSVModal(false);
const handleShowCSVModal = () => setShowCSVModal(true);

const fuzzySearch = useAppSelector(selectFuzzySearch);

const projectSize = useAppSelector(selectProjectSize);

const parseCSVFile = (fileContents: string | ArrayBuffer | null) => {
Expand Down Expand Up @@ -193,7 +196,8 @@ export function ImportCSV() {

const processedLines = processLines(
rows.map(formatCSVRowAsLine),
dfcPairsQuery.data ?? {}
dfcPairsQuery.data ?? {},
fuzzySearch
);
dispatch(
addMembers({
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/features/import/importText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,15 @@ import {
import { CardDocument, useAppDispatch, useAppSelector } from "@/common/types";
import { toTitleCase } from "@/common/utils";
import { addMembers, selectProjectSize } from "@/features/project/projectSlice";
import { selectFuzzySearch } from "@/features/searchSettings/searchSettingsSlice";
import { RightPaddedIcon } from "@/features/ui/styledComponents";

export function ImportText() {
const sampleCardsQuery = useGetSampleCardsQuery();
const dfcPairsQuery = useGetDFCPairsQuery();

const fuzzySearch = useAppSelector(selectFuzzySearch);

const dispatch = useAppDispatch();
const [showTextModal, setShowTextModal] = useState<boolean>(false);
const handleCloseTextModal = () => setShowTextModal(false);
Expand Down Expand Up @@ -82,7 +85,8 @@ export function ImportText() {

const processedLines = processStringAsMultipleLines(
textModalValue,
dfcPairsQuery.data ?? {}
dfcPairsQuery.data ?? {},
fuzzySearch
);
dispatch(
addMembers({
Expand Down
14 changes: 12 additions & 2 deletions frontend/src/features/import/importURL.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
import { useAppDispatch, useAppSelector } from "@/common/types";
import { useProjectName } from "@/features/backend/backendSlice";
import { addMembers, selectProjectSize } from "@/features/project/projectSlice";
import { selectFuzzySearch } from "@/features/searchSettings/searchSettingsSlice";
import { setError } from "@/features/toasts/toastsSlice";
import { Spinner } from "@/features/ui/spinner";
import { RightPaddedIcon } from "@/features/ui/styledComponents";
Expand All @@ -28,6 +29,7 @@ export function ImportURL() {
const dfcPairsQuery = useGetDFCPairsQuery();
const importSitesQuery = useGetImportSitesQuery();
const projectName = useProjectName();
const fuzzySearch = useAppSelector(selectFuzzySearch);

const projectSize = useAppSelector(selectProjectSize);
const dispatch = useAppDispatch();
Expand All @@ -49,7 +51,8 @@ export function ImportURL() {
const query = await triggerFn(URLModalValue);
const processedLines = processStringAsMultipleLines(
query.data ?? "",
dfcPairsQuery.data ?? {}
dfcPairsQuery.data ?? {},
fuzzySearch
);
dispatch(
addMembers({
Expand All @@ -74,7 +77,14 @@ export function ImportURL() {
setLoading(false);
}
}
}, [dispatch, URLModalValue, dfcPairsQuery.data, projectSize, triggerFn]);
}, [
dispatch,
URLModalValue,
dfcPairsQuery.data,
projectSize,
triggerFn,
fuzzySearch,
]);

return (
<>
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/features/searchSettings/searchSettingsSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export default searchSettingsSlice.reducer;
//# region selectors

export const selectSearchSettings = (state: RootState) => state.searchSettings;
export const selectFuzzySearch = (state: RootState) =>
state.searchSettings.searchTypeSettings.fuzzySearch;
export const selectSearchSettingsSourcesValid = (state: RootState) =>
state.searchSettings.sourceSettings.sources != null;

Expand Down

0 comments on commit 7b5164f

Please sign in to comment.