Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 7 additions & 1 deletion bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -570,8 +570,14 @@ program
.command("stats")
.description("Generate an overview with some statistics")
.requiredOption("-s, --since <date>, Specify the date which is going to be used to filter the data from (format: YYYY-MM-DD) (mandatory)")
.option("-w, --workflow [handle], Optionally filter test statistics by workflow (optional)")
.action((options) => {
stats.generateOverview(options.since);
if (options.workflow) {
const workflowHandle = typeof options.workflow === "string" ? options.workflow : undefined;
stats.generateWorkflowOverview(options.since, workflowHandle);
} else {
stats.generateOverview(options.since);
}
});

// Set/Get FIRM ID
Expand Down
264 changes: 255 additions & 9 deletions lib/cli/stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,33 @@ async function generateOverview(sinceDate) {
const TODAY = new Date().toJSON().toString().slice(0, 10);
const templateSummary = await getTemplatesSummary();
const yamlSummary = await getYamlSummary(sinceDate);
// Terminal
displayOverview(sinceDate, TODAY, templateSummary, yamlSummary);
// File
const row = createRow(sinceDate, TODAY, templateSummary, yamlSummary);
saveOverviewToFile(row);
}

async function generateWorkflowOverview(sinceDate, workflowHandle) {
const TODAY = new Date().toJSON().toString().slice(0, 10);
let workflowHandles;
if (workflowHandle) {
workflowHandles = [workflowHandle];
} else {
const workflowsFolder = path.join(process.cwd(), "workflows");
workflowHandles = fs.readdirSync(workflowsFolder).map((file) => path.basename(file, ".json"));
}
for (const handle of workflowHandles) {
const workflow = await fsUtils.getWorkflow(handle);
const templateSummary = await getWorkflowTemplateSummary(workflow);
const yamlSummary = await getYamlSummary(sinceDate, workflow);
displayWorkflowOverview(sinceDate, TODAY, templateSummary, yamlSummary);
const row = createWorkflowRow(sinceDate, TODAY, templateSummary, yamlSummary);
saveWorkflowOverviewToFile(row, handle);
}
}

// Return an object with the count of activities by file and by type
// Type could be: A (added), M (modified), D (deleted)
async function yamlFilesActivity(sinceDate) {
async function yamlFilesActivity(sinceDate, workflow) {
const countByType = {};
const filesChanged = exec.execSync(`git whatchanged --since="${sinceDate}" --name-status --pretty="format:"`);
if (!filesChanged) {
Expand All @@ -35,7 +52,9 @@ async function yamlFilesActivity(sinceDate) {
}

// Files to Search (YAML)
const YAML_EXPRESSION = `.*/.*/tests/.*_liquid_test.*.y(a)?ml`;
const templatesInWorkflow = workflow ? workflow.templates.reconciliations.concat(workflow.templates.accounts) : [];
const TEMPLATE_PATTERN = workflow ? `(${templatesInWorkflow.join("|")})` : `.*`;
const YAML_EXPRESSION = `.*/${TEMPLATE_PATTERN}/tests/.*_liquid_test.*.y(a)?ml`;
const fileTypeRegExp = RegExp(YAML_EXPRESSION, "g");

for (const row of nonEmptyRows) {
Expand Down Expand Up @@ -74,10 +93,12 @@ async function yamlFilesActivity(sinceDate) {
// Count how many YAML files are stored. We base on the presence of a non empty file
// Count how many unit tests are stored. We base on the presence of a title for each unit test
// Count how many YAML files have at least two unit tests
async function countYamlFiles(templateType) {
async function countYamlFiles(templateType, templatesInWorkflow) {
const files = fsUtils.listExistingFiles("yml");
const FOLDER = fsUtils.FOLDERS[templateType];
const YAML_EXPRESSION = `.*${FOLDER}/.*/tests/.*_liquid_test.*.y(a)?ml`;
const TEMPLATE_PATTERN = templatesInWorkflow ? `(${templatesInWorkflow.join("|")})` : `.*`;
// Issue with counting multiple yml files in the same tests folder?
const YAML_EXPRESSION = `.*${FOLDER}/${TEMPLATE_PATTERN}/tests/.*_liquid_test.*.y(a)?ml`;
const re = new RegExp(YAML_EXPRESSION, "g");
let countFiles = 0;
let countFilesWithAtLeastTwoTests = 0;
Expand Down Expand Up @@ -255,8 +276,99 @@ async function getTemplatesSummary() {
return summary;
}

async function getYamlSummary(sinceDate) {
const yamlActivity = await yamlFilesActivity(sinceDate);
async function getWorkflowTemplateSummary(workflow) {
const summary = {
workflow_name: "",
reconciliations: {
total: 0,
externallyManaged: 0,
externallyManagedPerc: 0,
yamlFiles: 0,
yamlFilesPerc: 0,
unitTests: 0,
yamlFilesWithAtLeastTwoTests: 0,
yamlFilesWithAtLeastTwoTestsPerc: 0,
},
exportFiles: {
total: 0,
externallyManaged: 0,
externallyManagedPerc: 0,
},
accountTemplates: {
total: 0,
externallyManaged: 0,
externallyManagedPerc: 0,
yamlFiles: 0,
yamlFilesPerc: 0,
unitTests: 0,
yamlFilesWithAtLeastTwoTests: 0,
yamlFilesWithAtLeastTwoTestsPerc: 0,
},
all: {
total: 0,
externallyManaged: 0,
externallyManagedPerc: 0,
yamlFiles: 0,
yamlFilesPerc: 0,
unitTests: 0,
yamlFilesWithAtLeastTwoTests: 0,
yamlFilesWithAtLeastTwoTestsPerc: 0,
},
};

// Fetch workflow
// Assume no empty items if present within the workflow.
summary.workflow_name = workflow.name;

// Reconciliations
const reconciliationsInWorkflow = workflow.templates.reconciliations;
const reconciliationsExtMan = await listExternallyManagedTemplates("reconciliationText", reconciliationsInWorkflow);
const reconciliationsTests = await countYamlFiles("reconciliationText", reconciliationsInWorkflow);
summary.reconciliations.total = reconciliationsInWorkflow.length;
summary.reconciliations.externallyManaged = reconciliationsExtMan.length;
summary.reconciliations.externallyManagedPerc = percentageRoundTwo(summary.reconciliations.externallyManaged, summary.reconciliations.total);
summary.reconciliations.yamlFiles = reconciliationsTests.files;
summary.reconciliations.yamlFilesPerc = percentageRoundTwo(summary.reconciliations.yamlFiles, summary.reconciliations.total);
summary.reconciliations.unitTests = reconciliationsTests.tests;
summary.reconciliations.yamlFilesWithAtLeastTwoTests = reconciliationsTests.filesWithAtLeastTwoTests;
summary.reconciliations.yamlFilesWithAtLeastTwoTestsPerc = percentageRoundTwo(summary.reconciliations.yamlFilesWithAtLeastTwoTests, summary.reconciliations.total);

// Export Files
const exportFilesInWorkflow = workflow.templates.exports;
const exportFilesExtMan = await listExternallyManagedTemplates("exportFile", exportFilesInWorkflow);
summary.exportFiles.total = exportFilesInWorkflow.length;
summary.exportFiles.externallyManaged = exportFilesExtMan.length;
summary.exportFiles.externallyManagedPerc = percentageRoundTwo(summary.exportFiles.externallyManaged, summary.exportFiles.total);

// Account Templates
const accountTemplatesInWorkflow = workflow.templates.accounts;
const accountTemplatesExtMan = await listExternallyManagedTemplates("accountTemplate", accountTemplatesInWorkflow);
const accountTemplatesTests = await countYamlFiles("accountTemplate", accountTemplatesInWorkflow);
summary.accountTemplates.total = accountTemplatesInWorkflow.length;
summary.accountTemplates.externallyManaged = accountTemplatesExtMan.length;
summary.accountTemplates.externallyManagedPerc = percentageRoundTwo(summary.accountTemplates.externallyManaged, summary.accountTemplates.total);
summary.accountTemplates.yamlFiles = accountTemplatesTests.files;
summary.accountTemplates.yamlFilesPerc = percentageRoundTwo(summary.accountTemplates.yamlFiles, summary.accountTemplates.total);
summary.accountTemplates.unitTests = accountTemplatesTests.tests;
summary.accountTemplates.yamlFilesWithAtLeastTwoTests = accountTemplatesTests.filesWithAtLeastTwoTests;
summary.accountTemplates.yamlFilesWithAtLeastTwoTestsPerc = percentageRoundTwo(summary.accountTemplates.yamlFilesWithAtLeastTwoTests, summary.accountTemplates.total);

// All
summary.all.total = summary.reconciliations.total + summary.exportFiles.total + summary.accountTemplates.total;
summary.all.externallyManaged =
summary.reconciliations.externallyManaged + summary.exportFiles.externallyManaged + summary.accountTemplates.externallyManaged;
summary.all.externallyManagedPerc = percentageRoundTwo(summary.all.externallyManaged, summary.all.total);
summary.all.yamlFiles = summary.reconciliations.yamlFiles + summary.accountTemplates.yamlFiles;
summary.all.yamlFilesPerc = percentageRoundTwo(summary.all.yamlFiles, summary.reconciliations.total + summary.accountTemplates.total);
summary.all.unitTests = summary.reconciliations.unitTests + summary.accountTemplates.unitTests;
summary.all.yamlFilesWithAtLeastTwoTests = summary.reconciliations.yamlFilesWithAtLeastTwoTests + summary.accountTemplates.yamlFilesWithAtLeastTwoTests;
summary.all.yamlFilesWithAtLeastTwoTestsPerc = percentageRoundTwo(summary.all.yamlFilesWithAtLeastTwoTests, summary.reconciliations.total + summary.accountTemplates.total);

return summary;
}

async function getYamlSummary(sinceDate, templatesInWorkflow) {
const yamlActivity = await yamlFilesActivity(sinceDate, templatesInWorkflow);
const summary = { created: 0, updated: 0 };
summary.created = (yamlActivity["A"] || 0) - (yamlActivity["D"] || 0);
summary.updated = yamlActivity["M"] || 0;
Expand Down Expand Up @@ -309,6 +421,54 @@ function displayOverview(sinceDate, today, templateSummary, yamlSummary) {
consola.log("------------------------------------");
}

function displayWorkflowOverview(sinceDate, today, templateSummary, yamlSummary) {
// Header
consola.log("");
consola.info(`${chalk.bold(`Workflow Summary - ${templateSummary.workflow_name} ( ${sinceDate} - ${today} ):`)}`);
consola.log("------------------------------------");
consola.log("");
// YAML file changes
consola.log(`New YAML files created in the period: ${yamlSummary.created}`);
consola.log(`Updates to existing YAML files in the period: ${yamlSummary.updated}`);
consola.log("");
consola.log("------------------------------------");
consola.log("");
// Reconciliations
consola.log(`${chalk.bold("Reconciliations:")}`);
consola.log(`Templates: ${templateSummary.reconciliations.total}`);
consola.log(`Externally Managed: ${templateSummary.reconciliations.externallyManaged} (${templateSummary.reconciliations.externallyManagedPerc}%)`);
consola.log(`YAML files: ${templateSummary.reconciliations.yamlFiles} (${templateSummary.reconciliations.yamlFilesPerc}%)`);
consola.log(`Unit Tests: ${templateSummary.reconciliations.unitTests}`);
consola.log(
`YAML files with at least two unit tests: ${templateSummary.reconciliations.yamlFilesWithAtLeastTwoTests} (${templateSummary.reconciliations.yamlFilesWithAtLeastTwoTestsPerc}%)`
);
consola.log("");
// Account Templates
consola.log(`${chalk.bold("Account Templates:")}`);
consola.log(`Templates: ${templateSummary.accountTemplates.total}`);
consola.log(`Externally Managed: ${templateSummary.accountTemplates.externallyManaged} (${templateSummary.accountTemplates.externallyManagedPerc}%)`);
consola.log(`YAML files: ${templateSummary.accountTemplates.yamlFiles} (${templateSummary.accountTemplates.yamlFilesPerc}%)`);
consola.log(`Unit Tests: ${templateSummary.accountTemplates.unitTests}`);
consola.log(
`YAML files with at least two unit tests: ${templateSummary.accountTemplates.yamlFilesWithAtLeastTwoTests} (${templateSummary.accountTemplates.yamlFilesWithAtLeastTwoTestsPerc}%)`
);
consola.log("");
// Export Files
consola.log(`${chalk.bold("Export Files:")}`);
consola.log(`Templates: ${templateSummary.exportFiles.total}`);
consola.log(`Externally Managed: ${templateSummary.exportFiles.externallyManaged} (${templateSummary.exportFiles.externallyManagedPerc}%)`);
consola.log("");
// All
consola.log(`${chalk.bold("All:")}`);
consola.log(`Templates: ${templateSummary.all.total}`);
consola.log(`Externally Managed: ${templateSummary.all.externallyManaged} (${templateSummary.all.externallyManagedPerc}%)`);
consola.log(`YAML files: ${templateSummary.all.yamlFiles} (${templateSummary.all.yamlFilesPerc}%)`);
consola.log(`Unit Tests: ${templateSummary.all.unitTests}`);
consola.log(`YAML files with at least two unit tests: ${templateSummary.all.yamlFilesWithAtLeastTwoTests} (${templateSummary.all.yamlFilesWithAtLeastTwoTestsPerc}%)`);
consola.log("");
consola.log("------------------------------------");
}

function createRow(sinceDate, today, templateSummary, yamlSummary) {
// Row to append to file
const rowContent = [
Expand Down Expand Up @@ -349,6 +509,43 @@ function createRow(sinceDate, today, templateSummary, yamlSummary) {
return row;
}

function createWorkflowRow(sinceDate, today, templateSummary, yamlSummary) {
const rowContent = [
templateSummary.workflow_name,
sinceDate,
today,
yamlSummary.created,
yamlSummary.updated,
templateSummary.all.total,
templateSummary.all.externallyManaged,
templateSummary.all.yamlFiles,
templateSummary.all.unitTests,
templateSummary.reconciliations.total,
templateSummary.reconciliations.externallyManaged,
templateSummary.reconciliations.yamlFiles,
templateSummary.reconciliations.unitTests,
templateSummary.accountTemplates.total,
templateSummary.accountTemplates.externallyManaged,
templateSummary.accountTemplates.yamlFiles,
templateSummary.accountTemplates.unitTests,
templateSummary.exportFiles.total,
templateSummary.exportFiles.externallyManaged,
templateSummary.all.externallyManagedPerc,
templateSummary.reconciliations.externallyManagedPerc,
templateSummary.accountTemplates.externallyManagedPerc,
templateSummary.exportFiles.externallyManagedPerc,
templateSummary.all.yamlFilesPerc,
templateSummary.reconciliations.yamlFilesPerc,
templateSummary.accountTemplates.yamlFilesPerc,
templateSummary.reconciliations.yamlFilesWithAtLeastTwoTests,
templateSummary.reconciliations.yamlFilesWithAtLeastTwoTestsPerc,
templateSummary.accountTemplates.yamlFilesWithAtLeastTwoTests,
templateSummary.accountTemplates.yamlFilesWithAtLeastTwoTestsPerc,
];
const row = `\r\n${rowContent.join(";")}`;
return row;
}

// content row must be a string with each column separated by ";"
function saveOverviewToFile(row) {
const COLUMNS = [
Expand Down Expand Up @@ -400,4 +597,53 @@ function saveOverviewToFile(row) {
fs.appendFileSync(CSV_PATH, row);
}

module.exports = { generateOverview };
// content row must be a string with each column separated by ";"
function saveWorkflowOverviewToFile(row, workflowHandle) {
const COLUMNS = [
"Workflow Name",
"Period - Start",
"Period - End",
"yaml files created in period",
"yaml files modified in period",
"All - templates",
"All - externally managed",
"All - yaml files",
"All - unit tests",
"Reconciliations - templates",
"Reconciliations - externally managed",
"Reconciliations - yaml files",
"Reconciliations - unit tests",
"Account Templates - templates",
"Account Templates - externally managed",
"Account Templates - yaml files",
"Account Templates - unit tests",
"Export Files - templates",
"Export Files - externally managed",
"All - externally managed (%)",
"Reconciliations - externally managed (%)",
"Account Templates - externally managed (%)",
"Export Files - externally managed (%)",
"All - yaml files (%)",
"Reconciliations - yaml files (%)",
"Account Templates - yaml files (%)",
"Reconciliations - yaml files with at least two tests",
"Reconciliations - yaml files with at least two tests (%)",
"Account Templates - yaml files with at least two tests",
"Account Templates - yaml files with at least two tests (%)",
];
const ROW_HEADER = `${COLUMNS.join(";")}`;
const CSV_PATH = `./stats/${workflowHandle}_stats.csv`;
// Create file and header columns
if (!fs.existsSync("./stats")) {
fs.mkdirSync("stats");
}
if (!fs.existsSync(CSV_PATH)) {
fs.writeFileSync(CSV_PATH, ROW_HEADER, (err) => {
consola.error(err);
});
}
// Append content
fs.appendFileSync(CSV_PATH, row);
}

module.exports = { generateOverview, generateWorkflowOverview };
15 changes: 15 additions & 0 deletions lib/utils/fsUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,20 @@ function checkLiquidTestDependencies(targetHandle) {
return dependentHandles;
}

function getWorkflow(workflowHandle) {
try {
const workflowPath = path.join(process.cwd(), "workflows", `${workflowHandle}.json`);
if (!fs.existsSync(workflowPath)) {
throw new Error(`Workflow "${workflowHandle}" not found`);
}
return JSON.parse(fs.readFileSync(workflowPath).toString());
} catch (error) {
consola.error(`An error occurred when trying to read the workflow "${workflowHandle}"`);
consola.error(error);
process.exit(1);
}
}

// Recursive option for fs.watch is not available in every OS (e.g. Linux)
function recursiveInspectDirectory({ basePath, collection, pathsArray = [], typeCheck = "liquid" }) {
collection.forEach((filePath) => {
Expand Down Expand Up @@ -567,4 +581,5 @@ module.exports = {
getTemplateId,
setTemplateId,
checkLiquidTestDependencies,
getWorkflow,
};
Loading