[improvement][project] global refactor , code format , support llm , support fuzzy detect ,support query filter and so on.

This commit is contained in:
lexluo
2023-07-08 15:00:03 +08:00
parent 5ffd617431
commit 404163f391
329 changed files with 21050 additions and 5036 deletions

View File

@@ -1,5 +1,5 @@
import basicConfig from './rollup.config.mjs'
import { terser } from '@rollup/plugin-terser'
import { terser } from "@rollup/plugin-terser"
import replace from '@rollup/plugin-replace'
const config = {

View File

@@ -42,7 +42,7 @@
&-typing-bubble {
width: fit-content;
padding: 16px !important;
padding: 8px 16px !important;
}
&-text-bubble {

View File

@@ -6,7 +6,7 @@ if [ $? -ne 0 ]; then
exit 1
fi
npm run build
npm run build:inner
if [ $? -ne 0 ]; then
echo "build failed"
exit 1

View File

@@ -1,2 +1,4 @@
const ENV_CONFIG = {};
const ENV_CONFIG = {
tmeAvatarUrl: 'http://tpp.tmeoa.com/photo/48/',
};
export default ENV_CONFIG;

View File

@@ -5,12 +5,18 @@ import { history } from 'umi';
import type { RunTimeLayoutConfig } from 'umi';
import RightContent from '@/components/RightContent';
import S2Icon, { ICON } from '@/components/S2Icon';
import qs from 'qs';
import { queryCurrentUser } from './services/user';
import { queryToken } from './services/login';
import defaultSettings from '../config/defaultSettings';
import settings from '../config/themeSettings';
import { deleteUrlQuery } from './utils/utils';
import { AUTH_TOKEN_KEY, FROM_URL_KEY } from '@/common/constants';
export { request } from './services/request';
import { ROUTE_AUTH_CODES } from '../config/routes';
const TOKEN_KEY = AUTH_TOKEN_KEY;
const replaceRoute = '/';
const getRuningEnv = async () => {
@@ -34,6 +40,25 @@ export const initialStateConfig = {
),
};
const getToken = async () => {
let { search } = window.location;
if (search.length > 0) {
search = search.slice(1);
}
const data = qs.parse(search);
if (data.code) {
try {
const fromUrl = localStorage.getItem(FROM_URL_KEY);
const res = await queryToken(data.code as string);
localStorage.setItem(TOKEN_KEY, res.payload);
const newUrl = deleteUrlQuery(window.location.href, 'code');
window.location.href = fromUrl || newUrl;
} catch (err) {
console.log(err);
}
}
};
const getAuthCodes = () => {
const { RUN_TYPE, APP_TARGET } = process.env;
if (RUN_TYPE === 'local') {
@@ -64,6 +89,12 @@ export async function getInitialState(): Promise<{
} catch (error) {}
return undefined;
};
const { query } = history.location as any;
const currentToken = query[TOKEN_KEY] || localStorage.getItem(TOKEN_KEY);
if (window.location.host.includes('tmeoa') && !currentToken) {
await getToken();
}
const currentUser = await fetchUserInfo();

View File

@@ -1,13 +1,11 @@
import { Tabs } from 'antd';
import { Tabs, Popover } from 'antd';
import React, { useEffect, useState } from 'react';
import { connect, Helmet } from 'umi';
import ProjectListTree from './components/ProjectList';
import EntitySection from './components/Entity/EntitySection';
import styles from './components/style.less';
import type { StateType } from './model';
import { RightOutlined, LeftOutlined } from '@ant-design/icons';
import SplitPane from 'react-split-pane';
import Pane from 'react-split-pane/lib/Pane';
import { DownOutlined } from '@ant-design/icons';
import EntitySection from './components/Entity/EntitySection';
import type { Dispatch } from 'umi';
const { TabPane } = Tabs;
@@ -17,21 +15,14 @@ type Props = {
dispatch: Dispatch;
};
const DEFAULT_LEFT_SIZE = '300px';
const ChatSetting: React.FC<Props> = ({ domainManger, dispatch }) => {
window.RUNNING_ENV = 'chat';
const [collapsed, setCollapsed] = useState(false);
const [leftSize, setLeftSize] = useState('');
const { selectDomainId, selectDomainName } = domainManger;
useEffect(() => {
const semanticLeftCollapsed = localStorage.getItem('semanticLeftCollapsed');
const semanticLeftSize =
semanticLeftCollapsed === 'true' ? '0px' : localStorage.getItem('semanticLeftSize');
setCollapsed(semanticLeftCollapsed === 'true');
setLeftSize(semanticLeftSize || DEFAULT_LEFT_SIZE);
}, []);
const [open, setOpen] = useState(false);
const handleOpenChange = (newOpen: boolean) => {
setOpen(newOpen);
};
useEffect(() => {
if (selectDomainId) {
dispatch({
@@ -49,64 +40,54 @@ const ChatSetting: React.FC<Props> = ({ domainManger, dispatch }) => {
}
}, [selectDomainId]);
const onCollapse = () => {
const collapsedValue = !collapsed;
setCollapsed(collapsedValue);
localStorage.setItem('semanticLeftCollapsed', String(collapsedValue));
const semanticLeftSize = collapsedValue ? '0px' : localStorage.getItem('semanticLeftSize');
const sizeValue = parseInt(semanticLeftSize || '0');
if (!collapsedValue && sizeValue <= 10) {
setLeftSize(DEFAULT_LEFT_SIZE);
localStorage.setItem('semanticLeftSize', DEFAULT_LEFT_SIZE);
} else {
setLeftSize(semanticLeftSize || DEFAULT_LEFT_SIZE);
}
};
useEffect(() => {
const width = document.getElementById('tab');
const switchWarpper: any = document.getElementById('switch');
if (width && switchWarpper) {
switchWarpper.style.width = width.offsetWidth * 0.77 + 'px';
}
});
return (
<div className={styles.projectBody}>
<Helmet title={'问答设置-超音数'} />
<SplitPane
split="vertical"
onChange={(size) => {
localStorage.setItem('semanticLeftSize', size[0]);
setLeftSize(size[0]);
}}
>
<Pane initialSize={leftSize || DEFAULT_LEFT_SIZE}>
<div className={styles.menu}>
<ProjectListTree createDomainBtnVisible={false} queryService="chat" />
</div>
</Pane>
<div className={styles.projectManger}>
<div className={styles.collapseLeftBtn} onClick={onCollapse}>
{collapsed ? <RightOutlined /> : <LeftOutlined />}
</div>
<h2 className={styles.title}>
{selectDomainName ? `选择的主题域:${selectDomainName}` : '主题域信息'}
</h2>
{selectDomainId ? (
<>
<Tabs className={styles.tab} defaultActiveKey="chatSetting" destroyInactiveTabPane>
<TabPane className={styles.tabPane} tab="问答设置" key="chatSetting">
<EntitySection />
</TabPane>
</Tabs>
</>
) : (
<h2 className={styles.mainTip}></h2>
)}
</div>
</SplitPane>
{/* 页面改版取消侧边栏转换为popover形式后因为popover不触发则组件不加载需要保留原本页面初始化需要ProjectListTree向model中写入首个主题域数据逻辑在此引入但并不显示 */}
<div style={{ display: 'none' }}>
<ProjectListTree />
</div>
<div className={styles.projectManger}>
<h2 className={styles.title}>
<Popover
zIndex={1000}
overlayInnerStyle={{
overflow: 'scroll',
maxHeight: '800px',
}}
content={
<ProjectListTree
onTreeSelected={() => {
setOpen(false);
}}
/>
}
trigger="click"
open={open}
onOpenChange={handleOpenChange}
>
<div className={styles.domainSelector}>
<span className={styles.domainTitle}>
{selectDomainName ? `选择的主题域:${selectDomainName}` : '主题域信息'}
</span>
<span className={styles.downIcon}>
<DownOutlined />
</span>
</div>
</Popover>
</h2>
{selectDomainId ? (
<>
<Tabs className={styles.tab} defaultActiveKey="chatSetting" destroyInactiveTabPane>
<TabPane className={styles.tabPane} tab="问答设置" key="chatSetting">
<EntitySection />
</TabPane>
</Tabs>
</>
) : (
<h2 className={styles.mainTip}></h2>
)}
</div>
</div>
);
};

View File

@@ -1,19 +1,102 @@
import React from 'react';
import { Form, Input, Spin } from 'antd';
import React, { useEffect, useState } from 'react';
import { Form, Input, Spin, Select, message } from 'antd';
import type { FormInstance } from 'antd/lib/form';
import { getDbNames, getTables } from '../../service';
const FormItem = Form.Item;
const { TextArea } = Input;
type Props = {
isEdit?: boolean;
dataBaseConfig: any;
form: FormInstance<any>;
tableLoading?: boolean;
mode?: 'normal' | 'fast';
};
const DataSourceBasicForm: React.FC<Props> = ({ isEdit, tableLoading = false }) => {
const DataSourceBasicForm: React.FC<Props> = ({
isEdit,
dataBaseConfig,
tableLoading = false,
mode = 'normal',
}) => {
const [dbNameList, setDbNameList] = useState<any[]>([]);
const [tableNameList, setTableNameList] = useState<any[]>([]);
const [currentDbName, setCurrentDbName] = useState<string>('');
const [currentTableName, setCurrentTableName] = useState<string>('');
const queryDbNameList = async (databaseId: number) => {
const { code, data, msg } = await getDbNames(databaseId);
if (code === 200) {
const list = data?.resultList || [];
setDbNameList(list);
} else {
message.error(msg);
}
};
const queryTableNameList = async (databaseName: string) => {
const { code, data, msg } = await getTables(dataBaseConfig.id, databaseName);
if (code === 200) {
const list = data?.resultList || [];
setTableNameList(list);
} else {
message.error(msg);
}
};
useEffect(() => {
if (dataBaseConfig?.id) {
queryDbNameList(dataBaseConfig.id);
}
}, [dataBaseConfig]);
return (
<Spin spinning={tableLoading}>
{mode === 'fast' && (
<>
<FormItem
name="dbName"
label="数据库名"
rules={[{ required: true, message: '请选择数据库/表' }]}
>
<Select
showSearch
placeholder="请选择数据库/表"
disabled={isEdit}
onChange={(dbName: string) => {
queryTableNameList(dbName);
setCurrentDbName(dbName);
}}
>
{dbNameList.map((item) => (
<Select.Option key={item.name} value={item.name}>
{item.name}
</Select.Option>
))}
</Select>
</FormItem>
<FormItem
name="tableName"
label="数据表名"
rules={[{ required: true, message: '请选择数据库/表' }]}
>
<Select
placeholder="请选择数据库/表"
disabled={isEdit}
showSearch
onChange={(tableName: string) => {
// queryTableNameList(tableName);
setCurrentTableName(tableName);
}}
>
{tableNameList.map((item) => (
<Select.Option key={item.name} value={item.name}>
{item.name}
</Select.Option>
))}
</Select>
</FormItem>
</>
)}
<FormItem
name="name"
label="数据源中文名"

View File

@@ -4,18 +4,25 @@ import BasicInfoForm from './DataSourceBasicForm';
import FieldForm from './DataSourceFieldForm';
import { formLayout } from '@/components/FormHelper/utils';
import { EnumDataSourceType } from '../constants';
import type { DataInstanceItem, FieldItem, SaveDataSetForm } from '../data';
import type { DataInstanceItem } from '../data';
import styles from '../style.less';
import { createDatasource, updateDatasource } from '../../service';
import { createDatasource, updateDatasource, getColumns } from '../../service';
import type { Dispatch } from 'umi';
import type { StateType } from '../../model';
import { connect } from 'umi';
export type CreateFormProps = {
domainManger: StateType;
dispatch: Dispatch;
createModalVisible: boolean;
sql: string;
sql?: string;
domainId: number;
dataSourceItem: DataInstanceItem | any;
onCancel?: () => void;
onSubmit?: (dataSourceInfo: any) => void;
scriptColumns: any[];
scriptColumns?: any[] | undefined;
basicInfoFormMode?: 'normal' | 'fast';
onDataBaseTableChange?: (tableName: string) => void;
};
const { Step } = Steps;
@@ -26,30 +33,40 @@ const initFormVal = {
};
const DataSourceCreateForm: React.FC<CreateFormProps> = ({
domainManger,
onCancel,
createModalVisible,
domainId,
scriptColumns,
sql,
sql = '',
onSubmit,
dataSourceItem,
basicInfoFormMode,
}) => {
const isEdit = !!dataSourceItem?.id;
const [fields, setFields] = useState<FieldItem[]>([]);
const [fields, setFields] = useState<any[]>([]);
const [currentStep, setCurrentStep] = useState(0);
const [saveLoading, setSaveLoading] = useState(false);
const formValRef = useRef(initFormVal as any);
const [form] = Form.useForm();
const updateFormVal = (val: SaveDataSetForm) => {
const { dataBaseConfig } = domainManger;
const updateFormVal = (val: any) => {
formValRef.current = val;
};
const [fieldColumns, setFieldColumns] = useState(scriptColumns || []);
useEffect(() => {
if (scriptColumns) {
setFieldColumns(scriptColumns);
}
}, [scriptColumns]);
const forward = () => setCurrentStep(currentStep + 1);
const backward = () => setCurrentStep(currentStep - 1);
const getFieldsClassify = (fieldsList: FieldItem[]) => {
const getFieldsClassify = (fieldsList: any[]) => {
const classify = fieldsList.reduce(
(fieldsClassify, item: FieldItem) => {
(fieldsClassify, item: any) => {
const {
type,
bizName,
@@ -126,11 +143,13 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
forward();
} else {
setSaveLoading(true);
const { dbName, tableName } = submitForm;
const queryParams = {
...submitForm,
sqlQuery: sql,
databaseId: dataSourceItem.databaseId,
queryType: 'sql_query',
databaseId: dataSourceItem?.databaseId || dataBaseConfig.id,
queryType: basicInfoFormMode === 'fast' ? 'table_query' : 'sql_query',
tableQuery: dbName && tableName ? `${dbName}.${tableName}` : '',
domainId,
};
const queryDatasource = isEdit ? updateDatasource : createDatasource;
@@ -149,8 +168,8 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
}
};
const initFields = (fieldsClassifyList: any[]) => {
const columnFields: any[] = scriptColumns.map((item: any) => {
const initFields = (fieldsClassifyList: any[], columns: any[]) => {
const columnFields: any[] = columns.map((item: any) => {
const { type, nameEn } = item;
const oldItem = fieldsClassifyList.find((oItem) => oItem.bizName === item.nameEn) || {};
return {
@@ -181,13 +200,32 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
});
};
const initData = () => {
const initData = async () => {
const { queryType, tableQuery } = dataSourceItem.datasourceDetail;
let tableQueryInitValue = {};
let columns = fieldColumns;
if (queryType === 'table_query') {
const tableQueryString = tableQuery || '';
const [dbName, tableName] = tableQueryString.split('.');
columns = await queryTableColumnList(dbName, tableName);
tableQueryInitValue = {
dbName,
tableName,
};
}
formatterInitData(columns, tableQueryInitValue);
};
const formatterInitData = (columns: any[], extendParams: Record<string, any> = {}) => {
const { id, name, bizName, description, datasourceDetail } = dataSourceItem as any;
const { dimensions, identifiers, measures } = datasourceDetail;
const initValue = {
id,
name,
bizName,
description,
...extendParams,
// ...tableQueryInitValue,
};
const editInitFormVal = {
...formValRef.current,
@@ -195,20 +233,19 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
};
updateFormVal(editInitFormVal);
form.setFieldsValue(initValue);
const { dimensions, identifiers, measures } = datasourceDetail;
const formatFields = [
...formatterDimensions(dimensions || []),
...(identifiers || []),
...formatterMeasures(measures || []),
];
initFields(formatFields);
initFields(formatFields, columns);
};
useEffect(() => {
if (isEdit) {
initData();
} else {
initFields([]);
initFields([], fieldColumns);
}
}, [dataSourceItem]);
@@ -227,11 +264,42 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
setFields(result);
};
const queryTableColumnList = async (dbName: string, tableName: string) => {
if (!dataBaseConfig?.id) {
return;
}
const { code, data, msg } = await getColumns(dataBaseConfig.id, dbName, tableName);
if (code === 200) {
const list = data?.resultList || [];
// setTableNameList(list);
const columns = list.map((item: any) => {
const { dataType, name } = item;
return {
nameEn: name,
type: dataType,
};
});
// setFields(columns);
initFields([], columns);
setFieldColumns(columns);
return columns;
} else {
message.error(msg);
}
};
const renderContent = () => {
if (currentStep === 1) {
return <FieldForm fields={fields} onFieldChange={handleFieldChange} />;
}
return <BasicInfoForm form={form} isEdit={isEdit} />;
return (
<BasicInfoForm
form={form}
isEdit={isEdit}
mode={basicInfoFormMode}
dataBaseConfig={dataBaseConfig}
/>
);
};
const renderFooter = () => {
@@ -280,6 +348,13 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
initialValues={{
...formValRef.current,
}}
onValuesChange={(value, values) => {
const { tableName } = value;
const { dbName } = values;
if (tableName) {
queryTableColumnList(dbName, tableName);
}
}}
className={styles.form}
>
{renderContent()}
@@ -288,4 +363,6 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
);
};
export default DataSourceCreateForm;
export default connect(({ domainManger }: { domainManger: StateType }) => ({
domainManger,
}))(DataSourceCreateForm);

View File

@@ -2,6 +2,7 @@ import React, { useState, useEffect, useRef } from 'react';
import { Button, Table, message, Tooltip, Space, Dropdown } from 'antd';
import SplitPane from 'react-split-pane';
import Pane from 'react-split-pane/lib/Pane';
import { connect } from 'umi';
import sqlFormatter from 'sql-formatter';
import {
FullscreenOutlined,
@@ -15,10 +16,12 @@ import {
import { isFunction } from 'lodash';
import FullScreen from '@/components/FullScreen';
import SqlEditor from '@/components/SqlEditor';
import type { TaskResultParams, TaskResultItem, DataInstanceItem, TaskResultColumn } from '../data';
import { excuteSql } from '../service';
import { getDatabaseByDomainId } from '../../service';
import type { TaskResultItem, DataInstanceItem, TaskResultColumn } from '../data';
import { excuteSql } from '@/pages/SemanticModel/service';
// import { getDatabaseByDomainId } from '../../service';
import DataSourceCreateForm from './DataSourceCreateForm';
import type { Dispatch } from 'umi';
import type { StateType } from '../../model';
import styles from '../style.less';
import 'ace-builds/src-min-noconflict/ext-searchbox';
@@ -27,7 +30,8 @@ import 'ace-builds/src-min-noconflict/theme-monokai';
import 'ace-builds/src-min-noconflict/mode-sql';
type IProps = {
oprType: 'add' | 'edit';
domainManger: StateType;
dispatch: Dispatch;
dataSourceItem: DataInstanceItem;
domainId: number;
onUpdateSql?: (sql: string) => void;
@@ -52,6 +56,7 @@ type JdbcSourceItems = {
};
const SqlDetail: React.FC<IProps> = ({
domainManger,
dataSourceItem,
onSubmitSuccess,
domainId,
@@ -59,6 +64,7 @@ const SqlDetail: React.FC<IProps> = ({
onUpdateSql,
onJdbcSourceChange,
}) => {
const { dataBaseConfig } = domainManger;
const [resultTable, setResultTable] = useState<ResultTableItem[]>([]);
const [resultTableLoading, setResultTableLoading] = useState(false);
const [resultCols, setResultCols] = useState<ResultColItem[]>([]);
@@ -111,20 +117,30 @@ const SqlDetail: React.FC<IProps> = ({
// return 'ClickHouse';
// });
const queryDatabaseConfig = async () => {
const { code, data } = await getDatabaseByDomainId(domainId);
if (code === 200) {
setJdbcSourceItems([
{
label: data?.name,
key: data?.id,
},
]);
onJdbcSourceChange?.(data?.id && Number(data?.id));
return;
}
message.error('数据库配置获取错误');
};
useEffect(() => {
setJdbcSourceItems([
{
label: dataBaseConfig?.name,
key: dataBaseConfig?.id,
},
]);
onJdbcSourceChange?.(dataBaseConfig?.id && Number(dataBaseConfig?.id));
}, [dataBaseConfig]);
// const queryDatabaseConfig = async () => {
// const { code, data } = await getDatabaseByDomainId(domainId);
// if (code === 200) {
// setJdbcSourceItems([
// {
// label: data?.name,
// key: data?.id,
// },
// ]);
// onJdbcSourceChange?.(data?.id && Number(data?.id));
// return;
// }
// message.error('数据库配置获取错误');
// };
function creatCalcItem(key: string, data: string) {
const line = document.createElement('div'); // 需要每条数据一行,这样避免数据换行的时候获得的宽度不准确
@@ -185,7 +201,7 @@ const SqlDetail: React.FC<IProps> = ({
}
};
const fetchTaskResult = (params: TaskResultParams) => {
const fetchTaskResult = (params) => {
setResultTable(
params.resultList.map((item, index) => {
return {
@@ -367,7 +383,7 @@ const SqlDetail: React.FC<IProps> = ({
}, [resultTable, isSqlResFullScreen]);
useEffect(() => {
queryDatabaseConfig();
// queryDatabaseConfig();
const windowHeight = window.innerHeight;
let size: ScreenSize = 'small';
if (windowHeight > 1100) {
@@ -527,4 +543,6 @@ const SqlDetail: React.FC<IProps> = ({
);
};
export default SqlDetail;
export default connect(({ domainManger }: { domainManger: StateType }) => ({
domainManger,
}))(SqlDetail);

View File

@@ -1,7 +1,6 @@
import React, { useState, useRef, useEffect } from 'react';
import { Tabs } from 'antd';
import SqlDetail from './SqlDetail';
import type { SqlItem } from '../data';
import styles from '../style.less';
@@ -11,7 +10,6 @@ type Panes = {
type: 'add' | 'edit';
scriptId?: number;
sql?: string;
sqlInfo?: SqlItem;
isSave?: boolean; // 暂存提示保存
};

View File

@@ -0,0 +1,16 @@
// 数据类型
export type DataInstanceItem = {
sourceInstanceId: number; // 数据实例id
sourceInstanceName: string; // 数据实例名
defaultSourceId: number; // 查询表需要的默认datasource id
bindSourceId: number;
};
// 任务查询结果列
export type TaskResultColumn = {
name: string;
type: string;
};
// 任务查询结果
export type TaskResultItem = Record<string, string | number>;

View File

@@ -1,12 +0,0 @@
import request from 'umi-request';
type ExcuteSqlParams = {
sql: string;
domainId: number;
};
// 执行脚本
export async function excuteSql(params: ExcuteSqlParams) {
const data = { ...params };
return request.post(`${process.env.API_BASE_URL}database/executeSql`, { data });
}

View File

@@ -1,4 +1,4 @@
import { Tabs } from 'antd';
import { Tabs, Popover } from 'antd';
import React, { useEffect, useState } from 'react';
import { connect, Helmet } from 'umi';
import ProjectListTree from './components/ProjectList';
@@ -9,11 +9,8 @@ import PermissionSection from './components/Permission/PermissionSection';
import DatabaseSection from './components/Database/DatabaseSection';
import styles from './components/style.less';
import type { StateType } from './model';
import { RightOutlined, LeftOutlined } from '@ant-design/icons';
import { DownOutlined } from '@ant-design/icons';
import SemanticFlow from './SemanticFlows';
// import SemanticGraph from './SemanticGraph';
import SplitPane from 'react-split-pane';
import Pane from 'react-split-pane/lib/Pane';
import type { Dispatch } from 'umi';
const { TabPane } = Tabs;
@@ -23,20 +20,15 @@ type Props = {
dispatch: Dispatch;
};
const DEFAULT_LEFT_SIZE = '300px';
const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
window.RUNNING_ENV = 'semantic';
const [collapsed, setCollapsed] = useState(false);
const [leftSize, setLeftSize] = useState('');
const { selectDomainId, selectDomainName } = domainManger;
useEffect(() => {
const semanticLeftCollapsed = localStorage.getItem('semanticLeftCollapsed');
const semanticLeftSize =
semanticLeftCollapsed === 'true' ? '0px' : localStorage.getItem('semanticLeftSize');
setCollapsed(semanticLeftCollapsed === 'true');
setLeftSize(semanticLeftSize || DEFAULT_LEFT_SIZE);
}, []);
const [open, setOpen] = useState(false);
const handleOpenChange = (newOpen: boolean) => {
setOpen(newOpen);
};
useEffect(() => {
if (selectDomainId) {
@@ -52,23 +44,15 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
domainId: selectDomainId,
},
});
dispatch({
type: 'domainManger/queryDatabaseByDomainId',
payload: {
domainId: selectDomainId,
},
});
}
}, [selectDomainId]);
const onCollapse = () => {
const collapsedValue = !collapsed;
setCollapsed(collapsedValue);
localStorage.setItem('semanticLeftCollapsed', String(collapsedValue));
const semanticLeftSize = collapsedValue ? '0px' : localStorage.getItem('semanticLeftSize');
const sizeValue = parseInt(semanticLeftSize || '0');
if (!collapsedValue && sizeValue <= 10) {
setLeftSize(DEFAULT_LEFT_SIZE);
localStorage.setItem('semanticLeftSize', DEFAULT_LEFT_SIZE);
} else {
setLeftSize(semanticLeftSize || DEFAULT_LEFT_SIZE);
}
};
useEffect(() => {
const width = document.getElementById('tab');
const switchWarpper: any = document.getElementById('switch');
@@ -80,61 +64,73 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
return (
<div className={styles.projectBody}>
<Helmet title={'语义建模-超音数'} />
<SplitPane
split="vertical"
onChange={(size) => {
localStorage.setItem('semanticLeftSize', size[0]);
setLeftSize(size[0]);
}}
>
<Pane initialSize={leftSize || DEFAULT_LEFT_SIZE}>
<div className={styles.menu}>
<ProjectListTree />
</div>
</Pane>
<div className={styles.projectManger}>
<div className={styles.collapseLeftBtn} onClick={onCollapse}>
{collapsed ? <RightOutlined /> : <LeftOutlined />}
</div>
<h2 className={styles.title}>
{selectDomainName ? `选择的主题域:${selectDomainName}` : '主题域信息'}
</h2>
{selectDomainId ? (
<>
<Tabs className={styles.tab} defaultActiveKey="xflow" destroyInactiveTabPane>
{/* <TabPane className={styles.tabPane} tab="关系可视化" key="graph">
{/* 页面改版取消侧边栏转换为popover形式后因为popover不触发则组件不加载需要保留原本页面初始化需要ProjectListTree向model中写入首个主题域数据逻辑在此引入但并不显示 */}
<div style={{ display: 'none' }}>
<ProjectListTree />
</div>
<div className={styles.projectManger}>
<h2 className={styles.title}>
<Popover
zIndex={1000}
overlayInnerStyle={{
overflow: 'scroll',
maxHeight: '800px',
}}
content={
<ProjectListTree
onTreeSelected={() => {
setOpen(false);
}}
/>
}
trigger="click"
open={open}
onOpenChange={handleOpenChange}
>
<div className={styles.domainSelector}>
<span className={styles.domainTitle}>
{selectDomainName ? `选择的主题域:${selectDomainName}` : '主题域信息'}
</span>
<span className={styles.downIcon}>
<DownOutlined />
</span>
</div>
</Popover>
</h2>
{selectDomainId ? (
<>
<Tabs className={styles.tab} defaultActiveKey="xflow" destroyInactiveTabPane>
{/* <TabPane className={styles.tabPane} tab="关系可视化" key="graph">
<div style={{ width: '100%', height: 'calc(100vh - 200px)' }}>
<SemanticGraph domainId={selectDomainId} />
</div>
</TabPane> */}
<TabPane className={styles.tabPane} tab="可视化建模" key="xflow">
<div style={{ width: '100%', height: 'calc(100vh - 200px)' }}>
<SemanticFlow />
</div>
</TabPane>
<TabPane className={styles.tabPane} tab="数据库" key="dataBase">
<DatabaseSection />
</TabPane>
<TabPane className={styles.tabPane} tab="数据源" key="dataSource">
<ClassDataSourceTable />
</TabPane>
<TabPane className={styles.tabPane} tab="维度" key="dimenstion">
<ClassDimensionTable key={selectDomainId} />
</TabPane>
<TabPane className={styles.tabPane} tab="指标" key="metric">
<ClassMetricTable />
</TabPane>
<TabPane className={styles.tabPane} tab="权限管理" key="permissonSetting">
<PermissionSection />
</TabPane>
</Tabs>
</>
) : (
<h2 className={styles.mainTip}></h2>
)}
</div>
</SplitPane>
<TabPane className={styles.tabPane} tab="可视化建模" key="xflow">
<div style={{ width: '100%', height: 'calc(100vh - 200px)' }}>
<SemanticFlow />
</div>
</TabPane>
<TabPane className={styles.tabPane} tab="数据库" key="dataBase">
<DatabaseSection />
</TabPane>
<TabPane className={styles.tabPane} tab="数据源" key="dataSource">
<ClassDataSourceTable />
</TabPane>
<TabPane className={styles.tabPane} tab="维度" key="dimenstion">
<ClassDimensionTable key={selectDomainId} />
</TabPane>
<TabPane className={styles.tabPane} tab="指标" key="metric">
<ClassMetricTable />
</TabPane>
<TabPane className={styles.tabPane} tab="权限管理" key="permissonSetting">
<PermissionSection />
</TabPane>
</Tabs>
</>
) : (
<h2 className={styles.mainTip}></h2>
)}
</div>
</div>
);
};

View File

@@ -6,6 +6,8 @@ import { NS_DATA_SOURCE_RELATION_MODAL_OPEN_STATE } from '../ConfigModelService'
import { connect } from 'umi';
import { DATASOURCE_NODE_RENDER_ID } from '../constant';
import DataSourceRelationFormDrawer from './DataSourceRelationFormDrawer';
import DataSourceCreateForm from '../../Datasource/components/DataSourceCreateForm';
import ClassDataSourceTypeModal from '../../components/ClassDataSourceTypeModal';
import { GraphApi } from '../service';
import type { StateType } from '../../model';
import DataSource from '../../Datasource';
@@ -19,6 +21,7 @@ export type CreateFormProps = {
const XflowJsonSchemaFormDrawerForm: React.FC<CreateFormProps> = (props) => {
const { domainManger } = props;
const { selectDomainId } = domainManger;
const [visible, setVisible] = useState(false);
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
const [dataSourceItem, setDataSourceItem] = useState<any>();
@@ -26,7 +29,8 @@ const XflowJsonSchemaFormDrawerForm: React.FC<CreateFormProps> = (props) => {
sourceData: {},
targetData: {},
});
const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false);
const [dataSourceModalVisible, setDataSourceModalVisible] = useState(false);
const app = useXFlowApp();
// 借用JsonSchemaForm钩子函数对元素状态进行监听
const { state, commandService, modelService } = useJsonSchemaFormModel({
@@ -53,7 +57,15 @@ const XflowJsonSchemaFormDrawerForm: React.FC<CreateFormProps> = (props) => {
const { renderKey, payload } = targetData as any;
if (renderKey === DATASOURCE_NODE_RENDER_ID) {
setDataSourceItem(payload);
setCreateModalVisible(true);
if (!payload) {
setCreateDataSourceModalOpen(true);
} else {
if (payload?.datasourceDetail?.queryType === 'table_query') {
setDataSourceModalVisible(true);
} else {
setCreateModalVisible(true);
}
}
} else {
const { sourceNodeData, targetNodeData } = targetData as any;
setNodeDataSource({
@@ -85,6 +97,31 @@ const XflowJsonSchemaFormDrawerForm: React.FC<CreateFormProps> = (props) => {
}}
open={visible}
/>
{dataSourceModalVisible && (
<DataSourceCreateForm
basicInfoFormMode="fast"
domainId={Number(selectDomainId)}
dataSourceItem={dataSourceItem}
onCancel={() => {
setDataSourceModalVisible(false);
}}
onSubmit={(dataSourceInfo: any) => {
setDataSourceModalVisible(false);
const { targetCell, targetData } = state;
targetCell?.setData({
...targetData,
label: dataSourceInfo.name,
payload: dataSourceInfo,
id: `dataSource-${dataSourceInfo.id}`,
});
setDataSourceItem(undefined);
commandService.executeCommand(XFlowGraphCommands.SAVE_GRAPH_DATA.id, {
saveGraphDataService: (meta, graphData) => GraphApi.saveGraphData!(meta, graphData),
});
}}
createModalVisible={dataSourceModalVisible}
/>
)}
<Drawer
width={'100%'}
destroyOnClose
@@ -116,6 +153,19 @@ const XflowJsonSchemaFormDrawerForm: React.FC<CreateFormProps> = (props) => {
}}
/>
</Drawer>
{
<ClassDataSourceTypeModal
open={createDataSourceModalOpen}
onTypeChange={(type) => {
if (type === 'fast') {
setDataSourceModalVisible(true);
} else {
setCreateModalVisible(true);
}
setCreateDataSourceModalOpen(false);
}}
/>
}
</WorkspacePanel>
);
};

View File

@@ -17,7 +17,7 @@ const initTooltips = () => {
outDiv.style.width = 'fit-content';
outDiv.style.height = 'fit-content';
const model = e.item.getModel();
console.log(model, e.item, 'model');
const { name, bizName, createdBy, updatedAt, description } = model;
const list = [
{

View File

@@ -106,7 +106,7 @@ const DomainManger: React.FC<Props> = ({ domainManger, domainId }) => {
const { id, name } = datasource;
const dataSourceId = `dataSource-${id}`;
let childrenList = [];
if (type === 'metirc') {
if (type === 'metric') {
childrenList = getMetricChildren(metrics, dataSourceId);
}
if (type === 'dimension') {
@@ -247,7 +247,6 @@ const DomainManger: React.FC<Props> = ({ domainManger, domainId }) => {
},
},
});
// 我使用TreeGraph进行layout布局采用{type: 'compactBox',direction: 'LR'}模式,如何使子节点与根节点的连线只连接到上下连接桩上
graphRef.current = new G6.TreeGraph({
container: 'semanticGraph',

View File

@@ -1,12 +0,0 @@
import request from 'umi-request';
type ExcuteSqlParams = {
sql: string;
domainId: number;
};
// 执行脚本
export async function excuteSql(params: ExcuteSqlParams) {
const data = { ...params };
return request.post(`${process.env.API_BASE_URL}database/executeSql`, { data });
}

View File

@@ -1,23 +1,31 @@
import type { ActionType, ProColumns } from '@ant-design/pro-table';
import ProTable from '@ant-design/pro-table';
import { message, Button, Drawer, Space, Popconfirm } from 'antd';
import React, { useRef, useState } from 'react';
import { message, Button, Drawer, Space, Popconfirm, Modal, Card, Row, Col } from 'antd';
import { ConsoleSqlOutlined, CoffeeOutlined } from '@ant-design/icons';
import React, { useRef, useState, useEffect } from 'react';
import type { Dispatch } from 'umi';
import { connect } from 'umi';
import DataSourceCreateForm from '../Datasource/components/DataSourceCreateForm';
import ClassDataSourceTypeModal from './ClassDataSourceTypeModal';
import type { StateType } from '../model';
import { getDatasourceList, deleteDatasource } from '../service';
import DataSource from '../Datasource';
import moment from 'moment';
const { Meta } = Card;
type Props = {
dispatch: Dispatch;
domainManger: StateType;
};
const ClassDataSourceTable: React.FC<Props> = ({ dispatch, domainManger }) => {
const { selectDomainId } = domainManger;
const { selectDomainId, dataBaseResultColsMap, dataBaseConfig } = domainManger;
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
const [dataSourceItem, setDataSourceItem] = useState<any>();
const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false);
const [dataSourceModalVisible, setDataSourceModalVisible] = useState(false);
const [fastModeSql, setFastModeSql] = useState<string>('');
const [fastModeTableName, setFastModeTableName] = useState<string>('');
const actionRef = useRef<ActionType>();
@@ -62,6 +70,10 @@ const ClassDataSourceTable: React.FC<Props> = ({ dispatch, domainManger }) => {
key="classEditBtn"
onClick={() => {
setDataSourceItem(record);
if (record.datasourceDetail.queryType === 'table_query') {
setDataSourceModalVisible(true);
return;
}
setCreateModalVisible(true);
}}
>
@@ -72,12 +84,12 @@ const ClassDataSourceTable: React.FC<Props> = ({ dispatch, domainManger }) => {
okText="是"
cancelText="否"
onConfirm={async () => {
const { code } = await deleteDatasource(record.id);
const { code, msg } = await deleteDatasource(record.id);
if (code === 200) {
setDataSourceItem(undefined);
actionRef.current?.reload();
} else {
message.error('删除失败');
message.error(msg);
}
}}
>
@@ -121,6 +133,20 @@ const ClassDataSourceTable: React.FC<Props> = ({ dispatch, domainManger }) => {
return resData;
};
const queryDataBaseExcuteSql = (tableName: string) => {
const sql = `select * from ${tableName}`;
setFastModeSql(sql);
setFastModeTableName(tableName);
dispatch({
type: 'domainManger/queryDataBaseExcuteSql',
payload: {
sql,
domainId: selectDomainId,
tableName,
},
});
};
return (
<>
<ProTable
@@ -140,13 +166,46 @@ const ClassDataSourceTable: React.FC<Props> = ({ dispatch, domainManger }) => {
type="primary"
onClick={() => {
setDataSourceItem(undefined);
setCreateModalVisible(true);
setCreateDataSourceModalOpen(true);
}}
>
</Button>,
]}
/>
{
<ClassDataSourceTypeModal
open={createDataSourceModalOpen}
onTypeChange={(type) => {
if (type === 'fast') {
setDataSourceModalVisible(true);
} else {
setCreateModalVisible(true);
}
setCreateDataSourceModalOpen(false);
}}
/>
}
{dataSourceModalVisible && (
<DataSourceCreateForm
sql={fastModeSql}
basicInfoFormMode="fast"
domainId={Number(selectDomainId)}
dataSourceItem={dataSourceItem}
onCancel={() => {
setDataSourceModalVisible(false);
}}
onDataBaseTableChange={(tableName: string) => {
queryDataBaseExcuteSql(tableName);
}}
onSubmit={() => {
setDataSourceModalVisible(false);
setDataSourceItem(undefined);
actionRef.current?.reload();
}}
createModalVisible={dataSourceModalVisible}
/>
)}
{createModalVisible && (
<Drawer
width={'100%'}

View File

@@ -0,0 +1,68 @@
import { Modal, Card, Row, Col } from 'antd';
import { ConsoleSqlOutlined, CoffeeOutlined } from '@ant-design/icons';
import React, { useState, useEffect } from 'react';
const { Meta } = Card;
type Props = {
open: boolean;
onTypeChange: (type: 'fast' | 'normal') => void;
};
const ClassDataSourceTypeModal: React.FC<Props> = ({ open, onTypeChange }) => {
const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false);
useEffect(() => {
setCreateDataSourceModalOpen(open);
}, [open]);
return (
<>
<Modal
open={createDataSourceModalOpen}
onCancel={() => {
setCreateDataSourceModalOpen(false);
}}
footer={null}
centered
closable={false}
>
<Row gutter={16} style={{ marginTop: '0px' }}>
<Col span={12}>
<Card
hoverable
style={{ height: 220 }}
onClick={() => {
onTypeChange('fast');
setCreateDataSourceModalOpen(false);
}}
cover={
<CoffeeOutlined
width={240}
style={{ paddingTop: '45px', height: 120, fontSize: '48px', color: '#1890ff' }}
/>
}
>
<Meta title="快速创建" description="自动进行数据源可视化创建" />
</Card>
</Col>
<Col span={12}>
<Card
onClick={() => {
onTypeChange('normal');
setCreateDataSourceModalOpen(false);
}}
hoverable
style={{ height: 220 }}
cover={
<ConsoleSqlOutlined
style={{ paddingTop: '45px', height: 120, fontSize: '48px', color: '#1890ff' }}
/>
}
>
<Meta title="SQL脚本" description="自定义SQL脚本创建数据源" />
</Card>
</Col>
</Row>
</Modal>
</>
);
};
export default ClassDataSourceTypeModal;

View File

@@ -88,6 +88,10 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
dataIndex: 'name',
title: '维度名称',
},
{
dataIndex: 'alias',
title: '别名',
},
{
dataIndex: 'bizName',
title: '字段名称',
@@ -146,12 +150,12 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
okText="是"
cancelText="否"
onConfirm={async () => {
const { code } = await deleteDimension(record.id);
const { code, msg } = await deleteDimension(record.id);
if (code === 200) {
setDimensionItem(undefined);
actionRef.current?.reload();
} else {
message.error('删除失败');
message.error(msg);
}
}}
>

View File

@@ -68,6 +68,10 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
dataIndex: 'name',
title: '指标名称',
},
{
dataIndex: 'alias',
title: '别名',
},
{
dataIndex: 'bizName',
title: '字段名称',
@@ -118,12 +122,12 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
okText="是"
cancelText="否"
onConfirm={async () => {
const { code } = await deleteMetric(record.id);
const { code, msg } = await deleteMetric(record.id);
if (code === 200) {
setMetricItem(undefined);
actionRef.current?.reload();
} else {
message.error('删除失败');
message.error(msg);
}
}}
>

View File

@@ -7,29 +7,39 @@ import { formLayout } from '@/components/FormHelper/utils';
import styles from '../style.less';
type Props = {
domainId: number;
dataBaseConfig: any;
onSubmit: (params?: any) => void;
};
const FormItem = Form.Item;
const TextArea = Input.TextArea;
const DatabaseCreateForm: ForwardRefRenderFunction<any, Props> = ({ domainId }, ref) => {
const DatabaseCreateForm: ForwardRefRenderFunction<any, Props> = (
{ domainId, dataBaseConfig, onSubmit },
ref,
) => {
const [form] = Form.useForm();
const [selectedDbType, setSelectedDbType] = useState<string>('h2');
const queryDatabaseConfig = async () => {
const { code, data } = await getDatabaseByDomainId(domainId);
if (code === 200) {
form.setFieldsValue({ ...data });
setSelectedDbType(data?.type);
return;
}
message.error('数据库配置获取错误');
};
// const queryDatabaseConfig = async () => {
// const { code, data } = await getDatabaseByDomainId(domainId);
// if (code === 200) {
// form.setFieldsValue({ ...data });
// setSelectedDbType(data?.type);
// return;
// }
// message.error('数据库配置获取错误');
// };
useEffect(() => {
form.resetFields();
queryDatabaseConfig();
}, [domainId]);
form.setFieldsValue({ ...dataBaseConfig });
setSelectedDbType(dataBaseConfig?.type);
}, [dataBaseConfig]);
// useEffect(() => {
// form.resetFields();
// // queryDatabaseConfig();
// }, [domainId]);
const getFormValidateFields = async () => {
return await form.validateFields();
@@ -48,6 +58,7 @@ const DatabaseCreateForm: ForwardRefRenderFunction<any, Props> = ({ domainId },
if (code === 200) {
message.success('保存成功');
onSubmit?.();
return;
}
message.error(msg);

View File

@@ -11,8 +11,8 @@ type Props = {
domainManger: StateType;
};
const DatabaseSection: React.FC<Props> = ({ domainManger }) => {
const { selectDomainId } = domainManger;
const DatabaseSection: React.FC<Props> = ({ domainManger, dispatch }) => {
const { selectDomainId, dataBaseConfig } = domainManger;
const entityCreateRef = useRef<any>({});
@@ -22,8 +22,16 @@ const DatabaseSection: React.FC<Props> = ({ domainManger }) => {
<ProCard title="数据库设置" bordered>
<DatabaseCreateForm
ref={entityCreateRef}
dataBaseConfig={dataBaseConfig}
domainId={Number(selectDomainId)}
onSubmit={() => {}}
onSubmit={() => {
dispatch({
type: 'domainManger/queryDatabaseByDomainId',
payload: {
domainId: selectDomainId,
},
});
}}
/>
</ProCard>
</Space>

View File

@@ -85,6 +85,7 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
>
<Input placeholder="名称不可重复" disabled={isEdit} />
</FormItem>
<FormItem
name="datasourceId"
label="所属数据源"
@@ -98,6 +99,9 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
))}
</Select>
</FormItem>
<FormItem name="alias" label="别名">
<Input placeholder="多个别名用英文逗号隔开" />
</FormItem>
<FormItem
name="semanticType"
label="类型"

View File

@@ -2,6 +2,8 @@ import React, { useEffect, useState } from 'react';
import { Button, Modal, message } from 'antd';
import { addDomainExtend, editDomainExtend, getDomainExtendDetailConfig } from '../../service';
import DimensionMetricVisibleTransfer from './DimensionMetricVisibleTransfer';
import { exChangeRichEntityListToIds } from './utils';
type Props = {
domainId: number;
themeData: any;
@@ -36,7 +38,6 @@ const DimensionMetricVisibleModal: React.FC<Props> = ({
onSubmit,
}) => {
const [sourceList, setSourceList] = useState<any[]>([]);
const [visibilityData, setVisibilityData] = useState<any>({});
const [selectedKeyList, setSelectedKeyList] = useState<string[]>([]);
const settingTypeConfig = settingType === 'dimension' ? dimensionConfig : metricConfig;
useEffect(() => {
@@ -47,27 +48,12 @@ const DimensionMetricVisibleModal: React.FC<Props> = ({
setSourceList(list);
}, [settingSourceList]);
const queryThemeListData: any = async () => {
const { code, data } = await getDomainExtendDetailConfig({
domainId,
});
if (code === 200) {
setVisibilityData(data.visibility);
return;
}
message.error('获取可见信息失败');
};
useEffect(() => {
queryThemeListData();
}, []);
useEffect(() => {
setSelectedKeyList(visibilityData?.[settingTypeConfig.visibleIdListKey] || []);
}, [visibilityData]);
setSelectedKeyList(themeData.visibility?.[settingTypeConfig.visibleIdListKey] || []);
}, [themeData]);
const saveEntity = async () => {
const { id } = themeData;
const { id, entity } = themeData;
let saveDomainExtendQuery = addDomainExtend;
if (id) {
saveDomainExtendQuery = editDomainExtend;
@@ -79,6 +65,8 @@ const DimensionMetricVisibleModal: React.FC<Props> = ({
}
return list;
}, []);
const entityParams = exChangeRichEntityListToIds(entity);
themeData.entity = entityParams;
const params = {
...themeData,
visibility: themeData.visibility || {},

View File

@@ -2,15 +2,17 @@ import { useEffect, useState, forwardRef, useImperativeHandle } from 'react';
import type { ForwardRefRenderFunction } from 'react';
import { message, Form, Input, Select, Button } from 'antd';
import { addDomainExtend, editDomainExtend } from '../../service';
import type { ISemantic, IChatConfig } from '../../data';
import { formLayout } from '@/components/FormHelper/utils';
import { exChangeRichEntityListToIds } from './utils';
import styles from '../style.less';
type Props = {
entityData: any;
metricList: any[];
dimensionList: any[];
entityData: IChatConfig.IEntity;
metricList: ISemantic.IMetricList;
dimensionList: ISemantic.IDimensionList;
domainId: number;
onSubmit: (params?: any) => void;
onSubmit: () => void;
};
const FormItem = Form.Item;
@@ -34,33 +36,19 @@ const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
if (Object.keys(entityData).length === 0) {
return;
}
const { detailData = {}, names = [] } = entityData;
if (!detailData.dimensionIds) {
entityData = {
...entityData,
detailData: {
...detailData,
dimensionIds: [],
},
};
}
if (!detailData.metricIds) {
entityData = {
...entityData,
detailData: {
...detailData,
metricIds: [],
},
};
}
form.setFieldsValue({ ...entityData, name: names.join(',') });
const names = entityData.names || [];
const formatEntityData = exChangeRichEntityListToIds(entityData);
form.setFieldsValue({
...formatEntityData,
name: names.join(','),
});
}, [entityData]);
useImperativeHandle(ref, () => ({
getFormValidateFields,
}));
useEffect(() => {
const metricOption = metricList.map((item: any) => {
const metricOption = metricList.map((item: ISemantic.IMetricItem) => {
return {
label: item.name,
value: item.id,
@@ -70,7 +58,7 @@ const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
}, [metricList]);
useEffect(() => {
const dimensionEnum = dimensionList.map((item: any) => {
const dimensionEnum = dimensionList.map((item: ISemantic.IDimensionItem) => {
return {
label: item.name,
value: item.id,
@@ -128,6 +116,13 @@ const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
mode="multiple"
allowClear
style={{ width: '100%' }}
filterOption={(inputValue: string, item: any) => {
const { label } = item;
if (label.includes(inputValue)) {
return true;
}
return false;
}}
placeholder="请选择主体标识"
options={dimensionListOptions}
/>
@@ -137,6 +132,13 @@ const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
mode="multiple"
allowClear
style={{ width: '100%' }}
filterOption={(inputValue: string, item: any) => {
const { label } = item;
if (label.includes(inputValue)) {
return true;
}
return false;
}}
placeholder="请选择展示维度信息"
options={dimensionListOptions}
/>
@@ -146,6 +148,13 @@ const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
mode="multiple"
allowClear
style={{ width: '100%' }}
filterOption={(inputValue: string, item: any) => {
const { label } = item;
if (label.includes(inputValue)) {
return true;
}
return false;
}}
placeholder="请选择展示指标信息"
options={metricListOptions}
/>

View File

@@ -3,10 +3,11 @@ import React, { useState, useEffect, useRef } from 'react';
import type { Dispatch } from 'umi';
import { connect } from 'umi';
import type { StateType } from '../../model';
import { getDomainExtendConfig } from '../../service';
import { getDomainExtendConfig, getDomainExtendDetailConfig } from '../../service';
import ProCard from '@ant-design/pro-card';
import EntityCreateForm from './EntityCreateForm';
import MetricSettingForm from './MetricSettingForm';
import type { IChatConfig } from '../../data';
import DimensionMetricVisibleForm from './DimensionMetricVisibleForm';
type Props = {
@@ -17,18 +18,21 @@ type Props = {
const EntitySection: React.FC<Props> = ({ domainManger, dispatch }) => {
const { selectDomainId, dimensionList, metricList } = domainManger;
const [entityData, setEntityData] = useState<any>({});
const [entityData, setEntityData] = useState<IChatConfig.IEntity>({} as IChatConfig.IEntity);
const [themeData, setThemeData] = useState<any>({});
const entityCreateRef = useRef<any>({});
const queryThemeListData: any = async () => {
const { code, data } = await getDomainExtendConfig({
const { code, data } = await getDomainExtendDetailConfig({
domainId: selectDomainId,
});
// getDomainExtendConfig({
// domainId: selectDomainId,
// });
if (code === 200) {
const target = data?.[0] || {};
const target = data;
if (target) {
setThemeData(target);
setEntityData({
@@ -75,7 +79,14 @@ const EntitySection: React.FC<Props> = ({ domainManger, dispatch }) => {
<MetricSettingForm
domainId={Number(selectDomainId)}
themeData={themeData}
metricList={metricList}
// metricList={metricList}
metricList={metricList.filter((item) => {
const blackMetricIdList = themeData.visibility?.blackMetricIdList;
if (Array.isArray(blackMetricIdList)) {
return !blackMetricIdList.includes(item.id);
}
return false;
})}
onSubmit={() => {
queryThemeListData();
}}
@@ -86,8 +97,22 @@ const EntitySection: React.FC<Props> = ({ domainManger, dispatch }) => {
ref={entityCreateRef}
domainId={Number(selectDomainId)}
entityData={entityData}
metricList={metricList}
dimensionList={dimensionList}
// metricList={metricList}
metricList={metricList.filter((item) => {
const blackMetricIdList = themeData.visibility?.blackMetricIdList;
if (Array.isArray(blackMetricIdList)) {
return !blackMetricIdList.includes(item.id);
}
return false;
})}
// dimensionList={dimensionList}
dimensionList={dimensionList.filter((item) => {
const blackDimensionList = themeData.visibility?.blackDimIdList;
if (Array.isArray(blackDimensionList)) {
return !blackDimensionList.includes(item.id);
}
return false;
})}
onSubmit={() => {
queryThemeListData();
}}

View File

@@ -17,7 +17,7 @@ const FormItem = Form.Item;
const Option = Select.Option;
const MetricSettingForm: ForwardRefRenderFunction<any, Props> = (
{ metricList, domainId, themeData: uniqueMetricData },
{ metricList, domainId, themeData: uniqueMetricData, onSubmit },
ref,
) => {
const [form] = Form.useForm();
@@ -82,6 +82,7 @@ const MetricSettingForm: ForwardRefRenderFunction<any, Props> = (
if (code === 200) {
form.setFieldValue('id', data);
onSubmit?.();
message.success('保存成功');
return;
}
@@ -116,6 +117,13 @@ const MetricSettingForm: ForwardRefRenderFunction<any, Props> = (
allowClear
showSearch
style={{ width: '100%' }}
filterOption={(inputValue: string, item: any) => {
const { label } = item;
if (label.includes(inputValue)) {
return true;
}
return false;
}}
placeholder="请选择展示指标信息"
options={metricListOptions}
/>

View File

@@ -0,0 +1,28 @@
import { IChatConfig, ISemantic } from '../../data';
export const exChangeRichEntityListToIds = (entityData: IChatConfig.IEntity) => {
const entityList = entityData.entityIds || [];
const detailData: {
dimensionIds: number[];
metricIds: number[];
} = { dimensionIds: [], metricIds: [] };
const { dimensionList, metricList } = entityData.entityInternalDetailDesc || {};
if (Array.isArray(dimensionList)) {
detailData.dimensionIds = dimensionList.map((item: ISemantic.IDimensionItem) => {
return item.id;
});
}
if (Array.isArray(metricList)) {
detailData.metricIds = metricList.map((item: ISemantic.IMetricItem) => {
return item.id;
});
}
const entityIds = entityList.map((item) => {
return item.id;
});
return {
...entityData,
entityIds,
detailData,
};
};

View File

@@ -159,6 +159,9 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
>
<Input placeholder="名称不可重复" disabled={isEdit} />
</FormItem>
<FormItem name="alias" label="别名">
<Input placeholder="多个别名用英文逗号隔开" />
</FormItem>
<FormItem
name="sensitiveLevel"
label="敏感度"

View File

@@ -215,7 +215,7 @@ const PermissionTable: React.FC<Props> = ({ domainManger }) => {
okText="是"
cancelText="否"
onConfirm={async () => {
const { code } = await removeGroupAuth({
const { code, msg } = await removeGroupAuth({
domainId: record.domainId,
groupId: record.groupId,
});
@@ -223,7 +223,7 @@ const PermissionTable: React.FC<Props> = ({ domainManger }) => {
setPermissonData({});
queryListData();
} else {
message.error('删除失败');
message.error(msg);
}
}}
>

View File

@@ -1,7 +1,7 @@
import { DownOutlined, PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
import { Input, message, Tree, Popconfirm, Space, Tooltip } from 'antd';
import { Input, message, Tree, Popconfirm, Space, Tooltip, Row, Col } from 'antd';
import type { DataNode } from 'antd/lib/tree';
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import type { FC, Key } from 'react';
import { connect } from 'umi';
import type { Dispatch } from 'umi';
@@ -21,6 +21,8 @@ type ProjectListProps = {
selectDomainName: string;
createDomainBtnVisible?: boolean;
dispatch: Dispatch;
onCreateDomainBtnClick?: () => void;
onTreeSelected?: () => void;
};
const projectTreeFlat = (projectTree: DataNode[], filterValue: string): DataNode[] => {
@@ -41,6 +43,8 @@ const projectTreeFlat = (projectTree: DataNode[], filterValue: string): DataNode
const ProjectListTree: FC<ProjectListProps> = ({
selectDomainId,
createDomainBtnVisible = true,
onCreateDomainBtnClick,
onTreeSelected,
dispatch,
}) => {
const [projectTree, setProjectTree] = useState<DataNode[]>([]);
@@ -89,6 +93,7 @@ const ProjectListTree: FC<ProjectListProps> = ({
const targetNodeData = classList.filter((item: any) => {
return item.id === selectedKeys;
})[0];
onTreeSelected?.();
dispatch({
type: 'domainManger/setSelectDomain',
selectDomainId: selectedKeys,
@@ -196,28 +201,29 @@ const ProjectListTree: FC<ProjectListProps> = ({
return (
<div className={styles.projectList}>
<h2 className={styles.treeTitle}>
<span className={styles.title}></span>
<Space>
{createDomainBtnVisible && (
<Tooltip title="新增顶级域">
<PlusCircleOutlined
onClick={() => {
setProjectInfoParams({ type: 'top', modelType: 'add' });
setProjectInfoModalVisible(true);
}}
className={styles.addBtn}
/>
</Tooltip>
)}
</Space>
</h2>
<Search
allowClear
className={styles.search}
placeholder="请输入主题域名称进行查询"
onSearch={onSearch}
/>
<Row>
<Col flex="1 1 200px">
<Search
allowClear
className={styles.search}
placeholder="请输入主题域名称进行查询"
onSearch={onSearch}
/>
</Col>
<Col flex="0 1 50px">
<Tooltip title="新增顶级域">
<PlusCircleOutlined
onClick={() => {
setProjectInfoParams({ type: 'top', modelType: 'add' });
setProjectInfoModalVisible(true);
onCreateDomainBtnClick?.();
}}
className={styles.addBtn}
/>
</Tooltip>
</Col>
</Row>
<Tree
expandedKeys={expandedKeys}
onExpand={handleExpand}

View File

@@ -3,62 +3,7 @@
flex-direction: row;
background-color: #fff;
height: calc(100vh - 48px);
.projectList {
display: flex;
flex-direction: column;
// width: 400px;
overflow: hidden;
// min-height: calc(100vh - 48px);
border-right: 1px solid #d9d9d9;
.treeTitle {
margin-bottom: 0;
padding: 20px;
line-height: 34px;
text-align: right;
background: #fff;
border-bottom: 1px solid #d9d9d9;
.title {
float: left;
}
.addBtn {
cursor: pointer;
&:hover {
color: #296DF3;
}
}
}
.search {
width: calc(100% - 20px);
margin: 10px;
}
.tree {
flex: 1;
padding: 10px;
.projectItem {
display: flex;
width: 100%;
cursor: auto;
.title {
flex: 1;
cursor: pointer;
}
.operation {
.icon {
margin-left: 6px;
cursor: pointer;
}
}
}
}
}
.projectManger {
width: 100%;
@@ -139,6 +84,65 @@
}
}
.projectList {
display: flex;
flex-direction: column;
width: 400px;
overflow: hidden;
// min-height: calc(100vh - 48px);
.addBtn {
cursor: pointer;
width: 100%;
font-size: 18px;
margin-top: 18px;
&:hover {
color: #296DF3;
}
}
.treeTitle {
margin-bottom: 0;
padding: 20px;
line-height: 34px;
text-align: right;
background: #fff;
border-bottom: 1px solid #d9d9d9;
.title {
float: left;
}
}
.search {
width: calc(100% - 20px);
margin: 10px;
}
.tree {
flex: 1;
padding: 10px;
.projectItem {
display: flex;
width: 100%;
cursor: auto;
.title {
flex: 1;
cursor: pointer;
}
.operation {
.icon {
margin-left: 6px;
cursor: pointer;
}
}
}
}
}
.user {
display: grid;
}
@@ -221,4 +225,25 @@
background: #f8f9fb;
}
}
}
}
.domainSelector {
display: flex;
width: max-content;
padding: 0 11px;
cursor: pointer;
position: relative;
// background-color: #fff;
// border: 1px solid #d9d9d9;
border-radius: 4px;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
color: #000a24d9;
.downIcon {
margin-left: 10px;
font-size: 14px;
}
&:hover {
color:#296DF3;
}
}

View File

@@ -62,3 +62,85 @@ export declare namespace IDataSource {
}
type IDataSourceList = IDataSourceItem[];
}
export declare namespace ISemantic {
interface IDimensionItem {
createdBy: string;
updatedBy: string;
createdAt: string;
updatedAt: string;
id: number;
name: string;
bizName: string;
description: string;
status: number;
typeEnum: any;
sensitiveLevel: number;
domainId: number;
type: string;
expr: string;
fullPath: string;
datasourceId: number;
datasourceName: string;
datasourceBizName: string;
semanticType: string;
alias: string;
useCnt: number;
}
interface IMeasure {
name: string;
agg: string;
expr: string;
constraint: string;
alias: string;
createMetric: string;
bizName: string;
isCreateMetric: number;
datasourceId: number;
}
interface ITypeParams {
measures: IMeasure[];
expr: string;
}
interface IMetricItem {
createdBy: string;
updatedBy: string;
createdAt: string;
updatedAt: string;
id: number;
name: string;
bizName: string;
description: string;
status: number;
typeEnum: string;
sensitiveLevel: number;
domainId: number;
domainName: string;
type: string;
typeParams: TypeParams;
fullPath: string;
dataFormatType: string;
dataFormat: string;
alias: string;
useCnt: number;
}
type IDimensionList = IDimensionItem[];
type IMetricList = IMetricItem[];
}
export declare namespace IChatConfig {
interface IEntity {
domainId: number;
domainName: string;
domainBizName: string;
names: string[];
entityIds: ISemantic.IMetricItem[];
entityInternalDetailDesc: {
dimensionList: ISemantic.IDimensionList;
metricList: ISemantic.IMetricList;
};
}
}

View File

@@ -1,6 +1,6 @@
import type { Reducer, Effect } from 'umi';
import { message } from 'antd';
import { getDimensionList, queryMetric } from './service';
import { getDimensionList, queryMetric, excuteSql, getDatabaseByDomainId } from './service';
export type StateType = {
current: number;
@@ -11,6 +11,8 @@ export type StateType = {
metricList: any[];
searchParams: Record<string, any>;
domainData: any;
dataBaseResultColsMap: any;
dataBaseConfig: any;
};
export type ModelType = {
@@ -19,11 +21,15 @@ export type ModelType = {
effects: {
queryDimensionList: Effect;
queryMetricList: Effect;
queryDataBaseExcuteSql: Effect;
queryDatabaseByDomainId: Effect;
};
reducers: {
setSelectDomain: Reducer<StateType>;
setPagination: Reducer<StateType>;
setDimensionList: Reducer<StateType>;
setDataBaseScriptColumn: Reducer<StateType>;
setDataBaseConfig: Reducer<StateType>;
setMetricList: Reducer<StateType>;
reset: Reducer<StateType>;
};
@@ -38,6 +44,8 @@ export const defaultState: StateType = {
dimensionList: [],
metricList: [],
domainData: {},
dataBaseResultColsMap: {},
dataBaseConfig: {},
};
const Model: ModelType = {
@@ -61,6 +69,46 @@ const Model: ModelType = {
message.error(msg);
}
},
*queryDataBaseExcuteSql({ payload }, { call, put, select }) {
const { tableName } = payload;
if (!tableName) {
return;
}
const isExists = yield select((state: any) => {
return state.domainManger.dataBaseResultColsMap[tableName];
});
if (isExists) {
return;
}
const { code, data, msg } = yield call(excuteSql, payload);
if (code === 200) {
const resultList = data.resultList.map((item, index) => {
return {
...item,
index,
};
});
const scriptColumns = data.columns;
yield put({
type: 'setDataBaseScriptColumn',
payload: { resultList, scriptColumns, tableName },
});
} else {
message.error(msg);
}
},
*queryDatabaseByDomainId({ payload }, { call, put }) {
const domainId = payload.domainId;
const { code, data, msg } = yield call(getDatabaseByDomainId, domainId);
if (code === 200) {
yield put({
type: 'setDataBaseConfig',
payload: { dataBaseConfig: data },
});
} else {
message.error(msg);
}
},
},
reducers: {
setSelectDomain(state = defaultState, action) {
@@ -89,6 +137,21 @@ const Model: ModelType = {
...action.payload,
};
},
setDataBaseScriptColumn(state = defaultState, action) {
return {
...state,
dataBaseResultColsMap: {
...state.dataBaseResultColsMap,
[action.payload.tableName]: { ...action.payload },
},
};
},
setDataBaseConfig(state = defaultState, action) {
return {
...state,
...action.payload,
};
},
reset() {
return defaultState;
},

View File

@@ -237,3 +237,32 @@ export function testDatabaseConnect(data: SaveDatabaseParams): Promise<any> {
data,
});
}
type ExcuteSqlParams = {
sql: string;
domainId: number;
};
// 执行脚本
export async function excuteSql(params: ExcuteSqlParams) {
const data = { ...params };
return request.post(`${process.env.API_BASE_URL}database/executeSql`, { data });
}
export function getDbNames(dbId: number): Promise<any> {
return request(`${process.env.API_BASE_URL}database/getDbNames/${dbId}`, {
method: 'GET',
});
}
export function getTables(dbId: number, dbName: string): Promise<any> {
return request(`${process.env.API_BASE_URL}database/getTables/${dbId}/${dbName}`, {
method: 'GET',
});
}
export function getColumns(dbId: number, dbName: string, tableName: string): Promise<any> {
return request(`${process.env.API_BASE_URL}database/getColumns/${dbId}/${dbName}/${tableName}`, {
method: 'GET',
});
}