Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: Adding study linkages to CDE tab in concept card #308

Merged
merged 12 commits into from
Aug 26, 2024
49 changes: 48 additions & 1 deletion src/components/search/concept-modal/concept-modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export const ConceptModalBody = ({ result }) => {
const [studies, setStudies] = useState([])
const [cdes, setCdes] = useState(null)
const [cdeRelatedConcepts, setCdeRelatedConcepts] = useState(null)
const [cdeRelatedStudies, setCdeRelatedStudies] = useState(null)
const [cdesLoading, setCdesLoading] = useState(true)

/** Abort controllers */
Expand All @@ -100,7 +101,7 @@ export const ConceptModalBody = ({ result }) => {
const tabs = {
'overview': { title: 'Overview', icon: <OverviewIcon />, content: <OverviewTab result={ result } />, },
'studies': { title: studyTitle, icon: <StudiesIcon />, content: <StudiesTab studies={ studies } />, },
'cdes': { title: cdeTitle, icon: <CdesIcon />, content: <CdesTab cdes={ cdes } cdeRelatedConcepts={ cdeRelatedConcepts } loading={ cdesLoading } /> },
'cdes': { title: cdeTitle, icon: <CdesIcon />, content: <CdesTab cdes={ cdes } cdeRelatedConcepts={ cdeRelatedConcepts } cdeRelatedStudies={cdeRelatedStudies} loading={ cdesLoading } /> },
'kgs': { title: 'Knowledge Graphs', icon: <KnowledgeGraphsIcon />, content: <KnowledgeGraphsTab graphs={ graphs } />, },
'explanation': { title: 'Explanation', icon: <ExplanationIcon />, content: <ExplanationTab result={ result } /> },
'tranql': { title: 'TranQL', icon: <TranQLIcon />, content: <TranQLTab result={ result } graphs = { graphs } /> }
Expand Down Expand Up @@ -207,7 +208,41 @@ export const ConceptModalBody = ({ result }) => {
}
)
}

const loadRelatedStudies = async (cdeId) => {
const formatCdeQuery = () => {
return `\
hina-shah marked this conversation as resolved.
Show resolved Hide resolved
SELECT publication->study
FROM "/schema"
WHERE publication="${cdeId}"`
}
const tranqlUrl = context.tranql_url
const controller = new AbortController()
fetchCdesTranqlController.current.push(controller)
const res = await fetch(
`${tranqlUrl}tranql/query`,
{
headers: { 'Content-Type': 'text/plain' },
method: 'POST',
body: formatCdeQuery(),
signal: controller.signal
}
)
const message = await res.json()
const studies = []
const nodes = message.message.knowledge_graph.nodes
for (const [key, value] of Object.entries(nodes)) {
const name = value.name;
const urlAttribute = value.attributes.find(attr => attr.name === 'url');
urlAttribute && studies.push({c_id: key,
c_name: name,
c_link: urlAttribute.value});
}
return studies
}

const relatedConcepts = {}
const relatedStudies = {}
if (cdeData) {
const cdeIds = cdeData.elements.map((cde) => cde.id)
await Promise.all(cdeIds.map(async (cdeId, i) => {
Expand All @@ -221,10 +256,21 @@ export const ConceptModalBody = ({ result }) => {
relatedConcepts[cdeId] = null
}
}))
await Promise.all(cdeIds.map(async (cdeId, i) => {
try {
relatedStudies[cdeId] = await loadRelatedStudies(cdeId)
} catch (e) {
// Here, we explicitly want to halt execution and forward this error to the outer handler
// if a related concept request was aborted, because we now have stale data and don't want to
// update state with it.
if (e.name === "CanceledError" || e.name === "AbortError") throw e
}
}))
}
setCdes(cdeData)
/** Note that relatedConcepts are *TranQL* concepts/nodes, not DUG concepts. Both have the top level fields `id` and `name`. */
setCdeRelatedConcepts(relatedConcepts)
setCdeRelatedStudies(relatedStudies)
setCdesLoading(false)
} catch (e) {
// Check both because this function uses both Fetch API & Axios
Expand All @@ -248,6 +294,7 @@ export const ConceptModalBody = ({ result }) => {
setStudies([])
setCdes(null)
setCdeRelatedConcepts(null)
setCdeRelatedStudies(null)
setGraphs([])

getVars()
Expand Down
65 changes: 64 additions & 1 deletion src/components/search/concept-modal/tabs/cdes/cde-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ import { List, Collapse, Typography, Space, Button } from 'antd'
import { ExportOutlined } from '@ant-design/icons'
import _Highlighter from 'react-highlight-words'
import { RelatedConceptsList } from './related-concepts'
import Highlighter from 'react-highlight-words'
hina-shah marked this conversation as resolved.
Show resolved Hide resolved
import { SideCollapse } from '../../../..'
import { useAnalytics } from '../../../../../contexts'

const { Text, Link } = Typography
const { Panel } = Collapse

/** Only display these many related studies initially. */
const SHOW_MORE_CUTOFF = 3

const Section = ({ title, children }) => (
<Space size="small" direction="vertical" className="cde-section">
<div style={{ display: "flex", alignItems: "center" }}>
Expand All @@ -19,7 +23,57 @@ const Section = ({ title, children }) => (
</Space>
)

export const CdeItem = ({ cde, cdeRelatedConcepts, highlight }) => {
const RelatedStudiesList = ({relatedStudySource}) => {
hina-shah marked this conversation as resolved.
Show resolved Hide resolved
const { analyticsEvents } = useAnalytics()
// const studyLinkClicked = () => {
// analyticsEvents.studyLinkClicked(study.c_id)
// }
const [showMore, setShowMore] = useState(false)

if (!relatedStudySource) return (
<Text style={{ fontSize: 13, color: "rgba(0, 0, 0, 0.45)" }}>We couldn&apos;t load any related studies.</Text>
)

// Sort the related studies.
const relatedStudies = relatedStudySource.sort((a, b) => { return b.c_id < a.c_id});
hina-shah marked this conversation as resolved.
Show resolved Hide resolved

if (relatedStudies.length === 0) return (
<Text style={{ fontSize: 13, color: "rgba(0, 0, 0, 0.45)" }}>We don&apos;t know of any related studies.</Text>
)

return (
<div>
<List
size="small"
dataSource={showMore ? relatedStudies : relatedStudies.slice(0, SHOW_MORE_CUTOFF)}
renderItem={(study) => (
<List.Item key={study.c_id}>
<List.Item.Meta
size="small"
description={
<>
<font size="-1"><a href={study.c_link} target="_blank" rel="noopener noreferrer">{study.c_id}</a></font>: {study.c_name}
hina-shah marked this conversation as resolved.
Show resolved Hide resolved
</>
}
/>
</List.Item>
)}
/>
{ relatedStudies.length >= SHOW_MORE_CUTOFF && (
hina-shah marked this conversation as resolved.
Show resolved Hide resolved
<Button
type="link"
size="small"
style={{ padding: 0 }}
onClick={ () => setShowMore(!showMore) }
>
{ showMore ? "Show less" : "Show more" }
</Button>
) }
</div>
)
}

export const CdeItem = ({ cde, cdeRelatedConcepts, cdeRelatedStudies, highlight }) => {
const [collapsed, setCollapsed] = useState(false)

const { analyticsEvents } = useAnalytics()
Expand All @@ -28,6 +82,10 @@ export const CdeItem = ({ cde, cdeRelatedConcepts, highlight }) => {
cdeRelatedConcepts[cde.id]
), [cdeRelatedConcepts, cde])

const relatedStudySource = useMemo(() => (
cdeRelatedStudies[cde.id]
), [cdeRelatedStudies, cde])

const Highlighter = useCallback(({ ...props }) => (
<_Highlighter autoEscape={ true } searchWords={highlight} {...props}/>
), [highlight])
Expand Down Expand Up @@ -86,6 +144,11 @@ export const CdeItem = ({ cde, cdeRelatedConcepts, highlight }) => {
} }
/>
</Section>
<Section title={"Studies using this measure (" + (relatedStudySource ? relatedStudySource.length : 0) + ")"}>
<RelatedStudiesList
relatedStudySource={relatedStudySource}
/>
</Section>
{false && <Section>
{/* Bottom button container */}
<Button type="primary" size="small" style={{ marginTop: "8px" }}>Go</Button>
Expand Down
4 changes: 2 additions & 2 deletions src/components/search/concept-modal/tabs/cdes/cde-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CdeItem } from './cde-item'

const { Text } = Typography

export const CdeList = ({ cdes, cdeRelatedConcepts, highlight, loading, failed }) => {
export const CdeList = ({ cdes, cdeRelatedConcepts, cdeRelatedStudies, highlight, loading, failed }) => {
if (failed) return (
<Empty
image={ Empty.PRESENTED_IMAGE_SIMPLE }
Expand All @@ -19,7 +19,7 @@ export const CdeList = ({ cdes, cdeRelatedConcepts, highlight, loading, failed }
}}
dataSource={cdes}
renderItem={(cde) => (
<CdeItem cde={ cde } cdeRelatedConcepts={ cdeRelatedConcepts } highlight={highlight} />
<CdeItem cde={ cde } cdeRelatedConcepts={ cdeRelatedConcepts } cdeRelatedStudies={cdeRelatedStudies} highlight={highlight} />
)}
/>
)
Expand Down
4 changes: 2 additions & 2 deletions src/components/search/concept-modal/tabs/cdes/cdes.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const { Text, Title } = Typography
const { CheckableTag: CheckableFacet } = Tag
const { Panel } = Collapse

export const CdesTab = ({ cdes, cdeRelatedConcepts, loading }) => {
export const CdesTab = ({ cdes, cdeRelatedConcepts, cdeRelatedStudies, loading }) => {
const [search, setSearch] = useState("")
const { context } = useEnvironment()

Expand Down Expand Up @@ -57,7 +57,7 @@ export const CdesTab = ({ cdes, cdeRelatedConcepts, loading }) => {
{ !failed && !loading && <DebouncedInput setValue={setSearch}/> }
</div>
{/* { search && <Text style={{ display: "block", marginTop: "8px", marginBottom: "-16px", fontSize: 15, color: "rgba(0, 0, 0, 0.45)" }}>Search results for <i>{search.trim()}</i>:</Text> } */}
<CdeList cdes={cdeSource} cdeRelatedConcepts={cdeRelatedConcepts} highlight={highlightTokens} loading={loading} failed={failed}/>
<CdeList cdes={cdeSource} cdeRelatedConcepts={cdeRelatedConcepts} cdeRelatedStudies={cdeRelatedStudies} highlight={highlightTokens} loading={loading} failed={failed}/>
</Space>
)
}