Browse Source

Merge pull request #3117 from nocodb/fix/gui-v2-table-rename

fix(gui-v2): table rename
pull/3298/head
աɨռɢӄաօռɢ 2 years ago committed by GitHub
parent
commit
344a05eece
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      packages/nc-gui-v2/components/dlg/TableCreate.vue
  2. 35
      packages/nc-gui-v2/components/dlg/TableRename.vue
  3. 2
      packages/nc-gui-v2/composables/useProject.ts
  4. 5
      packages/nc-gui-v2/composables/useTable.ts
  5. 3
      packages/nc-gui/components/ProjectTreeView.vue
  6. 5
      packages/nocodb-sdk/src/lib/Api.ts
  7. 81
      packages/nocodb/src/lib/meta/api/tableApis.ts
  8. 16
      packages/nocodb/src/lib/models/Model.ts
  9. 6
      scripts/cypress/integration/common/1a_table_operations.js
  10. 8
      scripts/sdk/swagger.json

26
packages/nc-gui-v2/components/dlg/TableCreate.vue

@ -19,7 +19,7 @@ const inputEl = ref<HTMLInputElement>()
const { addTab } = useTabs()
const { loadTables } = useProject()
const { loadTables, isMysql, isMssql, isPg } = useProject()
const { table, createTable, generateUniqueTitle, tables, project } = useTable(async (table) => {
await loadTables()
@ -39,7 +39,29 @@ const validateDuplicateAlias = (v: string) => (tables.value || []).every((t) =>
const validators = computed(() => {
return {
title: [validateTableName, validateDuplicateAlias],
title: [
validateTableName,
validateDuplicateAlias,
{
validator: (rule: any, value: any) => {
return new Promise<void>((resolve, reject) => {
let tableNameLengthLimit = 255
if (isMysql) {
tableNameLengthLimit = 64
} else if (isPg) {
tableNameLengthLimit = 63
} else if (isMssql) {
tableNameLengthLimit = 128
}
const projectPrefix = project?.value?.prefix || ''
if ((projectPrefix + value).length > tableNameLengthLimit) {
return reject(new Error(`Table name exceeds ${tableNameLengthLimit} characters`))
}
resolve()
})
},
},
],
table_name: [validateTableName],
}
})

35
packages/nc-gui-v2/components/dlg/TableRename.vue

@ -24,8 +24,7 @@ const dialogShow = computed({
})
const { updateTab } = useTabs()
const { loadTables } = useProject()
const { tables } = useProject()
const { loadTables, tables, project, isMysql, isMssql, isPg } = useProject()
const inputEl = $ref<any>()
let loading = $ref(false)
@ -38,18 +37,39 @@ const validators = computed(() => {
title: [
validateTableName,
{
validator: (rule: any, value: any, callback: (errMsg?: string) => void) => {
validator: (rule: any, value: any) => {
return new Promise<void>((resolve, reject) => {
let tableNameLengthLimit = 255
if (isMysql) {
tableNameLengthLimit = 64
} else if (isPg) {
tableNameLengthLimit = 63
} else if (isMssql) {
tableNameLengthLimit = 128
}
const projectPrefix = project?.value?.prefix || ''
if ((projectPrefix + value).length > tableNameLengthLimit) {
return reject(new Error(`Table name exceeds ${tableNameLengthLimit} characters`))
}
resolve()
})
},
},
{
validator: (rule: any, value: any) => {
return new Promise<void>((resolve, reject) => {
if (/^\s+|\s+$/.test(value)) {
callback('Leading or trailing whitespace not allowed in table name')
return reject(new Error('Leading or trailing whitespace not allowed in table name'))
}
if (
!(tables?.value || []).every(
(t) => t.id === tableMeta.id || t.table_name.toLowerCase() !== (value || '').toLowerCase(),
)
) {
callback('Duplicate table alias')
return reject(new Error('Duplicate table alias'))
}
callback()
resolve()
})
},
},
],
@ -71,7 +91,8 @@ const renameTable = async () => {
loading = true
try {
await $api.dbTable.update(tableMeta?.id as string, {
title: formState.title,
project_id: tableMeta?.project_id,
table_name: formState.title,
})
dialogShow.value = false
loadTables()

2
packages/nc-gui-v2/composables/useProject.ts

@ -20,6 +20,7 @@ export function useProject(projectId?: MaybeRef<string>) {
const projectBaseType = $computed(() => project.value?.bases?.[0]?.type || '')
const isMysql = computed(() => ['mysql', 'mysql2'].includes(projectBaseType))
const isMssql = computed(() => projectBaseType === 'mssql')
const isPg = computed(() => projectBaseType === 'pg')
const sqlUi = computed(
() => SqlUiFactory.create({ client: projectBaseType }) as Exclude<ReturnType<typeof SqlUiFactory['create']>, typeof OracleUi>,
@ -82,6 +83,7 @@ export function useProject(projectId?: MaybeRef<string>) {
loadProject,
loadTables,
isMysql,
isMssql,
isPg,
sqlUi,
isSharedBase,

5
packages/nc-gui-v2/composables/useTable.ts

@ -30,12 +30,15 @@ export function useTable(onTableCreate?: (tableMeta: TableType) => void) {
return table.columns.includes(col.column_name)
})
try {
const tableMeta = await $api.dbTable.create(project?.value?.id as string, {
...table,
columns,
})
onTableCreate?.(tableMeta)
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
watch(

3
packages/nc-gui/components/ProjectTreeView.vue

@ -1431,7 +1431,8 @@ export default {
let item = cookie;
try {
await this.$api.dbTable.update(item.id, {
title,
project_id: this.projectId,
table_name: title,
});
} catch (e) {
this.$toast.error(await this._extractSdkResponseErrorMsg(e)).goAway(3000);

5
packages/nocodb-sdk/src/lib/Api.ts

@ -121,6 +121,7 @@ export interface TableType {
columns?: ColumnType[];
columnsById?: object;
slug?: string;
project_id?: string;
}
export interface ViewType {
@ -169,7 +170,7 @@ export interface TableReqType {
deleted?: boolean;
order?: number;
mm?: boolean;
columns?: ColumnType[];
columns: ColumnType[];
}
export interface TableListType {
@ -1487,7 +1488,7 @@ export class Api<
*/
update: (
tableId: string,
data: { title?: string },
data: { table_name?: string; project_id?: string },
params: RequestParams = {}
) =>
this.request<any, any>({

81
packages/nocodb/src/lib/meta/api/tableApis.ts

@ -2,6 +2,7 @@ import { Request, Response, Router } from 'express';
import Model from '../../models/Model';
import { PagedResponseImpl } from '../helpers/PagedResponse';
import { Tele } from 'nc-help';
import DOMPurify from 'isomorphic-dompurify';
import {
AuditOperationSubTypes,
AuditOperationTypes,
@ -102,6 +103,8 @@ export async function tableCreate(req: Request<any, any, TableReqType>, res) {
}
}
req.body.table_name = DOMPurify.sanitize(req.body.table_name);
// validate table name
if (/^\s+|\s+$/.test(req.body.table_name)) {
NcError.badRequest(
@ -217,18 +220,86 @@ export async function tableCreate(req: Request<any, any, TableReqType>, res) {
export async function tableUpdate(req: Request<any, any>, res) {
const model = await Model.get(req.params.tableId);
const project = await Project.getWithInfo(req.body.project_id);
const base = project.bases[0];
if (!req.body.table_name) {
NcError.badRequest(
'Missing table name `table_name` property in request body'
);
}
if (project.prefix) {
if (!req.body.table_name.startsWith(project.prefix)) {
req.body.table_name = `${project.prefix}${req.body.table_name}`;
}
}
req.body.table_name = DOMPurify.sanitize(req.body.table_name);
// validate table name
if (/^\s+|\s+$/.test(req.body.table_name)) {
NcError.badRequest(
'Leading or trailing whitespace not allowed in table names'
);
}
if (
!(await Model.checkTitleAvailable({
table_name: req.body.table_name,
project_id: project.id,
base_id: base.id,
}))
) {
NcError.badRequest('Duplicate table name');
}
if (!req.body.title) {
req.body.title = getTableNameAlias(
req.body.table_name,
project.prefix,
base
);
}
if (
!(await Model.checkAliasAvailable({
title: req.body.title,
project_id: model.project_id,
base_id: model.base_id,
exclude_id: req.params.tableId,
project_id: project.id,
base_id: base.id,
}))
) {
NcError.badRequest('Duplicate table name');
NcError.badRequest('Duplicate table alias');
}
await Model.updateAlias(req.params.tableId, req.body.title);
const sqlMgr = await ProjectMgrv2.getSqlMgr(project);
const sqlClient = NcConnectionMgrv2.getSqlClient(base);
let tableNameLengthLimit = 255;
const sqlClientType = sqlClient.clientType;
if (sqlClientType === 'mysql2' || sqlClientType === 'mysql') {
tableNameLengthLimit = 64;
} else if (sqlClientType === 'pg') {
tableNameLengthLimit = 63;
} else if (sqlClientType === 'mssql') {
tableNameLengthLimit = 128;
}
if (req.body.table_name.length > tableNameLengthLimit) {
NcError.badRequest(`Table name exceeds ${tableNameLengthLimit} characters`);
}
await Model.updateAliasAndTableName(
req.params.tableId,
req.body.title,
req.body.table_name
);
await sqlMgr.sqlOpPlus(base, 'tableRename', {
...req.body,
tn: req.body.table_name,
tn_old: model.table_name,
});
Tele.emit('evt', { evt_type: 'table:updated' });

16
packages/nocodb/src/lib/models/Model.ts

@ -412,14 +412,25 @@ export default class Model implements TableType {
return insertObj;
}
static async updateAlias(tableId, title: string, ncMeta = Noco.ncMeta) {
if (!title) NcError.badRequest("Missing 'title' property in body");
static async updateAliasAndTableName(
tableId,
title: string,
table_name: string,
ncMeta = Noco.ncMeta
) {
if (!title) {
NcError.badRequest("Missing 'title' property in body");
}
if (!table_name) {
NcError.badRequest("Missing 'table_name' property in body");
}
// get existing cache
const key = `${CacheScope.MODEL}:${tableId}`;
const o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT);
// update alias
if (o) {
o.title = title;
o.table_name = table_name;
// set cache
await NocoCache.set(key, o);
}
@ -430,6 +441,7 @@ export default class Model implements TableType {
MetaTable.MODELS,
{
title,
table_name,
},
tableId
);

6
scripts/cypress/integration/common/1a_table_operations.js

@ -76,6 +76,9 @@ export const genTest = (apiType, dbType) => {
cy.closeTableTab("CityX");
// revert re-name operation to not impact rest of test suite
cy.renameTable("CityX", "City");
// 4. verify linked contents in other table
// 4a. Address table, has many field
cy.openTableTab("Address", 25);
@ -97,9 +100,6 @@ export const genTest = (apiType, dbType) => {
.contains("Kabul")
.should("exist");
cy.closeTableTab("Country");
// revert re-name operation to not impact rest of test suite
cy.renameTable("CityX", "City");
});
});
};

8
scripts/sdk/swagger.json

@ -1309,7 +1309,10 @@
"schema": {
"type": "object",
"properties": {
"title": {
"table_name": {
"type": "string"
},
"project_id": {
"type": "string"
}
}
@ -6050,6 +6053,9 @@
},
"slug": {
"type": "string"
},
"project_id": {
"type": "string"
}
},
"required": [

Loading…
Cancel
Save