Skip to content

Commit 6c7050d

Browse files
Added DMN Saving for checks.
1 parent 965299f commit 6c7050d

9 files changed

Lines changed: 156 additions & 151 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ public Response createCheck(@Context SecurityIdentity identity,
7373
//TODO: Add validations for user provided data
7474
newCheck.setOwnerId(userId);
7575
newCheck.setPublic(true); // By default all created checks are public
76+
newCheck.setVersion("1");
7677
try {
7778
String checkId = eligibilityCheckRepository.saveNewCheck(newCheck);
7879
newCheck.setId(checkId);

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public class EligibilityCheck {
1212
private String description;
1313
private String version;
1414
private boolean isActive;
15+
private String dmnModel;
1516
private List<InputDefinition> inputs;
1617
private List<ParameterDefinition> parameters;
1718
private String ownerId;
@@ -34,6 +35,14 @@ public void setName(String name) {
3435
this.name = name;
3536
}
3637

38+
public void setDmnModel(String dmnModel){
39+
this.dmnModel = dmnModel;
40+
}
41+
42+
public String getDmnModel(){
43+
return dmnModel;
44+
}
45+
3746
public String getModule() {
3847
return module;
3948
}

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

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55

66
import io.quarkus.logging.Log;
77
import jakarta.enterprise.context.ApplicationScoped;
8+
import jakarta.inject.Inject;
9+
810
import org.acme.constants.CollectionNames;
911
import org.acme.constants.FieldNames;
1012
import org.acme.enums.OptionalBoolean;
1113
import org.acme.model.domain.Benefit;
1214
import org.acme.model.domain.EligibilityCheck;
15+
import org.acme.model.domain.Screener;
1316

1417
import java.util.List;
1518
import java.util.Map;
@@ -18,6 +21,9 @@
1821
@ApplicationScoped
1922
public class EligibilityCheckRepositoryImpl implements EligibilityCheckRepository {
2023

24+
@Inject
25+
private StorageService storageService;
26+
2127
public List<EligibilityCheck> getAllPublicChecks(){
2228

2329
List<Map<String, Object>> checkMaps = FirestoreUtils.getFirestoreDocsByField(
@@ -29,18 +35,20 @@ public List<EligibilityCheck> getAllPublicChecks(){
2935
return checks; }
3036

3137
public Optional<EligibilityCheck> getCheck(String checkId){
32-
33-
List<Map<String, Object>> checkMaps = FirestoreUtils.getFirestoreDocsByField(
34-
CollectionNames.ELIGIBILITY_CHECK_COLLECTION, FieldNames.ID, checkId);
38+
Optional<Map<String, Object>> dataOpt = FirestoreUtils.getFirestoreDocById(CollectionNames.ELIGIBILITY_CHECK_COLLECTION, checkId);
39+
if (dataOpt.isEmpty()){
40+
return Optional.empty();
41+
}
42+
Map<String, Object> data = dataOpt.get();
3543

3644
ObjectMapper mapper = new ObjectMapper();
37-
List<EligibilityCheck> checks = checkMaps.stream().map( checkMap -> mapper.convertValue(checkMap, EligibilityCheck.class)).toList();
45+
EligibilityCheck check = mapper.convertValue(data, EligibilityCheck.class);
3846

39-
if (checks.isEmpty()){
40-
return Optional.empty();
41-
} else {
42-
return Optional.of(checks.getFirst());
43-
}
47+
String dmnPath = storageService.getCheckDmnModelPath(check.getModule(), checkId, check.getVersion());
48+
Optional<String> dmnModel = storageService.getStringFromStorage(dmnPath);
49+
dmnModel.ifPresent(check::setDmnModel);
50+
51+
return Optional.of(check);
4452
}
4553

4654
public List<EligibilityCheck> getChecksInBenefit(Benefit benefit){

builder-frontend/src/api/check.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,29 @@ export const updateCheck = async (check: EligibilityCheck) => {
9595
}
9696
};
9797

98+
export const saveCheckDmn = async (checkId: string, dmnModel: string) => {
99+
const url = apiUrl + "/save-check-dmn";
100+
try {
101+
const response = await authFetch(url, {
102+
method: "POST",
103+
headers: {
104+
"Content-Type": "application/json",
105+
Accept: "application/json",
106+
},
107+
body: JSON.stringify({ id: checkId, dmnModel: dmnModel }),
108+
});
109+
110+
if (!response.ok) {
111+
throw new Error(`DMN save failed with status: ${response.status}`);
112+
}
113+
const data = await response.json();
114+
return data;
115+
} catch (error) {
116+
console.error("Error saving DMN for check:", error);
117+
throw error; // rethrow so you can handle it in your component if needed
118+
}
119+
};
120+
98121
export const fetchUserDefinedChecks = async (): Promise<EligibilityCheck[]> => {
99122
// Simulate an API call delay -- TODO: update to greater than 1ms delay
100123
await new Promise((resolve) => setTimeout(resolve, 1000));

builder-frontend/src/components/homeScreen/eligibilityCheckList/eligibilityCheckDetail/EligibilityCheckDetail.tsx

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,26 @@ import { useParams } from "@solidjs/router";
33

44
import Header from "../../../Header";
55
import Loading from "../../../Loading";
6+
import ParameterModal from "./modals/ParameterModal";
7+
import KogitoDmnEditorView from "./KogitoDmnEditorView";
8+
69
import eligibilityCheckDetailResource from "./eligibilityCheckDetailResource";
710

811
import type { EligibilityCheck, ParameterDefinition } from "@/types";
9-
import ParameterModal from "./modals/ParameterModal";
1012

1113

1214
const EligibilityCheckDetail = () => {
1315
const { checkId } = useParams();
1416

1517
const [screenMode, setScreenMode] = createSignal<"inputs_params" | "dmn">("inputs_params");
18+
const [tmpDmnModel, setTmpDmnModel] = createSignal<string>("");
1619

1720
const { eligibilityCheck, actions, actionInProgress, initialLoadStatus } = (
1821
eligibilityCheckDetailResource(() => checkId)
1922
);
2023

2124
return (
22-
<div>
25+
<div class="h-screen flex flex-col">
2326
<Show when={initialLoadStatus.loading() || actionInProgress()}>
2427
<Loading/>
2528
</Show>
@@ -37,6 +40,14 @@ const EligibilityCheckDetail = () => {
3740
>
3841
DMN Definition
3942
</div>
43+
<Show when={screenMode() === "dmn"}>
44+
<div
45+
class="btn-default btn-gray"
46+
onClick={() => actions.saveDmnModel(tmpDmnModel())}
47+
>
48+
Save DMN
49+
</div>
50+
</Show>
4051
</div>
4152

4253
<Show when={eligibilityCheck().id !== undefined && !initialLoadStatus.loading()}>
@@ -45,9 +56,10 @@ const EligibilityCheckDetail = () => {
4556
<ParametersScreen eligibilityCheck={eligibilityCheck} addParameter={actions.addParameter}/>
4657
</Match>
4758
<Match when={screenMode() === "dmn"}>
48-
<div class="p-2">
49-
<h2 class="text-xl font-semibold mb-2">DMN</h2>
50-
</div>
59+
<KogitoDmnEditorView
60+
dmnModel={() => eligibilityCheck().dmnModel}
61+
setTmpDmnModel={setTmpDmnModel}
62+
/>
5163
</Match>
5264
</Switch>
5365
</Show>
@@ -74,7 +86,7 @@ const ParametersScreen = (
7486
Create New Parameter
7587
</div>
7688
<Show when={eligibilityCheck().parameters.length > 0} fallback={<p>No parameters defined.</p>}>
77-
<ul class="list-disc list-inside">
89+
<div class="flex flex-wrap gap-4">
7890
<For each={eligibilityCheck().parameters}>
7991
{(param) => (
8092
<div
@@ -88,7 +100,7 @@ const ParametersScreen = (
88100
</div>
89101
)}
90102
</For>
91-
</ul>
103+
</div>
92104
</Show>
93105
</div>
94106
{
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { onCleanup, onMount, Accessor, Setter } from "solid-js";
2+
3+
import * as DmnEditor from "@kogito-tooling/kie-editors-standalone/dist/dmn";
4+
5+
6+
/* Utility function to trim starting and ending quotes from XML string */
7+
const trimXml = (xml: string): string => {
8+
const firstChar: string = xml.charAt(0);
9+
const lastChar: string = xml.charAt(xml.length - 1);
10+
11+
if (firstChar === '"' && lastChar === '"') {
12+
// Return without starting and ending quotes
13+
return xml.slice(1, -1);
14+
}
15+
return xml;
16+
};
17+
18+
export default function KogitoDmnEditorView({
19+
dmnModel, setTmpDmnModel,
20+
}: {
21+
dmnModel: Accessor<string>;
22+
setTmpDmnModel: Setter<string>;
23+
}) {
24+
let editorElement: null | Element = null;
25+
let editorObject: null | any = null;
26+
let saveTimeoutId: null | number = null;
27+
28+
/* SolidJS Lifecycle */
29+
onMount(async () => {
30+
initializeEditor();
31+
});
32+
onCleanup(() => {
33+
if (editorObject) editorObject.close();
34+
if (saveTimeoutId) clearTimeout(saveTimeoutId);
35+
});
36+
37+
const initializeEditor = async () => {
38+
const modelXml: string = dmnModel();
39+
const initialDmnPromise: string = (modelXml) ? trimXml(modelXml): "";
40+
41+
editorObject = DmnEditor.open({
42+
container: editorElement,
43+
initialContent: Promise.resolve(initialDmnPromise),
44+
resources: new Map(),
45+
readOnly: false,
46+
});
47+
editorObject.subscribeToContentChanges(
48+
async (_: boolean) => {
49+
const xml = await editorObject.getContent();
50+
setTmpDmnModel(xml);
51+
}
52+
);
53+
};
54+
55+
return (
56+
<div class="h-full overflow-auto">
57+
<div
58+
class="h-full"
59+
ref={ (el: HTMLDivElement) => (editorElement = el) }
60+
/>
61+
</div>
62+
);
63+
}

builder-frontend/src/components/homeScreen/eligibilityCheckList/eligibilityCheckDetail/eligibilityCheckDetailResource.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import { createResource, createEffect, Accessor, createSignal } from "solid-js";
22
import { createStore } from "solid-js/store";
33

4-
import { fetchCheck, updateCheck } from "@/api/check";
4+
import { fetchCheck, saveCheckDmn, updateCheck } from "@/api/check";
55

6-
import type { EligibilityCheck, ParameterDefinition } from "@/types";
6+
import type { EligibilityCheckDetail, ParameterDefinition } from "@/types";
77

88

99
export interface EligibilityCheckDetailResource {
10-
eligibilityCheck: () => EligibilityCheck;
10+
eligibilityCheck: () => EligibilityCheckDetail;
1111
actions: {
1212
addParameter: (parameterDef: ParameterDefinition) => Promise<void>;
13+
saveDmnModel: (dmnString: string) => Promise<void>;
1314
};
1415
actionInProgress: Accessor<boolean>;
1516
initialLoadStatus: {
@@ -23,7 +24,7 @@ const eligibilityCheckDetailResource = (checkId: Accessor<string>): EligibilityC
2324
const [actionInProgress, setActionInProgress] = createSignal<boolean>(false);
2425

2526
// Local fine-grained store
26-
const [eligibilityCheck, setEligibilityCheck] = createStore<EligibilityCheck | null>(null);
27+
const [eligibilityCheck, setEligibilityCheck] = createStore<EligibilityCheckDetail | null>(null);
2728

2829
// When resource resolves, sync it into the store
2930
createEffect(() => {
@@ -33,7 +34,7 @@ const eligibilityCheckDetailResource = (checkId: Accessor<string>): EligibilityC
3334
});
3435

3536
const addParameter = async (parameterDef: ParameterDefinition) => {
36-
const updatedCheck: EligibilityCheck = { ...eligibilityCheck, parameters: [...eligibilityCheck.parameters, parameterDef] };
37+
const updatedCheck: EligibilityCheckDetail = { ...eligibilityCheck, parameters: [...eligibilityCheck.parameters, parameterDef] };
3738
setActionInProgress(true);
3839
try {
3940
await updateCheck(updatedCheck);
@@ -44,9 +45,23 @@ const eligibilityCheckDetailResource = (checkId: Accessor<string>): EligibilityC
4445
setActionInProgress(false);
4546
};
4647

48+
const saveDmnModel = async (dmnString: string) => {
49+
setActionInProgress(true);
50+
try {
51+
await saveCheckDmn(eligibilityCheck.id, dmnString);
52+
await refetch();
53+
} catch (e) {
54+
console.error("Failed to save DMN model", e);
55+
}
56+
setActionInProgress(false);
57+
}
58+
4759
return {
4860
eligibilityCheck: () => eligibilityCheck,
49-
actions: { addParameter },
61+
actions: {
62+
addParameter,
63+
saveDmnModel,
64+
},
5065
actionInProgress,
5166
initialLoadStatus: {
5267
loading: () => eligibilityCheckResource.loading,

0 commit comments

Comments
 (0)