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 @@ > - + ({ 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) {