Skip to content

Commit 47ca59c

Browse files
feat: Added testing and publishing for Custom EligibilityChecks
1 parent 2b423e8 commit 47ca59c

File tree

19 files changed

+978
-111
lines changed

19 files changed

+978
-111
lines changed

builder-api/src/main/java/org/acme/controller/DecisionResource.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.acme.enums.OptionalBoolean;
1313
import org.acme.model.domain.Benefit;
1414
import org.acme.model.domain.CheckConfig;
15+
import org.acme.model.domain.EligibilityCheck;
1516
import org.acme.model.domain.Screener;
1617
import org.acme.persistence.EligibilityCheckRepository;
1718
import org.acme.persistence.PublishedScreenerRepository;
@@ -159,6 +160,71 @@ private Map<String, Object> evaluateBenefit(Benefit benefit, Map<String, Object>
159160
}
160161
}
161162

163+
@POST
164+
@Path("/decision/v2/working_check")
165+
@Consumes(MediaType.APPLICATION_JSON)
166+
@Produces(MediaType.APPLICATION_JSON)
167+
public Response evaluateCheck(
168+
@Context SecurityIdentity identity,
169+
@QueryParam("checkId") String checkId,
170+
Map<String, Object> data
171+
) throws Exception {
172+
String userId = AuthUtils.getUserId(identity);
173+
174+
if (checkId == null || checkId.isBlank()){
175+
return Response.status(Response.Status.BAD_REQUEST)
176+
.entity("Error: Missing required query parameter: checkId")
177+
.build();
178+
}
179+
if (data == null || data.isEmpty()){
180+
return Response.status(Response.Status.BAD_REQUEST)
181+
.entity("Error: Missing decision inputs")
182+
.build();
183+
}
184+
185+
// Get CheckConfig from data
186+
if (!data.containsKey("checkConfig")) {
187+
return Response.status(Response.Status.BAD_REQUEST)
188+
.entity("Error: Missing CheckConfig in request body")
189+
.build();
190+
}
191+
// TODO: Fix to be more in-line with Java standards
192+
Map<String, Object> checkConfigData = (Map<String, Object>) data.get("checkConfig");
193+
CheckConfig checkConfig = new CheckConfig();
194+
checkConfig.setCheckId(checkId);
195+
checkConfig.setParameters((Map<String, Object>) checkConfigData.get("parameters"));
196+
197+
Map<String, Object> inputData = null;
198+
if (!data.containsKey("inputData")) {
199+
return Response.status(Response.Status.BAD_REQUEST)
200+
.entity("Error: Missing inputData in request body")
201+
.build();
202+
}
203+
inputData = (Map<String, Object>) data.get("inputData");
204+
205+
// Get EligibilityCheck
206+
Optional<EligibilityCheck> checkOpt = eligibilityCheckRepository.getWorkingCustomCheck(userId, checkId);
207+
if (checkOpt.isEmpty()) {
208+
return Response.status(Response.Status.NOT_FOUND)
209+
.entity("Error: Check not found")
210+
.build();
211+
}
212+
EligibilityCheck check = checkOpt.get();
213+
214+
try {
215+
String dmnFilepath = storageService.getCheckDmnModelPath(check.getId());
216+
String dmnModelName = check.getId();
217+
218+
OptionalBoolean result = dmnService.evaluateSimpleDmn(
219+
dmnFilepath, dmnModelName, inputData, checkConfig.getParameters()
220+
);
221+
return Response.ok().entity(Map.of("result", result)).build();
222+
} catch (Exception e) {
223+
Log.error("Error: " + e.getMessage());
224+
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
225+
}
226+
}
227+
162228
private boolean isUserAuthorizedToAccessScreenerByScreenerId(String userId, String screenerId) {
163229
Optional<Screener> screenerOpt = screenerRepository.getWorkingScreenerMetaDataOnly(screenerId);
164230
if (screenerOpt.isEmpty()){

builder-api/src/main/java/org/acme/controller/EligibilityCheckResource.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,14 @@ public Response publishCustomCheck(@Context SecurityIdentity identity, @PathPara
262262
return Response.status(Response.Status.UNAUTHORIZED).build();
263263
}
264264

265+
// Retrieve DMN Path before incrementing version
266+
Optional<String> workingDmnOpt = storageService.getStringFromStorage(storageService.getCheckDmnModelPath(check.getId()));
267+
if (!workingDmnOpt.isPresent()) {
268+
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
269+
.entity(Map.of("error", "could not find DMN file for working Check"))
270+
.build();
271+
}
272+
265273
// Update workingCheck so that the incremented version number is saved
266274
check.setVersion(check.getVersion() + 1);
267275
try {
@@ -278,10 +286,11 @@ public Response publishCustomCheck(@Context SecurityIdentity identity, @PathPara
278286
String publishedCheckId = eligibilityCheckRepository.saveNewPublishedCustomCheck(check);
279287

280288
// save published check DMN to storage
281-
Optional<String> workingDmnOpt = storageService.getStringFromStorage(storageService.getCheckDmnModelPath(check.getId()));
282289
if (workingDmnOpt.isPresent()){
283290
String workingDmn = workingDmnOpt.get();
284291
storageService.writeStringToStorage(storageService.getCheckDmnModelPath(publishedCheckId), workingDmn, "application/xml");
292+
} else {
293+
Log.warn("Could not find working DMN model for check " + check.getId() + ", published check created without DMN model");
285294
}
286295
} catch (Exception e){
287296
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
@@ -291,4 +300,33 @@ public Response publishCustomCheck(@Context SecurityIdentity identity, @PathPara
291300

292301
return Response.ok(check, MediaType.APPLICATION_JSON).build();
293302
}
303+
304+
@GET
305+
@Path("/custom-checks/{checkId}/related_published_checks")
306+
public Response getRelatedPublishedChecks(@Context SecurityIdentity identity, @PathParam("checkId") String checkId){
307+
String userId = AuthUtils.getUserId(identity);
308+
Optional<EligibilityCheck> checkOpt = eligibilityCheckRepository.getWorkingCustomCheck(userId, checkId);
309+
if (checkOpt.isEmpty()){
310+
return Response.status(Response.Status.NOT_FOUND).build();
311+
}
312+
313+
EligibilityCheck check = checkOpt.get();
314+
315+
// Authorization
316+
if (!userId.equals(check.getOwnerId())){
317+
return Response.status(Response.Status.UNAUTHORIZED).build();
318+
}
319+
320+
// Update workingCheck so that the incremented version number is saved
321+
check.setVersion(check.getVersion() + 1);
322+
try {
323+
List<EligibilityCheck> publishedChecks = eligibilityCheckRepository.getRelatedPublishedChecks(check);
324+
325+
return Response.ok(publishedChecks, MediaType.APPLICATION_JSON).build();
326+
} catch (Exception e){
327+
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
328+
.entity(Map.of("error", "could not update working Check, published check version was not created"))
329+
.build();
330+
}
331+
}
294332
}

builder-api/src/main/java/org/acme/model/domain/EligibilityCheck.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
44
import com.fasterxml.jackson.annotation.JsonProperty;
5-
import org.acme.constants.CheckStatus;
65

76
import java.util.List;
87

@@ -108,6 +107,4 @@ public Boolean getPublic() {
108107
public void setPublic(Boolean aPublic) {
109108
isPublic = aPublic;
110109
}
111-
112-
113110
}

builder-api/src/main/java/org/acme/persistence/EligibilityCheckRepository.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ public interface EligibilityCheckRepository {
1616

1717
List<EligibilityCheck> getWorkingCustomChecks(String userId);
1818

19+
List<EligibilityCheck> getRelatedPublishedChecks(EligibilityCheck workingCustomCheck);
20+
1921
List<EligibilityCheck> getPublishedCustomChecks(String userId);
2022

2123
Optional<EligibilityCheck> getWorkingCustomCheck(String userId, String checkId);

builder-api/src/main/java/org/acme/persistence/FirestoreUtils.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ public static List<Map<String, Object>> getFirestoreDocsByField(String collectio
5050
return data;
5151
})
5252
.toList();
53-
5453
}catch(Exception e){
5554
Log.error("Error fetching documents from firestore: ", e);
5655
return new ArrayList<>();
@@ -139,6 +138,30 @@ public static Optional<Map<String, Object>> getFirestoreDocById(String collectio
139138
}
140139
}
141140

141+
public static List<Map<String, Object>> getFirestoreDocsByIdPrefix (String collection, String prefix) {
142+
try {
143+
/* TODO: Temporary logic - codify prefix searching in a more robust way */
144+
ApiFuture<QuerySnapshot> query = db.collection(collection)
145+
.whereGreaterThanOrEqualTo(FieldPath.documentId(), prefix)
146+
.whereLessThan(FieldPath.documentId(), prefix + "\uf8ff")
147+
.get();
148+
149+
List<QueryDocumentSnapshot> documents;
150+
documents = query.get().getDocuments();
151+
152+
return documents.stream()
153+
.map(doc -> {
154+
Map<String, Object> data = doc.getData();
155+
data.put("id", doc.getId());
156+
return data;
157+
})
158+
.toList();
159+
}catch(Exception e){
160+
Log.error("Error fetching documents from firestore: ", e);
161+
return new ArrayList<>();
162+
}
163+
}
164+
142165
public static String persistDocument(String collectionName, Map<String, Object> data) throws Exception {
143166
try {
144167
DocumentReference documentRef = db.collection(collectionName)

builder-api/src/main/java/org/acme/persistence/impl/EligibilityCheckRepositoryImpl.java

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.fasterxml.jackson.annotation.JsonInclude;
44
import com.fasterxml.jackson.databind.ObjectMapper;
55

6+
import io.quarkus.logging.Log;
67
import jakarta.enterprise.context.ApplicationScoped;
78
import jakarta.inject.Inject;
89

@@ -72,7 +73,6 @@ public List<EligibilityCheck> getWorkingCustomChecks(String userId){
7273
List<Map<String, Object>> checkMaps = FirestoreUtils.getFirestoreDocsByField(CollectionNames.WORKING_CUSTOM_CHECK_COLLECTION, FieldNames.OWNER_ID, userId);
7374
ObjectMapper mapper = new ObjectMapper();
7475
return checkMaps.stream().map(checkMap -> mapper.convertValue(checkMap, EligibilityCheck.class)).toList();
75-
7676
}
7777

7878
public List<EligibilityCheck> getPublishedCustomChecks(String userId){
@@ -81,6 +81,18 @@ public List<EligibilityCheck> getPublishedCustomChecks(String userId){
8181
return checkMaps.stream().map(checkMap -> mapper.convertValue(checkMap, EligibilityCheck.class)).toList();
8282
}
8383

84+
public List<EligibilityCheck> getRelatedPublishedChecks(EligibilityCheck workingCustomCheck){
85+
/* Get all related Published Checks for a Working Check */
86+
List<Map<String, Object>> checkMaps = (
87+
FirestoreUtils.getFirestoreDocsByIdPrefix(
88+
CollectionNames.PUBLISHED_CUSTOM_CHECK_COLLECTION,
89+
getPublishedPrefix(workingCustomCheck)
90+
)
91+
);
92+
ObjectMapper mapper = new ObjectMapper();
93+
return checkMaps.stream().map(checkMap -> mapper.convertValue(checkMap, EligibilityCheck.class)).toList();
94+
}
95+
8496
public Optional<EligibilityCheck> getWorkingCustomCheck(String userId, String checkId){
8597
return getCustomCheck(userId, checkId, false);
8698
}
@@ -116,21 +128,22 @@ public String saveNewWorkingCustomCheck(EligibilityCheck check) throws Exception
116128
return FirestoreUtils.persistDocumentWithId(CollectionNames.WORKING_CUSTOM_CHECK_COLLECTION, checkId, data);
117129
}
118130

119-
public void updateWorkingCustomCheck(EligibilityCheck check) throws Exception{
131+
public void updateWorkingCustomCheck(EligibilityCheck check) throws Exception {
120132
ObjectMapper mapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
121133
Map<String, Object> data = mapper.convertValue(check, Map.class);
122134
FirestoreUtils.updateDocument(CollectionNames.WORKING_CUSTOM_CHECK_COLLECTION, data, check.getId());
123135
}
124136

125-
public String saveNewPublishedCustomCheck(EligibilityCheck check) throws Exception{
126-
check.setId(getPublishedId(check));
137+
public String saveNewPublishedCustomCheck(EligibilityCheck check) throws Exception {
127138
ObjectMapper mapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
128139
Map<String, Object> data = mapper.convertValue(check, Map.class);
140+
data.put("id", getPublishedId(check));
141+
data.put("datePublished", System.currentTimeMillis());
142+
129143
String checkDocId = getPublishedId(check);
130144
return FirestoreUtils.persistDocumentWithId(CollectionNames.PUBLISHED_CUSTOM_CHECK_COLLECTION, checkDocId, data);
131145
}
132146

133-
134147
public void updatePublishedCustomCheck(EligibilityCheck check) throws Exception{
135148
ObjectMapper mapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
136149
Map<String, Object> data = mapper.convertValue(check, Map.class);
@@ -149,7 +162,11 @@ public String getWorkingId(EligibilityCheck check) {
149162
return CheckStatus.WORKING.getCode() + "-" + check.getOwnerId() + "-" + check.getModule() + "-" + check.getName();
150163
}
151164

165+
public String getPublishedPrefix(EligibilityCheck check) {
166+
return CheckStatus.PUBLISHED.getCode() + "-" + check.getOwnerId() + "-" + check.getModule() + "-" + check.getName();
167+
}
168+
152169
public String getPublishedId(EligibilityCheck check) {
153-
return CheckStatus.PUBLISHED.getCode() + "-" + check.getOwnerId() + "-" + check.getModule() + "-" + check.getName() + "-" + check.getVersion().toString();
170+
return getPublishedPrefix(check) + "-" + check.getVersion().toString();
154171
}
155172
}

0 commit comments

Comments
 (0)