mirror of
https://github.com/nocodb/nocodb.git
synced 2026-04-24 23:55:35 +00:00
@@ -1439,7 +1439,7 @@ export default {
|
||||
}))
|
||||
// }
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
this.$toast.error(await this._extractSdkResponseErrorMsg(e)).goAway(3000)
|
||||
}
|
||||
this.loadingData = false
|
||||
},
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
class=" card nc-col-create-or-edit-card"
|
||||
>
|
||||
<v-form v-model="valid" @submit.prevent="save">
|
||||
<v-container fluid @click.stop.prevent>
|
||||
<v-container fluid @click.stop>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
|
||||
@@ -1,35 +1,28 @@
|
||||
<template>
|
||||
<div class="formula-wrapper">
|
||||
<v-menu
|
||||
v-model="autocomplete"
|
||||
bottom
|
||||
offset-y
|
||||
nudge-bottom="-25px"
|
||||
allow-overflow
|
||||
>
|
||||
<template #activator="_args">
|
||||
<!-- todo: autocomplete based on available functions and metadata -->
|
||||
<!-- <v-tooltip color="info" right>-->
|
||||
<!-- <template #activator="{on}">-->
|
||||
<v-text-field
|
||||
ref="input"
|
||||
v-model="formula.value"
|
||||
dense
|
||||
outlined
|
||||
class="caption"
|
||||
hide-details="auto"
|
||||
label="Formula"
|
||||
persistent-hint
|
||||
hint="Available formulas are ADD, AVG, CONCAT, +, -, /"
|
||||
:rules="[v => !!v || 'Required', v => parseAndValidateFormula(v)]"
|
||||
autocomplete="off"
|
||||
@input="handleInputDeb"
|
||||
@keydown.down.prevent="suggestionListDown"
|
||||
@keydown.up.prevent="suggestionListUp"
|
||||
@keydown.enter.prevent="selectText"
|
||||
/>
|
||||
</template>
|
||||
<v-list v-if="suggestion" ref="sugList" dense max-height="50vh" style="overflow: auto">
|
||||
<v-text-field
|
||||
ref="input"
|
||||
v-model="formula.value"
|
||||
dense
|
||||
outlined
|
||||
class="caption"
|
||||
hide-details="auto"
|
||||
label="Formula"
|
||||
:rules="[v => !!v || 'Required', v => parseAndValidateFormula(v)]"
|
||||
autocomplete="off"
|
||||
@input="handleInputDeb"
|
||||
@keydown.down.prevent="suggestionListDown"
|
||||
@keydown.up.prevent="suggestionListUp"
|
||||
@keydown.enter.prevent="selectText"
|
||||
/>
|
||||
<div class="hint">
|
||||
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">
|
||||
<v-card-text>Suggestions</v-card-text>
|
||||
<v-divider />
|
||||
<v-list ref="sugList" dense max-height="50vh" style="overflow: auto">
|
||||
<v-list-item-group
|
||||
v-model="selected"
|
||||
color="primary"
|
||||
@@ -42,16 +35,59 @@
|
||||
selectable
|
||||
@mousedown.prevent="appendText(it)"
|
||||
>
|
||||
<span
|
||||
class="caption"
|
||||
:class="{
|
||||
'primary--text text--lighten-2 font-weight-bold': it.type ==='function'
|
||||
}"
|
||||
>{{ it.text }}<span v-if="it.type ==='function'">(...)</span></span>
|
||||
<!-- Function -->
|
||||
<template v-if="it.type ==='function'">
|
||||
<v-list-item-content>
|
||||
<span
|
||||
class="caption primary--text text--lighten-2 font-weight-bold"
|
||||
>
|
||||
{{ it.text }}
|
||||
</span>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action>
|
||||
<span class="caption">
|
||||
Function
|
||||
</span>
|
||||
</v-list-item-action>
|
||||
</template>
|
||||
|
||||
<!-- Column -->
|
||||
<template v-if="it.type ==='column'">
|
||||
<v-list-item-content>
|
||||
<span
|
||||
class="caption text--darken-3 font-weight-bold"
|
||||
>
|
||||
{{ it.text }}
|
||||
</span>
|
||||
</v-list-item-content>
|
||||
|
||||
<v-list-item-action>
|
||||
<span class="caption">
|
||||
Column
|
||||
</span>
|
||||
</v-list-item-action>
|
||||
</template>
|
||||
|
||||
<!-- Operator -->
|
||||
<template v-if="it.type ==='op'">
|
||||
<v-list-item-content>
|
||||
<span
|
||||
class="caption indigo--text text--darken-3 font-weight-bold"
|
||||
>
|
||||
{{ it.text }}
|
||||
</span>
|
||||
</v-list-item-content>
|
||||
|
||||
<v-list-item-action>
|
||||
<span class="caption">
|
||||
Operator
|
||||
</span>
|
||||
</v-list-item-action>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list-item-group>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -59,7 +95,7 @@
|
||||
|
||||
import debounce from 'debounce'
|
||||
import jsep from 'jsep'
|
||||
import { UITypes } from 'nocodb-sdk'
|
||||
import { UITypes, jsepCurlyHook } from 'nocodb-sdk'
|
||||
import formulaList, { validations } from '../../../../../helpers/formulaList'
|
||||
import { getWordUntilCaret, insertAtCursor } from '@/helpers'
|
||||
import NcAutocompleteTree from '@/helpers/NcAutocompleteTree'
|
||||
@@ -76,14 +112,19 @@ export default {
|
||||
suggestion: null,
|
||||
wordToComplete: '',
|
||||
selected: 0,
|
||||
tooltip: true
|
||||
tooltip: true,
|
||||
sortOrder: {
|
||||
column: 0,
|
||||
function: 1,
|
||||
op: 2
|
||||
}
|
||||
}),
|
||||
computed: {
|
||||
suggestionsList() {
|
||||
const unsupportedFnList = this.sqlUi.getUnsupportedFnList()
|
||||
return [
|
||||
...this.availableFunctions.filter(fn => !unsupportedFnList.includes(fn)).map(fn => ({
|
||||
text: fn,
|
||||
text: fn + '()',
|
||||
type: 'function'
|
||||
})),
|
||||
...this.meta.columns.filter(c => !this.column || this.column.id !== c.id).map(c => ({
|
||||
@@ -120,6 +161,7 @@ export default {
|
||||
},
|
||||
created() {
|
||||
this.formula = { value: this.value || '' }
|
||||
jsep.plugins.register(jsepCurlyHook)
|
||||
},
|
||||
methods: {
|
||||
async save() {
|
||||
@@ -135,7 +177,7 @@ export default {
|
||||
this.$toast.success('Formula column saved successfully').goAway(3000)
|
||||
return this.$emit('saved', this.alias)
|
||||
} catch (e) {
|
||||
this.$toast.error(e.message).goAway(3000)
|
||||
this.$toast.error(await this._extractSdkResponseErrorMsg(e)).goAway(3000)
|
||||
}
|
||||
},
|
||||
async update() {
|
||||
@@ -165,7 +207,6 @@ export default {
|
||||
this.$toast.error(e.message).goAway(3000)
|
||||
}
|
||||
},
|
||||
// todo: validate formula based on meta
|
||||
parseAndValidateFormula(formula) {
|
||||
try {
|
||||
const pt = jsep(formula)
|
||||
@@ -196,7 +237,7 @@ export default {
|
||||
pt.arguments.map(arg => this.validateAgainstMeta(arg, arr))
|
||||
} else if (pt.type === 'Identifier') {
|
||||
if (this.meta.columns.filter(c => !this.column || this.column.id !== c.id).every(c => c.title !== pt.name)) {
|
||||
arr.push(`Column with name '${pt.name}' is not available`)
|
||||
arr.push(`Column '${pt.name}' is not available`)
|
||||
}
|
||||
} else if (pt.type === 'BinaryExpression') {
|
||||
if (!this.availableBinOps.includes(pt.operator)) {
|
||||
@@ -211,10 +252,14 @@ export default {
|
||||
const text = it.text
|
||||
const len = this.wordToComplete.length
|
||||
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') {
|
||||
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))
|
||||
}
|
||||
this.autocomplete = false
|
||||
this.suggestion = null
|
||||
},
|
||||
_handleInputDeb: debounce(async function(self) {
|
||||
await self.handleInput()
|
||||
@@ -228,22 +273,25 @@ export default {
|
||||
const query = getWordUntilCaret(this.$refs.input.$el.querySelector('input'))
|
||||
const parts = query.split(/\W+/)
|
||||
this.wordToComplete = parts.pop()
|
||||
this.suggestion = this.acTree.complete(this.wordToComplete)
|
||||
this.suggestion = this.acTree.complete(this.wordToComplete)?.sort((x, y) => this.sortOrder[x.type] - this.sortOrder[y.type])
|
||||
this.autocomplete = !!this.suggestion.length
|
||||
},
|
||||
selectText() {
|
||||
if (this.selected > -1 && this.selected < this.suggestion.length) {
|
||||
if (this.suggestion && this.selected > -1 && this.selected < this.suggestion.length) {
|
||||
this.appendText(this.suggestion[this.selected])
|
||||
this.autocomplete = false
|
||||
}
|
||||
},
|
||||
suggestionListDown() {
|
||||
this.selected = ++this.selected % this.suggestion.length
|
||||
this.scrollToSelectedOption()
|
||||
if (this.suggestion) {
|
||||
this.selected = ++this.selected % this.suggestion.length
|
||||
this.scrollToSelectedOption()
|
||||
}
|
||||
},
|
||||
suggestionListUp() {
|
||||
this.selected = --this.selected > -1 ? this.selected : this.suggestion.length - 1
|
||||
this.scrollToSelectedOption()
|
||||
if (this.suggestion) {
|
||||
this.selected = --this.selected > -1 ? this.selected : this.suggestion.length - 1
|
||||
this.scrollToSelectedOption()
|
||||
}
|
||||
},
|
||||
scrollToSelectedOption() {
|
||||
this.$nextTick(() => {
|
||||
@@ -263,4 +311,17 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
::v-deep {
|
||||
.formula-suggestion .v-card__text {
|
||||
font-size: 0.75rem;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.hint {
|
||||
font-size: 0.75rem;
|
||||
line-height: normal;
|
||||
padding: 10px 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
18
packages/nc-gui/package-lock.json
generated
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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -18,10 +18,10 @@ menuTitle: "Formulas"
|
||||
|
||||
### 4. Insert required formula
|
||||
|
||||
- Can use column names in equation
|
||||
- Can use explicit numberical values/ strings as needed
|
||||
- You can use explicit numerical values/ strings as needed, e.g. `123` (numeric) or `"123"` (string).
|
||||
- You can reference column names in equation with `{}`, e.g. `{column_name}`, if the column name conflicts with literals
|
||||
- Table below lists supported formula & associated syntax
|
||||
- Nested formula (formula equation referring to another formula column) are not supported
|
||||
- Nested formula (formula equation referring to another formula column) is supported
|
||||
|
||||
### 5. Click on 'Save'
|
||||
|
||||
@@ -31,99 +31,98 @@ menuTitle: "Formulas"
|
||||
|
||||
| Name | Syntax | Sample | Output |
|
||||
|-------------|----------------------------|----------------------------------|------------------------------------------------------------------|
|
||||
| **ABS** | `ABS(value)` | `ABS(Column)` | Absolute value of the input parameter |
|
||||
| **ADD** | `ADD(value1,[value2,...])` | `ADD(Column1, Column1)` | Sum of input parameters |
|
||||
| **AVG** | `AVG(value1,[value2,...])` | `AVG(Column1, Column1)` | Average of input parameters |
|
||||
| **CEILING** | `CEILING(value)` | `CEILING(Column)` | Rounded next largest integer value of input parameter |
|
||||
| **EXP** | `EXP(value)` | `EXP(Column)` | Exponential value of input parameter (`e^x`) |
|
||||
| **FLOOR** | `FLOOR(value)` | `FLOOR(Column)` | Rounded largest integer less than or equal to input parameter |
|
||||
| **INT** | `INT(value)` | `INT(Column)` | Integer value of input parameter |
|
||||
| **LOG** | `LOG([base], value)` | `LOG(10, Column)` | Logarithm of input parameter to the base (default = e) specified |
|
||||
| **MAX** | `MAX(value1,[value2,...])` | `MAX(Column1, Column2, Column3)` | Maximum value amongst input parameters |
|
||||
| **MIN** | `MIN(value1,[value2,...])` | `MIN(Column1, Column2, Column3)` | Minimum value amongst input parameters |
|
||||
| **MOD** | `MOD(value1, value2)` | `MOD(Column, 2)` | Remainder after integer division of input parameters |
|
||||
| **POWER** | `POWER(base, exponent)` | `POWER(Column, 3)` | `base` to the `exponent` power, as in `base^exponent` |
|
||||
| **ROUND** | `ROUND(value)` | `ROUND(Column)` | Nearest integer to the input parameter |
|
||||
| **SQRT** | `SQRT(value)` | `SQRT(Column)` | Square root of the input parameter |
|
||||
| **ABS** | `ABS(value)` | `ABS({Column})` | Absolute value of the input parameter |
|
||||
| **ADD** | `ADD(value1,[value2,...])` | `ADD({Column1}, {Column2})` | Sum of input parameters |
|
||||
| **AVG** | `AVG(value1,[value2,...])` | `AVG({Column1}, {Column2})` | Average of input parameters |
|
||||
| **CEILING** | `CEILING(value)` | `CEILING({Column})` | Rounded next largest integer value of input parameter |
|
||||
| **EXP** | `EXP(value)` | `EXP({Column})` | Exponential value of input parameter (`e^x`) |
|
||||
| **FLOOR** | `FLOOR(value)` | `FLOOR({Column})` | Rounded largest integer less than or equal to input parameter |
|
||||
| **INT** | `INT(value)` | `INT({Column})` | Integer value of input parameter |
|
||||
| **LOG** | `LOG([base], value)` | `LOG(10, {Column})` | Logarithm of input parameter to the base (default = e) specified |
|
||||
| **MAX** | `MAX(value1,[value2,...])` | `MAX({Column1}, {Column2}, {Column3})` | Maximum value amongst input parameters |
|
||||
| **MIN** | `MIN(value1,[value2,...])` | `MIN({Column1}, {Column2}, {Column3})` | Minimum value amongst input parameters |
|
||||
| **MOD** | `MOD(value1, value2)` | `MOD({Column}, 2)` | Remainder after integer division of input parameters |
|
||||
| **POWER** | `POWER(base, exponent)` | `POWER({Column}, 3)` | `base` to the `exponent` power, as in `base ^ exponent` |
|
||||
| **ROUND** | `ROUND(value)` | `ROUND({Column})` | Nearest integer to the input parameter |
|
||||
| **SQRT** | `SQRT(value)` | `SQRT({Column})` | Square root of the input parameter |
|
||||
|
||||
|
||||
### Numeric Operators
|
||||
|
||||
| Operator | Sample | Description |
|
||||
| -------- | ----------------------- | -------------------------------- |
|
||||
| `+` | `column1 + column2 + 2` | Addition of numeric values |
|
||||
| `-` | `column1 - column2` | Subtraction of numeric values |
|
||||
| `*` | `column1 * column2` | Multiplication of numeric values |
|
||||
| `/` | `column1 / column2` | Division of numeric values |
|
||||
| `+` | `{Column1} + {Column2} + 2` | Addition of numeric values |
|
||||
| `-` | `{Column1} - {Column2}` | Subtraction of numeric values |
|
||||
| `*` | `{Column1} * {Column2}` | Multiplication of numeric values |
|
||||
| `/` | `{Column1} / {Column2}` | Division of numeric values |
|
||||
|
||||
<alert type="success">
|
||||
Tip :To change the order of arithmetic operation, you can use round bracket parantheses (). <br/>
|
||||
Example: (column1 + (column2 * column3) / (3 - column4 ))
|
||||
Example: ({Column1} + ({Column2} * {Column3}) / (3 - $Column4$ ))
|
||||
</alert>
|
||||
|
||||
### String Functions
|
||||
|
||||
| Name | Syntax | Sample | Output |
|
||||
|-------------|----------------------------------|---------------------------------|---------------------------------------------------------------------------|
|
||||
| **CONCAT** | `CONCAT(str1, [str2,...])` | `CONCAT(fName, ' ', lName)` | Concatenated string of input parameters |
|
||||
| **LEFT** | `LEFT(str1, [str2,...])` | `LEFT(Column, 3)` | `n` characters from the beginning of input parameter |
|
||||
| **LEN** | `LEN(str)` | `LEN(Title)` | Input parameter character length |
|
||||
| **LOWER** | `LOWER(str)` | `LOWER(Title)` | Lower case converted string of input parameter |
|
||||
| **MID** | `SUBTR(str, position, [count])` | `MID(Column, 3, 2)` | Alias for `SUBSTR` |
|
||||
| **REPEAT** | `REPEAT(str, count)` | `REPEAT(Column, 2)` | Specified copies of the input parameter string concatenated together |
|
||||
| **REPLACE** | `REPLACE(str, srchStr, rplcStr)` | `REPLACE(Column, 'int', 'num')` | String, after replacing all occurrences of `srchStr` with `rplcStr` |
|
||||
| **RIGHT** | `RIGHT(str, count)` | `RIGHT(Column, 3)` | `n` characters from the end of input parameter |
|
||||
| **SEARCH** | `SEARCH(str, srchStr)` | `SEARCH(Column, 'str')` | Index of `srchStr` specified if found, 0 otherwise |
|
||||
| **SUBSTR** | `SUBTR(str, position, [count])` | `SUBSTR(Column, 3, 2)` | Substring of length 'count' of input string, from the postition specified |
|
||||
| **TRIM** | `TRIM(str)` | `TRIM(Title)` | Remove trailing and leading whitespaces from input parameter |
|
||||
| **UPPER** | `UPPER(str)` | `UPPER(Title)` | Upper case converted string of input parameter |
|
||||
| **URL** | `URL(str)` | `URL(Column)` | Convert to a hyperlink if it is a valid URL |
|
||||
| **CONCAT** | `CONCAT(str1, [str2,...])` | `CONCAT({Column1}, ' ', {Column2})` | Concatenated string of input parameters |
|
||||
| **LEFT** | `LEFT(str1, [str2,...])` | `LEFT({Column}, 3)` | `n` characters from the beginning of input parameter |
|
||||
| **LEN** | `LEN(str)` | `LEN({Column})` | Input parameter character length |
|
||||
| **LOWER** | `LOWER(str)` | `LOWER({Column})` | Lower case converted string of input parameter |
|
||||
| **MID** | `SUBTR(str, position, [count])` | `MID({Column}, 3, 2)` | Alias for `SUBSTR` |
|
||||
| **REPEAT** | `REPEAT(str, count)` | `REPEAT({Column}, 2)` | Specified copies of the input parameter string concatenated together |
|
||||
| **REPLACE** | `REPLACE(str, srchStr, rplcStr)` | `REPLACE({Column}, 'int', 'num')` | String, after replacing all occurrences of `srchStr` with `rplcStr` |
|
||||
| **RIGHT** | `RIGHT(str, count)` | `RIGHT({Column}, 3)` | `n` characters from the end of input parameter |
|
||||
| **SEARCH** | `SEARCH(str, srchStr)` | `SEARCH({Column}, 'str')` | Index of `srchStr` specified if found, 0 otherwise |
|
||||
| **SUBSTR** | `SUBTR(str, position, [count])` | `SUBSTR({Column}, 3, 2)` | Substring of length 'count' of input string, from the postition specified |
|
||||
| **TRIM** | `TRIM(str)` | `TRIM({Column})` | Remove trailing and leading whitespaces from input parameter |
|
||||
| **UPPER** | `UPPER(str)` | `UPPER({Column})` | Upper case converted string of input parameter |
|
||||
| **URL** | `URL(str)` | `URL({Column})` | Convert to a hyperlink if it is a valid URL |
|
||||
|
||||
### Date Functions
|
||||
|
||||
| Name | Syntax | Sample | Output | Remark |
|
||||
|---|---|---|---|---|
|
||||
| **DATEADD** | `DATEADD(DATE_COL, 1, 'day')` | `DATEADD(date, 1, 'day')` | Supposing the DATE_COL is 2022-03-14. The result is 2022-03-15. | DateTime columns and negative values are supported. Example: `DATEADD(DATE_TIME_COL, -1, 'day')` |
|
||||
| | `DATEADD(DATE_COL, 2, 'month')` | `DATEADD(date, 2, 'month')` | Supposing the DATE_COL is 2022-03-14 03:14. The result is 2022-05-14 03:14. | DateTime columns and negative values are supported. Example: `DATEADD(DATE_TIME_COL, -2, 'month')` |
|
||||
| | `IF(NOW() < DATE_COL, "true", "false")` | `IF(NOW() < date, "true", "false")` | If current date is less than DATE_COL, it returns true. Otherwise, it returns false. | DateTime columns and negative values are supported. |
|
||||
| | `IF(NOW() < DATEADD(DATE_COL,10,'day'), "true", "false")` | `IF(NOW() < DATEADD(date,10,'day'), "true", "false")` | If the current date is less than DATE_COL plus 10 days, it returns true. Otherwise, it returns false. | DateTime columns and negative values are supported. |
|
||||
| **DATEADD** | `DATEADD({DATE_COL}, 1, 'day')` | `DATEADD(date, 1, 'day')` | Supposing {DATE_COL} is 2022-03-14. The result is 2022-03-15. | DateTime columns and negative values are supported. Example: `DATEADD(DATE_TIME_COL, -1, 'day')` |
|
||||
| | `DATEADD({DATE_COL}, 2, 'month')` | `DATEADD(date, 2, 'month')` | Supposing {DATE_COL} is 2022-03-14 03:14. The result is 2022-05-14 03:14. | DateTime columns and negative values are supported. Example: `DATEADD(DATE_TIME_COL, -2, 'month')` |
|
||||
| | `IF(NOW() < {DATE_COL}, "true", "false")` | `IF(NOW() < date, "true", "false")` | If current date is less than {DATE_COL}, it returns true. Otherwise, it returns false. | DateTime columns and negative values are supported. |
|
||||
| | `IF(NOW() < DATEADD({DATE_COL},10,'day'), "true", "false")` | `IF(NOW() < DATEADD(date,10,'day'), "true", "false")` | If the current date is less than {DATE_COL} plus 10 days, it returns true. Otherwise, it returns false. | DateTime columns and negative values are supported. |
|
||||
|
||||
### Logical Operators
|
||||
|
||||
| Operator | Sample | Description |
|
||||
| -------- | -------------------- | ------------------------ |
|
||||
| `<` | `column1 < column2` | Less than |
|
||||
| `>` | `column1 > column2` | Greater than |
|
||||
| `<=` | `column1 <= column2` | Less than or equal to |
|
||||
| `>=` | `column1 >= column2` | Greater than or equal to |
|
||||
| `==` | `column1 == column2` | Equal to |
|
||||
| `!=` | `column1 != column2` | Not equal to |
|
||||
| `<` | `{Column1} < {Column2}` | Less than |
|
||||
| `>` | `{Column1} > {Column2}` | Greater than |
|
||||
| `<=` | `{Column1} <= {Column2}` | Less than or equal to |
|
||||
| `>=` | `{Column1} >= {Column2}` | Greater than or equal to |
|
||||
| `==` | `{Column1} == {Column2}` | Equal to |
|
||||
| `!=` | `{Column1} != {Column2}` | Not equal to |
|
||||
|
||||
|
||||
### Conditional Expressions
|
||||
|
||||
| Name | Syntax | Sample | Output |
|
||||
|------------|------------------------------------------------|---------------------------------------------|-------------------------------------------------------------|
|
||||
| **IF** | `IF(expr, successCase, [failCase])` | `IF(Column > 1, Value1, Value2)` | successCase if `expr` evaluates to TRUE, elseCase otherwise |
|
||||
| **SWITCH** | `SWITCH(expr, [pattern, value, ..., default])` | `SWITCH(Column1, 1, 'One', 2, 'Two', '--')` | Switch case value based on `expr` output |
|
||||
| **AND** | `AND(expr1, [expr2,...])` | `AND(Column > 2, Column < 10)` | TRUE if all `expr` evaluate to TRUE |
|
||||
| **OR** | `OR(expr1, [expr2,...])` | `OR(Column > 2, Column < 10)` | TRUE if at least one `expr` evaluates to TRUE |
|
||||
| **IF** | `IF(expr, successCase, [failCase])` | `IF({Column} > 1, Value1, Value2)` | successCase if `expr` evaluates to TRUE, elseCase otherwise |
|
||||
| **SWITCH** | `SWITCH(expr, [pattern, value, ..., default])` | `SWITCH({Column}, 1, 'One', 2, 'Two', '--')` | Switch case value based on `expr` output |
|
||||
| **AND** | `AND(expr1, [expr2,...])` | `AND({Column} > 2, {Column} < 10)` | TRUE if all `expr` evaluate to TRUE |
|
||||
| **OR** | `OR(expr1, [expr2,...])` | `OR({Column} > 2, {Column} < 10)` | TRUE if at least one `expr` evaluates to TRUE |
|
||||
|
||||
Logical operators, along with Numerical operators can be used to build conditional `expressions`.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
IF(marksSecured > 80, "GradeA", "GradeB")
|
||||
```
|
||||
IF({marksSecured} > 80, "GradeA", "GradeB")
|
||||
```
|
||||
|
||||
```bash
|
||||
SWITCH(quarterNumber,
|
||||
```
|
||||
SWITCH({quarterNumber},
|
||||
1, 'Jan-Mar',
|
||||
2, 'Apr-Jun',
|
||||
3, 'Jul-Sep',
|
||||
4, 'Oct-Dec',
|
||||
'INVALID'
|
||||
)
|
||||
```
|
||||
|
||||
```
|
||||
14
packages/nocodb-sdk/package-lock.json
generated
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",
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.21.1",
|
||||
"jsep": "^0.4.0"
|
||||
"jsep": "^1.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ava/typescript": "^1.1.1",
|
||||
|
||||
@@ -1,6 +1,67 @@
|
||||
import jsep from 'jsep';
|
||||
|
||||
import { ColumnType } from './Api';
|
||||
|
||||
export const jsepCurlyHook = {
|
||||
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;
|
||||
env.node = {
|
||||
type: jsep.IDENTIFIER,
|
||||
name: nodes.map((node) => node.name).join(' '),
|
||||
};
|
||||
return env.node;
|
||||
} else {
|
||||
context.throwError('Unclosed }');
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
} as jsep.IPlugin;
|
||||
|
||||
export async function substituteColumnAliasWithIdInFormula(
|
||||
formula,
|
||||
columns: ColumnType[]
|
||||
) {
|
||||
const substituteId = async (pt: any) => {
|
||||
if (pt.type === 'CallExpression') {
|
||||
for (const arg of pt.arguments || []) {
|
||||
await 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') {
|
||||
await substituteId(pt.left);
|
||||
await substituteId(pt.right);
|
||||
}
|
||||
};
|
||||
// register jsep curly hook
|
||||
jsep.plugins.register(jsepCurlyHook);
|
||||
const parsedFormula = jsep(formula);
|
||||
await substituteId(parsedFormula);
|
||||
return jsepTreeToFormula(parsedFormula);
|
||||
}
|
||||
|
||||
export function substituteColumnIdWithAliasInFormula(
|
||||
formula,
|
||||
columns: ColumnType[],
|
||||
@@ -15,7 +76,7 @@ export function substituteColumnIdWithAliasInFormula(
|
||||
} else if (pt.type === 'Literal') {
|
||||
return;
|
||||
} else if (pt.type === 'Identifier') {
|
||||
const colNameOrId = pt.name;
|
||||
const colNameOrId = pt?.name;
|
||||
const column = columns.find(
|
||||
(c) =>
|
||||
c.id === colNameOrId ||
|
||||
@@ -29,6 +90,8 @@ export function substituteColumnIdWithAliasInFormula(
|
||||
}
|
||||
};
|
||||
|
||||
// register jsep curly hook
|
||||
jsep.plugins.register(jsepCurlyHook);
|
||||
const parsedFormula = jsep(formula);
|
||||
const parsedRawFormula = rawFormula && jsep(rawFormula);
|
||||
substituteId(parsedFormula, parsedRawFormula);
|
||||
@@ -62,6 +125,43 @@ export function jsepTreeToFormula(node) {
|
||||
}
|
||||
|
||||
if (node.type === 'Identifier') {
|
||||
const formulas = [
|
||||
'AVG',
|
||||
'ADD',
|
||||
'DATEADD',
|
||||
'AND',
|
||||
'OR',
|
||||
'CONCAT',
|
||||
'TRIM',
|
||||
'UPPER',
|
||||
'LOWER',
|
||||
'LEN',
|
||||
'MIN',
|
||||
'MAX',
|
||||
'CEILING',
|
||||
'FLOOR',
|
||||
'ROUND',
|
||||
'MOD',
|
||||
'REPEAT',
|
||||
'LOG',
|
||||
'EXP',
|
||||
'POWER',
|
||||
'SQRT',
|
||||
'SQRT',
|
||||
'ABS',
|
||||
'NOW',
|
||||
'REPLACE',
|
||||
'SEARCH',
|
||||
'INT',
|
||||
'RIGHT',
|
||||
'LEFT',
|
||||
'SUBSTR',
|
||||
'MID',
|
||||
'IF',
|
||||
'SWITCH',
|
||||
'URL',
|
||||
];
|
||||
if (!formulas.includes(node.name)) return '{' + node.name + '}';
|
||||
return node.name;
|
||||
}
|
||||
|
||||
|
||||
18
packages/nocodb/package-lock.json
generated
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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -7,7 +7,7 @@ import FormulaColumn from '../../../../noco-models/FormulaColumn';
|
||||
import { XKnex } from '../../..';
|
||||
import LinkToAnotherRecordColumn from '../../../../noco-models/LinkToAnotherRecordColumn';
|
||||
import LookupColumn from '../../../../noco-models/LookupColumn';
|
||||
import { UITypes } from 'nocodb-sdk';
|
||||
import { jsepCurlyHook, UITypes } from 'nocodb-sdk';
|
||||
|
||||
// todo: switch function based on database
|
||||
|
||||
@@ -51,6 +51,8 @@ export default async function formulaQueryBuilderv2(
|
||||
model: Model,
|
||||
aliasToColumn = {}
|
||||
) {
|
||||
// register jsep curly hook
|
||||
jsep.plugins.register(jsepCurlyHook);
|
||||
const tree = jsep(_tree);
|
||||
|
||||
// todo: improve - implement a common solution for filter, sort, formula, etc
|
||||
@@ -647,7 +649,11 @@ export default async function formulaQueryBuilderv2(
|
||||
return query;
|
||||
} else if (pt.type === 'UnaryExpression') {
|
||||
const query = knex.raw(
|
||||
`${pt.operator}${fn(pt.argument, null, pt.operator).toQuery()}${colAlias}`
|
||||
`${pt.operator}${fn(
|
||||
pt.argument,
|
||||
null,
|
||||
pt.operator
|
||||
).toQuery()}${colAlias}`
|
||||
);
|
||||
if (prevBinaryOp && pt.operator !== prevBinaryOp) {
|
||||
query.wrap('(', ')');
|
||||
|
||||
@@ -3,7 +3,6 @@ import Model from '../../../noco-models/Model';
|
||||
import ProjectMgrv2 from '../../../sqlMgr/v2/ProjectMgrv2';
|
||||
import Base from '../../../noco-models/Base';
|
||||
import Column from '../../../noco-models/Column';
|
||||
import { substituteColumnAliasWithIdInFormula } from '../helpers/formulaHelpers';
|
||||
import validateParams from '../helpers/validateParams';
|
||||
import { Tele } from 'nc-help';
|
||||
|
||||
@@ -19,6 +18,8 @@ import {
|
||||
isVirtualCol,
|
||||
LinkToAnotherRecordType,
|
||||
RelationTypes,
|
||||
substituteColumnAliasWithIdInFormula,
|
||||
substituteColumnIdWithAliasInFormula,
|
||||
TableType,
|
||||
UITypes
|
||||
} from 'nocodb-sdk';
|
||||
@@ -32,6 +33,8 @@ import getColumnPropsFromUIDT from '../helpers/getColumnPropsFromUIDT';
|
||||
import mapDefaultPrimaryValue from '../helpers/mapDefaultPrimaryValue';
|
||||
import NcConnectionMgrv2 from '../../common/NcConnectionMgrv2';
|
||||
import { metaApiMetrics } from '../helpers/apiMetrics';
|
||||
import FormulaColumn from '../../../noco-models/FormulaColumn';
|
||||
import { MetaTable } from '../../../utils/globals';
|
||||
|
||||
const randomID = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 10);
|
||||
|
||||
@@ -493,9 +496,12 @@ export async function columnAdd(req: Request, res: Response<TableType>) {
|
||||
}> = (await sqlClient.columnList({ tn: table.table_name }))?.data?.list;
|
||||
|
||||
const insertedColumnMeta =
|
||||
columns.find(c => c.cn === colBody.column_name) || {} as any;
|
||||
columns.find(c => c.cn === colBody.column_name) || ({} as any);
|
||||
|
||||
if (colBody.uidt === UITypes.SingleSelect || colBody.uidt === UITypes.MultiSelect) {
|
||||
if (
|
||||
colBody.uidt === UITypes.SingleSelect ||
|
||||
colBody.uidt === UITypes.MultiSelect
|
||||
) {
|
||||
insertedColumnMeta.dtxp = colBody.dtxp;
|
||||
}
|
||||
|
||||
@@ -610,20 +616,47 @@ export async function columnUpdate(req: Request, res: Response<TableType>) {
|
||||
cn: c.column_name,
|
||||
cno: c.column_name
|
||||
})),
|
||||
columns: table.columns.map(c => {
|
||||
if (c.id === req.params.columnId) {
|
||||
return {
|
||||
...c,
|
||||
...colBody,
|
||||
cn: colBody.column_name,
|
||||
cno: c.column_name,
|
||||
altered: Altered.UPDATE_COLUMN
|
||||
};
|
||||
} else {
|
||||
(c as any).cn = c.column_name;
|
||||
}
|
||||
return c;
|
||||
})
|
||||
columns: await Promise.all(
|
||||
table.columns.map(async c => {
|
||||
if (c.id === req.params.columnId) {
|
||||
const res = {
|
||||
...c,
|
||||
...colBody,
|
||||
cn: colBody.column_name,
|
||||
cno: c.column_name,
|
||||
altered: Altered.UPDATE_COLUMN
|
||||
};
|
||||
|
||||
// update formula with new column name
|
||||
if (c.column_name != colBody.column_name) {
|
||||
const formulas = await Noco.ncMeta
|
||||
.knex(MetaTable.COL_FORMULA)
|
||||
.where('formula', 'like', `%${c.id}%`);
|
||||
if (formulas) {
|
||||
const new_column = c;
|
||||
new_column.column_name = colBody.column_name;
|
||||
new_column.title = colBody.title;
|
||||
for (const f of formulas) {
|
||||
// the formula with column IDs only
|
||||
const formula = f.formula;
|
||||
// replace column IDs with alias to get the new formula_raw
|
||||
const new_formula_raw = substituteColumnIdWithAliasInFormula(
|
||||
formula,
|
||||
[new_column]
|
||||
);
|
||||
await FormulaColumn.update(f.id, {
|
||||
formula_raw: new_formula_raw
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return Promise.resolve(res);
|
||||
} else {
|
||||
(c as any).cn = c.column_name;
|
||||
}
|
||||
return Promise.resolve(c);
|
||||
})
|
||||
)
|
||||
};
|
||||
|
||||
const sqlMgr = await ProjectMgrv2.getSqlMgr({ id: base.project_id });
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
import jsep from 'jsep';
|
||||
import jsepTreeToFormula from '../../common/helpers/jsepTreeToFormula';
|
||||
import Column from '../../../noco-models/Column';
|
||||
|
||||
export async function substituteColumnAliasWithIdInFormula(
|
||||
formula,
|
||||
columns: Column[]
|
||||
) {
|
||||
const substituteId = async (pt: any) => {
|
||||
if (pt.type === 'CallExpression') {
|
||||
for (const arg of pt.arguments || []) {
|
||||
await 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') {
|
||||
await substituteId(pt.left);
|
||||
await substituteId(pt.right);
|
||||
}
|
||||
};
|
||||
|
||||
const parsedFormula = jsep(formula);
|
||||
await substituteId(parsedFormula);
|
||||
return jsepTreeToFormula(parsedFormula);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -4,11 +4,15 @@ import User from '../../../noco-models/User';
|
||||
import Project from '../../../noco-models/Project';
|
||||
import ProjectUser from '../../../noco-models/ProjectUser';
|
||||
import Model from '../../../noco-models/Model';
|
||||
import { ModelTypes, UITypes, ViewTypes } from 'nocodb-sdk';
|
||||
import {
|
||||
ModelTypes,
|
||||
substituteColumnAliasWithIdInFormula,
|
||||
UITypes,
|
||||
ViewTypes
|
||||
} from 'nocodb-sdk';
|
||||
import Column from '../../../noco-models/Column';
|
||||
import LinkToAnotherRecordColumn from '../../../noco-models/LinkToAnotherRecordColumn';
|
||||
import NcHelp from '../../../utils/NcHelp';
|
||||
import { substituteColumnAliasWithIdInFormula } from '../../meta/helpers/formulaHelpers';
|
||||
import RollupColumn from '../../../noco-models/RollupColumn';
|
||||
import View from '../../../noco-models/View';
|
||||
import GridView from '../../../noco-models/GridView';
|
||||
|
||||
@@ -61,7 +61,7 @@ export const genTest = (apiType, dbType) => {
|
||||
.contains("Formula")
|
||||
.parent()
|
||||
.click()
|
||||
.type(formula)
|
||||
.type(formula, { parseSpecialCharSequences: false })
|
||||
.click();
|
||||
|
||||
// click on Save
|
||||
@@ -115,7 +115,7 @@ export const genTest = (apiType, dbType) => {
|
||||
.parent()
|
||||
.find("input")
|
||||
.clear()
|
||||
.type(newFormula)
|
||||
.type(newFormula, { parseSpecialCharSequences: false })
|
||||
.click();
|
||||
|
||||
cy.get(".nc-col-create-or-edit-card")
|
||||
@@ -189,7 +189,7 @@ export const genTest = (apiType, dbType) => {
|
||||
it("Formula: ADD, AVG, LEN", () => {
|
||||
addFormulaBasedColumn(
|
||||
"NC_MATH_0",
|
||||
"ADD(CityId, CountryId) + AVG(CityId, CountryId) + LEN(City)"
|
||||
"ADD({CityId}, {CountryId}) + AVG({CityId}, {CountryId}) + LEN({City})"
|
||||
);
|
||||
rowValidation("NC_MATH_0", RESULT_MATH_0);
|
||||
});
|
||||
@@ -198,7 +198,7 @@ export const genTest = (apiType, dbType) => {
|
||||
editColumnByName(
|
||||
"NC_MATH_0",
|
||||
"NC_STR_1",
|
||||
`CONCAT(UPPER(City), LOWER(City), TRIM(' trimmed '))`
|
||||
`CONCAT(UPPER({City}), LOWER({City}), TRIM(' trimmed '))`
|
||||
);
|
||||
rowValidation("NC_STR_1", RESULT_STRING);
|
||||
});
|
||||
@@ -207,7 +207,7 @@ export const genTest = (apiType, dbType) => {
|
||||
editColumnByName(
|
||||
"NC_STR_1",
|
||||
"NC_MATH_1",
|
||||
`CEILING(1.4) + FLOOR(1.6) + ROUND(2.5) + MOD(CityId, 3) + MIN(CityId, CountryId) + MAX(CityId, CountryId)`
|
||||
`CEILING(1.4) + FLOOR(1.6) + ROUND(2.5) + MOD({CityId}, 3) + MIN({CityId}, {CountryId}) + MAX({CityId}, {CountryId})`
|
||||
);
|
||||
rowValidation("NC_MATH_1", RESULT_MATH_1);
|
||||
});
|
||||
@@ -218,7 +218,7 @@ export const genTest = (apiType, dbType) => {
|
||||
editColumnByName(
|
||||
"NC_MATH_1",
|
||||
"NC_MATH_2",
|
||||
`LOG(CityId) + EXP(CityId) + POWER(CityId, 3) + SQRT(CountryId)`
|
||||
`LOG({CityId}) + EXP({CityId}) + POWER({CityId}, 3) + SQRT({CountryId})`
|
||||
);
|
||||
rowValidation("NC_MATH_2", RESULT_MATH_2);
|
||||
}
|
||||
@@ -237,6 +237,7 @@ export const genTest = (apiType, dbType) => {
|
||||
*
|
||||
* @author Pranav C Balan <pranavxc@gmail.com>
|
||||
* @author Raju Udava <sivadstala@gmail.com>
|
||||
* @author Wing-Kam Wong <wingkwong.code@gmail.com>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user