From 07f0f28666efebead8ec5cd1cafeb25149c84271 Mon Sep 17 00:00:00 2001
From: Joshua Chapman <30293265+jchapman68@users.noreply.github.com>
Date: Wed, 23 Aug 2023 11:28:26 +0100
Subject: [PATCH 01/16] Revert "Revert "EAR 1948 Option values for
rad-chk-ans""
---
.../MainNavigation/index.js | 10 ++-
eq-author/src/App/qcodes/QCodesTable/index.js | 82 ++++++++++++++++---
.../src/App/qcodes/QCodesTable/index.test.js | 26 +++---
eq-author/src/App/qcodes/QcodesPage.js | 25 +++++-
eq-author/src/App/qcodes/QcodesPage.test.js | 7 +-
eq-author/src/components/Panel/index.js | 2 +-
.../src/components/QCodeContext/index.js | 69 +++++++++++++++-
eq-author/src/constants/validationMessages.js | 8 +-
.../src/graphql/lists/listAnswer.graphql | 1 +
9 files changed, 195 insertions(+), 35 deletions(-)
diff --git a/eq-author/src/App/QuestionnaireDesignPage/MainNavigation/index.js b/eq-author/src/App/QuestionnaireDesignPage/MainNavigation/index.js
index 90dc9c655b..55a63c011b 100644
--- a/eq-author/src/App/QuestionnaireDesignPage/MainNavigation/index.js
+++ b/eq-author/src/App/QuestionnaireDesignPage/MainNavigation/index.js
@@ -61,6 +61,10 @@ export const UtilityBtns = styled.div`
}
`;
+const StyledIconText = styled(IconText)`
+ line-height: 1.2;
+`;
+
export const UnwrappedMainNavigation = ({
hasQuestionnaire,
totalErrorCount,
@@ -195,9 +199,9 @@ export const UnwrappedMainNavigation = ({
title === "QCodes" || totalErrorCount > 0 || !qcodesEnabled
}
>
-
- QCodes
-
+
+ QCodes and values
+
{qcodesEnabled && hasQCodeError && (
)}
diff --git a/eq-author/src/App/qcodes/QCodesTable/index.js b/eq-author/src/App/qcodes/QCodesTable/index.js
index ab57c0a305..63ce0237f7 100644
--- a/eq-author/src/App/qcodes/QCodesTable/index.js
+++ b/eq-author/src/App/qcodes/QCodesTable/index.js
@@ -47,6 +47,8 @@ import { DRIVING, ANOTHER } from "constants/list-answer-types";
import {
QCODE_IS_NOT_UNIQUE,
QCODE_REQUIRED,
+ VALUE_IS_NOT_UNIQUE,
+ VALUE_REQUIRED,
} from "constants/validationMessages";
const SpacedTableColumn = styled(TableColumn)`
@@ -73,7 +75,7 @@ const StyledTableBody = styled(TableBody)`
background-color: white;
`;
-const QcodeValidationError = styled(ValidationError)`
+const StyledValidationError = styled(ValidationError)`
justify-content: unset;
margin: 0;
padding-top: 0.2em;
@@ -115,13 +117,16 @@ const Row = memo((props) => {
questionShortCode,
label,
qCode: initialQcode,
+ value: initialValue,
type,
errorMessage,
+ valueErrorMessage,
option,
secondary,
listAnswerType,
drivingQCode,
anotherQCode,
+ hideOptionValue,
} = props;
// Uses different initial QCode depending on the QCode defined in the props
@@ -137,6 +142,10 @@ const Row = memo((props) => {
const [updateListCollector] = useMutation(UPDATE_LIST_COLLECTOR_PAGE, {
refetchQueries: ["GetQuestionnaire"],
});
+ const [value, setValue] = useState(initialValue);
+ const [updateValue] = useMutation(UPDATE_OPTION_QCODE, {
+ refetchQueries: ["GetQuestionnaire"],
+ });
const handleBlur = useCallback(
(qCode) => {
@@ -170,6 +179,13 @@ const Row = memo((props) => {
]
);
+ const handleBlurOptionValue = useCallback(
+ (value) => {
+ updateValue(mutationVariables({ id, value }));
+ },
+ [id, updateValue]
+ );
+
return (
{questionShortCode || questionTitle ? (
@@ -204,7 +220,7 @@ const Row = memo((props) => {
aria-label="QCode input field"
/>
{errorMessage && (
- {errorMessage}
+ {errorMessage}
)}
)
@@ -222,9 +238,28 @@ const Row = memo((props) => {
aria-label="QCode input field"
/>
{errorMessage && (
- {errorMessage}
+ {errorMessage}
+ )}
+
+ )}
+ {[CHECKBOX_OPTION, RADIO_OPTION, SELECT_OPTION].includes(type) &&
+ !hideOptionValue ? (
+
+ setValue(e.value)}
+ onBlur={() => handleBlurOptionValue(value)}
+ hasError={Boolean(valueErrorMessage)}
+ aria-label="Option Value input field"
+ />
+ {valueErrorMessage && (
+ {valueErrorMessage}
)}
+ ) : (
+
)}
);
@@ -237,35 +272,59 @@ Row.propTypes = {
questionShortCode: PropTypes.string,
label: PropTypes.string,
qCode: PropTypes.string,
+ value: PropTypes.string,
type: PropTypes.string,
qCodeCheck: PropTypes.func,
errorMessage: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
+ valueErrorMessage: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
secondary: PropTypes.bool,
option: PropTypes.bool,
listAnswerType: PropTypes.string,
drivingQCode: PropTypes.string,
anotherQCode: PropTypes.string,
+ hideOptionValue: PropTypes.bool,
};
export const QCodeTable = () => {
- const { answerRows, duplicatedQCodes, dataVersion } = useQCodeContext();
+ const { answerRows, duplicatedQCodes, dataVersion, duplicatedOptionValues } =
+ useQCodeContext();
const getErrorMessage = (qCode) =>
(!qCode && QCODE_REQUIRED) ||
(duplicatedQCodes.includes(qCode) && QCODE_IS_NOT_UNIQUE);
+ const getValueErrorMessage = (value, idValue) =>
+ (!value && VALUE_REQUIRED) ||
+ (duplicatedOptionValues.includes(idValue) && VALUE_IS_NOT_UNIQUE);
+
+ let currentQuestionId = "";
+ let idValue = "";
return (
- Short code
- Question
- Type
- Answer label
- Qcode
+ Short code
+ Question
+ Answer Type
+ Answer label
+ Q code for answer type
+
+ Value for checkbox, radio and select answer labels
+
{answerRows?.map((item, index) => {
+ if (
+ ![CHECKBOX_OPTION, RADIO_OPTION, SELECT_OPTION].includes(item.type)
+ ) {
+ currentQuestionId = item.id ? item.id : "";
+ }
+ if (
+ item.value &&
+ [CHECKBOX_OPTION, RADIO_OPTION, SELECT_OPTION].includes(item.type)
+ ) {
+ idValue = currentQuestionId.concat(item.value);
+ }
if (
item.additionalAnswer &&
(dataVersion === "3" || item.type !== "CheckboxOption")
@@ -277,12 +336,14 @@ export const QCodeTable = () => {
dataVersion={dataVersion}
{...item}
errorMessage={getErrorMessage(item.qCode)}
+ valueErrorMessage={getValueErrorMessage(item.value, idValue)}
/>
>
);
@@ -293,8 +354,9 @@ export const QCodeTable = () => {
dataVersion={dataVersion}
{...item}
errorMessage={getErrorMessage(
- item.qCode ?? item.drivingQCode ?? item.anotherQCode // Uses a different QCode depending on the QCode defined in item
+ item.qCode ?? item.drivingQCode ?? item.anotherQCode
)}
+ valueErrorMessage={getValueErrorMessage(item.value, idValue)}
/>
);
}
diff --git a/eq-author/src/App/qcodes/QCodesTable/index.test.js b/eq-author/src/App/qcodes/QCodesTable/index.test.js
index 0b0f974879..b1a39c65ff 100644
--- a/eq-author/src/App/qcodes/QCodesTable/index.test.js
+++ b/eq-author/src/App/qcodes/QCodesTable/index.test.js
@@ -138,11 +138,13 @@ const optionsSetup = (dataVersion) => {
id: "checkbox-option-1-id",
label: "checkbox-option-1-label",
qCode: "option-1",
+ value: "option-1",
},
{
id: "checkbox-option-2-id",
label: "checkbox-option-2-label",
qCode: "option-2",
+ value: "option-2",
},
],
mutuallyExclusiveOption: {
@@ -150,6 +152,7 @@ const optionsSetup = (dataVersion) => {
label: "Mutually-exclusive-option-label",
mutuallyExclusive: true,
qCode: "mutually-exclusive-option",
+ value: "mutually-exclusive-option",
},
})
);
@@ -189,9 +192,10 @@ describe("Qcode Table", () => {
const fieldHeadings = [
"Short code",
"Question",
- "Type",
+ "Answer Type",
"Answer label",
- "Qcode",
+ "Q code for answer type",
+ "Value for checkbox, radio and select answer labels",
];
fieldHeadings.forEach((heading) => expect(getByText(heading)).toBeTruthy());
});
@@ -205,7 +209,7 @@ describe("Qcode Table", () => {
const questionnaire = buildQuestionnaire({ answerCount: 1 });
questionnaire.sections[0].folders[0].pages[0].answers[0].qCode = "";
const { getAllByText } = renderWithContext({ questionnaire });
- expect(getAllByText("Qcode required")).toBeTruthy();
+ expect(getAllByText("Q code required")).toBeTruthy();
});
it("should not save qCode if it is the same as the initial qCode", () => {
@@ -670,14 +674,14 @@ describe("Qcode Table", () => {
utils = optionsSetup("3");
});
- it("should display answer qCodes without option qCodes for checkbox answers in data version 3", () => {
+ it("should display answer qCodes and option values for checkbox answers in data version 3", () => {
expect(
- utils.queryByTestId("checkbox-option-1-id-test-input")
- ).not.toBeInTheDocument();
+ utils.queryByTestId("checkbox-option-1-id-value-test-input")
+ ).toBeInTheDocument();
expect(
- utils.queryByTestId("checkbox-option-2-id-test-input")
- ).not.toBeInTheDocument();
+ utils.queryByTestId("checkbox-option-2-id-value-test-input")
+ ).toBeInTheDocument();
expect(
utils.getByTestId("checkbox-answer-id-test-input")
@@ -707,7 +711,7 @@ describe("Qcode Table", () => {
questionnaire.sections[0].folders[0].pages[0].answers[0].qCode = "";
questionnaire.dataVersion = "3";
const { getAllByText } = renderWithContext({ questionnaire });
- expect(getAllByText("Qcode required")).toBeTruthy();
+ expect(getAllByText("Q code required")).toBeTruthy();
});
it("should render a validation error when duplicate qCodes are present in data version 3", () => {
@@ -740,7 +744,7 @@ describe("Qcode Table", () => {
questionnaire.sections[0].folders[0].pages[0].answers[0].options[0] =
option;
const { getAllByText } = renderWithContext({ questionnaire });
- expect(getAllByText("Qcode required")).toBeTruthy();
+ expect(getAllByText("Q code required")).toBeTruthy();
});
it("should map qCode rows when additional answer is set to true and answer type is not checkbox option", () => {
@@ -766,7 +770,7 @@ describe("Qcode Table", () => {
questionnaire.sections[0].folders[0].pages[0].answers[0].options[0] =
option;
const { getAllByText } = renderWithContext({ questionnaire });
- expect(getAllByText("Qcode required")).toBeTruthy();
+ expect(getAllByText("Q code required")).toBeTruthy();
});
describe("List collector questions", () => {
diff --git a/eq-author/src/App/qcodes/QcodesPage.js b/eq-author/src/App/qcodes/QcodesPage.js
index a1bc220d1e..624639238f 100644
--- a/eq-author/src/App/qcodes/QcodesPage.js
+++ b/eq-author/src/App/qcodes/QcodesPage.js
@@ -7,6 +7,8 @@ import { Grid } from "components/Grid";
import { colors } from "constants/theme";
import MainCanvas from "components/MainCanvas";
import QcodesTable from "./QCodesTable";
+import { InformationPanel } from "components/Panel";
+import Panel from "components-themed/panels";
const Container = styled.div`
display: flex;
@@ -17,7 +19,7 @@ const Container = styled.div`
const StyledGrid = styled(Grid)`
overflow: hidden;
- padding-top: 2em;
+ padding-top: 0em;
&:focus-visible {
border: 3px solid ${colors.focus};
margin: 0;
@@ -30,9 +32,28 @@ const StyledMainCanvas = styled(MainCanvas)`
max-width: 80em;
`;
+const Padding = styled.div`
+ margin: 2em auto 1em;
+ eidth: 100%;
+ padding: 0 0.5em 0 1em;
+ max-width: 80em;
+`;
+
const QcodesPage = () => (
-
+
+
+
+ Unique Q codes must be assigned to each answer type.
+
+ Unique values must be assigned to allow downstream processing of
+ checkbox, radio and select answer labels.
+
+
+ For live or ongoing surveys, only change the Q code or value if the
+ context of the question or answer label has changed.
+
+
diff --git a/eq-author/src/App/qcodes/QcodesPage.test.js b/eq-author/src/App/qcodes/QcodesPage.test.js
index fa8f6787d2..42786a9bac 100644
--- a/eq-author/src/App/qcodes/QcodesPage.test.js
+++ b/eq-author/src/App/qcodes/QcodesPage.test.js
@@ -4,6 +4,7 @@ import { useMutation } from "@apollo/react-hooks";
import QcodesPage from "./QcodesPage";
import { QCodeContextProvider } from "components/QCodeContext";
import { buildQuestionnaire } from "tests/utils/createMockQuestionnaire";
+import Theme from "contexts/themeContext";
jest.mock("@apollo/react-hooks", () => ({
useMutation: jest.fn(),
@@ -14,14 +15,14 @@ useMutation.mockImplementation(jest.fn(() => [jest.fn()]));
describe("Qcodes Page", () => {
const questionnaire = buildQuestionnaire({ answerCount: 1 });
- const renderQcodesPage = () =>
+ const renderQcodesPage = (component) =>
render(
-
+ {component}
);
it("should render Qcodes page", () => {
- const { getByTestId } = renderQcodesPage();
+ const { getByTestId } = renderQcodesPage();
expect(getByTestId("qcodes-page-container")).toBeInTheDocument();
});
});
diff --git a/eq-author/src/components/Panel/index.js b/eq-author/src/components/Panel/index.js
index c7db01b58a..2b5f2cb072 100644
--- a/eq-author/src/components/Panel/index.js
+++ b/eq-author/src/components/Panel/index.js
@@ -31,7 +31,7 @@ const InformationPanel = ({ children }) => {
);
};
InformationPanel.propTypes = {
- children: PropTypes.string.isRequired,
+ children: PropTypes.node.isRequired,
};
export { Panel, InformationPanel };
diff --git a/eq-author/src/components/QCodeContext/index.js b/eq-author/src/components/QCodeContext/index.js
index a89185d8de..99f074ce95 100644
--- a/eq-author/src/components/QCodeContext/index.js
+++ b/eq-author/src/components/QCodeContext/index.js
@@ -10,6 +10,7 @@ import {
RADIO,
MUTUALLY_EXCLUSIVE,
ANSWER_OPTION_TYPES,
+ SELECT,
SELECT_OPTION,
} from "constants/answer-types";
@@ -61,11 +62,13 @@ const formatListCollector = (listCollectorPage) => [
label: listCollectorPage.drivingPositive,
type: RADIO_OPTION,
option: true,
+ hideOptionValue: true,
},
{
label: listCollectorPage.drivingNegative,
type: RADIO_OPTION,
option: true,
+ hideOptionValue: true,
},
{
id: listCollectorPage.id,
@@ -79,11 +82,13 @@ const formatListCollector = (listCollectorPage) => [
label: listCollectorPage.anotherPositive,
type: RADIO_OPTION,
option: true,
+ hideOptionValue: true,
},
{
label: listCollectorPage.anotherNegative,
type: RADIO_OPTION,
option: true,
+ hideOptionValue: true,
},
];
@@ -184,11 +189,51 @@ const getEmptyQCodes = (answerRows, dataVersion) => {
else {
return answerRows?.find(
({ qCode, type }) =>
- !qCode && ![CHECKBOX, RADIO_OPTION, SELECT_OPTION].includes(type)
+ !qCode &&
+ ![CHECKBOX, CHECKBOX_OPTION, RADIO_OPTION, SELECT_OPTION].includes(type)
);
}
};
+// getDuplicatedOptionValues :: [AnswerRow] -> [Value]
+// Return an array of Values which are duplicated within an answer in the given list of answer rows
+export const getDuplicatedOptionValues = (flattenedAnswers) => {
+ // acc - accumulator
+ let currentQuestionId = "";
+ let idValue = "";
+ const optionValueUsageMap = flattenedAnswers?.reduce(
+ (acc, { value, type, id }) => {
+ if ([RADIO, CHECKBOX, SELECT].includes(type)) {
+ currentQuestionId = id;
+ }
+ if (
+ value &&
+ [CHECKBOX_OPTION, RADIO_OPTION, SELECT_OPTION].includes(type)
+ ) {
+ idValue = currentQuestionId.concat(value);
+ const currentValue = acc.get(idValue);
+ acc.set(idValue, currentValue ? currentValue + 1 : 1);
+ }
+ return acc;
+ },
+ new Map()
+ );
+
+ return Array.from(optionValueUsageMap).reduce(
+ (acc, [value, count]) => (count > 1 ? [...acc, value] : acc),
+ []
+ );
+};
+
+const getEmptyOptionValues = (answerRows) => {
+ return answerRows?.find(
+ ({ value, type, hideOptionValue }) =>
+ !value &&
+ [CHECKBOX_OPTION, RADIO_OPTION, SELECT_OPTION].includes(type) &&
+ !hideOptionValue
+ );
+};
+
export const QCodeContextProvider = ({ questionnaire = {}, children }) => {
const answerRows = useMemo(
() => getFlattenedAnswerRows(questionnaire) ?? [],
@@ -202,7 +247,16 @@ export const QCodeContextProvider = ({ questionnaire = {}, children }) => {
const hasQCodeError =
duplicatedQCodes?.length ||
- getEmptyQCodes(answerRows, questionnaire.dataVersion);
+ getEmptyQCodes(answerRows, questionnaire.dataVersion) ||
+ getEmptyOptionValues(answerRows);
+
+ const duplicatedOptionValues = useMemo(
+ () => getDuplicatedOptionValues(answerRows) ?? [],
+ [answerRows]
+ );
+
+ const hasOptionValueError =
+ duplicatedOptionValues?.length || getEmptyOptionValues(answerRows);
const dataVersion = questionnaire?.dataVersion;
@@ -212,8 +266,17 @@ export const QCodeContextProvider = ({ questionnaire = {}, children }) => {
duplicatedQCodes,
dataVersion,
hasQCodeError,
+ duplicatedOptionValues,
+ hasOptionValueError,
}),
- [answerRows, duplicatedQCodes, dataVersion, hasQCodeError]
+ [
+ answerRows,
+ duplicatedQCodes,
+ dataVersion,
+ hasQCodeError,
+ duplicatedOptionValues,
+ hasOptionValueError,
+ ]
);
return (
diff --git a/eq-author/src/constants/validationMessages.js b/eq-author/src/constants/validationMessages.js
index f5185a7a52..faab3d5495 100644
--- a/eq-author/src/constants/validationMessages.js
+++ b/eq-author/src/constants/validationMessages.js
@@ -202,8 +202,12 @@ export const dynamicAnswer = {
ERR_REFERENCE_MOVED: "Answer must be from a previous question",
};
-export const QCODE_IS_NOT_UNIQUE = "Qcode must be unique";
-export const QCODE_REQUIRED = "Qcode required";
+export const QCODE_IS_NOT_UNIQUE =
+ "This Q code has been assigned to another answer type. Enter a unique Q code.";
+export const QCODE_REQUIRED = "Q code required";
+export const VALUE_IS_NOT_UNIQUE =
+ "This value has been assigned to another option for this answer type. Enter a unique value.";
+export const VALUE_REQUIRED = "Value required";
export const QUESTION_ANSWER_NOT_SELECTED = "Answer required";
export const CALCSUM_ANSWER_NOT_SELECTED =
"Select at least two answers to be calculated";
diff --git a/eq-author/src/graphql/lists/listAnswer.graphql b/eq-author/src/graphql/lists/listAnswer.graphql
index 366c51fc72..e13e4e916c 100644
--- a/eq-author/src/graphql/lists/listAnswer.graphql
+++ b/eq-author/src/graphql/lists/listAnswer.graphql
@@ -79,6 +79,7 @@ fragment ListAnswer on Answer {
mutuallyExclusive
label
description
+ value
validationErrorInfo {
...ValidationErrorInfo
}
From 25ab5348bd4e4c2c8fbe945e5b479c1de9caa547 Mon Sep 17 00:00:00 2001
From: farres1
Date: Thu, 1 Feb 2024 12:26:55 +0000
Subject: [PATCH 02/16] Add space to Q Codes in main navigation bar
---
.../src/App/QuestionnaireDesignPage/MainNavigation/index.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/eq-author/src/App/QuestionnaireDesignPage/MainNavigation/index.js b/eq-author/src/App/QuestionnaireDesignPage/MainNavigation/index.js
index 55a63c011b..0b32242675 100644
--- a/eq-author/src/App/QuestionnaireDesignPage/MainNavigation/index.js
+++ b/eq-author/src/App/QuestionnaireDesignPage/MainNavigation/index.js
@@ -200,7 +200,7 @@ export const UnwrappedMainNavigation = ({
}
>
- QCodes and values
+ Q Codes and values
{qcodesEnabled && hasQCodeError && (
From 88d9a0d8d9f6b11dc234bfe0804135ba1825476b Mon Sep 17 00:00:00 2001
From: farres1
Date: Thu, 1 Feb 2024 12:27:14 +0000
Subject: [PATCH 03/16] Fix width
---
eq-author/src/App/qcodes/QcodesPage.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/eq-author/src/App/qcodes/QcodesPage.js b/eq-author/src/App/qcodes/QcodesPage.js
index 624639238f..38766ae8ba 100644
--- a/eq-author/src/App/qcodes/QcodesPage.js
+++ b/eq-author/src/App/qcodes/QcodesPage.js
@@ -34,7 +34,7 @@ const StyledMainCanvas = styled(MainCanvas)`
const Padding = styled.div`
margin: 2em auto 1em;
- eidth: 100%;
+ width: 100%;
padding: 0 0.5em 0 1em;
max-width: 80em;
`;
From 3202e09d753a862fa3c1d1093a403d678a743ba8 Mon Sep 17 00:00:00 2001
From: sudeep
Date: Tue, 19 Mar 2024 11:51:11 +0000
Subject: [PATCH 04/16] add mutually exclusive answers in table rows
Signed-off-by: sudeep
---
eq-author/src/components/QCodeContext/index.js | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/eq-author/src/components/QCodeContext/index.js b/eq-author/src/components/QCodeContext/index.js
index 1d4a2bd722..c9cdc894e9 100644
--- a/eq-author/src/components/QCodeContext/index.js
+++ b/eq-author/src/components/QCodeContext/index.js
@@ -39,11 +39,14 @@ export const flattenAnswer = (answer) =>
option: true,
}
) ?? []),
- answer.mutuallyExclusiveOption && {
- ...answer.mutuallyExclusiveOption,
- type: "MutuallyExclusiveOption",
- option: true,
- },
+ ...(answer.options?.map(
+ (option) =>
+ answer.type === MUTUALLY_EXCLUSIVE && {
+ ...option,
+ type: "MutuallyExclusiveOption",
+ option: true,
+ }
+ ) ?? []),
answer.secondaryLabel && {
...answer,
label: answer.secondaryLabel,
From 70dcf4cd211e207dc2a0d35ff506f76ae7f881c9 Mon Sep 17 00:00:00 2001
From: sudeep
Date: Tue, 19 Mar 2024 11:51:40 +0000
Subject: [PATCH 05/16] display mutually exclusive options
Signed-off-by: sudeep
---
eq-author/src/App/qcodes/QCodesTable/index.js | 22 +++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/eq-author/src/App/qcodes/QCodesTable/index.js b/eq-author/src/App/qcodes/QCodesTable/index.js
index 63ce0237f7..7f4fce5358 100644
--- a/eq-author/src/App/qcodes/QCodesTable/index.js
+++ b/eq-author/src/App/qcodes/QCodesTable/index.js
@@ -204,7 +204,12 @@ const Row = memo((props) => {
{TYPE_TO_DESCRIPTION[type]}
{stripHtmlToText(label)}
{dataVersion === "3" ? (
- [CHECKBOX_OPTION, RADIO_OPTION, SELECT_OPTION].includes(type) ? (
+ [
+ CHECKBOX_OPTION,
+ RADIO_OPTION,
+ SELECT_OPTION,
+ MUTUALLY_EXCLUSIVE_OPTION,
+ ].includes(type) ? (
) : (
@@ -224,7 +229,12 @@ const Row = memo((props) => {
)}
)
- ) : [CHECKBOX, RADIO_OPTION, SELECT_OPTION].includes(type) ? (
+ ) : [
+ CHECKBOX,
+ RADIO_OPTION,
+ SELECT_OPTION,
+ MUTUALLY_EXCLUSIVE_OPTION,
+ ].includes(type) ? (
) : (
@@ -242,8 +252,12 @@ const Row = memo((props) => {
)}
)}
- {[CHECKBOX_OPTION, RADIO_OPTION, SELECT_OPTION].includes(type) &&
- !hideOptionValue ? (
+ {[
+ CHECKBOX_OPTION,
+ RADIO_OPTION,
+ SELECT_OPTION,
+ MUTUALLY_EXCLUSIVE_OPTION,
+ ].includes(type) && !hideOptionValue ? (
Date: Tue, 19 Mar 2024 11:52:45 +0000
Subject: [PATCH 06/16] update tests for mutually exclusive answers
Signed-off-by: sudeep
---
.../src/App/qcodes/QCodesTable/index.test.js | 69 ++++++++++++++-----
1 file changed, 50 insertions(+), 19 deletions(-)
diff --git a/eq-author/src/App/qcodes/QCodesTable/index.test.js b/eq-author/src/App/qcodes/QCodesTable/index.test.js
index b1a39c65ff..bede6eae6e 100644
--- a/eq-author/src/App/qcodes/QCodesTable/index.test.js
+++ b/eq-author/src/App/qcodes/QCodesTable/index.test.js
@@ -105,7 +105,7 @@ const textSetup = () => {
};
const optionsSetup = (dataVersion) => {
- const questionnaire = buildQuestionnaire({ answerCount: 2 });
+ const questionnaire = buildQuestionnaire({ answerCount: 3 });
Object.assign(questionnaire.sections[0].folders[0].pages[0], {
alias: "multiple-choice-answer-types-alias",
title: "Multiple choice answer types
",
@@ -147,13 +147,35 @@ const optionsSetup = (dataVersion) => {
value: "option-2",
},
],
- mutuallyExclusiveOption: {
- id: "checkbox-option-3-id",
- label: "Mutually-exclusive-option-label",
- mutuallyExclusive: true,
- qCode: "mutually-exclusive-option",
- value: "mutually-exclusive-option",
- },
+ })
+ );
+
+ Object.assign(
+ questionnaire.sections[0].folders[0].pages[0].answers[2],
+ (questionnaire.dataVersion = dataVersion),
+ generateAnswer({
+ qCode: "mutually-exclusive-1",
+ label: "mutually-exclusive-1-label",
+ type: "MutuallyExclusive",
+ options: [
+ {
+ qCode: "",
+ description: null,
+ label: "OR1",
+ additionalAnswer: null,
+ id: "9d6d013d-64c0-4757-b8e3-f991e2dcfb92",
+ value: "m1-value",
+ },
+ {
+ qCode: "",
+ description: null,
+ label: "OR2",
+ additionalAnswer: null,
+ id: "4b362d2e-1ec0-4cb2-b90d-572be5133ab2",
+ value: "m1-value",
+ },
+ ],
+ id: "mutually-exclusive-option-id",
})
);
@@ -605,15 +627,15 @@ describe("Qcode Table", () => {
describe("options", () => {
it("should display type", () => {
expect(utils.getAllByText(/Checkbox option/)).toHaveLength(2);
- expect(utils.getByText(/Mutually exclusive/)).toBeVisible();
+ expect(
+ utils.getAllByText(/Mutually exclusive option/)
+ ).toHaveLength(2);
});
it("should display answer label", () => {
expect(utils.getByText(/checkbox-option-1-label/)).toBeVisible();
expect(utils.getByText(/checkbox-option-2-label/)).toBeVisible();
- expect(
- utils.getByText(/Mutually-exclusive-option-label/)
- ).toBeVisible();
+ expect(utils.getByText(/mutually-exclusive-1-label/)).toBeVisible();
});
it("should display answer qCode", () => {
@@ -624,8 +646,8 @@ describe("Qcode Table", () => {
utils.getByTestId("checkbox-option-2-id-test-input").value
).toEqual("option-2");
expect(
- utils.getByTestId("checkbox-option-3-id-test-input").value
- ).toEqual("mutually-exclusive-option");
+ utils.getByTestId("mutually-exclusive-option-id-test-input").value
+ ).toEqual("mutually-exclusive-1");
});
it("should save qCode for option", () => {
@@ -647,17 +669,26 @@ describe("Qcode Table", () => {
it("should save qCode for mutually exclusive option", () => {
fireEvent.change(
- utils.getByTestId("checkbox-option-3-id-test-input"),
+ utils.getByTestId("mutually-exclusive-option-id-test-input"),
{
target: { value: "187" },
}
);
+ fireEvent.change(
+ utils.getByTestId("mutually-exclusive-option-id-test-input"),
+ {
+ target: { value: "mutually-exclusive-new-1" },
+ }
+ );
fireEvent.blur(
- utils.getByTestId("checkbox-option-3-id-test-input")
+ utils.getByTestId("mutually-exclusive-option-id-test-input")
);
expect(mock).toHaveBeenCalledWith({
variables: {
- input: { id: "checkbox-option-3-id", qCode: "187" },
+ input: {
+ id: "mutually-exclusive-option-id",
+ qCode: "mutually-exclusive-new-1",
+ },
},
});
});
@@ -688,8 +719,8 @@ describe("Qcode Table", () => {
).toBeInTheDocument();
expect(
- utils.getByTestId("checkbox-option-3-id-test-input").value
- ).toEqual("mutually-exclusive-option");
+ utils.getByTestId("mutually-exclusive-option-id-test-input").value
+ ).toEqual("mutually-exclusive-1");
});
it("should save qCode for checkbox answer", () => {
From 05a5b4e9828f6f469e80723dd6ad89f686145269 Mon Sep 17 00:00:00 2001
From: sudeep
Date: Tue, 19 Mar 2024 17:41:52 +0000
Subject: [PATCH 07/16] added new utility function
Signed-off-by: sudeep
---
eq-author/src/utils/questionnaireUtils/index.js | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/eq-author/src/utils/questionnaireUtils/index.js b/eq-author/src/utils/questionnaireUtils/index.js
index 26929a1a44..3bc75394a0 100644
--- a/eq-author/src/utils/questionnaireUtils/index.js
+++ b/eq-author/src/utils/questionnaireUtils/index.js
@@ -36,6 +36,11 @@ export const getPageByAnswerId = (questionnaire, id) =>
getPages(questionnaire)?.find(({ answers }) =>
answers?.some((answer) => answer.id === id)
);
+export const getAnswerByOptionId = (questionnaire, id) =>
+ getAnswers(questionnaire)?.find(
+ (answer) =>
+ answer.options && answer.options?.some((option) => option.id === id)
+ );
export const getPageByConfirmationId = (questionnaire, id) =>
getPages(questionnaire)?.find(({ confirmation }) => confirmation.id === id);
From 3d172400e94d4e3ff74a4c46c4b326e55b4531a8 Mon Sep 17 00:00:00 2001
From: sudeep
Date: Tue, 19 Mar 2024 17:42:33 +0000
Subject: [PATCH 08/16] corrected version display and validation error
Signed-off-by: sudeep
---
eq-author/src/App/qcodes/QCodesTable/index.js | 66 +++++++++++++++----
1 file changed, 52 insertions(+), 14 deletions(-)
diff --git a/eq-author/src/App/qcodes/QCodesTable/index.js b/eq-author/src/App/qcodes/QCodesTable/index.js
index 7f4fce5358..2cff0df929 100644
--- a/eq-author/src/App/qcodes/QCodesTable/index.js
+++ b/eq-author/src/App/qcodes/QCodesTable/index.js
@@ -51,6 +51,12 @@ import {
VALUE_REQUIRED,
} from "constants/validationMessages";
+import {
+ getPageByAnswerId,
+ getAnswerByOptionId,
+} from "utils/questionnaireUtils";
+import { useQuestionnaire } from "components/QuestionnaireContext";
+
const SpacedTableColumn = styled(TableColumn)`
padding: 0.5em 0.5em 0.2em;
color: ${colors.text};
@@ -252,12 +258,14 @@ const Row = memo((props) => {
)}
)}
- {[
+ {dataVersion === "3" &&
+ [
CHECKBOX_OPTION,
RADIO_OPTION,
SELECT_OPTION,
MUTUALLY_EXCLUSIVE_OPTION,
- ].includes(type) && !hideOptionValue ? (
+ ].includes(type) &&
+ !hideOptionValue ? (
{
+ const { questionnaire } = useQuestionnaire();
const { answerRows, duplicatedQCodes, dataVersion, duplicatedOptionValues } =
useQCodeContext();
const getErrorMessage = (qCode) =>
@@ -315,27 +324,56 @@ export const QCodeTable = () => {
return (
-
- Short code
- Question
- Answer Type
- Answer label
- Q code for answer type
-
- Value for checkbox, radio and select answer labels
-
-
+ {dataVersion === "3" ? (
+
+ Short code
+ Question
+ Answer Type
+ Answer label
+
+ Q code for answer type
+
+
+ Value for checkbox, radio and select answer labels
+
+
+ ) : (
+
+ Short code
+ Question
+ Answer Type
+ Answer label
+
+ Q code for answer type
+
+
+ )}
{answerRows?.map((item, index) => {
if (
- ![CHECKBOX_OPTION, RADIO_OPTION, SELECT_OPTION].includes(item.type)
+ ![
+ CHECKBOX_OPTION,
+ RADIO_OPTION,
+ SELECT_OPTION,
+ MUTUALLY_EXCLUSIVE_OPTION,
+ ].includes(item.type)
) {
currentQuestionId = item.id ? item.id : "";
}
+ if ([MUTUALLY_EXCLUSIVE_OPTION].includes(item.type)) {
+ const answer = getAnswerByOptionId(questionnaire, item.id);
+ const page = getPageByAnswerId(questionnaire, answer.id);
+ currentQuestionId = page.answers[0]?.id;
+ }
if (
item.value &&
- [CHECKBOX_OPTION, RADIO_OPTION, SELECT_OPTION].includes(item.type)
+ [
+ CHECKBOX_OPTION,
+ RADIO_OPTION,
+ SELECT_OPTION,
+ MUTUALLY_EXCLUSIVE_OPTION,
+ ].includes(item.type)
) {
idValue = currentQuestionId.concat(item.value);
}
From 8366e5a786c4b62d9ef8491b13aeb383e447b4df Mon Sep 17 00:00:00 2001
From: sudeep
Date: Tue, 19 Mar 2024 17:43:19 +0000
Subject: [PATCH 09/16] corrected context to check duplicates
Signed-off-by: sudeep
---
.../src/components/QCodeContext/index.js | 22 ++++++++++++++-----
1 file changed, 17 insertions(+), 5 deletions(-)
diff --git a/eq-author/src/components/QCodeContext/index.js b/eq-author/src/components/QCodeContext/index.js
index c9cdc894e9..b32527d00e 100644
--- a/eq-author/src/components/QCodeContext/index.js
+++ b/eq-author/src/components/QCodeContext/index.js
@@ -1,7 +1,7 @@
import React, { createContext, useContext, useMemo } from "react";
import PropTypes from "prop-types";
import CustomPropTypes from "custom-prop-types";
-import { getPages } from "utils/questionnaireUtils";
+import { getPages, getPageByAnswerId } from "utils/questionnaireUtils";
import {
RADIO_OPTION,
@@ -12,6 +12,7 @@ import {
ANSWER_OPTION_TYPES,
SELECT,
SELECT_OPTION,
+ MUTUALLY_EXCLUSIVE_OPTION,
} from "constants/answer-types";
import {
@@ -205,7 +206,7 @@ const getEmptyQCodes = (answerRows, dataVersion) => {
// getDuplicatedOptionValues :: [AnswerRow] -> [Value]
// Return an array of Values which are duplicated within an answer in the given list of answer rows
-export const getDuplicatedOptionValues = (flattenedAnswers) => {
+export const getDuplicatedOptionValues = (flattenedAnswers, questionnaire) => {
// acc - accumulator
let currentQuestionId = "";
let idValue = "";
@@ -214,9 +215,20 @@ export const getDuplicatedOptionValues = (flattenedAnswers) => {
if ([RADIO, CHECKBOX, SELECT].includes(type)) {
currentQuestionId = id;
}
+
+ if ([MUTUALLY_EXCLUSIVE].includes(type)) {
+ const page = getPageByAnswerId(questionnaire, id);
+ currentQuestionId = page.answers[0]?.id;
+ }
+
if (
value &&
- [CHECKBOX_OPTION, RADIO_OPTION, SELECT_OPTION].includes(type)
+ [
+ CHECKBOX_OPTION,
+ RADIO_OPTION,
+ SELECT_OPTION,
+ MUTUALLY_EXCLUSIVE_OPTION,
+ ].includes(type)
) {
idValue = currentQuestionId.concat(value);
const currentValue = acc.get(idValue);
@@ -259,8 +271,8 @@ export const QCodeContextProvider = ({ questionnaire = {}, children }) => {
getEmptyOptionValues(answerRows);
const duplicatedOptionValues = useMemo(
- () => getDuplicatedOptionValues(answerRows) ?? [],
- [answerRows]
+ () => getDuplicatedOptionValues(answerRows, questionnaire) ?? [],
+ [answerRows, questionnaire]
);
const hasOptionValueError =
From de0dfc4f15cc4919fc1b565fcced1e0d8c90fe74 Mon Sep 17 00:00:00 2001
From: sudeep
Date: Tue, 19 Mar 2024 18:18:32 +0000
Subject: [PATCH 10/16] corrected test
Signed-off-by: sudeep
---
eq-author/src/App/qcodes/QCodesTable/index.test.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/eq-author/src/App/qcodes/QCodesTable/index.test.js b/eq-author/src/App/qcodes/QCodesTable/index.test.js
index bede6eae6e..9946524a31 100644
--- a/eq-author/src/App/qcodes/QCodesTable/index.test.js
+++ b/eq-author/src/App/qcodes/QCodesTable/index.test.js
@@ -217,7 +217,6 @@ describe("Qcode Table", () => {
"Answer Type",
"Answer label",
"Q code for answer type",
- "Value for checkbox, radio and select answer labels",
];
fieldHeadings.forEach((heading) => expect(getByText(heading)).toBeTruthy());
});
From 1927ec19f40f99e8e5d43ba69edfd6c2a325b5ed Mon Sep 17 00:00:00 2001
From: sudeep
Date: Wed, 20 Mar 2024 12:32:09 +0000
Subject: [PATCH 11/16] solved qcodes error badge
Signed-off-by: sudeep
---
.../src/components/QCodeContext/index.js | 26 ++++++++++++++-----
1 file changed, 19 insertions(+), 7 deletions(-)
diff --git a/eq-author/src/components/QCodeContext/index.js b/eq-author/src/components/QCodeContext/index.js
index b32527d00e..21a36e9575 100644
--- a/eq-author/src/components/QCodeContext/index.js
+++ b/eq-author/src/components/QCodeContext/index.js
@@ -190,7 +190,12 @@ const getEmptyQCodes = (answerRows, dataVersion) => {
return answerRows?.find(
({ qCode, drivingQCode, anotherQCode, type }) =>
!(qCode || drivingQCode || anotherQCode) &&
- ![CHECKBOX_OPTION, RADIO_OPTION, SELECT_OPTION].includes(type)
+ ![
+ CHECKBOX_OPTION,
+ RADIO_OPTION,
+ SELECT_OPTION,
+ MUTUALLY_EXCLUSIVE_OPTION,
+ ].includes(type)
);
}
// If dataVersion is not 3, checkbox answers and radio options do not have QCodes, and therefore these can be empty
@@ -199,7 +204,13 @@ const getEmptyQCodes = (answerRows, dataVersion) => {
return answerRows?.find(
({ qCode, type }) =>
!qCode &&
- ![CHECKBOX, CHECKBOX_OPTION, RADIO_OPTION, SELECT_OPTION].includes(type)
+ ![
+ CHECKBOX,
+ CHECKBOX_OPTION,
+ RADIO_OPTION,
+ SELECT_OPTION,
+ MUTUALLY_EXCLUSIVE_OPTION,
+ ].includes(type)
);
}
};
@@ -265,16 +276,17 @@ export const QCodeContextProvider = ({ questionnaire = {}, children }) => {
[answerRows, questionnaire]
);
- const hasQCodeError =
- duplicatedQCodes?.length ||
- getEmptyQCodes(answerRows, questionnaire.dataVersion) ||
- getEmptyOptionValues(answerRows);
-
const duplicatedOptionValues = useMemo(
() => getDuplicatedOptionValues(answerRows, questionnaire) ?? [],
[answerRows, questionnaire]
);
+ const hasQCodeError =
+ duplicatedQCodes?.length ||
+ duplicatedOptionValues?.length ||
+ getEmptyQCodes(answerRows, questionnaire.dataVersion) ||
+ getEmptyOptionValues(answerRows);
+
const hasOptionValueError =
duplicatedOptionValues?.length || getEmptyOptionValues(answerRows);
From b0ba23c83a682c1ffdd11c87da14b6f51d0c70b2 Mon Sep 17 00:00:00 2001
From: sudeep
Date: Thu, 28 Mar 2024 09:52:43 +0000
Subject: [PATCH 12/16] updated tests
Signed-off-by: sudeep
---
eq-author/src/App/qcodes/QCodesTable/index.test.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/eq-author/src/App/qcodes/QCodesTable/index.test.js b/eq-author/src/App/qcodes/QCodesTable/index.test.js
index 9946524a31..7b51247d7b 100644
--- a/eq-author/src/App/qcodes/QCodesTable/index.test.js
+++ b/eq-author/src/App/qcodes/QCodesTable/index.test.js
@@ -163,7 +163,7 @@ const optionsSetup = (dataVersion) => {
description: null,
label: "OR1",
additionalAnswer: null,
- id: "9d6d013d-64c0-4757-b8e3-f991e2dcfb92",
+ id: "or1",
value: "m1-value",
},
{
@@ -171,7 +171,7 @@ const optionsSetup = (dataVersion) => {
description: null,
label: "OR2",
additionalAnswer: null,
- id: "4b362d2e-1ec0-4cb2-b90d-572be5133ab2",
+ id: "or2",
value: "m1-value",
},
],
From b590583cd3345c43c9cfc18500f5fd802a4d0acb Mon Sep 17 00:00:00 2001
From: sudeep
Date: Thu, 28 Mar 2024 11:00:50 +0000
Subject: [PATCH 13/16] empty commit
From b2f74a04f791b36848709d2257e9083f58059d53 Mon Sep 17 00:00:00 2001
From: sudeep
Date: Tue, 9 Apr 2024 13:42:09 +0100
Subject: [PATCH 14/16] solved qcode error badge for version 3
Signed-off-by: sudeep
---
eq-author/src/components/QCodeContext/index.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/eq-author/src/components/QCodeContext/index.js b/eq-author/src/components/QCodeContext/index.js
index 21a36e9575..8a5eb96782 100644
--- a/eq-author/src/components/QCodeContext/index.js
+++ b/eq-author/src/components/QCodeContext/index.js
@@ -283,9 +283,9 @@ export const QCodeContextProvider = ({ questionnaire = {}, children }) => {
const hasQCodeError =
duplicatedQCodes?.length ||
- duplicatedOptionValues?.length ||
getEmptyQCodes(answerRows, questionnaire.dataVersion) ||
- getEmptyOptionValues(answerRows);
+ (questionnaire.dataVersion === "3" && duplicatedOptionValues?.length) ||
+ (questionnaire.dataVersion === "3" && getEmptyOptionValues(answerRows));
const hasOptionValueError =
duplicatedOptionValues?.length || getEmptyOptionValues(answerRows);
From 6f74a673d7056c7aed38d507455582ff0c0ed79b Mon Sep 17 00:00:00 2001
From: sudeep
Date: Tue, 9 Apr 2024 13:54:15 +0100
Subject: [PATCH 15/16] solved empty qcode error badge in version 1
Signed-off-by: sudeep
---
eq-author/src/components/QCodeContext/index.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/eq-author/src/components/QCodeContext/index.js b/eq-author/src/components/QCodeContext/index.js
index 8a5eb96782..65d279daef 100644
--- a/eq-author/src/components/QCodeContext/index.js
+++ b/eq-author/src/components/QCodeContext/index.js
@@ -206,7 +206,6 @@ const getEmptyQCodes = (answerRows, dataVersion) => {
!qCode &&
![
CHECKBOX,
- CHECKBOX_OPTION,
RADIO_OPTION,
SELECT_OPTION,
MUTUALLY_EXCLUSIVE_OPTION,
From c6de8e84619c9eab292e7b7d361107a3644a4538 Mon Sep 17 00:00:00 2001
From: sudeep
Date: Tue, 9 Apr 2024 13:56:42 +0100
Subject: [PATCH 16/16] final solution for qcode error badge
Signed-off-by: sudeep
---
eq-author/src/components/QCodeContext/index.js | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/eq-author/src/components/QCodeContext/index.js b/eq-author/src/components/QCodeContext/index.js
index 65d279daef..3fd685858d 100644
--- a/eq-author/src/components/QCodeContext/index.js
+++ b/eq-author/src/components/QCodeContext/index.js
@@ -259,7 +259,12 @@ const getEmptyOptionValues = (answerRows) => {
return answerRows?.find(
({ value, type, hideOptionValue }) =>
!value &&
- [CHECKBOX_OPTION, RADIO_OPTION, SELECT_OPTION].includes(type) &&
+ [
+ CHECKBOX_OPTION,
+ RADIO_OPTION,
+ SELECT_OPTION,
+ MUTUALLY_EXCLUSIVE_OPTION,
+ ].includes(type) &&
!hideOptionValue
);
};