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,

Reply via email to