} />
)
diff --git a/ui/pages/Project/components/OnboardingWizard/OnboardingWizard.jsx b/ui/pages/Project/components/OnboardingWizard/OnboardingWizard.jsx
new file mode 100644
index 0000000000..c9e1d8fbac
--- /dev/null
+++ b/ui/pages/Project/components/OnboardingWizard/OnboardingWizard.jsx
@@ -0,0 +1,26 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { connect } from 'react-redux'
+
+import { getCurrentProject } from '../../selectors'
+
+const OnboardingWizard = ({ match, project }) => {
+ return (
+
+
Hello, world!
+
{ JSON.stringify(match, null, 2) }
+
{ JSON.stringify(project, null, 2) }
+
+ )
+}
+
+OnboardingWizard.propTypes = {
+ match: PropTypes.object,
+ project: PropTypes.object,
+}
+
+const mapStateToProps = state => ({
+ project: getCurrentProject(state),
+})
+
+export default connect(mapStateToProps)(OnboardingWizard)
From dbdffefd7195d6abe6e38ee520466e23d5aecc6b Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Fri, 29 Oct 2021 12:29:22 +1100
Subject: [PATCH 07/43] Ignore idea and vscode dirs
---
.dockerignore | 4 +++-
.gitignore | 1 +
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/.dockerignore b/.dockerignore
index bf881194fb..52d1cb5546 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1 +1,3 @@
-ui/node_modules/
\ No newline at end of file
+ui/node_modules/
+.idea/
+.vscode/
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 871a01e1b7..6a356cf957 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
.idea*
+.vscode*
*.iml
*.sqlite3
*.log
From ddf9b3e271533059a9af870bf9eebe1bc9d62808 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Fri, 29 Oct 2021 12:30:45 +1100
Subject: [PATCH 08/43] Serve 401 if user doesn't have edit permissions
---
.../components/OnboardingWizard/OnboardingWizard.jsx | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/ui/pages/Project/components/OnboardingWizard/OnboardingWizard.jsx b/ui/pages/Project/components/OnboardingWizard/OnboardingWizard.jsx
index c9e1d8fbac..c2cb29e1d6 100644
--- a/ui/pages/Project/components/OnboardingWizard/OnboardingWizard.jsx
+++ b/ui/pages/Project/components/OnboardingWizard/OnboardingWizard.jsx
@@ -2,14 +2,19 @@ import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
+import { Error401 } from '../../../../shared/components/page/Errors'
import { getCurrentProject } from '../../selectors'
const OnboardingWizard = ({ match, project }) => {
+ if (!project.canEdit) {
+ return
+ }
+
return (
Hello, world!
-
{ JSON.stringify(match, null, 2) }
-
{ JSON.stringify(project, null, 2) }
+
{JSON.stringify(match, null, 2)}
+
{JSON.stringify(project, null, 2)}
)
}
From 3cb6275c479894c53bc788df29f55d0611d094c3 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Mon, 1 Nov 2021 17:20:57 +1100
Subject: [PATCH 09/43] Renamed from OnboardingWizard
---
.../components/DataLoadingWizardPage.jsx | 26 ++++++++++++++++
.../OnboardingWizard/OnboardingWizard.jsx | 31 -------------------
2 files changed, 26 insertions(+), 31 deletions(-)
create mode 100644 ui/pages/Project/components/DataLoadingWizardPage.jsx
delete mode 100644 ui/pages/Project/components/OnboardingWizard/OnboardingWizard.jsx
diff --git a/ui/pages/Project/components/DataLoadingWizardPage.jsx b/ui/pages/Project/components/DataLoadingWizardPage.jsx
new file mode 100644
index 0000000000..79bbb72cee
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizardPage.jsx
@@ -0,0 +1,26 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { connect } from 'react-redux'
+
+import { Error401 } from '../../../shared/components/page/Errors'
+import { getCurrentProject } from '../selectors'
+
+import DataLoadingWizardForm from './DataLoadingWizardForm/DataLoadingWizardForm'
+
+const DataLoadingWizardPage = ({ project }) => {
+ if (!project.canEdit) {
+ return
+ }
+
+ return
+}
+
+DataLoadingWizardPage.propTypes = {
+ project: PropTypes.object,
+}
+
+const mapStateToProps = state => ({
+ project: getCurrentProject(state),
+})
+
+export default connect(mapStateToProps)(DataLoadingWizardPage)
diff --git a/ui/pages/Project/components/OnboardingWizard/OnboardingWizard.jsx b/ui/pages/Project/components/OnboardingWizard/OnboardingWizard.jsx
deleted file mode 100644
index c2cb29e1d6..0000000000
--- a/ui/pages/Project/components/OnboardingWizard/OnboardingWizard.jsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import { connect } from 'react-redux'
-
-import { Error401 } from '../../../../shared/components/page/Errors'
-import { getCurrentProject } from '../../selectors'
-
-const OnboardingWizard = ({ match, project }) => {
- if (!project.canEdit) {
- return
- }
-
- return (
-
-
Hello, world!
-
{JSON.stringify(match, null, 2)}
-
{JSON.stringify(project, null, 2)}
-
- )
-}
-
-OnboardingWizard.propTypes = {
- match: PropTypes.object,
- project: PropTypes.object,
-}
-
-const mapStateToProps = state => ({
- project: getCurrentProject(state),
-})
-
-export default connect(mapStateToProps)(OnboardingWizard)
From 28a10b13e13889c7046f7943727230f7220122ec Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Mon, 1 Nov 2021 17:21:31 +1100
Subject: [PATCH 10/43] Re-usable form components relating to UI
---
.../DataLoadingWizardForm/ui/Centered.jsx | 8 ++
.../DataLoadingWizardForm/ui/FormSection.jsx | 8 ++
.../ui/FormStepButtons.jsx | 84 +++++++++++++++++++
.../DataLoadingWizardForm/ui/index.js | 9 ++
4 files changed, 109 insertions(+)
create mode 100644 ui/pages/Project/components/DataLoadingWizardForm/ui/Centered.jsx
create mode 100644 ui/pages/Project/components/DataLoadingWizardForm/ui/FormSection.jsx
create mode 100644 ui/pages/Project/components/DataLoadingWizardForm/ui/FormStepButtons.jsx
create mode 100644 ui/pages/Project/components/DataLoadingWizardForm/ui/index.js
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/ui/Centered.jsx b/ui/pages/Project/components/DataLoadingWizardForm/ui/Centered.jsx
new file mode 100644
index 0000000000..0e82f3fd03
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizardForm/ui/Centered.jsx
@@ -0,0 +1,8 @@
+import styled from 'styled-components'
+
+const Centered = styled.div`
+ display: flex;
+ justify-content: center;
+`
+
+export default Centered
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/ui/FormSection.jsx b/ui/pages/Project/components/DataLoadingWizardForm/ui/FormSection.jsx
new file mode 100644
index 0000000000..66af0e103e
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizardForm/ui/FormSection.jsx
@@ -0,0 +1,8 @@
+import styled from 'styled-components'
+
+const FormSection = styled.div`
+ margin-top: 2em;
+ margin-bottom: 2em;
+`
+
+export default FormSection
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/ui/FormStepButtons.jsx b/ui/pages/Project/components/DataLoadingWizardForm/ui/FormStepButtons.jsx
new file mode 100644
index 0000000000..bca91cb5d5
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizardForm/ui/FormStepButtons.jsx
@@ -0,0 +1,84 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { Button } from 'semantic-ui-react'
+import styled from 'styled-components'
+
+const FormButtonGroup = styled.div`
+ display: flex;
+ justify-content: right;
+`
+
+const FormStepButtons = ({ isLastStep, onNext, onBack, onSubmit, disabled, loading, size }) => {
+ if (isLastStep) {
+ return (
+
+ Back
+
+
+ Next
+
+
+ Submit
+
+
+ )
+ }
+
+ return (
+
+ Back
+
+
+ Next
+
+
+ )
+}
+
+FormStepButtons.propTypes = {
+ isLastStep: PropTypes.bool,
+ onNext: PropTypes.func,
+ onBack: PropTypes.func,
+ onSubmit: PropTypes.func,
+ disabled: PropTypes.bool,
+ loading: PropTypes.bool,
+ size: PropTypes.string,
+}
+
+FormStepButtons.defaultProps = {
+ isLastStep: false,
+ onNext: () => {},
+ onBack: () => {},
+ onSubmit: () => {},
+ disabled: false,
+ loading: false,
+ size: 'small',
+}
+
+export default FormStepButtons
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/ui/index.js b/ui/pages/Project/components/DataLoadingWizardForm/ui/index.js
new file mode 100644
index 0000000000..db2f38cca4
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizardForm/ui/index.js
@@ -0,0 +1,9 @@
+import Centered from './Centered'
+import FormSection from './FormSection'
+import FormStepButtons from './FormStepButtons'
+
+export {
+ Centered,
+ FormSection,
+ FormStepButtons,
+}
From 69eda78d0afa10ba921fc3bb3b809f30a795e1e1 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Mon, 1 Nov 2021 17:21:43 +1100
Subject: [PATCH 11/43] Setting up the multi-step form
---
.../DataLoadingWizardForm.jsx | 75 +++++++++++++++++++
1 file changed, 75 insertions(+)
create mode 100644 ui/pages/Project/components/DataLoadingWizardForm/DataLoadingWizardForm.jsx
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/DataLoadingWizardForm.jsx b/ui/pages/Project/components/DataLoadingWizardForm/DataLoadingWizardForm.jsx
new file mode 100644
index 0000000000..29c9303553
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizardForm/DataLoadingWizardForm.jsx
@@ -0,0 +1,75 @@
+import React, { useState, useEffect } from 'react'
+import PropTypes from 'prop-types'
+import { Breadcrumb, Header } from 'semantic-ui-react'
+
+import { Centered, FormSection, FormStepButtons } from './ui'
+import { Welcome } from './steps'
+
+const FORM_STEPS = [
+ { key: 0, content: 'Welcome', link: false, active: true, component: Welcome },
+ { key: 1, content: 'Family metadata', link: false, active: false, component: () => {} },
+ { key: 2, content: 'Individual metadata', link: false, active: false, component: () => {} },
+ { key: 3, content: 'Sample mapping', link: false, active: false, component: () => {} },
+ { key: 4, content: 'Submit', link: false, active: false, component: () => {} },
+]
+
+const DataLoadingWizardForm = ({ project }) => {
+ // const [formState, setFormState] = useState({})
+ const [formStepIndex, setFormStepIndex] = useState(0)
+ const [formSteps, setFormSteps] = useState([...FORM_STEPS])
+
+ useEffect(() => {
+ setFormSteps(
+ formSteps.map(
+ (step) => { return { ...step, active: step.key === formStepIndex } },
+ ),
+ )
+ }, [formStepIndex])
+
+ return (
+
+
+
+
+
+
+
+
+ {/* React Hook components are just functions under the hood. */}
+ {/* Get the component property from the current step and call it to add it to the DOM. */}
+ {formSteps[formStepIndex].component({ project })}
+
+ {JSON.stringify(project, null, 2)}
+
+
+
+ setFormStepIndex(Math.min(formStepIndex + 1, formSteps.length - 1))}
+ onBack={() => setFormStepIndex(Math.max(0, formStepIndex - 1))}
+ onSubmit={() => alert('Yay')}
+ />
+
+
+
+
+
+
+
+
+ )
+}
+
+DataLoadingWizardForm.propTypes = {
+ project: PropTypes.object,
+}
+
+export default DataLoadingWizardForm
From b4b8e7bbbe176bb9365904f51f434b56f14f4431 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Mon, 1 Nov 2021 17:21:57 +1100
Subject: [PATCH 12/43] Welcome step
---
.../DataLoadingWizardForm/steps/Welcome.jsx | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
create mode 100644 ui/pages/Project/components/DataLoadingWizardForm/steps/Welcome.jsx
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/steps/Welcome.jsx b/ui/pages/Project/components/DataLoadingWizardForm/steps/Welcome.jsx
new file mode 100644
index 0000000000..cf2b22ca95
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizardForm/steps/Welcome.jsx
@@ -0,0 +1,16 @@
+import React from 'react'
+
+const Welcome = () => {
+ return (
+
+
+ Welcome to the seqr data loading wizard! This wizard will guide you through the process of uploading
+ the relevant individual template, family template and associated metadata files. Each step will validate
+ these files to ensure that the information relating individuals, families and samples is correctly formatted.
+
+
+ )
+}
+
+export default Welcome
+
From 7d657e343749541195d1e910baf683bdc239d25d Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Mon, 1 Nov 2021 17:22:12 +1100
Subject: [PATCH 13/43] Export steps from `steps` module
---
.../Project/components/DataLoadingWizardForm/steps/index.js | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 ui/pages/Project/components/DataLoadingWizardForm/steps/index.js
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/steps/index.js b/ui/pages/Project/components/DataLoadingWizardForm/steps/index.js
new file mode 100644
index 0000000000..c88905cec8
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizardForm/steps/index.js
@@ -0,0 +1,5 @@
+import Welcome from './Welcome'
+
+export {
+ Welcome,
+}
From 45afacde8f5dd39264b60c34a0825d5481183194 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Mon, 1 Nov 2021 17:22:21 +1100
Subject: [PATCH 14/43] Renamed components
---
ui/pages/Project/Project.jsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/ui/pages/Project/Project.jsx b/ui/pages/Project/Project.jsx
index 342fc8a917..5766a4c5cb 100644
--- a/ui/pages/Project/Project.jsx
+++ b/ui/pages/Project/Project.jsx
@@ -12,7 +12,7 @@ import CaseReview from './components/CaseReview'
import FamilyPage from './components/FamilyPage'
import Matchmaker from './components/Matchmaker'
import SavedVariants from './components/SavedVariants'
-import OnboardingWizard from './components/OnboardingWizard/OnboardingWizard'
+import DataLoadingWizardPage from './components/DataLoadingWizardPage'
class Project extends React.PureComponent
{
@@ -44,7 +44,7 @@ class Project extends React.PureComponent
-
+
} />
)
From 6d4de64c9726735a8fb2a4840e351222b60af260 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Mon, 8 Nov 2021 16:25:50 +1100
Subject: [PATCH 15/43] Outline of sections
---
.../DataLoadingWizardForm/steps/Welcome.jsx | 41 +++++++++++++++----
1 file changed, 34 insertions(+), 7 deletions(-)
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/steps/Welcome.jsx b/ui/pages/Project/components/DataLoadingWizardForm/steps/Welcome.jsx
index cf2b22ca95..9b6abab997 100644
--- a/ui/pages/Project/components/DataLoadingWizardForm/steps/Welcome.jsx
+++ b/ui/pages/Project/components/DataLoadingWizardForm/steps/Welcome.jsx
@@ -1,14 +1,41 @@
import React from 'react'
+import styled from 'styled-components'
+
+const WelcomeSection = styled.section`
+ margin: 1em 0;
+`
+
+const ReadableText = styled.p`
+ font-size: 16px;
+ line-height: 1.6;
+`
const Welcome = () => {
return (
-
-
- Welcome to the seqr data loading wizard! This wizard will guide you through the process of uploading
- the relevant individual template, family template and associated metadata files. Each step will validate
- these files to ensure that the information relating individuals, families and samples is correctly formatted.
-
-
+
+
+
+ Welcome to the seqr data loading wizard! This wizard will guide you through the process of uploading
+ the relevant individual template, family template and associated metadata files. Each step will validate
+ these files to ensure that the information relating individuals, families and samples is correctly formatted.
+
+
+
+
+ Introduction
+ Overview of what the collaborator needs to do goes here
+
+
+
+ Resources
+ Overview of required template and mapping formats
+
+
+
+ FAQ
+ Frequently asked questions and answers
+
+
)
}
From 43ef74811681ece4a16a9d47359959d771437010 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Mon, 8 Nov 2021 16:26:01 +1100
Subject: [PATCH 16/43] Stubs
---
.../steps/FamilyMetadataUpload.jsx | 15 +++++++++++++++
.../steps/IndividualMetadataUpload.jsx | 0
.../DataLoadingWizardForm/steps/Review.jsx | 0
.../DataLoadingWizardForm/steps/SampleMapping.jsx | 0
4 files changed, 15 insertions(+)
create mode 100644 ui/pages/Project/components/DataLoadingWizardForm/steps/FamilyMetadataUpload.jsx
create mode 100644 ui/pages/Project/components/DataLoadingWizardForm/steps/IndividualMetadataUpload.jsx
create mode 100644 ui/pages/Project/components/DataLoadingWizardForm/steps/Review.jsx
create mode 100644 ui/pages/Project/components/DataLoadingWizardForm/steps/SampleMapping.jsx
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/steps/FamilyMetadataUpload.jsx b/ui/pages/Project/components/DataLoadingWizardForm/steps/FamilyMetadataUpload.jsx
new file mode 100644
index 0000000000..a62dea4624
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizardForm/steps/FamilyMetadataUpload.jsx
@@ -0,0 +1,15 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+// import styled from 'styled-components'
+
+const FamilyMetadataUpload = ({ project, onFormChange }) => {
+ return
+}
+
+FamilyMetadataUpload.propTypes = {
+ project: PropTypes.object.isRequired,
+ onFormChange: PropTypes.func.isRequired,
+}
+
+export default FamilyMetadataUpload
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/steps/IndividualMetadataUpload.jsx b/ui/pages/Project/components/DataLoadingWizardForm/steps/IndividualMetadataUpload.jsx
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/steps/Review.jsx b/ui/pages/Project/components/DataLoadingWizardForm/steps/Review.jsx
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/steps/SampleMapping.jsx b/ui/pages/Project/components/DataLoadingWizardForm/steps/SampleMapping.jsx
new file mode 100644
index 0000000000..e69de29bb2
From be3857218d532aaad2993c18bab4f0275efd710b Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Mon, 8 Nov 2021 16:26:11 +1100
Subject: [PATCH 17/43] Module export
---
.../Project/components/DataLoadingWizardForm/steps/index.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/steps/index.js b/ui/pages/Project/components/DataLoadingWizardForm/steps/index.js
index c88905cec8..91bccea233 100644
--- a/ui/pages/Project/components/DataLoadingWizardForm/steps/index.js
+++ b/ui/pages/Project/components/DataLoadingWizardForm/steps/index.js
@@ -1,5 +1,7 @@
import Welcome from './Welcome'
+import FamilyMetadataUpload from './FamilyMetadataUpload'
export {
Welcome,
+ FamilyMetadataUpload,
}
From 20ae88f4a476b63ddd0f44154806858cf4160ae0 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Mon, 8 Nov 2021 17:10:24 +1100
Subject: [PATCH 18/43] Implemented state handling for changes occurring in
each form step
---
.../DataLoadingWizardForm.jsx | 135 ++++++++++++++----
1 file changed, 107 insertions(+), 28 deletions(-)
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/DataLoadingWizardForm.jsx b/ui/pages/Project/components/DataLoadingWizardForm/DataLoadingWizardForm.jsx
index 29c9303553..61ddfc21f2 100644
--- a/ui/pages/Project/components/DataLoadingWizardForm/DataLoadingWizardForm.jsx
+++ b/ui/pages/Project/components/DataLoadingWizardForm/DataLoadingWizardForm.jsx
@@ -1,31 +1,112 @@
-import React, { useState, useEffect } from 'react'
+import React, { useState, useEffect, useCallback } from 'react'
import PropTypes from 'prop-types'
-import { Breadcrumb, Header } from 'semantic-ui-react'
+import { Breadcrumb } from 'semantic-ui-react'
import { Centered, FormSection, FormStepButtons } from './ui'
-import { Welcome } from './steps'
-
-const FORM_STEPS = [
- { key: 0, content: 'Welcome', link: false, active: true, component: Welcome },
- { key: 1, content: 'Family metadata', link: false, active: false, component: () => {} },
- { key: 2, content: 'Individual metadata', link: false, active: false, component: () => {} },
- { key: 3, content: 'Sample mapping', link: false, active: false, component: () => {} },
- { key: 4, content: 'Submit', link: false, active: false, component: () => {} },
+import { Welcome, FamilyMetadataUpload } from './steps'
+import { Error404 } from '../../../../shared/components/page/Errors'
+
+const BREADCRUMBS = [
+ { key: 0, content: 'Welcome', link: true, active: true },
+ { key: 1, content: 'Family metadata', link: true, active: false },
+ { key: 2, content: 'Individual metadata', link: true, active: false },
+ { key: 3, content: 'Sample mapping', link: true, active: false },
+ { key: 4, content: 'Review', link: true, active: false },
]
+
const DataLoadingWizardForm = ({ project }) => {
- // const [formState, setFormState] = useState({})
- const [formStepIndex, setFormStepIndex] = useState(0)
- const [formSteps, setFormSteps] = useState([...FORM_STEPS])
+ // ---- Debug ---- //
+ console.group('Project Object')
+ console.log(project)
+ console.groupEnd()
+
+ // ---- State ----- //
+ const [breadCrumbs, setBreadcrumbs] = useState(BREADCRUMBS.map((b) => { return { ...b } }))
+ const [activeFormStepIndex, setActiveFormStepIndex] = useState(0)
+ const [formSteps, setFormSteps] = useState(BREADCRUMBS.map(
+ (_, index) => {
+ return { formData: {}, isComplete: (index === 0) }
+ },
+ ))
+
+ // ---- Callbacks ----- //
+ const updateFormStep = useCallback((stepNumber, { formData, isComplete }) => {
+ setFormSteps(formSteps.map(
+ (step, index) => {
+ if (stepNumber === index) {
+ return { formData, isComplete }
+ }
+ return { ...step }
+ },
+ ))
+ }, [])
+
+ const enableReview = useCallback(() => {
+ return 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(() => {
+ switch (activeFormStepIndex) {
+ case 0:
+ return
+ case 1:
+ return updateFormStep(1, { formData, isComplete })}
+ />
+ case 2:
+ return { breadCrumbs[activeFormStepIndex].content }
+ case 3:
+ return { breadCrumbs[activeFormStepIndex].content }
+ case 4:
+ return { breadCrumbs[activeFormStepIndex].content }
+ case 5:
+ return { breadCrumbs[activeFormStepIndex].content }
+ default:
+ return
+ }
+ }, [project, activeFormStepIndex, updateFormStep])
+
+
+ // ---- Effects ---- //
useEffect(() => {
- setFormSteps(
- formSteps.map(
- (step) => { return { ...step, active: step.key === formStepIndex } },
- ),
+ setBreadcrumbs(breadCrumbs.map((b, index) => {
+ return { ...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),
+ }
+ }),
)
- }, [formStepIndex])
+ }, [enableReview])
+ // ---- Render ----- //
return (
@@ -33,24 +114,22 @@ const DataLoadingWizardForm = ({ project }) => {
- {/* React Hook components are just functions under the hood. */}
- {/* Get the component property from the current step and call it to add it to the DOM. */}
- {formSteps[formStepIndex].component({ project })}
-
- {JSON.stringify(project, null, 2)}
+ {getFormStepComponent()}
setFormStepIndex(Math.min(formStepIndex + 1, formSteps.length - 1))}
- onBack={() => setFormStepIndex(Math.max(0, formStepIndex - 1))}
+ 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')}
/>
@@ -60,7 +139,7 @@ const DataLoadingWizardForm = ({ project }) => {
From 2d4f412416dd9c23717227c03583fc5d76d1e0d8 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Mon, 8 Nov 2021 17:11:11 +1100
Subject: [PATCH 19/43] Minor refactoring favouring positivity over negativity
in args
---
.../ui/FormStepButtons.jsx | 55 +++++++------------
1 file changed, 20 insertions(+), 35 deletions(-)
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/ui/FormStepButtons.jsx b/ui/pages/Project/components/DataLoadingWizardForm/ui/FormStepButtons.jsx
index bca91cb5d5..d8bd26fce4 100644
--- a/ui/pages/Project/components/DataLoadingWizardForm/ui/FormStepButtons.jsx
+++ b/ui/pages/Project/components/DataLoadingWizardForm/ui/FormStepButtons.jsx
@@ -8,55 +8,38 @@ const FormButtonGroup = styled.div`
justify-content: right;
`
-const FormStepButtons = ({ isLastStep, onNext, onBack, onSubmit, disabled, loading, size }) => {
- if (isLastStep) {
- return (
-
- Back
-
-
- Next
-
-
- Submit
-
-
- )
- }
+const FormStepButtons = ({ isLastStep, onNext, onBack, onSubmit, enableNext, enableSubmit, loading, size }) => {
+ const submitButton = (
+
+ Submit
+
+ )
return (
Back
Next
+ {
+ isLastStep ? submitButton : null
+ }
)
}
@@ -66,7 +49,8 @@ FormStepButtons.propTypes = {
onNext: PropTypes.func,
onBack: PropTypes.func,
onSubmit: PropTypes.func,
- disabled: PropTypes.bool,
+ enableNext: PropTypes.bool,
+ enableSubmit: PropTypes.bool,
loading: PropTypes.bool,
size: PropTypes.string,
}
@@ -76,7 +60,8 @@ FormStepButtons.defaultProps = {
onNext: () => {},
onBack: () => {},
onSubmit: () => {},
- disabled: false,
+ enableNext: false,
+ enableSubmit: false,
loading: false,
size: 'small',
}
From 58f3aeae127fec2dfdae08f72be6f8d65be901ef Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Thu, 27 Jan 2022 16:32:30 +1100
Subject: [PATCH 20/43] Renamed directory to `DataLoadingWizard`
---
.../DataLoadingWizard.jsx} | 54 ++++---
.../steps/FamilyMetadataUpload.jsx | 145 ++++++++++++++++++
.../steps/IndividualMetadataUpload.jsx | 0
.../steps/Review.jsx | 0
.../steps/SampleMapping.jsx | 0
.../DataLoadingWizard/steps/Welcome.jsx | 36 +++++
.../steps/index.js | 0
.../ui/Centered.jsx | 0
.../ui/FormSection.jsx | 0
.../ui/FormStepButtons.jsx | 3 +-
.../ui/index.js | 2 +
.../steps/FamilyMetadataUpload.jsx | 15 --
.../DataLoadingWizardForm/steps/Welcome.jsx | 43 ------
13 files changed, 216 insertions(+), 82 deletions(-)
rename ui/pages/Project/components/{DataLoadingWizardForm/DataLoadingWizardForm.jsx => DataLoadingWizard/DataLoadingWizard.jsx} (76%)
create mode 100644 ui/pages/Project/components/DataLoadingWizard/steps/FamilyMetadataUpload.jsx
rename ui/pages/Project/components/{DataLoadingWizardForm => DataLoadingWizard}/steps/IndividualMetadataUpload.jsx (100%)
rename ui/pages/Project/components/{DataLoadingWizardForm => DataLoadingWizard}/steps/Review.jsx (100%)
rename ui/pages/Project/components/{DataLoadingWizardForm => DataLoadingWizard}/steps/SampleMapping.jsx (100%)
create mode 100644 ui/pages/Project/components/DataLoadingWizard/steps/Welcome.jsx
rename ui/pages/Project/components/{DataLoadingWizardForm => DataLoadingWizard}/steps/index.js (100%)
rename ui/pages/Project/components/{DataLoadingWizardForm => DataLoadingWizard}/ui/Centered.jsx (100%)
rename ui/pages/Project/components/{DataLoadingWizardForm => DataLoadingWizard}/ui/FormSection.jsx (100%)
rename ui/pages/Project/components/{DataLoadingWizardForm => DataLoadingWizard}/ui/FormStepButtons.jsx (98%)
rename ui/pages/Project/components/{DataLoadingWizardForm => DataLoadingWizard}/ui/index.js (75%)
delete mode 100644 ui/pages/Project/components/DataLoadingWizardForm/steps/FamilyMetadataUpload.jsx
delete mode 100644 ui/pages/Project/components/DataLoadingWizardForm/steps/Welcome.jsx
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/DataLoadingWizardForm.jsx b/ui/pages/Project/components/DataLoadingWizard/DataLoadingWizard.jsx
similarity index 76%
rename from ui/pages/Project/components/DataLoadingWizardForm/DataLoadingWizardForm.jsx
rename to ui/pages/Project/components/DataLoadingWizard/DataLoadingWizard.jsx
index 61ddfc21f2..ec18354ac2 100644
--- a/ui/pages/Project/components/DataLoadingWizardForm/DataLoadingWizardForm.jsx
+++ b/ui/pages/Project/components/DataLoadingWizard/DataLoadingWizard.jsx
@@ -1,10 +1,16 @@
+/* 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 { Centered, FormSection, FormStepButtons } from './ui'
import { Welcome, FamilyMetadataUpload } from './steps'
import { Error404 } from '../../../../shared/components/page/Errors'
+import { getCurrentProject } from '../../selectors'
const BREADCRUMBS = [
{ key: 0, content: 'Welcome', link: true, active: true },
@@ -14,20 +20,17 @@ const BREADCRUMBS = [
{ key: 4, content: 'Review', link: true, active: false },
]
-
-const DataLoadingWizardForm = ({ project }) => {
+const BaseMultistepForm = ({ project }) => {
// ---- Debug ---- //
console.group('Project Object')
console.log(project)
console.groupEnd()
// ---- State ----- //
- const [breadCrumbs, setBreadcrumbs] = useState(BREADCRUMBS.map((b) => { return { ...b } }))
+ const [breadCrumbs, setBreadcrumbs] = useState(BREADCRUMBS.map(b => ({ ...b })))
const [activeFormStepIndex, setActiveFormStepIndex] = useState(0)
const [formSteps, setFormSteps] = useState(BREADCRUMBS.map(
- (_, index) => {
- return { formData: {}, isComplete: (index === 0) }
- },
+ (_, index) => ({ formData: {}, isComplete: (index === 0) }),
))
// ---- Callbacks ----- //
@@ -40,14 +43,12 @@ const DataLoadingWizardForm = ({ project }) => {
return { ...step }
},
))
- }, [])
-
- const enableReview = useCallback(() => {
- return formSteps
- .slice(0, formSteps.length - 1)
- .reduce((acc, step) => acc && step.isComplete, true)
}, [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()
@@ -61,10 +62,12 @@ const DataLoadingWizardForm = ({ project }) => {
case 0:
return
case 1:
- return updateFormStep(1, { formData, isComplete })}
- />
+ return (
+ updateFormStep(1, { formData, isComplete })}
+ />
+ )
case 2:
return { breadCrumbs[activeFormStepIndex].content }
case 3:
@@ -78,12 +81,9 @@ const DataLoadingWizardForm = ({ project }) => {
}
}, [project, activeFormStepIndex, updateFormStep])
-
// ---- Effects ---- //
useEffect(() => {
- setBreadcrumbs(breadCrumbs.map((b, index) => {
- return { ...b, active: (index === activeFormStepIndex) }
- }))
+ setBreadcrumbs(breadCrumbs.map((b, index) => ({ ...b, active: (index === activeFormStepIndex) })))
}, [activeFormStepIndex])
useEffect(() => {
@@ -147,8 +147,16 @@ const DataLoadingWizardForm = ({ project }) => {
)
}
-DataLoadingWizardForm.propTypes = {
- project: PropTypes.object,
+BaseMultistepForm.propTypes = {
+ project: PropTypes.object.isRequired,
}
-export default DataLoadingWizardForm
+const mapStateToProps = state => ({
+ project: getCurrentProject(state),
+})
+
+export const MultistepForm = connect(mapStateToProps)(BaseMultistepForm)
+
+const DataLoadingWizard = () =>
+
+export default DataLoadingWizard
diff --git a/ui/pages/Project/components/DataLoadingWizard/steps/FamilyMetadataUpload.jsx b/ui/pages/Project/components/DataLoadingWizard/steps/FamilyMetadataUpload.jsx
new file mode 100644
index 0000000000..41d3429ee1
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizard/steps/FamilyMetadataUpload.jsx
@@ -0,0 +1,145 @@
+import React, { useCallback, useState } from 'react'
+import PropTypes from 'prop-types'
+
+import { Form, Table, Icon, List, Message } from 'semantic-ui-react'
+import { ReadableText } from '../ui'
+import TemplateFile from '../templates/TemplateFile'
+import FamilyTemplateColumns from '../templates/Family'
+
+const TemplateDirectoryLink = () => (
+
+ here
+
+)
+
+const FamilyMetadataUpload = ({ project, onFormChange }) => {
+ const columns = FamilyTemplateColumns()
+
+ const [isLoading, setIsLoading] = useState(false)
+ const [error, setError] = useState(null)
+ const [rows, setRows] = useState([])
+ const [valid, setValid] = useState(null)
+
+ const parseFile = useCallback((event) => {
+ if (event.target.files[0]) {
+ setError(null)
+ setValid(null)
+ setRows([])
+
+ if (!event.target.files[0].name.match(/\.(tsv|csv)$/)) {
+ setError('Please upload a TSV or CSV file.')
+ setValid(false)
+ return
+ }
+
+ setIsLoading(true)
+ const parser = new TemplateFile(columns)
+ parser.parse(
+ event.target.files[0],
+ () => {
+ setError(null)
+ setIsLoading(false)
+ setValid(parser.valid)
+ setRows([...parser.rows])
+
+ console.debug(parser)
+ },
+ (e) => {
+ setError(e)
+ setIsLoading(false)
+ setValid(false)
+ setRows([])
+ },
+ )
+ }
+ }, [setIsLoading, setError, setRows])
+
+ return (
+ <>
+
+ In this section, you will provide all information relating to the families in your project. Please download
+ the families template from
+ {' '}
+
+ { '. ' }
+ Once you have filled in this template, upload it via this step and correct any validation errors
+ before proceeding.
+
+
+
+
+
+ {valid === false ? (
+
+ ) : null}
+
+
+
+
+
+ {columns.map(c => {`${c.key}${c.required ? ' *' : ''}`} )}
+ Comments
+
+
+
+ {error ?
+ (
+
+ {error ? error.toString() : 'No data loaded'}
+
+ ) :
+ rows.map(row => (
+
+
+ {row.valid ? : }
+
+ {row.columns.map(([columnDef, columnValue]) => (
+
+ {Array.isArray(columnValue.value) ? columnValue.value.join(', ') : columnValue.value}
+
+ ))}
+
+ {row.valid ? null : (
+
+ {
+ row.columns.map(([columnDef, columnValue]) => {
+ if (columnValue.valid) return null
+
+ const innerList = columnValue.errors.map((e, i) => (
+ {e}
+ ))
+ return (
+
+ {columnDef.key}
+ {innerList}
+
+ )
+ })
+ }
+
+ )}
+
+
+ )) }
+
+
+ >
+ )
+}
+
+FamilyMetadataUpload.propTypes = {
+ project: PropTypes.object.isRequired,
+ onFormChange: PropTypes.func.isRequired,
+}
+
+FamilyMetadataUpload.defaultProps = {}
+
+export default FamilyMetadataUpload
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/steps/IndividualMetadataUpload.jsx b/ui/pages/Project/components/DataLoadingWizard/steps/IndividualMetadataUpload.jsx
similarity index 100%
rename from ui/pages/Project/components/DataLoadingWizardForm/steps/IndividualMetadataUpload.jsx
rename to ui/pages/Project/components/DataLoadingWizard/steps/IndividualMetadataUpload.jsx
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/steps/Review.jsx b/ui/pages/Project/components/DataLoadingWizard/steps/Review.jsx
similarity index 100%
rename from ui/pages/Project/components/DataLoadingWizardForm/steps/Review.jsx
rename to ui/pages/Project/components/DataLoadingWizard/steps/Review.jsx
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/steps/SampleMapping.jsx b/ui/pages/Project/components/DataLoadingWizard/steps/SampleMapping.jsx
similarity index 100%
rename from ui/pages/Project/components/DataLoadingWizardForm/steps/SampleMapping.jsx
rename to ui/pages/Project/components/DataLoadingWizard/steps/SampleMapping.jsx
diff --git a/ui/pages/Project/components/DataLoadingWizard/steps/Welcome.jsx b/ui/pages/Project/components/DataLoadingWizard/steps/Welcome.jsx
new file mode 100644
index 0000000000..fd916c7a4f
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizard/steps/Welcome.jsx
@@ -0,0 +1,36 @@
+import React from 'react'
+import styled from 'styled-components'
+import { ReadableText } from '../ui'
+
+const WelcomeSection = styled.section`
+ margin: 1em 0;
+`
+
+const Welcome = () => (
+
+
+
+ Welcome to the seqr data loading wizard! This wizard will guide you through the process of uploading
+ the relevant individual template, family template and associated metadata files. Each step will validate
+ these files to ensure that the information relating individuals, families and samples is correctly formatted.
+
+
+
+
+ Introduction
+ Overview of what the collaborator needs to do goes here
+
+
+
+ Resources
+ Overview of required template and mapping formats
+
+
+
+ FAQ
+ Frequently asked questions and answers
+
+
+)
+
+export default Welcome
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/steps/index.js b/ui/pages/Project/components/DataLoadingWizard/steps/index.js
similarity index 100%
rename from ui/pages/Project/components/DataLoadingWizardForm/steps/index.js
rename to ui/pages/Project/components/DataLoadingWizard/steps/index.js
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/ui/Centered.jsx b/ui/pages/Project/components/DataLoadingWizard/ui/Centered.jsx
similarity index 100%
rename from ui/pages/Project/components/DataLoadingWizardForm/ui/Centered.jsx
rename to ui/pages/Project/components/DataLoadingWizard/ui/Centered.jsx
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/ui/FormSection.jsx b/ui/pages/Project/components/DataLoadingWizard/ui/FormSection.jsx
similarity index 100%
rename from ui/pages/Project/components/DataLoadingWizardForm/ui/FormSection.jsx
rename to ui/pages/Project/components/DataLoadingWizard/ui/FormSection.jsx
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/ui/FormStepButtons.jsx b/ui/pages/Project/components/DataLoadingWizard/ui/FormStepButtons.jsx
similarity index 98%
rename from ui/pages/Project/components/DataLoadingWizardForm/ui/FormStepButtons.jsx
rename to ui/pages/Project/components/DataLoadingWizard/ui/FormStepButtons.jsx
index d8bd26fce4..fec464d5ee 100644
--- a/ui/pages/Project/components/DataLoadingWizardForm/ui/FormStepButtons.jsx
+++ b/ui/pages/Project/components/DataLoadingWizard/ui/FormStepButtons.jsx
@@ -27,7 +27,8 @@ const FormStepButtons = ({ isLastStep, onNext, onBack, onSubmit, enableNext, ena
size={size}
onClick={onBack}
loading={loading}
- >Back
+ >
+ Back
{
- return
-}
-
-FamilyMetadataUpload.propTypes = {
- project: PropTypes.object.isRequired,
- onFormChange: PropTypes.func.isRequired,
-}
-
-export default FamilyMetadataUpload
diff --git a/ui/pages/Project/components/DataLoadingWizardForm/steps/Welcome.jsx b/ui/pages/Project/components/DataLoadingWizardForm/steps/Welcome.jsx
deleted file mode 100644
index 9b6abab997..0000000000
--- a/ui/pages/Project/components/DataLoadingWizardForm/steps/Welcome.jsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import React from 'react'
-import styled from 'styled-components'
-
-const WelcomeSection = styled.section`
- margin: 1em 0;
-`
-
-const ReadableText = styled.p`
- font-size: 16px;
- line-height: 1.6;
-`
-
-const Welcome = () => {
- return (
-
-
-
- Welcome to the seqr data loading wizard! This wizard will guide you through the process of uploading
- the relevant individual template, family template and associated metadata files. Each step will validate
- these files to ensure that the information relating individuals, families and samples is correctly formatted.
-
-
-
-
- Introduction
- Overview of what the collaborator needs to do goes here
-
-
-
- Resources
- Overview of required template and mapping formats
-
-
-
- FAQ
- Frequently asked questions and answers
-
-
- )
-}
-
-export default Welcome
-
From b9612190ff74df4a3be5a1294ea997fda4d6f8a0 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Thu, 27 Jan 2022 16:32:48 +1100
Subject: [PATCH 21/43] renamed component
---
ui/pages/Project/Project.jsx | 2 ++
1 file changed, 2 insertions(+)
diff --git a/ui/pages/Project/Project.jsx b/ui/pages/Project/Project.jsx
index f9e29ebac2..843037f6e2 100644
--- a/ui/pages/Project/Project.jsx
+++ b/ui/pages/Project/Project.jsx
@@ -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 {
@@ -45,6 +46,7 @@ class Project extends React.PureComponent {
+
)
From cb3f78b5cfd14f08f72b26337bdb410e4972511a Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Thu, 27 Jan 2022 16:33:08 +1100
Subject: [PATCH 22/43] Text with some breathing space
---
.../components/DataLoadingWizard/ui/ReadableText.jsx | 8 ++++++++
1 file changed, 8 insertions(+)
create mode 100644 ui/pages/Project/components/DataLoadingWizard/ui/ReadableText.jsx
diff --git a/ui/pages/Project/components/DataLoadingWizard/ui/ReadableText.jsx b/ui/pages/Project/components/DataLoadingWizard/ui/ReadableText.jsx
new file mode 100644
index 0000000000..992f01d07a
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizard/ui/ReadableText.jsx
@@ -0,0 +1,8 @@
+import styled from 'styled-components'
+
+const ReadableText = styled.p`
+ font-size: 16px;
+ line-height: 1.6;
+`
+
+export default ReadableText
From e6c0dea9c863908511c5fad9136ff4fdee535144 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Thu, 27 Jan 2022 16:33:26 +1100
Subject: [PATCH 23/43] Added papaparse for csv parsing
---
ui/package-lock.json | 11 +++++++++++
ui/package.json | 1 +
2 files changed, 12 insertions(+)
diff --git a/ui/package-lock.json b/ui/package-lock.json
index 36bd9317ca..31785d6935 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -24,6 +24,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",
@@ -15205,6 +15206,11 @@
"node": ">=4"
}
},
+ "node_modules/papaparse": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.1.tgz",
+ "integrity": "sha512-Dbt2yjLJrCwH2sRqKFFJaN5XgIASO9YOFeFP8rIBRG2Ain8mqk5r1M6DkfvqEVozVcz3r3HaUGw253hA1nLIcA=="
+ },
"node_modules/param-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",
@@ -31938,6 +31944,11 @@
"version": "1.0.0",
"dev": true
},
+ "papaparse": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.1.tgz",
+ "integrity": "sha512-Dbt2yjLJrCwH2sRqKFFJaN5XgIASO9YOFeFP8rIBRG2Ain8mqk5r1M6DkfvqEVozVcz3r3HaUGw253hA1nLIcA=="
+ },
"param-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",
diff --git a/ui/package.json b/ui/package.json
index 14b4380393..a53b471bdd 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -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",
From 8e9c40d38ac77b3de8c5ddf09f48ec6c53b725b1 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Thu, 27 Jan 2022 16:34:33 +1100
Subject: [PATCH 24/43] Metadata template file definitions
---
.../DataLoadingWizard/templates/Family.js | 41 ++++
.../DataLoadingWizard/templates/Individual.js | 89 ++++++++
.../DataLoadingWizard/templates/Metadata.js | 196 ++++++++++++++++++
3 files changed, 326 insertions(+)
create mode 100644 ui/pages/Project/components/DataLoadingWizard/templates/Family.js
create mode 100644 ui/pages/Project/components/DataLoadingWizard/templates/Individual.js
create mode 100644 ui/pages/Project/components/DataLoadingWizard/templates/Metadata.js
diff --git a/ui/pages/Project/components/DataLoadingWizard/templates/Family.js b/ui/pages/Project/components/DataLoadingWizard/templates/Family.js
new file mode 100644
index 0000000000..4deb83239c
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizard/templates/Family.js
@@ -0,0 +1,41 @@
+import TemplateColumn from './TemplateColumn'
+import { parseString, parseStringArray } from './parsers'
+
+const FamilyTemplateColumns = () => ([
+ new TemplateColumn({
+ id: 'familyId',
+ key: 'Family ID',
+ index: 0,
+ required: true,
+ parser: parseString,
+ validators: [
+ v => ((!v) ? 'A family ID must be present.' : null),
+ ],
+ }),
+ new TemplateColumn({
+ id: 'displayName',
+ key: 'Display Name',
+ index: 1,
+ required: false,
+ parser: parseString,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'description',
+ key: 'Description',
+ index: 2,
+ required: false,
+ parser: parseString,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'codedPhenotype',
+ key: 'Coded Phenotype',
+ index: 3,
+ required: false,
+ parser: parseStringArray,
+ validators: [],
+ }),
+])
+
+export default FamilyTemplateColumns
diff --git a/ui/pages/Project/components/DataLoadingWizard/templates/Individual.js b/ui/pages/Project/components/DataLoadingWizard/templates/Individual.js
new file mode 100644
index 0000000000..231ef76459
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizard/templates/Individual.js
@@ -0,0 +1,89 @@
+import TemplateColumn from './TemplateColumn'
+import { parseString } from './parsers'
+
+const IndividualTemplateColumns = () => ([
+ new TemplateColumn({
+ id: 'familyId',
+ key: 'Family ID',
+ index: 0,
+ required: true,
+ parser: parseString,
+ validators: [
+ v => ((!v) ? 'A family ID must be present.' : null),
+ ],
+ }),
+ new TemplateColumn({
+ id: 'individualId',
+ key: 'Individual ID',
+ index: 1,
+ required: true,
+ parser: parseString,
+ validators: [
+ v => ((!v) ? 'An individual ID must be present.' : null),
+ ],
+ }),
+ new TemplateColumn({
+ id: 'paternalId',
+ key: 'Paternal ID',
+ index: 2,
+ required: false,
+ parser: parseString,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'maternalId',
+ key: 'Maternal ID',
+ index: 3,
+ required: false,
+ parser: parseString,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'sex',
+ key: 'Sex',
+ index: 4,
+ required: true,
+ parser: parseString,
+ validators: [
+ (v) => {
+ if (!v) {
+ return (
+ 'A character representing sex must be present. ' +
+ "The character '1' represents male, '2' represents female, " +
+ "and any other character besides '1' and '2' represents unknown."
+ )
+ }
+ return null
+ },
+ ],
+ }),
+ new TemplateColumn({
+ id: 'affectedStatus',
+ key: 'Affected Status',
+ index: 5,
+ required: true,
+ parser: parseString,
+ validators: [
+ (v) => {
+ if (!v) {
+ return (
+ 'A character representing phenotype affect status must be present. ' +
+ "The characters '0' and '-9' represent missing, '1' represents unaffected and '2' represents affected. " +
+ 'Any other characters are interpreted as string phenotype values.'
+ )
+ }
+ return null
+ },
+ ],
+ }),
+ new TemplateColumn({
+ id: 'notes',
+ key: 'Notes',
+ index: 6,
+ required: false,
+ parser: parseString,
+ validators: [],
+ }),
+])
+
+export default IndividualTemplateColumns
diff --git a/ui/pages/Project/components/DataLoadingWizard/templates/Metadata.js b/ui/pages/Project/components/DataLoadingWizard/templates/Metadata.js
new file mode 100644
index 0000000000..546bec79ca
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizard/templates/Metadata.js
@@ -0,0 +1,196 @@
+import TemplateColumn from './TemplateColumn'
+import { parseString, parseStringArray, parseBoolean, parseYear } from './parsers'
+import { validateHpoTerms, validateModeOfInheritanceTerms, validateOmimTerms, validateOnsetCategory } from './validators'
+
+const MetadataTemplateColumns = () => ([
+ new TemplateColumn({
+ id: 'familyId',
+ key: 'Family ID',
+ index: 0,
+ required: true,
+ parser: parseString,
+ validators: [
+ v => ((!v) ? 'A family ID must be present.' : null),
+ ],
+ }),
+ new TemplateColumn({
+ id: 'individualId',
+ key: 'Individual ID',
+ index: 1,
+ required: true,
+ parser: parseString,
+ validators: [
+ v => ((!v) ? 'An individual ID must be present.' : null),
+ ],
+ }),
+ new TemplateColumn({
+ id: 'hpoTermsPresent',
+ key: 'HPO Terms (present)',
+ index: 2,
+ required: false,
+ parser: parseStringArray,
+ validators: [validateHpoTerms],
+ }),
+ new TemplateColumn({
+ id: 'hpoTermsAbsent',
+ key: 'HPO Terms (absent)',
+ index: 3,
+ required: false,
+ parser: parseStringArray,
+ validators: [validateHpoTerms],
+ }),
+ new TemplateColumn({
+ id: 'birthYear',
+ key: 'Birth Year',
+ index: 4,
+ required: false,
+ parser: parseYear,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'deathYear',
+ key: 'Death Year',
+ index: 5,
+ required: false,
+ parser: parseYear,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'ageOfOnset',
+ key: 'Age of Onset',
+ index: 6,
+ required: false,
+ parser: parseString,
+ validators: [validateOnsetCategory],
+ }),
+ new TemplateColumn({
+ id: 'individualNotes',
+ key: 'Individual Notes',
+ index: 7,
+ required: false,
+ parser: parseString,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'consanguinity',
+ key: 'Consanguinity',
+ index: 8,
+ required: false,
+ parser: parseBoolean,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'otherAffectedRelatives',
+ key: 'Other Affected Relatives',
+ index: 9,
+ required: false,
+ parser: parseBoolean,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'expectedInheritanceMode',
+ key: 'Expected Mode of Inheritance',
+ index: 10,
+ required: false,
+ parser: parseStringArray,
+ validators: [validateModeOfInheritanceTerms],
+ }),
+ new TemplateColumn({
+ id: 'fertilityMedications',
+ key: 'Fertility medications',
+ index: 11,
+ required: false,
+ parser: parseBoolean,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'intrauterineInsemination',
+ key: 'Intrauterine insemination',
+ index: 12,
+ required: false,
+ parser: parseBoolean,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'inVitroFertilization',
+ key: 'In vitro fertilization',
+ index: 13,
+ required: false,
+ parser: parseBoolean,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'intraCytoplasmicSpermInjection',
+ key: 'Intra-cytoplasmic sperm injection',
+ index: 14,
+ required: false,
+ parser: parseBoolean,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'gestationalSurrogacy',
+ key: 'Gestational surrogacy',
+ index: 15,
+ required: false,
+ parser: parseBoolean,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'donorEgg',
+ key: 'Donor egg',
+ index: 16,
+ required: false,
+ parser: parseBoolean,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'donorSperm',
+ key: 'Donor sperm',
+ index: 17,
+ required: false,
+ parser: parseBoolean,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'maternalAncestry',
+ key: 'Maternal Ancestry',
+ index: 18,
+ required: false,
+ parser: parseStringArray,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'paternalAncestry',
+ key: 'Paternal Ancestry',
+ index: 19,
+ required: false,
+ parser: parseStringArray,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'preDiscoveryOmimDisorders',
+ key: 'Pre-discovery OMIM disorders',
+ index: 20,
+ required: false,
+ parser: parseStringArray,
+ validators: [validateOmimTerms],
+ }),
+ new TemplateColumn({
+ id: 'previouslyTestedGenes',
+ key: 'Previously Tested Genes',
+ index: 21,
+ required: false,
+ parser: parseStringArray,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'candidateGenes',
+ key: 'Candidate Genes',
+ index: 22,
+ required: false,
+ parser: parseStringArray,
+ validators: [],
+ }),
+])
+
+export default MetadataTemplateColumns
From 419942bf5fa4e1ad102ec1c5f0ecf3b078927069 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Thu, 27 Jan 2022 16:34:46 +1100
Subject: [PATCH 25/43] parsers for file fields
---
.../DataLoadingWizard/templates/parsers.js | 83 +++++++++++++++++++
1 file changed, 83 insertions(+)
create mode 100644 ui/pages/Project/components/DataLoadingWizard/templates/parsers.js
diff --git a/ui/pages/Project/components/DataLoadingWizard/templates/parsers.js b/ui/pages/Project/components/DataLoadingWizard/templates/parsers.js
new file mode 100644
index 0000000000..935b50b7d8
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizard/templates/parsers.js
@@ -0,0 +1,83 @@
+/**
+ * @param {?string} value
+ * @returns {string|null}
+ */
+export const parseString = (value) => {
+ if (value?.toString()?.trim()) {
+ return value.toString().trim()
+ }
+ return null
+}
+
+/**
+ * @param {?string} value
+ * @param {string} separator
+ * @returns {string[]}
+ */
+export const parseStringArray = (value, separator = ',') => {
+ if (!value) return []
+
+ if (value.toString().trim()) {
+ return Array.from(
+ new Set(
+ value
+ .toString()
+ .trim()
+ .split(separator)
+ .map(p => p?.trim() || null)
+ .filter(p => p?.trim() && p.trim().length > 0),
+ ),
+ )
+ }
+ return []
+}
+
+/**
+ * @param {?string} value
+ * @returns {number|null}
+ */
+export const parseNumber = (value) => {
+ const n = Number(value?.toString()?.trim())
+ if (Number.isFinite(n)) {
+ return n
+ }
+ return null
+}
+
+/**
+ * @param {?string} value
+ * @returns {boolean|null}
+ */
+export const parseBoolean = (value) => {
+ const b = value?.toString()?.trim()?.toLowerCase()
+ if (value) {
+ switch (b) {
+ case 'true': return true
+ case 'false': return false
+ default: return null
+ }
+ }
+ return null
+}
+
+/**
+ * @param {?string} value
+ * @returns {string|null}
+ */
+export const parseYear = (value) => {
+ const d = value?.toString()?.trim()
+ const match = d.match(/(?\d{4})/)
+
+ if (match?.groups?.year) {
+ return match.groups.year
+ }
+ return null
+}
+
+export default {
+ parseString,
+ parseStringArray,
+ parseNumber,
+ parseBoolean,
+ parseYear,
+}
From 8c18d83deb0301cd9a942665746088c8449a748a Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Thu, 27 Jan 2022 16:35:08 +1100
Subject: [PATCH 26/43] validators for file columns
---
.../DataLoadingWizard/templates/validators.js | 126 ++++++++++++++++++
1 file changed, 126 insertions(+)
create mode 100644 ui/pages/Project/components/DataLoadingWizard/templates/validators.js
diff --git a/ui/pages/Project/components/DataLoadingWizard/templates/validators.js b/ui/pages/Project/components/DataLoadingWizard/templates/validators.js
new file mode 100644
index 0000000000..93dc436f3e
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizard/templates/validators.js
@@ -0,0 +1,126 @@
+import * as _ from 'lodash'
+import { INHERITANCE_MODE_OPTIONS, ONSET_AGE_OPTIONS } from '../../../constants'
+
+/**
+ * @param {*[]} collection
+ * @param {?string} message
+ * @returns {string[]}
+ */
+export const validateUnique = (collection, message = null) => {
+ const counts = _.countBy(collection)
+
+ const errors = _.zip(collection, Object.values(counts)).map(([key, count]) => {
+ if (count > 1) {
+ return message || `Value '${key}' is not unique. Counted ${count} times.`
+ }
+ return null
+ })
+
+ return errors.filter(e => e != null)
+}
+
+/**
+ * @param {?string} value
+ * @param {?string} message
+ * @returns {string|null}
+ */
+export const validateHpoTerm = (value, message = null) => {
+ if (!value) return null
+
+ if (!value.toString().toUpperCase().match(/HP:\d{7}/)) {
+ return message || `Value '${value} is not a valid HPO term.`
+ }
+ return null
+}
+
+/**
+ * @param {string[]} collection
+ * @param {?string} message
+ * @returns {string[]}
+ */
+export const validateHpoTerms = (collection, message = null) => (
+ collection
+ .map(v => validateHpoTerm(v, message))
+ .filter(e => e != null)
+)
+
+/**
+ * @param {?string} value
+ * @param {?string} message
+ * @returns {string|null}
+ */
+export const validateOnsetCategory = (value, message = null) => {
+ if (!value) return null
+
+ const categories = ONSET_AGE_OPTIONS.map(o => o.text)
+
+ if (!categories.map(c => c.toLowerCase()).includes(value.toLowerCase())) {
+ return message || `'${value}' must be one of ${categories.join(', ')}`
+ }
+
+ return null
+}
+
+/**
+ * @param {?string} value
+ * @param {?string} message
+ * @returns {string|null}
+ */
+export const validateModeOfInheritanceTerm = (value, message = null) => {
+ if (!value) return null
+
+ const categories = INHERITANCE_MODE_OPTIONS.map(o => o.text)
+
+ if (!categories.map(c => c.toLowerCase()).includes(value.toLowerCase())) {
+ return message || `'${value}' must be one of ${categories.join(', ')}`
+ }
+
+ return null
+}
+
+/**
+ * @param {string[]} collection
+ * @param {?string} message
+ * @returns {string[]}
+ */
+export const validateModeOfInheritanceTerms = (collection, message) => (
+ collection
+ .map(v => validateModeOfInheritanceTerm(v, message))
+ .filter(e => e != null)
+)
+
+/**
+ * @param {?string} value
+ * @param {?string} message
+ * @returns {string|null}
+ */
+export const validateOmimTerm = (value, message = null) => {
+ if (!value) return null
+
+ if (!value.toString().toUpperCase().match(/^OMIM:\d+$/)) {
+ return message || `Value '${value} is not a valid OMIM term.`
+ }
+ return null
+}
+
+/**
+ * @param {string[]} collection
+ * @param {?string} message
+ * @returns {string[]}
+ */
+export const validateOmimTerms = (collection, message) => (
+ collection
+ .map(v => validateOmimTerm(v, message))
+ .filter(e => e != null)
+)
+
+export default {
+ validateUnique,
+ validateHpoTerm,
+ validateHpoTerms,
+ validateOnsetCategory,
+ validateModeOfInheritanceTerm,
+ validateModeOfInheritanceTerms,
+ validateOmimTerm,
+ validateOmimTerms,
+}
From 3d59d78cac3349397419984e7928ac4760dcfc20 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Thu, 27 Jan 2022 16:36:39 +1100
Subject: [PATCH 27/43] Specification for how to parse and validate a column in
a CSV/TSV file
---
.../templates/TemplateColumn.js | 39 +++++++++++++++++++
1 file changed, 39 insertions(+)
create mode 100644 ui/pages/Project/components/DataLoadingWizard/templates/TemplateColumn.js
diff --git a/ui/pages/Project/components/DataLoadingWizard/templates/TemplateColumn.js b/ui/pages/Project/components/DataLoadingWizard/templates/TemplateColumn.js
new file mode 100644
index 0000000000..4de058e3c6
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizard/templates/TemplateColumn.js
@@ -0,0 +1,39 @@
+class TemplateColumn {
+
+ constructor({ id, key, index, required = false, parser = x => x, validators = [] }) {
+ this.id = id
+ this.key = key
+ this.index = index
+ this.required = required || false
+ this.parser = parser || (x => x)
+ this.validators = validators || []
+ }
+
+ /**
+ * @param {[]|{}} row
+ * @returns {{value: *, valid: boolean, errors: string[]}}
+ */
+ parse(row) {
+ let result = null
+ if (Array.isArray(row)) {
+ result = this.parser(row[this.index])
+ } else if (row.constructor.name === 'Object') {
+ result = this.parser(row[this.key])
+ } else {
+ throw new Error(
+ `TemplateColumn 'parse' function expects an Array or an Object, but received ${row.constructor.name}`,
+ )
+ }
+
+ const validationErrors = this.validators.map(validator => validator(result)).filter(e => e != null)
+
+ return {
+ value: result,
+ errors: validationErrors,
+ valid: validationErrors.length === 0,
+ }
+ }
+
+}
+
+export default TemplateColumn
From 616f668af030cbd510ea3426bcd7267928ac695e Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Thu, 27 Jan 2022 16:37:00 +1100
Subject: [PATCH 28/43] Specification for how to parse a template file with
many columns
---
.../templates/TemplateFile.js | 78 +++++++++++++++++++
1 file changed, 78 insertions(+)
create mode 100644 ui/pages/Project/components/DataLoadingWizard/templates/TemplateFile.js
diff --git a/ui/pages/Project/components/DataLoadingWizard/templates/TemplateFile.js b/ui/pages/Project/components/DataLoadingWizard/templates/TemplateFile.js
new file mode 100644
index 0000000000..e7cb6ae48f
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizard/templates/TemplateFile.js
@@ -0,0 +1,78 @@
+import * as Papa from 'papaparse'
+
+class TemplateFile {
+
+ /**
+ * @param {TemplateColumn[]} columns
+ */
+ constructor(columns) {
+ /**
+ * @type {TemplateColumn[]}
+ */
+ this.columns = [...columns]
+
+ /**
+ * @type {{valid: boolean, columns: [TemplateColumn, {value: *, valid: boolean, errors: string[]}][]}[]}
+ */
+ this.rows = []
+
+ /**
+ * @type {?boolean}
+ */
+ this.valid = null
+
+ /**
+ * @type {{type: string, code: string, message: string, row: number}[]}
+ */
+ this.fileErrors = []
+
+ /**
+ * @type {{delimiter: string, linebreak: string, aborted: boolean, fields: ?string[], truncated: boolean}}
+ */
+ this.fileMetadata = {}
+ }
+
+ parse(file, onComplete, onError) {
+ this.rows = []
+ this.valid = null
+ this.fileErrors = []
+ this.fileMetadata = {}
+
+ Papa.parse(
+ file,
+ {
+ header: true,
+ skipEmptyLines: true,
+ complete: (results) => {
+ this.fileMetadata = { ...results.meta }
+ this.fileErrors = [...results.errors]
+
+ if (this.fileErrors.length === 0) {
+ this.rows = results.data.map((row) => {
+ const columns = this.columns.map(c => [c, c.parse(row)])
+ const valid = columns.reduce((acc, column) => acc && column[1].valid, true)
+
+ return { valid, columns }
+ })
+
+ this.valid = this.rows.reduce((acc, row) => acc && row.valid, true)
+ } else {
+ this.valid = false
+ }
+
+ onComplete(results)
+ },
+ error: (e) => {
+ this.valid = false
+ this.fileErrors = [{ type: e.name, code: '500', message: e.message, row: -1 }]
+ onError(e)
+ },
+ },
+ )
+
+ return this
+ }
+
+}
+
+export default TemplateFile
From 4700a0f6aef9aa7c380118e5e3869669be7e6000 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Fri, 28 Jan 2022 19:26:32 +1100
Subject: [PATCH 29/43] Updated error messages
---
.../components/DataLoadingWizard/templates/validators.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/ui/pages/Project/components/DataLoadingWizard/templates/validators.js b/ui/pages/Project/components/DataLoadingWizard/templates/validators.js
index 93dc436f3e..f67bf8d3f1 100644
--- a/ui/pages/Project/components/DataLoadingWizard/templates/validators.js
+++ b/ui/pages/Project/components/DataLoadingWizard/templates/validators.js
@@ -11,7 +11,7 @@ export const validateUnique = (collection, message = null) => {
const errors = _.zip(collection, Object.values(counts)).map(([key, count]) => {
if (count > 1) {
- return message || `Value '${key}' is not unique. Counted ${count} times.`
+ return message || `'${key}' is not unique, and was counted ${count} times`
}
return null
})
@@ -28,7 +28,7 @@ export const validateHpoTerm = (value, message = null) => {
if (!value) return null
if (!value.toString().toUpperCase().match(/HP:\d{7}/)) {
- return message || `Value '${value} is not a valid HPO term.`
+ return message || `'${value}' is not a valid HPO term`
}
return null
}
@@ -98,7 +98,7 @@ export const validateOmimTerm = (value, message = null) => {
if (!value) return null
if (!value.toString().toUpperCase().match(/^OMIM:\d+$/)) {
- return message || `Value '${value} is not a valid OMIM term.`
+ return message || `'${value}' is not a valid OMIM term`
}
return null
}
From 917c5539e7e4b783a88dfcaa866e8794f6cbc699 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Fri, 28 Jan 2022 19:26:59 +1100
Subject: [PATCH 30/43] Renamed to `TemplateUpload`
---
.../steps/FamilyMetadataUpload.jsx | 145 ------------
.../steps/TemplateUpload.jsx | 217 ++++++++++++++++++
2 files changed, 217 insertions(+), 145 deletions(-)
delete mode 100644 ui/pages/Project/components/DataLoadingWizard/steps/FamilyMetadataUpload.jsx
create mode 100644 ui/pages/Project/components/DataLoadingWizard/steps/TemplateUpload.jsx
diff --git a/ui/pages/Project/components/DataLoadingWizard/steps/FamilyMetadataUpload.jsx b/ui/pages/Project/components/DataLoadingWizard/steps/FamilyMetadataUpload.jsx
deleted file mode 100644
index 41d3429ee1..0000000000
--- a/ui/pages/Project/components/DataLoadingWizard/steps/FamilyMetadataUpload.jsx
+++ /dev/null
@@ -1,145 +0,0 @@
-import React, { useCallback, useState } from 'react'
-import PropTypes from 'prop-types'
-
-import { Form, Table, Icon, List, Message } from 'semantic-ui-react'
-import { ReadableText } from '../ui'
-import TemplateFile from '../templates/TemplateFile'
-import FamilyTemplateColumns from '../templates/Family'
-
-const TemplateDirectoryLink = () => (
-
- here
-
-)
-
-const FamilyMetadataUpload = ({ project, onFormChange }) => {
- const columns = FamilyTemplateColumns()
-
- const [isLoading, setIsLoading] = useState(false)
- const [error, setError] = useState(null)
- const [rows, setRows] = useState([])
- const [valid, setValid] = useState(null)
-
- const parseFile = useCallback((event) => {
- if (event.target.files[0]) {
- setError(null)
- setValid(null)
- setRows([])
-
- if (!event.target.files[0].name.match(/\.(tsv|csv)$/)) {
- setError('Please upload a TSV or CSV file.')
- setValid(false)
- return
- }
-
- setIsLoading(true)
- const parser = new TemplateFile(columns)
- parser.parse(
- event.target.files[0],
- () => {
- setError(null)
- setIsLoading(false)
- setValid(parser.valid)
- setRows([...parser.rows])
-
- console.debug(parser)
- },
- (e) => {
- setError(e)
- setIsLoading(false)
- setValid(false)
- setRows([])
- },
- )
- }
- }, [setIsLoading, setError, setRows])
-
- return (
- <>
-
- In this section, you will provide all information relating to the families in your project. Please download
- the families template from
- {' '}
-
- { '. ' }
- Once you have filled in this template, upload it via this step and correct any validation errors
- before proceeding.
-
-
-
-
-
- {valid === false ? (
-
- ) : null}
-
-
-
-
-
- {columns.map(c => {`${c.key}${c.required ? ' *' : ''}`} )}
- Comments
-
-
-
- {error ?
- (
-
- {error ? error.toString() : 'No data loaded'}
-
- ) :
- rows.map(row => (
-
-
- {row.valid ? : }
-
- {row.columns.map(([columnDef, columnValue]) => (
-
- {Array.isArray(columnValue.value) ? columnValue.value.join(', ') : columnValue.value}
-
- ))}
-
- {row.valid ? null : (
-
- {
- row.columns.map(([columnDef, columnValue]) => {
- if (columnValue.valid) return null
-
- const innerList = columnValue.errors.map((e, i) => (
- {e}
- ))
- return (
-
- {columnDef.key}
- {innerList}
-
- )
- })
- }
-
- )}
-
-
- )) }
-
-
- >
- )
-}
-
-FamilyMetadataUpload.propTypes = {
- project: PropTypes.object.isRequired,
- onFormChange: PropTypes.func.isRequired,
-}
-
-FamilyMetadataUpload.defaultProps = {}
-
-export default FamilyMetadataUpload
diff --git a/ui/pages/Project/components/DataLoadingWizard/steps/TemplateUpload.jsx b/ui/pages/Project/components/DataLoadingWizard/steps/TemplateUpload.jsx
new file mode 100644
index 0000000000..418e10aede
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizard/steps/TemplateUpload.jsx
@@ -0,0 +1,217 @@
+import React, { useCallback, useState } from 'react'
+import PropTypes from 'prop-types'
+import { capitalize, isBoolean, isEqual } from 'lodash'
+
+import { Form, Table, Icon, List, Message } from 'semantic-ui-react'
+import styled from 'styled-components'
+import { ReadableText } from '../ui'
+import TemplateFile from '../templates/TemplateFile'
+
+const ScrollTable = styled.div`
+ margin-top: 1rem;
+ overflow-x: scroll;
+ overflow-y: hidden;
+`
+
+const TemplateDirectoryLink = () => (
+
+ here
+
+)
+
+const renderCellContent = (value) => {
+ if (Array.isArray(value)) {
+ return value.length ? {value.map(v => {v} )}
: '-'
+ }
+ if (isBoolean(value)) {
+ return capitalize(value.toString())
+ }
+ return value == null ? '-' : value.toString()
+}
+
+const ERROR_LIST_STYLE = { width: 250 }
+
+/**
+ * @param {TemplateRow} row
+ * @returns {JSX.Element}
+ */
+const renderRowErrorList = (row) => {
+ if (row.valid) return null
+
+ return (
+
+ {
+ row.columns.map(({ data, definition }) => {
+ if (data.valid) return null
+
+ const errorList = data.errors.map(e => {e} )
+ return (
+
+ {definition.key}
+ {errorList}
+
+ )
+ })
+ }
+
+ )
+}
+
+const TemplateUpload = ({ project, parser, onFormChange }) => {
+ const [isLoading, setIsLoading] = useState(false)
+ const [errors, setErrors] = useState(/** @type {string[]|JSXElement[]} */ [])
+ const [valid, setValid] = useState(true)
+ const [rows, setRows] = useState(/** @type {TemplateRow[]} */ [])
+
+ const parseFile = useCallback((event) => {
+ if (event.target.files[0]) {
+ if (!event.target.files[0].name.match(/\.(tsv|csv)$/)) {
+ setErrors(['Please upload a TSV or CSV file.'])
+ setValid(false)
+ setRows([])
+ return
+ }
+
+ setIsLoading(true)
+ parser.parse(
+ {
+ file: event.target.files[0],
+ onComplete: ((result) => {
+ console.debug(result)
+
+ setIsLoading(false)
+
+ // Copy array before sorting since sort function has side effects and modifies the original array
+ const columnMismatch = !isEqual([...result.header].sort(), parser.columns.map(c => c.key).sort())
+
+ if (columnMismatch) {
+ setErrors([
+ (
+
+
Template file should have the following header columns:
+
{parser.columns.map(c => {c.key} )}
+
However, your file contains the header columns:
+
{result.header.map(f => {f} )}
+
+ ),
+ ])
+ setValid(false)
+ setRows([])
+
+ return
+ }
+
+ setErrors(result.errors)
+ setValid(result.valid)
+ setRows(result.rows)
+ }),
+ onError: ((e) => {
+ setErrors([e.message])
+ setIsLoading(false)
+ setValid(false)
+ setRows([])
+ }),
+ },
+ )
+ }
+ }, [parser, setIsLoading, setErrors, setRows, setValid])
+
+ const renderTableBody = useCallback(() => {
+ if (!valid && !rows.length) {
+ return (
+
+ Data could not be loaded. See errors for help.
+
+ )
+ }
+
+ // Render rows if we have them even if the parsing result is not valid, so we can display the errors
+ // to the user.
+ if (rows.length) {
+ return rows.map(row => (
+
+
+ {row.valid ? : }
+
+
+ {renderRowErrorList(row)}
+
+ {
+ row.columns.map(({ data, definition }) => (
+
+ {renderCellContent(data.value)}
+
+ ))
+ }
+
+ ))
+ }
+
+ return (
+
+ No data to display
+
+ )
+ }, [rows, valid, errors])
+
+ return (
+ <>
+
+ In this section, you will provide all information relating to the families in your project. Please download
+ the families template from
+ {' '}
+
+ { '. ' }
+ Once you have filled in this template, upload it via this step and correct any validation errors
+ before proceeding.
+
+
+
+
+
+ {
+ !valid ? (
+
+ ) : null
+ }
+
+
+
+
+
+ Status
+
+ {
+ parser
+ .columns
+ .map(c => {`${c.key}${c.required ? ' *' : ''}`} )
+ }
+
+
+
+ {renderTableBody()}
+
+
+
+ >
+ )
+}
+
+TemplateUpload.propTypes = {
+ project: PropTypes.object.isRequired,
+ parser: PropTypes.instanceOf(TemplateFile).isRequired,
+ onFormChange: PropTypes.func.isRequired,
+}
+
+TemplateUpload.defaultProps = {}
+
+export default TemplateUpload
From 171924af89dcce66608e762bcecceed9d4708dbc Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Fri, 28 Jan 2022 19:27:43 +1100
Subject: [PATCH 31/43] Fixed a bug where errors returned as a nested list
---
.../DataLoadingWizard/templates/TemplateColumn.js | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/ui/pages/Project/components/DataLoadingWizard/templates/TemplateColumn.js b/ui/pages/Project/components/DataLoadingWizard/templates/TemplateColumn.js
index 4de058e3c6..b10701d723 100644
--- a/ui/pages/Project/components/DataLoadingWizard/templates/TemplateColumn.js
+++ b/ui/pages/Project/components/DataLoadingWizard/templates/TemplateColumn.js
@@ -10,7 +10,7 @@ class TemplateColumn {
}
/**
- * @param {[]|{}} row
+ * @param {*[]|{}} row
* @returns {{value: *, valid: boolean, errors: string[]}}
*/
parse(row) {
@@ -25,7 +25,9 @@ class TemplateColumn {
)
}
- const validationErrors = this.validators.map(validator => validator(result)).filter(e => e != null)
+ // Some validators may return a list of errors if they validate a field which is parsed into an array, for example
+ // a comma-delimited list of string values. Use flatMap to flatten all validation errors into a single array.
+ const validationErrors = this.validators.flatMap(validator => validator(result)).filter(e => e != null)
return {
value: result,
From 1a9f837c3874034d5507993ef81cad0bd7a79f93 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Fri, 28 Jan 2022 19:27:54 +1100
Subject: [PATCH 32/43] Renamed import
---
ui/pages/Project/components/DataLoadingWizard/steps/index.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/ui/pages/Project/components/DataLoadingWizard/steps/index.js b/ui/pages/Project/components/DataLoadingWizard/steps/index.js
index 91bccea233..c2fb5f7051 100644
--- a/ui/pages/Project/components/DataLoadingWizard/steps/index.js
+++ b/ui/pages/Project/components/DataLoadingWizard/steps/index.js
@@ -1,7 +1,7 @@
import Welcome from './Welcome'
-import FamilyMetadataUpload from './FamilyMetadataUpload'
+import TemplateUpload from './TemplateUpload'
export {
Welcome,
- FamilyMetadataUpload,
+ TemplateUpload,
}
From 5da41307c28700f9f462f79c6c9fb8fc39d5b4cd Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Thu, 3 Feb 2022 14:56:27 +1100
Subject: [PATCH 33/43] New styled components with props
---
.../DataLoadingWizard/ui/ReadableText.jsx | 15 +++++++++--
.../DataLoadingWizard/ui/Scrollable.jsx | 19 +++++++++++++
.../DataLoadingWizard/ui/WithSpace.jsx | 27 +++++++++++++++++++
3 files changed, 59 insertions(+), 2 deletions(-)
create mode 100644 ui/pages/Project/components/DataLoadingWizard/ui/Scrollable.jsx
create mode 100644 ui/pages/Project/components/DataLoadingWizard/ui/WithSpace.jsx
diff --git a/ui/pages/Project/components/DataLoadingWizard/ui/ReadableText.jsx b/ui/pages/Project/components/DataLoadingWizard/ui/ReadableText.jsx
index 992f01d07a..175c5552f4 100644
--- a/ui/pages/Project/components/DataLoadingWizard/ui/ReadableText.jsx
+++ b/ui/pages/Project/components/DataLoadingWizard/ui/ReadableText.jsx
@@ -1,8 +1,19 @@
import styled from 'styled-components'
+import PropTypes from 'prop-types'
const ReadableText = styled.p`
- font-size: 16px;
- line-height: 1.6;
+ font-size: ${props => props.fontSize};
+ line-height: ${props => props.lineHeight};
`
+ReadableText.propTypes = {
+ fontSize: PropTypes.string,
+ lineHeight: PropTypes.number,
+}
+
+ReadableText.defaultProps = {
+ fontSize: '16px',
+ lineHeight: 1.6,
+}
+
export default ReadableText
diff --git a/ui/pages/Project/components/DataLoadingWizard/ui/Scrollable.jsx b/ui/pages/Project/components/DataLoadingWizard/ui/Scrollable.jsx
new file mode 100644
index 0000000000..248e0bed38
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizard/ui/Scrollable.jsx
@@ -0,0 +1,19 @@
+import styled from 'styled-components'
+import PropTypes from 'prop-types'
+
+const Scrollable = styled.div`
+ overflow-x: ${props => props.x};
+ overflow-y: ${props => props.y};
+`
+
+Scrollable.propTypes = {
+ x: PropTypes.oneOf(['visible', 'hidden', 'scroll', 'auto', 'initial', 'inherit']),
+ y: PropTypes.oneOf(['visible', 'hidden', 'scroll', 'auto', 'initial', 'inherit']),
+}
+
+Scrollable.defaultProps = {
+ x: 'initial',
+ y: 'initial',
+}
+
+export default Scrollable
diff --git a/ui/pages/Project/components/DataLoadingWizard/ui/WithSpace.jsx b/ui/pages/Project/components/DataLoadingWizard/ui/WithSpace.jsx
new file mode 100644
index 0000000000..208b6c8772
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizard/ui/WithSpace.jsx
@@ -0,0 +1,27 @@
+import styled from 'styled-components'
+import PropTypes from 'prop-types'
+
+const WithSpace = styled.div`
+ ${props => `${props.type}-top: ${props.top}`};
+ ${props => `${props.type}-bottom: ${props.bottom}`};
+ ${props => `${props.type}-left: ${props.left}`};
+ ${props => `${props.type}-right: ${props.right}`};
+`
+
+WithSpace.propTypes = {
+ type: PropTypes.oneOf(['padding', 'margin']),
+ top: PropTypes.string,
+ bottom: PropTypes.string,
+ left: PropTypes.string,
+ right: PropTypes.string,
+}
+
+WithSpace.defaultProps = {
+ type: 'margin',
+ top: 'initial',
+ bottom: 'initial',
+ left: 'initial',
+ right: 'initial',
+}
+
+export default WithSpace
From c1c2e758e008ad39844f016e7c8ab86fd5f22ceb Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Thu, 3 Feb 2022 14:57:16 +1100
Subject: [PATCH 34/43] Moved to inner directory
---
.../templates/{ => File}/TemplateColumn.js | 0
.../templates/File/TemplateFile.js | 103 ++++++++++++++++++
.../templates/TemplateFile.js | 78 -------------
3 files changed, 103 insertions(+), 78 deletions(-)
rename ui/pages/Project/components/DataLoadingWizard/templates/{ => File}/TemplateColumn.js (100%)
create mode 100644 ui/pages/Project/components/DataLoadingWizard/templates/File/TemplateFile.js
delete mode 100644 ui/pages/Project/components/DataLoadingWizard/templates/TemplateFile.js
diff --git a/ui/pages/Project/components/DataLoadingWizard/templates/TemplateColumn.js b/ui/pages/Project/components/DataLoadingWizard/templates/File/TemplateColumn.js
similarity index 100%
rename from ui/pages/Project/components/DataLoadingWizard/templates/TemplateColumn.js
rename to ui/pages/Project/components/DataLoadingWizard/templates/File/TemplateColumn.js
diff --git a/ui/pages/Project/components/DataLoadingWizard/templates/File/TemplateFile.js b/ui/pages/Project/components/DataLoadingWizard/templates/File/TemplateFile.js
new file mode 100644
index 0000000000..6fb18e52bf
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizard/templates/File/TemplateFile.js
@@ -0,0 +1,103 @@
+import * as Papa from 'papaparse'
+import { isEqual } from 'lodash'
+import TemplateRow from './TemplateRow'
+
+/**
+ * Papa-parse error array
+ * @typedef {{type: string, code: string, message: string, row: number}[]} ParseErrorList
+ */
+
+/**
+ * Papa-parse metadata
+ * @typedef {
+ * {delimiter: string, linebreak: string, aborted: boolean, fields: ?string[], truncated: boolean}
+* } ParseMetadata
+ */
+
+/**
+ * TemplateFile `parse` onComplete callback signature
+ * @callback onComplete
+ * @param {{ rows: TemplateRow[], valid: boolean, errors: string[], header: string[] }} result
+ */
+
+/**
+ * TemplateFile `onError` onError callback signature
+ * @callback onError
+ * @param {Error} error
+ */
+
+class TemplateFile {
+
+ /**
+ * @param {TemplateColumn[]} columns
+ */
+ constructor(columns) {
+ if (!Array.isArray(columns)) {
+ throw new Error("Parameter 'columns' must be an array of TemplateColumn objects")
+ }
+
+ /**
+ * @type {TemplateColumn[]}
+ */
+ this.columns = Array.from(columns)
+ }
+
+ /**
+ * @param {File} file
+ * @param {onComplete} onComplete
+ * @param {onError} onError
+ *
+ * @returns {TemplateFile}
+ */
+ parse({ file, onComplete, onError }) {
+ Papa.parse(
+ file,
+ {
+ header: true,
+ skipEmptyLines: true,
+ /**
+ * @param {{data: *[], errors: ParseErrorList, meta: ParseMetadata}} results
+ */
+ complete: (results) => {
+ const errors = results.errors.map(e => `(Row ${e.row}) ${e.message}`)
+
+ if (!isEqual(this.columns.map(c => c.key).sort(), [...(results.meta.fields || [])].sort())) {
+ onComplete({
+ rows: [],
+ valid: false,
+ errors: ['File contains invalid columns in header'],
+ header: results.meta.fields || [],
+ })
+ return
+ }
+
+ if (errors.length > 0) {
+ onComplete({ rows: [], valid: false, errors, header: results.meta.fields || [] })
+ } else {
+ const rows = results.data.map((row, index) => {
+ const columns = this.columns.map(c => ({ data: c.parse(row), definition: c }))
+ return new TemplateRow({ index, columns })
+ })
+
+ const valid = rows.reduce((isValid, row) => isValid && row.valid, true)
+ const validationErrors = valid ? [] : ['Some rows contain invalid information']
+
+ onComplete({
+ rows,
+ valid,
+ errors: validationErrors,
+ header: results.meta.fields || [],
+ })
+ }
+ },
+ /**
+ * @param {Error} error
+ */
+ error: error => onError(error),
+ },
+ )
+ }
+
+}
+
+export default TemplateFile
diff --git a/ui/pages/Project/components/DataLoadingWizard/templates/TemplateFile.js b/ui/pages/Project/components/DataLoadingWizard/templates/TemplateFile.js
deleted file mode 100644
index e7cb6ae48f..0000000000
--- a/ui/pages/Project/components/DataLoadingWizard/templates/TemplateFile.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import * as Papa from 'papaparse'
-
-class TemplateFile {
-
- /**
- * @param {TemplateColumn[]} columns
- */
- constructor(columns) {
- /**
- * @type {TemplateColumn[]}
- */
- this.columns = [...columns]
-
- /**
- * @type {{valid: boolean, columns: [TemplateColumn, {value: *, valid: boolean, errors: string[]}][]}[]}
- */
- this.rows = []
-
- /**
- * @type {?boolean}
- */
- this.valid = null
-
- /**
- * @type {{type: string, code: string, message: string, row: number}[]}
- */
- this.fileErrors = []
-
- /**
- * @type {{delimiter: string, linebreak: string, aborted: boolean, fields: ?string[], truncated: boolean}}
- */
- this.fileMetadata = {}
- }
-
- parse(file, onComplete, onError) {
- this.rows = []
- this.valid = null
- this.fileErrors = []
- this.fileMetadata = {}
-
- Papa.parse(
- file,
- {
- header: true,
- skipEmptyLines: true,
- complete: (results) => {
- this.fileMetadata = { ...results.meta }
- this.fileErrors = [...results.errors]
-
- if (this.fileErrors.length === 0) {
- this.rows = results.data.map((row) => {
- const columns = this.columns.map(c => [c, c.parse(row)])
- const valid = columns.reduce((acc, column) => acc && column[1].valid, true)
-
- return { valid, columns }
- })
-
- this.valid = this.rows.reduce((acc, row) => acc && row.valid, true)
- } else {
- this.valid = false
- }
-
- onComplete(results)
- },
- error: (e) => {
- this.valid = false
- this.fileErrors = [{ type: e.name, code: '500', message: e.message, row: -1 }]
- onError(e)
- },
- },
- )
-
- return this
- }
-
-}
-
-export default TemplateFile
From 715dcffbb9d51824f3f9a64d03351ec4d4c810f7 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Thu, 3 Feb 2022 14:57:22 +1100
Subject: [PATCH 35/43] Moved to inner directory
---
.../DataLoadingWizard/templates/{ => utils}/parsers.js | 0
.../DataLoadingWizard/templates/{ => utils}/validators.js | 6 +++---
2 files changed, 3 insertions(+), 3 deletions(-)
rename ui/pages/Project/components/DataLoadingWizard/templates/{ => utils}/parsers.js (100%)
rename ui/pages/Project/components/DataLoadingWizard/templates/{ => utils}/validators.js (94%)
diff --git a/ui/pages/Project/components/DataLoadingWizard/templates/parsers.js b/ui/pages/Project/components/DataLoadingWizard/templates/utils/parsers.js
similarity index 100%
rename from ui/pages/Project/components/DataLoadingWizard/templates/parsers.js
rename to ui/pages/Project/components/DataLoadingWizard/templates/utils/parsers.js
diff --git a/ui/pages/Project/components/DataLoadingWizard/templates/validators.js b/ui/pages/Project/components/DataLoadingWizard/templates/utils/validators.js
similarity index 94%
rename from ui/pages/Project/components/DataLoadingWizard/templates/validators.js
rename to ui/pages/Project/components/DataLoadingWizard/templates/utils/validators.js
index f67bf8d3f1..a9c4aaebe9 100644
--- a/ui/pages/Project/components/DataLoadingWizard/templates/validators.js
+++ b/ui/pages/Project/components/DataLoadingWizard/templates/utils/validators.js
@@ -1,5 +1,5 @@
import * as _ from 'lodash'
-import { INHERITANCE_MODE_OPTIONS, ONSET_AGE_OPTIONS } from '../../../constants'
+import { INHERITANCE_MODE_OPTIONS, ONSET_AGE_OPTIONS } from '../../../../constants'
/**
* @param {*[]} collection
@@ -55,7 +55,7 @@ export const validateOnsetCategory = (value, message = null) => {
const categories = ONSET_AGE_OPTIONS.map(o => o.text)
if (!categories.map(c => c.toLowerCase()).includes(value.toLowerCase())) {
- return message || `'${value}' must be one of ${categories.join(', ')}`
+ return message || `'${value}' is not one of ${categories.join(', ')}`
}
return null
@@ -72,7 +72,7 @@ export const validateModeOfInheritanceTerm = (value, message = null) => {
const categories = INHERITANCE_MODE_OPTIONS.map(o => o.text)
if (!categories.map(c => c.toLowerCase()).includes(value.toLowerCase())) {
- return message || `'${value}' must be one of ${categories.join(', ')}`
+ return message || `'${value}' is not one of ${categories.join(', ')}`
}
return null
From b76aa7beb9aa0e3aae2692fedad5259c5ed976d2 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Thu, 3 Feb 2022 14:58:27 +1100
Subject: [PATCH 36/43] Instruction components with help/guide text
---
.../DataLoadingWizard/TemplateDirectoryLink.jsx | 13 +++++++++++++
.../DataLoadingWizard/TemplateHelp.jsx | 17 +++++++++++++++++
2 files changed, 30 insertions(+)
create mode 100644 ui/pages/Project/components/DataLoadingWizard/TemplateDirectoryLink.jsx
create mode 100644 ui/pages/Project/components/DataLoadingWizard/TemplateHelp.jsx
diff --git a/ui/pages/Project/components/DataLoadingWizard/TemplateDirectoryLink.jsx b/ui/pages/Project/components/DataLoadingWizard/TemplateDirectoryLink.jsx
new file mode 100644
index 0000000000..533b102b83
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizard/TemplateDirectoryLink.jsx
@@ -0,0 +1,13 @@
+import React from 'react'
+
+const TemplateDirectoryLink = () => (
+
+ here
+
+)
+
+export default TemplateDirectoryLink
diff --git a/ui/pages/Project/components/DataLoadingWizard/TemplateHelp.jsx b/ui/pages/Project/components/DataLoadingWizard/TemplateHelp.jsx
new file mode 100644
index 0000000000..4233af88f5
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizard/TemplateHelp.jsx
@@ -0,0 +1,17 @@
+import React from 'react'
+
+import { ReadableText } from './ui'
+import TemplateDirectoryLink from './TemplateDirectoryLink'
+
+const TemplateHelp = () => (
+
+ 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
+ {' '}
+
+ { '. ' }
+ Once you have filled in this template, upload it via this step and correct any validation errors before proceeding.
+
+)
+
+export default TemplateHelp
From 481949356747a71e55ffd7c97bd7b6825cbe0f77 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Thu, 3 Feb 2022 14:58:53 +1100
Subject: [PATCH 37/43] Encapsulating a row parsed from a template file
---
.../templates/File/TemplateRow.js | 34 +++++++++++++++++++
1 file changed, 34 insertions(+)
create mode 100644 ui/pages/Project/components/DataLoadingWizard/templates/File/TemplateRow.js
diff --git a/ui/pages/Project/components/DataLoadingWizard/templates/File/TemplateRow.js b/ui/pages/Project/components/DataLoadingWizard/templates/File/TemplateRow.js
new file mode 100644
index 0000000000..449834ddcf
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizard/templates/File/TemplateRow.js
@@ -0,0 +1,34 @@
+class TemplateRow {
+
+ /** @type {number} #index */
+ #index
+
+ /** @type {{data: {value: *, valid: boolean, errors: string[]}, definition: TemplateColumn }[]} #columns */
+ #columns
+
+ /** @type {boolean} #valid */
+ #valid
+
+ /**
+ * @param {number} index Row number
+ * @param {{data: {value: *, valid: boolean, errors: string[]}, definition: TemplateColumn }[]} columns
+ */
+ constructor({ index, columns }) {
+ if (!Array.isArray(columns)) {
+ throw new Error("Parameter 'columns' must be an array")
+ }
+
+ this.#index = index
+ this.#columns = Array.from(columns)
+ this.#valid = this.columns.map(column => column.data.valid).reduce((a, b) => a && b, true)
+ }
+
+ get index() { return this.#index }
+
+ get columns() { return this.#columns }
+
+ get valid() { return this.#valid }
+
+}
+
+export default TemplateRow
From 336992988b02af66eab28aafe7fb33fb022702eb Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Thu, 3 Feb 2022 14:59:12 +1100
Subject: [PATCH 38/43] Pedigree template definition
---
.../DataLoadingWizard/templates/Pedigree.js | 89 +++++++++++++++++++
1 file changed, 89 insertions(+)
create mode 100644 ui/pages/Project/components/DataLoadingWizard/templates/Pedigree.js
diff --git a/ui/pages/Project/components/DataLoadingWizard/templates/Pedigree.js b/ui/pages/Project/components/DataLoadingWizard/templates/Pedigree.js
new file mode 100644
index 0000000000..e3ccf7aa5d
--- /dev/null
+++ b/ui/pages/Project/components/DataLoadingWizard/templates/Pedigree.js
@@ -0,0 +1,89 @@
+import TemplateColumn from './File/TemplateColumn'
+import { parseString } from './utils/parsers'
+
+const PedigreeTemplateColumns = () => ([
+ new TemplateColumn({
+ id: 'familyId',
+ key: 'Family ID',
+ index: 0,
+ required: true,
+ parser: parseString,
+ validators: [
+ v => ((!v) ? 'A family ID must be present.' : null),
+ ],
+ }),
+ new TemplateColumn({
+ id: 'individualId',
+ key: 'Individual ID',
+ index: 1,
+ required: true,
+ parser: parseString,
+ validators: [
+ v => ((!v) ? 'An individual ID must be present.' : null),
+ ],
+ }),
+ new TemplateColumn({
+ id: 'paternalId',
+ key: 'Paternal ID',
+ index: 2,
+ required: false,
+ parser: parseString,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'maternalId',
+ key: 'Maternal ID',
+ index: 3,
+ required: false,
+ parser: parseString,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'sex',
+ key: 'Sex',
+ index: 4,
+ required: true,
+ parser: parseString,
+ validators: [
+ (v) => {
+ if (!v) {
+ return (
+ 'A character representing sex must be present. ' +
+ "The character '1' represents male, '2' represents female, " +
+ "and any other character besides '1' and '2' represents unknown."
+ )
+ }
+ return null
+ },
+ ],
+ }),
+ new TemplateColumn({
+ id: 'affectedStatus',
+ key: 'Affected Status',
+ index: 5,
+ required: true,
+ parser: parseString,
+ validators: [
+ (v) => {
+ if (!v) {
+ return (
+ 'A character representing phenotype affect status must be present. ' +
+ "The characters '0' and '-9' represent missing, '1' represents unaffected and '2' represents affected. " +
+ 'Any other characters are interpreted as string phenotype values.'
+ )
+ }
+ return null
+ },
+ ],
+ }),
+ new TemplateColumn({
+ id: 'notes',
+ key: 'Notes',
+ index: 6,
+ required: false,
+ parser: parseString,
+ validators: [],
+ }),
+])
+
+export default PedigreeTemplateColumns
From e21f36bfc4ca723a6cef91c3c5e28a10dc04b145 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Thu, 3 Feb 2022 14:59:27 +1100
Subject: [PATCH 39/43] Renamed
---
.../DataLoadingWizard/templates/Individual.js | 195 +++++++++++++----
.../DataLoadingWizard/templates/Metadata.js | 196 ------------------
2 files changed, 151 insertions(+), 240 deletions(-)
delete mode 100644 ui/pages/Project/components/DataLoadingWizard/templates/Metadata.js
diff --git a/ui/pages/Project/components/DataLoadingWizard/templates/Individual.js b/ui/pages/Project/components/DataLoadingWizard/templates/Individual.js
index 231ef76459..dd7a6b18da 100644
--- a/ui/pages/Project/components/DataLoadingWizard/templates/Individual.js
+++ b/ui/pages/Project/components/DataLoadingWizard/templates/Individual.js
@@ -1,5 +1,6 @@
-import TemplateColumn from './TemplateColumn'
-import { parseString } from './parsers'
+import TemplateColumn from './File/TemplateColumn'
+import { parseString, parseStringArray, parseBoolean, parseYear } from './utils/parsers'
+import { validateHpoTerms, validateModeOfInheritanceTerms, validateOmimTerms, validateOnsetCategory } from './utils/validators'
const IndividualTemplateColumns = () => ([
new TemplateColumn({
@@ -23,65 +24,171 @@ const IndividualTemplateColumns = () => ([
],
}),
new TemplateColumn({
- id: 'paternalId',
- key: 'Paternal ID',
+ id: 'hpoTermsPresent',
+ key: 'HPO Terms (present)',
index: 2,
required: false,
- parser: parseString,
- validators: [],
+ parser: parseStringArray,
+ validators: [validateHpoTerms],
}),
new TemplateColumn({
- id: 'maternalId',
- key: 'Maternal ID',
+ id: 'hpoTermsAbsent',
+ key: 'HPO Terms (absent)',
index: 3,
required: false,
- parser: parseString,
- validators: [],
+ parser: parseStringArray,
+ validators: [validateHpoTerms],
}),
new TemplateColumn({
- id: 'sex',
- key: 'Sex',
+ id: 'birthYear',
+ key: 'Birth Year',
index: 4,
- required: true,
- parser: parseString,
- validators: [
- (v) => {
- if (!v) {
- return (
- 'A character representing sex must be present. ' +
- "The character '1' represents male, '2' represents female, " +
- "and any other character besides '1' and '2' represents unknown."
- )
- }
- return null
- },
- ],
+ required: false,
+ parser: parseYear,
+ validators: [],
}),
new TemplateColumn({
- id: 'affectedStatus',
- key: 'Affected Status',
+ id: 'deathYear',
+ key: 'Death Year',
index: 5,
- required: true,
- parser: parseString,
- validators: [
- (v) => {
- if (!v) {
- return (
- 'A character representing phenotype affect status must be present. ' +
- "The characters '0' and '-9' represent missing, '1' represents unaffected and '2' represents affected. " +
- 'Any other characters are interpreted as string phenotype values.'
- )
- }
- return null
- },
- ],
+ required: false,
+ parser: parseYear,
+ validators: [],
}),
new TemplateColumn({
- id: 'notes',
- key: 'Notes',
+ id: 'ageOfOnset',
+ key: 'Age of Onset',
index: 6,
required: false,
parser: parseString,
+ validators: [validateOnsetCategory],
+ }),
+ new TemplateColumn({
+ id: 'individualNotes',
+ key: 'Individual Notes',
+ index: 7,
+ required: false,
+ parser: parseString,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'consanguinity',
+ key: 'Consanguinity',
+ index: 8,
+ required: false,
+ parser: parseBoolean,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'otherAffectedRelatives',
+ key: 'Other Affected Relatives',
+ index: 9,
+ required: false,
+ parser: parseBoolean,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'expectedInheritanceMode',
+ key: 'Expected Mode of Inheritance',
+ index: 10,
+ required: false,
+ parser: parseStringArray,
+ validators: [validateModeOfInheritanceTerms],
+ }),
+ new TemplateColumn({
+ id: 'fertilityMedications',
+ key: 'Fertility medications',
+ index: 11,
+ required: false,
+ parser: parseBoolean,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'intrauterineInsemination',
+ key: 'Intrauterine insemination',
+ index: 12,
+ required: false,
+ parser: parseBoolean,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'inVitroFertilization',
+ key: 'In vitro fertilization',
+ index: 13,
+ required: false,
+ parser: parseBoolean,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'intraCytoplasmicSpermInjection',
+ key: 'Intra-cytoplasmic sperm injection',
+ index: 14,
+ required: false,
+ parser: parseBoolean,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'gestationalSurrogacy',
+ key: 'Gestational surrogacy',
+ index: 15,
+ required: false,
+ parser: parseBoolean,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'donorEgg',
+ key: 'Donor egg',
+ index: 16,
+ required: false,
+ parser: parseBoolean,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'donorSperm',
+ key: 'Donor sperm',
+ index: 17,
+ required: false,
+ parser: parseBoolean,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'maternalAncestry',
+ key: 'Maternal Ancestry',
+ index: 18,
+ required: false,
+ parser: parseStringArray,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'paternalAncestry',
+ key: 'Paternal Ancestry',
+ index: 19,
+ required: false,
+ parser: parseStringArray,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'preDiscoveryOmimDisorders',
+ key: 'Pre-discovery OMIM disorders',
+ index: 20,
+ required: false,
+ parser: parseStringArray,
+ validators: [validateOmimTerms],
+ }),
+ new TemplateColumn({
+ id: 'previouslyTestedGenes',
+ key: 'Previously Tested Genes',
+ index: 21,
+ required: false,
+ parser: parseStringArray,
+ validators: [],
+ }),
+ new TemplateColumn({
+ id: 'candidateGenes',
+ key: 'Candidate Genes',
+ index: 22,
+ required: false,
+ parser: parseStringArray,
validators: [],
}),
])
diff --git a/ui/pages/Project/components/DataLoadingWizard/templates/Metadata.js b/ui/pages/Project/components/DataLoadingWizard/templates/Metadata.js
deleted file mode 100644
index 546bec79ca..0000000000
--- a/ui/pages/Project/components/DataLoadingWizard/templates/Metadata.js
+++ /dev/null
@@ -1,196 +0,0 @@
-import TemplateColumn from './TemplateColumn'
-import { parseString, parseStringArray, parseBoolean, parseYear } from './parsers'
-import { validateHpoTerms, validateModeOfInheritanceTerms, validateOmimTerms, validateOnsetCategory } from './validators'
-
-const MetadataTemplateColumns = () => ([
- new TemplateColumn({
- id: 'familyId',
- key: 'Family ID',
- index: 0,
- required: true,
- parser: parseString,
- validators: [
- v => ((!v) ? 'A family ID must be present.' : null),
- ],
- }),
- new TemplateColumn({
- id: 'individualId',
- key: 'Individual ID',
- index: 1,
- required: true,
- parser: parseString,
- validators: [
- v => ((!v) ? 'An individual ID must be present.' : null),
- ],
- }),
- new TemplateColumn({
- id: 'hpoTermsPresent',
- key: 'HPO Terms (present)',
- index: 2,
- required: false,
- parser: parseStringArray,
- validators: [validateHpoTerms],
- }),
- new TemplateColumn({
- id: 'hpoTermsAbsent',
- key: 'HPO Terms (absent)',
- index: 3,
- required: false,
- parser: parseStringArray,
- validators: [validateHpoTerms],
- }),
- new TemplateColumn({
- id: 'birthYear',
- key: 'Birth Year',
- index: 4,
- required: false,
- parser: parseYear,
- validators: [],
- }),
- new TemplateColumn({
- id: 'deathYear',
- key: 'Death Year',
- index: 5,
- required: false,
- parser: parseYear,
- validators: [],
- }),
- new TemplateColumn({
- id: 'ageOfOnset',
- key: 'Age of Onset',
- index: 6,
- required: false,
- parser: parseString,
- validators: [validateOnsetCategory],
- }),
- new TemplateColumn({
- id: 'individualNotes',
- key: 'Individual Notes',
- index: 7,
- required: false,
- parser: parseString,
- validators: [],
- }),
- new TemplateColumn({
- id: 'consanguinity',
- key: 'Consanguinity',
- index: 8,
- required: false,
- parser: parseBoolean,
- validators: [],
- }),
- new TemplateColumn({
- id: 'otherAffectedRelatives',
- key: 'Other Affected Relatives',
- index: 9,
- required: false,
- parser: parseBoolean,
- validators: [],
- }),
- new TemplateColumn({
- id: 'expectedInheritanceMode',
- key: 'Expected Mode of Inheritance',
- index: 10,
- required: false,
- parser: parseStringArray,
- validators: [validateModeOfInheritanceTerms],
- }),
- new TemplateColumn({
- id: 'fertilityMedications',
- key: 'Fertility medications',
- index: 11,
- required: false,
- parser: parseBoolean,
- validators: [],
- }),
- new TemplateColumn({
- id: 'intrauterineInsemination',
- key: 'Intrauterine insemination',
- index: 12,
- required: false,
- parser: parseBoolean,
- validators: [],
- }),
- new TemplateColumn({
- id: 'inVitroFertilization',
- key: 'In vitro fertilization',
- index: 13,
- required: false,
- parser: parseBoolean,
- validators: [],
- }),
- new TemplateColumn({
- id: 'intraCytoplasmicSpermInjection',
- key: 'Intra-cytoplasmic sperm injection',
- index: 14,
- required: false,
- parser: parseBoolean,
- validators: [],
- }),
- new TemplateColumn({
- id: 'gestationalSurrogacy',
- key: 'Gestational surrogacy',
- index: 15,
- required: false,
- parser: parseBoolean,
- validators: [],
- }),
- new TemplateColumn({
- id: 'donorEgg',
- key: 'Donor egg',
- index: 16,
- required: false,
- parser: parseBoolean,
- validators: [],
- }),
- new TemplateColumn({
- id: 'donorSperm',
- key: 'Donor sperm',
- index: 17,
- required: false,
- parser: parseBoolean,
- validators: [],
- }),
- new TemplateColumn({
- id: 'maternalAncestry',
- key: 'Maternal Ancestry',
- index: 18,
- required: false,
- parser: parseStringArray,
- validators: [],
- }),
- new TemplateColumn({
- id: 'paternalAncestry',
- key: 'Paternal Ancestry',
- index: 19,
- required: false,
- parser: parseStringArray,
- validators: [],
- }),
- new TemplateColumn({
- id: 'preDiscoveryOmimDisorders',
- key: 'Pre-discovery OMIM disorders',
- index: 20,
- required: false,
- parser: parseStringArray,
- validators: [validateOmimTerms],
- }),
- new TemplateColumn({
- id: 'previouslyTestedGenes',
- key: 'Previously Tested Genes',
- index: 21,
- required: false,
- parser: parseStringArray,
- validators: [],
- }),
- new TemplateColumn({
- id: 'candidateGenes',
- key: 'Candidate Genes',
- index: 22,
- required: false,
- parser: parseStringArray,
- validators: [],
- }),
-])
-
-export default MetadataTemplateColumns
From 711d873033c383ca96fc5c802f35eb141dbcf941 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Thu, 3 Feb 2022 14:59:44 +1100
Subject: [PATCH 40/43] Import path update
---
.../Project/components/DataLoadingWizard/templates/Family.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/ui/pages/Project/components/DataLoadingWizard/templates/Family.js b/ui/pages/Project/components/DataLoadingWizard/templates/Family.js
index 4deb83239c..e76be572a7 100644
--- a/ui/pages/Project/components/DataLoadingWizard/templates/Family.js
+++ b/ui/pages/Project/components/DataLoadingWizard/templates/Family.js
@@ -1,5 +1,5 @@
-import TemplateColumn from './TemplateColumn'
-import { parseString, parseStringArray } from './parsers'
+import TemplateColumn from './File/TemplateColumn'
+import { parseString, parseStringArray } from './utils/parsers'
const FamilyTemplateColumns = () => ([
new TemplateColumn({
From a45f9d7bb56194cf80db72a5e6cc3210eb1fe5c2 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Thu, 3 Feb 2022 14:59:54 +1100
Subject: [PATCH 41/43] Added new module exports
---
ui/pages/Project/components/DataLoadingWizard/ui/index.js | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/ui/pages/Project/components/DataLoadingWizard/ui/index.js b/ui/pages/Project/components/DataLoadingWizard/ui/index.js
index 421e5d52ea..5621e6685b 100644
--- a/ui/pages/Project/components/DataLoadingWizard/ui/index.js
+++ b/ui/pages/Project/components/DataLoadingWizard/ui/index.js
@@ -2,10 +2,14 @@ import Centered from './Centered'
import FormSection from './FormSection'
import FormStepButtons from './FormStepButtons'
import ReadableText from './ReadableText'
+import WithSpace from './WithSpace'
+import Scrollable from './Scrollable'
export {
Centered,
FormSection,
FormStepButtons,
ReadableText,
+ WithSpace,
+ Scrollable,
}
From 8d7fd14efbec9ceb2537f4f5f5717cbf7f6dc674 Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Thu, 3 Feb 2022 15:00:25 +1100
Subject: [PATCH 42/43] Added steps for pedigree, family and indiviual metadata
uploads
---
.../DataLoadingWizard/DataLoadingWizard.jsx | 52 +++++++++++++++----
1 file changed, 43 insertions(+), 9 deletions(-)
diff --git a/ui/pages/Project/components/DataLoadingWizard/DataLoadingWizard.jsx b/ui/pages/Project/components/DataLoadingWizard/DataLoadingWizard.jsx
index ec18354ac2..1f4bc46990 100644
--- a/ui/pages/Project/components/DataLoadingWizard/DataLoadingWizard.jsx
+++ b/ui/pages/Project/components/DataLoadingWizard/DataLoadingWizard.jsx
@@ -7,17 +7,24 @@ import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Breadcrumb } from 'semantic-ui-react'
-import { Centered, FormSection, FormStepButtons } from './ui'
-import { Welcome, FamilyMetadataUpload } from './steps'
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: 'Family metadata', link: true, active: false },
+ { key: 1, content: 'Pedigree', link: true, active: false },
{ key: 2, content: 'Individual metadata', link: true, active: false },
- { key: 3, content: 'Sample mapping', link: true, active: false },
- { key: 4, content: 'Review', 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 }) => {
@@ -58,20 +65,47 @@ const BaseMultistepForm = ({ project }) => {
}, [formSteps, activeFormStepIndex, enableReview])
const getFormStepComponent = useCallback(() => {
+ const onFormChange = ({ formData, isComplete }) => updateFormStep(activeFormStepIndex, { formData, isComplete })
+
switch (activeFormStepIndex) {
case 0:
return
case 1:
return (
- }
project={project}
- onFormChange={({ formData, isComplete }) => updateFormStep(1, { formData, isComplete })}
/>
)
case 2:
- return { breadCrumbs[activeFormStepIndex].content }
+ return (
+ }
+ project={project}
+ />
+ )
case 3:
- return { breadCrumbs[activeFormStepIndex].content }
+ return (
+ }
+ project={project}
+ />
+ )
case 4:
return { breadCrumbs[activeFormStepIndex].content }
case 5:
From badbcde65a1ea15c5b4aed4c1ceebe8660e5a30a Mon Sep 17 00:00:00 2001
From: daniaki <7043686+daniaki@users.noreply.github.com>
Date: Thu, 3 Feb 2022 15:01:02 +1100
Subject: [PATCH 43/43] Adding unique keys to lists for react performance;
refactoring reusable components into own files
---
.../steps/TemplateUpload.jsx | 117 ++++++++----------
1 file changed, 55 insertions(+), 62 deletions(-)
diff --git a/ui/pages/Project/components/DataLoadingWizard/steps/TemplateUpload.jsx b/ui/pages/Project/components/DataLoadingWizard/steps/TemplateUpload.jsx
index 418e10aede..258165c97c 100644
--- a/ui/pages/Project/components/DataLoadingWizard/steps/TemplateUpload.jsx
+++ b/ui/pages/Project/components/DataLoadingWizard/steps/TemplateUpload.jsx
@@ -3,33 +3,19 @@ import PropTypes from 'prop-types'
import { capitalize, isBoolean, isEqual } from 'lodash'
import { Form, Table, Icon, List, Message } from 'semantic-ui-react'
-import styled from 'styled-components'
-import { ReadableText } from '../ui'
-import TemplateFile from '../templates/TemplateFile'
-
-const ScrollTable = styled.div`
- margin-top: 1rem;
- overflow-x: scroll;
- overflow-y: hidden;
-`
-
-const TemplateDirectoryLink = () => (
-
- here
-
-)
+
+import TemplateFile from '../templates/File/TemplateFile'
+import { Scrollable, WithSpace } from '../ui'
const renderCellContent = (value) => {
if (Array.isArray(value)) {
- return value.length ? {value.map(v => {v} )}
: '-'
+ return value.length ? {value.map(v => {v} )}
: '-'
}
+
if (isBoolean(value)) {
return capitalize(value.toString())
}
+
return value == null ? '-' : value.toString()
}
@@ -43,14 +29,14 @@ const renderRowErrorList = (row) => {
if (row.valid) return null
return (
-
+
{
row.columns.map(({ data, definition }) => {
if (data.valid) return null
- const errorList = data.errors.map(e => {e} )
+ const errorList = data.errors.map(e => {e} )
return (
-
+
{definition.key}
{errorList}
@@ -61,7 +47,7 @@ const renderRowErrorList = (row) => {
)
}
-const TemplateUpload = ({ project, parser, onFormChange }) => {
+const TemplateUpload = ({ id, label, information, template, project, onFormChange }) => {
const [isLoading, setIsLoading] = useState(false)
const [errors, setErrors] = useState(/** @type {string[]|JSXElement[]} */ [])
const [valid, setValid] = useState(true)
@@ -77,7 +63,7 @@ const TemplateUpload = ({ project, parser, onFormChange }) => {
}
setIsLoading(true)
- parser.parse(
+ template.parse(
{
file: event.target.files[0],
onComplete: ((result) => {
@@ -86,16 +72,20 @@ const TemplateUpload = ({ project, parser, onFormChange }) => {
setIsLoading(false)
// Copy array before sorting since sort function has side effects and modifies the original array
- const columnMismatch = !isEqual([...result.header].sort(), parser.columns.map(c => c.key).sort())
+ const columnMismatch = !isEqual([...result.header].sort(), template.columns.map(c => c.key).sort())
if (columnMismatch) {
setErrors([
(
Template file should have the following header columns:
-
{parser.columns.map(c => {c.key} )}
+
+ {template.columns.map(c => {c.key} )}
+
However, your file contains the header columns:
-
{result.header.map(f => {f} )}
+
+ {result.header.map(f => {f} )}
+
),
])
@@ -118,7 +108,7 @@ const TemplateUpload = ({ project, parser, onFormChange }) => {
},
)
}
- }, [parser, setIsLoading, setErrors, setRows, setValid])
+ }, [template, setIsLoading, setErrors, setRows, setValid])
const renderTableBody = useCallback(() => {
if (!valid && !rows.length) {
@@ -133,7 +123,7 @@ const TemplateUpload = ({ project, parser, onFormChange }) => {
// to the user.
if (rows.length) {
return rows.map(row => (
-
+
{row.valid ? : }
@@ -142,7 +132,7 @@ const TemplateUpload = ({ project, parser, onFormChange }) => {
{
row.columns.map(({ data, definition }) => (
-
+
{renderCellContent(data.value)}
))
@@ -159,19 +149,11 @@ const TemplateUpload = ({ project, parser, onFormChange }) => {
}, [rows, valid, errors])
return (
- <>
-
- In this section, you will provide all information relating to the families in your project. Please download
- the families template from
- {' '}
-
- { '. ' }
- Once you have filled in this template, upload it via this step and correct any validation errors
- before proceeding.
-
+
+ {information}
+
{
@@ -179,39 +161,50 @@ const TemplateUpload = ({ project, parser, onFormChange }) => {
{e} )}
/>
) : null
}
-
-
-
-
- Status
-
- {
- parser
+
+
+
+
+
+ Status
+
+ {
+ template
.columns
- .map(c => {`${c.key}${c.required ? ' *' : ''}`} )
+ .map(c => (
+
+ {`${c.key}${c.required ? ' *' : ''}`}
+
+ ))
}
-
-
-
- {renderTableBody()}
-
-
-
- >
+
+
+
+ {renderTableBody()}
+
+
+
+
+
)
}
TemplateUpload.propTypes = {
+ id: PropTypes.string.isRequired,
+ label: PropTypes.string.isRequired,
+ information: PropTypes.element,
+ template: PropTypes.instanceOf(TemplateFile).isRequired,
project: PropTypes.object.isRequired,
- parser: PropTypes.instanceOf(TemplateFile).isRequired,
onFormChange: PropTypes.func.isRequired,
}
-TemplateUpload.defaultProps = {}
+TemplateUpload.defaultProps = {
+ information: null,
+}
export default TemplateUpload