Browse Source

feat: M2M

Signed-off-by: Pranav C <61551451+pranavxc@users.noreply.github.com>
pull/341/head
Pranav C 3 years ago
parent
commit
b08a03ca0c
  1. 3
      packages/nc-gui/components/project/spreadsheet/components/editColumn.vue
  2. 3
      packages/nc-gui/components/project/spreadsheet/components/editColumn/linkedToAnotherOptions.vue
  3. 13
      packages/nc-gui/components/project/spreadsheet/components/expandedForm.vue
  4. 18
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/belogsToCell.vue
  5. 21
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/components/item-chip.vue
  6. 65
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/components/listChildItems.vue
  7. 2
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/components/listItems.vue
  8. 21
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/hasManyCell.vue
  9. 21
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/manyToManyCell.vue
  10. 4
      packages/nc-gui/components/project/spreadsheet/components/virtualHeaderCell.vue
  11. 23
      packages/nc-gui/components/project/spreadsheet/views/xcGridView.vue
  12. 2
      packages/nc-gui/config/vuetify.options.js
  13. 4
      packages/nc-gui/plugins/globalMixin.js
  14. 2
      packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSql.ts
  15. 3
      packages/nocodb/src/lib/migrator/SqlMigrator/lib/KnexMigrator.ts
  16. 2
      packages/nocodb/src/lib/noco/NcProjectBuilder.ts
  17. 5
      packages/nocodb/src/lib/noco/meta/NcMetaIOImpl.ts
  18. 227
      packages/nocodb/src/lib/noco/meta/NcMetaMgr.ts
  19. 2
      packages/nocodb/src/lib/noco/rest/RestApiBuilder.ts
  20. 2
      packages/nocodb/src/lib/sqlMgr/SqlMgr.ts

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

@ -19,7 +19,7 @@
@input="newColumn.altered = newColumn.altered || 8" @input="newColumn.altered = newColumn.altered || 8"
:rules="[ :rules="[
v => !!v || 'Required', v => !!v || 'Required',
v => !meta || !meta.columns || !column || meta.columns.every(c => column && c.cn === column.cn || v !== c.cn ) && meta.v.every(c => v !== c._cn ) || 'Duplicate column name' v => !meta || !meta.columns || meta.columns.every(c => column && c.cn === column.cn || v !== c.cn ) && meta.v.every(c => v !== c._cn ) || 'Duplicate column name'
]" ]"
class="caption" class="caption"
label="Column name" label="Column name"
@ -94,6 +94,7 @@
:nodes="nodes" :nodes="nodes"
:meta="meta" :meta="meta"
:isSQLite="isSQLite" :isSQLite="isSQLite"
:alias="newColumn.cn"
@onColumnSelect="onRelColumnSelect" @onColumnSelect="onRelColumnSelect"
></linked-to-another-options> ></linked-to-another-options>
</v-col> </v-col>

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

@ -103,7 +103,7 @@
<script> <script>
export default { export default {
name: "linked-to-another-options", name: "linked-to-another-options",
props: ['nodes', 'column', 'meta', 'isSQLite'], props: ['nodes', 'column', 'meta', 'isSQLite','alias'],
data: () => ({ data: () => ({
type: 'hm', type: 'hm',
refTables: [], refTables: [],
@ -179,6 +179,7 @@ export default {
}, },
'xcM2MRelationCreate', 'xcM2MRelationCreate',
{ {
_cn:this.alias,
...this.relation, ...this.relation,
type: this.isSQLite || this.relation.type === 'virtual' ? 'virtual' : 'real', type: this.isSQLite || this.relation.type === 'virtual' ? 'virtual' : 'real',
parentTable: this.meta.tn, parentTable: this.meta.tn,

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

@ -50,8 +50,14 @@
:key="i" class="row-col my-4"> :key="i" class="row-col my-4">
<div> <div>
<label :for="`data-table-form-${col._cn}`" class="body-2 text-capitalize"> <label :for="`data-table-form-${col._cn}`" class="body-2 text-capitalize">
<span v-if="col.virtual"> <virtual-header-cell
</span> v-if="col.virtual"
:column="col"
:nodes="nodes"
:is-form="true"
:meta="meta"
>
</virtual-header-cell>
<header-cell <header-cell
v-else v-else
:is-form="true" :is-form="true"
@ -191,13 +197,14 @@ import EditableCell from "@/components/project/spreadsheet/components/editableCe
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import colors from "@/mixins/colors"; import colors from "@/mixins/colors";
import VirtualCell from "@/components/project/spreadsheet/components/virtualCell"; import VirtualCell from "@/components/project/spreadsheet/components/virtualCell";
import VirtualHeaderCell from "@/components/project/spreadsheet/components/virtualHeaderCell";
const relativeTime = require('dayjs/plugin/relativeTime') const relativeTime = require('dayjs/plugin/relativeTime')
const utc = require('dayjs/plugin/utc') const utc = require('dayjs/plugin/utc')
dayjs.extend(utc) dayjs.extend(utc)
dayjs.extend(relativeTime) dayjs.extend(relativeTime)
export default { export default {
components: {VirtualCell, EditableCell, HeaderCell}, components: {VirtualHeaderCell, VirtualCell, EditableCell, HeaderCell},
mixins: [colors], mixins: [colors],
props: { props: {
dbAlias: String, dbAlias: String,

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

@ -1,7 +1,7 @@
<template> <template>
<div class="d-flex"> <div class="d-flex d-100 chips-wrapper" :class="{active}">
<template v-if="!isForm"> <template v-if="!isForm">
<div class="d-flex align-center img-container flex-grow-1 hm-items"> <div class="chips d-flex align-center img-container flex-grow-1 hm-items">
<template v-if="value || localState"> <template v-if="value || localState">
<item-chip <item-chip
:active="active" :active="active"
@ -13,7 +13,7 @@
></item-chip> ></item-chip>
</template> </template>
</div> </div>
<div class=" align-center justify-center px-1 flex-shrink-1" <div class="action align-center justify-center px-1 flex-shrink-1"
:class="{'d-none': !active, 'd-flex':active }"> :class="{'d-none': !active, 'd-flex':active }">
<x-icon small :color="['primary','grey']" @click="showNewRecordModal">{{ <x-icon small :color="['primary','grey']" @click="showNewRecordModal">{{
value ? 'mdi-arrow-expand' : 'mdi-plus' value ? 'mdi-arrow-expand' : 'mdi-plus'
@ -37,6 +37,7 @@
<list-child-items <list-child-items
ref="childList" ref="childList"
v-if="parentMeta && isForm" v-if="parentMeta && isForm"
:isForm="isForm"
:local-state="localState? [localState] : []" :local-state="localState? [localState] : []"
:is-new="isNew" :is-new="isNew"
:size="10" :size="10"
@ -329,6 +330,17 @@ export default {
margin: 3px auto; margin: 3px auto;
} }
.chips-wrapper{
.chips{
max-width: 100%;
}
&.active{
.chips{
max-width: calc(100% - 30px);
}
}
}
</style> </style>
<!-- <!--
/** /**

21
packages/nc-gui/components/project/spreadsheet/components/virtualCell/components/item-chip.vue

@ -1,7 +1,13 @@
<template> <template>
<v-chip small text-color="textColor" :color="isDark ? '' : 'primary lighten-5'" <v-chip
@click="active && $emit('edit',item)" class="chip"
>{{ value }} :class="{active}"
small
text-color="textColor"
:color="isDark ? '' : 'primary lighten-5'"
@click="active && $emit('edit',item)"
>
<span class="name">{{ value }}</span>
<div v-show="active" class="mr-n1 ml-2"> <div v-show="active" class="mr-n1 ml-2">
<x-icon <x-icon
:color="['text' , 'textLight']" :color="['text' , 'textLight']"
@ -25,6 +31,13 @@ export default {
} }
</script> </script>
<style scoped> <style scoped lang="scss">
.chip {
max-width: max(100%, 60px);
.name {
text-overflow: ellipsis;
overflow: hidden;
}
}
</style> </style>

65
packages/nc-gui/components/project/spreadsheet/components/virtualCell/components/listChildItems.vue

@ -1,22 +1,44 @@
<template> <template>
<!-- <v-dialog v-model="show" width="600">--> <!-- <v-dialog v-model="show" width="600">-->
<v-card width="600" color="backgroundColor"> <v-card width="600" color="">
<v-card-title class="textColor--text mx-2">{{ meta ? meta._tn : 'Children' }} <v-card-title v-if="!isForm" class="textColor--text mx-2" :class="{'py-2':isForm}">
<span v-if="!isForm">{{ meta ? meta._tn : 'Children' }}</span>
<v-spacer> <v-spacer>
</v-spacer> </v-spacer>
<v-btn small class="caption" color="primary" @click="$emit('new-record')"> <v-btn
<v-icon small>{{ bt ? 'mdi-pencil' : 'mdi-plus' }}</v-icon>&nbsp; small
{{ bt ? 'Select' : 'Add' }} Record class="caption"
color="primary"
@click="$emit('new-record')"
>
<v-icon
small>mdi-link
</v-icon>&nbsp;
Link to '{{ meta._tn }}'
</v-btn> </v-btn>
</v-card-title> </v-card-title>
<v-card-text> <v-card-text>
<div class="items-container pt-2"> <div class="items-container pt-2 mb-n4">
<template v-if="(data && data.list && data.list.length) || (localState && localState.length)"> <div class="text-right mb-2 mt-n4 mx-2">
<v-btn
v-if="isForm"
x-small
class="caption"
color="primary"
outlined
@click="$emit('new-record')"
>
<v-icon
x-small>mdi-link
</v-icon>&nbsp;
Link to '{{ meta._tn }}'
</v-btn>
</div>
<template v-if="isDataAvail">
<v-card <v-card
v-for="(ch,i) in ((data && data.list) || localState)" v-for="(ch,i) in ((data && data.list) || localState)"
class="ma-2 child-list-modal child-card" class="mx-2 mb-2 child-list-modal child-card"
outlined outlined
:key="i" :key="i"
@click="$emit('edit',ch)" @click="$emit('edit',ch)"
@ -47,16 +69,27 @@
</v-card> </v-card>
</template> </template>
<div v-else-if="data" class="text-center pt-6 pb-4 textLight--text"> <div v-else-if="data || localState" class="text-center textLight--text"
:class="{'pt-6 pb-4' : !isForm , 'pt-1':isForm}">
No item{{ bt ? '' : 's' }} found No item{{ bt ? '' : 's' }} found
</div> </div>
<div v-if="isForm" class="mb-2 d-flex align-center justify-center">
<pagination
v-if="!bt && isDataAvail && data && data.count > 1"
:size="size"
:count="data && data.count"
v-model="page"
@input="loadData"
></pagination>
</div>
</div> </div>
</v-card-text> </v-card-text>
<v-card-actions class="justify-center py-2 flex-column"> <v-card-actions v-if="!isForm" class="justify-center flex-column" :class="{'py-0':isForm}">
<pagination <pagination
v-if="!bt && data && data.list" v-if="!bt && isDataAvail && data && data.count > 1"
:size="size" :size="size"
:count="data.count" :count="data && data.count"
v-model="page" v-model="page"
@input="loadData" @input="loadData"
class="mb-3" class="mb-3"
@ -74,6 +107,7 @@ export default {
name: "listChildItems", name: "listChildItems",
components: {Pagination}, components: {Pagination},
props: { props: {
isForm: Boolean,
bt: Boolean, bt: Boolean,
localState: [Array], localState: [Array],
isNew: Boolean, isNew: Boolean,
@ -101,7 +135,7 @@ export default {
page: 1 page: 1
}), }),
mounted() { mounted() {
this.loadData(); this.loadData();
}, },
methods: { methods: {
async loadData() { async loadData() {
@ -114,6 +148,9 @@ export default {
} }
}, },
computed: { computed: {
isDataAvail() {
return (this.data && this.data.list && this.data.list.length) || (this.localState && this.localState.length);
},
show: { show: {
set(v) { set(v) {
this.$emit('input', v) this.$emit('input', v)

2
packages/nc-gui/components/project/spreadsheet/components/virtualCell/components/listItems.vue

@ -1,6 +1,6 @@
<template> <template>
<v-dialog v-model="show" width="600"> <v-dialog v-model="show" width="600">
<v-card width="600" color="backgroundColor"> <v-card width="600" >
<v-card-title class="textColor--text mx-2 justify-center">{{ title }} <v-card-title class="textColor--text mx-2 justify-center">{{ title }}
</v-card-title> </v-card-title>

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

@ -1,7 +1,7 @@
<template> <template>
<div class="d-flex"> <div class="d-flex d-100 chips-wrapper" :class="{active}">
<template v-if="!isForm"> <template v-if="!isForm">
<div class="d-flex align-center img-container flex-grow-1 hm-items"> <div class="chips d-flex align-center img-container flex-grow-1 hm-items">
<template v-if="value||localState"> <template v-if="value||localState">
<item-chip <item-chip
v-for="(ch,i) in (value|| localState)" v-for="(ch,i) in (value|| localState)"
@ -14,7 +14,7 @@
></item-chip> ></item-chip>
</template> </template>
</div> </div>
<div class=" align-center justify-center px-1 flex-shrink-1" :class="{'d-none': !active, 'd-flex':active }"> <div class="actions align-center justify-center px-1 flex-shrink-1" :class="{'d-none': !active, 'd-flex':active }">
<x-icon small :color="['primary','grey']" @click="showNewRecordModal">mdi-plus</x-icon> <x-icon small :color="['primary','grey']" @click="showNewRecordModal">mdi-plus</x-icon>
<x-icon x-small :color="['primary','grey']" @click="showChildListModal" class="ml-2">mdi-arrow-expand</x-icon> <x-icon x-small :color="['primary','grey']" @click="showChildListModal" class="ml-2">mdi-arrow-expand</x-icon>
</div> </div>
@ -38,6 +38,7 @@
<list-child-items <list-child-items
:is="isForm ? 'list-child-items' : 'list-child-items-modal'" :is="isForm ? 'list-child-items' : 'list-child-items-modal'"
:isForm="isForm"
ref="childList" ref="childList"
v-if="childMeta && (childListModal || isForm)" v-if="childMeta && (childListModal || isForm)"
v-model="childListModal" v-model="childListModal"
@ -314,11 +315,8 @@ export default {
}, },
watch: { watch: {
isNew(n, o) { isNew(n, o) {
debugger
if (!n && o) { if (!n && o) {
debugger
let child; let child;
debugger
while (child = this.localState.pop()) { while (child = this.localState.pop()) {
this.addChildToParent(child) this.addChildToParent(child)
} }
@ -383,7 +381,16 @@ export default {
} }
} }
} }
.chips-wrapper{
.chips{
max-width: 100%;
}
&.active{
.chips{
max-width: calc(100% - 60px);
}
}
}
</style> </style>
<!-- <!--
/** /**

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

@ -1,7 +1,7 @@
<template> <template>
<div class="d-flex"> <div class="d-flex d-100 chips-wrapper" :class="{active}">
<template v-if="!isForm"> <template v-if="!isForm">
<div class="d-flex align-center img-container flex-grow-1 hm-items"> <div class="chips d-flex align-center img-container flex-grow-1 hm-items">
<template v-if="(value || localState)"> <template v-if="(value || localState)">
<item-chip v-for="(v,j) in (value || localState)" <item-chip v-for="(v,j) in (value || localState)"
:active="active" :active="active"
@ -14,7 +14,7 @@
</template> </template>
</div> </div>
<div class=" align-center justify-center px-1 flex-shrink-1" :class="{'d-none': !active, 'd-flex':active }"> <div class="actions align-center justify-center px-1 flex-shrink-1" :class="{'d-none': !active, 'd-flex':active }">
<x-icon small :color="['primary','grey']" @click="showNewRecordModal">mdi-plus</x-icon> <x-icon small :color="['primary','grey']" @click="showNewRecordModal">mdi-plus</x-icon>
<x-icon x-small :color="['primary','grey']" @click="showChildListModal" class="ml-2">mdi-arrow-expand</x-icon> <x-icon x-small :color="['primary','grey']" @click="showChildListModal" class="ml-2">mdi-arrow-expand</x-icon>
</div> </div>
@ -38,6 +38,7 @@
<list-child-items <list-child-items
:is="isForm ? 'list-child-items' : 'list-child-items-modal'" :is="isForm ? 'list-child-items' : 'list-child-items-modal'"
:isForm="isForm"
ref="childList" ref="childList"
v-if="childMeta && assocMeta && (isForm || childListModal)" v-if="childMeta && assocMeta && (isForm || childListModal)"
v-model="childListModal" v-model="childListModal"
@ -133,10 +134,11 @@ import DlgLabelSubmitCancel from "@/components/utils/dlgLabelSubmitCancel";
import ListItems from "@/components/project/spreadsheet/components/virtualCell/components/listItems"; import ListItems from "@/components/project/spreadsheet/components/virtualCell/components/listItems";
import ItemChip from "@/components/project/spreadsheet/components/virtualCell/components/item-chip"; import ItemChip from "@/components/project/spreadsheet/components/virtualCell/components/item-chip";
import ListChildItems from "@/components/project/spreadsheet/components/virtualCell/components/listChildItems"; import ListChildItems from "@/components/project/spreadsheet/components/virtualCell/components/listChildItems";
import listChildItemsModal from "@/components/project/spreadsheet/components/virtualCell/components/listChildItemsModal";
export default { export default {
name: "many-to-many-cell", name: "many-to-many-cell",
components: {ListChildItems, ItemChip, ListItems, DlgLabelSubmitCancel}, components: {ListChildItems, ItemChip, ListItems, DlgLabelSubmitCancel, listChildItemsModal },
props: { props: {
value: [Object, Array], value: [Object, Array],
meta: [Object], meta: [Object],
@ -430,6 +432,17 @@ export default {
margin: 3px auto; margin: 3px auto;
} }
.chips-wrapper{
.chips{
max-width: 100%;
}
&.active{
.chips{
max-width: calc(100% - 60px);
}
}
}
</style> </style>
<!-- <!--
/** /**

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

@ -18,7 +18,7 @@
<v-menu offset-y open-on-hover left> <v-menu offset-y open-on-hover left>
<template v-slot:activator="{on}"> <template v-slot:activator="{on}">
<v-icon v-on="on" small>mdi-menu-down</v-icon> <v-icon v-if="!isForm" v-on="on" small>mdi-menu-down</v-icon>
</template> </template>
<v-list dense> <v-list dense>
<v-list-item dense @click="editColumnMenu = true"> <v-list-item dense @click="editColumnMenu = true">
@ -78,7 +78,7 @@
import EditVirtualColumn from "@/components/project/spreadsheet/components/editVirtualColumn"; import EditVirtualColumn from "@/components/project/spreadsheet/components/editVirtualColumn";
export default { export default {
components: {EditVirtualColumn}, components: {EditVirtualColumn},
props: ['column', 'nodes', 'meta'], props: ['column', 'nodes', 'meta', 'isForm'],
name: "virtualHeaderCell", name: "virtualHeaderCell",
data: () => ({ data: () => ({
columnDeleteDialog: false, columnDeleteDialog: false,

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

@ -17,7 +17,7 @@
:key="i + '_' + col._cn" :key="i + '_' + col._cn"
v-show="showFields[col._cn]" v-show="showFields[col._cn]"
@xcresize="onresize(col._cn,$event)" @xcresize="onresize(col._cn,$event)"
@xcresizing="resizingCol = col._cn" @xcresizing="onXcResizing(col._cn,$event)"
@xcresized="resizingCol = null" @xcresized="resizingCol = null"
:data-col="col._cn" :data-col="col._cn"
> >
@ -372,6 +372,9 @@ export default {
this.meta && this.meta.columns && this.meta.columns.forEach(c => { this.meta && this.meta.columns && this.meta.columns.forEach(c => {
obj[c._cn] = columnStyling[c.uidt] && columnStyling[c.uidt].w || undefined; obj[c._cn] = columnStyling[c.uidt] && columnStyling[c.uidt].w || undefined;
}) })
this.meta && this.meta.v && this.meta.v.forEach(v => {
obj[v._cn] = v.bt ? '100px' : '200px';
})
Array.from(this.$el.querySelectorAll('th')).forEach(el => { Array.from(this.$el.querySelectorAll('th')).forEach(el => {
const width = el.getBoundingClientRect().width; const width = el.getBoundingClientRect().width;
obj[el.dataset.col] = obj[el.dataset.col] || ((width < 100 ? 100 : width) + 'px'); obj[el.dataset.col] = obj[el.dataset.col] || ((width < 100 ? 100 : width) + 'px');
@ -497,6 +500,10 @@ export default {
}, },
onresize(col, size) { onresize(col, size) {
this.$emit('update:columnsWidth', {...this.columnsWidth, [col]: size}); this.$emit('update:columnsWidth', {...this.columnsWidth, [col]: size});
},
onXcResizing(_cn, width) {
this.resizingCol = _cn;
this.resizingColWidth = width;
} }
}, },
computed: { computed: {
@ -524,7 +531,7 @@ export default {
style() { style() {
let style = ''; let style = '';
for (const [key, val] of Object.entries(this.columnsWidth || {})) { for (const [key, val] of Object.entries(this.columnsWidth || {})) {
if (val && key !== this.resizingCol) if (val && key !== this.resizingCol) {
style += ` style += `
[data-col="${key}"]{ [data-col="${key}"]{
min-width: ${val}; min-width: ${val};
@ -532,13 +539,23 @@ export default {
width: ${val}; width: ${val};
} }
`; `;
} else if (key === this.resizingCol) {
style += `
[data-col="${key}"]{
min-width: ${this.resizingColWidth};
max-width: ${this.resizingColWidth};
width: ${this.resizingColWidth};
}
`;
}
} }
return style; return style;
} },
}, },
data: () => ({ data: () => ({
resizingCol: null, resizingCol: null,
resizingColWidth: null,
selectedExpandRowIndex: null, selectedExpandRowIndex: null,
selectedExpandRowMeta: null, selectedExpandRowMeta: null,
addNewColMenu: false, addNewColMenu: false,

2
packages/nc-gui/config/vuetify.options.js

@ -20,7 +20,7 @@ export default function ({app}) {
textColor: '#ffffff', textColor: '#ffffff',
text: '#ffffff', text: '#ffffff',
textLight: '#b3b3b3', textLight: '#b3b3b3',
backgroundColor: '#969696', backgroundColor: '#565656',
backgroundColorDefault: '#1f1f1f' backgroundColorDefault: '#1f1f1f'
}, },
light: { light: {

4
packages/nc-gui/plugins/globalMixin.js

@ -49,6 +49,8 @@ export default async ({store}) => {
el.appendChild(resizer); el.appendChild(resizer);
resizer.addEventListener('mousedown', initDrag, false); resizer.addEventListener('mousedown', initDrag, false);
el.style.transition = '20ms width0';
let startX, startY, startWidth; let startX, startY, startWidth;
function initDrag(e) { function initDrag(e) {
@ -75,7 +77,7 @@ export default async ({store}) => {
function doDrag(e) { function doDrag(e) {
width = (startWidth + e.clientX - startX) + 'px'; width = (startWidth + e.clientX - startX) + 'px';
el.style.maxWidth = el.style.minWidth = el.style.width = width; el.style.maxWidth = el.style.minWidth = el.style.width = width;
emit(vnode, 'xcresizing'); emit(vnode, 'xcresizing', width);
// //
// p.style.height = (startHeight + e.clientY - startY) + 'px'; // p.style.height = (startHeight + e.clientY - startY) + 'px';
} }

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

@ -1453,7 +1453,7 @@ class BaseModelSql extends BaseModel {
const gs = _.groupBy(parents, this.dbModels[parent]?.columnToAlias?.[rcn] || rcn); const gs = _.groupBy(parents, this.dbModels[parent]?.columnToAlias?.[rcn] || rcn);
childs.forEach(row => { childs.forEach(row => {
row[this.dbModels?.[parent]?._tn || parent] = gs[row[this.dbModels[parent]?.columnToAlias?.[cn] || cn]]?.[0] || gs[row[cn || gs[row[this.dbModels[parent]?.columnToAlias?.[cn] || cn]]?.[0]]]?.[0]; row[this.dbModels?.[parent]?._tn || parent] = gs[row[this?.columnToAlias?.[cn] || cn]]?.[0];
}) })
} }

3
packages/nocodb/src/lib/migrator/SqlMigrator/lib/KnexMigrator.ts

@ -1065,7 +1065,7 @@ export default class KnexMigrator extends SqlMigrator {
* and only filenames are migrated to _evolution table * and only filenames are migrated to _evolution table
* @memberof KnexMigrator * @memberof KnexMigrator
*/ */
async migrationsUp(args: any = {}) { async migrationsUp(args: any = {}) {
const func = this.migrationsUp.name; const func = this.migrationsUp.name;
// const result = new Result(); // const result = new Result();
@ -1077,7 +1077,6 @@ export default class KnexMigrator extends SqlMigrator {
// if (NcConfigFactory.hasDbUrl()) { // if (NcConfigFactory.hasDbUrl()) {
// this.project = NcConfigFactory.make(); // this.project = NcConfigFactory.make();
// } // }
// console.log(this.project); // console.log(this.project);
return await this._migrationsUp({ return await this._migrationsUp({

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

@ -92,7 +92,7 @@ export default class NcProjectBuilder {
}); });
break; break;
case 'xcM2MRelationCreate': case 'xcM2MRelationCreate':
curBuilder.onManyToManyRelationCreate(data.req.args.parentTable, data.req.args.childTable, data.req.args); await curBuilder.onManyToManyRelationCreate(data.req.args.parentTable, data.req.args.childTable, data.req.args);
break; break;
case 'relationCreate': case 'relationCreate':

5
packages/nocodb/src/lib/noco/meta/NcMetaIOImpl.ts

@ -55,6 +55,7 @@ export default class NcMetaIOImpl extends NcMetaIO {
private connection: XKnex; private connection: XKnex;
// todo: need to fix
private trx: Knex.Transaction; private trx: Knex.Transaction;
@ -539,7 +540,9 @@ export default class NcMetaIOImpl extends NcMetaIO {
} }
async startTransaction() { async startTransaction() {
this.trx = await this.connection.transaction(); if (!this.trx) {
this.trx = await this.connection.transaction();
}
} }
async metaReset(project_id: string, dbAlias: string, apiType?: string): Promise<void> { async metaReset(project_id: string, dbAlias: string, apiType?: string): Promise<void> {

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

@ -2455,75 +2455,124 @@ export default class NcMetaMgr {
} }
// todo : transaction // todo : transaction in sql client
protected async xcRelationColumnDelete(args: any, req, deleteColumn = true): Promise<any> { protected async xcRelationColumnDelete(args: any, req, deleteColumn = true): Promise<any> {
const dbAlias = this.getDbAlias(args); // this.xcMeta.startTransaction();
const projectId = this.getProjectId(args); // try {
const dbAlias = this.getDbAlias(args);
const projectId = this.getProjectId(args);
// const parent = await this.xcMeta.metaGet(projectId, dbAlias, 'nc_models', { // const parent = await this.xcMeta.metaGet(projectId, dbAlias, 'nc_models', {
// title: args.args.parentTable // title: args.args.parentTable
// }); // });
// // @ts-ignore // // @ts-ignore
// const parentMeta = JSON.parse(parent.meta); // const parentMeta = JSON.parse(parent.meta);
// @ts-ignore // @ts-ignore
// todo: compare column // todo: compare column
switch (args.args.type) { switch (args.args.type) {
case 'bt': case 'bt':
case 'hm': case 'hm':
const child = await this.xcMeta.metaGet(projectId, dbAlias, 'nc_models', { const child = await this.xcMeta.metaGet(projectId, dbAlias, 'nc_models', {
title: args.args.childTable title: args.args.childTable
});
const childMeta = JSON.parse(child.meta);
const relation = childMeta.belongsTo.find(bt => bt.rtn === args.args.parentTable);
// todo: virtual relation delete
if(relation){
const opArgs = {
...args,
args: {
childColumn: relation.cn,
childTable: relation.tn,
parentTable: relation.rtn,
parentColumn: relation.rcn
},
api: 'relationDelete',
sqlOpPlus: true,
};
let out;
if (relation?.type === 'virtual') {
opArgs.api = 'xcVirtualRelationDelete';
out = await this.xcVirtualRelationDelete(opArgs, req);
} else {
out = await this.projectMgr.getSqlMgr({id: projectId}).handleRequest('relationDelete', opArgs);
}
if (this.listener) {
await this.listener({
req: opArgs,
res: out,
user: req.user,
ctx: {req}
}); });
} const childMeta = JSON.parse(child.meta);
} const relation = childMeta.belongsTo.find(bt => bt.rtn === args.args.parentTable);
if (deleteColumn) { // todo: virtual relation delete
const originalColumns = childMeta.columns; if (relation) {
const columns = childMeta.columns.map(c => ({ const opArgs = {
...c, ...(relation.cn === c.cn ? { ...args,
altered: 4, args: {
cno: c.cn childColumn: relation.cn,
} : {cno: c.cn}) childTable: relation.tn,
})) parentTable: relation.rtn,
parentColumn: relation.rcn
},
api: 'relationDelete',
sqlOpPlus: true,
};
let out;
if (relation?.type === 'virtual') {
opArgs.api = 'xcVirtualRelationDelete';
out = await this.xcVirtualRelationDelete(opArgs, req);
} else {
out = await this.projectMgr.getSqlMgr({id: projectId}).handleRequest('relationDelete', opArgs);
}
if (this.listener) {
await this.listener({
req: opArgs,
res: out,
user: req.user,
ctx: {req}
});
}
}
if (deleteColumn) {
const originalColumns = childMeta.columns;
const columns = childMeta.columns.map(c => ({
...c, ...(relation.cn === c.cn ? {
altered: 4,
cno: c.cn
} : {cno: c.cn})
}))
const opArgs = {
...args,
args: {
columns,
originalColumns,
tn: childMeta.tn,
},
sqlOpPlus: true,
api: 'tableUpdate'
}
const out = await this.projectMgr.getSqlMgr({id: projectId}).handleRequest('tableUpdate', opArgs);
if (this.listener) {
await this.listener({
req: opArgs,
res: out,
user: req.user,
ctx: {req}
});
}
}
break;
case 'mm': {
const assoc = await this.xcMeta.metaGet(projectId, dbAlias, 'nc_models', {
title: args.args.assocTable
});
const assocMeta = JSON.parse(assoc.meta);
const rel1 = assocMeta.belongsTo.find(bt => bt.rtn === args.args.parentTable)
const rel2 = assocMeta.belongsTo.find(bt => bt.rtn === args.args.childTable)
await this.xcRelationColumnDelete({
...args,
args: {
parentTable: rel1.rtn,
parentColumn: rel1.rcn,
childTable: rel1.tn,
childColumn: rel1.cn,
type: 'bt',
}
}, req, false)
await this.xcRelationColumnDelete({
...args,
args: {
parentTable: rel2.rtn,
parentColumn: rel2.rcn,
childTable: rel2.tn,
childColumn: rel2.cn,
type: 'bt',
}
}, req, false);
const opArgs = { const opArgs = {
...args, ...args,
args: { args: assocMeta,
columns, api: 'tableDelete',
originalColumns,
tn: childMeta.tn,
},
sqlOpPlus: true, sqlOpPlus: true,
api: 'tableUpdate' };
} const out = await this.projectMgr.getSqlMgr({id: projectId}).handleRequest('tableDelete', opArgs);
const out = await this.projectMgr.getSqlMgr({id: projectId}).handleRequest('tableUpdate', opArgs);
if (this.listener) { if (this.listener) {
await this.listener({ await this.listener({
@ -2533,57 +2582,15 @@ export default class NcMetaMgr {
ctx: {req} ctx: {req}
}); });
} }
}
break;
case 'mm': {
const assoc = await this.xcMeta.metaGet(projectId, dbAlias, 'nc_models', {
title: args.args.assocTable
});
const assocMeta = JSON.parse(assoc.meta);
const rel1 = assocMeta.belongsTo.find(bt => bt.rtn === args.args.parentTable)
const rel2 = assocMeta.belongsTo.find(bt => bt.rtn === args.args.childTable)
await this.xcRelationColumnDelete({
...args,
args: {
parentTable: rel1.rtn,
parentColumn: rel1.rcn,
childTable: rel1.tn,
childColumn: rel1.cn,
type: 'bt',
}
}, req, false)
await this.xcRelationColumnDelete({
...args,
args: {
parentTable: rel2.rtn,
parentColumn: rel2.rcn,
childTable: rel2.tn,
childColumn: rel2.cn,
type: 'bt',
}
}, req, false);
const opArgs = {
...args,
args: assocMeta,
api: 'tableDelete',
sqlOpPlus: true,
};
const out = await this.projectMgr.getSqlMgr({id: projectId}).handleRequest('tableDelete', opArgs);
if (this.listener) {
await this.listener({
req: opArgs,
res: out,
user: req.user,
ctx: {req}
});
} }
break;
} }
break; // this.xcMeta.commit()
} // } catch (e) {
// this.xcMeta.rollback(e)
// throw e;
// }
} }

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

@ -1190,7 +1190,7 @@ export class RestApiBuilder extends BaseApiBuilder<Noco> {
this.log('Within relation delete event - \'%s\' ==> \'%s\'', tnp, tnc) this.log('Within relation delete event - \'%s\' ==> \'%s\'', tnp, tnc)
this.deleteRoutesForTables([tnp, tnc]) this.deleteRoutesForTables([tnp, tnc])
const relations = await this.getRelationList(); const relations = await this.getXcRelationList();
{ {
const hasMany = this.extractHasManyRelationsOfTable(relations, tnp); const hasMany = this.extractHasManyRelationsOfTable(relations, tnp);

2
packages/nocodb/src/lib/sqlMgr/SqlMgr.ts

@ -101,7 +101,7 @@ export default class SqlMgr {
private project_id: any; private project_id: any;
private _project: any; private _project: any;
// @ts-ignore // @ts-ignore
private _migrator: any; private _migrator: KnexMigrator;
// @ts-ignore // @ts-ignore
private logsRef: any; private logsRef: any;
private currentProjectJson: any; private currentProjectJson: any;

Loading…
Cancel
Save