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

Seqr loading wizard #101

Draft
wants to merge 44 commits into
base: staging
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
eabcd21
Dev file for getting a PG service running locally via Docker.
daniaki Oct 27, 2021
f6f62a8
dotenv template file for devs
daniaki Oct 27, 2021
247d618
basic dev environment for conda
daniaki Oct 27, 2021
bbe5d13
ignore .envrc and data/ directory created by Docker Compose
daniaki Oct 27, 2021
063f52e
Temp webpack fixes from current OpenSSL3 breaking changes.
daniaki Oct 28, 2021
96bcbee
Parent wizard component and integration with Project routes
daniaki Oct 28, 2021
dbdffef
Ignore idea and vscode dirs
daniaki Oct 29, 2021
ddf9b3e
Serve 401 if user doesn't have edit permissions
daniaki Oct 29, 2021
3cb6275
Renamed from OnboardingWizard
daniaki Nov 1, 2021
28a10b1
Re-usable form components relating to UI
daniaki Nov 1, 2021
69eda78
Setting up the multi-step form
daniaki Nov 1, 2021
b4b8e7b
Welcome step
daniaki Nov 1, 2021
7d657e3
Export steps from `steps` module
daniaki Nov 1, 2021
45afacd
Renamed components
daniaki Nov 1, 2021
6d4de64
Outline of sections
daniaki Nov 8, 2021
43ef748
Stubs
daniaki Nov 8, 2021
be38572
Module export
daniaki Nov 8, 2021
20ae88f
Implemented state handling for changes occurring in each form step
daniaki Nov 8, 2021
2d4f412
Minor refactoring favouring positivity over negativity in args
daniaki Nov 8, 2021
64283fe
Merge branch 'dev' of https://github.com/populationgenomics/seqr into…
daniaki Jan 19, 2022
58f3aea
Renamed directory to `DataLoadingWizard`
daniaki Jan 27, 2022
b961219
renamed component
daniaki Jan 27, 2022
cb3f78b
Text with some breathing space
daniaki Jan 27, 2022
e6c0dea
Added papaparse for csv parsing
daniaki Jan 27, 2022
8e9c40d
Metadata template file definitions
daniaki Jan 27, 2022
419942b
parsers for file fields
daniaki Jan 27, 2022
8c18d83
validators for file columns
daniaki Jan 27, 2022
3d59d78
Specification for how to parse and validate a column in a CSV/TSV file
daniaki Jan 27, 2022
616f668
Specification for how to parse a template file with many columns
daniaki Jan 27, 2022
4700a0f
Updated error messages
daniaki Jan 28, 2022
917c553
Renamed to `TemplateUpload`
daniaki Jan 28, 2022
171924a
Fixed a bug where errors returned as a nested list
daniaki Jan 28, 2022
1a9f837
Renamed import
daniaki Jan 28, 2022
5da4130
New styled components with props
daniaki Feb 3, 2022
c1c2e75
Moved to inner directory
daniaki Feb 3, 2022
715dcff
Moved to inner directory
daniaki Feb 3, 2022
b76aa7b
Instruction components with help/guide text
daniaki Feb 3, 2022
4819493
Encapsulating a row parsed from a template file
daniaki Feb 3, 2022
3369929
Pedigree template definition
daniaki Feb 3, 2022
e21f36b
Renamed
daniaki Feb 3, 2022
711d873
Import path update
daniaki Feb 3, 2022
a45f9d7
Added new module exports
daniaki Feb 3, 2022
8d7fd14
Added steps for pedigree, family and indiviual metadata uploads
daniaki Feb 3, 2022
badbcde
Adding unique keys to lists for react performance; refactoring reusab…
daniaki Feb 3, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
ui/node_modules/
ui/node_modules/
.idea/
.vscode/
26 changes: 26 additions & 0 deletions .envrc.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Database settings for Django
export POSTGRES_SERVICE_HOSTNAME=localhost
export POSTGRES_SERVICE_PORT=5433
export POSTGRES_USERNAME=postgres
export POSTGRES_PASSWORD=a-very-secure-password
export POSTGRES_DATABASE=seqrdb

# Leave blank for local env
export DEPLOYMENT_TYPE=

# Convenience commands for creating database
export CREATE_REF_DB_CMD="psql -W -U ${POSTGRES_USERNAME} -h ${POSTGRES_SERVICE_HOSTNAME} -p ${POSTGRES_SERVICE_PORT} -c 'create database reference_data_db;'"
export LIST_DBS_CMD="psql -W -U ${POSTGRES_USERNAME} -h ${POSTGRES_SERVICE_HOSTNAME} -p ${POSTGRES_SERVICE_PORT} -c '\list'"

# ----------------------------------------------------------------------------
# Apple Developers
# ----------------------------------------------------------------------------
# Install libpg for the Postgres client library without the server (brew install libpg)
# Export these variables so that pip packages know where to find the appropriate binaries, libs and headers.
# export PATH="/opt/homebrew/opt/libpq/bin:$PATH"
# export LDFLAGS="-L/opt/homebrew/opt/libpq/lib $LDFLAGS"
# export CPPFLAGS="-I/opt/homebrew/opt/libpq/include $CPPFLAGS"

# Apple silicon (M1 chip etc) fix for grpcio
# export GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=1
# export GRPC_PYTHON_BUILD_SYSTEM_ZLIB=1
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.idea*
.vscode*
*.iml
*.sqlite3
*.log
Expand All @@ -22,3 +23,7 @@ seqr_settings
django_key
static/
.coverage

.envrc

data/
19 changes: 19 additions & 0 deletions docker-compose-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
version: '2.4'

services:
postgres:
image: postgres:12.6
command: postgres -c listen_addresses='*'
environment:
- PGPORT=5433
- POSTGRES_DB=seqrdb
- POSTGRES_PASSWORD=a-very-secure-password
volumes:
- ./data/postgres:/var/lib/postgresql/data
ports:
- "5433:5433"
healthcheck:
test: pg_isready -h postgres -U postgres
interval: 5s
timeout: 10s
retries: 100
13 changes: 13 additions & 0 deletions environment-dev.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: seqr

channels:
- cpg
- bioconda
- conda-forge
dependencies:
- python=3.8
- pip
- nodejs==16.10.0
- pip:
- '-r requirements-dev.txt'
- '-r requirements.txt'
4 changes: 4 additions & 0 deletions ui/config/webpack.config.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ const eslintFormatter = require('react-dev-utils/eslintFormatter');
const glob = require('glob');
const paths = require('./paths');

// HACK: OpenSSL 3 does not support md4 any more, but webpack hardcodes it all over the place: https://github.com/webpack/webpack/issues/13572
const crypto = require('crypto');
const cryptoOriginalCreateHash = crypto.createHash;
crypto.createHash = algorithm => cryptoOriginalCreateHash(algorithm === 'md4' ? 'sha256' : algorithm);

// This is the development configuration.

Expand Down
5 changes: 5 additions & 0 deletions ui/config/webpack.config.prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ const eslintFormatter = require('react-dev-utils/eslintFormatter');
const paths = require('./paths');
const getClientEnvironment = require('./env');

// HACK: OpenSSL 3 does not support md4 any more, but webpack hardcodes it all over the place: https://github.com/webpack/webpack/issues/13572
const crypto = require('crypto');
const cryptoOriginalCreateHash = crypto.createHash;
crypto.createHash = algorithm => cryptoOriginalCreateHash(algorithm === 'md4' ? 'sha256' : algorithm);

// Webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
const publicPath = paths.servedPath;
Expand Down
11 changes: 11 additions & 0 deletions ui/package-lock.json

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

1 change: 1 addition & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"lodash": "^4.17.21",
"markdown-draft-js": "^2.4.0",
"object-hash": "^1.3.0",
"papaparse": "^5.3.1",
"pedigreejs": "github:CCGE-BOADICEA/pedigreejs#2e17296",
"prop-types": "^15.7.2",
"query-string": "^6.1.0",
Expand Down
2 changes: 2 additions & 0 deletions ui/pages/Project/Project.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import CaseReview from './components/CaseReview'
import FamilyPage from './components/FamilyPage'
import Matchmaker from './components/Matchmaker'
import SavedVariants from './components/SavedVariants'
import DataLoadingWizard from './components/DataLoadingWizard/DataLoadingWizard'

class Project extends React.PureComponent {

Expand Down Expand Up @@ -45,6 +46,7 @@ class Project extends React.PureComponent {
<Route path={`${match.url}/family_page/:familyGuid/matchmaker_exchange`} component={Matchmaker} />
<Route path={`${match.url}/family_page/:familyGuid`} component={FamilyPage} />
<Route path={`${match.url}/saved_variants`} component={SavedVariants} />
<Route path={`${match.url}/wizard/`} component={DataLoadingWizard} />
<Route component={Error404} />
</Switch>
)
Expand Down
196 changes: 196 additions & 0 deletions ui/pages/Project/components/DataLoadingWizard/DataLoadingWizard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/* eslint-disable react-perf/jsx-no-new-function-as-prop */
/* eslint-disable no-console */
/* eslint-disable no-alert */

import React, { useState, useEffect, useCallback } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Breadcrumb } from 'semantic-ui-react'

import { Error404 } from '../../../../shared/components/page/Errors'
import { getCurrentProject } from '../../selectors'

import { Centered, FormSection, FormStepButtons } from './ui'
import { Welcome, TemplateUpload } from './steps'
import PedigreeTemplateColumns from './templates/Pedigree'
import IndividualTemplateColumns from './templates/Individual'
import FamilyTemplateColumns from './templates/Family'
import TemplateFile from './templates/File/TemplateFile'
import TemplateHelp from './TemplateHelp'

const BREADCRUMBS = [
{ key: 0, content: 'Welcome', link: true, active: true },
{ key: 1, content: 'Pedigree', link: true, active: false },
{ key: 2, content: 'Individual metadata', link: true, active: false },
{ key: 3, content: 'Family metadata', link: true, active: false },
{ key: 4, content: 'Sample mapping', link: true, active: false },
{ key: 5, content: 'Review', link: true, active: false },
]

const BaseMultistepForm = ({ project }) => {
// ---- Debug ---- //
console.group('Project Object')
console.log(project)
console.groupEnd()

// ---- State ----- //
const [breadCrumbs, setBreadcrumbs] = useState(BREADCRUMBS.map(b => ({ ...b })))
const [activeFormStepIndex, setActiveFormStepIndex] = useState(0)
const [formSteps, setFormSteps] = useState(BREADCRUMBS.map(
(_, index) => ({ formData: {}, isComplete: (index === 0) }),
))

// ---- Callbacks ----- //
const updateFormStep = useCallback((stepNumber, { formData, isComplete }) => {
setFormSteps(formSteps.map(
(step, index) => {
if (stepNumber === index) {
return { formData, isComplete }
}
return { ...step }
},
))
}, [formSteps])

const enableReview = useCallback(() => formSteps
.slice(0, formSteps.length - 1)
.reduce((acc, step) => acc && step.isComplete, true), [formSteps])

const enableNext = useCallback(() => {
if (activeFormStepIndex === (formSteps.length - 2)) {
return enableReview()
}

return activeFormStepIndex < formSteps.length
}, [formSteps, activeFormStepIndex, enableReview])

const getFormStepComponent = useCallback(() => {
const onFormChange = ({ formData, isComplete }) => updateFormStep(activeFormStepIndex, { formData, isComplete })

switch (activeFormStepIndex) {
case 0:
return <Welcome />
case 1:
return (
<TemplateUpload
id="pedigree-upload"
key="pedigree-upload"
label="Pedigree"
template={new TemplateFile(PedigreeTemplateColumns())}
onFormChange={onFormChange}
information={<TemplateHelp />}
project={project}
/>
)
case 2:
return (
<TemplateUpload
id="individual-metadata-upload"
key="individual-metadata-upload"
label="Individual Metadata"
template={new TemplateFile(IndividualTemplateColumns())}
onFormChange={onFormChange}
information={<TemplateHelp />}
project={project}
/>
)
case 3:
return (
<TemplateUpload
id="family-metadata-upload"
key="family-metadata-upload"
label="Family Metadata"
template={new TemplateFile(FamilyTemplateColumns())}
onFormChange={onFormChange}
information={<TemplateHelp />}
project={project}
/>
)
case 4:
return <div>{ breadCrumbs[activeFormStepIndex].content }</div>
case 5:
return <div>{ breadCrumbs[activeFormStepIndex].content }</div>
default:
return <Error404 />
}
}, [project, activeFormStepIndex, updateFormStep])

// ---- Effects ---- //
useEffect(() => {
setBreadcrumbs(breadCrumbs.map((b, index) => ({ ...b, active: (index === activeFormStepIndex) })))
}, [activeFormStepIndex])

useEffect(() => {
setBreadcrumbs(
breadCrumbs.map((b, index) => {
if (index === (breadCrumbs.length - 1)) {
return {
...b,
link: enableReview(),
onClick: enableReview() ? () => setActiveFormStepIndex(index) : null,
}
}

return {
...b,
link: true,
onClick: () => setActiveFormStepIndex(index),
}
}),
)
}, [enableReview])

// ---- Render ----- //
return (
<section>
<FormSection>
<Centered>
<Breadcrumb
icon="right angle"
size="large"
sections={breadCrumbs}
/>
</Centered>
</FormSection>

<FormSection>
{getFormStepComponent()}
</FormSection>

<FormSection>
<FormStepButtons
isLastStep={activeFormStepIndex === (formSteps.length - 1)}
onNext={() => setActiveFormStepIndex(Math.min(activeFormStepIndex + 1, formSteps.length - 1))}
onBack={() => setActiveFormStepIndex(Math.max(0, activeFormStepIndex - 1))}
enableNext={enableNext()}
enableSubmit={enableReview()}
onSubmit={() => alert('Yay')}
/>
</FormSection>

<FormSection>
<Centered>
<Breadcrumb
icon="right angle"
size="small"
sections={breadCrumbs}
/>
</Centered>
</FormSection>
</section>
)
}

BaseMultistepForm.propTypes = {
project: PropTypes.object.isRequired,
}

const mapStateToProps = state => ({
project: getCurrentProject(state),
})

export const MultistepForm = connect(mapStateToProps)(BaseMultistepForm)

const DataLoadingWizard = () => <MultistepForm />

export default DataLoadingWizard
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react'

const TemplateDirectoryLink = () => (
<a
href="https://drive.google.com/drive/folders/1iHYMDY8-RzfqvvyEdubOHOmGzbLGUSUk"
target="_blank"
rel="noreferrer"
>
here
</a>
)

export default TemplateDirectoryLink
17 changes: 17 additions & 0 deletions ui/pages/Project/components/DataLoadingWizard/TemplateHelp.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react'

import { ReadableText } from './ui'
import TemplateDirectoryLink from './TemplateDirectoryLink'

const TemplateHelp = () => (
<ReadableText>
In this section, you will provide all information relating to the family pedigree and associated metadata in your
project. Please download the families template from
{' '}
<TemplateDirectoryLink />
{ '. ' }
Once you have filled in this template, upload it via this step and correct any validation errors before proceeding.
</ReadableText>
)

export default TemplateHelp
Empty file.
Empty file.
Loading