From 3bc5839928b26942d14ca656972031a87f95ee57 Mon Sep 17 00:00:00 2001 From: frostyfan109 Date: Wed, 31 Jul 2024 11:54:59 -0400 Subject: [PATCH] Optimizations --- src/components/search/context.js | 4 +- .../variable-results-new.tsx | 52 ++++++++++-------- .../variable-view-context.tsx | 53 ++++++++++--------- src/hooks/use-lunr-search.js | 14 +++-- 4 files changed, 73 insertions(+), 50 deletions(-) diff --git a/src/components/search/context.js b/src/components/search/context.js index c660df58..f2b2d5d9 100644 --- a/src/components/search/context.js +++ b/src/components/search/context.js @@ -419,8 +419,10 @@ export const HelxSearch = ({ children }) => { study.elements.forEach((variable, indexByVariable) => { const variableToUpdate = Object.assign({}, variable); + // NOTE: We don't want to store the actual study inside here, since the histogram + // will try to do a deep clone on it, which can become very performance heavy for large searches. + variableToUpdate["study_id"] = study.c_id variableToUpdate["study_name"] = study.c_name - variableToUpdate["study"] = study variableToUpdate["withinFilter"] = "none" variables.push(variableToUpdate) diff --git a/src/components/search/results/variable-view-layout/variable-results-new.tsx b/src/components/search/results/variable-view-layout/variable-results-new.tsx index 3eb18c5e..04e48197 100644 --- a/src/components/search/results/variable-view-layout/variable-results-new.tsx +++ b/src/components/search/results/variable-view-layout/variable-results-new.tsx @@ -70,6 +70,32 @@ const VariableViewHistogram = () => { /> ), [variableHistogramConfig]) + const sliderMarks = useMemo(() => { + return normalizedVariableResults.reduce((acc, cur) => { + acc![cur.score] = { + label: cur.score, + style: { + display: "none" + } + } + return acc + }, {}) + }, [normalizedVariableResults]) + + const slider = useMemo(() => ( + + ), [scoreFilter, setScoreFilter, sliderMarks]) + return ( {
{ histogram } - result.score)) } - max={ Math.max(...normalizedVariableResults.map((result) => result.score)) } - step={ null } - marks={ normalizedVariableResults.reduce((acc, cur) => ({ - ...acc, - [cur.score]: { - label: cur.score, - style: { - display: "none" - } - } - }), {}) } - // Margin to align with the histogram - style={{ marginRight: 0, marginBottom: 4, marginTop: 16, flexGrow: 1 }} - className="histogram-slider" - /> + { slider }
{ } const VariableListItem = ({ variable, showStudySource=true, showDataSource=true, style={}, ...props }: VariableListItemProps) => { - const { dataSources, highlightTokens } = useVariableView()! + const { dataSources, highlightTokens, getStudyById } = useVariableView()! const [showMore, setShowMore] = useState(false) @@ -535,9 +543,9 @@ const VariableListItem = ({ variable, showStudySource=true, showDataSource=true, { showStudySource && ( Source:  - +   - + ) }
diff --git a/src/components/search/results/variable-view-layout/variable-view-context.tsx b/src/components/search/results/variable-view-layout/variable-view-context.tsx index f3298b63..a9c7210f 100644 --- a/src/components/search/results/variable-view-layout/variable-view-context.tsx +++ b/src/components/search/results/variable-view-layout/variable-view-context.tsx @@ -51,8 +51,9 @@ export interface VariableResult { name: string description: string score: number - study: StudyResult e_link: string + study_id: string + study_name: string data_source: string // e.g. "HEAL Studies" vs "Non-HEAL Studies" } @@ -77,6 +78,8 @@ export interface IVariableViewContext { studiesSource: StudyResult[] filteredStudies: StudyResult[] absScoreRange: [number, number] + getVariableById: (id: string) => VariableResult | undefined + getStudyById: (id: string) => StudyResult | undefined // Filters scoreFilter: [number, number] | undefined @@ -126,16 +129,17 @@ export const VariableViewProvider = ({ children }: VariableViewProviderProps) => const [sortOrderOption, setSortOrderOption] = useState("descending") const [collapseIntoVariables, setCollapseIntoVariables] = useState(true) - const variableIdMap = useMemo>(() => { - const map = new Map() - variableResults.forEach((variable) => { - map.set(variable.id, variable) + const [variableIdMap, studyIdMap] = useMemo<[Map, Map]>(() => { + const variableMap = new Map() + const studyMap = new Map() + variableStudyResults.forEach((study) => { + studyMap.set(study.c_id, study) + study.elements.forEach((variable) => variableMap.set(variable.id, variable)) }) - return map - }, [variableResults]) + return [variableMap, studyMap] + }, [variableStudyResults]) const normalizedVariableResults = useMemo(() => { - console.log("rerender normalized") const values = variableResults.map(result => result.score); const min = Math.min(...values) const max = Math.max(...values) @@ -171,7 +175,7 @@ export const VariableViewProvider = ({ children }: VariableViewProviderProps) => // We want to maintain the ordering of the variables, so compute using the ordered variables source. const studiesSource = useMemo(() => { return variablesSource.reduce((acc, variable) => { - const { study } = variable + const study = studyIdMap.get(variable.study_id)! const existingStudy = acc.find((s) => s.c_id === study.c_id) if (existingStudy) existingStudy.elements.push(variable) else acc.push({ @@ -180,34 +184,33 @@ export const VariableViewProvider = ({ children }: VariableViewProviderProps) => }) return acc }, []) - }, [variablesSource]) + }, [variablesSource, studyIdMap]) const variableDocs = useMemo(() => variablesSource.map((variable) => ({ id: variable.id, name: variable.name, description: variable.description, - study_id: variable.study.c_id, - study_name: variable.study.c_name + study_id: variable.study_id, + study_name: variable.study_name })), [variablesSource]) const lunrConfig = useMemo(() => ({ docs: variableDocs, index: { ref: "id", - fields: ["id", "name", "description", "study_id", "study_name"] + fields: ["id", "name", "description"] } }), [variableDocs]) const { index, lexicalSearch } = useLunrSearch(lunrConfig) const [filteredVariables, highlightTokens] = useMemo<[VariableResult[], string[]]>(() => { - console.log("rerender filtered") const { hits, tokens } = lexicalSearch(subsearch) - const matchedVariables = hits.map(({ ref: id }) => variableIdMap.get(id)!) + const matchedVariables = hits.reduce((acc, { ref: id }) => (acc.add(id), acc), new Set()) const highlightTokens = subsearch.length > 3 ? tokens.map((token) => token.toString()) : [] - - const filtered = [...variablesSource].filter((variable) => { - if (subsearch.length > 3 && !matchedVariables.includes(variable)) return false + + const filtered = variablesSource.filter((variable) => { + if (subsearch.length > 3 && !matchedVariables.has(variable.id)) return false if (scoreFilter) { const [minScore, maxScore] = scoreFilter if (variable.score < minScore || variable.score > maxScore) return false @@ -216,10 +219,10 @@ export const VariableViewProvider = ({ children }: VariableViewProviderProps) => return true }) return [filtered, highlightTokens] - }, [variablesSource, variableIdMap, scoreFilter, subsearch, lexicalSearch, hiddenDataSources]) + }, [variablesSource, scoreFilter, subsearch, lexicalSearch, hiddenDataSources]) const filteredStudies = useMemo(() => { - return [...studiesSource].reduce((acc, study) => { + return studiesSource.reduce((acc, study) => { const newStudy = { ...study, elements: study.elements.filter((variable) => filteredVariables.includes(variable)) @@ -230,7 +233,6 @@ export const VariableViewProvider = ({ children }: VariableViewProviderProps) => }, [studiesSource, filteredVariables]) const absScoreRange = useMemo<[number, number]>(() => { - console.log("rerender abs range") if (normalizedVariableResults.length < 2) return [normalizedVariableResults[0]?.score, normalizedVariableResults[0]?.score] return [ Math.min(...normalizedVariableResults.map((result) => result.score)), @@ -250,7 +252,6 @@ export const VariableViewProvider = ({ children }: VariableViewProviderProps) => const variablesHistogram = useRef() const variableHistogramConfig = useMemo(() => { const [minScore, maxScore] = absScoreRange - console.log("RERENDER") return { ...(variableHistogramConfigStatic as any), data: [...filteredVariables].sort((a, b) => a.score - b.score), @@ -315,12 +316,12 @@ export const VariableViewProvider = ({ children }: VariableViewProviderProps) => if (acc.hasOwnProperty(dataSource)) { const { studies, variables } = acc[dataSource] if (!variables.includes(cur.id)) variables.push(cur.id) - if (!studies.includes(cur.study.c_id)) studies.push(cur.study.c_id) + if (!studies.includes(cur.study_id)) studies.push(cur.study_id) } else { acc[dataSource] = { name: dataSource, color: FIXED_DATA_SOURCES[dataSource] ?? seededPalette.getNextColor(), - studies: [cur.study.c_id], + studies: [cur.study_id], variables: [cur.id] } } @@ -363,6 +364,9 @@ export const VariableViewProvider = ({ children }: VariableViewProviderProps) => setSortOrderOption("descending") }, [normalizedVariableResults]) + const getStudyById = useCallback((id: string) => studyIdMap.get(id), [studyIdMap]) + const getVariableById = useCallback((id: string) => variableIdMap.get(id), [variableIdMap]) + useEffect(() => { resetFilters() }, [normalizedVariableResults, resetFilters]) @@ -402,6 +406,7 @@ export const VariableViewProvider = ({ children }: VariableViewProviderProps) => studiesSource, filteredVariables, filteredStudies, + getStudyById, getVariableById, /** * Filters */ diff --git a/src/hooks/use-lunr-search.js b/src/hooks/use-lunr-search.js index 2fcbcc40..59966529 100644 --- a/src/hooks/use-lunr-search.js +++ b/src/hooks/use-lunr-search.js @@ -5,7 +5,7 @@ * */ -import { useCallback, useEffect } from 'react' +import { useCallback, useEffect, useMemo } from 'react' import { useLunr } from './' export const useLunrSearch = ({ @@ -39,13 +39,21 @@ export const useLunrSearch = ({ initIndex, populateIndex ) + + const docMap = useMemo(() => { + const map = new Map() + docs.forEach((doc) => map.set(doc[ref], doc)) + return map + }, [docs, ref]) + + const getDocByRef = useCallback((ref) => docMap.get(ref), [docMap]) const lexicalSearch = useCallback((...args) => { const result = lunrLexicalSearch(...args) const searchTokens = [] result.forEach(({ ref: id, score, matchData: { metadata } }) => { // `ref` is always a string, so the doc ref needs to be converted to a string. - const doc = docs.find((doc) => doc[ref].toString() === id) + const doc = getDocByRef(id) Object.entries(metadata).forEach(([partialTerm, hitFields]) => { Object.entries(hitFields).forEach(([ field, meta ]) => { const { position: [[start, length]] } = meta @@ -56,7 +64,7 @@ export const useLunrSearch = ({ }) }) return { hits: result, tokens: searchTokens } - }, [docs, lunrLexicalSearch]) + }, [docs, getDocByRef, lunrLexicalSearch]) return { index, lexicalSearch