Browse Source

feat: Add Rollup column type

re #504

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/510/head
Pranav C 3 years ago
parent
commit
5022340dc7
  1. 64
      packages/nc-gui/components/project/spreadsheet/components/editColumn.vue
  2. 205
      packages/nc-gui/components/project/spreadsheet/components/editColumn/rollupOptions.vue
  3. 10
      packages/nc-gui/components/project/spreadsheet/components/virtualCell.vue
  4. 166
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/rollupCell.vue
  5. 45
      packages/nc-gui/components/project/spreadsheet/components/virtualHeaderCell.vue
  6. 15
      packages/nc-gui/components/project/spreadsheet/helpers/uiTypes.js
  7. 2
      packages/nc-gui/plugins/ncApis/gqlApi.js
  8. 19
      packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSql.ts
  9. 25
      packages/nocodb/src/lib/dataMapper/lib/sql/genRollupSelect.ts
  10. 6
      packages/nocodb/src/lib/sqlMgr/code/gql-schema/xc-ts/BaseGqlXcTsSchema.ts

64
packages/nc-gui/components/project/spreadsheet/components/editColumn.vue

@ -99,11 +99,11 @@
</template>
</v-autocomplete>
<!-- <v-list dense max-height="calc(100vh - 300px)" style="overflow: auto">-->
<!-- <v-list-item v-for="item in uiTypes" @click.stop :key="item">-->
<!-- <span class="caption">{{ item }}</span>-->
<!-- </v-list-item>-->
<!-- </v-list>-->
<!-- <v-list dense max-height="calc(100vh - 300px)" style="overflow: auto">-->
<!-- <v-list-item v-for="item in uiTypes" @click.stop :key="item">-->
<!-- <span class="caption">{{ item }}</span>-->
<!-- </v-list-item>-->
<!-- </v-list>-->
</v-col>
<template v-if="newColumn.uidt !== 'Formula'">
@ -122,6 +122,21 @@
v-on="$listeners"
/>
</v-col>
<v-col
v-if="isRollup"
cols="12"
>
<rollup-options
ref="rollup"
:column="newColumn"
:nodes="nodes"
:meta="meta"
:is-s-q-lite="isSQLite"
:alias="newColumn.cn"
:is-m-s-s-q-l="isMSSQL"
v-on="$listeners"
/>
</v-col>
<v-col
v-if="isLinkToAnotherRecord"
cols="12"
@ -158,7 +173,7 @@
/>
</v-col>
<template v-if="newColumn.cn && newColumn.uidt && !isLinkToAnotherRecord && !isLookup">
<template v-if="newColumn.cn && newColumn.uidt && !isVirtual">
<v-col cols="12">
<v-container fluid class="wrapper">
<v-row>
@ -342,18 +357,18 @@
v-on="$listeners"
/>
<!-- <v-autocomplete
label="Formula"
hide-details
class="caption formula-type"
outlined
dense
:items="formulas"
>
<template #item="{item}">
<span class="green&#45;&#45;text text&#45;&#45;darken-2 caption font-weight-regular">{{ item }}</span>
</template>
</v-autocomplete>-->
<!-- <v-autocomplete
label="Formula"
hide-details
class="caption formula-type"
outlined
dense
:items="formulas"
>
<template #item="{item}">
<span class="green&#45;&#45;text text&#45;&#45;darken-2 caption font-weight-regular">{{ item }}</span>
</template>
</v-autocomplete>-->
</v-col>
</template>
</template>
@ -383,6 +398,7 @@
</template>
<script>
import RollupOptions from './editColumn/rollupOptions'
import FormulaOptions from '@/components/project/spreadsheet/components/editColumn/formulaOptions'
import LookupOptions from '@/components/project/spreadsheet/components/editColumn/lookupOptions'
import { uiTypes } from '@/components/project/spreadsheet/helpers/uiTypes'
@ -395,6 +411,7 @@ import { SqliteUi, MssqlUi } from '@/helpers/sqlUi'
export default {
name: 'EditColumn',
components: {
RollupOptions,
FormulaOptions,
LookupOptions,
LinkedToAnotherOptions,
@ -416,8 +433,6 @@ export default {
relationDeleteDlg: false,
newColumn: {},
uiTypes
// dataTypes: [],
// formulas: ['AVERAGE()', 'COUNT()', 'COUNTA()', 'COUNTALL()', 'SUM()', 'MIN()', 'MAX()', 'AND()', 'OR()', 'TRUE()', 'FALSE()', 'NOT()', 'XOR()', 'ISERROR()', 'IF()', 'LEN()', 'MID()', 'LEFT()', 'RIGHT()', 'FIND()', 'CONCATENATE()', 'T()', 'VALUE()', 'ARRAYJOIN()', 'ARRAYUNIQUE()', 'ARRAYCOMPACT()', 'ARRAYFLATTEN()', 'ROUND()', 'ROUNDUP()', 'ROUNDDOWN()', 'INT()', 'EVEN()', 'ODD()', 'MOD()', 'LOG()', 'EXP()', 'POWER()', 'SQRT()', 'CEILING()', 'FLOOR()', 'ABS()', 'RECORD_ID()', 'CREATED_TIME()', 'ERROR()', 'BLANK()', 'YEAR()', 'MONTH()', 'DAY()', 'HOUR()', 'MINUTE()', 'SECOND()', 'TODAY()', 'NOW()', 'WORKDAY()', 'DATETIME_PARSE()', 'DATETIME_FORMAT()', 'SET_LOCALE()', 'SET_TIMEZONE()', 'DATESTR()', 'TIMESTR()', 'TONOW()', 'FROMNOW()', 'DATEADD()', 'WEEKDAY()', 'WEEKNUM()', 'DATETIME_DIFF()', 'WORKDAY_DIFF()', 'IS_BEFORE()', 'IS_SAME()', 'IS_AFTER()', 'REPLACE()', 'REPT()', 'LOWER()', 'UPPER()', 'TRIM()', 'SUBSTITUTE()', 'SEARCH()', 'SWITCH()', 'LAST_MODIFIED_TIME()', 'ENCODE_URL_COMPONENT()', 'REGEX_EXTRACT()', 'REGEX_MATCH()', 'REGEX_REPLACE()']
}),
computed: {
isEditDisabled() {
@ -445,8 +460,14 @@ export default {
isLookup() {
return this.newColumn && this.newColumn.uidt === 'Lookup'
},
isRollup() {
return this.newColumn && this.newColumn.uidt === 'Rollup'
},
relation() {
return this.meta && this.column && this.meta.belongsTo && this.meta.belongsTo.find(bt => bt.cn === this.column.cn)
},
isVirtual() {
return this.isLinkToAnotherRecord || this.isLookup || this.isRollup
}
},
watch: {
@ -509,6 +530,9 @@ export default {
if (this.isLookup && this.$refs.lookup) {
return await this.$refs.lookup.save()
}
if (this.isRollup && this.$refs.rollup) {
return await this.$refs.rollup.save()
}
if (this.newColumn.uidt === 'Formula' && this.$refs.formula) {
return await this.$refs.formula.save()
}

205
packages/nc-gui/components/project/spreadsheet/components/editColumn/rollupOptions.vue

@ -0,0 +1,205 @@
<template>
<div>
<v-container fluid class="wrapper">
<v-row>
<v-col cols="6">
<v-autocomplete
ref="input"
v-model="rollup.table"
outlined
class="caption"
hide-details="auto"
label="Child Table"
:full-width="false"
:items="refTables"
item-text="_rltn"
:item-value="v => v"
:rules="[v => !!v || 'Required']"
dense
>
<template #item="{item}">
<span class="caption"><span class="font-weight-bold"> {{
item._rltn
}}</span> <small>({{ relationNames[item.type] }})
</small></span>
</template>
</v-autocomplete>
</v-col>
<v-col cols="6">
<v-autocomplete
ref="input"
v-model="rollup.column"
outlined
class="caption"
hide-details="auto"
label="Child column"
:full-width="false"
:items="columnList"
item-text="_rlcn"
dense
:loading="loadingColumns"
:item-value="v => v"
:rules="[v => !!v || 'Required']"
/>
</v-col>
<v-col cols="12">
<v-autocomplete
ref="aggrInput"
v-model="rollup.fn"
outlined
class="caption"
hide-details="auto"
label="Aggregate function"
:full-width="false"
:items="aggrFunctionsList"
dense
:loading="loadingColumns"
:rules="[v => !!v || 'Required']"
/>
</v-col>
</v-row>
</v-container>
</div>
</template>
<script>
export default {
name: 'RollupOptions',
props: ['nodes', 'column', 'meta', 'isSQLite', 'alias'],
data: () => ({
rollup: {},
loadingColumns: false,
relationNames: {
mm: 'Many To Many',
hm: 'Has Many'
// bt: 'Belongs To'
},
aggrFunctionsList: [
{ text: 'count', value: 'count' },
{ text: 'min', value: 'min' },
{ text: 'max', value: 'max' },
{ text: 'avg', value: 'avg' },
{ text: 'min', value: 'min' },
{ text: 'sum', value: 'sum' },
{ text: 'countDistinct', value: 'countDistinct' },
{ text: 'sumDistinct', value: 'sumDistinct' },
{ text: 'avgDistinct', value: 'avgDistinct' }
]
}),
computed: {
refTables() {
return this.meta
? [
// ...(this.meta.belongsTo || []).map(({ rtn, _rtn, rcn, tn, cn }) => ({
// type: 'bt',
// rtn,
// _rtn,
// rcn,
// tn,
// cn,
// ltn: rtn,
// _ltn: _rtn
// })),
...(this.meta.hasMany || []).map(({
tn,
_tn,
cn,
rcn,
rtn
}) => ({
type: 'hm',
tn,
_tn,
cn,
rcn,
rtn,
rltn: tn,
_rltn: _tn
})),
...(this.meta.manyToMany || []).map(({ vtn, _vtn, vrcn, vcn, rtn, _rtn, rcn, tn, cn }) => ({
type: 'mm',
tn,
cn,
vtn,
_vtn,
vrcn,
rcn,
rtn,
vcn,
_rtn,
rltn: rtn,
_rltn: _rtn
}))
]
: []
},
columnList() {
return ((
this.rollup &&
this.rollup.table &&
this.$store.state.meta.metas &&
this.$store.state.meta.metas[this.rollup.table.rltn] &&
this.$store.state.meta.metas[this.rollup.table.rltn].columns
) || []).map(({ cn, _cn }) => ({
rlcn: cn,
_rlcn: _cn
}))
}
},
methods: {
async onTableChange() {
this.loadingColumns = true
if (this.rollup.table) {
try {
await this.$store.dispatch('meta/ActLoadMeta', {
dbAlias: this.nodes.dbAlias,
env: this.nodes.env,
tn: this.rollup.table.ltn
})
} catch (e) {
// ignore
}
}
this.loadingColumns = false
},
async save() {
try {
await this.$store.dispatch('meta/ActLoadMeta', {
dbAlias: this.nodes.dbAlias,
env: this.nodes.env,
tn: this.meta.tn,
force: true
})
const meta = JSON.parse(JSON.stringify(this.$store.state.meta.metas[this.meta.tn]))
meta.v.push({
_cn: this.alias,
rollup: {
...this.rollup.table,
...this.rollup.column,
fn: this.rollup.fn
}
})
await this.$store.dispatch('sqlMgr/ActSqlOp', [{
env: this.nodes.env,
dbAlias: this.nodes.dbAlias
}, 'xcModelSet', {
tn: this.nodes.tn,
meta
}])
return this.$emit('saved', this.alias)
} catch (e) {
this.$toast.error(e.message).goAway(3000)
}
}
}
}
</script>
<style scoped>
</style>

10
packages/nc-gui/components/project/spreadsheet/components/virtualCell.vue

@ -68,11 +68,17 @@
:row="row"
:column="column"
/>
<rollup-cell
v-else-if="rollup"
:row="row"
:column="column"
/>
</v-lazy>
</div>
</template>
<script>
import RollupCell from './virtualCell/rollupCell'
import FormulaCell from '@/components/project/spreadsheet/components/virtualCell/formulaCell'
import hasManyCell from '@/components/project/spreadsheet/components/virtualCell/hasManyCell'
import LookupCell from '@/components/project/spreadsheet/components/virtualCell/lookupCell'
@ -84,6 +90,7 @@ import belongsToCell from '@/components/project/spreadsheet/components/virtualCe
export default {
name: 'VirtualCell',
components: {
RollupCell,
FormulaCell,
LookupCell,
belongsToCell,
@ -129,6 +136,9 @@ export default {
},
formula() {
return this.column && this.column.formula
},
rollup() {
return this.column && this.column.rollup
}
},
methods: {

166
packages/nc-gui/components/project/spreadsheet/components/virtualCell/rollupCell.vue

@ -0,0 +1,166 @@
<template>
<span>
{{ row[column._cn] }}
</span>
</template>
<script>
export default {
name: 'RollupCell',
components: { },
props: {
meta: [Object],
column: [Object],
nodes: [Object],
row: [Object]
},
data: () => ({
lookupListModal: false
}),
computed: {
// todo : optimize
lookupApi() {
return this.column && this.$ncApis.get({
env: this.nodes.env,
dbAlias: this.nodes.dbAlias,
table: this.column.lk.ltn
})
},
lookUpMeta() {
return this.$store.state.meta.metas[this.column.lk.ltn]
},
assocMeta() {
return this.column.lk.type === 'mm' && this.$store.state.meta.metas[this.column.lk.vtn]
},
lookUpColumnAlias() {
if (!this.lookUpMeta || !this.column.lk.lcn) {
return
}
return (this.$store.state.meta.metas[this.column.lk.ltn].columns.find(cl => cl.cn === this.column.lk.lcn) || {})._cn
},
lookUpColumn() {
if (!this.lookUpMeta || !this.column.lk.lcn) {
return
}
return (this.$store.state.meta.metas[this.column.lk.ltn].columns.find(cl => cl.cn === this.column.lk.lcn) || {})
},
localValueObj() {
if (!this.column || !this.row) {
return null
}
switch (this.column.lk.type) {
case 'mm':
return this.row[`${this.column.lk._ltn}MMList`]
case 'hm':
return this.row[`${this.column.lk._ltn}List`]
case 'bt':
return this.row[`${this.column.lk._ltn}Read`]
default:
return null
}
},
localValue() {
if (!this.localValueObj || !this.lookUpColumnAlias) {
return null
}
if (Array.isArray(this.localValueObj)) {
return this.localValueObj.map(o => o[this.lookUpColumnAlias])
}
return [this.localValueObj[this.lookUpColumnAlias]]
},
queryParams() {
switch (this.column.lk.type) {
case 'bt':
return { where: `(${this.lookUpMeta.columns.find(c => c.cn === this.column.lk.rcn)._cn},eq,${this.row[this.meta.columns.find(c => c.cn === this.column.lk.cn)._cn]})` }
case 'hm':
return { where: `(${this.lookUpMeta.columns.find(c => c.cn === this.column.lk.cn)._cn},eq,${this.row[this.meta.columns.find(c => c.cn === this.column.lk.rcn)._cn]})` }
case 'mm':
return this.assocMeta
? {
conditionGraph: {
[this.assocMeta.tn]: {
relationType: 'hm',
[this.assocMeta.columns.find(c => c.cn === this.column.lk.vcn).cn]: {
eq: this.row[this.meta.columns.find(c => c.cn === this.column.lk.cn)._cn]
}
}
}
}
: {}
default:
return {}
}
}
},
created() {
this.loadLookupMeta()
},
methods: {
async loadLookupMeta() {
if (!this.lookUpMeta) {
await this.$store.dispatch('meta/ActLoadMeta', {
env: this.nodes.env,
dbAlias: this.nodes.dbAlias,
tn: this.column.lk.ltn
})
}
if (this.column.lk.type === 'mm' && !this.assocMeta) {
await this.$store.dispatch('meta/ActLoadMeta', {
env: this.nodes.env,
dbAlias: this.nodes.dbAlias,
tn: this.column.lk.vtn
})
}
},
showLookupListModal() {
this.lookupListModal = true
}
}
}
</script>
<style scoped lang="scss">
.chips-wrapper {
.chips {
max-width: 100%;
&.lookup-items {
flex-wrap: wrap;
row-gap: 3px;
gap: 3px;
margin: 3px 0;
}
}
&.active {
.chips {
max-width: calc(100% - 44px);
}
}
}
</style>
<!--
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd
*
* @author Naveen MR <oof1lab@gmail.com>
* @author Pranav C Balan <pranavxc@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
-->

45
packages/nc-gui/components/project/spreadsheet/components/virtualHeaderCell.vue

@ -25,6 +25,17 @@
mdi-table-column-plus-before
</v-icon>
</template>
<template v-else-if="column.rollup">
<v-icon v-if="column.rollup.type === 'hm'" color="warning" x-small class="mr-1" v-on="on">
{{ rollupIcon }}
</v-icon>
<v-icon v-else-if="column.rollup.type === 'bt'" color="info" x-small class="mr-1" v-on="on">
{{ rollupIcon }}
</v-icon>
<v-icon v-else-if="column.rollup.type === 'mm'" color="pink" x-small class="mr-1" v-on="on">
{{ rollupIcon }}
</v-icon>
</template>
<span class="name flex-grow-1" :title="column._cn" v-on="on" v-html="alias">
<span v-if="column.rqd" class="error--text text--lighten-1" v-on="on">&nbsp;*</span>
@ -116,6 +127,7 @@
</div>
</template>
<script>
import { getUIDTIcon } from '../helpers/uiTypes'
import EditVirtualColumn from '@/components/project/spreadsheet/components/editVirtualColumn'
export default {
@ -124,7 +136,8 @@ export default {
props: ['column', 'nodes', 'meta', 'isForm', 'isPublicView', 'sqlUi'],
data: () => ({
columnDeleteDialog: false,
editColumnMenu: false
editColumnMenu: false,
rollupIcon: getUIDTIcon('Rollup')
}),
computed: {
alias() {
@ -204,6 +217,8 @@ export default {
return `'${this.column.lk._lcn}' from '${this.column.lk._ltn}' (${this.column.lk.type})`
} else if (this.column.formula) {
return `Formula - ${this.column.formula.value}`
} else if (this.column.rollup) {
return `${this.column.rollup.fn} of ${this.column.rollup._rlcn} (${this.column.rollup._rltn})`
}
return ''
}
@ -282,11 +297,39 @@ export default {
console.log(e)
}
},
async deleteRollupColumn() {
try {
await this.$store.dispatch('meta/ActLoadMeta', {
dbAlias: this.nodes.dbAlias,
env: this.nodes.env,
tn: this.meta.tn,
force: true
})
const meta = JSON.parse(JSON.stringify(this.$store.state.meta.metas[this.meta.tn]))
// remove rollup from virtual columns
meta.v = meta.v.filter(cl => !cl.rollup || cl._cn !== this.column._cn)
await this.$store.dispatch('sqlMgr/ActSqlOp', [{
env: this.nodes.env,
dbAlias: this.nodes.dbAlias
}, 'xcModelSet', {
tn: this.nodes.tn,
meta
}])
this.$emit('saved')
this.columnDeleteDialog = false
} catch (e) {
console.log(e)
}
},
async deleteColumn() {
if (this.column.lk) {
await this.deleteLookupColumn()
} else if (this.column.formula) {
await this.deleteFormulaColumn()
} else if (this.column.rollup) {
await this.deleteRollupColumn()
} else {
await this.deleteRelation()
}

15
packages/nc-gui/components/project/spreadsheet/helpers/uiTypes.js

@ -1,4 +1,3 @@
const uiTypes = [
{
name: 'ID',
@ -96,10 +95,10 @@ const uiTypes = [
name: 'Formula',
icon: 'mdi-math-integral'
},
// {
// name: 'Rollup',
// icon: 'mdi-movie-roll',
// },
{
name: 'Rollup',
icon: 'mdi-movie-roll'
},
{
name: 'Count',
icon: 'mdi-counter'
@ -146,8 +145,12 @@ const uiTypes = [
// },
]
const getUIDTIcon = (uidt) => {
return (uiTypes.find(t => t.name === uidt) || {}).icon
}
export {
uiTypes
uiTypes, getUIDTIcon
}
export default [
'ID',

2
packages/nc-gui/plugins/ncApis/gqlApi.js

@ -135,7 +135,7 @@ export default class GqlApi {
}
// add formula columns to query
str += this.meta.v.reduce((arr, v) => {
if (v.formula) {
if (v.formula || v.rollup) {
arr.push(v._cn)
}
return arr

19
packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSql.ts

@ -4,6 +4,7 @@ import Validator from 'validator';
import BaseModel, {XcFilter, XcFilterWithAlias} from '../BaseModel';
import formulaQueryBuilder from "./formulaQueryBuilderFromString";
import genRollupSelect from "./genRollupSelect";
/**
@ -726,7 +727,8 @@ class BaseModelSql extends BaseModel {
// .select(...fields.split(','))
.select(this.selectQuery(fields))
.select(...this.selectFormulas)
.xwhere(where, {...this.selectQuery(''),...this.selectFormulasObj})
.select(...this.selectRollups)
.xwhere(where, {...this.selectQuery(''), ...this.selectFormulasObj})
.xhaving(having, this.selectQuery(''))
.condition(condition, this.selectQuery(''))
.conditionGraph(conditionGraph);
@ -838,7 +840,7 @@ class BaseModelSql extends BaseModel {
return await this._run(this.$db
.conditionGraph(conditionGraph)
.count(`${this.tn}.${(this.pks[0] || this.columns[0]).cn} as count`)
.xwhere(where, {...this.selectQuery(''),...this.selectFormulasObj})
.xwhere(where, {...this.selectQuery(''), ...this.selectFormulasObj})
.xhaving(having, this.selectQuery(''))
.first());
} catch (e) {
@ -1898,9 +1900,22 @@ class BaseModelSql extends BaseModel {
return this._selectFormulasObj;
}
// todo: optimize
protected get selectRollups() {
return (this.virtualColumns || [])?.reduce((arr, v) => {
if (v.rollup) {
arr.push(
genRollupSelect({knex: this.dbDriver, rollup: v.rollup}).as(v._cn)
);
}
return arr;
}, [])
}
}
export {BaseModelSql};
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd

25
packages/nocodb/src/lib/dataMapper/lib/sql/genRollupSelect.ts

@ -0,0 +1,25 @@
import Knex from "knex";
export default function ({knex, rollup,}: { knex: Knex, rollup: any }) {
switch (rollup.type) {
case 'hm':
return knex(rollup.rltn)
[rollup.fn]?.(knex.ref(`${rollup.rltn}.${rollup.rlcn}`))
.where(
knex.ref(`${rollup.tn}.${rollup.cn}`), '=', knex.ref(`${rollup.rtn}.${rollup.rcn}`)
)
break;
case 'mm':
return knex(rollup.rltn)
[rollup.fn]?.(knex.ref(`${rollup.rltn}.${rollup.rlcn}`))
.innerJoin(rollup.vtn, knex.ref(`${rollup.vtn}.${rollup.vrcn}`), '=', knex.ref(`${rollup.rtn}.${rollup.rcn}`))
.where(knex.ref(`${rollup.vtn}.${rollup.vcn}`), '=', knex.ref(`${rollup.tn}.${rollup.cn}`))
default:
throw Error(`Unsupported relation type '${rollup.type}'`)
}
}

6
packages/nocodb/src/lib/sqlMgr/code/gql-schema/xc-ts/BaseGqlXcTsSchema.ts

@ -76,13 +76,13 @@ abstract class BaseGqlXcTsSchema extends BaseRender {
return str;
}
protected generateFormulaTypes(args: any): string {
protected generateVirtualTypes(args: any): string {
if (!args.v?.length) {
return '';
}
const props = [];
for (const v of args.v) {
if (!v.formula) continue
if (!v.formula && !v.rollup) continue
props.push(`\t\t${v._cn}: JSON`)
}
return props.length ? `\r\n${props.join('\r\n')}\r\n` : '';
@ -162,7 +162,7 @@ abstract class BaseGqlXcTsSchema extends BaseRender {
str += this.generateManyToManyTypeProps(args);
str += this.generateFormulaTypes(args);
str += this.generateVirtualTypes(args);
let belongsToRelations = args.belongsTo;
if (belongsToRelations.length > 1) {

Loading…
Cancel
Save