Browse Source

feat: Lookup implementation

Signed-off-by: Pranav C <61551451+pranavxc@users.noreply.github.com>
pull/401/head
Pranav C 3 years ago
parent
commit
d3354d2aae
  1. 32
      packages/nc-gui/components/project/spreadsheet/components/editColumn.vue
  2. 121
      packages/nc-gui/components/project/spreadsheet/components/editColumn/lookupOptions.vue
  3. 19
      packages/nc-gui/components/project/spreadsheet/components/virtualCell.vue
  4. 7
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/components/itemChip.vue
  5. 128
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/lookupCell.vue
  6. 108
      packages/nc-gui/components/project/spreadsheet/components/virtualHeaderCell.vue
  7. 4
      packages/nc-gui/components/project/spreadsheet/helpers/uiTypes.js
  8. 108
      packages/nc-gui/components/project/spreadsheet/mixins/spreadsheet.js
  9. 19
      packages/nc-gui/components/project/spreadsheet/rowsXcDataTable.vue
  10. 4
      packages/nc-gui/components/project/spreadsheet/views/xcGridView.vue
  11. 23
      packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSql.ts
  12. 4
      packages/nocodb/src/lib/noco/NcProjectBuilder.ts
  13. 58
      packages/nocodb/src/lib/noco/common/BaseApiBuilder.ts
  14. 7
      packages/nocodb/src/lib/noco/meta/NcMetaMgr.ts

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

@ -104,6 +104,20 @@
</v-col> </v-col>
<template v-if="newColumn.uidt !== 'Formula'"> <template v-if="newColumn.uidt !== 'Formula'">
<v-col
v-if="isLookup"
cols="12"
>
<lookup-options
ref="lookup"
:column="newColumn"
:nodes="nodes"
:meta="meta"
:is-s-q-lite="isSQLite"
:alias="newColumn.cn"
:is-m-s-s-q-l="isMSSQL"
/>
</v-col>
<v-col <v-col
v-if="isLinkToAnotherRecord" v-if="isLinkToAnotherRecord"
cols="12" cols="12"
@ -140,7 +154,7 @@
/> />
</v-col> </v-col>
<template v-if="newColumn.cn && newColumn.uidt && !isLinkToAnotherRecord"> <template v-if="newColumn.cn && newColumn.uidt && !isLinkToAnotherRecord && !isLookup">
<v-col cols="12"> <v-col cols="12">
<v-container fluid class="wrapper"> <v-container fluid class="wrapper">
<v-row> <v-row>
@ -352,6 +366,7 @@
</template> </template>
<script> <script>
import LookupOptions from '@/components/project/spreadsheet/components/editColumn/lookupOptions'
import { uiTypes } from '@/components/project/spreadsheet/helpers/uiTypes' import { uiTypes } from '@/components/project/spreadsheet/helpers/uiTypes'
import CustomSelectOptions from '@/components/project/spreadsheet/components/editColumn/customSelectOptions' import CustomSelectOptions from '@/components/project/spreadsheet/components/editColumn/customSelectOptions'
import RelationOptions from '@/components/project/spreadsheet/components/editColumn/relationOptions' import RelationOptions from '@/components/project/spreadsheet/components/editColumn/relationOptions'
@ -362,7 +377,7 @@ import { MssqlUi } from '@/helpers/MssqlUi'
export default { export default {
name: 'EditColumn', name: 'EditColumn',
components: { LinkedToAnotherOptions, DlgLabelSubmitCancel, RelationOptions, CustomSelectOptions }, components: { LookupOptions, LinkedToAnotherOptions, DlgLabelSubmitCancel, RelationOptions, CustomSelectOptions },
props: { props: {
nodes: Object, nodes: Object,
sqlUi: [Object, Function], sqlUi: [Object, Function],
@ -403,6 +418,9 @@ export default {
isLinkToAnotherRecord() { isLinkToAnotherRecord() {
return this.newColumn && this.newColumn.uidt === 'LinkToAnotherRecord' return this.newColumn && this.newColumn.uidt === 'LinkToAnotherRecord'
}, },
isLookup() {
return this.newColumn && this.newColumn.uidt === 'Lookup'
},
relation() { relation() {
return this.meta && this.column && this.meta.belongsTo && this.meta.belongsTo.find(bt => bt.cn === this.column.cn) return this.meta && this.column && this.meta.belongsTo && this.meta.belongsTo.find(bt => bt.cn === this.column.cn)
} }
@ -429,7 +447,7 @@ export default {
}) })
}, },
genColumnData() { genColumnData() {
this.newColumn = this.column ? { ...this.column } : this.sqlUi.getNewColumn(this.meta.columns.length + 1) this.newColumn = this.column ? { ...this.column } : this.sqlUi.getNewColumn([...this.meta.columns, ...(this.meta.v || [])].length + 1)
this.newColumn.cno = this.newColumn.cn this.newColumn.cno = this.newColumn.cn
}, },
/* /*
@ -464,6 +482,10 @@ export default {
await this.$refs.relation.saveRelation() await this.$refs.relation.saveRelation()
return this.$emit('saved') return this.$emit('saved')
} }
if (this.isLookup && this.$refs.lookup) {
await this.$refs.lookup.save()
return this.$emit('saved', this.newColumn.cn)
}
this.newColumn.tn = this.nodes.tn this.newColumn.tn = this.nodes.tn
this.newColumn._cn = this.newColumn.cn this.newColumn._cn = this.newColumn.cn
@ -584,6 +606,10 @@ export default {
<style scoped lang="scss"> <style scoped lang="scss">
::v-deep { ::v-deep {
.wrapper {
border: solid 2px #7f828b33;
border-radius: 4px;
}
.v-input__slot { .v-input__slot {
min-height: auto !important; min-height: auto !important;

121
packages/nc-gui/components/project/spreadsheet/components/editColumn/lookupOptions.vue

@ -0,0 +1,121 @@
<template>
<div>
<v-container fluid class="wrapper">
<v-row>
<v-col cols="6">
<v-autocomplete
ref="input"
v-model="lookup.table"
outlined
class="caption"
hide-details="auto"
label="Child Table"
:full-width="false"
:items="refTables"
item-text="_tn"
:item-value="v => v"
:rules="[v => !!v || 'Required']"
dense
/>
</v-col>
<v-col cols="6">
<v-autocomplete
ref="input"
v-model="lookup.column"
outlined
class="caption"
hide-details="auto"
label="Child column"
:full-width="false"
:items="columnList"
item-text="_cn"
dense
:loading="loadingColumns"
:item-value="v => v"
:rules="[v => !!v || 'Required']"
/>
</v-col>
</v-row>
</v-container>
</div>
</template>
<script>
export default {
name: 'LookupOptions',
props: ['nodes', 'column', 'meta', 'isSQLite', 'alias'],
data: () => ({
lookup: {},
loadingColumns: false
}),
computed: {
refTables() {
return this.meta
? [
...(this.meta.belongsTo || []).map(bt => ({ type: 'bt', relation: bt, tn: bt.rtn, _tn: bt._rtn })),
...(this.meta.hasMany || []).map(hm => ({ type: 'hm', relation: hm, tn: hm.tn, _tn: hm._tn })),
...(this.meta.manyToMany || []).map(mm => ({ type: 'mm', relation: mm, tn: mm.rtn, _tn: mm._rtn }))
]
: []
},
columnList() {
return ((
this.lookup &&
this.lookup.table &&
this.$store.state.meta.metas &&
this.$store.state.meta.metas[this.lookup.table.tn] &&
this.$store.state.meta.metas[this.lookup.table.tn].columns
) || []).map(({ cn, _cn }) => ({ cn, _cn }))
}
},
methods: {
async onTableChange() {
this.loadingColumns = true
if (this.lookup.table) {
try {
await this.$store.dispatch('meta/ActLoadMeta', {
dbAlias: this.nodes.dbAlias,
env: this.nodes.env,
tn: this.lookup.table.tn
})
} 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,
lookup: true,
...this.lookup.table,
cn: this.lookup.column.cn
})
await this.$store.dispatch('sqlMgr/ActSqlOp', [{
env: this.nodes.env,
dbAlias: this.nodes.dbAlias
}, 'xcModelSet', {
tn: this.nodes.tn,
meta
}])
} catch (e) {
this.$toast.error(e.message).goAway(3000)
}
}
}
}
</script>
<style scoped>
</style>

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

@ -49,12 +49,27 @@
:breadcrumbs="breadcrumbs" :breadcrumbs="breadcrumbs"
v-on="$listeners" v-on="$listeners"
/> />
<lookup-cell
v-else-if="lookup"
:disabled-columns="disabledColumns"
:active="active"
:row="row"
:meta="meta"
:nodes="nodes"
:api="api"
:sql-ui="sqlUi"
:is-new="isNew"
:is-form="isForm"
:column="column"
v-on="$listeners "
/>
</v-lazy> </v-lazy>
</div> </div>
</template> </template>
<script> <script>
import hasManyCell from '@/components/project/spreadsheet/components/virtualCell/hasManyCell' import hasManyCell from '@/components/project/spreadsheet/components/virtualCell/hasManyCell'
import LookupCell from '@/components/project/spreadsheet/components/virtualCell/lookupCell'
import manyToManyCell from '@/components/project/spreadsheet/components/virtualCell/manyToManyCell' import manyToManyCell from '@/components/project/spreadsheet/components/virtualCell/manyToManyCell'
import belongsToCell from '@/components/project/spreadsheet/components/virtualCell/belogsToCell' import belongsToCell from '@/components/project/spreadsheet/components/virtualCell/belogsToCell'
@ -63,6 +78,7 @@ import belongsToCell from '@/components/project/spreadsheet/components/virtualCe
export default { export default {
name: 'VirtualCell', name: 'VirtualCell',
components: { components: {
LookupCell,
belongsToCell, belongsToCell,
manyToManyCell, manyToManyCell,
hasManyCell hasManyCell
@ -100,6 +116,9 @@ export default {
}, },
mm() { mm() {
return this.column && this.column.mm return this.column && this.column.mm
},
lookup() {
return this.column && this.column.lookup
} }
}, },
methods: { methods: {

7
packages/nc-gui/components/project/spreadsheet/components/virtualCell/components/itemChip.vue

@ -5,10 +5,10 @@
small small
text-color="textColor" text-color="textColor"
:color="isDark ? '' : 'primary lighten-5'" :color="isDark ? '' : 'primary lighten-5'"
@click="active && $emit('edit',item)" @click="!readonly && active && $emit('edit',item)"
> >
<span class="name" :title="value">{{ value }}</span> <span class="name" :title="value">{{ value }}</span>
<div v-show="active" class="mr-n1 ml-2"> <div v-show="active" v-if="!readonly" class="mr-n1 ml-2">
<x-icon <x-icon
:color="['text' , 'textLight']" :color="['text' , 'textLight']"
x-small x-small
@ -27,7 +27,8 @@ export default {
props: { props: {
value: [String, Number, Boolean], value: [String, Number, Boolean],
active: Boolean, active: Boolean,
item: Object item: Object,
readonly: Boolean
} }
} }
</script> </script>

128
packages/nc-gui/components/project/spreadsheet/components/virtualCell/lookupCell.vue

@ -0,0 +1,128 @@
<template>
<div class="chips-wrapper">
<div class="chips d-flex align-center lookup-items">
<template v-if="localValue">
<item-chip
v-for="(value,i) in localValue"
:key="i"
:active="active"
:value="value"
:readonly="true"
/>
</template>
</div>
</div>
</template>
<script>
import ItemChip from '@/components/project/spreadsheet/components/virtualCell/components/itemChip'
export default {
name: 'LookupCell',
components: { ItemChip },
props: {
meta: [Object],
column: [Object],
nodes: [Object],
row: [Object],
api: [Object, Function],
sqlUi: [Object, Function],
active: Boolean,
isNew: Boolean,
isForm: Boolean
},
computed: {
lookUpMeta() {
return this.$store.state.meta.metas[this.column.tn]
},
lookUpColumnAlias() {
if (!this.lookUpMeta || !this.column.cn) {
return
}
return (this.$store.state.meta.metas[this.column.tn].columns.find(cl => cl.cn === this.column.cn) || {})._cn
},
localValueObj() {
if (!this.column || !this.row) {
return null
}
switch (this.column.type) {
case 'mm':
return this.row[`${this.column._tn}MMList`]
case 'hm':
return this.row[`${this.column._tn}List`]
case 'bt':
return this.row[`${this.column._tn}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]]
}
},
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.tn
})
}
}
}
}
</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/>.
*
*/
-->

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

@ -11,6 +11,9 @@
<v-icon v-else-if="column.mm" color="pink" x-small class="mr-1" v-on="on"> <v-icon v-else-if="column.mm" color="pink" x-small class="mr-1" v-on="on">
mdi-table-network mdi-table-network
</v-icon> </v-icon>
<v-icon v-else-if="column.lookup" color="pink" x-small class="mr-1" v-on="on">
mdi-table-network
</v-icon>
<span class="name flex-grow-1" :title="column._cn" v-on="on">{{ column._cn }}</span> <span class="name flex-grow-1" :title="column._cn" v-on="on">{{ column._cn }}</span>
@ -27,7 +30,7 @@
</v-icon> </v-icon>
</template> </template>
<v-list dense> <v-list dense>
<v-list-item dense @click="editColumnMenu = true"> <v-list-item v-if="!column.lookup" dense @click="editColumnMenu = true">
<x-icon small class="mr-1" color="primary"> <x-icon small class="mr-1" color="primary">
mdi-pencil mdi-pencil
</x-icon> </x-icon>
@ -108,37 +111,69 @@ export default {
}), }),
computed: { computed: {
type() { type() {
if (this.column.bt) { return 'bt' } if (this.column.bt) {
if (this.column.hm) { return 'hm' } return 'bt'
if (this.column.mm) { return 'mm' } }
if (this.column.hm) {
return 'hm'
}
if (this.column.mm) {
return 'mm'
}
return '' return ''
}, },
childColumn() { childColumn() {
if (this.column.bt) { return this.column.bt.cn } if (this.column.bt) {
if (this.column.hm) { return this.column.hm.cn } return this.column.bt.cn
if (this.column.mm) { return this.column.mm.rcn } }
if (this.column.hm) {
return this.column.hm.cn
}
if (this.column.mm) {
return this.column.mm.rcn
}
return '' return ''
}, },
childTable() { childTable() {
if (this.column.bt) { return this.column.bt.tn } if (this.column.bt) {
if (this.column.hm) { return this.column.hm.tn } return this.column.bt.tn
if (this.column.mm) { return this.column.mm.rtn } }
if (this.column.hm) {
return this.column.hm.tn
}
if (this.column.mm) {
return this.column.mm.rtn
}
return '' return ''
}, },
parentTable() { parentTable() {
if (this.column.bt) { return this.column.bt.rtn } if (this.column.bt) {
if (this.column.hm) { return this.column.hm.rtn } return this.column.bt.rtn
if (this.column.mm) { return this.column.mm.tn } }
if (this.column.hm) {
return this.column.hm.rtn
}
if (this.column.mm) {
return this.column.mm.tn
}
return '' return ''
}, },
parentColumn() { parentColumn() {
if (this.column.bt) { return this.column.bt.rcn } if (this.column.bt) {
if (this.column.hm) { return this.column.hm.rcn } return this.column.bt.rcn
if (this.column.mm) { return this.column.mm.cn } }
if (this.column.hm) {
return this.column.hm.rcn
}
if (this.column.mm) {
return this.column.mm.cn
}
return '' return ''
}, },
tooltipMsg() { tooltipMsg() {
if (!this.column) { return '' } if (!this.column) {
return ''
}
if (this.column.hm) { if (this.column.hm) {
return `'${this.column.hm._rtn}' has many '${this.column.hm._tn}'` return `'${this.column.hm._rtn}' has many '${this.column.hm._tn}'`
} else if (this.column.mm) { } else if (this.column.mm) {
@ -150,9 +185,8 @@ export default {
} }
}, },
methods: { methods: {
async deleteColumn() { async deleteRelation() {
try { try {
// const column = { ...this.column, cno: this.column.cn }
await this.$store.dispatch('sqlMgr/ActSqlOpPlus', [{ await this.$store.dispatch('sqlMgr/ActSqlOpPlus', [{
env: this.nodes.env, env: this.nodes.env,
dbAlias: this.nodes.dbAlias dbAlias: this.nodes.dbAlias
@ -169,6 +203,42 @@ export default {
} catch (e) { } catch (e) {
console.log(e) console.log(e)
} }
},
async deleteLookupColumn() {
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 lookup from virtual columns
meta.v = meta.v.filter(cl => cl.cn !== this.column.cn ||
cl.type !== this.column.type ||
cl._cn !== this.column._cn ||
cl.tn !== this.column.tn)
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.lookup) {
await this.deleteLookupColumn()
} else {
await this.deleteRelation()
}
} }
} }
} }

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

@ -12,6 +12,10 @@ const uiTypes = [
name: 'ForeignKey', name: 'ForeignKey',
icon: 'mdi-link-variant' icon: 'mdi-link-variant'
}, },
{
name: 'Lookup',
icon: 'mdi-link-variant'
},
{ {
name: 'SingleLineText', name: 'SingleLineText',
icon: 'mdi-format-color-text' icon: 'mdi-format-color-text'

108
packages/nc-gui/components/project/spreadsheet/mixins/spreadsheet.js

@ -23,23 +23,33 @@ export default {
// not implemented // not implemented
}, },
onKeyDown(e) { onKeyDown(e) {
if (this.selected.col === null || this.selected.row === null) { return } if (this.selected.col === null || this.selected.row === null) {
return
}
switch (e.keyCode) { switch (e.keyCode) {
// left // left
case 37: case 37:
if (this.selected.col > 0) { this.selected.col-- } if (this.selected.col > 0) {
this.selected.col--
}
break break
// right // right
case 39: case 39:
if (this.selected.col < this.colLength - 1) { this.selected.col++ } if (this.selected.col < this.colLength - 1) {
this.selected.col++
}
break break
// up // up
case 38: case 38:
if (this.selected.row > 0) { this.selected.row-- } if (this.selected.row > 0) {
this.selected.row--
}
break break
// down // down
case 40: case 40:
if (this.selected.row < this.rowLength - 1) { this.selected.row++ } if (this.selected.row < this.rowLength - 1) {
this.selected.row++
}
break break
// enter // enter
case 13: case 13:
@ -111,6 +121,59 @@ export default {
return this.xWhere ? where + `~and(${this.xWhere})` : where return this.xWhere ? where + `~and(${this.xWhere})` : where
}, },
nestedAndRollupColumnParams() {
// generate params for nested columns
const nestedFields = ((this.meta && this.meta.v && this.meta.v) || []).reduce((obj, vc) => {
if (vc.hm) {
obj.hm.push(vc.hm.tn)
} else if (vc.bt) {
obj.bt.push(vc.bt.rtn)
} else if (vc.mm) {
obj.mm.push(vc.mm.rtn)
}
return obj
}, { hm: [], bt: [], mm: [] })
// todo: handle if virtual column missing
// construct fields args based on lookup columns
const fieldsObj = ((this.meta && this.meta.v && this.meta.v) || []).reduce((obj, vc) => {
if (!vc.lookup) {
return obj
}
let key
let index
let column
switch (vc.type) {
case 'mm':
index = nestedFields.mm.indexOf(vc.tn) + 1
key = `mfields${index}`
column = vc.cn
break
case 'hm':
index = nestedFields.hm.indexOf(vc.tn) + 1
key = `hfields${index}`
column = vc.cn
break
case 'bt':
index = nestedFields.bt.indexOf(vc.tn) + 1
key = `bfields${index}`
column = vc.cn
break
}
if (index && column) {
obj[key] = `${obj[key] ? `${obj[key]},` : ''}${column}`
}
return obj
}, {})
return {
...Object.entries(nestedFields).reduce((ro, [k, a]) => ({ ...ro, [k]: a.join(',') }), {}),
...fieldsObj
}
},
queryParams() { queryParams() {
return { return {
limit: this.size, limit: this.size,
@ -118,10 +181,9 @@ export default {
// condition: this.condition, // condition: this.condition,
where: this.concatenatedXWhere, where: this.concatenatedXWhere,
sort: this.sort, sort: this.sort,
// todo: use reduce ...this.nestedAndRollupColumnParams
hm: (this.meta && this.meta.v && this.meta.v.filter(v => v.hm).map(({ hm }) => hm.tn).join()) || '', // ...Object.entries(nestedFields).reduce((ro, [k, a]) => ({ ...ro, [k]: a.join(',') })),
bt: (this.meta && this.meta.v && this.meta.v.filter(v => v.bt).map(({ bt }) => bt.rtn).join()) || '', // ...fieldsObj
mm: (this.meta && this.meta.v && this.meta.v.filter(v => v.mm).map(({ mm }) => mm.rtn).join()) || ''
} }
}, },
colLength() { colLength() {
@ -169,23 +231,31 @@ export default {
return this.nodes.tn || this.nodes.view_name return this.nodes.tn || this.nodes.view_name
}, },
primaryValueColumn() { primaryValueColumn() {
if (!this.meta || !this.availableColumns || !this.availableColumns.length) { return '' } if (!this.meta || !this.availableColumns || !this.availableColumns.length) {
return ''
}
return (this.availableColumns.find(col => col.pv) || { _cn: '' })._cn return (this.availableColumns.find(col => col.pv) || { _cn: '' })._cn
} }
}, },
watch: { watch: {
'viewStatus.type'() { 'viewStatus.type'() {
if (!this.loadingMeta || !this.loadingData) { this.syncDataDebounce(this) } if (!this.loadingMeta || !this.loadingData) {
this.syncDataDebounce(this)
}
}, },
showFields: { showFields: {
handler(v) { handler(v) {
if (!this.loadingMeta || !this.loadingData) { this.syncDataDebounce(this) } if (!this.loadingMeta || !this.loadingData) {
this.syncDataDebounce(this)
}
}, },
deep: true deep: true
}, },
fieldsOrder: { fieldsOrder: {
handler(v) { handler(v) {
if (!this.loadingMeta || !this.loadingData) { this.syncDataDebounce(this) } if (!this.loadingMeta || !this.loadingData) {
this.syncDataDebounce(this)
}
}, },
deep: true deep: true
}, },
@ -243,7 +313,9 @@ export default {
// if (!this.progress) { // if (!this.progress) {
// await this.loadTableData(); // await this.loadTableData();
// } // }
if (!this.loadingMeta || !this.loadingData) { this.syncDataDebounce(this) } if (!this.loadingMeta || !this.loadingData) {
this.syncDataDebounce(this)
}
}, },
deep: true deep: true
}, },
@ -256,12 +328,16 @@ export default {
// if (!this.progress) { // if (!this.progress) {
// await this.loadTableData(); // await this.loadTableData();
// } // }
if (!this.loadingMeta || !this.loadingData) { this.syncDataDebounce(this) } if (!this.loadingMeta || !this.loadingData) {
this.syncDataDebounce(this)
}
}, },
deep: true deep: true
}, },
columnsWidth() { columnsWidth() {
if (!this.loadingMeta || !this.loadingData) { this.syncDataDebounce(this) } if (!this.loadingMeta || !this.loadingData) {
this.syncDataDebounce(this)
}
}, },
sort(n, o) { sort(n, o) {
if (o !== n) { if (o !== n) {

19
packages/nc-gui/components/project/spreadsheet/rowsXcDataTable.vue

@ -681,7 +681,8 @@ export default {
} }
await this.sqlOp({ dbAlias: this.nodes.dbAlias }, 'xcVirtualTableUpdate', { await this.sqlOp({ dbAlias: this.nodes.dbAlias }, 'xcVirtualTableUpdate', {
id: this.selectedViewId, id: this.selectedViewId,
query_params: queryParams query_params: queryParams,
tn: this.meta.tn
}) })
} catch (e) { } catch (e) {
// this.$toast.error(e.message).goAway(3000); // this.$toast.error(e.message).goAway(3000);
@ -918,15 +919,8 @@ export default {
break break
} }
}, },
async loadMeta(updateShowFields = true) { async loadMeta(updateShowFields = true, col) {
this.loadingMeta = true this.loadingMeta = true
// const tableMeta = await this.$store.dispatch('sqlMgr/ActSqlOp', [{
// env: this.nodes.env,
// dbAlias: this.nodes.dbAlias
// }, 'tableXcModelGet', {
// tn: this.table
// }]);
// this.meta = JSON.parse(tableMeta.meta);
const tableMeta = await this.$store.dispatch('meta/ActLoadMeta', { const tableMeta = await this.$store.dispatch('meta/ActLoadMeta', {
env: this.nodes.env, env: this.nodes.env,
dbAlias: this.nodes.dbAlias, dbAlias: this.nodes.dbAlias,
@ -938,6 +932,9 @@ export default {
try { try {
const qp = JSON.parse(tableMeta.query_params) const qp = JSON.parse(tableMeta.query_params)
this.showFields = qp.showFields ? qp.showFields : this.showFields this.showFields = qp.showFields ? qp.showFields : this.showFields
if (col) {
this.$set(this.showFields, col, true)
}
} catch (e) { } catch (e) {
} }
} }
@ -983,8 +980,8 @@ export default {
this.selectedExpandRowIndex = row this.selectedExpandRowIndex = row
this.selectedExpandRowMeta = rowMeta this.selectedExpandRowMeta = rowMeta
}, },
async onNewColCreation() { async onNewColCreation(col) {
await this.loadMeta(true) await this.loadMeta(true, col)
this.$nextTick(async() => { this.$nextTick(async() => {
await this.loadTableData() await this.loadTableData()
// this.mapFieldsAndShowFields(); // this.mapFieldsAndShowFields();

4
packages/nc-gui/components/project/spreadsheet/views/xcGridView.vue

@ -431,10 +431,10 @@ export default {
this.selected.col = null this.selected.col = null
this.selected.row = null this.selected.row = null
}, },
onNewColCreation() { onNewColCreation(col) {
this.addNewColMenu = false this.addNewColMenu = false
this.addNewColModal = false this.addNewColModal = false
this.$emit('onNewColCreation') this.$emit('onNewColCreation', col)
}, },
expandRow(...args) { expandRow(...args) {
this.$emit('expandRow', ...args) this.$emit('expandRow', ...args)

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

@ -1101,7 +1101,7 @@ class BaseModelSql extends BaseModel {
* @private * @private
*/ */
async _getChildListInParent({parent, child}, rest = {}, index) { async _getChildListInParent({parent, child}, rest = {}, index) {
const {where, limit, offset, sort, ...restArgs} = this._getChildListArgs(rest, index, child); const {where, limit, offset, sort, ...restArgs} = this._getChildListArgs(rest, index, child, 'h');
let {fields} = restArgs; let {fields} = restArgs;
const {cn} = this.hasManyRelations.find(({tn}) => tn === child) || {}; const {cn} = this.hasManyRelations.find(({tn}) => tn === child) || {};
const _cn = this.dbModels[child].columnToAlias?.[cn]; const _cn = this.dbModels[child].columnToAlias?.[cn];
@ -1155,7 +1155,7 @@ class BaseModelSql extends BaseModel {
} }
public async _getGroupedManyToManyList({rest = {}, index = 0, child, parentIds}) { public async _getGroupedManyToManyList({rest = {}, index = 0, child, parentIds}) {
const {where, limit, offset, sort, ...restArgs} = this._getChildListArgs(rest, index, child); const {where, limit, offset, sort, ...restArgs} = this._getChildListArgs(rest, index, child, 'm');
let {fields} = restArgs; let {fields} = restArgs;
const {tn, cn, vtn, vcn, vrcn, rtn, rcn} = this.manyToManyRelations.find(({rtn}) => rtn === child) || {}; const {tn, cn, vtn, vcn, vrcn, rtn, rcn} = this.manyToManyRelations.find(({rtn}) => rtn === child) || {};
// @ts-ignore // @ts-ignore
@ -1445,7 +1445,7 @@ class BaseModelSql extends BaseModel {
* @private * @private
*/ */
async _belongsTo({parent, rcn, parentIds, childs, cn, ...rest}, index) { async _belongsTo({parent, rcn, parentIds, childs, cn, ...rest}, index) {
let {fields} = this._getChildListArgs(rest, index, parent); let {fields} = this._getChildListArgs(rest, index, parent, 'b');
if (fields !== '*' && fields.split(',').indexOf(rcn) === -1) { if (fields !== '*' && fields.split(',').indexOf(rcn) === -1) {
fields += ',' + rcn; fields += ',' + rcn;
} }
@ -1642,18 +1642,21 @@ class BaseModelSql extends BaseModel {
* @returns {Object} consisting of fields*,where*,limit*,offset*,sort* * @returns {Object} consisting of fields*,where*,limit*,offset*,sort*
* @private * @private
*/ */
_getChildListArgs(args: any, index?: number, child?: string) { _getChildListArgs(args: any, index?: number, child?: string, prefix = '') {
index++; index++;
const obj: XcFilter = {}; const obj: XcFilter = {};
obj.where = args[`where${index}`] || args[`w${index}`] || ''; obj.where = args[`${prefix}where${index}`] || args[`w${index}`] || '';
obj.limit = Math.max(Math.min(args[`limit${index}`] || args[`l${index}`] || this.config.limitDefault, this.config.limitMax), this.config.limitMin); obj.limit = Math.max(Math.min(args[`${prefix}limit${index}`] || args[`${prefix}l${index}`] || this.config.limitDefault, this.config.limitMax), this.config.limitMin);
obj.offset = args[`offset${index}`] || args[`o${index}`] || 0; obj.offset = args[`${prefix}offset${index}`] || args[`${prefix}o${index}`] || 0;
obj.fields = args[`fields${index}`] || args[`f${index}`] || this.getPKandPV(child); obj.fields = args[`${prefix}fields${index}`] || args[`f${index}`];
obj.sort = args[`sort${index}`] || args[`s${index}`]; obj.sort = args[`${prefix}sort${index}`] || args[`${prefix}s${index}`];
obj.fields = obj.fields ? `${obj.fields},${this.getTablePKandPVFields(child)}` : this.getTablePKandPVFields(child);
return obj; return obj;
} }
private getPKandPV(child: string) { private getTablePKandPVFields(child: string) {
return child ? return child ?
(this.dbModels[child]?.columns?.filter(col => col.pk || col.pv).map(col => col.cn) || ['*']).join(',') (this.dbModels[child]?.columns?.filter(col => col.pk || col.pv).map(col => col.cn) || ['*']).join(',')
: '*'; : '*';

4
packages/nocodb/src/lib/noco/NcProjectBuilder.ts

@ -146,6 +146,10 @@ export default class NcProjectBuilder {
break; break;
case 'xcVirtualTableUpdate':
await curBuilder.onVirtualTableUpdate(data.req.args);
break;
case 'tableCreate': case 'tableCreate':
await curBuilder.onTableCreate(data.req.args.tn, data.req.args); await curBuilder.onTableCreate(data.req.args.tn, data.req.args);

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

@ -470,9 +470,19 @@ export default abstract class BaseApiBuilder<T extends Noco> implements XcDynami
} }
} }
} }
// update lookup columns
this.metas[bt.rtn].v?.forEach(v => {
if (v.tn === tn && v.cn === column.cno) {
relationTableMetas.add(this.metas[bt.rtn])
v.cn = column.cn;
}
})
} }
} }
// update column name in has many // update column name in has many
if (newMeta.hasMany?.length) { if (newMeta.hasMany?.length) {
for (const hm of newMeta.hasMany) { for (const hm of newMeta.hasMany) {
@ -489,6 +499,44 @@ export default abstract class BaseApiBuilder<T extends Noco> implements XcDynami
} }
} }
} }
// update lookup columns
this.metas[hm.tn].v?.forEach(v => {
if (v.tn === tn && v.cn === column.cno) {
relationTableMetas.add(this.metas[hm.tn])
v.cn = column.cn;
}
})
}
}
// update column name in many to many
if (newMeta.manyToMany?.length) {
for (const mm of newMeta.manyToMany) {
if (mm.cn === column.cno) {
mm.cn = column.cn;
mm._cn = column._cn;
// update column name in child table metadata
relationTableMetas.add(this.metas[mm.rtn])
for (const cMm of this.metas[mm.rtn]?.manyToMany) {
if (cMm.rcn === column.cno && cMm.rtn === tn) {
cMm.rcn = column.cn;
cMm._rcn = column._cn;
}
}
}
// update lookup columns
this.metas[mm.rtn].v?.forEach(v => {
if (v.tn === tn && v.cn === column.cno) {
relationTableMetas.add(this.metas[mm.tn])
v.cn = column.cn;
}
})
} }
} }
} }
@ -605,6 +653,8 @@ export default abstract class BaseApiBuilder<T extends Noco> implements XcDynami
}, { }, {
title: relMeta.tn title: relMeta.tn
}); });
this.models[relMeta.tn] = this.getBaseModel(relMeta);
XcCache.del([this.projectId, this.dbAlias, 'table', relMeta.tn].join('::'));
} }
} }
@ -1757,6 +1807,14 @@ export default abstract class BaseApiBuilder<T extends Noco> implements XcDynami
public async onTableCreate(_tn: string, _args?: any) { public async onTableCreate(_tn: string, _args?: any) {
Tele.emit('evt', {evt_type: 'table:created'}) Tele.emit('evt', {evt_type: 'table:created'})
} }
public onVirtualTableUpdate(args: any) {
const meta = XcCache.get([this.projectId, this.dbAlias, 'table', args.tn].join('::'));
if(meta && meta.id === args.id) {
XcCache.del([this.projectId, this.dbAlias, 'table', args.tn].join('::'));
// todo: update meta and model
}
}
} }
export {IGNORE_TABLES}; export {IGNORE_TABLES};

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

@ -1728,9 +1728,6 @@ export default class NcMetaMgr {
// NOTE: updated // NOTE: updated
protected async tableXcModelGet(req, args): Promise<any> { protected async tableXcModelGet(req, args): Promise<any> {
console.time('tableXcModelGet')
const roles = req.session?.passport?.user?.roles; const roles = req.session?.passport?.user?.roles;
const dbAlias = await this.getDbAlias(args); const dbAlias = await this.getDbAlias(args);
@ -1745,7 +1742,8 @@ export default class NcMetaMgr {
'meta', 'meta',
'parent_model_title', 'parent_model_title',
'title', 'title',
'query_params' 'query_params',
'id'
]); ]);
this.cacheModelSet(args.project_id, dbAlias, 'table', args.args.tn, meta); this.cacheModelSet(args.project_id, dbAlias, 'table', args.args.tn, meta);
} }
@ -1819,7 +1817,6 @@ export default class NcMetaMgr {
meta.meta = JSON.stringify(parsedTableMeta); meta.meta = JSON.stringify(parsedTableMeta);
console.timeEnd('tableXcModelGet')
return meta; return meta;
} }

Loading…
Cancel
Save