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

(feat) O3-3712: add a concept answer to a concept in the form builder #351

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

Willie-theBeastMutua
Copy link

@Willie-theBeastMutua Willie-theBeastMutua commented Sep 11, 2024

Requirements

  • This PR has a title that briefly describes the work done including the ticket number. If there is a ticket, make sure your PR title includes a conventional commit label. See existing PR titles for inspiration.
  • My work conforms to the OpenMRS 3.0 Styleguide and design documentation.
  • My work includes tests or is validated by existing tests.

Summary

Users are now be able to manually add specific concepts as answers to a concept of type Coded after searching of them (similar to how we add a backing concept)

Screenshots

BeforeClick

AfterClick
select answer 1
select Ans 2
selected ans 3
select ans 4
select ans 5

Related Issue

https://openmrs.atlassian.net/browse/O3-3712

Other

@NethmiRodrigo NethmiRodrigo changed the title add a concept answer to a concept in the form builder (feat) O3-3712: add a concept answer to a concept in the form builder Sep 18, 2024
@NethmiRodrigo
Copy link
Contributor

Can you please add these changes to the edit-modal component as well?

@Willie-theBeastMutua
Copy link
Author

Okay doing that

@Willie-theBeastMutua
Copy link
Author

Hello @NethmiRodrigo , I added the changes to edit-modal component as requested

package.json Outdated Show resolved Hide resolved
package.json Outdated Show resolved Hide resolved
package.json Outdated Show resolved Hide resolved
src/components/interactive-builder/add-question.modal.tsx Outdated Show resolved Hide resolved
src/components/interactive-builder/add-question.modal.tsx Outdated Show resolved Hide resolved
src/components/interactive-builder/add-question.modal.tsx Outdated Show resolved Hide resolved
src/components/interactive-builder/add-question.modal.tsx Outdated Show resolved Hide resolved
src/components/interactive-builder/question-modal.scss Outdated Show resolved Hide resolved
src/components/interactive-builder/question-modal.scss Outdated Show resolved Hide resolved
src/components/interactive-builder/question-modal.scss Outdated Show resolved Hide resolved
@NethmiRodrigo
Copy link
Contributor

@Willie-theBeastMutua can you please update your fork and fix the changes, some of the code has gotten reverted, maybe due to a bad rebase

@Willie-theBeastMutua
Copy link
Author

Willie-theBeastMutua commented Oct 7, 2024

Hello ** @NethmiRodrigo** the code has been updated and fixed kindly check

@Willie-theBeastMutua
Copy link
Author

The merge you conducted reverted some of the changes in the main but i have fixed them i hope its now okay

@NethmiRodrigo
Copy link
Contributor

Hey @Willie-theBeastMutua thank you for the progress. One minor detail, I see that we can't remove the answers once added. Can you add support to remove an added answer, and also add a check that you can't add the same concept multiple times as an answer?

@Willie-theBeastMutua
Copy link
Author

@NethmiRodrigo support for deletion and checks for duplicate concept answers has been added kindly check

@NethmiRodrigo
Copy link
Contributor

@Willie-theBeastMutua could you add like a small x icon to the tags so we could remove individual answers, instead of the trash icon removing all answers? We can remove the trash icon. And could you align the add icon button to be aligned with the search?
Screenshot 2024-10-09 at 12 24 24 PM
Also, the additional answers don't get added to the answers key in the form JSON, so when you click save, all the answers that were added gets lost

@Willie-theBeastMutua
Copy link
Author

@NethmiRodrigo the changes have been made and it saves successfully

@Willie-theBeastMutua
Copy link
Author

@NethmiRodrigo did you have a look

@NethmiRodrigo
Copy link
Contributor

Sorry I couldn't re-review it, will do asap. Thanks for pinging me. Meanwhile, can you resolve the conflicts and update your branch?

@Willie-theBeastMutua
Copy link
Author

@NethmiRodrigo branch has been updated

@Willie-theBeastMutua
Copy link
Author

@NethmiRodrigo can we restart the test as it times out and when i test the same test from my end it should work. Or advice on what is failing....

@NethmiRodrigo
Copy link
Contributor

NethmiRodrigo commented Oct 16, 2024

@Willie-theBeastMutua I re-ran the tests but it failed again due to a metadata issue (unrelated to your changes).
One more minor thing, I can add an answer that's already added in the UI. This doesn't get added to the actual object so when I go into edit, it only shows one instance of the answer, but is there something we can do to restrict adding duplicates of the same answer
Screenshot 2024-10-16 at 12 45 50 PM
Screenshot 2024-10-16 at 12 47 28 PM

@Willie-theBeastMutua
Copy link
Author

@NethmiRodrigo to get you right.. i added the check for duplicates to only save one instance. You want the same check to be available in the UI?

@NethmiRodrigo
Copy link
Contributor

@Willie-theBeastMutua yep, exactly!

@Willie-theBeastMutua
Copy link
Author

@NethmiRodrigo the checks have been added. Kindly check

@NethmiRodrigo
Copy link
Contributor

Could you please update your branch @Willie-theBeastMutua?

Copy link
Contributor

@NethmiRodrigo NethmiRodrigo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Willie-theBeastMutua, some very minor suggestions

src/components/interactive-builder/add-question.modal.tsx Outdated Show resolved Hide resolved
closeModal();
};

const createQuestion = () => {
const createQuestion = useCallback(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for clarification purposes, any particular reason as to why we changed this to a useCallback?

Comment on lines 389 to 394
useEffect(() => {
if (isCreatingQuestion) {
createQuestion();
setIsCreatingQuestion(false);
}
}, [isCreatingQuestion, createQuestion]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we just call the handleCreateQuestion function, instead of having a useEffect to do it? See - https://react.dev/learn/you-might-not-need-an-effect

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use effect is used due to react's state management when combining the added answers and the selected answers. Saving without checking when the state is changed only saves the selected answers and not the update. I tried not using it but react's state management did not allow me. The call back is used to execute also the use effect. They are both part of the same solution

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I see. So instead of having a useEffect and useCallback we could change the handleSaveMoreAnswers to return the answers list, like so:

const handleSaveMoreAnswers = () => {
    const newAnswers = addedAnswers.filter(
      (newAnswer) => !selectedAnswers.some((prevAnswer) => prevAnswer.id === newAnswer.id),
    );

    const updatedAnswers = [...selectedAnswers, ...newAnswers];
    setSelectedAnswers(updatedAnswers);
    setaddedAnswers([]);
    return updatedAnswers;
  };

And then in the handleCreateQuestion, we pass in the list to the createQuestion function, like so:

 const handleCreateQuestion = () => {
    const updatedAnswers = handleSaveMoreAnswers();
    createQuestion(updatedAnswers);
    closeModal();
  };

And accept change the createQuestion function to accept a parameter and then use that param for the object:

const createQuestion = (conceptAnswers) => {
    try {
      const questionIndex = schema.pages[pageIndex]?.sections?.[sectionIndex]?.questions?.length ?? 0;
      const computedQuestionId = `question${questionIndex + 1}Section${sectionIndex + 1}Page-${pageIndex + 1}`;

      const newQuestion = {
        label: questionLabel,
        ......,
        ...(conceptAnswers.length && {
            answers: conceptAnswers.map((answer) => ({
              concept: answer.id,
              label: answer.text,
            })),
          }),
      };
    }
};

This way we could avoid the useCallback with a massive dependency list

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let me try that

@Willie-theBeastMutua
Copy link
Author

Could you please update your branch @Willie-theBeastMutua?

Okay let me do that

@Willie-theBeastMutua
Copy link
Author

@NethmiRodrigo I was able to remove the callback and useEffect according to your suggestion and it worked perfectly. Thank you so much
Kindly check the changes and advice.

@Willie-theBeastMutua
Copy link
Author

@NethmiRodrigo the change has been made

)}
) : null}

{(selectedConcept || questionToEdit) && questionToEdit?.questionOptions.answers?.length ? (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of the condition questionToEdit?.questionOptions.answers?.length, if a user when creating a question doesn't select any answer, they wouldn't be able to add any answers when editing either, and that shouldn't be the case.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NethmiRodrigo sorry for replying late. This is what is used even in the add-question.modal. I have used it to ensure that the concept that was selected has a coded datatype. In the add-question modal if the API returns a concept without answers even if its datatype is coded then the select answers field will not be displayed.

That was my thinking based on what has been implemented. Unless you have another idea as to how we can implement it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the add-question-modal, this condition makes sense. In the edit version, if the user has not selected any answer during creation, they should still be able to add answers. What I meant was that we should just remove this condition and show the Add more answers if the concept is of the datatype coded

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NethmiRodrigo how do we check if the datatype is coded? Because even in add question, the check is done to see if the concept has answers there is no check for the datatype

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So in the add-question-modal, the reason that there is no check for the datatype is because as soon as you search for a concept from the API and select a concept, if the concept has answers, it got added to a state, so the check for the datatype coded wasn't necessary (hacky way to do it) -

const handleConceptSelect = (concept: Concept) => {
const updatedDatePickerType = getDatePickerType(concept);
if (updatedDatePickerType) setDatePickerType(updatedDatePickerType);
setConceptToLookup('');
setSelectedConcept(concept);
setAnswers(
concept?.answers?.map((answer) => ({
concept: answer?.uuid,
label: answer?.display,
})),
);

@Willie-theBeastMutua
Copy link
Author

@NethmiRodrigo I found a way to do it kindly check and advise

@@ -875,7 +876,14 @@ const EditQuestionModal: React.FC<EditQuestionModalProps> = ({
</div>
) : null}

{(selectedConcept || questionToEdit) && questionToEdit?.questionOptions.answers?.length ? (
{!hasConceptChanged && editConceptInfo?.datatype?.uuid == '8d4a48b6-c2cc-11de-8d13-0010c6dffd0f' ? (
Copy link
Contributor

@NethmiRodrigo NethmiRodrigo Nov 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have a hard-coded UUID?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Willie-theBeastMutua please check my comment, just removing the conditional logic is sufficient, and we'll merge this in. Thanks!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have a hard-coded UUID?

The UUID id for the coded datatype that is the check

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Willie-theBeastMutua please check my comment, just removing the conditional logic is sufficient, and we'll merge this in. Thanks!

If I remove the logic, the more answers will display even when the concept is not of a coded datatype so I added a hook that collects data on the concept and I check for the UUID for the coded datatype. I can use the word "Coded" to check but my fear was what would happen if the language changed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignore my suggestion about simply removing the condition, I see why the hook is needed.
@ibacher if we do a datatype.display === "Coded", would that be language sensitive?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Willie-theBeastMutua for now lets assume it wont change with the language

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NethmiRodrigo Yes, but if you used datatype.name is shouldn't be, except in some edge-cases.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general display is the (possibly) localized metadata, but name is the "raw" value.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NethmiRodrigo this is done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants