From e291bbbdfe5d3caba78689d65c39be4eaf2c28f1 Mon Sep 17 00:00:00 2001 From: Graham McNeill Date: Thu, 5 Dec 2024 09:59:44 +0000 Subject: [PATCH] [Platform] Update metadata on study page and credible set page (#589) --- apps/platform/src/constants.js | 2 +- .../src/pages/CredibleSetPage/Profile.tsx | 2 +- .../pages/CredibleSetPage/ProfileHeader.gql | 2 + .../pages/CredibleSetPage/ProfileHeader.tsx | 108 ++------ apps/platform/src/pages/StudyPage/Header.tsx | 2 +- apps/platform/src/pages/StudyPage/Profile.tsx | 2 +- .../pages/StudyPage/StudyProfileHeader.gql | 28 +- .../pages/StudyPage/StudyProfileHeader.tsx | 242 ++++++++++-------- .../src/study/SharedTraitStudies/Body.tsx | 53 +--- .../ui/src/components/DisplaySampleSize.tsx | 32 +++ .../ui/src/components/SummaryStatsTable.tsx | 56 ++++ packages/ui/src/index.tsx | 2 + 12 files changed, 296 insertions(+), 235 deletions(-) create mode 100644 packages/ui/src/components/DisplaySampleSize.tsx create mode 100644 packages/ui/src/components/SummaryStatsTable.tsx diff --git a/apps/platform/src/constants.js b/apps/platform/src/constants.js index 0d25a56cf..22a27127f 100644 --- a/apps/platform/src/constants.js +++ b/apps/platform/src/constants.js @@ -341,7 +341,7 @@ export const variantConsequenceSource = { }, }; -export const poulationMap = { +export const populationMap = { fin: "Finish", afr: "African", nfe: "non-Finnish Europeans", diff --git a/apps/platform/src/pages/CredibleSetPage/Profile.tsx b/apps/platform/src/pages/CredibleSetPage/Profile.tsx index 41ea78c51..41d35c46d 100644 --- a/apps/platform/src/pages/CredibleSetPage/Profile.tsx +++ b/apps/platform/src/pages/CredibleSetPage/Profile.tsx @@ -53,7 +53,7 @@ function Profile({ studyLocusId, variantId, referenceAllele, alternateAllele }: variables={{ studyLocusId: studyLocusId, variantIds: [variantId] }} client={client} > - + diff --git a/apps/platform/src/pages/CredibleSetPage/ProfileHeader.gql b/apps/platform/src/pages/CredibleSetPage/ProfileHeader.gql index 15d1846f6..c5af1b08f 100644 --- a/apps/platform/src/pages/CredibleSetPage/ProfileHeader.gql +++ b/apps/platform/src/pages/CredibleSetPage/ProfileHeader.gql @@ -49,6 +49,8 @@ fragment CredibleSetProfileHeaderFragment on credibleSet { publicationJournal pubmedId nSamples + cohorts + initialSampleSize studyType hasSumstats analysisFlags diff --git a/apps/platform/src/pages/CredibleSetPage/ProfileHeader.tsx b/apps/platform/src/pages/CredibleSetPage/ProfileHeader.tsx index a0132e87b..ad7c568ee 100644 --- a/apps/platform/src/pages/CredibleSetPage/ProfileHeader.tsx +++ b/apps/platform/src/pages/CredibleSetPage/ProfileHeader.tsx @@ -10,71 +10,15 @@ import { ClinvarStars, LabelChip, DetailPopover, + SummaryStatsTable, + DisplaySampleSize, } from "ui"; import { Box, Typography } from "@mui/material"; import CREDIBLE_SET_PROFILE_HEADER_FRAGMENT from "./ProfileHeader.gql"; -import { getStudyCategory } from "sections/src/utils/getStudyCategory"; import { epmcUrl } from "../../utils/urls"; -import { credsetConfidenceMap, poulationMap } from "../../constants"; -import { v1 } from "uuid"; +import { credsetConfidenceMap, populationMap } from "../../constants"; -type ProfileHeaderProps = { - variantId: string; -}; - -const dicSummary = [ - { id: "n_variants", label: "Total variants", tooltip: "Number of harmonised variants" }, - { id: "n_variants_sig", label: "Significant variants", tooltip: "P-value significant variants" }, - { id: "mean_beta", label: "Mean beta", tooltip: "Mean effect size across all variants" }, - { - id: "gc_lambda", - label: "GC lambda", - tooltip: "Additive Genomic Control (GC) lambda indicating GWAS inflation", - }, - { - id: "mean_diff_pz", - label: "Mean diff P-Z", - tooltip: "Mean difference between reported and calculated log p-values", - }, - { - id: "se_diff_pz", - label: "SD diff P-Z", - tooltip: "Standard deviation of the difference between reported and calculated log p-values", - }, -]; - -function SummaryStatsTable({ sumstatQCValues }: any) { - return ( - <> - - Harmonised summary statistics - - - - {dicSummary.map((sumstat: any) => { - const summStatValue = sumstatQCValues.find( - (v: any) => v.QCCheckName === sumstat.id - ).QCCheckValue; - return ( - - - - {summStatValue} - - - ); - })} - -
- - {sumstat.label} - -
- - ); -} - -function ProfileHeader({ variantId }: ProfileHeaderProps) { +function ProfileHeader() { const { loading, error, data } = usePlatformApi(); // TODO: Errors! @@ -82,7 +26,6 @@ function ProfileHeader({ variantId }: ProfileHeaderProps) { const credibleSet = data?.credibleSet; const study = credibleSet?.study; - const studyCategory = study ? getStudyCategory(study.projectId) : null; const target = study?.target; const leadVariant = credibleSet?.locus.rows[0]; const beta = leadVariant?.beta ?? credibleSet?.beta; @@ -249,7 +192,7 @@ function ProfileHeader({ variantId }: ProfileHeaderProps) { {study?.studyType.replace(/(qtl|gwas)/gi, match => match.toUpperCase())} Study - {studyCategory !== "QTL" && ( + {study?.studyType === "gwas" && ( <> {study?.traitFromSource} @@ -276,7 +219,7 @@ function ProfileHeader({ variantId }: ProfileHeaderProps) { )} )} - {studyCategory === "QTL" && ( + {study?.studyType !== "gwas" && ( <> {target?.id && ( @@ -308,18 +251,9 @@ function ProfileHeader({ variantId }: ProfileHeaderProps) { )} - {study?.analysisFlags && ( - - Analysis - - } - > - {study?.analysisFlags ? study.analysisFlags : "Not Available"} - - )} + + {study?.analysisFlags?.join(", ")} + {!study?.hasSumstats ? "Not Available" @@ -330,21 +264,27 @@ function ProfileHeader({ variantId }: ProfileHeaderProps) { : "Available" } - - {study?.nSamples.toLocaleString()} - - - {/* LD Ancestries */} - {study?.ldPopulationStructure?.length > 0 && - study.ldPopulationStructure.map(({ ldPopulation, relativeSampleSize }, index) => ( + {study?.nSamples && + + + + } + {study?.ldPopulationStructure?.length > 0 && + + {study.ldPopulationStructure.map(({ ldPopulation, relativeSampleSize }) => ( ))} - + + } ); diff --git a/apps/platform/src/pages/StudyPage/Header.tsx b/apps/platform/src/pages/StudyPage/Header.tsx index 2818075a5..35150ea40 100644 --- a/apps/platform/src/pages/StudyPage/Header.tsx +++ b/apps/platform/src/pages/StudyPage/Header.tsx @@ -29,7 +29,7 @@ function Header({ if (diseases?.length) { traitLinks = ( d.id)} names={diseases.map(d => d.name)} diff --git a/apps/platform/src/pages/StudyPage/Profile.tsx b/apps/platform/src/pages/StudyPage/Profile.tsx index 1e135061f..bb4eddcb3 100644 --- a/apps/platform/src/pages/StudyPage/Profile.tsx +++ b/apps/platform/src/pages/StudyPage/Profile.tsx @@ -68,7 +68,7 @@ function Profile({ studyId, studyType, projectId, diseases }: ProfileProps) { }} client={client} > - + {studyType === "gwas" && ( diff --git a/apps/platform/src/pages/StudyPage/StudyProfileHeader.gql b/apps/platform/src/pages/StudyPage/StudyProfileHeader.gql index 5f83a1d3d..425202661 100644 --- a/apps/platform/src/pages/StudyPage/StudyProfileHeader.gql +++ b/apps/platform/src/pages/StudyPage/StudyProfileHeader.gql @@ -1,15 +1,24 @@ fragment StudyProfileHeaderFragment on Gwas { + studyType publicationFirstAuthor publicationDate publicationJournal pubmedId traitFromSource + backgroundTraits { + id + name + } + diseases { + id + name + } + target { + id + approvedSymbol + } nSamples initialSampleSize - replicationSamples { - sampleSize - ancestry - } nCases nControls cohorts @@ -17,10 +26,15 @@ fragment StudyProfileHeaderFragment on Gwas { ldPopulation relativeSampleSize } + hasSumstats + sumstatQCValues { + QCCheckName + QCCheckValue + } qualityControls analysisFlags - discoverySamples { - sampleSize - ancestry + biosample { + biosampleId + biosampleName } } diff --git a/apps/platform/src/pages/StudyPage/StudyProfileHeader.tsx b/apps/platform/src/pages/StudyPage/StudyProfileHeader.tsx index 0f58c0243..877947830 100644 --- a/apps/platform/src/pages/StudyPage/StudyProfileHeader.tsx +++ b/apps/platform/src/pages/StudyPage/StudyProfileHeader.tsx @@ -1,136 +1,178 @@ -import { usePlatformApi, Link, Field, ProfileHeader as BaseProfileHeader, Tooltip } from "ui"; -import { Typography, Box } from "@mui/material"; +import { Fragment } from "react"; +import { + usePlatformApi, + Link, + Field, + ProfileHeader as BaseProfileHeader, + DetailPopover, + SummaryStatsTable, + LabelChip, + DisplaySampleSize, +} from "ui"; +import { Box } from "@mui/material"; +import { populationMap } from "../../constants"; import STUDY_PROFILE_HEADER_FRAGMENT from "./StudyProfileHeader.gql"; -type samplesType = { - ancestry: string; - sampleSize: number; -}[]; - -function formatSamples(samples: samplesType) { - return samples.map(({ ancestry, sampleSize }) => `${ancestry}: ${sampleSize}`).join(", "); -} - -type ProfileHeaderProps = { - studyCategory: string; -}; - -function ProfileHeader({ studyCategory }: ProfileHeaderProps) { +function ProfileHeader() { const { loading, error, data } = usePlatformApi(); - // TODO: Errors! + // TODO: Errors! if (error) return null; const { + studyType, publicationFirstAuthor, publicationDate, publicationJournal, pubmedId, + hasSumstats, + sumstatQCValues, nSamples, initialSampleSize, - replicationSamples, traitFromSource, + backgroundTraits, + diseases, + target, nCases, nControls, cohorts, ldPopulationStructure, qualityControls, analysisFlags, - discoverySamples, + biosample, } = data?.study || {}; return ( - <> - - {publicationFirstAuthor} - - - {publicationDate} - - - {publicationJournal} - - - - {pubmedId} - - - - {traitFromSource} - - - {nSamples} + + + + {studyType?.replace(/(qtl|gwas)/gi, match => match.toUpperCase())} - - {studyCategory === "GWAS" ? ( - initialSampleSize - ) : studyCategory === "FINNGEN" ? ( - discoverySamples?.length ? ( - initialSampleSize ? ( - - Initial sample size: {initialSampleSize} - - } - showHelpIcon + {studyType === "gwas" && ( + <> + + {traitFromSource} + + {diseases?.length > 0 && ( + + {diseases.map(({ id, name }, index) => ( + + {index > 0 ? ", " : null} + {name} + + ))} + + )} + {backgroundTraits?.length > 0 && ( + + {backgroundTraits.map(({ id, name }, index) => ( + + {index > 0 ? ", " : null} + {name} + + ))} + + )} + + )} + {studyType !== "gwas" && ( + <> + {target?.id && ( + + {target.approvedSymbol} + + )} + {biosample?.biosampleId && ( + + - {formatSamples(discoverySamples)} - - ) : ( - formatSamples(discoverySamples) - ) - ) : null - ) : null} - - - {studyCategory === "GWAS" && - replicationSamples?.length && - formatSamples(replicationSamples)} + {biosample.biosampleName} + + + )} + + )} + {publicationFirstAuthor && ( + + {publicationFirstAuthor} et al. {publicationJournal} ( + {publicationDate?.slice(0, 4)}) + + )} + {pubmedId && + + + {pubmedId} + + + } + + + + + {!hasSumstats + ? "Not Available" + : sumstatQCValues + ? + + + : "Available" + } + {qualityControls?.length > 0 && + + +
    + {qualityControls.map(warning => ( +
  • {warning}
  • + ))} +
+
+
+ } + {nSamples && + + + + } - {nCases} + {/* do not show anything when value 0 */} + {nCases ? nCases?.toLocaleString() : null} - {nControls} + {/* do not show anything when value 0 */} + {nCases ? nControls?.toLocaleString() : null} - - {((studyCategory === "GWAS" && cohorts?.length) || studyCategory === "FINNGEN") && - (ldPopulationStructure?.length ? ( - - - LD populations and relative sample sizes - - {ldPopulationStructure.map(({ ldPopulation, relativeSampleSize }) => ( - - - {ldPopulation}: {relativeSampleSize} - - - ))} - - } - showHelpIcon - > - {studyCategory === "GWAS" ? cohorts.join(", ") : "FinnGen"} - - ) : studyCategory === "GWAS" ? ( - cohorts.join(", ") - ) : ( - "FinnGen" - ))} + + {analysisFlags?.join(", ")} - - {studyCategory === "GWAS" && qualityControls?.length && qualityControls.join(", ")} - - - {studyCategory === "GWAS" && analysisFlags?.length && analysisFlags.join(", ")} - - + {ldPopulationStructure?.length > 0 && + + {ldPopulationStructure.map(({ ldPopulation, relativeSampleSize }) => ( + + ))} + + } +
+
); } diff --git a/packages/sections/src/study/SharedTraitStudies/Body.tsx b/packages/sections/src/study/SharedTraitStudies/Body.tsx index 14796e68c..68ef3d35f 100644 --- a/packages/sections/src/study/SharedTraitStudies/Body.tsx +++ b/packages/sections/src/study/SharedTraitStudies/Body.tsx @@ -43,34 +43,6 @@ function getColumns(diseaseIds: string[]) { id: "traitFromSource", label: "Reported trait", }, - { - id: "author", - label: "First author", - renderCell: ({ projectId, publicationFirstAuthor }) => - getStudyCategory(projectId) === "FINNGEN" ? "FinnGen" : publicationFirstAuthor || naLabel, - exportValue: ({ projectId, publicationFirstAuthor }) => - getStudyCategory(projectId) === "FINNGEN" ? "FinnGen" : publicationFirstAuthor, - }, - { - id: "publicationDate", - label: "Year", - renderCell: ({ projectId, publicationDate }) => - getStudyCategory(projectId) === "FINNGEN" - ? "2023" - : publicationDate - ? publicationDate.slice(0, 4) - : naLabel, - exportValue: ({ projectId, publicationYear }) => - getStudyCategory(projectId) === "FINNGEN" ? "2023" : publicationYear, - }, - { - id: "publicationJournal", - label: "Journal", - renderCell: ({ projectId, publicationJournal }) => - getStudyCategory(projectId) === "FINNGEN" ? naLabel : publicationJournal || naLabel, - exportValue: ({ projectId, publicationJournal }) => - getStudyCategory(projectId) === "FINNGEN" ? naLabel : publicationJournal, - }, { id: "nSamples", label: "Sample size", @@ -113,20 +85,21 @@ function getColumns(diseaseIds: string[]) { getStudyCategory(projectId) === "FINNGEN" ? "FinnGen" : cohorts?.length - ? cohorts.join(", ") - : null, + ? cohorts.join(", ") + : null, }, { - id: "pubmedId", - label: "PubMed ID", - renderCell: ({ projectId, pubmedId }) => - getStudyCategory(projectId) === "GWAS" && pubmedId ? ( - - ) : ( - naLabel - ), - exportValue: ({ projectId, pubmedId }) => - getStudyCategory(projectId) === "GWAS" && pubmedId ? pubmedId : null, + id: "publication", + label: "Publication", + renderCell: ({ publicationFirstAuthor, publicationDate, pubmedId }) => { + if (!publicationFirstAuthor) return naLabel; + return + }, + filterValue: ({ publicationYear, publicationFirstAuthor }) => + `${publicationYear} ${publicationFirstAuthor}`, }, ]; } diff --git a/packages/ui/src/components/DisplaySampleSize.tsx b/packages/ui/src/components/DisplaySampleSize.tsx new file mode 100644 index 000000000..c991a75c4 --- /dev/null +++ b/packages/ui/src/components/DisplaySampleSize.tsx @@ -0,0 +1,32 @@ +import { Typography } from "@mui/material"; +import { Tooltip } from "ui"; + +type DisplaySampleSizeProps = { + nSamples: number; + cohorts?: string[]; + initialSampleSize?: string; +}; + +export default function DisplaySampleSize({ + nSamples, + cohorts, + initialSampleSize +}: DisplaySampleSizeProps) { + + const display = <> + {nSamples?.toLocaleString()} + {cohorts ? ` (cohorts: ${cohorts.join(", ")})` : ""} + ; + + if (initialSampleSize) { + const title = <> + Initial sample size + {initialSampleSize} + ; + return + {display} + ; + } + + return display; +} \ No newline at end of file diff --git a/packages/ui/src/components/SummaryStatsTable.tsx b/packages/ui/src/components/SummaryStatsTable.tsx new file mode 100644 index 000000000..a548d3ea3 --- /dev/null +++ b/packages/ui/src/components/SummaryStatsTable.tsx @@ -0,0 +1,56 @@ + +import { Typography } from "@mui/material"; +import { v1 } from "uuid"; +import { Tooltip } from "ui"; + +const dicSummary = [ + { id: "n_variants", label: "Total variants", tooltip: "Number of harmonised variants" }, + { id: "n_variants_sig", label: "Significant variants", tooltip: "P-value significant variants" }, + { id: "mean_beta", label: "Mean beta", tooltip: "Mean effect size across all variants" }, + { + id: "gc_lambda", + label: "GC lambda", + tooltip: "Additive Genomic Control (GC) lambda indicating GWAS inflation", + }, + { + id: "mean_diff_pz", + label: "Mean diff P-Z", + tooltip: "Mean difference between reported and calculated log p-values", + }, + { + id: "se_diff_pz", + label: "SD diff P-Z", + tooltip: "Standard deviation of the difference between reported and calculated log p-values", + }, +]; + +export default function SummaryStatsTable({ sumstatQCValues }: any) { + return ( + <> + + Harmonised summary statistics + + + + {dicSummary.map((sumstat: any) => { + const summStatValue = sumstatQCValues.find( + (v: any) => v.QCCheckName === sumstat.id + ).QCCheckValue; + return ( + + + + {summStatValue} + + + ); + })} + +
+ + {sumstat.label} + +
+ + ); +} \ No newline at end of file diff --git a/packages/ui/src/index.tsx b/packages/ui/src/index.tsx index c0e9e419b..b7998bbfc 100644 --- a/packages/ui/src/index.tsx +++ b/packages/ui/src/index.tsx @@ -16,6 +16,7 @@ export { default as TooltipStyledLabel } from "./components/TooltipStyledLabel"; export { default as DirectionOfEffectIcon } from "./components/DirectionOfEffectIcon"; export { default as DirectionOfEffectTooltip } from "./components/DirectionOfEffectTooltip"; export { default as DisplayVariantId } from "./components/DisplayVariantId"; +export { default as DisplaySampleSize } from "./components/DisplaySampleSize"; export { default as LabelChip } from "./components/LabelChip"; export { default as BasePage } from "./components/BasePage"; export { default as NewChip } from "./components/NewChip"; @@ -28,6 +29,7 @@ export { default as EllsWrapper } from "./components/EllsWrapper"; export { default as ErrorBoundary } from "./components/ErrorBoundary"; export { default as GlobalSearch } from "./components/GlobalSearch/GlobalSearch"; export { default as DetailPopover } from "./components/DetailPopover"; +export { default as SummaryStatsTable } from "./components/SummaryStatsTable"; export { default as PrivateWrapper } from "./components/PrivateWrapper"; export { default as NavBar } from "./components/NavBar";