Commit 3c598fe3 by zhaochengxiang

资源目录同步schema

parent 9fb407f4
import * as service from '../service/dataassetmanager';
import * as metadataService from '../service/metadata';
import { call } from 'redux-saga/effects';
export function* importElement(payload) {
......@@ -240,3 +241,27 @@ export function* getPreviewRangeByDirId(payload) {
export function* resourceAddOrUpdateDirectory(payload) {
return yield call(service.resourceAddOrUpdateDirectory, payload)
}
export function* getSystems() {
return yield call(metadataService.getSystems)
}
export function* getDatasources(payload) {
return yield call(metadataService.getDatasources, payload)
}
export function* getDatabases(payload) {
return yield call(metadataService.getDatabases, payload)
}
export function* getSchemas(payload) {
return yield call(metadataService.getSchemas, payload)
}
export function* resourceTestSyncStrategy(payload) {
return yield call(service.resourceTestSyncStrategy, payload)
}
export function* getDirectoryWithSyncStrategy(payload) {
return yield call(service.getDirectoryWithSyncStrategy, payload)
}
\ No newline at end of file
......@@ -247,3 +247,11 @@ export function getPreviewRangeByDirId(payload) {
export function resourceAddOrUpdateDirectory(payload) {
return PostJSON("/dataassetmanager/resourceApi/addOrUpdateDirectory", payload);
}
export function resourceTestSyncStrategy(payload) {
return PostJSON("/dataassetmanager/resourceApi/testSyncStrategy", payload)
}
export function getDirectoryWithSyncStrategy(payload) {
return GetJSON("/dataassetmanager/resourceApi/getDirectoryWithSyncStrategy", payload)
}
\ No newline at end of file
import { GetJSON } from "../util/axios"
import { GetJSON, PostJSON } from "../util/axios"
export function queryDatabase(payload) {
return GetJSON("/metadatarepo/rest/tag/getDatabaseBySystemCode", payload);
......@@ -19,3 +19,19 @@ export function queryAllFields(payload) {
export function getSystemAllGraph(payload) {
return GetJSON("/metadatarelation/getSystemAllGraph", payload);
}
export function getSystems() {
return PostJSON('/metadatarepo/rest/system/listAllSystem')
}
export function getDatasources(payload) {
return GetJSON('/metadatarepo/rest/user/getUserSystem', payload)
}
export function getDatabases(payload) {
return PostJSON('/metadatarepo/rest/metadata/getChildBySysAndPath', payload)
}
export function getSchemas(payload) {
return GetJSON('/metadatarepo/rest/query/getChildBySys', payload)
}
\ No newline at end of file
......@@ -495,3 +495,11 @@ export function getScrollbarWidth() {
return scrollbarWidth;
}
export function getValidString(strs) {
for (const str of strs) {
if (typeof str === 'string' && str.length > 0) {
return str
}
}
}
\ No newline at end of file
import produce from "immer"
export function formatTreeData(datasources, catalogMap) {
const treeData = produce(datasources, (draft) => {
draft?.forEach((datasource) => {
datasource.title = datasource.catalogName
datasource.value = `catalog-${datasource.catalogId}`
datasource.id = datasource.catalogId
datasource.disabled = true
datasource.children = produce(datasource.scopes, (draftScopes) => {
draftScopes?.forEach((scope) => {
scope.title = scope.scopeName
scope.value = scope.scopeId
scope.id = scope.scopeId
scope.catalogId = datasource.value
})
})
})
})
const getMap = (children) => {
return children?.forEach((child) => {
getMap(child.children)
catalogMap[child.value] = child
})
}
getMap(treeData)
return treeData
}
export const envReducer = (prevState, action) => {
if (action.type === 'selectEnv') {
const env = action.payload
return { ...prevState, env, domain: undefined, datasource: undefined, database: undefined, schema: undefined}
} if (action.type === 'selectDatasource') {
const { domain, datasource } = action.payload
return { ...prevState, domain, datasource, database: undefined, schema: undefined }
} else if (action.type === 'selectDatabase') {
const database = action.payload
return { ...prevState, database, schema: undefined }
} else if (action.type === 'selectSchema') {
const schema = action.payload
return { ...prevState, schema }
}
return prevState
}
\ No newline at end of file
import React from 'react'
import { Modal, Button, Spin, Form, Input, Radio, Select } from 'antd'
import { Modal, Button, Spin, Form, Input, Radio, Select, Space, TreeSelect, Row, Col, Checkbox } from 'antd'
import { QuestionCircleOutlined } from '@ant-design/icons'
import { dispatch } from '../../../model'
import { envReducer, formatTreeData } from './update-node-help'
import { getValidString, showMessage } from '../../../util'
const resourceTypes = [
{ key: 'innerSource', name: '内部资源' },
......@@ -16,6 +19,7 @@ const FC = (props) => {
const [node, setNode] = React.useState()
const basicRef = React.useRef()
const syncBasicRef = React.useRef()
React.useEffect(() => {
if (visible && id) {
......@@ -25,7 +29,7 @@ const FC = (props) => {
const getDetail = () => {
dispatch({
type: 'assetmanage.getDirectoryById',
type: 'assetmanage.getDirectoryWithSyncStrategy',
payload: {
dirId: id
},
......@@ -45,22 +49,25 @@ const FC = (props) => {
const save = async() => {
try {
const rows = await basicRef.current?.validate()
const syncRows = await syncBasicRef.current?.validate()
setWaiting(true)
let parentPath = ''
if (action === 'add') {
if (basicRef.current?.getType === 'child') {
parentPath = node.path
parentPath = node?.directory?.path
}
} else {
parentPath = node?.path.substring(0, node?.path.lastIndexOf("/"))
parentPath = node?.directory?.path.substring(0, node?.directory?.path.lastIndexOf("/"))
}
dispatch({
type: 'assetmanage.resourceAddOrUpdateDirectory',
payload: {
data: {
directory: (action === 'add')?rows:{...node, ...rows},
directory: (action === 'add')?rows:{...node?.directory, ...rows},
resourceDirectorySyncStrategy: syncRows
},
params: {
parentPath
......@@ -101,8 +108,9 @@ const FC = (props) => {
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={waiting} >
<Basic ref={basicRef} node={node} action={action} />
<Spin spinning={loading||waiting} >
<Basic ref={basicRef} node={node?.directory} action={action} />
<SyncBasic ref={syncBasicRef} node={node?.resourceDirectorySyncStrategy} action={action} />
</Spin>
</Modal>
)
......@@ -204,14 +212,490 @@ export const Basic = React.forwardRef(function ({ node, action }, ref) {
label="描述"
name="desc"
>
<Input.TextArea placeholder="请输入描述" autoSize={{ minRows: 4, maxRows: 4 }} />
<Input placeholder="请输入描述" />
</Form.Item>
<Form.Item
label="备注"
name="remarks"
>
<Input.TextArea placeholder="请输入备注" autoSize={{ minRows: 4, maxRows: 4 }} />
<Input placeholder="请输入备注" />
</Form.Item>
</Form>
)
})
export const SyncBasic = React.forwardRef(function ({ node, action }, ref) {
const [testData, setTestData] = React.useState()
const [testResultData, setTestResultData] = React.useState()
const [form] = Form.useForm()
React.useImperativeHandle(ref, () => ({
validate: async () => {
const rows = await form.validateFields()
if (!rows.blacklist) {
rows.blacklist = ''
}
if (!rows.whitelist) {
rows.whitelist = ''
}
return rows
},
}), [form])
React.useEffect(() => {
if (node) {
if (action !== 'add') {
form.setFieldsValue(node)
}
}
}, [action, node])
const onTestClick = () => {
if (!testData) {
showMessage('warn', '请先输入测试数据')
return
}
const rows = form?.getFieldsValue()
if (!rows.blacklist) {
rows.blacklist = ''
}
if (!rows.whitelist) {
rows.whitelist = ''
}
dispatch({
type: 'assetmanage.resourceTestSyncStrategy',
payload: {
params: {
testTableStr: testData,
},
data: rows,
},
callback: data => {
},
error: () => {
}
})
}
const onValuesChange = (changedValues, allValues) => {
}
return (
<Form
form={form}
labelCol={{ span: 5 }}
wrapperCol={{ span: 17 }}
autoComplete="off"
onValuesChange={onValuesChange}
>
<Form.Item
label="同步Schema"
name="schemas"
>
<SyncSchemaItem />
</Form.Item>
<Form.Item
label="自动同步"
name='autoSyncStatus'
tooltip={{ title: '选择自动同步后,新增元数据时,自动更新到该目录下', icon: <QuestionCircleOutlined /> }}
>
<AutoSyncItem />
</Form.Item>
<Form.Item
label='过滤条件'
name='blacklist'
tooltip={{ title: '设置过滤条件后,同步Schema时将不会同步符合过滤条件的表', icon: <QuestionCircleOutlined /> }}
>
<Input placeholder='输入正则表达式' />
</Form.Item>
<Form.Item
label='白名单'
name='whitelist'
tooltip={{ title: '设置的白名单,不受过滤条件影响', icon: <QuestionCircleOutlined /> }}
>
<Input placeholder='输入表名完整路径,多个表以,隔开' />
</Form.Item>
<Form.Item
label='测试数据'
>
<Row gutter={10}>
<Col span={20}>
<Input placeholder='请输入测试数据' onChange={(e) => {
setTestData(e.target.value)
}}/>
</Col>
<Col span={4}>
<Button onClick={onTestClick}>测试</Button>
</Col>
</Row>
</Form.Item>
<Form.Item
label='测试结果'
>
<Input value={testResultData} disabled={true} />
</Form.Item>
</Form>
)
})
const SyncSchemaItem = ({ value, onChange }) => {
const [selectSchemasParams, setSelectSchemasParams] = React.useState({
visible: false
})
const triggerChange = (newValue) => {
onChange?.(newValue)
}
return (
<div>
<Space>
<Button onClick={() => {
setSelectSchemasParams({
visible: true
})
}}>
选择Schema
</Button>
</Space>
<Row>
{
value?.map((item, index) => {
return (
<Col key={index} span={24} style={{ lineHeight: '32px' }}>
<Row>
<Col span={20}>{item.namePath}</Col>
<Col span={4}>
<Button
size='small'
type='danger'
onClick={() => {
const newValue = [...value??[]]
newValue.splice(index, 1)
triggerChange?.(newValue)
}}
>删除</Button>
</Col>
</Row>
</Col>
)
})
}
</Row>
<SelectSchemas
{...selectSchemasParams}
onCancel={(val) => {
setSelectSchemasParams({
visible: false
})
if ((val??[]).length>0) {
const newValue = [...value??[], ...val??[]]
triggerChange?.(newValue)
}
}}
/>
</div>
)
}
const AutoSyncItem = ({ value, onChange }) => {
return (
<Checkbox
checked={value===1}
onChange={(e) => {
onChange(e.target.checked?1:0)
}}
/>
)
}
const SelectSchemas = ({ visible, onCancel }) => {
const [schemas, setSchemas] = React.useState()
React.useEffect(() => {
if (visible) {
setSchemas()
}
}, [visible])
const close = (value) => {
onCancel?.(value)
}
const save = () => {
close(schemas)
}
const footer = React.useMemo(() => {
return [
<Button key={'cancel'}
onClick={() => close()}
>取消</Button>,
<Button key={'save'} type='primary'
onClick={() => save()}
>确定</Button>
]
}, [close, save])
return (
<Modal
className='add-to-asset'
visible={visible}
footer={footer}
width='654px'
title='选择Schema'
bodyStyle={{ padding: '15px' }}
centered destroyOnClose
onCancel={() => { close() }}
>
<Config onState={(state) => {
const newSchemas = (state.schema??[]).map(item => {
return {
idPath: item.idPath,
catalog: state.env?.domainId?.toString(),
namePath: `${state.env?.domainName}/${state.datasource?.scopeName}/${state.database?.name}/${item.name}`
}
})
setSchemas(newSchemas)
}} />
</Modal>
)
}
function Config({ onState }) {
const catalogMap = React.useRef({})
const [expandedKeys, setExpandedKeys] = React.useState()
const [systems, setSystems] = React.useState(undefined)
const [datasources, setDatasources] = React.useState(undefined)
const [dbs, setDBs] = React.useState(undefined)
const [schemas, setSchemas] = React.useState(undefined)
const [loadingSystems, setLoadingSystems] = React.useState(false)
const [loadingDatasources, setLoadingDatasources] = React.useState(false)
const [loadingDBs, setLoadingDBs] = React.useState(false)
const [loadingSchemas, setLoadingSchemas] = React.useState(false)
const [state, dispatchState] = React.useReducer(envReducer, {})
const { env, datasource, database, schema } = state
const selectEnv = React.useCallback((env) => {
dispatchState({ type: 'selectEnv', payload: env })
}, [])
const selectDomain = React.useCallback((domain, datasource) => {
dispatchState({ type: 'selectDatasource', payload: { domain, datasource } })
}, [])
const selectDb = React.useCallback((db) => {
dispatchState({ type: 'selectDatabase', payload: db })
}, [])
const selectSchema = React.useCallback((schema) => {
dispatchState({ type: 'selectSchema', payload: schema })
}, [])
const treeData = React.useMemo(() => {
const children = formatTreeData(datasources??[], catalogMap.current)
const expandedKeys = children?.map((c) => c.value)
selectDomain(undefined, undefined)
setExpandedKeys(expandedKeys)
return children
}, [datasources])
const disabled = loadingSystems || loadingDatasources || loadingDBs || loadingSchemas
React.useEffect(() => {
getSystems()
}, [])
React.useEffect(() => {
if (env) {
getDatasources()
} else {
setDatasources(undefined)
}
}, [env])
React.useEffect(() => {
if (datasource) {
getDBs()
} else {
setDBs(undefined)
}
}, [datasource])
React.useEffect(() => {
if (database) {
getSchemas()
} else {
setSchemas(undefined)
}
}, [database])
React.useEffect(() => {
onState?.(state)
}, [state])
const getSystems = () => {
setLoadingSystems(true)
dispatch({
type: 'assetmanage.getSystems',
callback: data => {
setLoadingSystems(false)
setSystems(data)
},
error: () => {
setLoadingSystems(false)
}
})
}
const getDatasources = () => {
setLoadingDatasources(true)
dispatch({
type: 'assetmanage.getDatasources',
payload: {
catalog: env?.domainId
},
callback: data => {
setLoadingDatasources(false)
setDatasources(data)
},
error: () => {
setLoadingDatasources(false)
}
})
}
const getDBs = () => {
setLoadingDBs(true)
dispatch({
type: 'assetmanage.getDatabases',
payload: {
params: {
parentClass: 'Catalog',
parentNamePath: env?.domainId,
system: datasource?.id
},
data: ['Catalog,Database']
},
callback: data => {
setLoadingDBs(false)
setDBs(data)
},
error: () => {
setLoadingDBs(false)
}
})
}
const getSchemas = () => {
setLoadingSchemas(true)
dispatch({
type: 'assetmanage.getSchemas',
payload: {
parentId: database?._id,
sysId: datasource?.id
},
callback: data => {
setLoadingSchemas(false)
setSchemas(data)
},
error: () => {
setLoadingSchemas(false)
}
})
}
return (
<Spin spinning={loadingSystems||loadingDatasources||loadingDBs||loadingSchemas}>
<Space>
<Select
placeholder="请选择环境"
value={systems?.length>0?env?.domainId:undefined}
loading={loadingSystems}
allowClear={true}
disabled={disabled}
onChange={(val) => {
if (val) {
const index = (systems||[]).findIndex(item => item.domainId === val)
if (index !== -1) {
selectEnv(systems[index])
}
} else {
selectEnv(undefined)
}
}}
style={{ width: 150 }}
>
{systems?.map((item, i) => <Select.Option key={i} value={item.domainId} > {item.domainName} </Select.Option>)}
</Select>
<TreeSelect
dropdownMatchSelectWidth={210}
listHeight={450}
treeData={treeData}
placeholder="请选择系统"
treeExpandedKeys={expandedKeys}
value={datasource?datasource.id:undefined}
onTreeExpand={(keys) => {
setExpandedKeys(keys)
}}
onChange={(val, label, extra) => {
const _datasource = catalogMap.current?.[val]
let _domain = undefined
if (_datasource?.catalogId) {
_domain = catalogMap.current?.[_datasource.catalogId]
}
selectDomain(_domain, _datasource)
}}
disabled={disabled}
allowClear={true}
style={{ width: 150 }}
/>
<Select
placeholder="请选择数据库"
value={dbs?.length>0?database?._id:undefined}
loading={loadingDBs}
allowClear={true}
disabled={disabled}
onChange={(val, option: any) => {
if (val) {
const index = (dbs||[]).findIndex(item => item._id === val)
if (index !== -1) {
selectDb(dbs[index])
}
} else {
selectDb(undefined)
}
}}
style={{ width: 150 }}
>
{dbs?.map((item, i) => <Select.Option key={i} value={item._id} > {getValidString([undefined, item.cnName, item.name])} </Select.Option>)}
</Select>
<Select
placeholder="请选择Schema"
value={schemas?.length>0?(schema??[]).map(item => item._id):undefined}
loading={loadingSchemas}
allowClear={true}
disabled={disabled}
mode="multiple"
onChange={(val) => {
if ((val??[]).length > 0) {
selectSchema((schemas??[]).filter(item => val.indexOf(item._id)!==-1))
} else {
selectSchema(undefined)
}
}}
style={{ width: 150 }}
>
{schemas?.map((item, i) => <Select.Option key={i} value={item._id} > {item.name} </Select.Option>)}
</Select>
</Space>
</Spin>
)
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment