This is an automated email from the ASF dual-hosted git repository.
nicholasjiang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-webui.git
The following commit(s) were added to refs/heads/main by this push:
new 1707e92a [Feature] Support editing column (#278)
1707e92a is described below
commit 1707e92aa826deb1b78e3c0b6c8f63282cfdea88
Author: s7monk <[email protected]>
AuthorDate: Mon Jun 3 14:24:21 2024 +0800
[Feature] Support editing column (#278)
---
paimon-web-ui/src/api/models/catalog/index.ts | 11 +
.../src/api/models/catalog/types/table.ts | 9 +
paimon-web-ui/src/locales/zh/modules/metadata.ts | 2 +-
.../metadata/components/columns-form/index.tsx | 53 +++-
.../components/table-column-content/index.tsx | 344 +++++++++++----------
5 files changed, 242 insertions(+), 177 deletions(-)
diff --git a/paimon-web-ui/src/api/models/catalog/index.ts
b/paimon-web-ui/src/api/models/catalog/index.ts
index de887a22..e0de4144 100644
--- a/paimon-web-ui/src/api/models/catalog/index.ts
+++ b/paimon-web-ui/src/api/models/catalog/index.ts
@@ -16,6 +16,7 @@ specific language governing permissions and limitations
under the License. */
import type {
+ AlterTableDTO,
Catalog,
CatalogDTO,
ColumnParams,
@@ -91,6 +92,16 @@ export function createTable() {
})
}
+/**
+ * # Alter Table
+ */
+export function alterTable() {
+ return httpRequest.createHooks!<unknown, AlterTableDTO>({
+ url: '/table/alter',
+ method: 'post',
+ })
+}
+
/**
* # Get options
*/
diff --git a/paimon-web-ui/src/api/models/catalog/types/table.ts
b/paimon-web-ui/src/api/models/catalog/types/table.ts
index 45a48eff..4f5952d7 100644
--- a/paimon-web-ui/src/api/models/catalog/types/table.ts
+++ b/paimon-web-ui/src/api/models/catalog/types/table.ts
@@ -54,6 +54,13 @@ export interface TableDTO {
options?: TableOption[]
}
+export interface AlterTableDTO {
+ catalogName: string
+ databaseName: string
+ tableName: string
+ tableColumns?: ColumnDTO[]
+}
+
export interface TableDetail {
catalogId?: number
catalogName?: string
@@ -67,11 +74,13 @@ export interface TableDetail {
}
export interface ColumnDTO {
+ id: number
field: string
dataType: DataTypeDTO
comment: string
defaultValue: string
pk: boolean
+ sort: number
}
export interface DataTypeDTO {
diff --git a/paimon-web-ui/src/locales/zh/modules/metadata.ts
b/paimon-web-ui/src/locales/zh/modules/metadata.ts
index 95231a7f..a1118949 100644
--- a/paimon-web-ui/src/locales/zh/modules/metadata.ts
+++ b/paimon-web-ui/src/locales/zh/modules/metadata.ts
@@ -50,7 +50,7 @@ export default {
column_pk: '主键',
column_nullable: '是否为空',
column_default: '默认值',
- column_comment: '标注',
+ column_comment: '备注',
column_action: '操作',
partition_columns: '分区列',
diff --git a/paimon-web-ui/src/views/metadata/components/columns-form/index.tsx
b/paimon-web-ui/src/views/metadata/components/columns-form/index.tsx
index f912c99b..086b2f50 100644
--- a/paimon-web-ui/src/views/metadata/components/columns-form/index.tsx
+++ b/paimon-web-ui/src/views/metadata/components/columns-form/index.tsx
@@ -19,7 +19,7 @@ import { Add } from '@vicons/ionicons5'
import ColumnFormContent, { newField } from '../table-column-content'
import { useCatalogStore } from '@/store/catalog'
-import { type ColumnDTO, createColumns } from '@/api/models/catalog'
+import { type ColumnDTO, alterTable, createColumns } from
'@/api/models/catalog'
import { transformOption } from '@/views/metadata/constant'
interface ColumnFormType {
@@ -46,7 +46,8 @@ export default defineComponent({
const message = useMessage()
const catalogStore = useCatalogStore()
- const [, createFetch, { loading }] = createColumns()
+ const [, createFetch, { loading: createLoading }] = createColumns()
+ const [, editColumn, { loading: editLoading }] = alterTable()
const formRef = ref()
const formValue = ref<ColumnFormType>(resetState())
@@ -55,7 +56,9 @@ export default defineComponent({
return Boolean(props.tableColumns)
})
- async function handleConfirm() {
+ const loading = computed(() => createLoading.value || editLoading.value)
+
+ async function handleAddColumn() {
await formRef.value.validate()
await createFetch({
params: transformOption({
@@ -69,6 +72,24 @@ export default defineComponent({
props.onConfirm!()
}
+ async function handleEditColumn() {
+ await formRef.value.validate()
+ const currentTable = toRaw(catalogStore.currentTable)!
+ const { catalogName, databaseName, tableName } = currentTable
+ await editColumn({
+ params: {
+ catalogName,
+ databaseName,
+ tableName,
+ tableColumns: toRaw(formValue.value).tableColumns,
+ },
+ })
+
+ handleCloseModal()
+ message.success(t(`${isEdit.value ? 'Edit' : 'Create'} Column`))
+ props.onConfirm!()
+ }
+
function handleCloseModal() {
props.onClose!()
nextTick(() => {
@@ -105,8 +126,9 @@ export default defineComponent({
t,
handleCloseModal,
- handleConfirm,
+ handleAddColumn,
handleAddOption,
+ handleEditColumn,
}
},
render() {
@@ -115,16 +137,20 @@ export default defineComponent({
<n-card
bordered={true}
title={`${this.isEdit ? 'Edit' : 'Create'} Column`}
- style="width: 1100px"
+ style={{ width: this.isEdit ? '1000px' : '1110px' }}
>
{{
- 'header-extra': () => (
- <n-button quaternary circle size="tiny"
onClick={this.handleAddOption}>
- <n-icon>
- <Add />
- </n-icon>
- </n-button>
- ),
+ 'header-extra': () => {
+ if (!this.isEdit) {
+ return (
+ <n-button quaternary circle size="tiny"
onClick={this.handleAddOption}>
+ <n-icon>
+ <Add />
+ </n-icon>
+ </n-button>
+ )
+ }
+ },
'default': () => (
<n-form
ref="formRef"
@@ -135,13 +161,14 @@ export default defineComponent({
>
<ColumnFormContent
v-model:data={this.formValue.tableColumns}
+ isEdit={this.isEdit}
/>
</n-form>
),
'action': () => (
<n-space justify="end">
<n-button
onClick={this.handleCloseModal}>{this.t('layout.cancel')}</n-button>
- <n-button type="primary" loading={this.loading}
onClick={this.handleConfirm}>
+ <n-button type="primary" loading={this.loading}
onClick={this.isEdit ? this.handleEditColumn : this.handleAddColumn}>
{this.t('layout.confirm')}
</n-button>
</n-space>
diff --git
a/paimon-web-ui/src/views/metadata/components/table-column-content/index.tsx
b/paimon-web-ui/src/views/metadata/components/table-column-content/index.tsx
index 4bbfad47..b4abf79c 100644
--- a/paimon-web-ui/src/views/metadata/components/table-column-content/index.tsx
+++ b/paimon-web-ui/src/views/metadata/components/table-column-content/index.tsx
@@ -26,10 +26,15 @@ const props = {
type: Array as PropType<ColumnDTO[]>,
default: () => [],
},
+ 'isEdit': {
+ type: Boolean as PropType<boolean>,
+ default: false,
+ },
'onUpdate:data': [Function, Array] as PropType<((value: ColumnDTO[]) =>
void) | undefined>,
}
export const newField: ColumnDTO = {
+ id: 0,
field: '',
dataType: {
nullable: true,
@@ -38,6 +43,7 @@ export const newField: ColumnDTO = {
comment: '',
defaultValue: '',
pk: false,
+ sort: 0,
}
export default defineComponent({
@@ -54,177 +60,189 @@ export default defineComponent({
props.data.splice(i, 1)
}
- const columns: DataTableColumns<ColumnDTO> = [
- {
- title: '#',
- key: 'field',
- render: () => {
- return (
- <n-icon class="drag-handle">
- <UnorderedListOutlined />
- </n-icon>
- )
- },
- },
- {
- title: t('metadata.column_field'),
- key: 'field',
- width: 120,
- render: (row, index) => {
- return (
- <n-form-item
- show-feedback={false}
- showLabel={false}
- path={`tableColumns[${index}].field`}
- rule={{
- required: true,
- message: 'Field is required',
- trigger: ['input', 'blur'],
- }}
- >
- <n-input v-model:value={row.field} />
- </n-form-item>
- )
+ watch(props.data, (newData) => {
+ newData.forEach((item, index) => {
+ item.sort = index
+ })
+ }, { deep: true })
+
+ const columns = computed(() => {
+ const baseColumns: DataTableColumns<ColumnDTO> = [
+ {
+ title: '#',
+ key: 'field',
+ render: () => {
+ return (
+ <n-icon class="drag-handle">
+ <UnorderedListOutlined />
+ </n-icon>
+ )
+ },
},
- },
- {
- title: t('metadata.column_type'),
- key: 'dataType',
- width: 150,
- render: (row, index) => {
- return (
- <n-form-item
- show-feedback={false}
- showLabel={false}
- path={`tableColumns[${index}].dataType.type`}
- rule={{
- required: true,
- message: 'DateType is required',
- trigger: ['input', 'blur'],
- }}
- >
- <n-select
- placeholder={t('metadata.column_type')}
- v-model:value={row.dataType.type}
- options={dataTypeOptions}
- />
- </n-form-item>
- )
+ {
+ title: t('metadata.column_field'),
+ key: 'field',
+ width: 150,
+ render: (row, index) => {
+ return (
+ <n-form-item
+ show-feedback={false}
+ showLabel={false}
+ path={`tableColumns[${index}].field`}
+ rule={{
+ required: true,
+ message: 'Field is required',
+ trigger: ['input', 'blur'],
+ }}
+ >
+ <n-input v-model:value={row.field} />
+ </n-form-item>
+ )
+ },
},
- },
- {
- title: t('metadata.column_length'),
- key: 'length',
- width: 190,
- render: (row, index) => {
- const hasStartLength = hasLength.includes(row.dataType.type || '')
- const hasScaleLength = hasEndLength.includes(row.dataType.type || '')
- return (
- <n-space wrap={false}>
- {hasStartLength && (
- <n-form-item
- show-feedback={false}
- showLabel={false}
- path={`tableColumns[${index}].dataType.precision`}
- rule={{
- type: 'number',
- required: true,
- trigger: ['input', 'blur'],
- }}
- >
- <n-input-number v-model:value={row.dataType.precision}
show-button={false} />
- </n-form-item>
- )}
- {hasScaleLength && (
- <n-form-item
- show-feedback={false}
- showLabel={false}
- path={`tableColumns[${index}].dataType.scale`}
- rule={{
- type: 'number',
- required: true,
- trigger: ['input', 'blur'],
- }}
- >
- <n-input-number v-model:value={row.dataType.scale}
show-button={false} />
- </n-form-item>
- )}
- </n-space>
- )
+ {
+ title: t('metadata.column_type'),
+ key: 'dataType',
+ width: 150,
+ render: (row, index) => {
+ return (
+ <n-form-item
+ show-feedback={false}
+ showLabel={false}
+ path={`tableColumns[${index}].dataType.type`}
+ rule={{
+ required: true,
+ message: 'DateType is required',
+ trigger: ['input', 'blur'],
+ }}
+ >
+ <n-select
+ placeholder={t('metadata.column_type')}
+ v-model:value={row.dataType.type}
+ options={dataTypeOptions}
+ />
+ </n-form-item>
+ )
+ },
},
- },
- {
- title: t('metadata.column_pk'),
- key: 'pk',
- width: 60,
- render: (row, index) => {
- return (
- <n-form-item show-feedback={false} showLabel={false}
path={`tableColumns[${index}].pk`}>
- <n-checkbox v-model:checked={row.pk} />
- </n-form-item>
- )
+ {
+ title: t('metadata.column_length'),
+ key: 'length',
+ width: 170,
+ render: (row, index) => {
+ const hasStartLength = hasLength.includes(row.dataType.type || '')
+ const hasScaleLength = hasEndLength.includes(row.dataType.type ||
'')
+ return (
+ <n-space wrap={false}>
+ {hasStartLength && (
+ <n-form-item
+ show-feedback={false}
+ showLabel={false}
+ path={`tableColumns[${index}].dataType.precision`}
+ rule={{
+ type: 'number',
+ required: true,
+ trigger: ['input', 'blur'],
+ }}
+ >
+ <n-input-number v-model:value={row.dataType.precision}
show-button={false} />
+ </n-form-item>
+ )}
+ {hasScaleLength && (
+ <n-form-item
+ show-feedback={false}
+ showLabel={false}
+ path={`tableColumns[${index}].dataType.scale`}
+ rule={{
+ type: 'number',
+ required: true,
+ trigger: ['input', 'blur'],
+ }}
+ >
+ <n-input-number v-model:value={row.dataType.scale}
show-button={false} />
+ </n-form-item>
+ )}
+ </n-space>
+ )
+ },
},
- },
- {
- title: t('metadata.column_nullable'),
- key: 'dataType.nullable',
- width: 80,
- render: (row, index) => {
- return (
- <n-form-item
- show-feedback={false}
- showLabel={false}
- path={`tableColumns[${index}].dataType.nullable`}
- >
- <n-checkbox v-model:checked={row.dataType.nullable} />
- </n-form-item>
- )
+ {
+ title: t('metadata.column_pk'),
+ key: 'pk',
+ width: 60,
+ render: (row, index) => {
+ return (
+ <n-form-item show-feedback={false} showLabel={false}
path={`tableColumns[${index}].pk`}>
+ <n-checkbox v-model:checked={row.pk} />
+ </n-form-item>
+ )
+ },
},
- },
- {
- title: t('metadata.column_default'),
- key: 'defaultValue',
- width: 150,
- render: (row, index) => {
- return (
- <n-form-item
- show-feedback={false}
- showLabel={false}
- path={`tableColumns[${index}].defaultValue`}
- >
- <n-input v-model:value={row.defaultValue} />
- </n-form-item>
- )
+ {
+ title: t('metadata.column_nullable'),
+ key: 'dataType.nullable',
+ width: 80,
+ render: (row, index) => {
+ return (
+ <n-form-item
+ show-feedback={false}
+ showLabel={false}
+ path={`tableColumns[${index}].dataType.nullable`}
+ >
+ <n-checkbox v-model:checked={row.dataType.nullable} />
+ </n-form-item>
+ )
+ },
},
- },
- {
- title: t('metadata.column_comment'),
- key: 'comment',
- width: 150,
- render: (row, index) => {
- return (
- <n-form-item
- show-feedback={false}
- showLabel={false}
- path={`tableColumns[${index}].defaultValue`}
- >
- <n-input v-model:value={row.comment} />
- </n-form-item>
- )
+ {
+ title: t('metadata.column_default'),
+ key: 'defaultValue',
+ width: 150,
+ render: (row, index) => {
+ return (
+ <n-form-item
+ show-feedback={false}
+ showLabel={false}
+ path={`tableColumns[${index}].defaultValue`}
+ >
+ <n-input v-model:value={row.defaultValue} />
+ </n-form-item>
+ )
+ },
},
- },
- {
- title: t('metadata.column_action'),
- key: 'Operate',
- render: (_, index) => {
- return (
- <n-button onClick={() => onDelete(index)} tertiary type="error">
- Remove
- </n-button>
- )
+ {
+ title: t('metadata.column_comment'),
+ key: 'comment',
+ width: 150,
+ render: (row, index) => {
+ return (
+ <n-form-item
+ show-feedback={false}
+ showLabel={false}
+ path={`tableColumns[${index}].defaultValue`}
+ >
+ <n-input v-model:value={row.comment} />
+ </n-form-item>
+ )
+ },
},
- },
- ]
+ ]
+
+ if (!props.isEdit) {
+ baseColumns.push({
+ title: t('metadata.column_action'),
+ key: 'Operate',
+ render: (_, index) => {
+ return (
+ <n-button onClick={() => onDelete(index)} tertiary type="error">
+ Remove
+ </n-button>
+ )
+ },
+ })
+ }
+ return baseColumns
+ })
return {
columns,