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 {
|
||||
exp: value.replace(REGEX_OF_VARIABLE, (match) => {
|
||||
return store[match] || match;
|
||||
return `{{${store[match] || match}}}`;
|
||||
}),
|
||||
scope,
|
||||
};
|
||||
|
@ -18,6 +18,15 @@ describe('evaluate', () => {
|
||||
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', () => {
|
||||
const result = formulaEval('{{a}}', {
|
||||
a() {
|
||||
@ -28,16 +37,29 @@ describe('evaluate', () => {
|
||||
});
|
||||
|
||||
it('function result Date', () => {
|
||||
const now = new Date();
|
||||
const result = formulaEval('{{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)', () => {
|
||||
expect(() => mathEval('{{a.0}}', { a: [1, 2, 3] })).toThrow();
|
||||
it('deep array', () => {
|
||||
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)', () => {
|
||||
@ -45,7 +67,12 @@ describe('evaluate', () => {
|
||||
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] });
|
||||
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 = {}) {
|
||||
const context = cloneDeep(scope);
|
||||
const newContext = {};
|
||||
const exp = expression.trim().replace(/{{\s*([^{}]+)\s*}}/g, (_, 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) {
|
||||
result = 'null';
|
||||
} else if (typeof item === 'function') {
|
||||
result = item();
|
||||
result = typeof result === 'string' ? `'${result.replace(/'/g, "\\'")}'` : result;
|
||||
} else {
|
||||
result = replaceNumberIndex(v, context);
|
||||
return 'null';
|
||||
}
|
||||
|
||||
if (result instanceof Date) {
|
||||
result = `'${result.toISOString()}'`;
|
||||
}
|
||||
|
||||
return ` ${result} `;
|
||||
newContext[randomKey] = item;
|
||||
return ` ${randomKey} `;
|
||||
});
|
||||
return this(exp, context);
|
||||
|
||||
return this(exp, newContext);
|
||||
}
|
||||
|
@ -69,29 +69,31 @@ export class FormulaField extends Field {
|
||||
};
|
||||
|
||||
updateFieldData = async (instance, { transaction }) => {
|
||||
if (this.collection.name === instance.collectionName && instance.name === this.options.name) {
|
||||
this.options = Object.assign(this.options, instance.options);
|
||||
const { name } = this.options;
|
||||
if (this.collection.name !== instance.collectionName || instance.name !== this.options.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const records = await this.collection.repository.find({
|
||||
order: [this.collection.model.primaryKeyAttribute],
|
||||
transaction,
|
||||
});
|
||||
this.options = Object.assign(this.options, instance.options);
|
||||
const { name } = this.options;
|
||||
|
||||
for (const record of records) {
|
||||
const scope = record.toJSON();
|
||||
const result = this.calculate(scope);
|
||||
await record.update(
|
||||
{
|
||||
[name]: result,
|
||||
},
|
||||
{
|
||||
transaction,
|
||||
silent: true,
|
||||
hooks: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
const records = await this.collection.repository.find({
|
||||
order: [this.collection.model.primaryKeyAttribute],
|
||||
transaction,
|
||||
});
|
||||
|
||||
for (const record of records) {
|
||||
const scope = record.toJSON();
|
||||
const result = this.calculate(scope);
|
||||
await record.update(
|
||||
{
|
||||
[name]: result,
|
||||
},
|
||||
{
|
||||
transaction,
|
||||
silent: true,
|
||||
hooks: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -166,7 +166,8 @@ describe('workflow > instructions > calculation', () => {
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
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 () => {
|
||||
|
Loading…
Reference in New Issue
Block a user