Browse Source

feat: GQL - M2M

Signed-off-by: Pranav C <61551451+pranavxc@users.noreply.github.com>
pull/341/head
Pranav C 3 years ago
parent
commit
3add4bb2d3
  1. 1
      packages/nc-gui/components/project/spreadsheet/apis/gqlApi.js
  2. 19
      packages/nc-gui/components/project/spreadsheet/components/editColumn/linkedToAnotherOptions.vue
  3. 6
      packages/nc-gui/components/project/spreadsheet/components/expandedForm.vue
  4. 9
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/belogsToCell.vue
  5. 12
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/hasManyCell.vue
  6. 8
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/manyToManyCell.vue
  7. 14
      packages/nocodb/src/lib/noco/common/BaseApiBuilder.ts
  8. 19
      packages/nocodb/src/lib/noco/gql/GqlApiBuilder.ts
  9. 18
      packages/nocodb/src/lib/noco/meta/NcMetaMgr.ts
  10. 13
      packages/nocodb/src/lib/noco/rest/RestApiBuilder.ts
  11. 13
      packages/nocodb/src/lib/sqlMgr/code/gql-schema/xc-ts/GqlXcTsSchemaMssql.ts

1
packages/nc-gui/components/project/spreadsheet/apis/gqlApi.js

@ -90,7 +90,6 @@ export default class GqlApi {
} }
async gqlRelationReqBody(params) { async gqlRelationReqBody(params) {
let str = ''; let str = '';
if (params.childs) { if (params.childs) {
for (const child of params.childs.split(',')) { for (const child of params.childs.split(',')) {

19
packages/nc-gui/components/project/spreadsheet/components/editColumn/linkedToAnotherOptions.vue

@ -14,11 +14,12 @@
<v-container fluid class="wrapper"> <v-container fluid class="wrapper">
<v-row> <v-row>
<v-col cols="6"> <v-col cols="12">
<v-autocomplete <v-autocomplete
validate-on-blur
outlined outlined
class="caption" class="caption"
hide-details hide-details="auto"
:loading="isRefTablesLoading" :loading="isRefTablesLoading"
label="Child Table" label="Child Table"
:full-width="false" :full-width="false"
@ -28,10 +29,11 @@
item-value="tn" item-value="tn"
required required
dense dense
:rules="tableRules"
></v-autocomplete> ></v-autocomplete>
</v-col </v-col
> >
<v-col cols="6"> <!-- <v-col cols="6">
<v-text-field <v-text-field
outlined outlined
class="caption" class="caption"
@ -45,7 +47,7 @@
@change="onColumnSelect" @change="onColumnSelect"
></v-text-field> ></v-text-field>
</v-col </v-col
> >-->
</v-row> </v-row>
<template v-if="!isSQLite"> <template v-if="!isSQLite">
<v-row> <v-row>
@ -132,6 +134,15 @@ export default {
type: 'real' type: 'real'
} }
}, },
computed: {
tableRules() {
return []
// this.meta ? [
// v => this.type !== 'mm' || !this.meta.manyToMany.some(mm => mm.tn === v && mm.rtn === this.meta.tn || mm.rtn === v && mm.tn === this.meta.tn) || 'Duplicate relation is not allowed at the moment',
// v => this.type !== 'hm' || !this.meta.hasMany.some(hm => hm.tn === v) || 'Duplicate relation is not allowed at the moment'
// ] : []
}
},
methods: { methods: {
async loadColumnList() { async loadColumnList() {
this.isRefColumnsLoading = true; this.isRefColumnsLoading = true;

6
packages/nc-gui/components/project/spreadsheet/components/expandedForm.vue

@ -1,5 +1,6 @@
<template> <template>
<v-card width="1000" max-width="100%"> <v-card width="1000" max-width="100%">
<v-toolbar height="55" class="elevation-1"> <v-toolbar height="55" class="elevation-1">
<div class="d-100 d-flex "> <div class="d-100 d-flex ">
<h5 class="title text-center"> <h5 class="title text-center">
@ -308,6 +309,7 @@ export default {
obj[col] = this.localState[col]; obj[col] = this.localState[col];
return obj; return obj;
}, {}); }, {});
if (this.isNew) { if (this.isNew) {
const data = await this.api.insert(updatedObj); const data = await this.api.insert(updatedObj);
Object.assign(this.localState, data) Object.assign(this.localState, data)
@ -379,7 +381,9 @@ export default {
if (this.showSystemFields) { if (this.showSystemFields) {
return this.meta.columns || []; return this.meta.columns || [];
} else { } else {
return (this.meta.columns.filter(c => !(c.pk && c.ai) && !hideCols.includes(c.cn))) || []; return this.meta.columns.filter(c => !(c.pk && c.ai) && !hideCols.includes(c.cn)
&& !((this.meta.v || []).some(v => v.bt && v.bt.cn === c.cn))
) || [];
} }
}, },
isChanged() { isChanged() {

9
packages/nc-gui/components/project/spreadsheet/components/virtualCell/belogsToCell.vue

@ -221,9 +221,10 @@ export default {
const pid = this.parentMeta.columns.filter((c) => c.pk).map(c => parent[c._cn]).join('___'); const pid = this.parentMeta.columns.filter((c) => c.pk).map(c => parent[c._cn]).join('___');
const id = this.meta.columns.filter((c) => c.pk).map(c => this.row[c._cn]).join('___'); const id = this.meta.columns.filter((c) => c.pk).map(c => this.row[c._cn]).join('___');
const _cn = this.meta.columns.find(c => c.cn === this.bt.cn)._cn; const _cn = this.meta.columns.find(c => c.cn === this.bt.cn)._cn;
if (this.isNew) { if (this.isNew) {
this.localState = parent; this.localState = parent;
this.$emit('updateCol', this.row, _cn, pid) this.$emit('updateCol', this.row, _cn, +pid || pid)
this.newRecordModal = false; this.newRecordModal = false;
return return
} }
@ -272,9 +273,9 @@ export default {
parentQueryParams() { parentQueryParams() {
if (!this.parentMeta) return {} if (!this.parentMeta) return {}
return { return {
childs: (this.parentMeta && this.parentMeta.hasMany && this.parentMeta.hasMany.map(hm => hm.tn).join()) || '', childs: (this.parentMeta && this.parentMeta.v && this.parentMeta.v.filter(v => v.hm).map(({hm}) => hm.tn).join()) || '',
parents: (this.parentMeta && this.parentMeta.belongsTo && this.parentMeta.belongsTo.map(hm => hm.rtn).join()) || '', parents: (this.parentMeta && this.parentMeta.v && this.parentMeta.v.filter(v => v.bt).map(({bt}) => bt.rtn).join()) || '',
many: (this.parentMeta && this.parentMeta.manyToMany && this.parentMeta.manyToMany.map(mm => mm.rtn).join()) || '' many: (this.parentMeta && this.parentMeta.v && this.parentMeta.v.filter(v => v.mm).map(({mm}) => mm.rtn).join()) || ''
} }
}, },
parentAvailableColumns() { parentAvailableColumns() {

12
packages/nc-gui/components/project/spreadsheet/components/virtualCell/hasManyCell.vue

@ -242,7 +242,7 @@ export default {
this.newRecordModal = false; this.newRecordModal = false;
await this.childApi.update(id, { await this.childApi.update(id, {
[_cn]: +this.parentId [_cn]: +this.parentId || this.parentId
}, { }, {
[_cn]: child[this.childForeignKey] [_cn]: child[this.childForeignKey]
}); });
@ -266,7 +266,7 @@ export default {
await this.loadChildMeta(); await this.loadChildMeta();
this.isNewChild = true; this.isNewChild = true;
this.selectedChild = { this.selectedChild = {
[this.childForeignKey]: this.parentId [this.childForeignKey]: +this.parentId || this.parentId
}; };
this.expandFormModal = true; this.expandFormModal = true;
setTimeout(() => { setTimeout(() => {
@ -316,7 +316,7 @@ export default {
const columns = []; const columns = [];
if (this.childMeta.columns) { if (this.childMeta.columns) {
columns.push(...this.childMeta.columns.filter(c => !(c.pk && c.ai) && !hideCols.includes(c.cn))) columns.push(...this.childMeta.columns.filter(c => !(c.pk && c.ai) && !hideCols.includes(c.cn) && !((this.childMeta.v || []).some(v => v.bt && v.bt.cn === c.cn))))
} }
if (this.childMeta.v) { if (this.childMeta.v) {
columns.push(...this.childMeta.v.map(v => ({...v, virtual: 1}))); columns.push(...this.childMeta.v.map(v => ({...v, virtual: 1})));
@ -326,9 +326,9 @@ export default {
childQueryParams() { childQueryParams() {
if (!this.childMeta) return {} if (!this.childMeta) return {}
return { return {
childs: (this.childMeta && this.childMeta.hasMany && this.childMeta.hasMany.map(hm => hm.tn).join()) || '', childs: (this.childMeta && this.childMeta.v && this.childMeta.v.filter(v => v.hm).map(({hm}) => hm.tn).join()) || '',
parents: (this.childMeta && this.childMeta.belongsTo && this.childMeta.belongsTo.map(hm => hm.rtn).join()) || '', parents: (this.childMeta && this.childMeta.v && this.childMeta.v.filter(v => v.bt).map(({bt}) => bt.rtn).join()) || '',
many: (this.childMeta && this.childMeta.manyToMany && this.childMeta.manyToMany.map(mm => mm.rtn).join()) || '' many: (this.childMeta && this.childMeta.v && this.childMeta.v.filter(v => v.mm).map(({mm}) => mm.rtn).join()) || ''
} }
}, },
parentId() { parentId() {

8
packages/nc-gui/components/project/spreadsheet/components/virtualCell/manyToManyCell.vue

@ -12,7 +12,7 @@
@unlink="unlinkChild" @unlink="unlinkChild"
></item-chip> ></item-chip>
</template> <v-chip v-if="value && value.length === 10" class="caption pointer ml-1 grey--text" @click="showChildListModal">more...</v-chip> </template> <span v-if="value && value.length === 10" class="caption pointer ml-1 grey--text" @click="showChildListModal">more...</span>
</div> </div>
<div class="actions align-center justify-center px-1 flex-shrink-1" <div class="actions align-center justify-center px-1 flex-shrink-1"
:class="{'d-none': !active, 'd-flex':active }"> :class="{'d-none': !active, 'd-flex':active }">
@ -360,9 +360,9 @@ export default {
childQueryParams() { childQueryParams() {
if (!this.childMeta) return {} if (!this.childMeta) return {}
return { return {
childs: (this.childMeta && this.childMeta.hasMany && this.childMeta.hasMany.map(hm => hm.tn).join()) || '', childs: (this.childMeta && this.childMeta.v && this.childMeta.v.filter(v => v.hm).map(({hm}) => hm.tn).join()) || '',
parents: (this.childMeta && this.childMeta.belongsTo && this.childMeta.belongsTo.map(hm => hm.rtn).join()) || '', parents: (this.childMeta && this.childMeta.v && this.childMeta.v.filter(v=>v.bt).map(({bt}) => bt.rtn).join()) || '',
many: (this.childMeta && this.childMeta.manyToMany && this.childMeta.manyToMany.map(mm => mm.rtn).join()) || '' many: (this.childMeta && this.childMeta.v && this.childMeta.v.filter(v=>v.mm).map(({mm}) => mm.rtn).join()) || ''
} }
}, },
conditionGraph() { conditionGraph() {

14
packages/nocodb/src/lib/noco/common/BaseApiBuilder.ts

@ -914,20 +914,22 @@ export default abstract class BaseApiBuilder<T extends Noco> implements XcDynami
} }
protected generateContextForTable(tn: string, columns: any[], relations, hasMany: any[], belongsTo: any[], type = 'table', table_name_alias?: string): any { protected generateContextForTable(tn: string, columns: any[], relations, hasMany: any[], belongsTo: any[], type = 'table', tableNameAlias?: string): any {
this.baseLog(`generateContextForTable : '%s' %s`, tn, type); this.baseLog(`generateContextForTable : '%s' %s`, tn, type);
for (const col of columns) { for (const col of columns) {
col._cn = this.getColumnNameAlias(col); col._cn = col._cn || this.getColumnNameAlias(col);
} }
const tableNameAlias = table_name_alias || this.getTableNameAlias(tn); // tslint:disable-next-line:variable-name
const _tn = tableNameAlias || this.getTableNameAlias(tn)
const ctx = { const ctx = {
dbType: this.connectionConfig.client, dbType: this.connectionConfig.client,
tn, tn,
_tn: tableNameAlias, _tn,
tn_camelize: inflection.camelize(tableNameAlias), tn_camelize: inflection.camelize(_tn),
tn_camelize_low: inflection.camelize(tableNameAlias, true), tn_camelize_low: inflection.camelize(_tn, true),
columns, columns,
relations, relations,
hasMany, hasMany,

19
packages/nocodb/src/lib/noco/gql/GqlApiBuilder.ts

@ -525,7 +525,7 @@ export class GqlApiBuilder extends BaseApiBuilder<Noco> implements XcMetaMgr {
}>; }>;
type?: 'table' | 'view', type?: 'table' | 'view',
columns?: { columns?: {
[tn: string]: any [key: string]: any
} }
}): Promise<any> { }): Promise<any> {
this.log('xcTablesPopulate : names - %o , type - %s', args?.tableNames, args?.type) this.log('xcTablesPopulate : names - %o , type - %s', args?.tableNames, args?.type)
@ -535,8 +535,8 @@ export class GqlApiBuilder extends BaseApiBuilder<Noco> implements XcMetaMgr {
// set table name alias // set table name alias
relations.forEach(r => { relations.forEach(r => {
r._rtn = this.getTableNameAlias(r.rtn); r._rtn = args?.tableNames?.find(t => t.tn === r.rtn)?._tn || this.getTableNameAlias(r.rtn);
r._tn = this.getTableNameAlias(r.tn); r._tn = args?.tableNames?.find(t => t.tn === r.tn)?._tn || this.getTableNameAlias(r.tn);
r.enabled = true; r.enabled = true;
}) })
@ -778,7 +778,6 @@ export class GqlApiBuilder extends BaseApiBuilder<Noco> implements XcMetaMgr {
await this.getManyToManyRelations(); await this.getManyToManyRelations();
// generate schema of models // generate schema of models
for (const meta of Object.values(this.metas)) { for (const meta of Object.values(this.metas)) {
/**************** prepare GQL: schemas, types, resolvers ****************/ /**************** prepare GQL: schemas, types, resolvers ****************/
@ -1131,7 +1130,7 @@ export class GqlApiBuilder extends BaseApiBuilder<Noco> implements XcMetaMgr {
/* update parent table meta and resolvers */ /* update parent table meta and resolvers */
{ {
const columns = await this.getColumnList(tnp); const columns = this.metas[tnp]?.columns;
const hasMany = this.extractHasManyRelationsOfTable(relations, tnp); const hasMany = this.extractHasManyRelationsOfTable(relations, tnp);
const belongsTo = this.extractBelongsToRelationsOfTable(relations, tnp); const belongsTo = this.extractBelongsToRelationsOfTable(relations, tnp);
const ctx = this.generateContextForTable(tnp, columns, relations, hasMany, belongsTo); const ctx = this.generateContextForTable(tnp, columns, relations, hasMany, belongsTo);
@ -1175,7 +1174,8 @@ export class GqlApiBuilder extends BaseApiBuilder<Noco> implements XcMetaMgr {
await this.xcMeta.metaUpdate(this.projectId, this.dbAlias, 'nc_models', { await this.xcMeta.metaUpdate(this.projectId, this.dbAlias, 'nc_models', {
title: tnp, title: tnp,
meta: JSON.stringify(oldMeta), meta: JSON.stringify(oldMeta),
schema: this.schemas[tnp] schema: this.schemas[tnp],
...(queryParams ? {query_params: JSON.stringify(queryParams)} : {})
}, {'title': tnp}) }, {'title': tnp})
} }
@ -1259,7 +1259,7 @@ export class GqlApiBuilder extends BaseApiBuilder<Noco> implements XcMetaMgr {
/* update child table meta and resolvers */ /* update child table meta and resolvers */
{ {
const columns = await this.getColumnList(tnc); const columns = this.metas[tnc]?.columns;
const belongsTo = this.extractBelongsToRelationsOfTable(relations, tnc); const belongsTo = this.extractBelongsToRelationsOfTable(relations, tnc);
const hasMany = this.extractHasManyRelationsOfTable(relations, tnc); const hasMany = this.extractHasManyRelationsOfTable(relations, tnc);
const ctx = this.generateContextForTable(tnc, columns, relations, hasMany, belongsTo); const ctx = this.generateContextForTable(tnc, columns, relations, hasMany, belongsTo);
@ -1299,7 +1299,8 @@ export class GqlApiBuilder extends BaseApiBuilder<Noco> implements XcMetaMgr {
await this.xcMeta.metaUpdate(this.projectId, this.dbAlias, 'nc_models', { await this.xcMeta.metaUpdate(this.projectId, this.dbAlias, 'nc_models', {
title: tnc, title: tnc,
meta: JSON.stringify(oldMeta), meta: JSON.stringify(oldMeta),
schema: this.schemas[tnc] schema: this.schemas[tnc],
...(queryParams ? {query_params: JSON.stringify(queryParams)} : {})
}, {'title': tnc}) }, {'title': tnc})
} }
@ -1831,8 +1832,6 @@ export class GqlApiBuilder extends BaseApiBuilder<Noco> implements XcMetaMgr {
title: tn title: tn
}) })
} }
{ {
const listPropName = `${this.metas[child]._tn}MMList`; const listPropName = `${this.metas[child]._tn}MMList`;
this.log(`onRelationCreate : Generating and inserting '%s' loaders`, listPropName); this.log(`onRelationCreate : Generating and inserting '%s' loaders`, listPropName);

18
packages/nocodb/src/lib/noco/meta/NcMetaMgr.ts

@ -2308,8 +2308,8 @@ export default class NcMetaMgr {
const associateTableCols = []; const associateTableCols = [];
associateTableCols.push({ associateTableCols.push({
cn: `${childMeta.tn}_id`, cn: `${childMeta.tn}_c_id`,
_cn: `${childMeta.tn}_id`, _cn: `${childMeta._tn}CId`,
rqd: true, rqd: true,
pk: true, pk: true,
ai: false, ai: false,
@ -2320,8 +2320,8 @@ export default class NcMetaMgr {
un: childPK.un, un: childPK.un,
altered: 1 altered: 1
}, { }, {
cn: `${parentMeta.tn}_id`, cn: `${parentMeta.tn}_p_id`,
_cn: `${parentMeta.tn}_id`, _cn: `${parentMeta._tn}PId`,
rqd: true, rqd: true,
pk: true, pk: true,
ai: false, ai: false,
@ -2333,13 +2333,15 @@ export default class NcMetaMgr {
altered: 1 altered: 1
}); });
// todo: associative table naming
const aTn = `${this.projectConfigs[projectId]?.prefix ?? ''}_nc_m2m_${parentMeta.tn}_${childMeta.tn}_${Math.floor(Math.random() * 1000)}`; const aTn = `${this.projectConfigs[projectId]?.prefix ?? ''}_nc_m2m_${parentMeta.tn}_${childMeta.tn}_${Math.floor(Math.random() * 1000)}`;
const aTnAlias = `m2m${parentMeta._tn}_${childMeta._tn}`;
const out = await this.projectMgr.getSqlMgr({id: projectId}).handleRequest('tableCreate', { const out = await this.projectMgr.getSqlMgr({id: projectId}).handleRequest('tableCreate', {
...args, ...args,
args: { args: {
tn: aTn, tn: aTn,
_tn: aTn, _tn: aTnAlias,
columns: associateTableCols columns: associateTableCols
} }
}); });
@ -2350,7 +2352,7 @@ export default class NcMetaMgr {
...args, ...args,
args: { args: {
tn: aTn, tn: aTn,
_tn: aTn, _tn: aTnAlias,
columns: associateTableCols columns: associateTableCols
}, api: 'tableCreate' }, api: 'tableCreate'
}, },
@ -2366,7 +2368,7 @@ export default class NcMetaMgr {
const rel1Args = { const rel1Args = {
...args.args, ...args.args,
childTable: aTn, childTable: aTn,
childColumn: `${parentMeta.tn}_id`, childColumn: `${parentMeta.tn}_p_id`,
parentTable: parentMeta.tn, parentTable: parentMeta.tn,
parentColumn: parentPK.cn, parentColumn: parentPK.cn,
type: 'real' type: 'real'
@ -2374,7 +2376,7 @@ export default class NcMetaMgr {
const rel2Args = { const rel2Args = {
...args.args, ...args.args,
childTable: aTn, childTable: aTn,
childColumn: `${childMeta.tn}_id`, childColumn: `${childMeta.tn}_c_id`,
parentTable: childMeta.tn, parentTable: childMeta.tn,
parentColumn: childPK.cn, parentColumn: childPK.cn,
type: 'real' type: 'real'

13
packages/nocodb/src/lib/noco/rest/RestApiBuilder.ts

@ -284,6 +284,15 @@ export class RestApiBuilder extends BaseApiBuilder<Noco> {
/* Get all relations */ /* Get all relations */
const relations = await this.relationsSyncAndGet(); const relations = await this.relationsSyncAndGet();
// set table name alias
relations.forEach(r => {
r._rtn = args?.tableNames?.find(t => t.tn === r.rtn)?._tn || this.getTableNameAlias(r.rtn);
r._tn = args?.tableNames?.find(t => t.tn === r.tn)?._tn || this.getTableNameAlias(r.tn);
r.enabled = true;
})
this.relationsCount = relations.length; this.relationsCount = relations.length;
if (args?.tableNames?.length) { if (args?.tableNames?.length) {
@ -984,7 +993,7 @@ export class RestApiBuilder extends BaseApiBuilder<Noco> {
const relations = await this.getXcRelationList(); const relations = await this.getXcRelationList();
{ {
const swaggerArr = []; const swaggerArr = [];
const columns = await this.getColumnList(tnp); const columns = this.metas[tnp]?.columns;
const hasMany = this.extractHasManyRelationsOfTable(relations, tnp); const hasMany = this.extractHasManyRelationsOfTable(relations, tnp);
// set table name alias // set table name alias
@ -1080,7 +1089,7 @@ export class RestApiBuilder extends BaseApiBuilder<Noco> {
} }
{ {
const swaggerArr = []; const swaggerArr = [];
const columns = await this.getColumnList(tnp); const columns = this.metas[tnc]?.columns;
const belongsTo = this.extractBelongsToRelationsOfTable(relations, tnc); const belongsTo = this.extractBelongsToRelationsOfTable(relations, tnc);
const ctx = this.generateContextForTable(tnc, columns, relations, [], belongsTo); const ctx = this.generateContextForTable(tnc, columns, relations, [], belongsTo);
const meta = ModelXcMetaFactory.create(this.connectionConfig, this.generateRendererArgs(ctx)).getObject(); const meta = ModelXcMetaFactory.create(this.connectionConfig, this.generateRendererArgs(ctx)).getObject();

13
packages/nocodb/src/lib/sqlMgr/code/gql-schema/xc-ts/GqlXcTsSchemaMssql.ts

@ -1,6 +1,3 @@
import inflection from "inflection";
import lodash from "lodash";
import {AGG_DEFAULT_COLS, GROUPBY_DEFAULT_COLS} from "./schemaHelp";
import BaseGqlXcTsSchema from "./BaseGqlXcTsSchema"; import BaseGqlXcTsSchema from "./BaseGqlXcTsSchema";
@ -19,15 +16,16 @@ class GqlXcTsSchemaMssql extends BaseGqlXcTsSchema {
super({dir, filename, ctx}); super({dir, filename, ctx});
} }
/*
/** /!**
* *
* @param args * @param args
* @param args.columns * @param args.columns
* @param args.relations * @param args.relations
* @returns {string} * @returns {string}
* @private * @private
*/ *!/
_renderColumns(args) { _renderColumns(args) {
let str = ''; let str = '';
@ -168,6 +166,7 @@ class GqlXcTsSchemaMssql extends BaseGqlXcTsSchema {
return `${str}\r\n\r\n${strWhere}`; return `${str}\r\n\r\n${strWhere}`;
} }
*/
_getGraphqlType(columnObj): any { _getGraphqlType(columnObj): any {
@ -249,9 +248,9 @@ class GqlXcTsSchemaMssql extends BaseGqlXcTsSchema {
} }
getString() { /* getString() {
return this._renderColumns(this.ctx); return this._renderColumns(this.ctx);
} }*/
} }

Loading…
Cancel
Save