Browse Source

feat: GQL - M2M

Signed-off-by: Pranav C <61551451+pranavxc@users.noreply.github.com>
pull/341/head
Pranav C 3 years ago
parent
commit
c63c3edee2
  1. 5
      packages/nc-gui/components/project/spreadsheet/components/editColumn.vue
  2. 22
      packages/nc-gui/components/project/spreadsheet/components/editColumn/linkedToAnotherOptions.vue
  3. 4
      packages/nc-gui/components/project/spreadsheet/components/editVirtualColumn.vue
  4. 18
      packages/nc-gui/components/project/spreadsheet/components/expandedForm.vue
  5. 10
      packages/nc-gui/components/project/spreadsheet/components/headerCell.vue
  6. 13
      packages/nc-gui/components/project/spreadsheet/components/virtualCell.vue
  7. 5
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/belogsToCell.vue
  8. 2
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/components/item-chip.vue
  9. 38
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/hasManyCell.vue
  10. 72
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/manyToManyCell.vue
  11. 13
      packages/nc-gui/components/project/spreadsheet/components/virtualHeaderCell.vue
  12. 28
      packages/nc-gui/components/project/spreadsheet/rowsXcDataTable.vue
  13. 1
      packages/nc-gui/helpers/index.js
  14. 14
      packages/nc-gui/store/sqlMgr.js
  15. 39
      packages/nocodb/src/lib/noco/gql/GqlApiBuilder.ts

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

@ -1,7 +1,7 @@
<template> <template>
<v-card min-width="300px" max-width="400px" max-height="95vh" style="overflow: auto" <v-card min-width="300px" max-width="400px" max-height="95vh" style="overflow: auto"
class="elevation-0 card"> class="elevation-0 card">
<v-form v-model="valid"> <v-form ref="form" v-model="valid">
<v-container fluid @click.stop.prevent> <v-container fluid @click.stop.prevent>
<v-row> <v-row>
<v-col cols="12" class="d-flex pb-0"> <v-col cols="12" class="d-flex pb-0">
@ -370,6 +370,9 @@ export default {
this.newColumn = {}; this.newColumn = {};
}, },
async save() { async save() {
if (!this.$refs.form.validate()) {
return;
}
try { try {
if (this.newColumn.uidt === 'Formula') { if (this.newColumn.uidt === 'Formula') {

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

@ -3,7 +3,7 @@
<v-container fluid class="wrapper mb-3"> <v-container fluid class="wrapper mb-3">
<v-row> <v-row>
<v-col> <v-col>
<v-radio-group row hide-details dense v-model="type" class="pt-0 mt-0"> <v-radio-group row hide-details dense v-model="type" @change="$refs.input.validate()" class="pt-0 mt-0">
<v-radio value="hm" label="Has Many"></v-radio> <v-radio value="hm" label="Has Many"></v-radio>
<v-radio value="mm" label="Many To Many"></v-radio> <v-radio value="mm" label="Many To Many"></v-radio>
<v-radio disabled value="oo" label="One To One"></v-radio> <v-radio disabled value="oo" label="One To One"></v-radio>
@ -16,7 +16,7 @@
<v-row> <v-row>
<v-col cols="12"> <v-col cols="12">
<v-autocomplete <v-autocomplete
validate-on-blur ref="input"
outlined outlined
class="caption" class="caption"
hide-details="auto" hide-details="auto"
@ -136,11 +136,19 @@ export default {
}, },
computed: { computed: {
tableRules() { tableRules() {
return [] return [
// this.meta ? [ v => !!v || 'Required',
// v => this.type !== 'mm' || !this.meta.manyToMany.some(mm => mm.tn === v && mm.rtn === this.meta.tn || mm.rtn === v && mm.tn === this.meta.tn) || 'Duplicate relation is not allowed at the moment', v => {
// v => this.type !== 'hm' || !this.meta.hasMany.some(hm => hm.tn === v) || 'Duplicate relation is not allowed at the moment' if (this.type === 'mm')
// ] : [] return !(this.meta.manyToMany || [])
.some(mm => mm.tn === v && mm.rtn === this.meta.tn || mm.rtn === v && mm.tn === this.meta.tn)
|| 'Duplicate relation is not allowed at the moment';
if (this.type === 'hm')
return !(this.meta.hasMany || [])
.some(hm => hm.tn === v)
|| 'Duplicate relation is not allowed at the moment';
},
]
} }
}, },
methods: { methods: {

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

@ -8,7 +8,7 @@
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn x-small outlined @click="close">Cancel</v-btn> <v-btn x-small outlined @click="close">Cancel</v-btn>
<v-btn x-small color="primary" @click="save" :disabled="!valid">Save</v-btn> <v-btn x-small color="primary" @click="comingSoon" :disabled="!valid">Save</v-btn>
</v-col> </v-col>
<v-col cols="12"> <v-col cols="12">
<v-text-field <v-text-field
@ -52,7 +52,7 @@ export default {
}, },
methods: { methods: {
close() { close() {
this.$emit('close'); this.$emit('input', false);
this.newColumn = {}; this.newColumn = {};
}, },
async save() { async save() {

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

@ -1,6 +1,5 @@
<template> <template>
<v-card width="1000" max-width="100%"> <v-card width="1000" max-width="100%">
<v-toolbar height="55" class="elevation-1"> <v-toolbar height="55" class="elevation-1">
<div class="d-100 d-flex "> <div class="d-100 d-flex ">
<h5 class="title text-center"> <h5 class="title text-center">
@ -69,6 +68,7 @@
</label> </label>
<virtual-cell <virtual-cell
ref="virtual"
v-if="col.virtual" v-if="col.virtual"
:disabledColumns="disabledColumns" :disabledColumns="disabledColumns"
:column="col" :column="col"
@ -81,7 +81,7 @@
:is-new="isNew" :is-new="isNew"
:is-form="true" :is-form="true"
@updateCol="updateCol" @updateCol="updateCol"
@newRecordsSaved="$listeners.loadTableData" @newRecordsSaved="$listeners.loadTableData || (() => {})"
></virtual-cell> ></virtual-cell>
<div <div
@ -312,7 +312,15 @@ export default {
if (this.isNew) { if (this.isNew) {
const data = await this.api.insert(updatedObj); const data = await this.api.insert(updatedObj);
Object.assign(this.localState, data) this.localState = {...this.localState, ...data};
// save hasmany and manytomany relations from local state
if (this.$refs.virtual && Array.isArray(this.$refs.virtual)) {
for (const vcell of this.$refs.virtual) {
if (vcell.save) await vcell.save(this.localState);
}
}
await this.reload(); await this.reload();
} else { } else {
if (Object.keys(updatedObj).length) { if (Object.keys(updatedObj).length) {
@ -326,6 +334,7 @@ export default {
this.$emit('update:oldRow', {...this.localState}) this.$emit('update:oldRow', {...this.localState})
this.changedColumns = {}; this.changedColumns = {};
this.$emit('input', this.localState); this.$emit('input', this.localState);
this.$emit('update:isNew', false);
this.$toast.success(`${this.localState[this.primaryValueColumn]} updated successfully.`, { this.$toast.success(`${this.localState[this.primaryValueColumn]} updated successfully.`, {
position: 'bottom-right' position: 'bottom-right'
@ -339,7 +348,8 @@ 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; const data = await this.api.list({...(this.queryParams || {}), where}) || [{}];
this.localState = data[0] || this.localState;
if (!this.isNew && this.toggleDrawer) { if (!this.isNew && this.toggleDrawer) {
this.getAuditsAndComments() this.getAuditsAndComments()
} }

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

@ -1,5 +1,5 @@
<template> <template>
<div class="d-flex align-center"> <div class="d-flex align-center d-100">
<v-icon v-if="column.pk" color="warning" x-small class="mr-1">mdi-key-variant</v-icon> <v-icon v-if="column.pk" color="warning" x-small class="mr-1">mdi-key-variant</v-icon>
@ -18,7 +18,7 @@
<v-icon color="grey" class="" v-else-if="isString">mdi-alpha-a</v-icon> <v-icon color="grey" class="" v-else-if="isString">mdi-alpha-a</v-icon>
<v-icon color="grey" small class="mr-1" v-else-if="isTextArea">mdi-card-text-outline</v-icon> <v-icon color="grey" small class="mr-1" v-else-if="isTextArea">mdi-card-text-outline</v-icon>
{{ value }} <span class="name" :title="value">{{ value }}</span>
<span v-if="column.rqd" class="error--text text--lighten-1">&nbsp;*</span> <span v-if="column.rqd" class="error--text text--lighten-1">&nbsp;*</span>
@ -159,7 +159,11 @@ export default {
</script> </script>
<style scoped> <style scoped>
.name{
max-width: calc(100% - 40px);
overflow: hidden;
text-overflow: ellipsis;
}
</style> </style>
<!-- <!--
/** /**

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

@ -2,6 +2,7 @@
<div> <div>
<v-lazy> <v-lazy>
<has-many-cell <has-many-cell
ref="cell"
v-if="hm" v-if="hm"
:row="row" :row="row"
:value="row[`${hm._tn}List`]" :value="row[`${hm._tn}List`]"
@ -15,6 +16,7 @@
v-on="$listeners" v-on="$listeners"
/> />
<many-to-many-cell <many-to-many-cell
ref="cell"
v-else-if="mm" v-else-if="mm"
:row="row" :row="row"
:value="row[`${mm._rtn}MMList`]" :value="row[`${mm._rtn}MMList`]"
@ -29,6 +31,7 @@
v-on="$listeners" v-on="$listeners"
/> />
<belongs-to-cell <belongs-to-cell
ref="cell"
:disabled-columns="disabledColumns" :disabled-columns="disabledColumns"
v-else-if="bt" v-else-if="bt"
:active="active" :active="active"
@ -89,6 +92,16 @@ export default {
mm() { mm() {
return this.column && this.column.mm; return this.column && this.column.mm;
} }
},
methods: {
async save(row) {
if (row && this.$refs.cell && this.$refs.cell.saveLocalState) {
try {
await this.$refs.cell.saveLocalState(row);
} catch (e) {
}
}
}
} }
} }
</script> </script>

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

@ -7,7 +7,6 @@
:active="active" :active="active"
:item="value" :item="value"
:value="cellValue" :value="cellValue"
:key="i"
@edit="editParent" @edit="editParent"
@unlink="unlink" @unlink="unlink"
></item-chip> ></item-chip>
@ -78,7 +77,7 @@
:available-columns="parentAvailableColumns" :available-columns="parentAvailableColumns"
:nodes="nodes" :nodes="nodes"
:query-params="parentQueryParams" :query-params="parentQueryParams"
:is-new="isNewParent" :is-new.sync="isNewParent"
icon-color="warning" icon-color="warning"
ref="expandedForm" ref="expandedForm"
v-model="selectedParent" v-model="selectedParent"
@ -284,7 +283,7 @@ export default {
const columns = []; const columns = [];
if (this.parentMeta.columns) { if (this.parentMeta.columns) {
columns.push(...this.parentMeta.columns.filter(c => !(c.pk && c.ai) && !hideCols.includes(c.cn))) columns.push(...this.parentMeta.columns.filter(c => !(c.pk && c.ai) && !hideCols.includes(c.cn)&& !((this.parentMeta.v || []).some(v => v.bt && v.bt.cn === c.cn))))
} }
if (this.parentMeta.v) { if (this.parentMeta.v) {
columns.push(...this.parentMeta.v.map(v => ({...v, virtual: 1}))); columns.push(...this.parentMeta.v.map(v => ({...v, virtual: 1})));

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

@ -7,7 +7,7 @@
:color="isDark ? '' : 'primary lighten-5'" :color="isDark ? '' : 'primary lighten-5'"
@click="active && $emit('edit',item)" @click="active && $emit('edit',item)"
> >
<span class="name">{{ value }}</span> <span class="name" :title="value">{{ 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']"

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

@ -13,9 +13,9 @@
@unlink="unlinkChild" @unlink="unlinkChild"
></item-chip> ></item-chip>
<v-chip v-if="value && value.length === 10" class="caption pointer ml-1 grey--text" <span v-if="value && value.length === 10" class="caption pointer ml-1 grey--text"
@click="showChildListModal">more... @click="showChildListModal">more...
</v-chip> </span>
</template> </template>
</div> </div>
<div class="actions align-center justify-center px-1 flex-shrink-1" <div class="actions align-center justify-center px-1 flex-shrink-1"
@ -101,7 +101,7 @@
:nodes="nodes" :nodes="nodes"
:query-params="childQueryParams" :query-params="childQueryParams"
ref="expandedForm" ref="expandedForm"
:is-new="isNewChild" :is-new.sync="isNewChild"
:disabled-columns="disabledChildColumns" :disabled-columns="disabledChildColumns"
></component> ></component>
@ -119,6 +119,7 @@ import ItemChip from "@/components/project/spreadsheet/components/virtualCell/co
import ListChildItems from "@/components/project/spreadsheet/components/virtualCell/components/listChildItems"; import ListChildItems from "@/components/project/spreadsheet/components/virtualCell/components/listChildItems";
import listChildItemsModal import listChildItemsModal
from "@/components/project/spreadsheet/components/virtualCell/components/listChildItemsModal"; from "@/components/project/spreadsheet/components/virtualCell/components/listChildItemsModal";
import {parseIfInteger} from "@/helpers";
export default { export default {
name: "has-many-cell", name: "has-many-cell",
@ -242,7 +243,7 @@ export default {
this.newRecordModal = false; this.newRecordModal = false;
await this.childApi.update(id, { await this.childApi.update(id, {
[_cn]: +this.parentId || this.parentId [_cn]: parseIfInteger(this.parentId)
}, { }, {
[_cn]: child[this.childForeignKey] [_cn]: child[this.childForeignKey]
}); });
@ -266,7 +267,7 @@ export default {
await this.loadChildMeta(); await this.loadChildMeta();
this.isNewChild = true; this.isNewChild = true;
this.selectedChild = { this.selectedChild = {
[this.childForeignKey]: +this.parentId || this.parentId [this.childForeignKey]: parseIfInteger(this.parentId)
}; };
this.expandFormModal = true; this.expandFormModal = true;
setTimeout(() => { setTimeout(() => {
@ -280,6 +281,25 @@ export default {
} }
return Object.values(cellObj)[1] return Object.values(cellObj)[1]
} }
},
async saveLocalState(row) {
let child;
while (child = this.localState.pop()) {
if (row) {
// todo: use common method
const pid = this.meta.columns.filter((c) => c.pk).map(c => row[c._cn]).join('___')
const id = this.childMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___');
const _cn = this.childForeignKey;
await this.childApi.update(id, {
[_cn]: parseIfInteger(pid)
}, {
[_cn]: child[this.childForeignKey]
});
} else {
await this.addChildToParent(child)
}
}
this.$emit('newRecordsSaved');
} }
}, },
computed: { computed: {
@ -338,11 +358,7 @@ export default {
watch: { watch: {
isNew(n, o) { isNew(n, o) {
if (!n && o) { if (!n && o) {
let child; this.saveLocalState();
while (child = this.localState.pop()) {
this.addChildToParent(child)
}
this.$emit('newRecordsSaved')
} }
} }
}, },

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

@ -12,7 +12,8 @@
@unlink="unlinkChild" @unlink="unlinkChild"
></item-chip> ></item-chip>
</template> <span v-if="value && value.length === 10" class="caption pointer ml-1 grey--text" @click="showChildListModal">more...</span> </template>
<span v-if="value && value.length === 10" class="caption pointer ml-1 grey--text" @click="showChildListModal">more...</span>
</div> </div>
<div class="actions align-center justify-center px-1 flex-shrink-1" <div class="actions align-center justify-center px-1 flex-shrink-1"
:class="{'d-none': !active, 'd-flex':active }"> :class="{'d-none': !active, 'd-flex':active }">
@ -58,31 +59,6 @@
@edit="editChild" @edit="editChild"
@unlink="unlinkChild" @unlink="unlinkChild"
/> />
<v-dialog
:overlay-opacity="0.8"
v-if="selectedChild"
width="1000px"
max-width="100%"
class=" mx-auto"
v-model="showExpandModal">
<expanded-form
v-if="selectedChild"
:db-alias="nodes.dbAlias"
:has-many="childMeta.hasMany"
:belongs-to="childMeta.belongsTo"
@cancel="selectedChild = null"
@input="$emit('loadTableData');showChildListModal();"
:table="childMeta.tn"
v-model="selectedChild"
:old-row="{...selectedChild}"
:meta="childMeta"
:sql-ui="sqlUi"
:primary-value-column="childPrimaryCol"
:api="childApi"
></expanded-form>
</v-dialog>
<dlg-label-submit-cancel <dlg-label-submit-cancel
type="primary" type="primary"
v-if="dialogShow" v-if="dialogShow"
@ -107,7 +83,6 @@
:has-many="childMeta.hasMany" :has-many="childMeta.hasMany"
:belongs-to="childMeta.belongsTo" :belongs-to="childMeta.belongsTo"
:table="childMeta.tn" :table="childMeta.tn"
v-model="selectedChild"
:old-row="{...selectedChild}" :old-row="{...selectedChild}"
:meta="childMeta" :meta="childMeta"
:sql-ui="sqlUi" :sql-ui="sqlUi"
@ -118,7 +93,8 @@
:nodes="nodes" :nodes="nodes"
:query-params="childQueryParams" :query-params="childQueryParams"
ref="expandedForm" ref="expandedForm"
:is-new="isNewChild" :is-new.sync="isNewChild"
v-model="selectedChild"
@cancel="selectedChild = null" @cancel="selectedChild = null"
@input="onChildSave" @input="onChildSave"
></component> ></component>
@ -137,6 +113,7 @@ import ItemChip from "@/components/project/spreadsheet/components/virtualCell/co
import ListChildItems from "@/components/project/spreadsheet/components/virtualCell/components/listChildItems"; import ListChildItems from "@/components/project/spreadsheet/components/virtualCell/components/listChildItems";
import listChildItemsModal import listChildItemsModal
from "@/components/project/spreadsheet/components/virtualCell/components/listChildItemsModal"; from "@/components/project/spreadsheet/components/virtualCell/components/listChildItemsModal";
import {parseIfInteger} from "@/helpers";
export default { export default {
name: "many-to-many-cell", name: "many-to-many-cell",
@ -175,7 +152,8 @@ export default {
methods: { methods: {
async onChildSave(child) { async onChildSave(child) {
if (this.isNewChild) { if (this.isNewChild) {
await this.addChildToParent(child) this.isNewChild = false;
await this.addChildToParent(child);
} else { } else {
this.$emit('loadTableData') this.$emit('loadTableData')
} }
@ -273,8 +251,8 @@ export default {
const vpidCol = this.assocMeta.columns.find(c => c.cn === this.mm.vcn)._cn; const vpidCol = this.assocMeta.columns.find(c => c.cn === this.mm.vcn)._cn;
try { try {
await this.assocApi.insert({ await this.assocApi.insert({
[vcidCol]: +cid, [vcidCol]: parseIfInteger(cid),
[vpidCol]: +pid [vpidCol]: parseIfInteger(pid)
}); });
this.$emit('loadTableData') this.$emit('loadTableData')
@ -310,6 +288,26 @@ export default {
this.$refs.expandedForm && this.$refs.expandedForm.reload() this.$refs.expandedForm && this.$refs.expandedForm.reload()
}, 500) }, 500)
}, },
async saveLocalState(row) {
let child;
while (child = this.localState.pop()) {
if (row) {
// todo: use common method
const cid = this.childMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___');
const pid = this.meta.columns.filter((c) => c.pk).map(c => row[c._cn]).join('___');
const vcidCol = this.assocMeta.columns.find(c => c.cn === this.mm.vrcn)._cn;
const vpidCol = this.assocMeta.columns.find(c => c.cn === this.mm.vcn)._cn;
await this.assocApi.insert({
[vcidCol]: parseIfInteger(cid),
[vpidCol]: parseIfInteger(pid)
});
} else {
await this.addChildToParent(child)
}
}
this.$emit('newRecordsSaved');
}
}, },
computed: { computed: {
getCellValue() { getCellValue() {
@ -361,8 +359,8 @@ export default {
if (!this.childMeta) return {} if (!this.childMeta) return {}
return { return {
childs: (this.childMeta && this.childMeta.v && this.childMeta.v.filter(v => v.hm).map(({hm}) => hm.tn).join()) || '', childs: (this.childMeta && this.childMeta.v && this.childMeta.v.filter(v => v.hm).map(({hm}) => hm.tn).join()) || '',
parents: (this.childMeta && this.childMeta.v && this.childMeta.v.filter(v=>v.bt).map(({bt}) => bt.rtn).join()) || '', parents: (this.childMeta && this.childMeta.v && this.childMeta.v.filter(v => v.bt).map(({bt}) => bt.rtn).join()) || '',
many: (this.childMeta && this.childMeta.v && this.childMeta.v.filter(v=>v.mm).map(({mm}) => mm.rtn).join()) || '' many: (this.childMeta && this.childMeta.v && this.childMeta.v.filter(v => v.mm).map(({mm}) => mm.rtn).join()) || ''
} }
}, },
conditionGraph() { conditionGraph() {
@ -382,7 +380,7 @@ export default {
const columns = []; const columns = [];
if (this.childMeta.columns) { if (this.childMeta.columns) {
columns.push(...this.childMeta.columns.filter(c => !(c.pk && c.ai) && !hideCols.includes(c.cn))) columns.push(...this.childMeta.columns.filter(c => !(c.pk && c.ai) && !hideCols.includes(c.cn) && !((this.childMeta.v || []).some(v => v.bt && v.bt.cn === c.cn))))
} }
if (this.childMeta.v) { if (this.childMeta.v) {
columns.push(...this.childMeta.v.map(v => ({...v, virtual: 1}))); columns.push(...this.childMeta.v.map(v => ({...v, virtual: 1})));
@ -397,11 +395,7 @@ export default {
watch: { watch: {
async isNew(n, o) { async isNew(n, o) {
if (!n && o) { if (!n && o) {
let child; await this.saveLocalState();
while (child = this.localState.pop()) {
await this.addChildToParent(child)
}
this.$emit('newRecordsSaved')
} }
} }
}, },

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

@ -7,7 +7,7 @@
<v-icon v-else-if="column.bt" color="info" x-small class="mr-1" v-on="on">mdi-table-arrow-left</v-icon> <v-icon v-else-if="column.bt" color="info" x-small class="mr-1" v-on="on">mdi-table-arrow-left</v-icon>
<v-icon v-else-if="column.mm" color="pink" x-small class="mr-1" v-on="on">mdi-table-network</v-icon> <v-icon v-else-if="column.mm" color="pink" x-small class="mr-1" v-on="on">mdi-table-network</v-icon>
<span v-on="on">{{ column._cn }}</span> <span v-on="on" class="name" :title="column._cn">{{ column._cn }}</span>
<span v-if="column.rqd" v-on="on" class="error--text text--lighten-1">&nbsp;*</span> <span v-if="column.rqd" v-on="on" class="error--text text--lighten-1">&nbsp;*</span>
</template> </template>
@ -66,6 +66,7 @@
</template> </template>
<edit-virtual-column <edit-virtual-column
v-if="editColumnMenu" v-if="editColumnMenu"
v-model="editColumnMenu"
:nodes="nodes" :nodes="nodes"
:edit-column="true" :edit-column="true"
:column="column" :column="column"
@ -76,8 +77,9 @@
</template> </template>
<script> <script>
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', 'isForm'], props: ['column', 'nodes', 'meta', 'isForm'],
name: "virtualHeaderCell", name: "virtualHeaderCell",
data: () => ({ data: () => ({
@ -146,7 +148,12 @@ export default {
</script> </script>
<style scoped> <style scoped>
.name {
max-width: calc(100px - 40px);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style> </style>
<!-- <!--
/** /**

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

@ -92,7 +92,7 @@
</column-filter> </column-filter>
<v-tooltip bottom> <v-tooltip bottom>
<template v-slot:activator="{on}"> <template v-slot:activator="{on}">
<v-btn :disabled="isLocked" v-on="on" small @click="deleteTable('showDialog')" outlined text> <v-btn :disabled="isLocked" v-on="on" small @click="checkAndDeleteTable" outlined text>
<x-icon small color="red grey">mdi-delete-outline</x-icon> <x-icon small color="red grey">mdi-delete-outline</x-icon>
</v-btn> </v-btn>
</template> </template>
@ -134,19 +134,12 @@
color="grey darken-3" color="grey darken-3"
>{{ toggleDrawer ? 'mdi-door-closed' : 'mdi-door-open' }} >{{ toggleDrawer ? 'mdi-door-closed' : 'mdi-door-open' }}
</v-icon> </v-icon>
</x-btn> </x-btn>
<!-- <v-spacer></v-spacer>-->
<!-- <v-text-field outlined dense hide-details class="elevation-0" append-icon="mdi-magnify"></v-text-field>-->
</v-toolbar> </v-toolbar>
<div :class="`cell-height-${cellHeight}`" <div :class="`cell-height-${cellHeight}`"
style=" height:calc(100% - 32px);overflow:auto;transition: width 500ms " style=" height:calc(100% - 32px);overflow:auto;transition: width 100ms "
class="d-flex" class="d-flex"
> >
<div class="flex-grow-1 h-100" style="overflow-y: auto"> <div class="flex-grow-1 h-100" style="overflow-y: auto">
@ -498,7 +491,7 @@ export default {
showTabs: [Boolean, Number] showTabs: [Boolean, Number]
}, },
data: () => ({ data: () => ({
key:1, key: 1,
dataLoaded: false, dataLoaded: false,
searchQueryVal: '', searchQueryVal: '',
columnsWidth: null, columnsWidth: null,
@ -604,10 +597,21 @@ export default {
...mapActions({ ...mapActions({
loadTablesFromChildTreeNode: "project/loadTablesFromChildTreeNode" loadTablesFromChildTreeNode: "project/loadTablesFromChildTreeNode"
}), }),
async reload(){ checkAndDeleteTable() {
if (
!this.meta &&
this.meta.hasMany && this.meta.hasMany.length ||
this.meta.manyToMany && this.meta.manyToMany.length ||
this.meta.belongsTo && this.meta.belongsTo.length
) {
return this.$toast.info('Please delete relations before deleting table.').goAway(3000)
}
this.deleteTable('showDialog')
},
async reload() {
this.$store.commit('meta/MutClear'); this.$store.commit('meta/MutClear');
await this.loadTableData(); await this.loadTableData();
this.key=Math.random(); this.key = Math.random();
}, },
reloadComments() { reloadComments() {
if (this.$refs.ncgridview) { if (this.$refs.ncgridview) {

1
packages/nc-gui/helpers/index.js

@ -1,5 +1,6 @@
export const isEmail = v => /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i.test(v) export const isEmail = v => /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i.test(v)
export const parseIfInteger = v => /^\d+$/.test(v) ? +v : v;
/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd
* *

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

@ -387,6 +387,20 @@ export const actions = {
})).data; })).data;
// clear meta cache on relation create/delete
// todo: clear only necessary metas
// todo: include missing operations
if (['relationCreate',
'xcM2MRelationCreate',
'xcVirtualRelationCreate',
'relationDelete',
'xcVirtualRelationDelete',
'xcRelationColumnDelete'].includes(op)) {
commit('meta/MutClear', null, {root: true})
}
if (op === 'tableXcModelGet') { if (op === 'tableXcModelGet') {
try { try {
const meta = JSON.parse(model.meta); const meta = JSON.parse(model.meta);

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

@ -1,13 +1,4 @@
import GqlResolver from "./GqlResolver"; import GqlResolver from "./GqlResolver";
import inflection from 'inflection';
// import {
// ExpressXcTsPolicyGql,
// GqlXcSchemaFactory,
// ModelXcMetaFactory
// } from "nc-help";
import {BaseType} from 'xc-core-ts'; import {BaseType} from 'xc-core-ts';
import {DbConfig, NcConfig} from "../../../interface/config"; import {DbConfig, NcConfig} from "../../../interface/config";
import Noco from "../Noco"; import Noco from "../Noco";
@ -391,7 +382,7 @@ export class GqlApiBuilder extends BaseApiBuilder<Noco> implements XcMetaMgr {
const colNameAlias = self.models[bt.tn]?.columnToAlias[bt.cn]; const colNameAlias = self.models[bt.tn]?.columnToAlias[bt.cn];
const rcolNameAlias = self.models[bt.rtn]?.columnToAlias[bt.rcn]; const rcolNameAlias = self.models[bt.rtn]?.columnToAlias[bt.rcn];
const middlewareBody = middlewaresArr.find(({title}) => title === bt.rtn)?.functions?.[0]; const middlewareBody = middlewaresArr.find(({title}) => title === bt.rtn)?.functions?.[0];
const propName = `${inflection.camelize(bt._rtn, false)}Read`; const propName = `${bt._rtn}Read`;
if (propName in this.types[tn].prototype) { if (propName in this.types[tn].prototype) {
continue; continue;
} }
@ -702,8 +693,8 @@ export class GqlApiBuilder extends BaseApiBuilder<Noco> implements XcMetaMgr {
const colNameAlias = self.models[hm.rtn]?.columnToAlias[hm.rcn]; const colNameAlias = self.models[hm.rtn]?.columnToAlias[hm.rcn];
const countPropName = `${inflection.camelize(hm._tn, false)}Count`; const countPropName = `${hm._tn}Count`;
const listPropName = `${inflection.camelize(hm._tn, false)}List`; const listPropName = `${hm._tn}List`;
this.log(`xcTablesPopulate : Populating '%s' and '%s' loaders`, listPropName, countPropName); this.log(`xcTablesPopulate : Populating '%s' and '%s' loaders`, listPropName, countPropName);
@ -747,7 +738,7 @@ export class GqlApiBuilder extends BaseApiBuilder<Noco> implements XcMetaMgr {
for (const bt of schema.belongsTo) { for (const bt of schema.belongsTo) {
const colNameAlias = self.models[bt.tn]?.columnToAlias[bt.cn]; const colNameAlias = self.models[bt.tn]?.columnToAlias[bt.cn];
const propName = `${inflection.camelize(bt._rtn, false)}Read`; const propName = `${bt._rtn}Read`;
if (propName in this.types[tn].prototype) { if (propName in this.types[tn].prototype) {
@ -1369,7 +1360,7 @@ export class GqlApiBuilder extends BaseApiBuilder<Noco> implements XcMetaMgr {
/* update parent table meta and resolvers */ /* update parent table meta and resolvers */
{ {
const columns = await this.getColumnList(tnp); const columns = this.metas[tnp]?.columns;//await this.getColumnList(tnp);
const hasMany = this.extractHasManyRelationsOfTable(relations, tnp); const hasMany = this.extractHasManyRelationsOfTable(relations, tnp);
const belongsTo = this.extractBelongsToRelationsOfTable(relations, tnp); const belongsTo = this.extractBelongsToRelationsOfTable(relations, tnp);
const ctx = this.generateContextForTable(tnp, columns, relations, hasMany, belongsTo); const ctx = this.generateContextForTable(tnp, columns, relations, hasMany, belongsTo);
@ -1385,18 +1376,20 @@ export class GqlApiBuilder extends BaseApiBuilder<Noco> implements XcMetaMgr {
const oldMeta = JSON.parse(existingModel.meta); const oldMeta = JSON.parse(existingModel.meta);
Object.assign(oldMeta, { Object.assign(oldMeta, {
hasMany: meta.hasMany, hasMany: meta.hasMany,
schema: this.schemas[tnp] v: oldMeta.v.filter(({hm}) => !hm || hm.rtn !== tnp || hm.tn !== tnc)
}); });
// todo: backup schema
await this.xcMeta.metaUpdate(this.projectId, this.dbAlias, 'nc_models', { await this.xcMeta.metaUpdate(this.projectId, this.dbAlias, 'nc_models', {
title: tnp, title: tnp,
meta: JSON.stringify(oldMeta) meta: JSON.stringify(oldMeta),
schema: this.schemas[tnp]
}, {'title': tnp}) }, {'title': tnp})
this.metas[tnp] = oldMeta; this.models[tnp] = this.getBaseModel(oldMeta);
} }
const countPropName = `${inflection.camelize(this.getTableNameAlias(tnc), false)}Count`; const countPropName = `${this.getTableNameAlias(tnc)}Count`;
const listPropName = `${inflection.camelize(this.getTableNameAlias(tnc), false)}List`; const listPropName = `${this.getTableNameAlias(tnc)}List`;
this.log(`onRelationDelete : Deleting '%s' and '%s' loaders`, countPropName, listPropName); this.log(`onRelationDelete : Deleting '%s' and '%s' loaders`, countPropName, listPropName);
/* defining HasMany list method within GQL Type class */ /* defining HasMany list method within GQL Type class */
@ -1432,15 +1425,17 @@ export class GqlApiBuilder extends BaseApiBuilder<Noco> implements XcMetaMgr {
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)
}); });
await this.xcMeta.metaUpdate(this.projectId, this.dbAlias, 'nc_models', { await this.xcMeta.metaUpdate(this.projectId, this.dbAlias, 'nc_models', {
title: tnc, title: tnc,
meta: JSON.stringify(oldMeta) meta: JSON.stringify(oldMeta),
schema: this.schemas[tnc]
}, {'title': tnc}); }, {'title': tnc});
this.metas[tnc] = oldMeta; this.models[tnc] = this.getBaseModel(oldMeta);
} }
const propName = `${inflection.camelize(this.getTableNameAlias(tnp), false)}Read`; const propName = `${this.getTableNameAlias(tnp)}Read`;
this.log(`onRelationDelete : Deleting '%s' loader`, propName); this.log(`onRelationDelete : Deleting '%s' loader`, propName);

Loading…
Cancel
Save