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

LUI-197 UI for saving concept reference ranges #196

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
97bd354
Initial commit
dicksonmulli Aug 27, 2024
45c5846
Concept references header on the UI
dicksonmulli Aug 28, 2024
8004fd8
Showing reference range title and table headers
dicksonmulli Aug 28, 2024
c6ee428
Concept reference form - restructured
dicksonmulli Aug 29, 2024
0b72cf4
Showing saved data
dicksonmulli Aug 29, 2024
3d6c088
Updating concept page with reference ranges
dicksonmulli Aug 30, 2024
1af1c3d
Successfully saving new data
dicksonmulli Sep 1, 2024
9bcb15c
Hiding reference ranges when concept type is not numeric
dicksonmulli Sep 2, 2024
24963af
Updating version for testing purposes
dicksonmulli Sep 2, 2024
c888df0
Removing an existing row on the UI
dicksonmulli Sep 4, 2024
2695383
Adding tests
dicksonmulli Sep 5, 2024
71860fd
Hiding and showing referenceRange row on conceptForm
dicksonmulli Sep 5, 2024
5fee0d8
Creating a reflection object of reference range
dicksonmulli Sep 8, 2024
cd19cda
Using reflection to map reference ranges
dicksonmulli Sep 10, 2024
46e9cdf
Updating reflection in tests
dicksonmulli Sep 11, 2024
a43a391
Code refactor
dicksonmulli Sep 11, 2024
a1c1f4d
Update reference ranges
dicksonmulli Sep 14, 2024
04dfa3f
Reverting the core version
dicksonmulli Sep 14, 2024
270311a
Sorting reference ranges
dicksonmulli Sep 17, 2024
d3dd833
Fixing null pointer
dicksonmulli Sep 17, 2024
6cd4978
Code refactor
dicksonmulli Sep 18, 2024
36634bb
Ability to edit concept reference ranges
dicksonmulli Sep 22, 2024
2194cfb
Updating reference range validations
dicksonmulli Sep 22, 2024
71fea3e
Reconciliation of admin and dictionary views
dicksonmulli Sep 22, 2024
29ffdac
Fixing an error when 2 rows are added and the first one removed befor…
dicksonmulli Sep 27, 2024
a66e598
Conditional display of concept reference ranges based on openMRS-core…
dicksonmulli Oct 8, 2024
dd54bf1
Refactoring
dicksonmulli Oct 11, 2024
988d917
code refactor
dicksonmulli Oct 17, 2024
a98b735
code refactor
dicksonmulli Oct 17, 2024
2be1fed
removed unnecessary method
dicksonmulli Oct 18, 2024
234fb75
Hidding concept set row when checkbox is not clicked
dicksonmulli Oct 23, 2024
695d589
Showing 'Is set' when checkbox is checked
dicksonmulli Oct 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions api/src/main/java/org/openmrs/module/legacyui/GeneralUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import org.openmrs.Concept;
import org.openmrs.api.context.Context;
import org.openmrs.util.OpenmrsConstants;

public class GeneralUtils {

Expand Down Expand Up @@ -68,4 +69,23 @@ public static Concept getConcept(String id) {

return cpt;
}

/**
* Checks if current version of openmrs is greater or equal to 2.7.0 The aim is to try loading
* ConceptReferenceRange class, which is in version 2.7.0. If the ConceptReferenceRange class is
* loaded, then the current version is greater than or equal to 2.7.0
*
* @return true if current version is greater or equal to 2.7.0 and false otherwise
* @since 1.17.0
*/
public static boolean isTwoPointSevenAndAbove() {
Copy link
Member

Choose a reason for hiding this comment

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

This is cimplicated and brittle. Why don't you use the same trick of trying to load a class like concept reference range which was added in 2.7?

Copy link
Author

Choose a reason for hiding this comment

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

This boolean is used in UI/JSP hence the need to check the version instead of loading a particular class. I think this will be reliable as long as we have the version constant(s).

Copy link
Member

Choose a reason for hiding this comment

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

We can still have a boolean with loading a particular class.

try {
Class.forName("org.openmrs.ConceptReferenceRange");

return true;
}
catch (ClassNotFoundException exception) {
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,21 @@
*/
package org.openmrs.web.controller;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import javax.servlet.ServletException;
Expand Down Expand Up @@ -54,6 +58,7 @@
import org.openmrs.api.ConceptsLockedException;
import org.openmrs.api.DuplicateConceptNameException;
import org.openmrs.api.context.Context;
import org.openmrs.module.legacyui.GeneralUtils;
import org.openmrs.module.web.extension.ConceptUsageExtension;
import org.openmrs.module.web.extension.provider.Link;
import org.openmrs.propertyeditor.ConceptAnswersEditor;
Expand All @@ -69,7 +74,9 @@
import org.openmrs.validator.ValidateUtil;
import org.openmrs.web.WebConstants;
import org.openmrs.web.attribute.WebAttributeUtil;
import org.openmrs.web.controller.concept.ConceptReferenceRange;
import org.openmrs.web.controller.concept.ConceptReferenceTermWebValidator;
import org.openmrs.web.controller.mappper.ConceptFormMapper;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.beans.propertyeditors.CustomNumberEditor;
import org.springframework.context.support.MessageSourceAccessor;
Expand Down Expand Up @@ -271,6 +278,8 @@ protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse

validateConceptUsesPersistedObjects(concept, errors);

new ConceptFormValidator().validateConceptReferenceRange(concept, errors);

if (!errors.hasErrors()) {
if (action.equals(msa.getMessage("Concept.cancel"))) {
return new ModelAndView(new RedirectView("index.htm"));
Expand Down Expand Up @@ -406,6 +415,9 @@ protected Map<String, Object> referenceData(HttpServletRequest request) throws E
map.put("locale", Context.getLocale()); // should be same string format as conceptNamesByLocale map keys

map.put("attributeTypes", cs.getAllConceptAttributeTypes());

map.put("canUseConceptReferenceRanges", GeneralUtils.isTwoPointSevenAndAbove());

return map;
}

Expand Down Expand Up @@ -459,6 +471,8 @@ public class ConceptFormBackingObject {

public Collection<ConceptAttribute> activeAttributes;

public List<ConceptReferenceRange> referenceRanges;

/**
* Default constructor must take in a Concept object to create itself
*
Expand Down Expand Up @@ -517,6 +531,10 @@ public ConceptFormBackingObject(Concept concept) {
this.allowDecimal = cn.getAllowDecimal();
this.displayPrecision = cn.getDisplayPrecision();
this.units = cn.getUnits();

this.referenceRanges = ListUtils.lazyList(
new ArrayList<>(new ConceptFormMapper().mapToWebReferenceRanges(cn)),
FactoryUtils.instantiateFactory(ConceptReferenceRange.class));
} else if (concept instanceof ConceptComplex) {
ConceptComplex complex = (ConceptComplex) concept;
this.handlerKey = complex.getHandler();
Expand Down Expand Up @@ -662,6 +680,8 @@ public Concept getConceptFromFormData() {
cn.setDisplayPrecision(displayPrecision);
cn.setUnits(units);

setConceptReferenceRanges(cn);

concept = cn;

} else if (concept.getDatatype().getName().equals("Complex")) {
Expand All @@ -678,6 +698,160 @@ public Concept getConceptFromFormData() {
return concept;
}

/**
* This method sets reference ranges to concept numeric.
*
* @param cn ConceptNumeric
* @since 1.17.0
*/
private void setConceptReferenceRanges(ConceptNumeric cn) {
if (this.referenceRanges == null) {
return;
}
for (ConceptReferenceRange referenceRange : this.referenceRanges) {
if (referenceRange == null) {
continue;
}

if (referenceRange.getId() != null) {
if (referenceRange.getId() <= 0) {
removeReferenceRange(cn, referenceRange);
} else {
updateReferenceRange(cn, referenceRange);
}
} else {
if (referenceRange.getHiAbsolute() != null && referenceRange.getLowAbsolute() != null) {
addReferenceRange(cn, referenceRange);
}
}
}
}

/**
* This method removes a reference range from conceptNumeric
*
* @param cn conceptNumeric
* @param referenceRange referenceRange
* @since 1.17.0
*/
private void removeReferenceRange(ConceptNumeric cn, ConceptReferenceRange referenceRange) {
try {
Object platformReferenceRange = new ConceptFormMapper().mapToConceptReferenceRange(referenceRange, cn);
setMethodValue(cn, "removeReferenceRange", platformReferenceRange);
}
catch (Exception exception) {
// Note that openMRS-core version 2.7.0 or higher is required for this functionality to work.
}
}

/**
* This method adds a new reference range to conceptNumeric
*
* @param cn conceptNumeric
* @param referenceRange referenceRange
* @since 1.17.0
*/
private void addReferenceRange(ConceptNumeric cn, ConceptReferenceRange referenceRange) {
referenceRange.setConceptNumeric(cn);
try {
Object platformReferenceRange = new ConceptFormMapper().mapToConceptReferenceRange(referenceRange, cn);
setMethodValue(cn, "addReferenceRange", platformReferenceRange);
}
catch (Exception exception) {
// Note that openMRS-core version 2.7.0 or higher is required for this functionality to work.
}
}

/**
* This method updates concept reference range if a field value has changed.
*
* @param cn ConceptNumeric
* @param referenceRange ConceptReferenceRange
* @since 1.17.0
*/
public void updateReferenceRange(ConceptNumeric cn, ConceptReferenceRange referenceRange) {
try {
Set<?> existingReferenceRanges = getExistingReferenceRanges(cn);

for (Object existingRange : existingReferenceRanges) {
Method getIdMethod = existingRange.getClass().getMethod("getId");
Object idValue = getIdMethod.invoke(existingRange);

if (Objects.equals(idValue, referenceRange.getId()) && hasReferenceRangeChanged(existingRange, referenceRange)) {
updateReferenceRangeFields(existingRange, referenceRange);
break;
}
}
} catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException |
ClassNotFoundException exception) {
// Note that openMRS-core version 2.7.0 or higher is required for this functionality to work.
} catch (Exception exception) {
// Note that openMRS-core version 2.7.0 or higher is required for this functionality to work.
}
}

/**
* This method gets the existing reference ranges
*
* @param cn ConceptNumeric
* @return a set of reference ranges
* @since 1.17.0
*/
private Set<?> getExistingReferenceRanges(ConceptNumeric cn) {
try {
Method getReferenceRangesMethod = cn.getClass().getMethod("getReferenceRanges");
return (Set<?>) getReferenceRangesMethod.invoke(cn);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
// Note that openMRS-core version 2.7.0 or higher is required for this functionality to work.
return Collections.emptySet();
}
}

private boolean hasReferenceRangeChanged(Object existingRange, ConceptReferenceRange referenceRange)
throws Exception {
return !Objects.equals(referenceRange.getCriteria(), getMethodValue(existingRange, "getCriteria"))
|| !Objects.equals(referenceRange.getHiAbsolute(), getMethodValue(existingRange, "getHiAbsolute"))
|| !Objects.equals(referenceRange.getHiCritical(), getMethodValue(existingRange, "getHiCritical"))
|| !Objects.equals(referenceRange.getHiNormal(), getMethodValue(existingRange, "getHiNormal"))
|| !Objects.equals(referenceRange.getLowAbsolute(), getMethodValue(existingRange, "getLowAbsolute"))
|| !Objects.equals(referenceRange.getLowCritical(), getMethodValue(existingRange, "getLowCritical"))
|| !Objects.equals(referenceRange.getLowNormal(), getMethodValue(existingRange, "getLowNormal"));
}

/**
* This method updates reference range fields
*
* @param existingRange existing reference range
* @param referenceRange the updated reference range
* @throws Exception exception
* @since 1.17.0
*/
private void updateReferenceRangeFields(Object existingRange, ConceptReferenceRange referenceRange) throws Exception {
updateField(existingRange, "setHiAbsolute", referenceRange.getHiAbsolute());
updateField(existingRange, "setHiCritical", referenceRange.getHiCritical());
updateField(existingRange, "setHiNormal", referenceRange.getHiNormal());
updateField(existingRange, "setLowAbsolute", referenceRange.getLowAbsolute());
updateField(existingRange, "setLowCritical", referenceRange.getLowCritical());
updateField(existingRange, "setLowNormal", referenceRange.getLowNormal());
updateField(existingRange, "setCriteria", referenceRange.getCriteria());
}

private void updateField(Object obj, String methodName, Object value) throws Exception {
if (value != null) {
setMethodValue(obj, methodName, value);
}
}

private Object getMethodValue(Object obj, String methodName) throws Exception {
Method method = obj.getClass().getMethod(methodName);
return method.invoke(obj);
}

private void setMethodValue(Object obj, String methodName, Object value) throws Exception {
Method method = obj.getClass().getMethod(methodName, value.getClass());
method.invoke(obj, value);
}

/**
* Builds a white-space separated list of concept ids belonging to a concept set
*
Expand Down Expand Up @@ -938,6 +1112,36 @@ public List<Form> getFormsInUse() {
return Context.getFormService().getFormsContainingConcept(concept);
}

/**
* Get reference ranges
*
* @return the referenceRanges
* @since 1.17.0
*/
public List<ConceptReferenceRange> getReferenceRanges() {
return referenceRanges;
}

/**
* Sets reference ranges
*
* @param referenceRanges the referenceRanges to set
* @since 1.17.0
*/
public void setReferenceRanges(List<ConceptReferenceRange> referenceRanges) {
this.referenceRanges = referenceRanges;
}

/**
* Adds a new reference range to the list of reference ranges
*
* @param referenceRange the referenceRange to add
* @since 1.17.0
*/
public void addReferenceRange(ConceptReferenceRange referenceRange) {
getReferenceRanges().add(referenceRange);
}

/**
* Get the list of extensions/metadata and the specific instances of them that use this
* concept.
Expand Down
Loading