Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d281118
Merge pull request #29 from bcgov/test
DavidOkulski May 20, 2025
f1cbc61
Merge pull request #37 from bcgov/test
DavidOkulski Jun 6, 2025
4628757
Merge pull request #67 from bcgov/test
bzimonjaSDPR Jul 16, 2025
212796b
Merge pull request #69 from bcgov/test
DavidOkulski Jul 17, 2025
009e9cc
Increase pod count and add tini
DavidOkulski Jul 23, 2025
c8d7296
Increase pod count
DavidOkulski Jul 23, 2025
d4b6530
Merge pull request #70 from bcgov/pr-fix-defunct-pid
saranyaviswam Jul 23, 2025
a9963ed
Merge branch 'dev' of https://github.com/bcgov/Communication-Layer
tgunderson Jul 24, 2025
56872e0
Changes for Submit
saranyaviswam Jul 24, 2025
cfe4a19
Merge branch 'dev' into ADO-2799
saranyaviswam Jul 24, 2025
55dcf15
Changes for Submit action 2799
saranyaviswam Jul 25, 2025
dc71f3a
Merge pull request #71 from bcgov/ADO-2799
bzimonjaSDPR Jul 25, 2025
4072971
added support for databinding conditions
DavidOkulski Jul 28, 2025
5cd10be
Update formatting
DavidOkulski Jul 28, 2025
b63fc35
Merge pull request #72 from bcgov/pr-databinding-conditions
bzimonjaSDPR Jul 28, 2025
027288b
Update gitignore
danilomeireles Jul 31, 2025
2e52616
Update error messages.
danilomeireles Jul 31, 2025
2d4b00b
Add validation
danilomeireles Jul 31, 2025
2519e17
refactor databindings
DavidOkulski Aug 1, 2025
0f585ea
Pass Template and Tool properties.
danilomeireles Aug 1, 2025
22605eb
Updated schema to support new Klamm schema
DavidOkulski Aug 1, 2025
c13f52a
Add area validation.
danilomeireles Aug 1, 2025
0795496
Merge pull request #74 from bcgov/pr-databinding-refactor
saranyaviswam Aug 1, 2025
68e09aa
Update form definition to support old databindings
DavidOkulski Aug 5, 2025
b4f2356
Merge pull request #75 from bcgov/pr-update-form-definition
bzimonjaSDPR Aug 5, 2025
5073992
Fix the area validation logic.
danilomeireles Aug 5, 2025
a8fb45f
Merge branch 'dev' into ADO-3075
danilomeireles Aug 6, 2025
7c369ec
Merge pull request #73 from bcgov/ADO-3075
bzimonjaSDPR Aug 6, 2025
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/node_modules
.env
/common/*
.idea/
156 changes: 91 additions & 65 deletions databindingsHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ async function populateDatabindings(formJson, params) {
}

// Map the API data to the form fields
const boundData = bindDataToFields(formJson, apiData);
const boundData = bindDataToFields(formJson, apiData, params);

return boundData;
} catch (error) {
Expand Down Expand Up @@ -51,80 +51,106 @@ async function fetchDataFromSources(dataSources, params) {
return data;
}

function bindDataToFields(formJson, fetchedData) {
function getBindingValue(bindings, fetchedData, params = {}) {
// Normalize singular binding to array if needed
let list;
if (Array.isArray(bindings)) {
list = bindings;
} else if (bindings && bindings.source && bindings.path) {
list = [{
order: 0,
condition: '',
source: bindings.source,
path: bindings.path
}];
} else {
return null;
}

const sortedBindings = list.slice().sort((a, b) => (a.order || 0) - (b.order || 0));

// Evaluate each binding
for (const binding of sortedBindings) {
// Parse condition
const [conditionField, valuesListString = ''] = (binding.condition || '').split('=').map(str => str.trim());

// Evaluate condition
if (conditionField) {
const allowedValues = valuesListString.split(',').map(str => str.trim());
const currentParam = params[conditionField];
if (!allowedValues.includes(currentParam)) {
continue;
}
}

const sourceData = fetchedData[binding.source] || {};
const results = JSONPath({ path: binding.path, json: sourceData });

if (Array.isArray(results) && results.length > 0 && results[0] != null && results[0] !== '') {
return results[0];
}
}

return null;
}

function bindDataToFields(formJson, fetchedData, params = {}) {
const formData = {};

const processItemsForDatabinding = (items) => {
items.forEach(field => {
//helper function to bind group
function bindRowsToGroup(field, rows) {
return rows.map((rowObj, index) => {
const row = {};
const binding = Array.isArray(field.databindings) ? field.databindings[0] : field.databindings || null;
const localDatabindings = binding ? { ...fetchedData, [binding.source]: rowObj } : { ...fetchedData };

field.groupItems.forEach(groupItem =>
groupItem.fields.forEach(groupField => {
if (!groupField.databindings) return;
const fieldIdInGroup = `${field.id}-${index}-${groupField.id}`;
const subVal = getBindingValue(groupField.databindings,localDatabindings,params);
row[fieldIdInGroup] = transformValueToBindIfNeeded(groupField, subVal) || '';
})
);
return row;
});
}

function processItemsForDatabinding(items) {
items.forEach(field => {
// containers
if (field.type === 'container' && field.containerItems) {
processItemsForDatabinding(field.containerItems);
} else if (field.databindings != null) {

const dataSourceName = field.databindings.source;
const dataPath = field.databindings.path;
const fetchedSourceData = fetchedData[dataSourceName];
let valueFromPath = "";

// Fetch the value from the fetched data
if (fetchedSourceData) {
valueFromPath = JSONPath(dataPath, fetchedSourceData);
// If the field is a group, handle groupItems
if (field.type === 'group' && field.groupItems) {
// Create an array to store groupData objects for each item in valueFromPath
const groupDataArray = valueFromPath.map((pathObj, index) => {
const groupData = {};

// For each item in valueFromPath, iterate over the group fields
field.groupItems.forEach(groupItem => {
groupItem.fields.forEach(groupField => {
if (groupField.databindings) {
const fieldBindings = groupField.databindings.path;
const fieldIdInGroup = `${field.id}-${index}-${groupField.id}`;
// Assign data from pathObj or an empty string if not available
groupData[fieldIdInGroup] = transformValueToBindIfNeeded(groupField, pathObj[fieldBindings]) || '';
}
});
});
return groupData;
});

// Assign the groupDataArray to formData for this field's ID
formData[field.id] = groupDataArray;
} else {
formData[field.id] = valueFromPath.length > 0 ? transformValueToBindIfNeeded(field, valueFromPath[0]) : null;
}
return processItemsForDatabinding(field.containerItems);
}

// groups
if (field.type === 'group' && field.groupItems) {
if (field.repeater) {
// repeatable group
const binding = Array.isArray(field.databindings) ? field.databindings[0] : field.databindings || null;
const rows = binding ? JSONPath({ path: binding.path, json: fetchedData }) || [] : [];
formData[field.id] = bindRowsToGroup(field, rows);
} else {
// single-instance group
const binding = Array.isArray(field.databindings) ? field.databindings[0]: field.databindings || null;
const rows = binding? (JSONPath({ path: binding.path, json: fetchedData }) || []).slice(0, 1) : [{}];
formData[field.id] = bindRowsToGroup(field, rows);
}
} else if (field.type === 'group' && field.groupItems && !field.repeater) {
const transformedItem = {};
//get the databindings from individual filed from non-repeating group
field.groupItems.forEach(groupItem => {
groupItem.fields.forEach(groupField => {
if (groupField.databindings) {
const dataSourceName = groupField.databindings.source;
const dataPath = groupField.databindings.path;
const fetchedSourceData = fetchedData[dataSourceName];
const fieldIdInGroup = `${field.id}-0-${groupField.id}`;
if (fetchedSourceData) {
const valueFromPathForGroupField = JSONPath(dataPath, fetchedSourceData);
//do date conversion here uisng a function - chceking the type as date and then checking format and applying
transformedItem[fieldIdInGroup] = valueFromPathForGroupField.length > 0 ? transformValueToBindIfNeeded(groupField, valueFromPathForGroupField[0]) : null; // Replace with actual value
}
}
});
});
formData[field.id] = [transformedItem];
return;
}

//Single fields
if (field.databindings) {
const raw = getBindingValue(field.databindings, fetchedData, params);
formData[field.id] = raw != null ? transformValueToBindIfNeeded(field, raw) : null;
}
});
}
};

if (formJson?.data?.items) {
if (Array.isArray(formJson?.data?.items)) {
processItemsForDatabinding(formJson.data.items);
}

// Iterate through fields
formJson.data.items.forEach

// console.dir(formData, { depth: null, colors: true });
return formData;
}

Expand Down
5 changes: 4 additions & 1 deletion dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ RUN apt-get update && apt-get install -y \
libxrandr2 \
libxrender1 \
xdg-utils \
&& rm -rf /var/lib/apt/lists/*
tini \
&& rm -rf /var/lib/apt/lists/*

# Install Chromium
RUN apt-get update && apt-get install -y chromium && rm -rf /var/lib/apt/lists/*
Expand All @@ -35,6 +36,8 @@ RUN npm install

COPY . .

ENTRYPOINT ["/usr/bin/tini", "--"]

EXPOSE 3030

CMD ["npm", "run", "start"]
30 changes: 27 additions & 3 deletions errorHandling/errorMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@
"FORM_NOT_FOUND_IN_REQUEST":"The form is not found in the incoming request. Try again later and report to support, if the issue is not solved",
"GENERATE_ERROR_MSG":"Error generating the form .Close this window and try again in a few seconds",
"FORM_ALREADY_FINALIZED":"The form you are trying to generate is already finalized",
"FORM_CANNOT_BE_GENERATED":"The form cannot be generated."
"FORM_CANNOT_BE_GENERATED":"The form cannot be generated.",
"INVALID_FORM_ID":"Invalid Form ID.",
"INVALID_TOOL":"Invalid Tool.",
"INVALID_AREA":"Invalid Area.",
"PARAMS_NOT_FOUND":"Parameters not found to generate form",
"UNKNOWN_ORIGIN_SERVER":"Unknown Origin Server",
"NO_ACTION_FOUND":"No action found",
"UNKNOWN_ACTION":"Unknown action encountered",
"ERROR_IN_EXECUTING_ACTION":"Error in executing action type"
},
"test": {
"FORM_ID_REQUIRED": "Close this window and try again in a few seconds",
Expand All @@ -27,7 +35,15 @@
"FORM_NOT_FOUND_IN_REQUEST":"Try again later and report to support, if the issue is not solved",
"GENERATE_ERROR_MSG":"Close this window and try again in a few seconds",
"FORM_ALREADY_FINALIZED":"The form you are trying to generate is already finalized",
"FORM_CANNOT_BE_GENERATED":"The form cannot be generated."
"FORM_CANNOT_BE_GENERATED":"The form cannot be generated.",
"INVALID_FORM_ID":"Invalid Form ID.",
"INVALID_TOOL":"Invalid Tool.",
"INVALID_AREA":"Invalid Area.",
"PARAMS_NOT_FOUND":"Parameters not found to generate form",
"UNKNOWN_ORIGIN_SERVER":"Unknown Origin Server",
"NO_ACTION_FOUND":"No action found",
"UNKNOWN_ACTION":"Unknown action encountered",
"ERROR_IN_EXECUTING_ACTION":"Error in executing action type"
},
"prod": {
"FORM_ID_REQUIRED": "Close this window and try again in a few seconds",
Expand All @@ -42,6 +58,14 @@
"FORM_NOT_FOUND_IN_REQUEST":"Try again later and report to support, if the issue is not solved",
"GENERATE_ERROR_MSG":"Close this window and try again in a few seconds",
"FORM_ALREADY_FINALIZED":"The form you are trying to generate is already finalized",
"FORM_CANNOT_BE_GENERATED":"The form cannot be generated."
"FORM_CANNOT_BE_GENERATED":"The form cannot be generated.",
"INVALID_FORM_ID":"Invalid Form ID.",
"INVALID_TOOL":"Invalid Tool.",
"INVALID_AREA":"Invalid Area.",
"PARAMS_NOT_FOUND":"Parameters not found to generate form",
"UNKNOWN_ORIGIN_SERVER":"Unknown Origin Server",
"NO_ACTION_FOUND":"No action found",
"UNKNOWN_ACTION":"Unknown action encountered",
"ERROR_IN_EXECUTING_ACTION":"Error in executing action type"
}
}
28 changes: 28 additions & 0 deletions generateHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,34 @@ async function generateNewTemplate(req, res) {
.send({ error: getErrorMessage("FORM_ALREADY_FINALIZED") });
}

if (icm_metadata["Template"] !== template_id) {
return res
.status(400)
.send({ error: getErrorMessage("INVALID_FORM_ID") });
}

if (icm_metadata["Tool"]?.toLowerCase() !== "formfoundry") {
return res
.status(400)
.send({ error: getErrorMessage("INVALID_TOOL") });
}

const requestArea = params.area?.toLowerCase();

if (requestArea) {
const icmArea = icm_metadata.Categorie?.toLowerCase();

const isServiceRequest = requestArea === "service request";

const isAreaValid =
(isServiceRequest && icmArea === "application") ||
(!isServiceRequest && requestArea === icmArea);

if (!isAreaValid) {
return res.status(400).send({ error: getErrorMessage("INVALID_AREA") });
}
}

const formJson = await constructFormJson(template_id, params);

if (formJson != null) {
Expand Down
4 changes: 2 additions & 2 deletions helm/values.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
replicaCount: 1
replicaCount: 2

image:
repository: ghcr.io/bcgov/communication-layer
tag: test
tag: latest
pullPolicy: Always

service:
Expand Down
24 changes: 11 additions & 13 deletions portal/generatePortalIntegratedHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,29 @@ const {getParametersFromPortal , expireTokenInPortal} = require("./loadPortalDa
async function generatePortalIntegratedTemplate(req, res) {
try {
const params = req.body;
const token = params["id"];
const token = params["id"];
const userId = "test";
if (!token ) {
return res
.status(400)
.send({ error: 'No token found' });
}
const userId = "test";
const portalId = params["portalId"];
//const targetApp = appConfig[portalId];
}

const rawHost = (req.get("X-Original-Server") || req.hostname);
const targetApp = appConfig[rawHost];

if(!targetApp) {
return res.status(400).send({ error: 'Unknown app ID' });
return res.status(400).send({ error: getErrorMessage("UNKNOWN_ORIGIN_SERVER") });
}
const paramsFromPortal = await getParametersFromPortal(targetApp,token,userId);
const paramsFromPortal = await getParametersFromPortal(targetApp,token,userId);

if(!paramsFromPortal) {
return res.status(400).send({ error: 'Parameters not found to generate form' });
return res.status(400).send({ error: getErrorMessage("PARAMS_NOT_FOUND") });
}

const formJson = await constructFormJson(paramsFromPortal);

if (formJson != null) {
//expire token
const isTokenExpired = await expireTokenInPortal(targetApp,token,userId);
console.log("isTokenExpired >> ",isTokenExpired);

res.status(200).send({
save_data: formJson
});
Expand All @@ -46,9 +41,10 @@ async function generatePortalIntegratedTemplate(req, res) {
}
} catch (error) {
console.error(`Error generating the form:`, error);

return res
.status(400)
.send({ error: getErrorMessage("GENERATE_ERROR_MSG") });
.send({ error: getErrorMessage("FORM_CANNOT_BE_GENERATED") });
}
}

Expand All @@ -71,4 +67,6 @@ async function constructFormJson(params) {
return fullJSON;
}



module.exports = generatePortalIntegratedTemplate;
Loading