Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Feature/status message handler #83

Merged
merged 9 commits into from
Oct 13, 2020
6 changes: 6 additions & 0 deletions enzyme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Enzyme, { configure, shallow, mount, render } from "enzyme"
import Adapter from "enzyme-adapter-react-16"

configure({ adapter: new Adapter() })
export { shallow, mount, render }
export default Enzyme
410 changes: 410 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
},
"devDependencies": {
"concurrently": "^5.3.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.5",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-flowtype": "^5.2.0",
"eslint-plugin-import": "^2.22.0",
Expand Down
2 changes: 1 addition & 1 deletion src/components/Nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const useStyles = makeStyles(theme => ({
appBar: {
borderBottom: `1px solid ${theme.palette.divider}`,
color: theme.palette.text.primary,
backgroundColor: "white",
backgroundColor: "#FFF",
},
logo: {
height: "auto",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const useStyles = makeStyles(theme => ({
},
cardHeader: {
backgroundColor: theme.palette.primary.main,
color: "white",
color: "#FFF",
fontWeight: "bold",
},
cardHeaderAction: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const useStyles = makeStyles(theme => ({
justifyContent: "space-between",
flexShrink: 0,
borderTop: "solid 1px #ccc",
backgroundColor: "white",
backgroundColor: "#FFF",
position: "fixed",
left: 0,
bottom: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Typography from "@material-ui/core/Typography"
const useStyles = makeStyles(theme => ({
paperTitle: {
fontWeight: "bold",
color: "white",
color: "#FFF",
width: "100%",
padding: theme.spacing(3),
backgroundColor: "#9b416b",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const AccordionSummary = withStyles(theme => ({
},
},
content: {
color: "white",
color: "#FFF",
fontWeight: "bold",
"&$expanded": {
margin: `${theme.spacing(2)} 0`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const useQontoStepIconStyles = makeStyles(theme => ({
},
floating: {
border: "solid 1px #000",
backgroundColor: "white",
backgroundColor: "#FFF",
boxShadow: 0,
},
}))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { addObjectToFolder } from "../../../features/wizardSubmissionFolderSlice

import { WizardAjvResolver } from "./WizardAjvResolver"
import JSONSchemaParser from "./WizardJSONSchemaParser"
import WizardStatusMessageHandler from "./WizardStatusMessageHandler"

import objectAPIService from "services/objectAPI"
import schemaAPIService from "services/schemaAPI"
Expand Down Expand Up @@ -59,17 +60,6 @@ const useStyles = makeStyles(theme => ({
},
}))

const checkResponseError = (response, prefixText) => {
switch (response.status) {
case 504:
return `Unfortunately we couldn't connect to our server.`
case 400:
return `${prefixText}, details: ${response.data.detail}`
default:
return "Unfortunately an unexpected error happened on our servers"
}
}

type FormContentProps = {
resolver: typeof WizardAjvResolver,
formSchema: any,
Expand Down Expand Up @@ -111,14 +101,15 @@ const FormContent = ({ resolver, formSchema, onSubmit }: FormContentProps) => {
const WizardFillObjectDetailsForm = () => {
const objectType = useSelector(state => state.objectType)
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState("")
const [successMessage, setSuccessMessage] = useState("")
const [successStatus, setSuccessStatus] = useState("info")
const [error, setError] = useState(false)
const [errorPrefix, setErrorPrefix] = useState("")
const [successStatus, setSuccessStatus] = useState("")
const [formSchema, setFormSchema] = useState({})
const [validationSchema, setValidationSchema] = useState({})
const [submitting, setSubmitting] = useState(false)
const dispatch = useDispatch()
const { id: folderId } = useSelector(state => state.submissionFolder)
const [responseInfo, setResponseInfo] = useState([])

/*
* Submit form with cleaned values and check for response errors
Expand All @@ -127,15 +118,14 @@ const WizardFillObjectDetailsForm = () => {
setSubmitting(true)
const waitForServertimer = setTimeout(() => {
setSuccessStatus("info")
setSuccessMessage(`For some reason, your file is still being saved
to our database, please wait. If saving doesn't go through in two
minutes, please try saving the file again.`)
}, 5000)
const cleanedValues = JSONSchemaParser.cleanUpFormValues(data)
const response = await objectAPIService.createFromJSON(objectType, cleanedValues)

setResponseInfo(response)

if (response.ok) {
setSuccessStatus("success")
setSuccessMessage(`Submitted with accessionid ${response.data.accessionId}`)
dispatch(
addObjectToFolder(folderId, {
accessionId: response.data.accessionId,
Expand All @@ -144,7 +134,7 @@ const WizardFillObjectDetailsForm = () => {
)
} else {
setSuccessStatus("error")
setSuccessMessage(checkResponseError(response, "Validation failed"))
setErrorPrefix("Validation failed")
}
clearTimeout(waitForServertimer)
setSubmitting(false)
Expand All @@ -158,11 +148,13 @@ const WizardFillObjectDetailsForm = () => {
let schema = localStorage.getItem(`cached_${objectType}_schema`)
if (!schema || !new Ajv().validateSchema(JSON.parse(schema))) {
const response = await schemaAPIService.getSchemaByObjectType(objectType)
setResponseInfo(response)
if (response.ok) {
schema = response.data
localStorage.setItem(`cached_${objectType}_schema`, JSON.stringify(schema))
} else {
setError(checkResponseError(response, "Unfortunately an error happened while catching form fields"))
setError(true)
setErrorPrefix("Unfortunately an error happened while catching form fields")
setIsLoading(false)
return
}
Expand All @@ -177,20 +169,19 @@ const WizardFillObjectDetailsForm = () => {
}, [objectType])

if (isLoading) return <CircularProgress />
if (error) return <Alert severity="error">{error}</Alert>
// Schema validation error differs from response status handler
if (error) return <Alert severity="error">{errorPrefix}</Alert>

return (
<Container maxWidth="md">
<FormContent formSchema={formSchema} resolver={WizardAjvResolver(validationSchema)} onSubmit={onSubmit} />
{submitting && <LinearProgress />}
{successMessage && (
<Alert
severity={successStatus}
onClose={() => {
setSuccessMessage("")
}}
>
{successMessage}
</Alert>
{successStatus && (
<WizardStatusMessageHandler
successStatus={successStatus}
response={responseInfo}
prefixText={errorPrefix}
></WizardStatusMessageHandler>
)}
</Container>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//@flow
import React, { useState } from "react"

import Snackbar from "@material-ui/core/Snackbar"
import Alert from "@material-ui/lab/Alert"
import { useDispatch } from "react-redux"

import { setErrorMessage } from "features/wizardErrorMessageSlice"

/*
* Error messages are shown both in snackbar and Formik form errors. Latter needs error message from state
*/
const ErrorHandler = ({ response, prefixText }: { response: any, prefixText: string }) => {
const [openStatus, setOpenStatus] = useState(true)
const dispatch = useDispatch()
let message = ""
switch (response.status) {
case 504:
message = `Unfortunately we couldn't connect to our server.`
break
case 400:
message = `${prefixText}, details: ${response.data.detail}`
break
default:
message = "Unfortunately an unexpected error happened on our servers"
}
dispatch(setErrorMessage(message))
return (
<div>
<Snackbar open={openStatus}>
<Alert
severity="error"
onClose={() => {
setOpenStatus(false)
}}
>
{message}
</Alert>
</Snackbar>
</div>
)
}

// Info messages
const InfoHandler = () => {
const message = `For some reason, your file is still being saved
to our database, please wait. If saving doesn't go through in two
minutes, please try saving the file again.`

return <Alert severity="info">{message}</Alert>
}

// Success messages
const SuccessHandler = ({ response }: { response: any }) => {
const message = `Submitted with accessionid ${response.data.accessionId}`
return <Alert severity="success">{message}</Alert>
}

const WizardStatusMessageHandler = ({
successStatus,
response,
prefixText,
}: {
successStatus: string,
response: any,
prefixText: string,
}) => {
const messageTemplate = status => {
switch (status) {
case "success":
return <SuccessHandler response={response}></SuccessHandler>
case "info":
return <InfoHandler></InfoHandler>
case "error":
return <ErrorHandler response={response} prefixText={prefixText}></ErrorHandler>
default:
return
}
}

return <div>{!Array.isArray(response) && messageTemplate(successStatus)}</div>
}

export default WizardStatusMessageHandler
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from "react"

import { Provider } from "react-redux"
import configureStore from "redux-mock-store"

import { shallow, mount } from "../../../../enzyme"

import WizardStatusMessageHandler from "./WizardStatusMessageHandler"

const mockStore = configureStore([])

describe("WizardStatusMessageHandler", () => {
const store = mockStore({
errorMessage: "",
})
it("should render appropriate components", () => {
const statusList = ["error", "info", "success"]
statusList.forEach(status => {
const wrapper = shallow(<WizardStatusMessageHandler successStatus={status} />)
expect(wrapper.find(status.charAt(0).toUpperCase() + status.slice(1) + "Handler").length).toBe(1)
})
})

it("should open snackbar on error message", () => {
const wrapper = mount(
<Provider store={store}>
<WizardStatusMessageHandler response={{ status: 504 }} successStatus="error" />
</Provider>
)
expect(wrapper.find("ForwardRef(Snackbar)").length).toBe(1)
})
})
Loading