Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
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
94548ce
Merge pull request #76 from bcgov/dev
bzimonjaSDPR Aug 6, 2025
4725f40
Changes for Open Form in Portal
saranyaviswam Aug 6, 2025
9495213
Merge branch 'dev' into ADO-2671_OpenFormInPortal
saranyaviswam Aug 6, 2025
f561cb2
Merge pull request #77 from bcgov/ADO-2671_OpenFormInPortal
bzimonjaSDPR Aug 6, 2025
6980189
Update the actionType to actionType as per coming from form
saranyaviswam Aug 7, 2025
d308dc6
Merge pull request #78 from bcgov/ADO-2671_OpenFormInPortal
bzimonjaSDPR Aug 7, 2025
314ce43
Add new endpoint to process json clob data
danilomeireles Aug 8, 2025
03963e0
Process clob data data.
danilomeireles Aug 9, 2025
d96e3d6
Update router.js
danilomeireles Aug 10, 2025
5afb659
Refactor processIcmJsonClobData
danilomeireles Aug 10, 2025
21bf42a
Code cleanup.
danilomeireles Aug 10, 2025
ccd46ea
Use existing settings for siebel url and username
danilomeireles Aug 11, 2025
accf5f8
Fix URL path
danilomeireles Aug 12, 2025
b91ad53
(ADO-3158) truncate json UUID before XML creation, remove index/offes…
NicolaSDPR1 Aug 12, 2025
0b62fa0
Merge pull request #80 from bcgov/dev
bzimonjaSDPR Aug 12, 2025
77221e2
Merge pull request #81 from bcgov/ADO-3158-JSON-XML-Changes
bzimonjaSDPR Aug 14, 2025
41355cd
Add new endpoint to process json clob data
danilomeireles Aug 8, 2025
c7859fd
Process clob data data.
danilomeireles Aug 9, 2025
7007a82
Update router.js
danilomeireles Aug 10, 2025
5c9e449
Refactor processIcmJsonClobData
danilomeireles Aug 10, 2025
d5d8345
Code cleanup.
danilomeireles Aug 10, 2025
c7cd6ea
Use existing settings for siebel url and username
danilomeireles Aug 11, 2025
82dfe40
Fix URL path
danilomeireles Aug 12, 2025
c6ccad0
Update databindings host/endpoint processing
DavidOkulski Aug 15, 2025
9f7eb30
Merge branch 'ADO-2495' of https://github.com/bcgov/Communication-Lay…
danilomeireles Aug 15, 2025
6fac249
Update the attachement id value.
danilomeireles Aug 15, 2025
f09d46a
Create a second endpoint with only the answers in the response
danilomeireles Aug 18, 2025
37fdee5
Rename answers endpoint
danilomeireles Aug 18, 2025
308dd81
Update response object.
danilomeireles Aug 18, 2025
7039f9a
Merge pull request #79 from bcgov/ADO-2495
bzimonjaSDPR Aug 18, 2025
c373f46
Quick fix using XMLDEC to ensure the beginning xml tag does not inclu…
NicolaSDPR1 Aug 19, 2025
44af38c
Merge pull request #82 from bcgov/ADO-3158-XML-quick-fix
bzimonjaSDPR Aug 19, 2025
dfa66b7
Remove answer drilldown
DavidOkulski Aug 20, 2025
c156809
Use apiHost for portal endpoints
DavidOkulski Aug 20, 2025
cd130c8
Merge pull request #83 from bcgov/pr-portalendpoint-apiHost
DavidOkulski Aug 20, 2025
cbf350a
Merge pull request #84 from bcgov/dev
DavidOkulski Aug 20, 2025
0ad7e44
Update resource limits
DavidOkulski Aug 21, 2025
006dcd4
Merge pull request #85 from bcgov/pr-resource-update
bzimonjaSDPR Aug 21, 2025
8bcfc65
Merge pull request #86 from bcgov/dev
DavidOkulski Aug 21, 2025
64dc7ac
remove logging
DavidOkulski Aug 21, 2025
ada86f2
Merge pull request #88 from bcgov/dev
DavidOkulski Aug 21, 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/
200 changes: 129 additions & 71 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 All @@ -40,6 +40,7 @@ async function fetchDataFromSources(dataSources, params) {

let updatedParams = updateParams(source.params || {}, params, data);
const response = await readJsonFormApi(source, { ...params, ...updatedParams });
//console.log("Response:",response);
data[source.name] = response;

} catch (error) {
Expand All @@ -51,80 +52,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 Expand Up @@ -161,6 +188,7 @@ async function readJsonFormApi(datasource, pathParams) {
const headers = {
Authorization: `Bearer ${grant.id_token.token}`,
"X-ICM-TrustedUsername": username,
"X-API-Host": apiHost,
}
if (type.toUpperCase() === 'GET') {
// For GET requests, add params directly in axios config
Expand All @@ -173,6 +201,7 @@ async function readJsonFormApi(datasource, pathParams) {
}

// Store response data
//console.log("Response:",response);
return ensureObjectOrArray(response.data);

} catch (error) {
Expand All @@ -184,6 +213,10 @@ async function readJsonFormApi(datasource, pathParams) {
function buildUrlWithParams(host, endpoint, pathVariables) {
const hostFromEnv = getHost(pathVariables,host);
const endpointFromEnv = getEndpoint(endpoint);

if (!hostFromEnv) throw new Error("API host not resolved");
if (!endpointFromEnv) throw new Error("API endpoint not resolved");

let url = `${hostFromEnv}${endpointFromEnv}`;
// Replace any placeholder variables like @@attachmentId
Object.keys(pathVariables).forEach(key => {
Expand All @@ -196,18 +229,23 @@ function buildUrlWithParams(host, endpoint, pathVariables) {


function getHost(params, host) {
// Use host from environment variable if available, otherwise fall back to JSON
try {
return params["apiHost"];
} catch {
return process.env[host];
// Use process.env if present
const fromEnv = resolveMaybeEnv(host);
if (fromEnv) return fromEnv;

//Fall back to path params
if (params && typeof params.apiHost === "string" && params.apiHost.trim()) {
return params.apiHost.trim();
}

return host ? (process.env[host] ?? host) : null;
}

function getEndpoint(endpoint) {
// Use endpoint from environment variable if available, otherwise fall back to JSON
return process.env[endpoint] || endpoint;
return resolveMaybeEnv(endpoint) ?? endpoint;
}

function buildBodyWithParams(bodyFromJson, pathVariables) {

let bodyString = JSON.stringify(bodyFromJson);
Expand Down Expand Up @@ -290,5 +328,25 @@ function updateParams(params, pathParams = {}, allFetchedData = {}) {
return updated;
}

function resolveMaybeEnv(str) {
if (typeof str !== "string" || !str) {
return null;
}

// Check if value is in the form "process.env.VAR_NAME"
const envPattern = /^process\.env\.(.+)$/;
const match = str.match(envPattern);

if (!match) {
return str;
}

const envVarName = match[1];
const envValue = process.env[envVarName];

return envValue != null ? envValue : str;
}


module.exports.populateDatabindings = populateDatabindings;
module.exports.buildUrlWithParams = buildUrlWithParams;
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
8 changes: 7 additions & 1 deletion helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ service:

configMapName: commlayer

resources: {}
resources:
requests:
memory: "128Mi"
cpu: "50m"
limits:
memory: "512Mi"
cpu: "250m"

nodeSelector: {}

Expand Down
Loading