Browse Source

feat: introduce formula curly hook

pull/1979/head
Wing-Kam Wong 3 years ago
parent
commit
a5f77a50f3
  1. 13
      packages/nc-gui/components/project/spreadsheet/components/editColumn/FormulaOptions.vue
  2. 24
      packages/nc-gui/helpers/formulaCurlyHook.js
  3. 18
      packages/nc-gui/package-lock.json
  4. 2
      packages/nc-gui/package.json
  5. 14
      packages/nocodb-sdk/package-lock.json
  6. 2
      packages/nocodb-sdk/package.json
  7. 33
      packages/nocodb-sdk/src/lib/formulaHelpers.ts
  8. 18
      packages/nocodb/package-lock.json
  9. 2
      packages/nocodb/package.json
  10. 92
      packages/nocodb/src/lib/noco/meta/helpers/formulaHelpers.ts

13
packages/nc-gui/components/project/spreadsheet/components/editColumn/FormulaOptions.vue

@ -16,7 +16,7 @@
@keydown.enter.prevent="selectText"
/>
<div class="hint">
Hint: Use $ to reference columns, e.g: $column_name$. For more, please check out
Hint: Use {} to reference columns, e.g: {column_name}. For more, please check out
<a href="https://docs.nocodb.com/setup-and-usages/formulas#available-formula-features" target="_blank">Formulas</a>.
</div>
<v-card v-if="suggestion && suggestion.length" class="formula-suggestion">
@ -97,6 +97,7 @@ import debounce from 'debounce'
import jsep from 'jsep'
import { UITypes } from 'nocodb-sdk'
import formulaList, { validations } from '../../../../../helpers/formulaList'
import curly from '../../../../../helpers/formulaCurlyHook'
import { getWordUntilCaret, insertAtCursor } from '@/helpers'
import NcAutocompleteTree from '@/helpers/NcAutocompleteTree'
@ -161,6 +162,7 @@ export default {
},
created() {
this.formula = { value: this.value || '' }
jsep.plugins.register(curly)
},
methods: {
async save() {
@ -219,6 +221,7 @@ export default {
}
},
validateAgainstMeta(pt, arr = []) {
console.log('pt.type= ' + pt.type)
if (pt.type === 'CallExpression') {
if (!this.availableFunctions.includes(pt.callee.name)) {
arr.push(`'${pt.callee.name}' function is not available`)
@ -235,11 +238,7 @@ export default {
}
pt.arguments.map(arg => this.validateAgainstMeta(arg, arr))
} else if (pt.type === 'Identifier') {
// $column_name$ -> column_name
const sanitizedPtName = pt.name.substring(1, pt.name.length - 1)
if (this.meta.columns.filter(c => !this.column || this.column.id !== c.id).find(c => c.title === pt.name)) {
arr.push(`Column '${pt.name}' needs to be wrapped by $. Try $${pt.name}$ instead`)
} else if (this.meta.columns.filter(c => !this.column || this.column.id !== c.id).every(c => c.title !== sanitizedPtName)) {
if (this.meta.columns.filter(c => !this.column || this.column.id !== c.id).every(c => c.title !== pt.name)) {
arr.push(`Column '${pt.name}' is not available`)
}
} else if (pt.type === 'BinaryExpression') {
@ -257,7 +256,7 @@ export default {
if (it.type === 'function') {
this.$set(this.formula, 'value', insertAtCursor(this.$refs.input.$el.querySelector('input'), text, len, 1))
} else if (it.type === 'column') {
this.$set(this.formula, 'value', insertAtCursor(this.$refs.input.$el.querySelector('input'), '$' + text + '$', len))
this.$set(this.formula, 'value', insertAtCursor(this.$refs.input.$el.querySelector('input'), '{' + text + '}', len))
} else {
this.$set(this.formula, 'value', insertAtCursor(this.$refs.input.$el.querySelector('input'), text, len))
}

24
packages/nc-gui/helpers/formulaCurlyHook.js

@ -0,0 +1,24 @@
const OCURLY_CODE = 123; // {
const CCURLY_CODE = 125; // }
export default {
name: 'curly',
init(jsep) {
jsep.hooks.add('gobble-token', function gobbleCurlyLiteral(env) {
const { context } = env
if (!jsep.isIdentifierStart(context.code) && context.code === OCURLY_CODE) {
context.index += 1
let nodes = context.gobbleExpressions(CCURLY_CODE)
if (context.code === CCURLY_CODE) {
context.index += 1
if (nodes.length > 0) {
env.node = nodes[0]
}
return env.node
} else {
context.throwError('Unclosed }')
}
}
});
}
}

18
packages/nc-gui/package-lock.json generated

@ -22,7 +22,7 @@
"fix-path": "^3.0.0",
"httpsnippet": "^2.0.0",
"inflection": "^1.12.0",
"jsep": "^0.4.0",
"jsep": "^1.3.6",
"material-design-icons-iconfont": "^5.0.1",
"monaco-editor": "^0.19.3",
"monaco-themes": "^0.2.5",
@ -76,7 +76,7 @@
"license": "MIT",
"dependencies": {
"axios": "^0.21.1",
"jsep": "^0.4.0"
"jsep": "^1.3.6"
},
"devDependencies": {
"@ava/typescript": "^1.1.1",
@ -9952,9 +9952,9 @@
}
},
"node_modules/jsep": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/jsep/-/jsep-0.4.0.tgz",
"integrity": "sha512-UDkrzhJK8hmgXeGK8WIiecc/cuW4Vnx5nnrRma7yaxK0WXlvZ4VerGrcxPzifd/CA6QdcI1hpXqr22tHKXpcQA==",
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/jsep/-/jsep-1.3.6.tgz",
"integrity": "sha512-o7fP1eZVROIChADx7HKiwGRVI0tUqgUUGhaok6DP7cMxpDeparuooREDBDeNk2G5KIB49MBSkRYsCOu4PmZ+1w==",
"engines": {
"node": ">= 10.16.0"
}
@ -24714,9 +24714,9 @@
}
},
"jsep": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/jsep/-/jsep-0.4.0.tgz",
"integrity": "sha512-UDkrzhJK8hmgXeGK8WIiecc/cuW4Vnx5nnrRma7yaxK0WXlvZ4VerGrcxPzifd/CA6QdcI1hpXqr22tHKXpcQA=="
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/jsep/-/jsep-1.3.6.tgz",
"integrity": "sha512-o7fP1eZVROIChADx7HKiwGRVI0tUqgUUGhaok6DP7cMxpDeparuooREDBDeNk2G5KIB49MBSkRYsCOu4PmZ+1w=="
},
"jsesc": {
"version": "2.5.2",
@ -25389,7 +25389,7 @@
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-prettier": "^4.0.0",
"gh-pages": "^3.1.0",
"jsep": "^0.4.0",
"jsep": "^1.3.6",
"npm-run-all": "^4.1.5",
"nyc": "^15.1.0",
"open-cli": "^6.0.1",

2
packages/nc-gui/package.json

@ -25,7 +25,7 @@
"fix-path": "^3.0.0",
"httpsnippet": "^2.0.0",
"inflection": "^1.12.0",
"jsep": "^0.4.0",
"jsep": "^1.3.6",
"material-design-icons-iconfont": "^5.0.1",
"monaco-editor": "^0.19.3",
"monaco-themes": "^0.2.5",

14
packages/nocodb-sdk/package-lock.json generated

@ -10,7 +10,7 @@
"license": "MIT",
"dependencies": {
"axios": "^0.21.1",
"jsep": "^0.4.0"
"jsep": "^1.3.6"
},
"devDependencies": {
"@ava/typescript": "^1.1.1",
@ -6764,9 +6764,9 @@
}
},
"node_modules/jsep": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/jsep/-/jsep-0.4.0.tgz",
"integrity": "sha512-UDkrzhJK8hmgXeGK8WIiecc/cuW4Vnx5nnrRma7yaxK0WXlvZ4VerGrcxPzifd/CA6QdcI1hpXqr22tHKXpcQA==",
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/jsep/-/jsep-1.3.6.tgz",
"integrity": "sha512-o7fP1eZVROIChADx7HKiwGRVI0tUqgUUGhaok6DP7cMxpDeparuooREDBDeNk2G5KIB49MBSkRYsCOu4PmZ+1w==",
"engines": {
"node": ">= 10.16.0"
}
@ -15535,9 +15535,9 @@
}
},
"jsep": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/jsep/-/jsep-0.4.0.tgz",
"integrity": "sha512-UDkrzhJK8hmgXeGK8WIiecc/cuW4Vnx5nnrRma7yaxK0WXlvZ4VerGrcxPzifd/CA6QdcI1hpXqr22tHKXpcQA=="
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/jsep/-/jsep-1.3.6.tgz",
"integrity": "sha512-o7fP1eZVROIChADx7HKiwGRVI0tUqgUUGhaok6DP7cMxpDeparuooREDBDeNk2G5KIB49MBSkRYsCOu4PmZ+1w=="
},
"jsesc": {
"version": "2.5.2",

2
packages/nocodb-sdk/package.json

@ -44,7 +44,7 @@
},
"dependencies": {
"axios": "^0.21.1",
"jsep": "^0.4.0"
"jsep": "^1.3.6"
},
"devDependencies": {
"@ava/typescript": "^1.1.1",

33
packages/nocodb-sdk/src/lib/formulaHelpers.ts

@ -1,6 +1,7 @@
import jsep from 'jsep';
import { ColumnType } from './Api';
export function substituteColumnIdWithAliasInFormula(
formula,
columns: ColumnType[],
@ -23,15 +24,39 @@ export function substituteColumnIdWithAliasInFormula(
c.title === colNameOrId
);
pt.name = column?.title || ptRaw?.name || pt?.name;
if (pt.name[0] != '$' && pt.name[pt.name.length - 1] != '$') {
pt.name = '$' + pt.name + '$';
}
} else if (pt.type === 'BinaryExpression') {
substituteId(pt.left, ptRaw?.left);
substituteId(pt.right, ptRaw?.right);
}
};
// register curly hook
jsep.plugins.register({
name: 'curly',
init(jsep) {
jsep.hooks.add('gobble-token', function gobbleCurlyLiteral(env) {
const OCURLY_CODE = 123; // {
const CCURLY_CODE = 125; // }
const { context } = env;
if (
!jsep.isIdentifierStart(context.code) &&
context.code === OCURLY_CODE
) {
context.index += 1;
const nodes = context.gobbleExpressions(CCURLY_CODE);
if (context.code === CCURLY_CODE) {
context.index += 1;
if (nodes.length > 0) {
env.node = nodes[0];
}
return env.node;
} else {
context.throwError('Unclosed }');
}
}
});
},
} as jsep.IPlugin);
const parsedFormula = jsep(formula);
const parsedRawFormula = rawFormula && jsep(rawFormula);
substituteId(parsedFormula, parsedRawFormula);
@ -65,7 +90,7 @@ export function jsepTreeToFormula(node) {
}
if (node.type === 'Identifier') {
return node.name;
return '{' + node.name + '}';
}
if (node.type === 'Literal') {

18
packages/nocodb/package-lock.json generated

@ -52,7 +52,7 @@
"ioredis-mock": "^7.1.0",
"is-docker": "^2.2.1",
"js-beautify": "^1.11.0",
"jsep": "^0.4.0",
"jsep": "^1.3.6",
"json2csv": "^5.0.6",
"jsonfile": "^6.1.0",
"jsonwebtoken": "^8.5.1",
@ -160,7 +160,7 @@
"license": "MIT",
"dependencies": {
"axios": "^0.21.1",
"jsep": "^0.4.0"
"jsep": "^1.3.6"
},
"devDependencies": {
"@ava/typescript": "^1.1.1",
@ -13834,9 +13834,9 @@
"optional": true
},
"node_modules/jsep": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/jsep/-/jsep-0.4.0.tgz",
"integrity": "sha512-UDkrzhJK8hmgXeGK8WIiecc/cuW4Vnx5nnrRma7yaxK0WXlvZ4VerGrcxPzifd/CA6QdcI1hpXqr22tHKXpcQA==",
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/jsep/-/jsep-1.3.6.tgz",
"integrity": "sha512-o7fP1eZVROIChADx7HKiwGRVI0tUqgUUGhaok6DP7cMxpDeparuooREDBDeNk2G5KIB49MBSkRYsCOu4PmZ+1w==",
"engines": {
"node": ">= 10.16.0"
}
@ -35846,9 +35846,9 @@
"optional": true
},
"jsep": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/jsep/-/jsep-0.4.0.tgz",
"integrity": "sha512-UDkrzhJK8hmgXeGK8WIiecc/cuW4Vnx5nnrRma7yaxK0WXlvZ4VerGrcxPzifd/CA6QdcI1hpXqr22tHKXpcQA=="
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/jsep/-/jsep-1.3.6.tgz",
"integrity": "sha512-o7fP1eZVROIChADx7HKiwGRVI0tUqgUUGhaok6DP7cMxpDeparuooREDBDeNk2G5KIB49MBSkRYsCOu4PmZ+1w=="
},
"jsesc": {
"version": "2.5.2",
@ -37849,7 +37849,7 @@
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-prettier": "^4.0.0",
"gh-pages": "^3.1.0",
"jsep": "^0.4.0",
"jsep": "^1.3.6",
"npm-run-all": "^4.1.5",
"nyc": "^15.1.0",
"open-cli": "^6.0.1",

2
packages/nocodb/package.json

@ -134,7 +134,7 @@
"ioredis-mock": "^7.1.0",
"is-docker": "^2.2.1",
"js-beautify": "^1.11.0",
"jsep": "^0.4.0",
"jsep": "^1.3.6",
"json2csv": "^5.0.6",
"jsonfile": "^6.1.0",
"jsonwebtoken": "^8.5.1",

92
packages/nocodb/src/lib/noco/meta/helpers/formulaHelpers.ts

@ -14,53 +14,79 @@ export async function substituteColumnAliasWithIdInFormula(
} else if (pt.type === 'Literal') {
return;
} else if (pt.type === 'Identifier') {
const sanitizedPtName = pt.name.substring(1, pt.name.length - 1);
const colNameOrId = sanitizedPtName;
const colNameOrId = pt.name;
const column = columns.find(
c =>
c.id === colNameOrId ||
c.column_name === colNameOrId ||
c.title === colNameOrId
);
pt.name = column.id;
pt.name = '{' + column.id + '}';
} else if (pt.type === 'BinaryExpression') {
await substituteId(pt.left);
await substituteId(pt.right);
}
};
const parsedFormula = jsep(formula);
await substituteId(parsedFormula);
return jsepTreeToFormula(parsedFormula);
}
export function substituteColumnIdWithAliasInFormula(
formula,
columns: Column[]
// register curly hook
jsep.plugins.register({
name: 'curly',
init(jsep) {
jsep.hooks.add('gobble-token', function gobbleCurlyLiteral(env) {
const OCURLY_CODE = 123; // {
const CCURLY_CODE = 125; // }
const { context } = env;
if (
!jsep.isIdentifierStart(context.code) &&
context.code === OCURLY_CODE
) {
const substituteId = (pt: any) => {
if (pt.type === 'CallExpression') {
for (const arg of pt.arguments || []) {
substituteId(arg);
context.index += 1;
const nodes = context.gobbleExpressions(CCURLY_CODE);
if (context.code === CCURLY_CODE) {
context.index += 1;
if (nodes.length > 0) {
env.node = nodes[0];
}
} else if (pt.type === 'Literal') {
return;
} else if (pt.type === 'Identifier') {
const colNameOrId = pt.name;
const column = columns.find(
c =>
c.id === colNameOrId ||
c.column_name === colNameOrId ||
c.title === colNameOrId
);
pt.name = column.id;
} else if (pt.type === 'BinaryExpression') {
substituteId(pt.left);
substituteId(pt.right);
return env.node;
} else {
context.throwError('Unclosed }');
}
};
}
});
}
} as jsep.IPlugin);
const parsedFormula = jsep(formula);
substituteId(parsedFormula);
await substituteId(parsedFormula);
return jsepTreeToFormula(parsedFormula);
}
// not in use
// export function substituteColumnIdWithAliasInFormula(
// formula,
// columns: Column[]
// ) {
// const substituteId = (pt: any) => {
// if (pt.type === 'CallExpression') {
// for (const arg of pt.arguments || []) {
// substituteId(arg);
// }
// } else if (pt.type === 'Literal') {
// return;
// } else if (pt.type === 'Identifier') {
// const colNameOrId = pt.name;
// const column = columns.find(
// c =>
// c.id === colNameOrId ||
// c.column_name === colNameOrId ||
// c.title === colNameOrId
// );
// pt.name = column.id;
// } else if (pt.type === 'BinaryExpression') {
// substituteId(pt.left);
// substituteId(pt.right);
// }
// };
//
// const parsedFormula = jsep(formula);
// substituteId(parsedFormula);
// return jsepTreeToFormula(parsedFormula);
// }

Loading…
Cancel
Save