fix(core): refactor evaluate to support dash in key path (#3517)
* fix(core): refactor evaluate to support dash in key path * fix(core): fix evaluate expression from date variable * fix(plugin-workflow): fix test case * fix(client): fix pre-replace logic
This commit is contained in:
parent
0d8604ee2c
commit
c02e759830
@ -162,7 +162,7 @@ async function replaceVariables(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
exp: value.replace(REGEX_OF_VARIABLE, (match) => {
|
exp: value.replace(REGEX_OF_VARIABLE, (match) => {
|
||||||
return store[match] || match;
|
return `{{${store[match] || match}}}`;
|
||||||
}),
|
}),
|
||||||
scope,
|
scope,
|
||||||
};
|
};
|
||||||
|
@ -18,6 +18,15 @@ describe('evaluate', () => {
|
|||||||
expect(result).toBe("I'm done.");
|
expect(result).toBe("I'm done.");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('function result null', () => {
|
||||||
|
const result = formulaEval('{{a}}', {
|
||||||
|
a() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(result).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
it('function result number', () => {
|
it('function result number', () => {
|
||||||
const result = formulaEval('{{a}}', {
|
const result = formulaEval('{{a}}', {
|
||||||
a() {
|
a() {
|
||||||
@ -28,16 +37,29 @@ describe('evaluate', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('function result Date', () => {
|
it('function result Date', () => {
|
||||||
|
const now = new Date();
|
||||||
const result = formulaEval('{{a}}', {
|
const result = formulaEval('{{a}}', {
|
||||||
a() {
|
a() {
|
||||||
return new Date();
|
return now;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(result).toMatch(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z/);
|
expect(result).toBeInstanceOf(Date);
|
||||||
|
expect(result).toBe(now);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('number path to array item 0 (math.js)', () => {
|
it('deep array', () => {
|
||||||
expect(() => mathEval('{{a.0}}', { a: [1, 2, 3] })).toThrow();
|
const result = formulaEval('{{a.b}}', { a: [{ b: 1 }, { b: 2 }] });
|
||||||
|
expect(result).toEqual([1, 2]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('key with dash', () => {
|
||||||
|
const result = formulaEval('{{a-b}}', { 'a-b': 1 });
|
||||||
|
expect(result).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deep key with dash', () => {
|
||||||
|
const result = formulaEval('{{a.b-c}}', { a: { 'b-c': 1 } });
|
||||||
|
expect(result).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('number path to array item 1 (math.js)', () => {
|
it('number path to array item 1 (math.js)', () => {
|
||||||
@ -45,7 +67,12 @@ describe('evaluate', () => {
|
|||||||
expect(result).toBe(1);
|
expect(result).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('number path to array item 1 (math.js)', () => {
|
// NOTE: This case is skipped because `a.<number>` is not able to be configured from UI.
|
||||||
|
it.skip('number path to array item 0 (math.js)', () => {
|
||||||
|
expect(() => mathEval('{{a.0}}', { a: [1, 2, 3] })).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('number path to array item 1 (math.js)', () => {
|
||||||
const result = mathEval('{{a.1}}', { a: [1, 2, 3] });
|
const result = mathEval('{{a.1}}', { a: [1, 2, 3] });
|
||||||
expect(result).toBe(1);
|
expect(result).toBe(1);
|
||||||
});
|
});
|
||||||
|
@ -17,49 +17,25 @@ export function appendArrayColumn(scope, key) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceNumberIndex(path: string, scope: Scope): string {
|
|
||||||
const segments = path.split('.');
|
|
||||||
const paths: string[] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < segments.length; i++) {
|
|
||||||
const p = segments[i];
|
|
||||||
if (p[0] && '0123456789'.indexOf(p[0]) > -1) {
|
|
||||||
paths.push(Array.isArray(get(scope, segments.slice(0, i))) ? `[${p}]` : `["${p}"]`);
|
|
||||||
} else {
|
|
||||||
if (i) {
|
|
||||||
paths.push('.', p);
|
|
||||||
} else {
|
|
||||||
paths.push(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return paths.join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function evaluate(this: Evaluator, expression: string, scope: Scope = {}) {
|
export function evaluate(this: Evaluator, expression: string, scope: Scope = {}) {
|
||||||
const context = cloneDeep(scope);
|
const context = cloneDeep(scope);
|
||||||
|
const newContext = {};
|
||||||
const exp = expression.trim().replace(/{{\s*([^{}]+)\s*}}/g, (_, v) => {
|
const exp = expression.trim().replace(/{{\s*([^{}]+)\s*}}/g, (_, v) => {
|
||||||
appendArrayColumn(context, v);
|
appendArrayColumn(context, v);
|
||||||
|
|
||||||
const item = get(context, v);
|
let item = get(context, v);
|
||||||
|
|
||||||
let result;
|
if (typeof item === 'function') {
|
||||||
|
item = item();
|
||||||
|
}
|
||||||
|
|
||||||
|
const randomKey = `$$${Math.random().toString(36).slice(2, 10).padEnd(8, '0')}`;
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
result = 'null';
|
return 'null';
|
||||||
} else if (typeof item === 'function') {
|
|
||||||
result = item();
|
|
||||||
result = typeof result === 'string' ? `'${result.replace(/'/g, "\\'")}'` : result;
|
|
||||||
} else {
|
|
||||||
result = replaceNumberIndex(v, context);
|
|
||||||
}
|
}
|
||||||
|
newContext[randomKey] = item;
|
||||||
if (result instanceof Date) {
|
return ` ${randomKey} `;
|
||||||
result = `'${result.toISOString()}'`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ` ${result} `;
|
|
||||||
});
|
});
|
||||||
return this(exp, context);
|
|
||||||
|
return this(exp, newContext);
|
||||||
}
|
}
|
||||||
|
@ -69,29 +69,31 @@ export class FormulaField extends Field {
|
|||||||
};
|
};
|
||||||
|
|
||||||
updateFieldData = async (instance, { transaction }) => {
|
updateFieldData = async (instance, { transaction }) => {
|
||||||
if (this.collection.name === instance.collectionName && instance.name === this.options.name) {
|
if (this.collection.name !== instance.collectionName || instance.name !== this.options.name) {
|
||||||
this.options = Object.assign(this.options, instance.options);
|
return;
|
||||||
const { name } = this.options;
|
}
|
||||||
|
|
||||||
const records = await this.collection.repository.find({
|
this.options = Object.assign(this.options, instance.options);
|
||||||
order: [this.collection.model.primaryKeyAttribute],
|
const { name } = this.options;
|
||||||
transaction,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const record of records) {
|
const records = await this.collection.repository.find({
|
||||||
const scope = record.toJSON();
|
order: [this.collection.model.primaryKeyAttribute],
|
||||||
const result = this.calculate(scope);
|
transaction,
|
||||||
await record.update(
|
});
|
||||||
{
|
|
||||||
[name]: result,
|
for (const record of records) {
|
||||||
},
|
const scope = record.toJSON();
|
||||||
{
|
const result = this.calculate(scope);
|
||||||
transaction,
|
await record.update(
|
||||||
silent: true,
|
{
|
||||||
hooks: false,
|
[name]: result,
|
||||||
},
|
},
|
||||||
);
|
{
|
||||||
}
|
transaction,
|
||||||
|
silent: true,
|
||||||
|
hooks: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -166,7 +166,8 @@ describe('workflow > instructions > calculation', () => {
|
|||||||
|
|
||||||
const [execution] = await workflow.getExecutions();
|
const [execution] = await workflow.getExecutions();
|
||||||
const [job] = await execution.getJobs();
|
const [job] = await execution.getJobs();
|
||||||
expect(job.result).toBe('a $context.data.title ');
|
expect((job.result as string).startsWith('a $$')).toBe(true);
|
||||||
|
expect((job.result as string).endsWith(' ')).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('text', async () => {
|
it('text', async () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user