Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add template typedef review usage test #1502

Merged
merged 22 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
143 changes: 93 additions & 50 deletions mod/workspace/_workspace.js
Original file line number Diff line number Diff line change
Expand Up @@ -307,84 +307,127 @@ async function test(req, res) {
return
}

const testResults = {};
const errArr = []
let custom_templates = {};
let templateUsage = {};
const test = {
results: {},
errArr: [],
used_templates: [],
properties: new Set(['template', 'templates', 'query'])
}

test.workspace_templates = new Set(Object.entries(workspace.templates)
.filter(([key, value]) => value._type === 'workspace')
.map(([key, value]) => key))

// Create clone of workspace_templates
test.unused_templates = new Set([...test.workspace_templates])

test.overwritten_templates = new Set()

for (const localeKey of Object.keys(workspace.locales)) {

// Will get layer and assignTemplates to workspace.
const locale = await getLocale({ locale: localeKey, user: req.params.user })

custom_templates = {
...Object.fromEntries(
Object.entries(workspace.templates).filter(([key, value]) => value._type === 'workspace_template')
)
};

for (const layerKey of Object.keys(locale.layers)) {

// Will get layer and assignTemplates to workspace.
const layer = await getLayer({ locale: localeKey, layer: layerKey, user: req.params.user })

if (layer.template) {
checkTemplate(layer.template, custom_templates, templateUsage);
}

layer.templates?.forEach(template => {
checkTemplate(template, custom_templates, templateUsage);
});
locale.layers[layerKey] = layer;

if (layer.err) errArr.push(`${layerKey}: ${layer.err}`)
if (layer.err) test.errArr.push(`${layerKey}: ${layer.err}`)
}

// Test locale and all of its layers as nested object.
templateUse(locale, test);
}

//Finding the unused templates from the custom_template where we don't see any count in the templateUsage object.
const unused_templates = Object.keys(custom_templates).filter(template => !Object.keys(templateUsage).includes(template))

//Adding the results to the testResults object.
testResults.usage = templateUsage;
testResults.unused_templates = unused_templates;

// From here on its 🐢 Templates all the way down.
for (const key of Object.keys(workspace.templates)) {

const template = await getTemplate(key)

if (template.err) errArr.push(`${key}: ${template.err.path}`)
if (template.err) test.errArr.push(`${key}: ${template.err.path}`)
}

testResults.errors = errArr.flat();
test.results.errors = test.errArr.flat();

test.results.unused_templates = Array.from(test.unused_templates);

test.results.overwritten_templates = Array.from(test.overwritten_templates);

// Reduce the test.used_templates array to count the occurance of each template.
test.results.usage = Object.fromEntries(test.used_templates
// sort by usage
.sort((a,b) => a>b ? 1: a<b ? -1 : 0)
.reduce((acc, e) => acc.set(e, (acc.get(e) || 0) + 1), new Map()));

res.setHeader('content-type', 'application/json');

res.send(JSON.stringify(testResults));
res.send(JSON.stringify(test.results));
}

//Helper function to update the template usage
function updateTemplateUsage(templateKey, templateUsage) {
if (!templateKey) return;
/**
@function templateUse

//if we find a templatekey in the usage object then incremenet the count by 1.
// else we will add a new entry to the object with a count of 1.
if (templateUsage[templateKey]) {
templateUsage[templateKey].count++;
} else {
templateUsage[templateKey] = { count: 1 };
}
}
@description
Iterates through all nested object properties.
Test properties found in the test.properties Set.
Removes template keys from test.unused_templates Set.
Add template keys to test.used_templates Array.

@param {Object} obj The object to test.
@param {Object} test The test config object.
@property {Set} test.properties Set of properties to test ['template', 'templates', 'query']
@property {Set} test.workspace_templates Set of templates _type=workspace templates.
@property {Set} test.unused_templates Set of templates not (yet) used.
@property {Set} test.overwritten_templates Set of _type=workspace templates which have been overwritten.
@property {Array} test.used_templates Array of template keys for each usage.
*/
function templateUse(obj, test) {

//Helper function used to check against a template coming from either a layer.template or a layer.templates entries.
function checkTemplate(template, custom_templates, templateUsage) {
//We set a template_key based on if the template is a string or an object.
const template_key = typeof template === 'string' ? template : template.key;
//Get the template from the workspace.templates
const workspace_template = custom_templates[template_key];
if (typeof obj !== 'object') return;

//If we get the template and have a key, then we will increase the usage counter.
if (template_key && workspace_template) {
updateTemplateUsage(template_key, templateUsage);
}
}
Object.entries(obj).forEach(entry => {

// entry key === ['template', 'templates', 'query']
if (test.properties.has(entry[0])) {

if (Array.isArray(entry[1])) {

entry[1]
.filter(item => typeof item === 'string')
.forEach(item => {
test.unused_templates.delete(item)
test.used_templates.push(item)
})
}

if (typeof entry[1] === 'object'
&& Object.hasOwn(entry[1], 'key')

) {
if (test.workspace_templates.has(entry[1].key)) {
test.overwritten_templates.add(entry[1].key)
}
return;
}

if (typeof entry[1] === 'string') {
test.unused_templates.delete(entry[1])
test.used_templates.push(entry[1])
}
}

// Iterate through each array, eg. infoj
if (Array.isArray(entry[1])) {

entry[1].forEach(entry => templateUse(entry, test))

// Iterate through nested objects eg. layers
} else if (entry[1] instanceof Object) {

templateUse(entry[1], test)
}
})
}
13 changes: 4 additions & 9 deletions mod/workspace/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,6 @@ const query_templates = require('./templates/_queries')

const workspace_src = process.env.WORKSPACE?.split(':')[0]

/**
@global
@typedef {string} _type
The _type property on a template used to distinguish templates that get added to the workspace.templates object for further testing.
Expected values for this are 'core', 'custom_template', 'workspace_template'
*/

/**
@function cacheWorkspace

Expand Down Expand Up @@ -114,6 +107,8 @@ async function cacheWorkspace() {
*/
function mark_template(templates_object, type) {

if (!templates_object) return;

return Object.fromEntries(
Object.entries(templates_object)
.map(([key, template]) => [key, { ...template, _type: type }])
Expand All @@ -128,10 +123,10 @@ async function cacheWorkspace() {
...mark_template(msg_templates, 'core'),
...mark_template(query_templates, 'core'),

...mark_template(custom_templates, 'custom_templates'),
...mark_template(custom_templates, 'custom'),

// Default templates can be overridden by assigning a template with the same key.
...mark_template(workspace.templates, 'workspace_template')
...mark_template(workspace.templates, 'workspace')
}

// A workspace must have a default locale [template]
Expand Down
11 changes: 11 additions & 0 deletions mod/workspace/getTemplate.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ const workspaceCache = require('./cache')

const envReplace = require('../utils/envReplace')

/**
@global
@typedef {Object} template A template is an object property of the workspace.templates
@property {Object} _type The _type property distinguish the origin of a template. 'core' templates are added from the /mod/workspace/templates directory. A 'custom' is added from a custom_template JSON file defined in the process.env. A 'workspace' is added from the workspace itself. A _type='template' object is assigned in the [assignWorkspaceTemplates]{@link module:/workspace/mergeTemplates~assignWorkspaceTemplates} method.
@property {String} src The source is a location from which a template object is loaded when required. Once loaded the template will be cached.
@property {Object} cached The cached template.
@property {String} template The string representation of a template, eg. html, sql.
@property {Function} render A method which resolves in a template string.
@property {Boolean} module The template is a module.
*/

/**
@function getTemplate
@async
Expand Down
6 changes: 5 additions & 1 deletion mod/workspace/mergeTemplates.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,10 @@ module.exports = async function mergeTemplates(obj) {

//The object key must not be overwritten by a template key.
delete template.key;

//The object template must not be overwritten by a templates template.
delete template.template;

// Merge template --> obj
obj = merge(obj, template)
}
Expand All @@ -119,6 +121,8 @@ module.exports = async function mergeTemplates(obj) {
@description
The method parses an object for a template object property. The template property value will be assigned to the workspace.templates{} object matching the template key value.

The template._type property will be set to 'template' indicating that the templates origin is in the workspace. It is possible to overassign _type:'core' templates which are loaded from the /mod/workspace/templates directory.

The method will call itself for nested objects.

@param {Object} obj
Expand All @@ -133,7 +137,7 @@ function assignWorkspaceTemplates(obj) {

if (entry[0] === 'template' && entry[1].key) {

entry[1]._type = 'workspace_template';
entry[1]._type = 'template';
workspace.templates[entry[1].key] = Object.assign(workspace.templates[entry[1].key] || {}, entry[1])

return;
Expand Down
3 changes: 3 additions & 0 deletions tests/workspace.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
},
"minmax_query_mock": {
"src": "file:/tests/assets/queries/minmax_mock.sql"
},
"data_array": {
"src": "file:/tests/assets/queries/data_array.sql"
}
},
"locale": {
Expand Down
Loading