diff --git a/WebApp/GeneratedFormExamples/Example1.json b/WebApp/GeneratedFormExamples/Example1.json new file mode 100644 index 0000000..355f9e8 --- /dev/null +++ b/WebApp/GeneratedFormExamples/Example1.json @@ -0,0 +1,47 @@ +{ + "targetDatabases": [ + "d1", + "d2" + ], + "description": "desc1", + "steps": [ + { + "goal": "s1", + "columns": [ + "s1col1", + "s1col2" + ], + "aggregation": { + "groupBy": [ + "s1gb1", + "s1gb2" + ], + "orderBy": [ + { + "column": "s1ob1", + "direction": "DESC" + }, + { + "column": "s1ob2", + "direction": "ASC" + } + ] + }, + "filtering": { + "conditions": [ + { + "column": "s1ccol1", + "operator": "=", + "value": "s1cval1" + }, + { + "column": "s1ccol2", + "operator": "=", + "value": "s1cval2" + } + ] + }, + "limit": 1 + } + ] +} diff --git a/WebApp/GeneratedFormExamples/Example1.png b/WebApp/GeneratedFormExamples/Example1.png new file mode 100644 index 0000000..7e441ad Binary files /dev/null and b/WebApp/GeneratedFormExamples/Example1.png differ diff --git a/WebApp/GeneratedFormExamples/Example2.json b/WebApp/GeneratedFormExamples/Example2.json new file mode 100644 index 0000000..4ade7b0 --- /dev/null +++ b/WebApp/GeneratedFormExamples/Example2.json @@ -0,0 +1,85 @@ +Generated Query: { + "targetDatabases": [ + "d1", + "d2" + ], + "description": "desc1", + "steps": [ + { + "goal": "", + "columns": [ + "s1col1", + "s1col2" + ], + "aggregation": { + "groupBy": [ + "s1gb1", + "s1gb2" + ], + "orderBy": [ + { + "column": "s1ob1", + "direction": "DESC" + }, + { + "column": "s1ob2", + "direction": "ASC" + } + ] + }, + "filtering": { + "conditions": [ + { + "column": "s1ccol1", + "operator": "=", + "value": "s1cval1" + }, + { + "column": "s1ccol2", + "operator": "=", + "value": "s1cval2" + } + ] + }, + "limit": 1 + }, + { + "goal": "", + "columns": [ + "s2col1", + "s2col2" + ], + "aggregation": { + "groupBy": [ + "s2gb1", + "s2gb2" + ], + "orderBy": [ + { + "column": "s2ob1", + "direction": "ASC" + }, + { + "column": "s2ob2", + "direction": "ASC" + } + ] + }, + "filtering": { + "conditions": [ + { + "column": "s2ccol1", + "operator": "=", + "value": "s2cval1" + }, + { + "column": "s2ccol2", + "operator": "=", + "value": "s2cval2" + } + ] + }, + "limit": 2 + } + ] +} diff --git a/WebApp/dynamic_form.html b/WebApp/dynamic_form.html index 3997e64..8e7b653 100644 --- a/WebApp/dynamic_form.html +++ b/WebApp/dynamic_form.html @@ -11,6 +11,9 @@

Dynamic JSON Query Form

- + + + + diff --git a/WebApp/scripts/JSONExtraction.js b/WebApp/scripts/JSONExtraction.js new file mode 100644 index 0000000..b277f99 --- /dev/null +++ b/WebApp/scripts/JSONExtraction.js @@ -0,0 +1,102 @@ +function collectFromForm() { + // Go throught the structure of the document and extract filled information from form + // Also filters out from the final query fields that are present in the form + // i.e the HTML code is generated, but input fields are empty. + + // Get target databases + + const targetDatabaseFields = document.querySelectorAll('.targetDatabase .repeatable-container .repeatable-row input[type="text"]'); + const targetDatabases = Array.from(targetDatabaseFields) + .map(input => input.value.trim()) + .filter(val => val !== ''); + + + // Get Description + const descriptionField = document.getElementById('description'); + const description = descriptionField ? descriptionField.value.trim() : ''; + + // Get steps + const stepsRows = document.querySelectorAll('.steps .repeatable-container > .repeatable-row'); + const steps = Array.from(stepsRows).map(stepRow => { + + // Get the goal of the step + const goalField = stepRow.querySelector('[class^="goal_steps_"] input'); + const stepGoal = goalField ? goalField.value.trim() : ''; + + // Columns + const columnsContainer = stepRow.querySelector('[class^="columns_steps_"] .repeatable-container'); + const columnFields = columnsContainer ? columnsContainer.querySelectorAll('.repeatable-row input[type="text"]') : []; + const columns = Array.from(columnFields) + .map(cf => cf.value.trim()) + .filter(val => val !== ''); + + // Get group-by's columns + const groupByContainer = stepRow.querySelector('[class^="aggregation_steps_"] .group-by .repeatable-container'); + const groupByFields = groupByContainer ? groupByContainer.querySelectorAll('.repeatable-row input[type="text"]') : []; + const groupBy = Array.from(groupByFields) + .map(groupByField => groupByField.value.trim()) + .filter(val => val !== ''); + + // Get order-by's objects + const orderByContainer = stepRow.querySelector('[class^="aggregation_steps_"] .order-by .repeatable-container'); + const orderByRows = orderByContainer ? orderByContainer.querySelectorAll('.repeatable-row') : []; + const orderByObjects = Array.from(orderByRows).map(row => { + const columnField = row.querySelector('input[type="text"]'); + const directionField = row.querySelector('select'); + const column = columnField ? columnField.value.trim() : ''; + const direction = directionField ? directionField.value.trim() : ''; + return (column && direction) ? {column, direction} : null; + }).filter(object => object !== null); + + // Get Conditions + const conditionsContainer = stepRow.querySelector('[class^="filtering_steps_"] .conditions .repeatable-container'); + const conditionsRows = conditionsContainer ? conditionsContainer.querySelectorAll('.repeatable-row') : []; + const conditionsObjects = Array.from(conditionsRows).map(row => { + const textFields = row.querySelectorAll('input[type="text"]'); + const condColumnField = textFields [0] || null; + const valueField = textFields [1] || null; + const operatorField = row.querySelector('select'); + + const column = condColumnField ? condColumnField.value.trim() : ''; + const operator = operatorField ? operatorField.value.trim() : ''; + const value = valueField ? valueField.value.trim(): ''; + + return (column && operator && value) ? { column, operator, value } : null; + }).filter(condition => condition !== null); + + // Get limit + const limitField = stepRow.querySelector('[class^="limit_steps_"] input[type="number"]'); + const limitVal = limitField && limitField.value !== '' ? parseInt(limitField.value, 10): undefined; + const limit = isNaN(limitVal) ? undefined : limitVal; + + // Finally build the entire step + const stepObject = { + goal: stepGoal, + columns: columns, + aggregation: { + groupBy: groupBy, + orderBy: orderByObjects + }, + filtering: { + conditions: conditionsObjects + }, + limit: limit + }; + + // Get rid of empty steps (these are for some unknown (yet) reason generated alongside + // valid steps) + const isEmptyStep = !stepGoal && columns.length === 0 && groupBy.length === 0 && + orderByObjects.length === 0 && conditionsObjects.length === 0 && + typeof limit === 'undefined'; + + return isEmptyStep ? null : stepObject; + }).filter(stepObject => stepObject !== null); + + const queryObject = { + targetDatabases, + description, + steps + }; + + return queryObject; +} diff --git a/WebApp/scripts/dynamic_form_render.js b/WebApp/scripts/dynamic_form_render.js index 5aa1389..476d556 100644 --- a/WebApp/scripts/dynamic_form_render.js +++ b/WebApp/scripts/dynamic_form_render.js @@ -49,7 +49,7 @@ function renderForm(schema) { generateQueryButton.textContent = "Generate JSON"; generateQueryButton.type="button"; generateQueryButton.onclick = () => { - const query = generateQueryFromForm(schema); + const query = collectFromForm(); console.log("Generated Query:", JSON.stringify(query, null, 2)); };