From dbd97542c0f5e33ef4517c4741bb17050f624516 Mon Sep 17 00:00:00 2001
From: Pranav C <61551451+pranavxc@users.noreply.github.com>
Date: Thu, 5 Aug 2021 18:18:33 +0530
Subject: [PATCH] feat: Formula GQL support, replace column name with alias
Signed-off-by: Pranav C <61551451+pranavxc@users.noreply.github.com>
---
.../components/editColumn/formulaOptions.vue | 77 ++++++++++---------
.../project/spreadsheet/rowsXcDataTable.vue | 6 +-
packages/nc-gui/layouts/default.vue | 1 +
packages/nc-gui/plugins/ncApis/gqlApi.js | 10 +--
.../lib/sql/formulaQueryBuilderFromString.ts | 8 +-
.../nocodb/src/lib/noco/common/BaseModel.ts | 10 +--
.../nocodb/src/lib/noco/gql/GqlApiBuilder.ts | 70 +++++++++++++----
.../gql-schema/xc-ts/BaseGqlXcTsSchema.ts | 17 +++-
8 files changed, 127 insertions(+), 72 deletions(-)
diff --git a/packages/nc-gui/components/project/spreadsheet/components/editColumn/formulaOptions.vue b/packages/nc-gui/components/project/spreadsheet/components/editColumn/formulaOptions.vue
index b317989a6e..e7887620a7 100644
--- a/packages/nc-gui/components/project/spreadsheet/components/editColumn/formulaOptions.vue
+++ b/packages/nc-gui/components/project/spreadsheet/components/editColumn/formulaOptions.vue
@@ -9,31 +9,30 @@
>
-
-
-
-
- Example: AVG(column1, column2, column3)
-
+
+
+
+
+
+
-
+
({ text: fn, type: 'function' })),
- ...this.meta.columns.map(c => ({ text: c.cn, type: 'column', c })),
+ ...this.meta.columns.map(c => ({ text: c._cn, type: 'column', c })),
...this.availableBinOps.map(op => ({ text: op, type: 'op' }))
]
},
@@ -177,7 +177,7 @@ export default {
}
pt.arguments.map(arg => this.validateAgainstMeta(arg, arr))
} else if (pt.type === 'Identifier') {
- if (this.meta.columns.every(c => c.cn !== pt.name)) {
+ if (this.meta.columns.every(c => c._cn !== pt.name)) {
arr.push(`Column with name '${pt.name}' is not available`)
}
} else if (pt.type === 'BinaryExpression') {
@@ -236,24 +236,25 @@ export default {
},
suggestionListDown() {
this.selected = ++this.selected % this.suggestion.length
+ this.scrollToSelectedOption()
},
suggestionListUp() {
this.selected = --this.selected > -1 ? this.selected : this.suggestion.length - 1
+ this.scrollToSelectedOption()
+ },
+ scrollToSelectedOption() {
+ this.$nextTick(() => {
+ if (this.$refs.sugOptions[this.selected]) {
+ try {
+ this.$refs.sugList.$el.scrollTo({ top: this.$refs.sugOptions[this.selected].$el.offsetTop, behavior: 'smooth' })
+ } catch (e) {
+ }
+ }
+ })
}
}
}
diff --git a/packages/nc-gui/components/project/spreadsheet/rowsXcDataTable.vue b/packages/nc-gui/components/project/spreadsheet/rowsXcDataTable.vue
index 666872f088..355d69479c 100644
--- a/packages/nc-gui/components/project/spreadsheet/rowsXcDataTable.vue
+++ b/packages/nc-gui/components/project/spreadsheet/rowsXcDataTable.vue
@@ -803,9 +803,13 @@ export default {
}
const id = this.meta.columns.filter(c => c.pk).map(c => rowObj[c._cn]).join('___')
- await this.api.update(id, {
+
+ const newData = await this.api.update(id, {
[column._cn]: rowObj[column._cn]
}, { [column._cn]: oldRow[column._cn] })
+
+ this.$set(this.data[row], 'row', { ...rowObj, ...newData })
+
this.$set(oldRow, column._cn, rowObj[column._cn])
this.$toast.success(`${rowObj[this.primaryValueColumn] ? `${rowObj[this.primaryValueColumn]}'s c` : 'C'}olumn '${column.cn}' updated successfully.`, {
position: 'bottom-center'
diff --git a/packages/nc-gui/layouts/default.vue b/packages/nc-gui/layouts/default.vue
index 997e63b2ac..4e7c10c711 100644
--- a/packages/nc-gui/layouts/default.vue
+++ b/packages/nc-gui/layouts/default.vue
@@ -675,6 +675,7 @@ export default {
toggleTreeviewWindow: 'windows/MutToggleTreeviewWindow'
}),
async loadProjectInfo() {
+ debugger
if (this.$route.params.project_id) {
try {
const { info } = (await this.$axios.get(`/nc/${this.$route.params.project_id}/projectApiInfo`, {
diff --git a/packages/nc-gui/plugins/ncApis/gqlApi.js b/packages/nc-gui/plugins/ncApis/gqlApi.js
index e9c4cd9b36..591b12c997 100644
--- a/packages/nc-gui/plugins/ncApis/gqlApi.js
+++ b/packages/nc-gui/plugins/ncApis/gqlApi.js
@@ -92,7 +92,7 @@ export default class GqlApi {
}
// todo: query only visible columns
- async gqlRelationReqBody(params) {
+ async gqlRelationReqBody(params = {}) {
let str = ''
if (params.hm) {
for (const child of params.hm.split(',')) {
@@ -173,10 +173,10 @@ export default class GqlApi {
return { list, count }
}
- async update(id, data, oldData) {
+ async update(id, data, oldData, params = {}) {
const data1 = await this.post(`/nc/${this.$ctx.projectId}/v1/graphql`, {
query: `mutation update($id:String!, $data:${this.tableCamelized}Input){
- ${this.gqlMutationUpdateName}(id: $id, data: $data)
+ ${this.gqlMutationUpdateName}(id: $id, data: $data){${this.gqlReqBody}${await this.gqlRelationReqBody(params)}}
}`,
variables: {
id, data
@@ -195,10 +195,10 @@ export default class GqlApi {
return data1.data.data[this.gqlMutationUpdateName]
}
- async insert(data) {
+ async insert(data, params = {}) {
const data1 = await this.post(`/nc/${this.$ctx.projectId}/v1/graphql`, {
query: `mutation create($data:${this.tableCamelized}Input){
- ${this.gqlMutationCreateName}(data: $data){${this.gqlReqBody}}
+ ${this.gqlMutationCreateName}(data: $data){${this.gqlReqBody}${await this.gqlRelationReqBody(params)}}
}`,
variables: {
data
diff --git a/packages/nocodb/src/lib/dataMapper/lib/sql/formulaQueryBuilderFromString.ts b/packages/nocodb/src/lib/dataMapper/lib/sql/formulaQueryBuilderFromString.ts
index 1231d84f1e..7ee15c8a48 100644
--- a/packages/nocodb/src/lib/dataMapper/lib/sql/formulaQueryBuilderFromString.ts
+++ b/packages/nocodb/src/lib/dataMapper/lib/sql/formulaQueryBuilderFromString.ts
@@ -4,13 +4,11 @@ import jsep from 'jsep';
// todo: switch function based on database
export function formulaQueryBuilderFromString(str, alias, knex) {
- return formulaQueryBuilder(jsep(str), alias,knex)
+ return formulaQueryBuilder(jsep(str), alias, knex)
}
-
-
-export default function formulaQueryBuilder(tree, alias, knex) {
+export default function formulaQueryBuilder(tree, alias, knex, aliasToColumn = {}) {
const fn = (pt, a?, prevBinaryOp ?) => {
const colAlias = a ? ` as ${a}` : '';
if (pt.type === 'CallExpression') {
@@ -61,7 +59,7 @@ export default function formulaQueryBuilder(tree, alias, knex) {
} else if (pt.type === 'Literal') {
return knex.raw(`?${colAlias}`, [pt.value]);
} else if (pt.type === 'Identifier') {
- return knex.raw(`??${colAlias}`, [pt.name]);
+ return knex.raw(`??${colAlias}`, [aliasToColumn[pt.name] || pt.name]);
} else if (pt.type === 'BinaryExpression') {
const query = knex.raw(`${fn(pt.left, null, pt.operator).toQuery()} ${pt.operator} ${fn(pt.right, null, pt.operator).toQuery()}${colAlias}`)
if (prevBinaryOp && pt.operator !== prevBinaryOp) {
diff --git a/packages/nocodb/src/lib/noco/common/BaseModel.ts b/packages/nocodb/src/lib/noco/common/BaseModel.ts
index ef5c9c77f1..b4e2120c19 100644
--- a/packages/nocodb/src/lib/noco/common/BaseModel.ts
+++ b/packages/nocodb/src/lib/noco/common/BaseModel.ts
@@ -76,8 +76,8 @@ class BaseModel> extends BaseModelSql {
await this.handleHooks('after.delete', data, req)
}
- private async handleHooks(hookName, _data, req): Promise {
- let data = _data;
+ private async handleHooks(hookName, data, req): Promise {
+ // const data = _data;
try {
@@ -86,13 +86,13 @@ class BaseModel> extends BaseModelSql {
&& this.builder.hooks[this.tn][hookName]
) {
- if (hookName === 'after.update') {
+/* if (hookName === 'after.update') {
try {
data = await this.nestedRead(req.params.id, this.defaultNestedQueryParams)
} catch (_) {
- /* ignore */
+ /!* ignore *!/
}
- }
+ }*/
for (const hook of this.builder.hooks[this.tn][hookName]) {
diff --git a/packages/nocodb/src/lib/noco/gql/GqlApiBuilder.ts b/packages/nocodb/src/lib/noco/gql/GqlApiBuilder.ts
index 37b9ccaec1..6bb92f5396 100644
--- a/packages/nocodb/src/lib/noco/gql/GqlApiBuilder.ts
+++ b/packages/nocodb/src/lib/noco/gql/GqlApiBuilder.ts
@@ -1788,7 +1788,7 @@ export class GqlApiBuilder extends BaseApiBuilder implements XcMetaMgr {
const schemaStr = mergeTypeDefs([
...Object.values(this.schemas).filter(Boolean),
` ${this.customResolver?.schema || ''} \n ${commonSchema}`,
- ...this.typesWithFormulaProps
+ // ...this.typesWithFormulaProps
], {
commentDescriptions: true,
forceSchemaDefinition: true,
@@ -1976,27 +1976,65 @@ export class GqlApiBuilder extends BaseApiBuilder implements XcMetaMgr {
}
- // todo: dump it in db
- // extending types for formula column
- private get typesWithFormulaProps(): string[] {
- const schemas = [];
+ /* // todo: dump it in db
+ // extending types for formula column
+ private get typesWithFormulaProps(): string[] {
+ const schemas = [];
- for (const meta of Object.values(this.metas)) {
- const props = [];
- for (const v of meta.v) {
- if (!v.formula) continue
- props.push(`${v._cn}: JSON`)
- }
- if (props.length) {
- schemas.push(`type ${meta._tn} {\n${props.join('\n')}\n}`)
+ for (const meta of Object.values(this.metas)) {
+ const props = [];
+ for (const v of meta.v) {
+ if (!v.formula) continue
+ props.push(`${v._cn}: JSON`)
+ }
+ if (props.length) {
+ schemas.push(`type ${meta._tn} {\n${props.join('\n')}\n}`)
+ }
}
- }
- return schemas;
- }
+ return schemas;
+ }*/
async onMetaUpdate(tn: string): Promise {
await super.onMetaUpdate(tn);
+ const meta = this.metas[tn];
+
+ const ctx = this.generateContextForTable(tn, meta.columns,
+ [...meta.belongsTo, meta.hasMany],
+ meta.hasMany,
+ meta.belongsTo
+ )
+
+ const oldSchema = this.schemas[tn];
+ // this.log(`onTableUpdate : Populating new schema for '%s' table`, changeObj.tn);
+ // meta.schema =
+ this.schemas[tn] = GqlXcSchemaFactory.create(this.connectionConfig, this.generateRendererArgs({
+ ...meta,
+ ...ctx
+ })).getString();
+ if (oldSchema !== this.schemas[tn]) {
+ // this.log(`onTableUpdate : Updating and taking backup of schema - '%s' table`, tn);
+
+ const oldModel = await this.xcMeta.metaGet(this.projectId, this.dbAlias, 'nc_models', {
+ title: tn
+ });
+
+ // keep upto 5 schema backup on table update
+ let previousSchemas = [oldSchema]
+ if (oldModel.schema_previous) {
+ previousSchemas = [...JSON.parse(oldModel.schema_previous), oldSchema].slice(-5);
+ }
+
+ await this.xcMeta.metaUpdate(this.projectId, this.dbAlias, 'nc_models', {
+ schema: meta.schema,
+ schema_previous: JSON.stringify(previousSchemas)
+ }, {
+ title: tn
+ });
+
+ }
+
+
return this.reInitializeGraphqlEndpoint();
}
}
diff --git a/packages/nocodb/src/lib/sqlMgr/code/gql-schema/xc-ts/BaseGqlXcTsSchema.ts b/packages/nocodb/src/lib/sqlMgr/code/gql-schema/xc-ts/BaseGqlXcTsSchema.ts
index 249ad69c47..57985ba85e 100644
--- a/packages/nocodb/src/lib/sqlMgr/code/gql-schema/xc-ts/BaseGqlXcTsSchema.ts
+++ b/packages/nocodb/src/lib/sqlMgr/code/gql-schema/xc-ts/BaseGqlXcTsSchema.ts
@@ -76,6 +76,18 @@ abstract class BaseGqlXcTsSchema extends BaseRender {
return str;
}
+ protected generateFormulaTypes(args: any): string {
+ if (!args.v?.length) {
+ return '';
+ }
+ const props = [];
+ for (const v of args.v) {
+ if (!v.formula) continue
+ props.push(`\t\t${v._cn}: JSON`)
+ }
+ return props.length ? `\r\n${props.join('\r\n')}\r\n` : '';
+ }
+
protected _getInputType(args): string {
let str = `input ${args._tn}Input { \r\n`
for (const column of args.columns) {
@@ -108,7 +120,7 @@ abstract class BaseGqlXcTsSchema extends BaseRender {
protected _getMutation(args): string {
let str = `type Mutation { \r\n`
str += `\t\t${args._tn}Create(data:${args._tn}Input): ${args._tn}\r\n`
- str += `\t\t${args._tn}Update(id:String,data:${args._tn}Input): Int\r\n` // ${args._tn}\r\n`
+ str += `\t\t${args._tn}Update(id:String,data:${args._tn}Input): ${args._tn}\r\n` // ${args._tn}\r\n`
str += `\t\t${args._tn}Delete(id:String): Int\r\n`// ${args._tn}\r\n`
str += `\t\t${args._tn}CreateBulk(data: [${args._tn}Input]): [Int]\r\n`
str += `\t\t${args._tn}UpdateBulk(data: [${args._tn}Input]): [Int]\r\n`
@@ -127,7 +139,7 @@ abstract class BaseGqlXcTsSchema extends BaseRender {
console.log(`Skipping ${args.tn}.${column._cn}`);
} else {
str += `\t\t${column._cn.replace(/ /g, '_')}: ${this._getGraphqlType(column)},\r\n`;
- strWhere += `\t\t${column._cn .replace(/ /g, '_')}: ${this._getGraphqlConditionType(column)},\r\n`;
+ strWhere += `\t\t${column._cn.replace(/ /g, '_')}: ${this._getGraphqlConditionType(column)},\r\n`;
}
}
@@ -150,6 +162,7 @@ abstract class BaseGqlXcTsSchema extends BaseRender {
str += this.generateManyToManyTypeProps(args);
+ str += this.generateFormulaTypes(args);
let belongsToRelations = args.belongsTo;
if (belongsToRelations.length > 1) {