Browse Source

feat: relation column delete

Signed-off-by: Pranav C <61551451+pranavxc@users.noreply.github.com>
pull/341/head
Pranav C 3 years ago
parent
commit
f33fe5c514
  1. 4
      packages/nc-gui/components/project/spreadsheet/components/editableCell/dateTimePickerCell.vue
  2. 34
      packages/nc-gui/components/project/spreadsheet/components/expandedForm.vue
  3. 9
      packages/nc-gui/components/project/spreadsheet/components/virtualCell.vue
  4. 90
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/belogsToCell.vue
  5. 135
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/components/listChildItems.vue
  6. 121
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/components/listChildItemsModal.vue
  7. 2
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/components/listItems.vue
  8. 78
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/hasManyCell.vue
  9. 71
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/manyToManyCell.vue
  10. 80
      packages/nc-gui/components/project/spreadsheet/components/virtualHeaderCell.vue
  11. 13
      packages/nc-gui/components/project/spreadsheet/rowsXcDataTable.vue
  12. 3
      packages/nc-gui/components/project/spreadsheet/views/xcGridView.vue
  13. 1
      packages/nc-gui/store/sqlMgr.js
  14. 18
      packages/nocodb/src/lib/noco/NcProjectBuilder.ts
  15. 23
      packages/nocodb/src/lib/noco/common/BaseApiBuilder.ts
  16. 126
      packages/nocodb/src/lib/noco/meta/NcMetaMgr.ts
  17. 6
      packages/nocodb/src/lib/noco/rest/RestApiBuilder.ts

4
packages/nc-gui/components/project/spreadsheet/components/editableCell/dateTimePickerCell.vue

@ -30,12 +30,14 @@ export default {
computed: { computed: {
localState: { localState: {
get() { get() {
// todo : time value correction
if(/^\d{6,}$/.test(this.value)){ if(/^\d{6,}$/.test(this.value)){
return new Date(+this.value); return new Date(+this.value);
} }
return /\dT\d/.test(this.value) ? new Date(this.value.replace(/(\d)T(?=\d)/, '$1 ')) : new Date(this.value); return /\dT\d/.test(this.value) ? new Date(this.value.replace(/(\d)T(?=\d)/, '$1 ')) : this.value;
}, },
set(val) { set(val) {
// if(/^\d{6,}$/.test(this.value)){ // if(/^\d{6,}$/.test(this.value)){

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

@ -51,7 +51,6 @@
<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"> <span v-if="col.virtual">
{{ col._cn }}
</span> </span>
<header-cell <header-cell
v-else v-else
@ -66,15 +65,16 @@
v-if="col.virtual" v-if="col.virtual"
:disabledColumns="disabledColumns" :disabledColumns="disabledColumns"
:column="col" :column="col"
:row="localState" :row="value"
:nodes="nodes" :nodes="nodes"
:meta="meta" :meta="meta"
:api="api" :api="api"
:active="true" :active="true"
:sql-ui="sqlUi" :sql-ui="sqlUi"
@loadTableData="reload"
:is-new="isNew" :is-new="isNew"
:is-form="true"
@updateCol="updateCol" @updateCol="updateCol"
@newRecordsSaved="$listeners.loadTableData"
></virtual-cell> ></virtual-cell>
<div <div
@ -204,7 +204,6 @@ export default {
value: Object, value: Object,
meta: Object, meta: Object,
sqlUi: [Object, Function], sqlUi: [Object, Function],
selectedRowMeta: Object,
table: String, table: String,
primaryValueColumn: String, primaryValueColumn: String,
api: [Object], api: [Object],
@ -236,7 +235,7 @@ export default {
localState: {}, localState: {},
changedColumns: {}, changedColumns: {},
comment: null, comment: null,
showSystemFields: false showSystemFields: false,
}), }),
created() { created() {
this.localState = {...this.value} this.localState = {...this.value}
@ -278,8 +277,8 @@ export default {
&& (rowObj[columnObj._cn] === undefined || rowObj[columnObj._cn] === null) && (rowObj[columnObj._cn] === undefined || rowObj[columnObj._cn] === null)
&& !columnObj.default); && !columnObj.default);
}, },
updateCol(row, _cn, pid) { updateCol(_row, _cn, pid) {
this.$set(row, _cn, pid) this.$set(this.localState, _cn, pid)
this.$set(this.changedColumns, _cn, true) this.$set(this.changedColumns, _cn, true)
}, },
isYou(email) { isYou(email) {
@ -331,7 +330,7 @@ export default {
const where = this.meta.columns.filter((c) => c.pk).map(c => `(${c._cn},eq,${this.localState[c._cn]})`).join('~and'); const where = this.meta.columns.filter((c) => c.pk).map(c => `(${c._cn},eq,${this.localState[c._cn]})`).join('~and');
this.$set(this, 'changedColumns', {}); this.$set(this, 'changedColumns', {});
// this.localState = await this.api.read(id); // this.localState = await this.api.read(id);
this.localState = (await this.api.list({...(this.queryParams || {}), where}) || [{}])[0]; this.localState = (await this.api.list({...(this.queryParams || {}), where}) || [{}])[0] || this.localState;
if (!this.isNew && this.toggleDrawer) { if (!this.isNew && this.toggleDrawer) {
this.getAuditsAndComments() this.getAuditsAndComments()
} }
@ -359,6 +358,9 @@ export default {
} }
}, },
computed: { computed: {
primaryKey() {
return this.isNew ? '' : this.meta.columns.filter((c) => c.pk).map(c => this.localState[c._cn]).join('___');
},
edited() { edited() {
return !!Object.keys(this.changedColumns).length; return !!Object.keys(this.changedColumns).length;
}, },
@ -373,6 +375,9 @@ export default {
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))) || [];
} }
}, },
isChanged() {
return Object.values(this.changedColumns).some(Boolean)
}
} }
} }
</script> </script>
@ -482,13 +487,14 @@ h5 {
.comment-box.focus { .comment-box.focus {
border: 1px solid #4185f4; border: 1px solid #4185f4;
} }
.required > div > label + *{
border:1px solid red; .required > div > label + * {
border: 1px solid red;
border-radius: 4px; border-radius: 4px;
min-height: 42px; //min-height: 42px;
display: flex; //display: flex;
align-items: center; //align-items: center;
justify-content: flex-end; //justify-content: flex-end;
background: var(--v-backgroundColorDefault-base); background: var(--v-backgroundColorDefault-base);
} }
</style> </style>

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

@ -11,6 +11,7 @@
:active="active" :active="active"
:sql-ui="sqlUi" :sql-ui="sqlUi"
:is-new="isNew" :is-new="isNew"
:is-form="isForm"
v-on="$listeners" v-on="$listeners"
/> />
<many-to-many-cell <many-to-many-cell
@ -23,8 +24,9 @@
:sql-ui="sqlUi" :sql-ui="sqlUi"
:active="active" :active="active"
:is-new="isNew" :is-new="isNew"
v-on="$listeners"
:api="api" :api="api"
:is-form="isForm"
v-on="$listeners"
/> />
<belongs-to-cell <belongs-to-cell
:disabled-columns="disabledColumns" :disabled-columns="disabledColumns"
@ -38,6 +40,7 @@
:api="api" :api="api"
:sql-ui="sqlUi" :sql-ui="sqlUi"
:is-new="isNew" :is-new="isNew"
:is-form="isForm"
v-on="$listeners" v-on="$listeners"
/> />
</v-lazy> </v-lazy>
@ -70,6 +73,10 @@ export default {
type: Boolean, type: Boolean,
default: false default: false
}, },
isForm: {
type: Boolean,
default: false
},
disabledColumns: Object disabledColumns: Object
}, },
computed: { computed: {

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

@ -1,25 +1,26 @@
<template> <template>
<div class="d-flex"> <div class="d-flex">
<div class="d-flex align-center img-container flex-grow-1 hm-items"> <template v-if="!isForm">
<template v-if="value || localState"> <div class="d-flex align-center img-container flex-grow-1 hm-items">
<item-chip <template v-if="value || localState">
:active="active" <item-chip
:item="value" :active="active"
:value="Object.values(value || localState)[1]" :item="value"
:key="i" :value="Object.values(value || localState)[1]"
@edit="editParent" :key="i"
@unlink="unlink" @edit="editParent"
></item-chip> @unlink="unlink"
</template> ></item-chip>
</div> </template>
<div class=" align-center justify-center px-1 flex-shrink-1" </div>
:class="{'d-none': !active, 'd-flex':active }"> <div class=" align-center justify-center px-1 flex-shrink-1"
<x-icon small :color="['primary','grey']" @click="showNewRecordModal">{{ :class="{'d-none': !active, 'd-flex':active }">
value ? 'mdi-arrow-expand' : 'mdi-plus' <x-icon small :color="['primary','grey']" @click="showNewRecordModal">{{
}} value ? 'mdi-arrow-expand' : 'mdi-plus'
</x-icon> }}
</div> </x-icon>
</div>
</template>
<list-items <list-items
v-if="newRecordModal" v-if="newRecordModal"
:size="10" :size="10"
@ -33,6 +34,26 @@
:query-params="parentQueryParams" :query-params="parentQueryParams"
/> />
<list-child-items
ref="childList"
v-if="parentMeta && isForm"
:local-state="localState? [localState] : []"
:is-new="isNew"
:size="10"
:parent-meta="parentMeta"
:meta="parentMeta"
:primary-col="parentPrimaryCol"
:primary-key="parentPrimaryKey"
:api="parentApi"
:query-params="{
...parentQueryParams,
where: `(${parentPrimaryKey},eq,${parentId})`
}"
@new-record="showNewRecordModal"
@edit="editParent"
@unlink="unlink"
:bt="true"
/>
<v-dialog <v-dialog
:overlay-opacity="0.8" :overlay-opacity="0.8"
@ -74,11 +95,13 @@
import ApiFactory from "@/components/project/spreadsheet/apis/apiFactory"; import ApiFactory from "@/components/project/spreadsheet/apis/apiFactory";
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";
export default { export default {
name: "belongs-to-cell", name: "belongs-to-cell",
components: {ItemChip, ListItems}, components: {ListChildItems, ItemChip, ListItems},
props: { props: {
isForm: Boolean,
value: [Object, Array], value: [Object, Array],
meta: [Object], meta: [Object],
bt: Object, bt: Object,
@ -104,7 +127,11 @@ export default {
expandFormModal: false, expandFormModal: false,
localState: null, localState: null,
}), }),
async mounted() {
if (this.isForm) {
await this.loadParentMeta()
}
},
methods: { methods: {
async onParentSave(parent) { async onParentSave(parent) {
if (this.isNewParent) { if (this.isNewParent) {
@ -136,6 +163,9 @@ export default {
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('___');
await this.api.update(id, {[_cn]: null}, this.row) await this.api.update(id, {[_cn]: null}, this.row)
this.$emit('loadTableData') this.$emit('loadTableData')
if (this.isForm && this.$refs.childList) {
this.$refs.childList.loadData();
}
}, },
async showParentListModal() { async showParentListModal() {
this.parentListModal = true; this.parentListModal = true;
@ -156,9 +186,11 @@ export default {
} else { } else {
const id = this.parentMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___'); const id = this.parentMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___');
await this.parentApi.delete(id) await this.parentApi.delete(id)
this.showParentListModal();
this.dialogShow = false; this.dialogShow = false;
this.$emit('loadTableData') this.$emit('loadTableData')
if (this.isForm && this.$refs.childList) {
this.$refs.childList.loadData();
}
} }
} }
}, },
@ -177,9 +209,8 @@ export default {
async showNewRecordModal() { async showNewRecordModal() {
await this.loadParentMeta(); await this.loadParentMeta();
this.newRecordModal = true; this.newRecordModal = true;
// this.list = await this.parentApi.paginatedList({})
}, },
async addParentToChild(parent) { async addParentToChild(parent) {
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('___');
@ -196,9 +227,13 @@ export default {
}, { }, {
[_cn]: parent[this.parentPrimaryKey] [_cn]: parent[this.parentPrimaryKey]
}); });
this.newRecordModal = false; this.newRecordModal = false;
this.$emit('loadTableData') this.$emit('loadTableData')
if (this.isForm && this.$refs.childList) {
this.$refs.childList.loadData();
}
}, },
async editParent(parent) { async editParent(parent) {
await this.loadParentMeta(); await this.loadParentMeta();
@ -216,6 +251,9 @@ export default {
ApiFactory.create(this.$store.getters['project/GtrProjectType'], ApiFactory.create(this.$store.getters['project/GtrProjectType'],
this.parentMeta && this.parentMeta._tn, this.parentMeta && this.parentMeta.columns, this) : null; this.parentMeta && this.parentMeta._tn, this.parentMeta && this.parentMeta.columns, this) : null;
}, },
parentId() {
return this.value && this.parentMeta && this.parentMeta.columns.filter((c) => c.pk).map(c => this.value[c._cn]).join('___')
},
parentPrimaryCol() { parentPrimaryCol() {
return this.parentMeta && (this.parentMeta.columns.find(c => c.pv) || {})._cn return this.parentMeta && (this.parentMeta.columns.find(c => c.pv) || {})._cn
}, },
@ -285,8 +323,6 @@ export default {
} }
.hm-items { .hm-items {
//min-width: 200px;
//max-width: 400px;
flex-wrap: wrap; flex-wrap: wrap;
row-gap: 3px; row-gap: 3px;
gap: 3px; gap: 3px;

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

@ -1,65 +1,69 @@
<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="backgroundColor">
<v-card-title class="textColor--text mx-2">{{ meta ? meta._tn : 'Children' }} <v-card-title class="textColor--text mx-2">{{ meta ? meta._tn : 'Children' }}
<v-spacer> <v-spacer>
</v-spacer> </v-spacer>
<v-btn small class="caption" color="primary" @click="$emit('new-record')"> <v-btn small class="caption" color="primary" @click="$emit('new-record')">
<v-icon small>mdi-plus</v-icon>&nbsp; <v-icon small>{{ bt ? 'mdi-pencil' : 'mdi-plus' }}</v-icon>&nbsp;
Add Record {{ bt ? 'Select' : 'Add' }} Record
</v-btn> </v-btn>
</v-card-title> </v-card-title>
<v-card-text> <v-card-text>
<div class="items-container"> <div class="items-container pt-2">
<template v-if="data && data.list"> <template v-if="(data && data.list && data.list.length) || (localState && localState.length)">
<v-card <v-card
v-for="(ch,i) in data.list" v-for="(ch,i) in ((data && data.list) || localState)"
class="ma-2 child-list-modal child-card" class="ma-2 child-list-modal child-card"
outlined outlined
:key="i" :key="i"
@click="$emit('edit',ch)" @click="$emit('edit',ch)"
> >
<div class="remove-child-icon d-flex align-center"> <div class="remove-child-icon d-flex align-center">
<x-icon <x-icon
:tooltip="`Unlink this '${meta._tn}' from '${parentMeta._tn}'`" :tooltip="`Unlink this '${meta._tn}' from '${parentMeta._tn}'`"
:color="['error','grey']" :color="['error','grey']"
small small
@click.stop="$emit('unlink',ch,i)" @click.stop="$emit('unlink',ch,i)"
icon.class="mr-1 mt-n1" icon.class="mr-1 mt-n1"
>mdi-link-variant-remove >mdi-link-variant-remove
</x-icon> </x-icon>
<x-icon <x-icon
v-if="!mm" v-if="!mm && !bt"
:tooltip="`Delete row in '${meta._tn}'`" :tooltip="`Delete row in '${meta._tn}'`"
:color="['error','grey']" :color="['error','grey']"
small small
@click.stop="$emit('delete',ch,i)" @click.stop="$emit('delete',ch,i)"
>mdi-delete-outline >mdi-delete-outline
</x-icon> </x-icon>
</div> </div>
<v-card-title class="primary-value textColor--text text--lighten-2">{{ ch[primaryCol] }} <v-card-title class="primary-value textColor--text text--lighten-2">{{ ch[primaryCol] }}
<span class="grey--text caption primary-key" <span class="grey--text caption primary-key"
v-if="primaryKey">(Primary Key : {{ ch[primaryKey] }})</span> v-if="primaryKey">(Primary Key : {{ ch[primaryKey] }})</span>
</v-card-title> </v-card-title>
</v-card> </v-card>
</template> </template>
<div v-else-if="data" class="text-center pt-6 pb-4 textLight--text">
No item{{ bt ? '' : 's' }} found
</div> </div>
</v-card-text> </div>
<v-card-actions class="justify-center py-2 flex-column"> </v-card-text>
<pagination <v-card-actions class="justify-center py-2 flex-column">
v-if="data && data.list" <pagination
:size="size" v-if="!bt && data && data.list"
:count="data.count" :size="size"
v-model="page" :count="data.count"
@input="loadData" v-model="page"
class="mb-3" @input="loadData"
></pagination> class="mb-3"
</v-card-actions> ></pagination>
</v-card> </v-card-actions>
</v-dialog> </v-card>
<!-- </v-dialog>-->
</template> </template>
@ -70,6 +74,9 @@ export default {
name: "listChildItems", name: "listChildItems",
components: {Pagination}, components: {Pagination},
props: { props: {
bt: Boolean,
localState: [Array],
isNew: Boolean,
value: Boolean, value: Boolean,
title: { title: {
type: String, type: String,
@ -87,20 +94,19 @@ export default {
parentMeta: Object, parentMeta: Object,
size: Number, size: Number,
api: [Object, Function], api: [Object, Function],
mm:[Object, Boolean] mm: [Object, Boolean]
}, },
data: () => ({ data: () => ({
data: null, data: null,
page: 1 page: 1
}), }),
mounted() { mounted() {
this.loadData(); this.loadData();
}, },
methods: { methods: {
async loadData() { async loadData() {
if (!this.api) return; if (!this.api || this.isNew) return;
this.data = await this.api.paginatedList({
this.data = await this.api.paginatedList({
limit: this.size, limit: this.size,
offset: this.size * (this.page - 1), offset: this.size * (this.page - 1),
...this.queryParams ...this.queryParams
@ -115,6 +121,11 @@ export default {
return this.value; return this.value;
} }
} }
},
watch: {
queryParams() {
this.loadData();
}
} }
} }
</script> </script>

121
packages/nc-gui/components/project/spreadsheet/components/virtualCell/components/listChildItemsModal.vue

@ -0,0 +1,121 @@
<template>
<v-dialog v-model="show" width="600">
<list-child-items
v-if="show"
ref="child"
:local-state="localState"
:is-new="isNew"
:size="10"
:meta="meta"
:parent-meta="meta"
:primary-col="primaryCol"
:primary-key="primaryKey"
:api="api"
:query-params="queryParams"
v-bind="$attrs"
v-on="$listeners"
/>
</v-dialog>
</template>
<script>
import Pagination from "@/components/project/spreadsheet/components/pagination";
import ListChildItems from "@/components/project/spreadsheet/components/virtualCell/components/listChildItems";
export default {
name: "listChildItemsModal",
components: {ListChildItems, Pagination},
props: {
localState: Array,
isNew: Boolean,
value: Boolean,
title: {
type: String,
default: 'Link Record'
},
queryParams: {
type: Object,
default() {
return {};
}
},
primaryKey: String,
primaryCol: String,
meta: Object,
parentMeta: Object,
size: Number,
api: [Object, Function],
mm: [Object, Boolean]
},
data: () => ({
data: null,
page: 1
}),
mounted() {
},
methods: {
async loadData() {
if (this.$refs && this.$refs.child) {
this.$refs.child.loadData();
}
}
},
computed: {
show: {
set(v) {
this.$emit('input', v)
}, get() {
return this.value;
}
}
}
}
</script>
<style scoped lang="scss">
.child-list-modal {
position: relative;
.remove-child-icon {
position: absolute;
right: 10px;
top: 10px;
bottom: 10px;
opacity: 0;
}
&:hover .remove-child-icon {
opacity: 1;
}
}
</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/>.
*
*/
-->

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

@ -46,7 +46,7 @@
</template> </template>
<div v-else class="text-center py-15 textLight--text"> <div v-else-if="data" class="text-center py-15 textLight--text">
No items found No items found
</div> </div>
</div> </div>

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

@ -1,22 +1,24 @@
<template> <template>
<div class="d-flex"> <div class="d-flex">
<div class="d-flex align-center img-container flex-grow-1 hm-items"> <template v-if="!isForm">
<template v-if="value||localState"> <div class="d-flex align-center img-container flex-grow-1 hm-items">
<item-chip <template v-if="value||localState">
v-for="(ch,i) in (value|| localState)" <item-chip
:active="active" v-for="(ch,i) in (value|| localState)"
:item="ch" :active="active"
:value="Object.values(ch)[1]" :item="ch"
:key="i" :value="Object.values(ch)[1]"
@edit="editChild" :key="i"
@unlink="unlinkChild" @edit="editChild"
></item-chip> @unlink="unlinkChild"
</template> ></item-chip>
</div> </template>
<div class=" align-center justify-center px-1 flex-shrink-1" :class="{'d-none': !active, 'd-flex':active }"> </div>
<x-icon small :color="['primary','grey']" @click="showNewRecordModal">mdi-plus</x-icon> <div class=" align-center justify-center px-1 flex-shrink-1" :class="{'d-none': !active, 'd-flex':active }">
<x-icon x-small :color="['primary','grey']" @click="showChildListModal" class="ml-2">mdi-arrow-expand</x-icon> <x-icon small :color="['primary','grey']" @click="showNewRecordModal">mdi-plus</x-icon>
</div> <x-icon x-small :color="['primary','grey']" @click="showChildListModal" class="ml-2">mdi-arrow-expand</x-icon>
</div>
</template>
<list-items <list-items
v-if="newRecordModal" v-if="newRecordModal"
@ -35,9 +37,12 @@
}"/> }"/>
<list-child-items <list-child-items
:is="isForm ? 'list-child-items' : 'list-child-items-modal'"
ref="childList" ref="childList"
v-if="childListModal" v-if="childMeta && (childListModal || isForm)"
v-model="childListModal" v-model="childListModal"
:local-state.sync="localState"
:is-new="isNew"
:size="10" :size="10"
:meta="childMeta" :meta="childMeta"
:parent-meta="meta" :parent-meta="meta"
@ -106,6 +111,8 @@ import Pagination from "@/components/project/spreadsheet/components/pagination";
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: "has-many-cell", name: "has-many-cell",
@ -114,7 +121,8 @@ export default {
ItemChip, ItemChip,
ListItems, ListItems,
Pagination, Pagination,
DlgLabelSubmitCancel DlgLabelSubmitCancel,
listChildItemsModal
}, },
props: { props: {
value: [Object, Array], value: [Object, Array],
@ -124,7 +132,8 @@ export default {
row: [Object], row: [Object],
sqlUi: [Object, Function], sqlUi: [Object, Function],
active: Boolean, active: Boolean,
isNew: Boolean isNew: Boolean,
isForm: Boolean,
}, },
data: () => ({ data: () => ({
newRecordModal: false, newRecordModal: false,
@ -138,7 +147,11 @@ export default {
isNewChild: false, isNewChild: false,
localState: [] localState: []
}), }),
async mounted() {
if (this.isForm) {
await this.loadChildMeta()
}
},
methods: { methods: {
async showChildListModal() { async showChildListModal() {
await this.loadChildMeta(); await this.loadChildMeta();
@ -157,7 +170,7 @@ export default {
await this.childApi.delete(id) await this.childApi.delete(id)
this.dialogShow = false; this.dialogShow = false;
this.$emit('loadTableData') this.$emit('loadTableData')
if (this.childListModal && this.$refs.childList) { if ((this.childListModal || this.isForm) && this.$refs.childList) {
this.$refs.childList.loadData(); this.$refs.childList.loadData();
} }
} catch (e) { } catch (e) {
@ -167,6 +180,11 @@ export default {
} }
}, },
async unlinkChild(child) { async unlinkChild(child) {
if (this.isNew) {
this.localState.splice(this.localState.indexOf(child), 1)
return;
}
await this.loadChildMeta(); await this.loadChildMeta();
const column = this.childMeta.columns.find(c => c.cn === this.hm.cn); const column = this.childMeta.columns.find(c => c.cn === this.hm.cn);
if (column.rqd) { if (column.rqd) {
@ -177,7 +195,7 @@ export default {
const id = this.childMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___'); const id = this.childMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___');
await this.childApi.update(id, {[_cn]: null}, child) await this.childApi.update(id, {[_cn]: null}, child)
this.$emit('loadTableData') this.$emit('loadTableData')
if (this.childListModal && this.$refs.childList) { if ((this.childListModal || this.isForm) && this.$refs.childList) {
this.$refs.childList.loadData(); this.$refs.childList.loadData();
} }
// } // }
@ -214,12 +232,12 @@ export default {
await this.childApi.update(id, { await this.childApi.update(id, {
[_cn]: this.parentId [_cn]: this.parentId
}, { }, {
[_cn]: child[this.childPrimaryKey] [_cn]: child[this.childForeignKey]
}); });
this.$emit('loadTableData') this.$emit('loadTableData')
if (this.childListModal) { if ((this.childListModal || this.isForm) && this.$refs.childList) {
await this.showChildListModal() this.$refs.childList.loadData();
} }
}, },
async editChild(child) { async editChild(child) {
@ -295,12 +313,16 @@ 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)
} }
this.$emit('newRecordsSaved')
} }
} }
} }

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

@ -1,24 +1,24 @@
<template> <template>
<div class="d-flex"> <div class="d-flex">
<template v-if="!isForm">
<div class="d-flex align-center img-container flex-grow-1 hm-items">
<div class="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" :item="v"
:item="v" :value="Object.values(v)[2]"
:value="Object.values(v)[2]" :key="j"
:key="j" @edit="editChild"
@edit="editChild" @unlink="unlinkChild"
@unlink="unlinkChild" ></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=" 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> </template>
<list-items <list-items
@ -37,9 +37,11 @@
:query-params="childQueryParams"/> :query-params="childQueryParams"/>
<list-child-items <list-child-items
:is="isForm ? 'list-child-items' : 'list-child-items-modal'"
ref="childList" ref="childList"
v-if="childListModal" v-if="childMeta && assocMeta && (isForm || childListModal)"
v-model="childListModal" v-model="childListModal"
:is-new="isNew"
:size="10" :size="10"
:meta="childMeta" :meta="childMeta"
:parent-meta="meta" :parent-meta="meta"
@ -49,6 +51,7 @@
:mm="mm" :mm="mm"
:parent-id="row && row[parentPrimaryKey]" :parent-id="row && row[parentPrimaryKey]"
:query-params="{...childQueryParams, conditionGraph }" :query-params="{...childQueryParams, conditionGraph }"
:local-state="localState"
@new-record="showNewRecordModal" @new-record="showNewRecordModal"
@edit="editChild" @edit="editChild"
@unlink="unlinkChild" @unlink="unlinkChild"
@ -144,6 +147,7 @@ export default {
sqlUi: [Object, Function], sqlUi: [Object, Function],
active: Boolean, active: Boolean,
isNew: Boolean, isNew: Boolean,
isForm: Boolean,
}, },
data: () => ({ data: () => ({
isNewChild: false, isNewChild: false,
@ -159,7 +163,11 @@ export default {
expandFormModal: false, expandFormModal: false,
localState: [] localState: []
}), }),
async mounted() {
if (this.isForm) {
await Promise.all([this.loadChildMeta(), this.loadAssociateTableMeta()]);
}
},
methods: { methods: {
async onChildSave(child) { async onChildSave(child) {
if (this.isNewChild) { if (this.isNewChild) {
@ -172,6 +180,10 @@ export default {
await Promise.all([this.loadChildMeta(), this.loadAssociateTableMeta()]); await Promise.all([this.loadChildMeta(), this.loadAssociateTableMeta()]);
this.childListModal = true; this.childListModal = true;
}, async unlinkChild(child) { }, async unlinkChild(child) {
if (this.isNew) {
this.localState.splice(this.localState.indexOf(child), 1)
return;
}
await Promise.all([this.loadChildMeta(), this.loadAssociateTableMeta()]); await Promise.all([this.loadChildMeta(), this.loadAssociateTableMeta()]);
const _pcn = this.meta.columns.find(c => c.cn === this.mm.cn)._cn; const _pcn = this.meta.columns.find(c => c.cn === this.mm.cn)._cn;
@ -183,9 +195,8 @@ export default {
const id = this.assocMeta.columns.filter((c) => c.cn === apcn || c.cn === accn).map(c => c.cn === apcn ? this.row[_pcn] : child[_ccn]).join('___'); const id = this.assocMeta.columns.filter((c) => c.cn === apcn || c.cn === accn).map(c => c.cn === apcn ? this.row[_pcn] : child[_ccn]).join('___');
await this.assocApi.delete(id) await this.assocApi.delete(id)
this.$emit('loadTableData') this.$emit('loadTableData')
if (this.childListModal && this.$refs.childList) { if ((this.childListModal || this.isForm) && this.$refs.childList) {
this.$refs.childList.loadData(); this.$refs.childList.loadData();
// this.showChildListModal()
} }
}, },
async removeChild(child) { async removeChild(child) {
@ -198,9 +209,11 @@ export default {
} else { } else {
const id = this.childMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___'); const id = this.childMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___');
await this.childApi.delete(id) await this.childApi.delete(id)
this.showChildListModal();
this.dialogShow = false; this.dialogShow = false;
this.$emit('loadTableData') this.$emit('loadTableData')
if ((this.childListModal || this.isForm) && this.$refs.childList) {
this.$refs.childList.loadData();
}
} }
} }
}, },
@ -231,7 +244,7 @@ export default {
async showNewRecordModal() { async showNewRecordModal() {
await Promise.all([this.loadChildMeta(), this.loadAssociateTableMeta()]); await Promise.all([this.loadChildMeta(), this.loadAssociateTableMeta()]);
this.newRecordModal = true; this.newRecordModal = true;
// this.list = await this.childApi.paginatedList({}) // this.list = await this.c hildApi.paginatedList({})
}, },
async addChildToParent(child) { async addChildToParent(child) {
if (this.isNew) { if (this.isNew) {
@ -257,6 +270,9 @@ export default {
console.log(e) console.log(e)
} }
this.newRecordModal = false; this.newRecordModal = false;
if ((this.childListModal || this.isForm) && this.$refs.childList) {
this.$refs.childList.loadData();
}
}, },
@ -348,12 +364,13 @@ export default {
}, },
}, },
watch: { watch: {
isNew(n, o) { async isNew(n, o) {
if (!n && o) { if (!n && o) {
let child; let child;
while (child = this.localState.pop()) { while (child = this.localState.pop()) {
this.addChildToParent(child) await this.addChildToParent(child)
} }
this.$emit('newRecordsSaved')
} }
} }
} }

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

@ -33,23 +33,66 @@
</template> </template>
<span class="caption font-weight-bold">Primary value will be shown in place of primary key</span> <span class="caption font-weight-bold">Primary value will be shown in place of primary key</span>
</v-tooltip> </v-tooltip>
</v-list-item> </v-list-item> -->
<v-list-item @click="columnDeleteDialog = true"> <v-list-item @click="columnDeleteDialog = true">
<x-icon small class="mr-1" color="error">mdi-delete-outline</x-icon> <x-icon small class="mr-1" color="error">mdi-delete-outline</x-icon>
<span class="caption">Delete</span> <span class="caption">Delete</span>
</v-list-item>--> </v-list-item>
</v-list> </v-list>
</v-menu> </v-menu>
<v-dialog v-model="columnDeleteDialog" max-width="500"
persistent>
<v-card>
<v-card-title class="grey darken-2 subheading white--text">Confirm</v-card-title>
<v-divider></v-divider>
<v-card-text class="mt-4 title">Do you want to delete <span class="font-weight-bold">'{{
column.cn
}}'</span> column ?
</v-card-text>
<v-divider></v-divider>
<v-card-actions class="d-flex pa-4">
<v-spacer></v-spacer>
<v-btn small @click="columnDeleteDialog = false">Cancel</v-btn>
<v-btn small color="error" @click="deleteColumn">Confirm</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
props: ['column'], props: ['column', 'nodes'],
name: "virtualHeaderCell", name: "virtualHeaderCell",
data: () => ({}), data: () => ({
columnDeleteDialog: false
}),
computed: { computed: {
type() {
if (this.column.bt) return 'bt'
if (this.column.hm) return 'hm'
if (this.column.mm) return 'mm'
},
childColumn() {
if (this.column.bt) return this.column.bt.cn
if (this.column.hm) return this.column.hm.cn
if (this.column.mm) return this.column.mm.rcn
},
childTable() {
if (this.column.bt) return this.column.bt.tn
if (this.column.hm) return this.column.hm.tn
if (this.column.mm) return this.column.mm.rtn
},
parentTable() {
if (this.column.bt) return this.column.bt.rtn
if (this.column.hm) return this.column.hm.rtn
if (this.column.mm) return this.column.mm.tn
},
parentColumn() {
if (this.column.bt) return this.column.bt.rcn
if (this.column.hm) return this.column.hm.rcn
if (this.column.mm) return this.column.mm.cn
},
tooltipMsg() { tooltipMsg() {
if (!this.column) return ''; if (!this.column) return '';
if (this.column.hm) { if (this.column.hm) {
@ -60,6 +103,27 @@ export default {
return `'${this.column.bt._tn}' belongs to '${this.column.bt._rtn}'` return `'${this.column.bt._tn}' belongs to '${this.column.bt._rtn}'`
} }
} }
}, methods: {
async deleteColumn() {
try {
const column = {...this.column, cno: this.column.cn};
await this.$store.dispatch('sqlMgr/ActSqlOpPlus', [{
env: this.nodes.env,
dbAlias: this.nodes.dbAlias
}, "xcRelationColumnDelete", {
type: this.type,
childColumn: this.childColumn,
childTable: this.childTable,
parentTable: this.parentTable,
parentColumn: this.parentColumn,
assocTable: this.column.mm && this.column.mm.vtn
}]);
this.$emit('saved');
this.columnDeleteDialog = false;
} catch (e) {
console.log(e)
}
}
} }
} }
</script> </script>

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

@ -399,12 +399,11 @@
v-model="showExpandModal"> v-model="showExpandModal">
<expanded-form <expanded-form
:key="selectedExpandRowIndex"
:db-alias="nodes.dbAlias" :db-alias="nodes.dbAlias"
:has-many="hasMany" :has-many="hasMany"
:belongs-to="belongsTo" :belongs-to="belongsTo"
v-if="selectedExpandRowIndex != null && data[selectedExpandRowIndex]" v-if="selectedExpandRowIndex != null && data[selectedExpandRowIndex]"
@cancel="showExpandModal = false;"
@input="showExpandModal = false; (data[selectedExpandRowIndex] && data[selectedExpandRowIndex].rowMeta && delete data[selectedExpandRowIndex].rowMeta.new)"
:table="table" :table="table"
v-model="data[selectedExpandRowIndex].row" v-model="data[selectedExpandRowIndex].row"
:oldRow.sync="data[selectedExpandRowIndex].oldRow" :oldRow.sync="data[selectedExpandRowIndex].oldRow"
@ -414,10 +413,13 @@
:sql-ui="sqlUi" :sql-ui="sqlUi"
:primary-value-column="primaryValueColumn" :primary-value-column="primaryValueColumn"
:api="api" :api="api"
@commented="reloadComments"
:availableColumns="availableColumns" :availableColumns="availableColumns"
:nodes="nodes" :nodes="nodes"
:query-params="queryParams" :query-params="queryParams"
@cancel="showExpandModal = false;"
@input="showExpandModal = false; (data[selectedExpandRowIndex] && data[selectedExpandRowIndex].rowMeta && delete data[selectedExpandRowIndex].rowMeta.new) ; loadTableData()"
@commented="reloadComments"
@loadTableData="loadTableData"
></expanded-form> ></expanded-form>
</v-dialog> </v-dialog>
@ -820,7 +822,7 @@ export default {
const {rowMeta} = this.data[this.data.length - 1]; const {rowMeta} = this.data[this.data.length - 1];
this.expandRow(this.data.length - 1, rowMeta) this.expandRow(this.data.length - 1, rowMeta)
} }
this.save() // this.save()
}, },
@ -859,7 +861,7 @@ export default {
}, },
loadTableDataDeb: debounce(async function (self) { loadTableDataDeb: debounce(async function (self) {
await self.loadTableDataFn() await self.loadTableDataFn()
}, 100), }, 200),
loadTableData() { loadTableData() {
this.loadTableDataDeb(this) this.loadTableDataDeb(this)
}, },
@ -918,7 +920,6 @@ export default {
return this.meta && this.meta._tn ? ApiFactory.create(this.$store.getters['project/GtrProjectType'], this.meta && this.meta._tn, this.meta && this.meta.columns, this) : null; return this.meta && this.meta._tn ? ApiFactory.create(this.$store.getters['project/GtrProjectType'], this.meta && this.meta._tn, this.meta && this.meta.columns, this) : null;
} }
}, },
} }
</script> </script>

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

@ -27,6 +27,7 @@
<virtual-header-cell v-if="col.virtual" <virtual-header-cell v-if="col.virtual"
:column="col" :column="col"
:nodes="nodes"
/> />
@ -132,8 +133,8 @@
:api="api" :api="api"
:active="selected.col === col && selected.row === row" :active="selected.col === col && selected.row === row"
:sql-ui="sqlUi" :sql-ui="sqlUi"
v-on="$listeners"
:is-new="rowMeta.new" :is-new="rowMeta.new"
v-on="$listeners"
@updateCol="(...args) => updateCol(...args, columnObj.bt && meta.columns.find( c => c.cn === columnObj.bt.cn), col, row)" @updateCol="(...args) => updateCol(...args, columnObj.bt && meta.columns.find( c => c.cn === columnObj.bt.cn), col, row)"
></virtual-cell> ></virtual-cell>

1
packages/nc-gui/store/sqlMgr.js

@ -263,6 +263,7 @@ function translateUiToLibCall(args, op, opArgs) {
case 'relationDelete': case 'relationDelete':
case 'xcVirtualRelationDelete': case 'xcVirtualRelationDelete':
case 'xcRelationColumnDelete':
data.type = "Relation delete"; data.type = "Relation delete";
data.title = ''; data.title = '';
data.module = ""; data.module = "";

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

@ -128,13 +128,23 @@ export default class NcProjectBuilder {
}); });
console.log(`Added new relation between : ${data.req.args.parentTable} ==> ${data.req.args.childTable}`) console.log(`Added new relation between : ${data.req.args.parentTable} ==> ${data.req.args.childTable}`)
break; break;
case 'xcRelationColumnDelete':
case 'xcVirtualRelationDelete': await curBuilder.onRelationCreate(data.req.args.parentTable, data.req.args.childTable, {
await curBuilder.onRelationDelete(data.req.args.parentTable, data.req.args.childTable, {
...data.req.args, ...data.req.args,
virtual: true virtual: true
}); });
console.log(`Deleted relation between : ${data.req.args.parentTable} ==> ${data.req.args.childTable}`) console.log(`Added new relation between : ${data.req.args.parentTable} ==> ${data.req.args.childTable}`)
break;
case 'xcVirtualRelationDelete':
if(data.req.args?.type === 'mm'){
curBuilder.onManyToManyRelationDelete(data.req.args.parentTable, data.req.args.childTable)
}
// await curBuilder.onRelationDelete(data.req.args.parentTable, data.req.args.childTable, {
// ...data.req.args,
// virtual: true
// });
// console.log(`Deleted relation between : ${data.req.args.parentTable} ==> ${data.req.args.childTable}`)
break; break;

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

@ -366,7 +366,7 @@ export default abstract class BaseApiBuilder<T extends Noco> implements XcDynami
this.baseLog(`onTableUpdate : Generating new model meta for '%s' table`, tn) this.baseLog(`onTableUpdate : Generating new model meta for '%s' table`, tn)
/* create models from table */ /* create models from table */
const newMeta:any = ModelXcMetaFactory.create(this.connectionConfig, {dir: '', ctx, filename: ''}).getObject(); const newMeta: any = ModelXcMetaFactory.create(this.connectionConfig, {dir: '', ctx, filename: ''}).getObject();
/* get ACL row */ /* get ACL row */
@ -750,6 +750,27 @@ export default abstract class BaseApiBuilder<T extends Noco> implements XcDynami
return this.getManyToManyRelations({parent, child}) return this.getManyToManyRelations({parent, child})
} }
public async onManyToManyRelationDelete(parent: string, child: string, _args?: any) {
const parentMeta = this.metas[parent];
const childMeta = this.metas[child];
parentMeta.manyToMany = parentMeta.manyToMany.filter(mm => !(mm.tn === parent && mm.rtn === child || mm.tn === child && mm.rtn === child))
childMeta.manyToMany = childMeta.manyToMany.filter(mm => !(mm.tn === parent && mm.rtn === child || mm.tn === child && mm.rtn === child))
parentMeta.v = parentMeta.v.filter(({mm}) => !mm || !(mm.tn === parent && mm.rtn === child || mm.tn === child && mm.rtn === child))
childMeta.v = childMeta.v.filter(({mm}) => !mm || !(mm.tn === parent && mm.rtn === child || mm.tn === child && mm.rtn === child))
for (const meta of [parentMeta, childMeta]) {
await this.xcMeta.metaUpdate(this.projectId, this.dbAlias, 'nc_models', {
mm: 1,
}, {title: meta.tn})
XcCache.del([this.projectId, this.dbAlias, 'table', meta.tn].join('::'));
this.models[meta.tn] = this.getBaseModel(meta)
}
}
protected async loadCommon(): Promise<any> { protected async loadCommon(): Promise<any> {
this.baseLog(`loadCommon :`); this.baseLog(`loadCommon :`);

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

@ -1470,6 +1470,10 @@ export default class NcMetaMgr {
result = await this.xcM2MRelationCreate(args, req); result = await this.xcM2MRelationCreate(args, req);
break; break;
case 'xcRelationColumnDelete':
result = await this.xcRelationColumnDelete(args, req);
break;
case 'xcVirtualRelationDelete': case 'xcVirtualRelationDelete':
result = await this.xcVirtualRelationDelete(args, req); result = await this.xcVirtualRelationDelete(args, req);
break; break;
@ -2448,6 +2452,128 @@ export default class NcMetaMgr {
} }
}
// todo : transaction
protected async xcRelationColumnDelete(args: any, req): Promise<any> {
const dbAlias = this.getDbAlias(args);
const projectId = this.getProjectId(args);
// const parent = await this.xcMeta.metaGet(projectId, dbAlias, 'nc_models', {
// title: args.args.parentTable
// });
// // @ts-ignore
// const parentMeta = JSON.parse(parent.meta);
// @ts-ignore
// todo: compare column
switch (args.args.type) {
case 'bt':
case 'hm':
const child = await this.xcMeta.metaGet(projectId, dbAlias, 'nc_models', {
title: args.args.childTable
});
const childMeta = JSON.parse(child.meta);
const relation = childMeta.belongsTo.find(bt => bt.rtn === args.args.parentTable);
{
const opArgs = {
...args,
args: {
childColumn: relation.cn,
childTable: relation.tn,
parentTable: relation.rtn,
parentColumn: relation.rcn
},
api: 'relationDelete'
};
const 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 originalColumns = childMeta.columns;
const columns = childMeta.columns.map(c => ({...c, ...(relation.cn === c.cn ? {altered: 4} : {})}))
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.parentTable)
await this.xcRelationColumnDelete({
...args,
args: {
parentTable: rel1.rtn,
parentColumn: rel1.rcn,
childTable: rel1.tn,
childColumn: rel1.cn,
}
},req)
await this.xcRelationColumnDelete({
...args,
args: {
parentTable: rel2.rtn,
parentColumn: rel2.rcn,
childTable: rel2.tn,
childColumn: rel2.cn,
}
},req);
const opArgs = {
...args,
args: assocMeta,
api: 'tableDelete'
};
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;
}
} }
protected async xcVirtualRelationDelete(args: any, req): Promise<any> { protected async xcVirtualRelationDelete(args: any, req): Promise<any> {

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

@ -1028,7 +1028,6 @@ export class RestApiBuilder extends BaseApiBuilder<Noco> {
}); });
/* Add new has many relation to virtual columns */ /* Add new has many relation to virtual columns */
oldMeta.v = oldMeta.v || []; oldMeta.v = oldMeta.v || [];
oldMeta.v.push({ oldMeta.v.push({
@ -1211,8 +1210,10 @@ export class RestApiBuilder extends BaseApiBuilder<Noco> {
const oldMeta = JSON.parse(existingModel.meta); const oldMeta = JSON.parse(existingModel.meta);
Object.assign(oldMeta, { Object.assign(oldMeta, {
hasMany: meta.hasMany, hasMany: meta.hasMany,
v: oldMeta.v.filter(({hm}) => !hm || hm.rtn !== tnp || hm.tn !== tnc)
}); });
// todo: delete from query_params
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),
@ -1244,8 +1245,9 @@ export class RestApiBuilder extends BaseApiBuilder<Noco> {
const oldMeta = JSON.parse(existingModel.meta); const oldMeta = JSON.parse(existingModel.meta);
Object.assign(oldMeta, { Object.assign(oldMeta, {
belongsTo: meta.belongsTo, belongsTo: meta.belongsTo,
v: oldMeta.v.filter(({bt}) => !bt || bt.rtn !== tnp || bt.tn !== tnc)
}); });
// todo: delete from query_params
await this.xcMeta.metaUpdate(this.projectId, await this.xcMeta.metaUpdate(this.projectId,
this.dbAlias, this.dbAlias,
'nc_models', { 'nc_models', {

Loading…
Cancel
Save