Commit c22af466 authored by shpl19's avatar shpl19
Browse files

temp - changes to add/update table

parent c344635f
Pipeline #25860 passed with stages
in 2 minutes
Subproject commit ed5adee518fdc089faa39bee67e6aac7dcc91ca9
Subproject commit d378c2e3c5324a90a0a72479ce4be68e5ff65c68
......@@ -21,6 +21,7 @@
import { InputType, ValidationType } from "../utils/enumHandler";
import { SubForm, SubFormOptions } from "./subForm";
import { Table, TableOptions } from "./table";
/** Parameters used to create a input object. */
export interface InputOptions {
......@@ -42,6 +43,8 @@ export interface InputOptions {
sugestions?: Sugestion[];
/** A SubForm object that is a reference to another Form */
subForm?: SubFormOptions;
/** A table object */
table?: Table;
}
/** Validation contains the type of it, and n arguments to validate if necessary */
export interface Validation {
......@@ -82,6 +85,9 @@ export class Input {
public readonly sugestions: Sugestion[];
/** A SubForm object that is a reference to another Form */
public readonly subForm: SubForm;
/** A table object */
public table?: Table;
/**
* Creates a new instance of Input Class
* @param options - InputOptions instance to create a input.
......@@ -108,6 +114,11 @@ export class Input {
} else {
this.subForm = null;
}
if (options.table) {
this.table = options.table;
} else {
this.table = null;
}
this.validation = options.validation;
}
}
......@@ -21,6 +21,7 @@
import { expect } from "chai";
import { inputUpdateScenario } from "../../test/scenario";
import { DiffHandler } from "../utils/diffHandler";
import { InputType, UpdateType, ValidationType } from "../utils/enumHandler";
import { TestHandler } from "../utils/testHandler";
import { Input, InputOptions } from "./input";
......@@ -31,4 +32,11 @@ describe("InputUpdate", () => {
TestHandler.testInputUpdate(inputUpdateScenario.resInputUpdate, inputUpdateScenario.expInputUpdate);
done();
});
it("should create a valid inputUpdate which changes the table", (done) => {
const resInputUpdate = DiffHandler.diff(inputUpdateScenario.formWithDiffTable1, inputUpdateScenario.formWithTable1);
TestHandler.testInputUpdate(resInputUpdate.inputUpdates[0], inputUpdateScenario.expInputUpdate2);
done();
});
});
/*
* form-creator-api. RESTful API to manage and answer forms.
* Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre
* Departamento de Informatica - Universidade Federal do Parana - C3SL/UFPR
*
* This file is part of form-creator-api.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/** Parameters used to create a form object. */
export interface TableOptions {
/** Unique identifier of a Table instance */
id?: number;
rows: number;
columns: number;
matrix: any[][];
}
/**
* Form Class to manage project's forms
*/
export class Table {
/** Unique identifier of a Form instance */
public readonly id: number;
/** Form's title. An human-understandable identifier. Not unique */
public readonly rows: number;
/** Form Description, as propose */
public readonly columns: number;
/** Array of input. containing question */
public readonly matrix: any[][];
/**
* Creates a new instance of Form Class
* @param options - TableOptions instance to create a table.
*/
constructor(options: TableOptions) {
this.id = options.id ? options.id : null;
this.rows = options.rows;
this.columns = options.columns;
this.matrix = new Array(this.rows).fill(null).map(() => new Array(this.columns).fill(null));
}
}
......@@ -149,6 +149,23 @@ describe("Database Handler", () => {
});
});
it("should insert a table cell", (done) => {
dbhandler.form.executeQuery(dbHandlerScenario.insertCell, (err: Error, result?: QueryResult) => {
expect(err).to.be.a("null");
expect(result.command).to.be.equal("INSERT");
expect(result.rowCount).to.be.equal(1);
done();
});
});
it("should remove an existent table cell", (done) => {
dbhandler.form.executeQuery(dbHandlerScenario.deleteCell, (err: Error, result?: QueryResult) => {
expect(err).to.be.a("null");
expect(result.command).to.be.equal("UPDATE");
done();
});
});
it("should select all input validations", (done) => {
dbhandler.form.executeQuery(dbHandlerScenario.selectInputValidations, (err: Error, result?: QueryResult) => {
expect(err).to.be.a("null");
......@@ -639,4 +656,12 @@ describe("Read and Write on Database", () => {
done();
});
it("should insert an input with table", (done) => {
dbhandler.form.write(dbHandlerScenario.formWithTable1, (err: Error, formResult: Form) => {
expect(err).to.be.a("null");
TestHandler.testForm(dbHandlerScenario.formWithTable1, formResult);
done();
});
});
});
......@@ -23,6 +23,7 @@ import { Form, FormOptions } from "../core/form";
import { FormUpdate, FormUpdateOptions } from "../core/formUpdate";
import { Input, InputOptions } from "../core/input";
import { InputUpdate, InputUpdateOptions } from "../core/inputUpdate";
import { Table } from "../core/table";
import { InputType, UpdateType, ValidationType } from "./enumHandler";
import { Sorter } from "./sorter";
......@@ -61,6 +62,51 @@ export class DiffHandler {
if (sortedNewInputs[j].placement !== sortedOldInputs[i].placement) {
formUpdate.inputUpdates.push(DiffHandler.swapInput(sortedNewInputs[j], sortedOldInputs[i]));
}
if (sortedNewInputs[j].type === InputType.TABLE){
let rowsMax: number = sortedOldInputs[i].table.rows;
let columnsMax: number = sortedOldInputs[i].table.columns;
if (sortedNewInputs[j].table.rows > rowsMax){
rowsMax = sortedNewInputs[j].table.rows;
}
if (sortedNewInputs[j].table.columns > columnsMax){
columnsMax = sortedNewInputs[j].table.columns;
}
const matrixTmp: any[][] = new Array(rowsMax).fill(-1).map(() => new Array(columnsMax).fill(-1));
let k: number = 0;
let l: number = 0;
for (k; k < sortedNewInputs[j].table.rows; k++){
l = 0;
for (l; l < sortedNewInputs[j].table.columns; l++){
matrixTmp[k][l] = sortedNewInputs[j].table.matrix[k][l];
}
}
const tableTmp: Table = {
id: sortedNewInputs[j].table.id,
rows: rowsMax,
columns: columnsMax,
matrix: matrixTmp
};
const inputTmp: Input = {
id: sortedNewInputs[j].id,
placement: sortedNewInputs[j].placement,
description: sortedNewInputs[j].description,
enabled: sortedNewInputs[j].enabled,
question: sortedNewInputs[j].question,
type: sortedNewInputs[j].type,
validation: sortedNewInputs[j].validation,
sugestions: sortedNewInputs[j].sugestions,
subForm: sortedNewInputs[j].subForm,
table: tableTmp
};
formUpdate.inputUpdates.push(DiffHandler.tableInput(inputTmp));
}
j++;
i++;
}
......@@ -151,6 +197,21 @@ export class DiffHandler {
return inputUpdate;
}
/**
* Create an InputUpdate object which updates an Input that has a table.
* @param input - An input that has a table
* @returns - An InputUpdate object.
*/
private static tableInput(newInput: Input): InputUpdate {
const inputUpdate: InputUpdate = {
input: newInput
, inputOperation: UpdateType.TABLE
, value: null
};
return inputUpdate;
}
private static isIdValid(obj: any): boolean {
return ((obj.id !== null) && (obj.id !== undefined) && (obj.id > 0) && (typeof obj.id === "number"));
}
......
......@@ -29,6 +29,7 @@ export enum InputType {
RADIO,
SELECT,
SUBFORM,
TABLE,
NONE
}
......@@ -37,6 +38,7 @@ export enum UpdateType {
REMOVE,
SWAP,
REENABLED,
TABLE,
NONE
}
......@@ -86,6 +88,8 @@ export class EnumHandler {
return "select";
case InputType.SUBFORM:
return "subform";
case InputType.TABLE:
return "table";
default:
return "";
}
......@@ -109,6 +113,8 @@ export class EnumHandler {
return InputType.SELECT;
case "subform":
return InputType.SUBFORM;
case "table":
return InputType.TABLE;
default:
return InputType.NONE;
}
......@@ -185,6 +191,8 @@ export class EnumHandler {
return "swap";
case UpdateType.REENABLED:
return "reenabled";
case UpdateType.TABLE:
return "table";
default:
return "";
}
......
......@@ -26,6 +26,7 @@ import { FormUpdate, FormUpdateOptions } from "../core/formUpdate";
import { Input, InputOptions, Sugestion, Validation } from "../core/input";
import { InputUpdate, InputUpdateOptions } from "../core/inputUpdate";
import { SubForm } from "../core/subForm";
import { Table, TableOptions } from "../core/table";
import { EnumHandler, InputType, UpdateType, ValidationType } from "./enumHandler";
import { ErrorHandler } from "./errorHandler";
import { OptHandler } from "./optHandler";
......@@ -246,6 +247,7 @@ export class FormQueryBuilder extends QueryBuilder {
return;
}
// console.log("TIPO FORM: ", typeof(result.rows[0]["id"]));
const formTmp: Form = new Form({
id: result.rows[0]["id"]
, title: result.rows[0]["title"]
......@@ -329,6 +331,7 @@ export class FormQueryBuilder extends QueryBuilder {
, enabled: i["enabled"]
, validation: []
, sugestions: []
, table: undefined
, subForm: undefined
};
inputArrayTmp.push(new Input(inputTmp));
......@@ -393,6 +396,7 @@ export class FormQueryBuilder extends QueryBuilder {
, type: input.type
, validation: input.validation
, sugestions: input.sugestions
, table: input.table
, subForm: new SubForm({
id: i.id
, inputId: i.id_input
......@@ -411,22 +415,46 @@ export class FormQueryBuilder extends QueryBuilder {
outerCallback(null, newSubFormInputs);
});
},
(newSubFormInputs: Input[], anotherCallback: (er: Error, newForm: Form) => void) => {
(newSubFormInputs: Input[], anotherCallback: (er: Error, newTableInputs?: Input[]) => void) => {
let inputsTmp = inputs;
for (const i of newSubFormInputs) {
inputsTmp.push(i);
}
inputsTmp = Sorter.sortByPlacement(inputsTmp);
// inputsTmp = Sorter.sortByPlacement(inputsTmp);
const tableInputs: Input[] = inputsTmp.filter((obj) => obj.type === InputType.TABLE);
const otherInputs: Input[] = inputsTmp.filter((obj) => obj.type !== InputType.TABLE);
eachSeries(tableInputs, (input, innerCallback) => {
this.executeReadTable(input.id, (error: Error, table?: Table) => {
input.table = table;
// console.log("INPUT C/ TAB: ", input);
innerCallback(error);
});
}, (e) => {
if (e) {
anotherCallback(e, null);
return;
}
inputsTmp = otherInputs;
for (const i of tableInputs) {
inputsTmp.push(i);
}
inputsTmp = Sorter.sortByPlacement(inputsTmp);
anotherCallback(null, inputsTmp);
});
},
(newTableInputs: Input[], tableCallback: (er: Error, form?: Form) => void) => {
// console.log("INPUTS TMP: ", newTableInputs);
const formTmp: Form = new Form({
id: form.id
, title: form.title
, description: form.description
, inputs: inputsTmp
, inputs: newTableInputs
, answerTimes: form.answerTimes
, status: form.status
});
anotherCallback(null, formTmp);
tableCallback(null, formTmp);
}
], (er, formTmp?: Form) => {
if (er) {
......@@ -505,6 +533,68 @@ export class FormQueryBuilder extends QueryBuilder {
});
}
/**
* Asynchronously read tables from database without transactions.
* @param id - Form identifier which inputs are linked to.
* @param cb - Callback function which contains the data read.
* @param cb.err - Error information when the method fails.
* @param cb.result - inputs or null if inputs not exists.
*/
private executeReadTable(id: number, cb: (err: Error, table?: Table) => void) {
waterfall([
(callback: (err: Error, options: TableOptions) => void) => {
const queryString: string = "SELECT t.table_id, COUNT (DISTINCT row) AS row, COUNT (DISTINCT column_t) as column_t \
FROM tables t \
INNER JOIN input_table i ON t.table_id = i.table_id \
WHERE i.input_id=$1 AND t.enabled=true \
GROUP BY t.table_id;";
const query: QueryOptions = {
query: queryString
, parameters: [id]
};
this.executeQuery(query, (err: Error, result?: QueryResult) => {
// console.log("RESULTADO: ", result);
const tableOptions: TableOptions = {
id: result.rows[0]["table_id"],
rows: parseInt(result.rows[0]["row"], 10),
columns: parseInt(result.rows[0]["column_t"], 10),
matrix: []
};
callback(err, tableOptions);
});
},
(options: TableOptions, callback: (err: Error, table?: Table) => void) => {
const queryString: string = "SELECT id, table_id, row, column_t, value \
FROM tables \
WHERE table_id=$1 AND enabled=true ORDER BY id;";
const query: QueryOptions = {
query: queryString
, parameters: [options.id]
};
this.executeQuery(query, (err: Error, result?: QueryResult) => {
// console.log(options);
const table: Table = new Table(options);
// console.log(table.matrix);
for (const row of result.rows) {
const tRow: number = row["row"];
const tColumn: number = row["column_t"];
table.matrix[tRow][tColumn] = row["value"];
}
// console.log(table.matrix);
callback(err, table);
});
}
], (err, table?: Table) => {
if (err) {
cb(err);
return;
}
cb(null, table);
});
}
/**
* Asynchronously read validations from database without transactions.
* @param id - Form identifier which validations from inputs are linked to.
......@@ -659,6 +749,11 @@ export class FormQueryBuilder extends QueryBuilder {
(callback: (err: Error, result?: number) => void) => {
this.executeWriteInput(formId, input, callback);
},
(inputId: number, callback: (err: Error, resultInputId?: number) => void) => {
this.writeTableController(inputId, input, (error: Error) => {
callback(error, inputId);
});
},
(inputId: number, callback: (err: Error, resultInputId?: number) => void) => {
this.writeValidationController(inputId, input.validation, (error: Error) => {
callback(error, inputId);
......@@ -772,6 +867,140 @@ export class FormQueryBuilder extends QueryBuilder {
});
}
/**
* Asynchronously write a table on database without transactions.
* @param inputId - Input identifier which validations are linked to.
* @param input - The input containing the table to be added.
* @param cb - Callback function which contains informations about method's execution.
* @param cb.err - Error information when the method fails.
*/
private writeTableController(inputId: number, input: Input, cb: (err: Error) => void) {
let row: number = -1;
let column: number;
if (input.type !== InputType.TABLE){
cb(null);
return;
} else {
waterfall([
(callback: (err: Error, tableId: number) => void) => {
this.executeCreateTable (inputId, callback);
},
(tableId: number, callback: (err: Error) => void) => {
eachSeries(input.table.matrix, (line, outerCallback) => {
column = 0;
row++;
eachSeries(line, (value, innerCallback) => {
this.executeWriteCell(tableId, row, column, value, innerCallback);
column++;
}, (err) => {
outerCallback(err);
});
}, (err) => {
callback(err);
});
}
], (err) => {
if (err) {
cb(err);
return;
}
cb(null);
});
}
}
/**
* Asynchronously insert or update a table cell on database without transactions.
* @param tableId - Id of the table to be changed.
* @param row - The cell's row.
* @param column - The cell's column.
* @param value - The value of the cell.
* @param cb - Callback function which contains informations about method's execution.
* @param cb.err - Error information when the method fails.
* @param cb.result - Form identifier or null if any error occurs.
*/
private executeWriteCell(tableId: number, row: number, column: number, value: string, cb: (err: Error) => void) {
const queryString: string = "INSERT INTO tables (table_id, row, column_t, value) \
VALUES ($1, $2, $3, $4) \
ON CONFLICT ON CONSTRAINT table_id_row_col \
DO UPDATE SET (table_id, row, column_t, value) = ($1, $2, $3, $4);";
const query: QueryOptions = {
query: queryString
, parameters: [
tableId
, row
, column
, value
]
};
this.executeQuery(query, (err: Error, result?: QueryResult) => {
if (err) {
cb(err);
return;
}
cb(null);
});
}
/**
* Asynchronously create a table on database without transactions.
* @param inputId - Input that has a table associated.
* @param cb - Callback function which contains informations about method's execution.
* @param cb.err - Error information when the method fails.
* @param cb.result - Form identifier or null if any error occurs.
*/
private executeCreateTable(inputId: number, cb: (err: Error, result?: number) => void) {
const queryString: string = "INSERT INTO input_table (input_id) \
VALUES ($1) \
RETURNING table_id;";
const query: QueryOptions = {
query: queryString
, parameters: [
inputId
]
};
this.executeQuery(query, (err: Error, result?: QueryResult) => {
if (err) {
cb(err);
return;
}
cb(null, result.rows[0]["table_id"]);
});
}
/**
* Asynchronously delete a table cell on database without transactions.
* @param tableId - Id of the table to be changed.
* @param row - The cell's row.
* @param column - The cell's column.
* @param cb - Callback function which contains informations about method's execution.
* @param cb.err - Error information when the method fails.
* @param cb.result - Form identifier or null if any error occurs.
*/
private executeDeleteCell(tableId: number, row: number, column: number, cb: (err: Error) => void) {
const queryString: string = "UPDATE tables SET enabled=false \
WHERE table_id=$1 AND row=$2 AND column_t=$3;";
const query: QueryOptions = {
query: queryString
, parameters: [
tableId,
row,
column
]
};
this.executeQuery(query, (err: Error, result?: QueryResult) => {
if (err) {
cb(err);
return;
}
cb(null);
});
}
/**
* Asynchronously insert a form on database without transactions.
* @param form - Form to be inserted.
......@@ -1185,6 +1414,28 @@ export class FormQueryBuilder extends QueryBuilder {
});
break;
}
case UpdateType.TABLE: {
let row: number = -1;
let column: number;
eachSeries(inputUpdate.input.table.matrix, (line, outerCallback) => {
column = 0;
row++;
eachSeries(line, (value, innerCallback) => {
if (value !== -1){
this.executeWriteCell(inputUpdate.input.table.id, row, column, value, innerCallback);
} else {
this.executeDeleteCell(inputUpdate.input.table.id, row, column, innerCallback);
}
column++;
}, (err) => {
outerCallback(err);
});
}, (err) => {
callback(err);
});
break;
}