-
Notifications
You must be signed in to change notification settings - Fork 2
Td 2435 generic assessment UI #357
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
base: master
Are you sure you want to change the base?
Changes from all commits
f265c16
48dc4be
ddad0df
07d1ad5
d065fc4
662aecb
b0cf037
aeeb84b
612d3fe
0ca367b
26c932f
aa365bb
2874102
8a997c1
a17c8c4
8e04610
0664436
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -144,6 +144,7 @@ function createAssessment() { | |
if(cache.get("surveyType") === "benchmark") { return createWidget(); } | ||
if(cache.get("surveyType") === "cognitive") { return createCognitiveAssessment(); } | ||
if(cache.get("surveyType") === "order") { return createWidget(); } | ||
if(cache.get("surveyType") === "generic") { return createGenericAssessment(); } | ||
|
||
const params = { | ||
deck_id: cache.get("deckID"), | ||
|
@@ -240,6 +241,25 @@ function createElement(options = {}) { | |
return element; | ||
} | ||
|
||
function createGenericAssessment() { | ||
Traitify.http.authKey = "admin-secret"; | ||
const query = Traitify.GraphQL.generic.create; | ||
const variables = { | ||
surveyID: cache.get("surveyID"), | ||
profileID: cache.get("profileID") | ||
}; | ||
|
||
Traitify.http.post(Traitify.GraphQL.generic.path, {query, variables}).then((response) => { | ||
try { | ||
const id = response.data.getOrCreateGenericAssessment.id; | ||
cache.set("assessmentID", id); | ||
} catch(error) { | ||
console.log(error); | ||
} | ||
setTimeout(createWidget, 500); | ||
}); | ||
} | ||
|
||
function destroyWidget() { Traitify.destroy(); } | ||
|
||
function setupTargets() { | ||
|
@@ -369,6 +389,7 @@ function setupDom() { | |
options: [ | ||
{text: "Benchmark", value: "benchmark"}, | ||
{text: "Cognitive", value: "cognitive"}, | ||
{text: "Generic", value: "generic"}, | ||
{text: "Order", value: "order"}, | ||
{text: "Personality", value: "personality"} | ||
], | ||
|
@@ -404,6 +425,9 @@ function setupDom() { | |
row.appendChild(createOption({name: "orderID", text: "Order ID:"})); | ||
group.appendChild(row) | ||
|
||
row = createElement({className: surveyType !== "generic" ? "hide" : "", id: "generic-options"}); | ||
row.appendChild(createOption({name: "profileID", text: "Profile ID:"})); | ||
group.appendChild(row); | ||
row = createElement({className: "row"}); | ||
row.appendChild(createElement({onClick: createAssessment, tag: "button", text: "Create / Load"})); | ||
group.appendChild(row); | ||
|
@@ -427,6 +451,30 @@ function setupCognitive() { | |
}); | ||
} | ||
|
||
function setupGeneric() { | ||
Traitify.http.authKey = "admin-secret"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does generic require a secret key to list them? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, secret key is applied to listing query and mutation graphql request There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we'd need to change that to public key since this is on the front-end, we can't use a secret key in the candidate's browser |
||
const query = Traitify.GraphQL.generic.surveys; | ||
const variables = {localeKey: cache.get("locale")}; | ||
|
||
Traitify.http.post(Traitify.GraphQL.generic.path, {query, variables}).then((response) => { | ||
try { | ||
const options = response.data.genericSurveys | ||
.map(({id, name}) => ({text: name, value: id})) | ||
.sort((a, b) => a.text.localeCompare(b.text)); | ||
|
||
document.querySelector("#generic-options").appendChild(createOption({ | ||
name: "surveyID", | ||
onChange: onInputChange, | ||
options, | ||
text: "Survey:" | ||
})); | ||
} catch(error) { | ||
console.log(error); | ||
} | ||
}); | ||
|
||
} | ||
|
||
function setupTraitify() { | ||
const environment = cache.get("environment"); | ||
|
||
|
@@ -455,7 +503,7 @@ function onSurveyTypeChange(e) { | |
const name = e.target.name; | ||
const value = e.target.value; | ||
const assessmentID = cache.get(`${value}AssessmentID`); | ||
const otherValues = ["benchmark", "cognitive", "order", "personality"].filter((type) => type !== value); | ||
const otherValues = ["benchmark", "cognitive", "order", "personality", "generic"].filter((type) => type !== value); | ||
|
||
cache.set("assessmentID", assessmentID); | ||
|
||
|
@@ -468,4 +516,5 @@ function onSurveyTypeChange(e) { | |
setupTraitify(); | ||
setupDom(); | ||
setupCognitive(); | ||
setupGeneric(); | ||
createWidget(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import useResults from "lib/hooks/use-results"; | ||
|
||
export default function Generic() { | ||
const results = useResults({surveyType: "generic"}); | ||
console.log("Generic assessment result:", results); | ||
return ( | ||
<div> | ||
<h1>Generic Report</h1> | ||
<p>This is a placeholder for a generic report. Please customize it as needed.</p> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import PropTypes from "prop-types"; | ||
import style from "./style.scss"; | ||
|
||
export default function Container({children, progress}) { | ||
return ( | ||
<div className={`${style.container}`}> | ||
prd-duy-huynh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{progress < 100 && ( | ||
<div className={style.progressBar}> | ||
<div className={style.progress} style={{width: `${progress}%`}} /> | ||
</div> | ||
)} | ||
{children} | ||
</div> | ||
); | ||
} | ||
|
||
Container.propTypes = { | ||
children: PropTypes.node.isRequired, | ||
progress: PropTypes.number.isRequired | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import {faArrowLeft} from "@fortawesome/free-solid-svg-icons"; | ||
import {useEffect, useState} from "react"; | ||
import Icon from "components/common/icon"; | ||
import Markdown from "components/common/markdown"; | ||
import Modal from "components/common/modal"; | ||
import useAssessment from "lib/hooks/use-assessment"; | ||
import useGraphql from "lib/hooks/use-graphql"; | ||
import useHttp from "lib/hooks/use-http"; | ||
import useDidUpdate from "lib/hooks/use-did-update"; | ||
import useTranslate from "lib/hooks/use-translate"; | ||
import Container from "./container"; | ||
import QuestionSet from "./question-set"; | ||
import style from "./style.scss"; | ||
|
||
export default function Generic() { | ||
const [questionSetIndex, setQuestionSetIndex] = useState(0); | ||
const [answers, setAnswers] = useState([]); | ||
const [showInstructions, setShowInstructions] = useState(false); | ||
const [showConclusions, setShowConclusions] = useState(false); | ||
const [submitAttempts, setSubmitAttempts] = useState(0); | ||
|
||
const assessment = useAssessment({surveyType: "generic"}); | ||
const questionSets = assessment ? assessment.survey.questionSets : []; | ||
const questionCount = questionSets.reduce((count, set) => count + set.questions.length, 0); | ||
const currentQuestionSet = questionSets ? questionSets[questionSetIndex] : {}; | ||
const progress = questionSetIndex >= 0 ? (questionSetIndex / questionSets.length) * 100 : 0; | ||
const finished = questionSets.length > 0 && questionCount === answers.length; | ||
|
||
const graphQL = useGraphql(); | ||
const http = useHttp(); | ||
const translate = useTranslate(); | ||
const props = {progress}; | ||
|
||
const updateAnswer = (questionId, selectedOptionId) => { | ||
const currentAnswers = answers.filter((answer) => answer.questionId !== questionId); | ||
setAnswers([...currentAnswers, | ||
{questionId, selectedResponseOptionId: selectedOptionId}]); | ||
}; | ||
|
||
const next = () => { setQuestionSetIndex(questionSetIndex + 1); }; | ||
const back = () => { setQuestionSetIndex(questionSetIndex - 1); }; | ||
|
||
const onSubmit = () => { | ||
if(submitAttempts > 3) { return; } | ||
const query = graphQL.generic.update; | ||
const variables = { | ||
assessmentID: assessment.id, | ||
answers | ||
}; | ||
|
||
http.post(graphQL.generic.path, {query, variables}).then(({data, errors}) => { | ||
if(!errors && data.submitGenericAssessmentAnswers) { | ||
setShowConclusions(true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the conclusions might need to be in the results component from a candidate's perspective, so here we'd want either cache the results if they come back from this request, or clear the cache (like in the personality survey) so it automatically requests it again and the results component is rendered. This part could go fine for now if you want to focus on results in the next PR |
||
} else { | ||
console.warn(errors || data); // eslint-disable-line no-console | ||
|
||
setTimeout(() => setSubmitAttempts((x) => x + 1), 2000); | ||
} | ||
}); | ||
}; | ||
|
||
useDidUpdate(() => { onSubmit(); }, [submitAttempts]); | ||
|
||
useEffect(() => { | ||
setShowInstructions(true); | ||
}, [assessment]); | ||
|
||
useEffect(() => { | ||
if(!finished) { return; } | ||
onSubmit(); | ||
}, [finished]); | ||
|
||
if(showConclusions) { | ||
return ( | ||
<Container {...props}> | ||
<Markdown className={style.markdown}> | ||
{assessment.survey.conclusions} | ||
</Markdown> | ||
<button type="button" className={style.btnPrimary}>Finished!</button> | ||
</Container> | ||
); | ||
} | ||
|
||
return ( | ||
<Container {...props}> | ||
{currentQuestionSet && ( | ||
<QuestionSet | ||
key={questionSetIndex} | ||
questionSet={currentQuestionSet} | ||
updateAnswer={updateAnswer} | ||
next={next} | ||
/> | ||
)} | ||
|
||
{questionSetIndex > 0 && ( | ||
<button onClick={back} type="button" className={style.back}> | ||
<Icon className={style.icon} alt={translate("back")} icon={faArrowLeft} /> | ||
Go Back | ||
</button> | ||
)} | ||
{showInstructions | ||
&& ( | ||
<Modal | ||
title="Instructions" | ||
onClose={() => setShowInstructions(false)} | ||
containerClass={style.modalContainer} | ||
> | ||
<Markdown> | ||
{assessment.survey.instructions} | ||
</Markdown> | ||
<hr className={style.grayDivider} /> | ||
<div className={style.footer}> | ||
<button | ||
type="button" | ||
className={style.cancelBtn} | ||
onClick={() => setShowInstructions(false)} | ||
> | ||
Cancel | ||
</button> | ||
<button | ||
type="button" | ||
className={style.btnPrimary} | ||
onClick={() => setShowInstructions(false)} | ||
> | ||
{assessment.survey.instructionButton} | ||
</button> | ||
</div> | ||
</Modal> | ||
)} | ||
</Container> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import PropTypes from "prop-types"; | ||
import {useEffect, useState} from "react"; | ||
import Responses from "./responses"; | ||
import style from "./style.scss"; | ||
|
||
export default function QuestionSet({next, questionSet, updateAnswer}) { | ||
const questionSetClass = [style.questionSet].join(" "); | ||
const [selectedOptions, setSelectedOptions] = useState([]); | ||
const setFinished = questionSet.questions.length === selectedOptions.length; | ||
const selectOption = (questionId, optionId) => { | ||
if(!selectedOptions.includes(questionId)) setSelectedOptions([...selectedOptions, questionId]); | ||
updateAnswer(questionId, optionId); | ||
}; | ||
|
||
useEffect(() => { | ||
if(!setFinished) return; | ||
next(); | ||
}, [setFinished]); | ||
|
||
return ( | ||
<div className={questionSetClass}> | ||
<img src={questionSet.setImage} alt={questionSet.text} /> | ||
<hr /> | ||
{questionSet.questions.map((question) => ( | ||
<div key={question.id}> | ||
<h3 className={style.question}>{question.text}</h3> | ||
<Responses | ||
responseOptions={question.responseOptions} | ||
updateAnswer={(optionId) => selectOption(question.id, optionId)} | ||
/> | ||
</div> | ||
))} | ||
</div> | ||
); | ||
} | ||
|
||
QuestionSet.propTypes = { | ||
next: PropTypes.func.isRequired, | ||
questionSet: PropTypes.shape({ | ||
text: PropTypes.string.isRequired, | ||
questions: PropTypes.arrayOf(PropTypes.shape({ | ||
id: PropTypes.string.isRequired, | ||
text: PropTypes.string.isRequired | ||
})).isRequired, | ||
setImage: PropTypes.string.isRequired | ||
}).isRequired, | ||
updateAnswer: PropTypes.func.isRequired | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also auth key