Skip to content

Commit

Permalink
Updated family view (#771)
Browse files Browse the repository at this point in the history
* Started family view

* completed family view + pedigree function

* Finished family page

* Started refactor

* Created separate loading component

* Got basic GraphQL working in frontend

* Getting GraphQL types working

* Lots of cleanup + progress towards GraphQL types

* Merged dev into family view branch and resolved

* Adding graphql queries

* Swapped sample API to GraphQL

* Added compile to build

* Added eslintignore

* Swapped family view to GraphQL

* Worked throuhg refactor doc and implemented suggestions

* Fixed search nav links

* Separate TangledTree into its own PR

* Fix linting errors

* Implemented suggestions

* Neatened temp pedigree component

* Resync package-lock after dev merge

* Added TangledTree component

* Made suggested changes

* Allow svgs in vite

* fixed changed path

* Added readme to pedigree folder

* Formatting + colours

* Improve graph borders + start family view

* Temp add example of view

* Add deceased to PersonNode

* Progressive updates

* Small small changes

* Hide + show more web fields by default

* A bunch of refactoring + including individual view

* Linting

* Simplify simplify

* Fix quotes

* Fix test

* Minor stability improvements

* Add participantID to more places

* PR clean-up

* My review feedback

* Apply review feedback + add analysis view

* Linting

---------

Co-authored-by: Daniel <iamdanielreti@gmail.com>
Co-authored-by: Michael Franklin <illusional@users.noreply.github.com>
  • Loading branch information
3 people authored Aug 26, 2024
1 parent 0bae32f commit e7c6d49
Show file tree
Hide file tree
Showing 30 changed files with 2,422 additions and 296 deletions.
12 changes: 12 additions & 0 deletions api/graphql/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -1393,6 +1393,18 @@ async def analysis_runner(
)
return GraphQLAnalysisRunner.from_internal(analysis_runners[0])

@strawberry.field
async def analyses(
self,
info: Info[GraphQLContext, 'Query'],
id: GraphQLFilter[int],
) -> list[GraphQLAnalysis]:
connection = info.context['connection']
analyses = await AnalysisLayer(connection).query(
AnalysisFilter(id=id.to_internal_filter())
)
return [GraphQLAnalysis.from_internal(a) for a in analyses]


schema = strawberry.Schema(
query=Query, mutation=None, extensions=[QueryDepthLimiter(max_depth=10)]
Expand Down
20 changes: 15 additions & 5 deletions models/models/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ def get_entity_keys(
"""
Read through nested participants and full out the keys for the grid response
"""
has_sequencing_groups = False
has_assays = False

hidden_participant_meta_keys: set[str] = set()
hidden_sample_meta_keys = {'reads', 'vcfs', 'gvcf'}
hidden_assay_meta_keys = {
Expand Down Expand Up @@ -220,12 +223,14 @@ def update_d_from_meta(d: dict[str, bool], meta: dict[str, Any]):

if not s.sequencing_groups:
continue
has_sequencing_groups = True
for sg in s.sequencing_groups or []:
if sg.meta:
update_d_from_meta(sg_meta_keys, sg.meta)

if not sg.assays:
continue
has_assays = True
for a in sg.assays:
if a.meta:
update_d_from_meta(assay_meta_keys, a.meta)
Expand Down Expand Up @@ -318,6 +323,11 @@ def update_d_from_meta(d: dict[str, bool], meta: dict[str, Any]):
is_visible=True,
filter_key='external_id',
),
Field(
key='type',
label='Type',
is_visible=True,
),
Field(
key='sample_root_id',
label='Root Sample ID',
Expand Down Expand Up @@ -350,7 +360,7 @@ def update_d_from_meta(d: dict[str, bool], meta: dict[str, Any]):
Field(
key='type',
label='Type',
is_visible=True,
is_visible=has_assays,
filter_key='type',
)
]
Expand All @@ -368,7 +378,7 @@ def update_d_from_meta(d: dict[str, bool], meta: dict[str, Any]):
Field(
key='id',
label='Sequencing Group ID',
is_visible=True,
is_visible=has_sequencing_groups,
filter_key='id',
filter_types=[
ProjectParticipantGridFilterType.eq,
Expand All @@ -378,19 +388,19 @@ def update_d_from_meta(d: dict[str, bool], meta: dict[str, Any]):
Field(
key='type',
label='Type',
is_visible=True,
is_visible=has_sequencing_groups,
filter_key='type',
),
Field(
key='technology',
label='Technology',
is_visible=True,
is_visible=has_sequencing_groups,
filter_key='technology',
),
Field(
key='platform',
label='Platform',
is_visible=True,
is_visible=has_sequencing_groups,
filter_key='platform',
),
]
Expand Down
5 changes: 5 additions & 0 deletions test/data/basic-trio.ped
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#Family Individual ID Paternal ID Maternal ID Sex Affected
Fam1 Father 1 2
Fam1 Mother 2 1
Fam1 Child1 Father Mother 2 1
Fam1 Child2 Father Mother 1 2
10 changes: 8 additions & 2 deletions test/data/generate_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@
)


async def main(ped_path=default_ped_location, project='greek-myth'):
async def main(
ped_path=default_ped_location, project='greek-myth', sample_prefix='GRK'
):
"""Doing the generation for you"""

papi = ProjectApi()
Expand Down Expand Up @@ -131,7 +133,9 @@ def generate_random_number_within_distribution():
nsamples = generate_random_number_within_distribution()
for _ in range(nsamples):
sample = SampleUpsert(
external_ids={PRIMARY_EXTERNAL_ORG: f'GRK{sample_id_index}'},
external_ids={
PRIMARY_EXTERNAL_ORG: f'{sample_prefix}{sample_id_index}'
},
type=random.choice(sample_types),
meta={
'collection_date': datetime.datetime.now()
Expand Down Expand Up @@ -207,6 +211,7 @@ def generate_random_number_within_distribution():
)
for s in sequencing_group_ids
]

ar_entries_inserted = len(
await asyncio.gather(
*[
Expand Down Expand Up @@ -264,5 +269,6 @@ def generate_random_number_within_distribution():
help='Path to the pedigree file',
)
parser.add_argument('--project', type=str, default='greek-myth')
parser.add_argument('--sample-prefix', type=str, default='GRK')
args = vars(parser.parse_args())
asyncio.new_event_loop().run_until_complete(main(**args))
5 changes: 4 additions & 1 deletion test/test_web.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import unittest
from test.testbase import DbIsolatedTest, run_as_sync
from typing import Any

from api.routes.web import (
Expand Down Expand Up @@ -43,6 +42,7 @@
sequencing_group_id_format,
sequencing_group_id_transform_to_raw,
)
from test.testbase import DbIsolatedTest, run_as_sync

default_assay_meta = {
'sequencing_type': 'genome',
Expand Down Expand Up @@ -131,6 +131,9 @@
is_visible=False,
filter_key='sample_root_id',
),
ProjectParticipantGridField(
key='type', label='Type', is_visible=True, filter_key=None, filter_types=None
),
ProjectParticipantGridField(
key='created_date',
label='Created date',
Expand Down
4 changes: 2 additions & 2 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 18 additions & 6 deletions web/src/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Route, Routes as Switch } from 'react-router-dom'
import SwaggerUI from 'swagger-ui-react'

import ProjectsAdmin from './pages/admin/ProjectsAdmin'
import { AnalysisViewPage } from './pages/analysis/AnalysisView'
import {
BillingCostByAnalysis,
BillingCostByCategory,
Expand All @@ -14,10 +15,11 @@ import {
BillingSeqrProp,
} from './pages/billing'
import DocumentationArticle from './pages/docs/Documentation'
import FamilyView from './pages/family/FamilyView'
import { FamilyPage } from './pages/family/FamilyView'
import Details from './pages/insights/Details'
import Summary from './pages/insights/Summary'
import OurDnaDashboard from './pages/ourdna/OurDnaDashboard'
import { ParticipantPage } from './pages/participant/ParticipantViewContainer'
import AnalysisRunnerSummary from './pages/project/AnalysisRunnerView/AnalysisRunnerSummary'
import ProjectOverview from './pages/project/ProjectOverview'
import SampleView from './pages/sample/SampleView'
Expand All @@ -26,6 +28,7 @@ import ErrorBoundary from './shared/utilities/errorBoundary'
const Routes: React.FunctionComponent = () => (
<Switch>
<Route path="/" element={<DocumentationArticle articleid="index" />} />
{/* <Route path="/tt" element={<TangledTreeExamples />} /> */}

<Route path="admin" element={<ProjectsAdmin />} />
<Route
Expand All @@ -37,10 +40,10 @@ const Routes: React.FunctionComponent = () => (
}
/>
<Route
path="project/:projectName/participant/:participantName"
path="/participant/:participantId"
element={
<ErrorBoundary>
<SampleView />
<ParticipantPage />
</ErrorBoundary>
}
/>
Expand Down Expand Up @@ -103,7 +106,7 @@ const Routes: React.FunctionComponent = () => (
<Route path="/documentation/:id?" element={<DocumentationArticle />} />

<Route
path="sample/:sampleName/:sequencingGroupName?"
path="/sample/:sampleName/:sequencingGroupName?"
element={
<ErrorBoundary>
<SampleView />
Expand All @@ -112,10 +115,19 @@ const Routes: React.FunctionComponent = () => (
/>

<Route
path="/family/:familyID"
path="/family/:familyId"
element={
<ErrorBoundary>
<FamilyPage />
</ErrorBoundary>
}
/>

<Route
path="/analysis/:analysisId"
element={
<ErrorBoundary>
<FamilyView />
<AnalysisViewPage />
</ErrorBoundary>
}
/>
Expand Down
14 changes: 13 additions & 1 deletion web/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@

--color-text-href: rgb(65, 131, 196);

--color-pedigree-person-border: rgb(50, 50, 50);
--color-pedigree-affected: rgb(76, 76, 76);
--color-pedigree-unaffected: rgb(240, 240, 240);

--ourdna-blue-transparent: rgba(113, 172, 225, 0.5);
--ourdna-red-transparent: rgba(191, 0, 59, 0.5);
--ourdna-yellow-transparent: rgba(232, 199, 29, 0.5);
Expand Down Expand Up @@ -66,7 +70,7 @@ html[data-theme='dark-mode'] {
--color-bg-disabled-card: #353535;
--color-table-header: #383838;

--color-border-color: #3a3a3a;
--color-border-color: #565656;
--color-border-default: #292a2b;
--color-border-red: #921111;
--color-divider: rgba(255, 255, 255, 0.2);
Expand All @@ -85,6 +89,10 @@ html[data-theme='dark-mode'] {

--color-text-href: rgb(188, 188, 251);

--color-pedigree-person-border: rgb(169, 169, 169);
--color-pedigree-affected: rgb(51, 51, 51);
--color-pedigree-unaffected: rgb(153, 153, 153);

--color-header-row: #383838;
--color-exome-row: #887830;
--color-genome-row: #805223;
Expand Down Expand Up @@ -490,3 +498,7 @@ html[data-theme='dark-mode'] .ui.table {
.ui.attached.inverted.menu {
border: 1px solid #555 !important;
}

.dimmed.dimmable > .ui.modals.dimmer.visible {
display: flex !important;
}
97 changes: 97 additions & 0 deletions web/src/pages/analysis/AnalysisGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import * as React from 'react'

import { Table as SUITable } from 'semantic-ui-react'
import { GraphQlAnalysis } from '../../__generated__/graphql'

import Table from '../../shared/components/Table'
import AnalysisLink from '../../shared/components/links/AnalysisLink'

export interface IAnalysisGridAnalysis extends Partial<GraphQlAnalysis> {
sgs?: string[]
}

export const AnalysisGrid: React.FC<{
analyses: IAnalysisGridAnalysis[]
participantBySgId: { [sgId: string]: { externalId: string } }
sgsById?: { [sgId: string]: { technology: string; platform: string } }
highlightedIndividual?: string | null
setAnalysisIdToView: (analysisId?: number | null) => void
showSequencingGroup?: boolean
}> = ({
analyses,
participantBySgId,
sgsById,
highlightedIndividual,
setAnalysisIdToView,
showSequencingGroup,
}) => {
return (
<Table>
<thead>
<SUITable.Row>
<SUITable.HeaderCell>ID</SUITable.HeaderCell>
{showSequencingGroup && (
<SUITable.HeaderCell>Sequencing group</SUITable.HeaderCell>
)}
<SUITable.HeaderCell>Created</SUITable.HeaderCell>
<SUITable.HeaderCell>Type</SUITable.HeaderCell>
<SUITable.HeaderCell>Sequencing type</SUITable.HeaderCell>
<SUITable.HeaderCell>Sequencing technology</SUITable.HeaderCell>
<SUITable.HeaderCell>Output</SUITable.HeaderCell>
</SUITable.Row>
</thead>
<tbody>
{analyses?.map((a) => {
const sgId = a.sgs?.length === 1 ? a.sgs[0] : null
const sg = sgId ? sgsById?.[sgId] : null
return (
<SUITable.Row
key={a.id}
style={{
backgroundColor: a.sgs?.some(
(sg) =>
!!highlightedIndividual &&
participantBySgId[sg]?.externalId === highlightedIndividual
)
? 'var(--color-page-total-row)'
: 'var(--color-bg-card)',
}}
>
<SUITable.Cell>
<AnalysisLink
id={a.id}
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
setAnalysisIdToView(a.id)
}}
/>
</SUITable.Cell>
{showSequencingGroup && (
<SUITable.Cell>
{sg
? sgId
: a.sgs?.map((sg) => (
<li>
{sg}{' '}
{participantBySgId && sg in participantBySgId
? `(${participantBySgId[sg]?.externalId})`
: ''}
</li>
))}
</SUITable.Cell>
)}
<SUITable.Cell>{a.timestampCompleted}</SUITable.Cell>
<SUITable.Cell>{a.type}</SUITable.Cell>
<SUITable.Cell>{a.meta?.sequencing_type}</SUITable.Cell>
<SUITable.Cell>
{!!sg && `${sg?.technology} (${sg?.platform})`}
</SUITable.Cell>
<td style={{ wordBreak: 'break-all' }}>{a.output}</td>
</SUITable.Row>
)
})}
</tbody>
</Table>
)
}
Loading

0 comments on commit e7c6d49

Please sign in to comment.