Browse Source

Merge pull request #5936 from nocodb/fix/5934-delete-table

fix: Skip relation delete if it's a virtual relation
pull/5940/head
Raju Udava 1 year ago committed by GitHub
parent
commit
4331041827
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      packages/nc-gui/components/cell/MultiSelect.vue
  2. 6
      packages/nc-gui/components/cell/SingleSelect.vue
  3. 2
      packages/nc-gui/components/dashboard/TreeView.vue
  4. 11
      packages/nc-gui/components/smartsheet/Form.vue
  5. 6
      packages/nc-gui/components/smartsheet/Grid.vue
  6. 8
      packages/nc-gui/components/smartsheet/column/LinkedToAnotherRecordOptions.vue
  7. 2
      packages/nc-gui/composables/useColumnCreateStore.ts
  8. 3
      packages/nc-gui/composables/useMetas.ts
  9. 10
      packages/nc-gui/pages/[projectType]/form/[viewId]/index/index.vue
  10. 12
      packages/nc-gui/pages/[projectType]/form/[viewId]/index/survey.vue
  11. 27
      packages/nocodb/src/db/sql-mgr/v2/ProjectMgrv2.ts
  12. 2
      packages/nocodb/src/meta/meta.service.ts
  13. 8
      packages/nocodb/src/models/Model.ts
  14. 14
      packages/nocodb/src/models/View.ts
  15. 10
      packages/nocodb/src/services/columns.service.ts
  16. 5
      packages/nocodb/src/services/tables.service.ts
  17. 5
      packages/nocodb/src/services/users/users.service.ts

4
packages/nc-gui/components/cell/MultiSelect.vue

@ -8,6 +8,7 @@ import {
ActiveCellInj, ActiveCellInj,
CellClickHookInj, CellClickHookInj,
ColumnInj, ColumnInj,
EditModeInj,
IsKanbanInj, IsKanbanInj,
ReadonlyInj, ReadonlyInj,
RowHeightInj, RowHeightInj,
@ -27,7 +28,6 @@ import {
useRoles, useRoles,
useSelectedCellKeyupListener, useSelectedCellKeyupListener,
watch, watch,
EditModeInj
} from '#imports' } from '#imports'
import MdiCloseCircle from '~icons/mdi/close-circle' import MdiCloseCircle from '~icons/mdi/close-circle'
@ -101,7 +101,7 @@ const isOptionMissing = computed(() => {
const hasEditRoles = computed(() => hasRole('owner', true) || hasRole('creator', true) || hasRole('editor', true)) const hasEditRoles = computed(() => hasRole('owner', true) || hasRole('creator', true) || hasRole('editor', true))
const editAllowed = computed(() => (hasEditRoles.value || isForm.value) && (active.value)) const editAllowed = computed(() => (hasEditRoles.value || isForm.value) && active.value)
const vModel = computed({ const vModel = computed({
get: () => { get: () => {

6
packages/nc-gui/components/cell/SingleSelect.vue

@ -8,6 +8,7 @@ import {
ActiveCellInj, ActiveCellInj,
CellClickHookInj, CellClickHookInj,
ColumnInj, ColumnInj,
EditModeInj,
IsFormInj, IsFormInj,
IsKanbanInj, IsKanbanInj,
ReadonlyInj, ReadonlyInj,
@ -23,7 +24,6 @@ import {
useRoles, useRoles,
useSelectedCellKeyupListener, useSelectedCellKeyupListener,
watch, watch,
EditModeInj
} from '#imports' } from '#imports'
interface Props { interface Props {
@ -96,7 +96,7 @@ const isOptionMissing = computed(() => {
const hasEditRoles = computed(() => hasRole('owner', true) || hasRole('creator', true) || hasRole('editor', true)) const hasEditRoles = computed(() => hasRole('owner', true) || hasRole('creator', true) || hasRole('editor', true))
const editAllowed = computed(() => (hasEditRoles.value || isForm.value) && (active.value)) const editAllowed = computed(() => (hasEditRoles.value || isForm.value) && active.value)
const vModel = computed({ const vModel = computed({
get: () => tempSelectedOptState.value ?? modelValue, get: () => tempSelectedOptState.value ?? modelValue,
@ -257,7 +257,7 @@ const selectedOpt = computed(() => {
<template> <template>
<div class="h-full w-full flex items-center nc-single-select" :class="{ 'read-only': readOnly }" @click="toggleMenu"> <div class="h-full w-full flex items-center nc-single-select" :class="{ 'read-only': readOnly }" @click="toggleMenu">
<div v-if="!(active || isEditable) "> <div v-if="!(active || isEditable)">
<a-tag v-if="selectedOpt" class="rounded-tag" :color="selectedOpt.color"> <a-tag v-if="selectedOpt" class="rounded-tag" :color="selectedOpt.color">
<span <span
:style="{ :style="{

2
packages/nc-gui/components/dashboard/TreeView.vue

@ -97,7 +97,7 @@ const initSortable = (el: Element) => {
onEnd: async (evt) => { onEnd: async (evt) => {
const { newIndex = 0, oldIndex = 0 } = evt const { newIndex = 0, oldIndex = 0 } = evt
if(newIndex === oldIndex) return if (newIndex === oldIndex) return
const itemEl = evt.item as HTMLLIElement const itemEl = evt.item as HTMLLIElement
const item = tablesById[itemEl.dataset.id as string] const item = tablesById[itemEl.dataset.id as string]

11
packages/nc-gui/components/smartsheet/Form.vue

@ -535,7 +535,7 @@ watch(view, (nextView) => {
<a-textarea <a-textarea
v-model:value="formViewData.heading" v-model:value="formViewData.heading"
class="w-full !font-bold !text-4xl !border-0 !border-b-1 !border-dashed !rounded-none !border-gray-400" class="w-full !font-bold !text-4xl !border-0 !border-b-1 !border-dashed !rounded-none !border-gray-400"
:style="{ borderRightWidth: '0px !important', 'height': '54px', 'min-height': '54px', resize: 'vertical' }" :style="{ 'borderRightWidth': '0px !important', 'height': '54px', 'min-height': '54px', 'resize': 'vertical' }"
size="large" size="large"
hide-details hide-details
placeholder="Form Title" placeholder="Form Title"
@ -554,7 +554,7 @@ watch(view, (nextView) => {
<a-textarea <a-textarea
v-model:value="formViewData.subheading" v-model:value="formViewData.subheading"
class="w-full !border-0 !border-b-1 !border-dashed !rounded-none !border-gray-400" class="w-full !border-0 !border-b-1 !border-dashed !rounded-none !border-gray-400"
:style="{ borderRightWidth: '0px !important', height: '40px', 'min-height': '40px', resize: 'vertical' }" :style="{ 'borderRightWidth': '0px !important', 'height': '40px', 'min-height': '40px', 'resize': 'vertical' }"
size="large" size="large"
hide-details hide-details
:placeholder="$t('msg.info.formDesc')" :placeholder="$t('msg.info.formDesc')"
@ -743,7 +743,9 @@ watch(view, (nextView) => {
</LazySmartsheetDivDataCell> </LazySmartsheetDivDataCell>
</a-form-item> </a-form-item>
<div class="nc-form-help-text text-gray-500 text-xs" data-testid="nc-form-input-help-text-label">{{ element.description }}</div> <div class="nc-form-help-text text-gray-500 text-xs" data-testid="nc-form-input-help-text-label">
{{ element.description }}
</div>
</div> </div>
</template> </template>
@ -861,7 +863,8 @@ watch(view, (nextView) => {
} }
} }
.nc-form-help-text, .nc-input-required-error { .nc-form-help-text,
.nc-input-required-error {
max-width: 100%; max-width: 100%;
word-break: break-all; word-break: break-all;
white-space: pre-line; white-space: pre-line;

6
packages/nc-gui/components/smartsheet/Grid.vue

@ -746,7 +746,7 @@ const saveOrUpdateRecords = async (args: { metaValue?: TableType; viewMetaValue?
async function reloadViewDataHandler(shouldShowLoading: boolean | void) { async function reloadViewDataHandler(shouldShowLoading: boolean | void) {
// save any unsaved data before reload // save any unsaved data before reload
await saveOrUpdateRecords(); await saveOrUpdateRecords()
// set value if spinner should be hidden // set value if spinner should be hidden
showLoading.value = !!shouldShowLoading showLoading.value = !!shouldShowLoading
@ -768,7 +768,7 @@ onBeforeUnmount(async () => {
const viewMetaValue = view.value const viewMetaValue = view.value
const dataValue = data.value const dataValue = data.value
if (viewMetaValue) { if (viewMetaValue) {
getMeta(viewMetaValue.fk_model_id).then((res) => { getMeta(viewMetaValue.fk_model_id, false, true).then((res) => {
const metaValue = res const metaValue = res
if (!metaValue) return if (!metaValue) return
saveOrUpdateRecords({ saveOrUpdateRecords({
@ -815,7 +815,7 @@ watch(
switchingTab.value = true switchingTab.value = true
// whenever tab changes or view changes save any unsaved data // whenever tab changes or view changes save any unsaved data
if (old?.id) { if (old?.id) {
const oldMeta = await getMeta(old.fk_model_id!) const oldMeta = await getMeta(old.fk_model_id!, false, true)
if (oldMeta) { if (oldMeta) {
await saveOrUpdateRecords({ await saveOrUpdateRecords({
viewMetaValue: old, viewMetaValue: old,

8
packages/nc-gui/components/smartsheet/column/LinkedToAnotherRecordOptions.vue

@ -129,11 +129,9 @@ const filterOption = (value: string, option: { key: string }) => option.key.toLo
<div class="flex flex-row"> <div class="flex flex-row">
<a-form-item> <a-form-item>
<a-checkbox v-model:checked="vModel.virtual" :disabled="appInfo.isCloud" name="virtual" <a-checkbox v-model:checked="vModel.virtual" :disabled="appInfo.isCloud" name="virtual" @change="onDataTypeChange"
@change="onDataTypeChange" >Virtual Relation
>Virtual Relation </a-checkbox>
</a-checkbox
>
</a-form-item> </a-form-item>
</div> </div>
</div> </div>

2
packages/nc-gui/composables/useColumnCreateStore.ts

@ -291,7 +291,7 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
isMssql, isMssql,
isPg, isPg,
isMysql, isMysql,
isXcdbBase isXcdbBase,
} }
}, },
) )

3
packages/nc-gui/composables/useMetas.ts

@ -26,7 +26,7 @@ export function useMetas() {
} }
// todo: this needs a proper refactor, arbitrary waiting times are usually not a good idea // todo: this needs a proper refactor, arbitrary waiting times are usually not a good idea
const getMeta = async (tableIdOrTitle: string, force = false): Promise<TableType | null> => { const getMeta = async (tableIdOrTitle: string, force = false, skipIfCacheMiss = false): Promise<TableType | null> => {
if (!tableIdOrTitle) return null if (!tableIdOrTitle) return null
/** wait until loading is finished if requesting same meta */ /** wait until loading is finished if requesting same meta */
if (!force && loadingState.value[tableIdOrTitle]) { if (!force && loadingState.value[tableIdOrTitle]) {
@ -58,6 +58,7 @@ export function useMetas() {
return metas.value[tableIdOrTitle] return metas.value[tableIdOrTitle]
} }
} }
if (skipIfCacheMiss) return null
loadingState.value[tableIdOrTitle] = true loadingState.value[tableIdOrTitle] = true

10
packages/nc-gui/pages/[projectType]/form/[viewId]/index/index.vue

@ -75,10 +75,7 @@ const onDecode = async (scannedCodeValue: string) => {
class="color-transition relative flex flex-col justify-center gap-2 w-full max-w-[max(33%,600px)] m-auto py-4 pb-8 px-16 md:(bg-white dark:bg-slate-700 rounded-lg border-1 border-gray-200 shadow-xl)" class="color-transition relative flex flex-col justify-center gap-2 w-full max-w-[max(33%,600px)] m-auto py-4 pb-8 px-16 md:(bg-white dark:bg-slate-700 rounded-lg border-1 border-gray-200 shadow-xl)"
> >
<template v-if="sharedFormView"> <template v-if="sharedFormView">
<h1 <h1 class="prose-2xl font-bold self-center my-4" style="word-break: break-all">
class="prose-2xl font-bold self-center my-4"
style="word-break: break-all"
>
{{ sharedFormView.heading }} {{ sharedFormView.heading }}
</h1> </h1>
@ -189,7 +186,10 @@ const onDecode = async (scannedCodeValue: string) => {
</a-button> </a-button>
</LazySmartsheetDivDataCell> </LazySmartsheetDivDataCell>
<div class="flex flex-col gap-2 text-slate-500 dark:text-slate-300 text-[0.75rem] my-2 px-1" style="word-break: break-all"> <div
class="flex flex-col gap-2 text-slate-500 dark:text-slate-300 text-[0.75rem] my-2 px-1"
style="word-break: break-all"
>
<div v-for="error of v$.localState[field.title]?.$errors" :key="error" class="text-red-500"> <div v-for="error of v$.localState[field.title]?.$errors" :key="error" class="text-red-500">
{{ error.$message }} {{ error.$message }}
</div> </div>

12
packages/nc-gui/pages/[projectType]/form/[viewId]/index/survey.vue

@ -244,10 +244,7 @@ onMounted(() => {
class="max-w-[max(33%,600px)] mx-auto flex flex-col justify-end" class="max-w-[max(33%,600px)] mx-auto flex flex-col justify-end"
> >
<div class="px-4 md:px-0 flex flex-col justify-end"> <div class="px-4 md:px-0 flex flex-col justify-end">
<h1 <h1 class="prose-2xl font-bold self-center my-4" data-testid="nc-survey-form__heading" style="word-break: break-all">
class="prose-2xl font-bold self-center my-4"
data-testid="nc-survey-form__heading"
style="word-break: break-all">
{{ sharedFormView.heading }} {{ sharedFormView.heading }}
</h1> </h1>
@ -309,7 +306,10 @@ onMounted(() => {
@update:edit-enabled="editEnabled[index] = $event" @update:edit-enabled="editEnabled[index] = $event"
/> />
<div class="flex flex-col gap-2 text-slate-500 dark:text-slate-300 text-[0.75rem] my-2 px-1" style="word-break: break-all"> <div
class="flex flex-col gap-2 text-slate-500 dark:text-slate-300 text-[0.75rem] my-2 px-1"
style="word-break: break-all"
>
<div v-for="error of v$.localState[field.title]?.$errors" :key="error" class="text-red-500"> <div v-for="error of v$.localState[field.title]?.$errors" :key="error" class="text-red-500">
{{ error.$message }} {{ error.$message }}
</div> </div>
@ -353,7 +353,7 @@ onMounted(() => {
:mouse-enter-delay="0.25" :mouse-enter-delay="0.25"
:mouse-leave-delay="0" :mouse-leave-delay="0"
> >
<!-- Ok button for question --> <!-- Ok button for question -->
<button <button
class="bg-opacity-100 scaling-btn flex items-center gap-1" class="bg-opacity-100 scaling-btn flex items-center gap-1"
data-testid="nc-survey-form__btn-next" data-testid="nc-survey-form__btn-next"

27
packages/nocodb/src/db/sql-mgr/v2/ProjectMgrv2.ts

@ -1,21 +1,24 @@
import { MetaService } from '../../../meta/meta.service' import SqlMgrv2 from './SqlMgrv2';
import SqlMgrv2 from './SqlMgrv2' import SqlMgrv2Trans from './SqlMgrv2Trans';
import SqlMgrv2Trans from './SqlMgrv2Trans' import type { MetaService } from '../../../meta/meta.service';
// import type NcMetaIO from '../../../meta/NcMetaIO'; // import type NcMetaIO from '../../../meta/NcMetaIO';
import type Base from '../../../models/Base' import type Base from '../../../models/Base';
export default class ProjectMgrv2 { export default class ProjectMgrv2 {
private static sqlMgrMap: { private static sqlMgrMap: {
[key: string]: SqlMgrv2; [key: string]: SqlMgrv2;
} = {} } = {};
public static getSqlMgr(project: { id: string }, ncMeta: MetaService = null): SqlMgrv2 { public static getSqlMgr(
if (ncMeta) return new SqlMgrv2(project, ncMeta) project: { id: string },
ncMeta: MetaService = null,
): SqlMgrv2 {
if (ncMeta) return new SqlMgrv2(project, ncMeta);
if (!this.sqlMgrMap[project.id]) { if (!this.sqlMgrMap[project.id]) {
this.sqlMgrMap[project.id] = new SqlMgrv2(project) this.sqlMgrMap[project.id] = new SqlMgrv2(project);
} }
return this.sqlMgrMap[project.id] return this.sqlMgrMap[project.id];
} }
public static async getSqlMgrTrans( public static async getSqlMgrTrans(
@ -24,8 +27,8 @@ export default class ProjectMgrv2 {
ncMeta: any, ncMeta: any,
base: Base, base: Base,
): Promise<SqlMgrv2Trans> { ): Promise<SqlMgrv2Trans> {
const sqlMgr = new SqlMgrv2Trans(project, ncMeta, base) const sqlMgr = new SqlMgrv2Trans(project, ncMeta, base);
await sqlMgr.startTransaction(base) await sqlMgr.startTransaction(base);
return sqlMgr return sqlMgr;
} }
} }

2
packages/nocodb/src/meta/meta.service.ts

@ -619,7 +619,7 @@ export class MetaService {
offset?: number; offset?: number;
xcCondition?; xcCondition?;
fields?: string[]; fields?: string[];
orderBy: { [key: string]: 'asc' | 'desc' }; orderBy?: { [key: string]: 'asc' | 'desc' };
}, },
): Promise<any[]> { ): Promise<any[]> {
const query = this.knexConnection(target); const query = this.knexConnection(target);

8
packages/nocodb/src/models/Model.ts

@ -293,9 +293,9 @@ export default class Model implements TableType {
)); ));
if (!modelData) { if (!modelData) {
modelData = await ncMeta.metaGet2(null, null, MetaTable.MODELS, k); modelData = await ncMeta.metaGet2(null, null, MetaTable.MODELS, k);
modelData.meta = parseMetaProp(modelData);
} }
if (modelData) { if (modelData) {
modelData.meta = parseMetaProp(modelData);
await NocoCache.set(`${CacheScope.MODEL}:${modelData.id}`, modelData); await NocoCache.set(`${CacheScope.MODEL}:${modelData.id}`, modelData);
return new Model(modelData); return new Model(modelData);
} }
@ -327,8 +327,10 @@ export default class Model implements TableType {
table_name, table_name,
}, },
); );
modelData.meta = parseMetaProp(modelData); if (modelData) {
await NocoCache.set(`${CacheScope.MODEL}:${modelData.id}`, modelData); modelData.meta = parseMetaProp(modelData);
await NocoCache.set(`${CacheScope.MODEL}:${modelData.id}`, modelData);
}
// modelData.filters = await Filter.getFilterObject({ // modelData.filters = await Filter.getFilterObject({
// viewId: modelData.id // viewId: modelData.id
// }); // });

14
packages/nocodb/src/models/View.ts

@ -127,8 +127,10 @@ export default class View implements ViewType {
)); ));
if (!view) { if (!view) {
view = await ncMeta.metaGet2(null, null, MetaTable.VIEWS, viewId); view = await ncMeta.metaGet2(null, null, MetaTable.VIEWS, viewId);
view.meta = parseMetaProp(view); if (view) {
await NocoCache.set(`${CacheScope.VIEW}:${view.id}`, view); view.meta = parseMetaProp(view);
await NocoCache.set(`${CacheScope.VIEW}:${view.id}`, view);
}
} }
return view && new View(view); return view && new View(view);
@ -205,8 +207,10 @@ export default class View implements ViewType {
}, },
null, null,
); );
view.meta = parseMetaProp(view); if (view) {
await NocoCache.set(`${CacheScope.VIEW}:${fk_model_id}:default`, view); view.meta = parseMetaProp(view);
await NocoCache.set(`${CacheScope.VIEW}:${fk_model_id}:default`, view);
}
} }
return view && new View(view); return view && new View(view);
} }
@ -1273,7 +1277,7 @@ export default class View implements ViewType {
); );
} }
async delete(ncMeta = Noco.ncMeta){ async delete(ncMeta = Noco.ncMeta) {
await View.delete(this.id, ncMeta); await View.delete(this.id, ncMeta);
} }

10
packages/nocodb/src/services/columns.service.ts

@ -1216,6 +1216,7 @@ export class ColumnsService {
childColumn: mmParentCol, childColumn: mmParentCol,
base, base,
ncMeta, ncMeta,
virtual: !!relationColOpt.virtual,
}, },
true, true,
); );
@ -1230,6 +1231,7 @@ export class ColumnsService {
childColumn: mmChildCol, childColumn: mmChildCol,
base, base,
ncMeta, ncMeta,
virtual: !!relationColOpt.virtual,
}, },
true, true,
); );
@ -1387,6 +1389,7 @@ export class ColumnsService {
parentTable, parentTable,
sqlMgr, sqlMgr,
ncMeta = Noco.ncMeta, ncMeta = Noco.ncMeta,
virtual,
}: { }: {
relationColOpt: LinkToAnotherRecordColumn; relationColOpt: LinkToAnotherRecordColumn;
base: Base; base: Base;
@ -1396,6 +1399,7 @@ export class ColumnsService {
parentTable: Model; parentTable: Model;
sqlMgr: SqlMgrv2; sqlMgr: SqlMgrv2;
ncMeta?: MetaService; ncMeta?: MetaService;
virtual?: boolean;
}, },
ignoreFkDelete = false, ignoreFkDelete = false,
) => { ) => {
@ -1424,7 +1428,7 @@ export class ColumnsService {
foreignKeyName = relationColOpt.fk_index_name; foreignKeyName = relationColOpt.fk_index_name;
} }
if (!relationColOpt?.virtual) { if (!relationColOpt?.virtual && !virtual) {
// todo: handle relation delete exception // todo: handle relation delete exception
try { try {
await sqlMgr.sqlOpPlus(base, 'relationDelete', { await sqlMgr.sqlOpPlus(base, 'relationDelete', {
@ -1435,7 +1439,7 @@ export class ColumnsService {
foreignKeyName, foreignKeyName,
}); });
} catch (e) { } catch (e) {
console.log(e); console.log(e.message);
} }
} }
@ -1481,7 +1485,7 @@ export class ColumnsService {
if (index.cn !== childColumn.column_name) continue; if (index.cn !== childColumn.column_name) continue;
await sqlMgr.sqlOpPlus(base, 'indexDelete', { await sqlMgr.sqlOpPlus(base, 'indexDelete', {
...index, ...index,
tn: cTable.table_name, tn: cTable.table_name,
columns: [childColumn.column_name], columns: [childColumn.column_name],
indexName: index.index_name, indexName: index.index_name,

5
packages/nocodb/src/services/tables.service.ts

@ -16,7 +16,6 @@ import getTableNameAlias, { getColumnNameAlias } from '../helpers/getTableName';
import mapDefaultDisplayValue from '../helpers/mapDefaultDisplayValue'; import mapDefaultDisplayValue from '../helpers/mapDefaultDisplayValue';
import { import {
Audit, Audit,
Base,
Column, Column,
Model, Model,
ModelRoleVisibility, ModelRoleVisibility,
@ -247,6 +246,10 @@ export class TablesService {
id: param.tableId, id: param.tableId,
}); });
if (!table) {
NcError.notFound('Table not found');
}
// todo: optimise // todo: optimise
const viewList = <View[]>( const viewList = <View[]>(
await this.xcVisibilityMetaGet(table.project_id, [table]) await this.xcVisibilityMetaGet(table.project_id, [table])

5
packages/nocodb/src/services/users/users.service.ts

@ -51,7 +51,10 @@ export class UsersService {
email: string; email: string;
lastname: any; lastname: any;
}) { }) {
return this.metaService.metaInsert2(null, null, MetaTable.USERS, { ...param, email: param.email?.toLowerCase() }); return this.metaService.metaInsert2(null, null, MetaTable.USERS, {
...param,
email: param.email?.toLowerCase(),
});
} }
async registerNewUserIfAllowed({ async registerNewUserIfAllowed({

Loading…
Cancel
Save