Browse Source

feat: introduce formula curly hook

pull/1979/head
Wing-Kam Wong 2 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. 94
      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" @keydown.enter.prevent="selectText"
/> />
<div class="hint"> <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>. <a href="https://docs.nocodb.com/setup-and-usages/formulas#available-formula-features" target="_blank">Formulas</a>.
</div> </div>
<v-card v-if="suggestion && suggestion.length" class="formula-suggestion"> <v-card v-if="suggestion && suggestion.length" class="formula-suggestion">
@ -97,6 +97,7 @@ import debounce from 'debounce'
import jsep from 'jsep' import jsep from 'jsep'
import { UITypes } from 'nocodb-sdk' import { UITypes } from 'nocodb-sdk'
import formulaList, { validations } from '../../../../../helpers/formulaList' import formulaList, { validations } from '../../../../../helpers/formulaList'
import curly from '../../../../../helpers/formulaCurlyHook'
import { getWordUntilCaret, insertAtCursor } from '@/helpers' import { getWordUntilCaret, insertAtCursor } from '@/helpers'
import NcAutocompleteTree from '@/helpers/NcAutocompleteTree' import NcAutocompleteTree from '@/helpers/NcAutocompleteTree'
@ -161,6 +162,7 @@ export default {
}, },
created() { created() {
this.formula = { value: this.value || '' } this.formula = { value: this.value || '' }
jsep.plugins.register(curly)
}, },
methods: { methods: {
async save() { async save() {
@ -219,6 +221,7 @@ export default {
} }
}, },
validateAgainstMeta(pt, arr = []) { validateAgainstMeta(pt, arr = []) {
console.log('pt.type= ' + pt.type)
if (pt.type === 'CallExpression') { if (pt.type === 'CallExpression') {
if (!this.availableFunctions.includes(pt.callee.name)) { if (!this.availableFunctions.includes(pt.callee.name)) {
arr.push(`'${pt.callee.name}' function is not available`) arr.push(`'${pt.callee.name}' function is not available`)
@ -235,11 +238,7 @@ export default {
} }
pt.arguments.map(arg => this.validateAgainstMeta(arg, arr)) pt.arguments.map(arg => this.validateAgainstMeta(arg, arr))
} else if (pt.type === 'Identifier') { } else if (pt.type === 'Identifier') {
// $column_name$ -> column_name if (this.meta.columns.filter(c => !this.column || this.column.id !== c.id).every(c => c.title !== pt.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)) {
arr.push(`Column '${pt.name}' is not available`) arr.push(`Column '${pt.name}' is not available`)
} }
} else if (pt.type === 'BinaryExpression') { } else if (pt.type === 'BinaryExpression') {
@ -257,7 +256,7 @@ export default {
if (it.type === 'function') { if (it.type === 'function') {
this.$set(this.formula, 'value', insertAtCursor(this.$refs.input.$el.querySelector('input'), text, len, 1)) this.$set(this.formula, 'value', insertAtCursor(this.$refs.input.$el.querySelector('input'), text, len, 1))
} else if (it.type === 'column') { } 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 { } else {
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))
} }

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

2
packages/nc-gui/package.json

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

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

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

2
packages/nocodb-sdk/package.json

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

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

@ -1,6 +1,7 @@
import jsep from 'jsep'; import jsep from 'jsep';
import { ColumnType } from './Api'; import { ColumnType } from './Api';
export function substituteColumnIdWithAliasInFormula( export function substituteColumnIdWithAliasInFormula(
formula, formula,
columns: ColumnType[], columns: ColumnType[],
@ -23,15 +24,39 @@ export function substituteColumnIdWithAliasInFormula(
c.title === colNameOrId c.title === colNameOrId
); );
pt.name = column?.title || ptRaw?.name || pt?.name; 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') { } else if (pt.type === 'BinaryExpression') {
substituteId(pt.left, ptRaw?.left); substituteId(pt.left, ptRaw?.left);
substituteId(pt.right, ptRaw?.right); 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 parsedFormula = jsep(formula);
const parsedRawFormula = rawFormula && jsep(rawFormula); const parsedRawFormula = rawFormula && jsep(rawFormula);
substituteId(parsedFormula, parsedRawFormula); substituteId(parsedFormula, parsedRawFormula);
@ -65,7 +90,7 @@ export function jsepTreeToFormula(node) {
} }
if (node.type === 'Identifier') { if (node.type === 'Identifier') {
return node.name; return '{' + node.name + '}';
} }
if (node.type === 'Literal') { if (node.type === 'Literal') {

18
packages/nocodb/package-lock.json generated

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

2
packages/nocodb/package.json

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

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

@ -14,53 +14,79 @@ export async function substituteColumnAliasWithIdInFormula(
} else if (pt.type === 'Literal') { } else if (pt.type === 'Literal') {
return; return;
} else if (pt.type === 'Identifier') { } else if (pt.type === 'Identifier') {
const sanitizedPtName = pt.name.substring(1, pt.name.length - 1); const colNameOrId = pt.name;
const colNameOrId = sanitizedPtName;
const column = columns.find( const column = columns.find(
c => c =>
c.id === colNameOrId || c.id === colNameOrId ||
c.column_name === colNameOrId || c.column_name === colNameOrId ||
c.title === colNameOrId c.title === colNameOrId
); );
pt.name = column.id; pt.name = '{' + column.id + '}';
} else if (pt.type === 'BinaryExpression') { } else if (pt.type === 'BinaryExpression') {
await substituteId(pt.left); await substituteId(pt.left);
await substituteId(pt.right); await substituteId(pt.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 parsedFormula = jsep(formula);
await substituteId(parsedFormula); await substituteId(parsedFormula);
return jsepTreeToFormula(parsedFormula); return jsepTreeToFormula(parsedFormula);
} }
export function substituteColumnIdWithAliasInFormula( // not in use
formula, // export function substituteColumnIdWithAliasInFormula(
columns: Column[] // formula,
) { // columns: Column[]
const substituteId = (pt: any) => { // ) {
if (pt.type === 'CallExpression') { // const substituteId = (pt: any) => {
for (const arg of pt.arguments || []) { // if (pt.type === 'CallExpression') {
substituteId(arg); // for (const arg of pt.arguments || []) {
} // substituteId(arg);
} else if (pt.type === 'Literal') { // }
return; // } else if (pt.type === 'Literal') {
} else if (pt.type === 'Identifier') { // return;
const colNameOrId = pt.name; // } else if (pt.type === 'Identifier') {
const column = columns.find( // const colNameOrId = pt.name;
c => // const column = columns.find(
c.id === colNameOrId || // c =>
c.column_name === colNameOrId || // c.id === colNameOrId ||
c.title === colNameOrId // c.column_name === colNameOrId ||
); // c.title === colNameOrId
pt.name = column.id; // );
} else if (pt.type === 'BinaryExpression') { // pt.name = column.id;
substituteId(pt.left); // } else if (pt.type === 'BinaryExpression') {
substituteId(pt.right); // substituteId(pt.left);
} // substituteId(pt.right);
}; // }
// };
const parsedFormula = jsep(formula); //
substituteId(parsedFormula); // const parsedFormula = jsep(formula);
return jsepTreeToFormula(parsedFormula); // substituteId(parsedFormula);
} // return jsepTreeToFormula(parsedFormula);
// }

Loading…
Cancel
Save