Leo 2 years ago
parent
commit
46ebbf73d3

+ 219 - 6
src/module/datasource/component/DatasourceManage.jsx

@@ -1,5 +1,14 @@
 import React, { useState, useEffect, useImperativeHandle } from 'react'
-import { Space, Table, Modal, message, Popconfirm, Button, Select } from 'antd'
+import {
+  Space,
+  Table,
+  Modal,
+  message,
+  Popconfirm,
+  Button,
+  Select,
+  Tabs,
+} from 'antd'
 import DatasourceData from '../component/DatasourceData'
 import DatasourceEdit from '../component/DatasourceEdit'
 import {
@@ -10,13 +19,20 @@ import {
   getAilabList,
   getUserProjects,
   shareAilab,
+  getLakeTable,
+  ailabTablePreview,
+  ailabSchema,
+  lakeTablePreview,
+  lakeSchema,
 } from '../services'
 
 const { Option } = Select
+const { TabPane } = Tabs
 
 export default function DatasourceManage({ onRef, dataType }) {
   // 查看数据源弹窗是否可可视
   const [dataModalVisible, setDataModalVisible] = useState(false)
+  const [dataPreviewVisible, setDataPreviewVisible] = useState(false)
   const [dsModalVisible, setDsModalVisible] = useState(false)
 
   // 初始化数据源列表数据 constructor
@@ -24,6 +40,8 @@ export default function DatasourceManage({ onRef, dataType }) {
   // 初始化ailab列表数据
   const [ailabDataList, setAilabDataList] = useState([])
 
+  const [lakeDataList, setLakeDataList] = useState([])
+
   // 表格Loading状态
   const [dataLoading, setDataLoading] = useState(false)
 
@@ -45,6 +63,18 @@ export default function DatasourceManage({ onRef, dataType }) {
   const [selectedAilabTable, setSelectedAilabTable] = useState([])
   const [projectsList, setProjectsList] = useState([])
   const [selectedProjects, setSelectedProjects] = useState([])
+  // 表内容表头
+  const [contentCols, setContentCols] = useState([])
+  // 表内容表数据
+  const [contentData, setContentData] = useState([])
+  // 表内容表格Loading状态
+  const [contentLoading, setContentLoading] = useState(false)
+  // 表结构表头
+  const [schemaCols, setScheamCols] = useState([])
+  // 表结构表数据
+  const [schemaData, setScheamData] = useState([])
+  // 表结构表格Loading状态
+  const [schemaLoading, setSchemaLoading] = useState(false)
 
   const onSelectChange = newSelectedRowKeys => {
     setSelectedRowKeys(newSelectedRowKeys)
@@ -90,11 +120,30 @@ export default function DatasourceManage({ onRef, dataType }) {
     setDataLoading(false)
   }
 
+  const fetchLakeList = async () => {
+    setDataLoading(true)
+    const { data } = await getLakeTable()
+    if (data.code === 200) {
+      const list = data.data.map(item => {
+        return {
+          key: item.id,
+          database_name: item.database_name,
+          datatable_name: item.table_name,
+        }
+      })
+      setLakeDataList(list)
+    } else {
+      message.error(data.msg)
+    }
+    setDataLoading(false)
+  }
+
   // 暴露更新列表方法
   useImperativeHandle(onRef, () => {
     return {
       updateSourceList: fetchDataSourceList,
       updateAilabList: fetchAilabList,
+      updateLakeList: fetchLakeList,
     }
   })
 
@@ -122,6 +171,95 @@ export default function DatasourceManage({ onRef, dataType }) {
     }
     setTableWatcher(false)
   }
+  const handleContent = contentData => {
+    const colsList = contentData?.header?.map(item => {
+      return {
+        title: item,
+        dataIndex: item,
+        key: item,
+      }
+    })
+    const dataList = contentData?.content?.map((item, index) => {
+      const row = {}
+      row['key'] = index + 1
+      item.forEach((val, index) => {
+        const title = contentData.header[index]
+        row[title] = val
+      })
+      return row
+    })
+    setContentCols(colsList)
+    setContentData(dataList)
+  }
+
+  const handleSchema = schemaData => {
+    const colsList = [
+      {
+        title: '序号',
+        dataIndex: 'order',
+        key: 'order',
+      },
+      {
+        title: '字段',
+        dataIndex: 'field',
+        key: 'field',
+      },
+      {
+        title: '属性',
+        dataIndex: 'attribute',
+        key: 'attribute',
+      },
+    ]
+    const dataList = schemaData?.map((item, index) => {
+      const row = {}
+      row['key'] = index + 1
+      const [order, field, attribute] = item.split(':')
+      row['order'] = order
+      row['field'] = field
+      row['attribute'] = attribute
+      return row
+    })
+    setScheamCols(colsList)
+    setScheamData(dataList)
+  }
+
+  const fetchAilabDataPreview = async (key, type) => {
+    setContentLoading(true)
+    const { data } =
+      type === 'ailab'
+        ? await ailabTablePreview(key)
+        : await lakeTablePreview(key)
+    if (data.code === 200) {
+      handleContent(data.data)
+    } else {
+      message.error(data.msg)
+    }
+    setContentLoading(false)
+  }
+
+  const fetchAilabSchema = async (key, type) => {
+    setSchemaLoading(true)
+    const { data } =
+      type === 'ailab' ? await ailabSchema(key) : await lakeSchema(key)
+    if (data.code === 200) {
+      handleSchema(data.data)
+    } else {
+      message.error(data.msg)
+    }
+    setSchemaLoading(false)
+  }
+
+  const watchAilabData = async table_name => {
+    fetchAilabDataPreview(table_name, 'ailab')
+    fetchAilabSchema(table_name, 'ailab')
+    setDataPreviewVisible(true)
+  }
+
+  const watchLakeData = async table_name => {
+    fetchAilabDataPreview(table_name, 'lake')
+    fetchAilabSchema(table_name, 'lake')
+    setDataPreviewVisible(true)
+  }
 
   useEffect(() => {
     switch (dataType) {
@@ -132,6 +270,7 @@ export default function DatasourceManage({ onRef, dataType }) {
         fetchAilabList()
         break
       case 'datalake':
+        fetchLakeList()
         break
       default:
         break
@@ -280,10 +419,41 @@ export default function DatasourceManage({ onRef, dataType }) {
           </Button>
           <Button
             onClick={() => {
-              fetchTableNames(record.key)
+              watchAilabData(record.key)
             }}
-            type="link"
-            disabled={tableWatcher}>
+            type="link">
+            查看
+          </Button>
+        </Space>
+      ),
+    },
+  ]
+
+  const dl_columns = [
+    {
+      title: '数据库名称',
+      dataIndex: 'database_name',
+      key: 'database_name',
+      align: 'center',
+    },
+    {
+      title: '数据表名称',
+      dataIndex: 'datatable_name',
+      key: 'datatable_name',
+      align: 'center',
+    },
+    {
+      title: '操作',
+      key: 'operation',
+      align: 'center',
+      width: '25%',
+      render: (_, record) => (
+        <Space size="middle">
+          <Button
+            onClick={() => {
+              watchLakeData(record.key)
+            }}
+            type="link">
             查看
           </Button>
         </Space>
@@ -300,6 +470,10 @@ export default function DatasourceManage({ onRef, dataType }) {
     setDsModalVisible(false)
   }
 
+  const handledataPreviewCancel = () => {
+    setDataPreviewVisible(false)
+  }
+
   // 查看数据源弹窗数据
   const dataModal = (
     <Modal
@@ -314,6 +488,44 @@ export default function DatasourceManage({ onRef, dataType }) {
     </Modal>
   )
 
+  const dataPreviewModal = (
+    <Modal
+      title="数据表预览"
+      visible={dataPreviewVisible}
+      onCancel={handledataPreviewCancel}
+      footer={null}
+      destroyOnClose={true}
+      width={'80%'}
+      bodyStyle={{ paddingTop: 0 }}>
+      {
+        <Tabs size={'small'}>
+          <TabPane
+            tab="预览表内容"
+            key="content"
+            style={{ height: '700px', overflow: 'auto' }}>
+            <Table
+              columns={contentCols}
+              dataSource={contentData}
+              bordered
+              loading={contentLoading}
+            />
+          </TabPane>
+          <TabPane
+            tab="预览表结构"
+            key="schema"
+            style={{ height: '700px', overflow: 'auto' }}>
+            <Table
+              columns={schemaCols}
+              dataSource={schemaData}
+              bordered
+              loading={schemaLoading}
+            />
+          </TabPane>
+        </Tabs>
+      }
+    </Modal>
+  )
+
   const dsEditModal = (
     <Modal
       title="编辑数据源"
@@ -429,13 +641,14 @@ export default function DatasourceManage({ onRef, dataType }) {
       )}
       {dataType === 'datalake' && (
         <Table
-          columns={ds_columns}
-          dataSource={dataSourceList}
+          columns={dl_columns}
+          dataSource={lakeDataList}
           bordered
           loading={dataLoading}
         />
       )}
       {dataModal}
+      {dataPreviewModal}
       {dsEditModal}
       {shareMadal}
     </div>

+ 248 - 27
src/module/datasource/page/DatasourceView.jsx

@@ -1,18 +1,21 @@
-import { Tabs, Button, Space, Select, Modal, Input } from 'antd'
+import { Tabs, Button, Space, Select, Modal, Input, Form, message } from 'antd'
 import DataSourceManage from '../component/DatasourceManage.jsx'
 import DatasourceAdd from '../component/DatasourceAdd'
 import DatasourceSyncView from '../component/DatasourceSyncView'
 import DatasourceLog from '../component/DatasourceLog'
-import React, { useState } from 'react'
+import React, { useState, useEffect } from 'react'
 import { Link, useNavigate, useLocation } from 'react-router-dom'
 import styled from 'styled-components'
-import { useEffect } from 'react'
+import { importDatalake, craeteAilab } from '../services'
+import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'
+
 const { TabPane } = Tabs
 
 const Datasource = styled.div`
   padding: 20px;
 `
-// const Main = () {}
+const FormItem = Form.Item
+const FormList = Form.List
 
 export default function DatasourceView() {
   const navigate = useNavigate()
@@ -29,27 +32,9 @@ export default function DatasourceView() {
   const [currentLogPageSize, setCurrentLogPageSize] = useState(10)
   // 导入数据表
   const [importData, setImportData] = useState(false)
-  const importTable = async () => {}
-  const importModal = (
-    <Modal
-      title="导入数据表"
-      visible="importData"
-      onOk={importTable}
-      okText="确认导入"
-      onCancel={() => {
-        setImportData(false)
-      }}>
-      <Space style={{ padding: '10px 10px 10px ' }}>
-        <span>数据库名称:</span>
-        <Input></Input>
-      </Space>
-      <br />
-      <Space style={{ padding: '10px 10px 10px ' }}>
-        <span>数据表名称:</span>
-        <Input></Input>
-      </Space>
-    </Modal>
-  )
+  const [isCreateTable, setIsCreateTable] = useState(false)
+  const [datatableForm] = Form.useForm()
+  const [ailabtableForm] = Form.useForm()
   // 数据隔离
   const [currentDataType, setCurrentDataType] = useState('ailab')
 
@@ -71,6 +56,13 @@ export default function DatasourceView() {
   const updateDataSource = () => {
     sourceRef.current.updateSourceList()
   }
+
+  const updateAilabList = () => {
+    sourceRef.current.updateAilabList()
+  }
+  const updateLakeList = () => {
+    sourceRef.current.updateLakeList()
+  }
   const updateLogs = () => {
     logRef.current?.updateLogList()
   }
@@ -84,6 +76,230 @@ export default function DatasourceView() {
     setCurrentDataType(val)
   }
 
+  const importTable = () => {
+    datatableForm
+      .validateFields()
+      .then(async () => {
+        const { database_name, datatable_name } = datatableForm.getFieldValue()
+        const { data } = await importDatalake({
+          database_name,
+          table_name: datatable_name,
+        })
+        if (data.code === 200) {
+          message.success('导入成功')
+          datatableForm.resetFields()
+          setImportData(false)
+          updateLakeList()
+        } else {
+          message.error(data.msg)
+        }
+      })
+      .catch(err => {
+        message.error('请检查表单数据是否完整')
+      })
+  }
+  const importModal = (
+    <Modal
+      title="导入数据表"
+      visible={importData}
+      onOk={importTable}
+      okText="确认导入"
+      onCancel={() => {
+        datatableForm.resetFields()
+        setImportData(false)
+      }}>
+      <Form form={datatableForm}>
+        <FormItem
+          name="database_name"
+          label="数据库名称"
+          rules={[
+            {
+              required: true,
+              message: '请输入数据库名称!',
+            },
+          ]}>
+          <Input placeholder="输入数据库名称" />
+        </FormItem>
+        <FormItem
+          name="datatable_name"
+          label="数据表名称"
+          rules={[
+            {
+              required: true,
+              message: '请输入数据表名称!',
+            },
+          ]}>
+          <Input placeholder="输入数据表名称" />
+        </FormItem>
+      </Form>
+    </Modal>
+  )
+
+  const createAilabTable = () => {
+    console.log()
+    ailabtableForm
+      .validateFields()
+      .then(async () => {
+        const { database_field, table_name, partition_column } =
+          ailabtableForm.getFieldsValue()
+        const params = {
+          project_id: sessionStorage.getItem('project_id'),
+          table_name,
+          partition_column,
+          columns: database_field,
+        }
+        const { data } = await craeteAilab(params)
+        if (data.code === 200) {
+          message.success('创建成功')
+          ailabtableForm.resetFields()
+          setIsCreateTable(false)
+        } else {
+          message.error(data.msg)
+        }
+      })
+      .catch(err => {
+        message.error('请检查表单数据是否完整')
+      })
+  }
+
+  const formItemLayout = {
+    labelCol: {
+      xs: { span: 24 },
+      sm: { span: 6 },
+    },
+    wrapperCol: {
+      xs: { span: 24 },
+      sm: { span: 18 },
+    },
+  }
+  const formItemLayoutWithOutLabel = {
+    wrapperCol: {
+      xs: { span: 24, offset: 0 },
+      sm: { span: 18, offset: 6 },
+    },
+  }
+
+  const createTableModal = (
+    <Modal
+      title="创建数据表"
+      visible={isCreateTable}
+      onOk={createAilabTable}
+      okText="确认创建"
+      onCancel={() => {
+        ailabtableForm.resetFields()
+        setIsCreateTable(false)
+      }}>
+      <Form form={ailabtableForm} {...formItemLayout}>
+        <FormItem
+          name="table_name"
+          label="数据表名称"
+          rules={[
+            {
+              required: true,
+              message: '请输入数据表名称!',
+            },
+          ]}>
+          <Input placeholder="输入数据表名称" />
+        </FormItem>
+        <FormList
+          name="database_field"
+          rules={[
+            {
+              validator: async (_, database_field) => {
+                if (!database_field || database_field.length < 1) {
+                  return Promise.reject(new Error('最少存在一个字段'))
+                }
+              },
+            },
+          ]}>
+          {(fields, { add, remove }, { errors }) => (
+            <div style={{ maxHeight: '400px', overflow: 'auto' }}>
+              {fields.map((field, index) => (
+                <Form.Item
+                  {...(index === 0
+                    ? formItemLayout
+                    : formItemLayoutWithOutLabel)}
+                  label={index === 0 ? '表字段设置' : ''}
+                  required={true}
+                  key={field.key}>
+                  <Space>
+                    <Form.Item
+                      {...field}
+                      key={`filed_${field.key}`}
+                      name={[field.name, 'column_name']}
+                      validateTrigger={['onChange', 'onBlur']}
+                      rules={[
+                        {
+                          required: true,
+                          whitespace: true,
+                          message: '请输入表字段',
+                        },
+                      ]}
+                      noStyle>
+                      <Input placeholder="输入表字段" />
+                    </Form.Item>
+                    <Form.Item
+                      {...field}
+                      key={`type_${field.key}`}
+                      name={[field.name, 'Column_type']}
+                      validateTrigger={['onChange', 'onBlur']}
+                      rules={[
+                        {
+                          required: true,
+                          whitespace: true,
+                          message: '请选择表类型',
+                        },
+                      ]}
+                      noStyle>
+                      <Select
+                        style={{ minWidth: '100px' }}
+                        placeholder="选择表类型"
+                        options={[
+                          { value: 'tinyint', label: 'tinyint' },
+                          { value: 'smallint', label: 'smallint' },
+                          { value: 'int', label: 'int' },
+                          { value: 'bigint', label: 'bigint' },
+                          { value: 'boolean', label: 'boolean' },
+                          { value: 'float', label: 'float' },
+                          { value: 'double', label: 'double' },
+                          { value: 'string', label: 'string' },
+                          { value: 'binary', label: 'binary' },
+                          { value: 'timestamp', label: 'timestamp' },
+                          { value: 'decimal', label: 'decimal' },
+                          { value: 'char', label: 'char' },
+                          { value: 'varchar', label: 'varchar' },
+                          { value: 'date', label: 'date' },
+                        ]}
+                      />
+                    </Form.Item>
+                    {fields.length > 1 ? (
+                      <MinusCircleOutlined
+                        className="dynamic-delete-button"
+                        onClick={() => remove(field.name)}
+                      />
+                    ) : null}
+                  </Space>
+                </Form.Item>
+              ))}
+              <Form.Item {...formItemLayoutWithOutLabel}>
+                <Button
+                  type="dashed"
+                  onClick={() => add()}
+                  block
+                  icon={<PlusOutlined />}>
+                  继续添加
+                </Button>
+              </Form.Item>
+            </div>
+          )}
+        </FormList>
+        <FormItem name="partition_column" label="分区字段">
+          <Input placeholder="输入分区字段" />
+        </FormItem>
+      </Form>
+    </Modal>
+  )
+
   // Tab标签右边按钮
   const tabExtraBtn = () => {
     switch (currentKey) {
@@ -103,14 +319,18 @@ export default function DatasourceView() {
               <DatasourceAdd updateDataSource={updateDataSource} />
             )}
             {currentDataType === 'ailab' && (
-              <Button type="primary" onClick={() => {}}>
+              <Button
+                type="primary"
+                onClick={() => {
+                  setIsCreateTable(true)
+                }}>
                 创建数据表
               </Button>
             )}
             {currentDataType === 'datalake' && (
               <Button
                 type="primary"
-                onclick={() => {
+                onClick={() => {
                   setImportData(true)
                 }}>
                 导入数据表
@@ -163,6 +383,7 @@ export default function DatasourceView() {
         </TabPane>
       </Tabs>
       {importModal}
+      {createTableModal}
     </Datasource>
   )
 }

+ 45 - 0
src/module/datasource/services/index.js

@@ -160,3 +160,48 @@ export const shareAilab = (params) =>
     data: params
   })
 
+// 倒入表
+export const importDatalake = (params) =>
+  request({
+    url: `/jpt/datasource/import_datalake`,
+    method: 'post',
+    data: params
+  })
+
+// 获取数据湖列表
+export const getLakeTable = () =>
+  request({
+    url: `jpt/datasource/lake_table?project_id=${sessionStorage.getItem('project_id')}`,
+    method: 'get',
+  })
+
+// 新建ailab表
+export const craeteAilab = (params) =>
+  request({
+    url: `jpt/datasource/create_table`,
+    method: 'post',
+    data: params
+  })
+
+export const ailabTablePreview = (table_name) =>
+  request({
+    url: `jpt/datasource/preview_ailab_table?table_name=${table_name}`,
+    method: 'get',
+  })
+export const ailabSchema = (table_name) =>
+  request({
+    url: `jpt/datasource/ailab_table_schema?table_name=${table_name}`,
+    method: 'get',
+  })
+
+export const lakeTablePreview = (table_name) =>
+  request({
+    url: `jpt/datasource/preview_lake_table?table_name=${table_name}`,
+    method: 'get',
+  })
+export const lakeSchema = (table_name) =>
+  request({
+    url: `jpt/datasource/lake_table_schema?table_name=${table_name}`,
+    method: 'get',
+  })
+