Skip to content

Commit

Permalink
feat(core/reports): add report builder by xls
Browse files Browse the repository at this point in the history
  • Loading branch information
peppedeka committed Nov 3, 2021
1 parent c551ce9 commit 9ef65dd
Show file tree
Hide file tree
Showing 27 changed files with 1,505 additions and 78 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
/src/dev-app/mat-node-icon/** @peppedeka
/src/dev-app/mat-page-slider/** @peppedeka
/src/dev-app/mat-report-from-form/** @peppedeka
/src/dev-app/mat-report-from-xls/** @peppedeka
/src/dev-app/mat-reports/** @peppedeka
/src/dev-app/mat-time/** @peppedeka
/src/dev-app/mat-widgets/** @peppedeka
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
},
"license": "AGPL-3.0-or-later",
"engines": {
"node": ">=14.0.0 <17.0.0",
"node": ">=14.0.0 <=17.0.1",
"yarn": ">= 1.0.0",
"npm": "Please use Yarn instead of NPM to install dependencies. See: https://yarnpkg.com/lang/en/docs/install/"
},
Expand Down
2 changes: 1 addition & 1 deletion packages.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ NO_STAMP_NPM_PACKAGE_SUBSTITUTIONS = dict(NPM_PACKAGE_SUBSTITUTIONS, **{

ANGULAR_PACKAGES_CONFIG = [
("@angular/animations", struct(entry_points = ["browser"])),
("@angular/cdk", struct(entry_points = ["a11y", "accordion", "bidi", "coercion", "collections", "drag-drop", "keycodes", "layout", "observers", "overlay", "platform", "portal", "scrolling", "table", "text-field"])),
("@angular/cdk", struct(entry_points = ["a11y", "accordion", "bidi", "clipboard", "coercion", "collections", "drag-drop", "keycodes", "layout", "observers", "overlay", "platform", "portal", "scrolling", "table", "text-field"])),
("@angular/common", struct(entry_points = ["http/testing", "http", "testing"])),
("@angular/compiler", struct(entry_points = ["testing"])),
("@angular/core", struct(entry_points = ["testing"])),
Expand Down
1 change: 1 addition & 0 deletions pkg-externals.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ load("//src/material:config.bzl", "MATERIAL_ENTRYPOINTS", "MATERIAL_TESTING_ENTR
PKG_EXTERNALS = [
# Framework packages.
"@angular/animations",
"@angular/cdk/clipboard",
"@angular/cdk/coercion",
"@angular/cdk/collections",
"@angular/cdk/drag-drop",
Expand Down
2 changes: 1 addition & 1 deletion src/core/forms/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export * from './valid-slide';
export * from './validation-service';
export * from './video-url-field';
export * from './warning-alert-service';

export * from './random-ajf-context-generator';
export * from './read-only-field';
export * from './read-only-table-field';

Expand Down
88 changes: 88 additions & 0 deletions src/core/forms/random-ajf-context-generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* @license
* Copyright (C) Gnucoop soc. coop.
*
* This file is part of the Advanced JSON forms (ajf).
*
* Advanced JSON forms (ajf) 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.
*
* Advanced JSON forms (ajf) 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 Advanced JSON forms (ajf).
* If not, see http://www.gnu.org/licenses/.
*
*/

import {AjfContext} from '@ajf/core/common';
import {AjfField} from './interface/fields/field';
import {AjfFieldType} from './interface/fields/field-type';
import {AjfFieldWithChoices} from './interface/fields/field-with-choices';
import {AjfRangeField} from './interface/fields/range-field';
import {AjfFormSerializer} from './serializers/form-serializer';
import {AjfFormCreate} from './utils/forms/create-form';
import {flattenNodes} from './utils/nodes/flatten-nodes';
import {isField} from './utils/nodes/is-field';

export function generateRandomCtx(formSchema: AjfFormCreate): AjfContext[] {
const ctxMap: AjfContext[] = [];
const allFields: AjfField[] = flattenNodes(formSchema.nodes!).filter(f =>
isField(f),
) as AjfField[];
const generateRandomNumberOfContext = Math.floor(Math.random() * 100) + 1;
for (let i = 0; i < generateRandomNumberOfContext; i++) {
const ctx: AjfContext = {};
allFields.forEach(field => {
switch (field.fieldType) {
default:
ctx[field.name] = 0;
break;
case AjfFieldType.Number:
ctx[field.name] = Math.floor(Math.random() * 1000) + 1;
break;
case AjfFieldType.Boolean:
ctx[field.name] = Math.random() < 0.5;
break;
case AjfFieldType.SingleChoice:
const singleChoices = (field as AjfFieldWithChoices<any>).choicesOrigin.choices.map(
c => c.value,
);
ctx[field.name] = singleChoices[Math.floor(Math.random() * singleChoices.length)];
break;
case AjfFieldType.MultipleChoice:
const multipleChoices = (field as AjfFieldWithChoices<any>).choicesOrigin.choices.map(
c => c.value,
);
ctx[field.name] = [
multipleChoices[Math.floor(Math.random() * multipleChoices.length)],
multipleChoices[Math.floor(Math.random() * multipleChoices.length)],
];
break;
case AjfFieldType.Range:
const rangeField = field as AjfRangeField;
const end = rangeField.end ?? 10;
const start = rangeField.start ?? 1;
const value = Math.floor(start + Math.random() * (end + 1 - start));
ctx[field.name] = value;
}
});
ctxMap.push(ctx);
}
return ctxMap;
}

export function buildformDatas(formSchemas: {[name: string]: AjfFormCreate}): {
[name: string]: AjfContext[];
} {
const forms: {[name: string]: AjfContext[]} = {};
Object.keys(formSchemas).forEach(nameSchema => {
forms[nameSchema] = generateRandomCtx(AjfFormSerializer.fromJson(formSchemas[nameSchema]));
});
return forms;
}
1 change: 1 addition & 0 deletions src/core/models/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ ng_module(
),
deps = [
"//src/core/common",
"//src/core/table",
"//src/core/utils",
"@npm//@angular/core",
"@npm//date-fns",
Expand Down
119 changes: 108 additions & 11 deletions src/core/models/utils/expression-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {AjfContext} from '@ajf/core/common';
import * as dateFns from 'date-fns';
import {parseScript} from 'meriyah';
import * as numbroMod from 'numbro';
import {AjfTableCell} from '@ajf/core/table';

import {AjfValidationFn} from '../interface/validation-function';

Expand All @@ -41,16 +42,21 @@ export const getCodeIdentifiers = (
includeDollarValue: boolean = false,
): string[] => {
const identifiers = [] as string[];
parseScript(source, {
onToken: (token, start, end) => {
if (token == 'Identifier') {
const identifier = source.substring(start, end);
if (includeDollarValue || identifier !== '$value') {
identifiers.push(identifier);
try {
parseScript(source.toString(), {
onToken: (token, start, end) => {
if (token == 'Identifier') {
const identifier = source.toString().substring(start, end);
if (includeDollarValue || identifier !== '$value') {
identifiers.push(identifier);
}
}
}
},
});
},
});
} catch (e) {
console.log(source);
console.log(e);
}
return identifiers;
};

Expand Down Expand Up @@ -103,6 +109,7 @@ export class AjfExpressionUtils {
parseFloat: {fn: parseFloat},
parseDate: {fn: dateUtils.parse},
Date: {fn: Date},
plainArray: {fn: plainArray},
COUNTFORMS: {fn: COUNTFORMS},
COUNTFORMS_UNIQUE: {fn: COUNTFORMS_UNIQUE},
SUM: {fn: SUM},
Expand All @@ -112,6 +119,9 @@ export class AjfExpressionUtils {
MAX: {fn: MAX},
MEDIAN: {fn: MEDIAN},
MODE: {fn: MODE},
ALL_VALUES_OF: {fn: ALL_VALUES_OF},
REPEAT: {fn: REPEAT},
buildDataset: {fn: buildDataset},
};
}

Expand Down Expand Up @@ -602,6 +612,25 @@ export function getCoordinate(source: any, zoom?: number): [number, number, numb
}
}

/**
* Calculates all the possible results that a field has taken
*/
export function ALL_VALUES_OF(forms: Form[], fieldName: string): string[] {
forms = (forms || []).slice(0);

return [...new Set(forms.map(f => `${f[fieldName]}`))];
}

export function plainArray(params: any[]): any[] {
let res: any[] = [];
params.forEach(param => {
param = Array.isArray(param) ? param : [param];
res = [...res, ...param];
});

return res;
}

/**
* Counts the collected forms. The form name must be specified. An optional condition can be added
* to discriminate which forms to count in.
Expand Down Expand Up @@ -677,12 +706,12 @@ export function SUM(forms: Form[], expression: string, condition?: string): numb
}
const evaluatedExpression = evaluateExpression(expression.replace('__', `__${i}`), f);
if (Number.isFinite(evaluateExpression)) {
acc += evaluatedExpression;
acc += +evaluatedExpression;
}
}
});
} else {
forms.forEach(f => (acc += evaluateExpression(expression, f)));
forms.forEach(f => (acc += +evaluateExpression(expression, f)));
}

return acc;
Expand Down Expand Up @@ -787,3 +816,71 @@ export function MODE(forms: Form[], fieldName: string): number[] {
.filter(v => map[+v] === maxCount)
.map(v => +v);
}

export function buildDataset(
dataset: (string | number | string[] | number[])[],
colspans: number[],
): AjfTableCell[][] {
const res: AjfTableCell[][] = [];
const normalizeDataset: any[][] = [];
dataset.forEach((row: any, indexRow: number) => {
row = Array.isArray(row) ? row : [row];
normalizeDataset[indexRow % colspans.length] =
normalizeDataset[indexRow % colspans.length] != null
? [...normalizeDataset[indexRow % colspans.length], ...row]
: [...row];
});
const transpose = normalizeDataset[0].map((_: any, colIndex: number) =>
normalizeDataset.map((row: any) => row[colIndex]),
);
transpose.forEach((data: any[], index: number) => {
const row: AjfTableCell[] = [];
data.forEach((cellValue: string | number, cellIndex: number) => {
row.push({
value: cellValue,
colspan: colspans[cellIndex],
rowspan: 1,
style: {
textAlign: 'center',
color: 'black',
backgroundColor: index % 2 === 0 ? 'white' : '#ddd',
},
});
});
res.push(row);
});
return res;
}

/**
*
* @param values all values of iteration
* @param forms the form data
* @param fn the fuction of expression-utils to apply at iteration
* @param param1 first param of fn
* @param param2 second param of fn
* @returns the result of fn applied to all values param conditions
* &current is an anchor key, The params with &current will be modified with the iteration values.
*/
export function REPEAT(
values: string[],
forms: Form[],
fn: AjfValidationFn,
param1: string,
param2: string,
): any[] {
const res: any[] = [];
const newExp1 =
param1 != null && param1.includes('&current')
? (v: any) => param1.replace('&current', `"${v}"`)
: () => param1;
const newExp2 =
param2 != null && param2.includes('&current')
? (v: any) => param2.replace('&current', `"${v}"`)
: () => param2;
values.forEach(v => {
const vv = (fn as any)(forms, newExp1(v), newExp2(v));
res.push(vv);
});
return res;
}
1 change: 1 addition & 0 deletions src/core/reports/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,5 @@ export * from './utils/widgets-instances/create-widget-instance';
export * from './utils/widgets-instances/widget-to-widget-instance';

export * from './report-from-form/report-from-form';
export * from './report-from-xls/report-from-xls';
export * from './report-to-pdf/report-to-pdf';

0 comments on commit 9ef65dd

Please sign in to comment.