[improvement][semantic-fe] Refactor database settings functionality.

This commit is contained in:
tristanliu
2023-09-04 12:29:07 +08:00
parent d5c5c63a75
commit f5a7068d5e
34 changed files with 1222 additions and 441 deletions

View File

@@ -41,13 +41,40 @@ const ROUTES = [
envEnableList: [ENV_KEY.CHAT], envEnableList: [ENV_KEY.CHAT],
}, },
{ {
path: '/semanticModel/model/:domainId?/:modelId?/:menuKey?', path: '/model',
component: './SemanticModel/DomainManager',
name: 'semanticModel', name: 'semanticModel',
component: './SemanticModel/DomainManager',
envEnableList: [ENV_KEY.SEMANTIC],
routes: [
{
path: '/model',
redirect: '/model/:domainId?/:modelId?/:menuKey?',
},
{
path: '/model/:domainId?/:modelId?/:menuKey?',
component: './SemanticModel/DomainManager',
name: 'model',
envEnableList: [ENV_KEY.SEMANTIC], envEnableList: [ENV_KEY.SEMANTIC],
}, },
{ {
path: '/Metric', path: '/database',
name: 'database',
component: './SemanticModel/components/Database/DatabaseTable',
envEnableList: [ENV_KEY.SEMANTIC],
},
],
},
{
path: '/database',
name: 'database',
hideInMenu: true,
component: './SemanticModel/components/Database/DatabaseTable',
envEnableList: [ENV_KEY.SEMANTIC],
},
{
path: '/metric',
name: 'metric', name: 'metric',
component: './SemanticModel/Metric', component: './SemanticModel/Metric',
envEnableList: [ENV_KEY.SEMANTIC], envEnableList: [ENV_KEY.SEMANTIC],
@@ -61,10 +88,10 @@ const ROUTES = [
}, },
{ {
path: '/', path: '/',
redirect: APP_TARGET === 'inner' ? '/semanticModel/model/' : '/chat', redirect: APP_TARGET === 'inner' ? '/model' : '/chat',
envRedirect: { envRedirect: {
[ENV_KEY.CHAT]: '/chat', [ENV_KEY.CHAT]: '/chat',
[ENV_KEY.SEMANTIC]: '/semanticModel/model', [ENV_KEY.SEMANTIC]: '/model',
}, },
}, },
{ {

View File

@@ -141,7 +141,58 @@ ol {
backdrop-filter: blur(10px) !important; backdrop-filter: blur(10px) !important;
} }
.ant-menu.ant-menu-dark {
color: #fff;
}
.ant-menu-submenu-selected {
background-color: #296DF3 !important;
}
.ant-menu.ant-menu-dark .ant-menu-sub {
background-color: #fff;
.ant-menu-item-selected {
background-color: #296DF3;
.ant-pro-menu-item-title {
color: #fff;
}
}
.ant-menu-item {
&:hover {
background-color: #e3e3e3;
.ant-menu-item-selected {
background-color: #e3e3e3;
.ant-pro-menu-item-title {
color: #181a1a !important;
}
}
.ant-pro-menu-item-title {
color: #181a1a !important;
}
}
& > span > a {
color: #181a1a;
&:hover {
color: #181a1a;
}
}
}
}
.ant-menu-item:active {
background: inherit
}
// .ant-menu-dark .ant-menu-item > span > a {
// color: #181a1a;
// &:hover {
// color: #fff;
// }
// }
// .ant-menu-dark.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected {
// // color: #fff;
// background-color: #1b4aef;
// }
.customizeHeader { .customizeHeader {
background-color: rgba(0, 0, 0, 0.2); background-color: rgba(0, 0, 0, 0.2);

View File

@@ -11,7 +11,7 @@ type Props = {
const ChatSetting: React.FC<Props> = () => { const ChatSetting: React.FC<Props> = () => {
return ( return (
<> <>
<Helmet title={'模型管理-超音数'} /> <Helmet title={'语义模型-超音数'} />
<OverviewContainer mode={'chatSetting'} /> <OverviewContainer mode={'chatSetting'} />
</> </>
); );

View File

@@ -0,0 +1,57 @@
// import { Tabs } from 'antd';
import React from 'react';
import { connect } from 'umi';
// import styles from '../components/style.less';
import type { StateType } from '../model';
import ProCard from '@ant-design/pro-card';
import EntitySection from '../components/Entity/EntitySection';
// import RecommendedQuestionsSection from '../components/Entity/RecommendedQuestionsSection';
import { ChatConfigType } from '../enum';
import type { Dispatch } from 'umi';
type Props = {
domainManger: StateType;
dispatch: Dispatch;
};
const ChatSettingSection: React.FC<Props> = () => {
// const isModelItem = [
// {
// label: '指标模式',
// key: 'metric',
// children: <EntitySection chatConfigType={ChatConfigType.AGG} />,
// },
// {
// label: '实体模式',
// key: 'dimenstion',
// children: <EntitySection chatConfigType={ChatConfigType.DETAIL} />,
// },
// {
// label: '推荐问题',
// key: 'recommendedQuestions',
// children: <RecommendedQuestionsSection />,
// },
// ];
return (
<div style={{ width: 900, margin: '0 auto' }}>
{/* <Tabs
className={styles.chatSettingSectionTab}
items={isModelItem}
destroyInactiveTabPane
tabPosition="left"
/> */}
<ProCard bordered title="指标模式" style={{ marginBottom: 20 }}>
<EntitySection chatConfigType={ChatConfigType.AGG} />
</ProCard>
<ProCard bordered title="实体模式" style={{ marginBottom: 20 }}>
<EntitySection chatConfigType={ChatConfigType.DETAIL} />
</ProCard>
</div>
);
};
export default connect(({ domainManger }: { domainManger: StateType }) => ({
domainManger,
}))(ChatSettingSection);

View File

@@ -2,30 +2,27 @@ import React, { useEffect, useState } from 'react';
import { Form, Input, Spin, Select, message } from 'antd'; import { Form, Input, Spin, Select, message } from 'antd';
import type { FormInstance } from 'antd/lib/form'; import type { FormInstance } from 'antd/lib/form';
import { getDbNames, getTables } from '../../service'; import { getDbNames, getTables } from '../../service';
import { ISemantic } from '../../data';
const FormItem = Form.Item; const FormItem = Form.Item;
const { TextArea } = Input; const { TextArea } = Input;
type Props = { type Props = {
isEdit?: boolean; isEdit?: boolean;
dataBaseConfig: any; databaseConfigList: ISemantic.IDatabaseItemList;
form: FormInstance<any>; form: FormInstance<any>;
tableLoading?: boolean;
mode?: 'normal' | 'fast'; mode?: 'normal' | 'fast';
}; };
const DataSourceBasicForm: React.FC<Props> = ({ const DataSourceBasicForm: React.FC<Props> = ({ isEdit, databaseConfigList, mode = 'normal' }) => {
isEdit, const [currentDbLinkConfigId, setCurrentDbLinkConfigId] = useState<number>();
dataBaseConfig,
tableLoading = false,
mode = 'normal',
}) => {
const [dbNameList, setDbNameList] = useState<any[]>([]); const [dbNameList, setDbNameList] = useState<any[]>([]);
const [tableNameList, setTableNameList] = useState<any[]>([]); const [tableNameList, setTableNameList] = useState<any[]>([]);
const [currentDbName, setCurrentDbName] = useState<string>(''); const [loading, setLoading] = useState<boolean>(false);
const [currentTableName, setCurrentTableName] = useState<string>('');
const queryDbNameList = async (databaseId: number) => { const queryDbNameList = async (databaseId: number) => {
setLoading(true);
const { code, data, msg } = await getDbNames(databaseId); const { code, data, msg } = await getDbNames(databaseId);
setLoading(false);
if (code === 200) { if (code === 200) {
const list = data?.resultList || []; const list = data?.resultList || [];
setDbNameList(list); setDbNameList(list);
@@ -34,7 +31,12 @@ const DataSourceBasicForm: React.FC<Props> = ({
} }
}; };
const queryTableNameList = async (databaseName: string) => { const queryTableNameList = async (databaseName: string) => {
const { code, data, msg } = await getTables(dataBaseConfig.id, databaseName); if (!currentDbLinkConfigId) {
return;
}
setLoading(true);
const { code, data, msg } = await getTables(currentDbLinkConfigId, databaseName);
setLoading(false);
if (code === 200) { if (code === 200) {
const list = data?.resultList || []; const list = data?.resultList || [];
setTableNameList(list); setTableNameList(list);
@@ -42,16 +44,32 @@ const DataSourceBasicForm: React.FC<Props> = ({
message.error(msg); message.error(msg);
} }
}; };
useEffect(() => {
if (dataBaseConfig?.id) {
queryDbNameList(dataBaseConfig.id);
}
}, [dataBaseConfig]);
return ( return (
<Spin spinning={tableLoading}> <Spin spinning={loading}>
{mode === 'fast' && ( {mode === 'fast' && (
<> <>
<FormItem
name="databaseId"
label="数据库连接"
rules={[{ required: true, message: '请选择数据库连接' }]}
>
<Select
showSearch
placeholder="请选择数据库连接"
disabled={isEdit}
onChange={(dbLinkConfigId: number) => {
queryDbNameList(dbLinkConfigId);
setCurrentDbLinkConfigId(dbLinkConfigId);
}}
>
{databaseConfigList.map((item) => (
<Select.Option key={item.id} value={item.id} disabled={!item.hasUsePermission}>
{item.name}
</Select.Option>
))}
</Select>
</FormItem>
<FormItem <FormItem
name="dbName" name="dbName"
label="数据库名" label="数据库名"
@@ -59,11 +77,10 @@ const DataSourceBasicForm: React.FC<Props> = ({
> >
<Select <Select
showSearch showSearch
placeholder="请选择数据库/表" placeholder="请选择一个数据库连接"
disabled={isEdit} disabled={isEdit}
onChange={(dbName: string) => { onChange={(dbName: string) => {
queryTableNameList(dbName); queryTableNameList(dbName);
setCurrentDbName(dbName);
}} }}
> >
{dbNameList.map((item) => ( {dbNameList.map((item) => (
@@ -78,15 +95,7 @@ const DataSourceBasicForm: React.FC<Props> = ({
label="数据表名" label="数据表名"
rules={[{ required: true, message: '请选择数据库/表' }]} rules={[{ required: true, message: '请选择数据库/表' }]}
> >
<Select <Select placeholder="请选择数据库/表" disabled={isEdit} showSearch>
placeholder="请选择数据库/表"
disabled={isEdit}
showSearch
onChange={(tableName: string) => {
// queryTableNameList(tableName);
setCurrentTableName(tableName);
}}
>
{tableNameList.map((item) => ( {tableNameList.map((item) => (
<Select.Option key={item.name} value={item.name}> <Select.Option key={item.name} value={item.name}>
{item.name} {item.name}

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { Form, Button, Modal, Steps, message } from 'antd'; import { Form, Button, Modal, Steps, message } from 'antd';
import BasicInfoForm from './DataSourceBasicForm'; import DataSourceBasicForm from './DataSourceBasicForm';
import FieldForm from './DataSourceFieldForm'; import FieldForm from './DataSourceFieldForm';
import { formLayout } from '@/components/FormHelper/utils'; import { formLayout } from '@/components/FormHelper/utils';
import { EnumDataSourceType } from '../constants'; import { EnumDataSourceType } from '../constants';
@@ -46,9 +46,10 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
const [currentStep, setCurrentStep] = useState(0); const [currentStep, setCurrentStep] = useState(0);
const [saveLoading, setSaveLoading] = useState(false); const [saveLoading, setSaveLoading] = useState(false);
const [hasEmptyNameField, setHasEmptyNameField] = useState<boolean>(false); const [hasEmptyNameField, setHasEmptyNameField] = useState<boolean>(false);
const [formDatabaseId, setFormDatabaseId] = useState<number>();
const formValRef = useRef(initFormVal as any); const formValRef = useRef(initFormVal as any);
const [form] = Form.useForm(); const [form] = Form.useForm();
const { dataBaseConfig, selectModelId: modelId } = domainManger; const { databaseConfigList, selectModelId: modelId } = domainManger;
const updateFormVal = (val: any) => { const updateFormVal = (val: any) => {
formValRef.current = val; formValRef.current = val;
}; };
@@ -82,11 +83,13 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
bizName, bizName,
timeGranularity, timeGranularity,
agg, agg,
isCreateDimension, isCreateDimension: createDimension,
name, name,
isCreateMetric, isCreateMetric: createMetric,
dateFormat, dateFormat,
} = item; } = item;
const isCreateDimension = createDimension ? 1 : 0;
const isCreateMetric = createMetric ? 1 : 0;
switch (type) { switch (type) {
case EnumDataSourceType.CATEGORICAL: case EnumDataSourceType.CATEGORICAL:
fieldsClassify.dimensions.push({ fieldsClassify.dimensions.push({
@@ -157,7 +160,7 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
const queryParams = { const queryParams = {
...submitForm, ...submitForm,
sqlQuery: sql, sqlQuery: sql,
databaseId: dataSourceItem?.databaseId || dataBaseConfig.id, databaseId: dataSourceItem?.databaseId || formDatabaseId,
queryType: basicInfoFormMode === 'fast' ? 'table_query' : 'sql_query', queryType: basicInfoFormMode === 'fast' ? 'table_query' : 'sql_query',
tableQuery: dbName && tableName ? `${dbName}.${tableName}` : '', tableQuery: dbName && tableName ? `${dbName}.${tableName}` : '',
modelId, modelId,
@@ -217,7 +220,7 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
if (queryType === 'table_query') { if (queryType === 'table_query') {
const tableQueryString = tableQuery || ''; const tableQueryString = tableQuery || '';
const [dbName, tableName] = tableQueryString.split('.'); const [dbName, tableName] = tableQueryString.split('.');
columns = await queryTableColumnList(dbName, tableName); columns = await queryTableColumnList(dataSourceItem.databaseId, dbName, tableName);
tableQueryInitValue = { tableQueryInitValue = {
dbName, dbName,
tableName, tableName,
@@ -227,13 +230,14 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
}; };
const formatterInitData = (columns: any[], extendParams: Record<string, any> = {}) => { const formatterInitData = (columns: any[], extendParams: Record<string, any> = {}) => {
const { id, name, bizName, description, datasourceDetail } = dataSourceItem as any; const { id, name, bizName, description, datasourceDetail, databaseId } = dataSourceItem as any;
const { dimensions, identifiers, measures } = datasourceDetail; const { dimensions, identifiers, measures } = datasourceDetail;
const initValue = { const initValue = {
id, id,
name, name,
bizName, bizName,
description, description,
databaseId,
...extendParams, ...extendParams,
// ...tableQueryInitValue, // ...tableQueryInitValue,
}; };
@@ -274,11 +278,8 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
setFields(result); setFields(result);
}; };
const queryTableColumnList = async (dbName: string, tableName: string) => { const queryTableColumnList = async (databaseId: number, dbName: string, tableName: string) => {
if (!dataBaseConfig?.id) { const { code, data, msg } = await getColumns(databaseId, dbName, tableName);
return;
}
const { code, data, msg } = await getColumns(dataBaseConfig.id, dbName, tableName);
if (code === 200) { if (code === 200) {
const list = data?.resultList || []; const list = data?.resultList || [];
// setTableNameList(list); // setTableNameList(list);
@@ -299,16 +300,20 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
}; };
const renderContent = () => { const renderContent = () => {
if (currentStep === 1) {
return <FieldForm fields={fields} onFieldChange={handleFieldChange} />;
}
return ( return (
<BasicInfoForm <>
<div style={{ display: currentStep === 1 ? 'block' : 'none' }}>
<FieldForm fields={fields} onFieldChange={handleFieldChange} />;
</div>
<div style={{ display: currentStep !== 1 ? 'block' : 'none' }}>
<DataSourceBasicForm
form={form} form={form}
isEdit={isEdit} isEdit={isEdit}
mode={basicInfoFormMode} mode={basicInfoFormMode}
dataBaseConfig={dataBaseConfig} databaseConfigList={databaseConfigList}
/> />
</div>
</>
); );
}; };
@@ -365,9 +370,10 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
}} }}
onValuesChange={(value, values) => { onValuesChange={(value, values) => {
const { tableName } = value; const { tableName } = value;
const { dbName } = values; const { dbName, databaseId } = values;
setFormDatabaseId(databaseId);
if (tableName) { if (tableName) {
queryTableColumnList(dbName, tableName); queryTableColumnList(databaseId, dbName, tableName);
} }
}} }}
className={styles.form} className={styles.form}

View File

@@ -230,9 +230,12 @@ const FieldForm: React.FC<Props> = ({ fields, onFieldChange }) => {
style={{ marginBottom: '10px' }} style={{ marginBottom: '10px' }}
banner banner
message={ message={
<Marquee pauseOnHover gradient={false}> <div>
// //
</Marquee> </div>
// <Marquee pauseOnHover gradient={false}>
// 为了保障同一个主题域下维度/指标列表唯一,消除歧义,若本主题域下的多个数据源存在相同的字段名并且都勾选了快速创建,系统默认这些相同字段的指标维度是同一个,同时列表中将只显示最后一次创建的指标/维度。
// </Marquee>
} }
/> />
<Table<FieldItem> <Table<FieldItem>

View File

@@ -27,11 +27,12 @@ import 'ace-builds/src-min-noconflict/ext-searchbox';
import 'ace-builds/src-min-noconflict/theme-sqlserver'; import 'ace-builds/src-min-noconflict/theme-sqlserver';
import 'ace-builds/src-min-noconflict/theme-monokai'; import 'ace-builds/src-min-noconflict/theme-monokai';
import 'ace-builds/src-min-noconflict/mode-sql'; import 'ace-builds/src-min-noconflict/mode-sql';
import { IDataSource, ISemantic } from '../../data';
type IProps = { type IProps = {
domainManger: StateType; domainManger: StateType;
dispatch: Dispatch; dispatch: Dispatch;
dataSourceItem: DataInstanceItem; dataSourceItem: IDataSource.IDataSourceItem;
onUpdateSql?: (sql: string) => void; onUpdateSql?: (sql: string) => void;
sql?: string; sql?: string;
onSubmitSuccess?: (dataSourceInfo: any) => void; onSubmitSuccess?: (dataSourceInfo: any) => void;
@@ -61,7 +62,7 @@ const SqlDetail: React.FC<IProps> = ({
onUpdateSql, onUpdateSql,
onJdbcSourceChange, onJdbcSourceChange,
}) => { }) => {
const { dataBaseConfig, selectModelId: modelId } = domainManger; const { databaseConfigList, selectModelId: modelId } = domainManger;
const [resultTable, setResultTable] = useState<ResultTableItem[]>([]); const [resultTable, setResultTable] = useState<ResultTableItem[]>([]);
const [resultTableLoading, setResultTableLoading] = useState(false); const [resultTableLoading, setResultTableLoading] = useState(false);
const [resultCols, setResultCols] = useState<ResultColItem[]>([]); const [resultCols, setResultCols] = useState<ResultColItem[]>([]);
@@ -71,6 +72,7 @@ const SqlDetail: React.FC<IProps> = ({
total: 0, total: 0,
}); });
const [jdbcSourceItems, setJdbcSourceItems] = useState<JdbcSourceItems[]>([]); const [jdbcSourceItems, setJdbcSourceItems] = useState<JdbcSourceItems[]>([]);
const [currentJdbcSourceItem, setCurrentJdbcSourceItem] = useState<JdbcSourceItems>();
const [dataSourceModalVisible, setDataSourceModalVisible] = useState(false); const [dataSourceModalVisible, setDataSourceModalVisible] = useState(false);
const [tableScroll, setTableScroll] = useState({ const [tableScroll, setTableScroll] = useState({
@@ -99,15 +101,40 @@ const SqlDetail: React.FC<IProps> = ({
const [scriptColumns, setScriptColumns] = useState<any[]>([]); const [scriptColumns, setScriptColumns] = useState<any[]>([]);
// useEffect(() => {
// const list = databaseConfigList.map((item: ISemantic.IDatabaseItem) => {
// return {
// label: item.name,
// key: item.id,
// disabled: !item.hasUsePermission,
// };
// });
// setJdbcSourceItems(list);
// const config = list[0];
// setCurrentJdbcSourceItem(config);
// onJdbcSourceChange?.(config?.key && Number(config?.key));
// }, [databaseConfigList]);
useEffect(() => { useEffect(() => {
setJdbcSourceItems([ const list = databaseConfigList.map((item: ISemantic.IDatabaseItem) => {
{ return {
label: dataBaseConfig?.name, label: item.name,
key: dataBaseConfig?.id, key: item.id,
}, disabled: !item.hasUsePermission,
]); };
onJdbcSourceChange?.(dataBaseConfig?.id && Number(dataBaseConfig?.id)); });
}, [dataBaseConfig]); setJdbcSourceItems(list);
let targetDataBase = list[0];
if (dataSourceItem?.id) {
const { databaseId } = dataSourceItem;
const target = list.find((item) => item.key === databaseId);
if (target) {
targetDataBase = target;
}
}
setCurrentJdbcSourceItem(targetDataBase);
// onJdbcSourceChange?.(targetDataBase?.key && Number(targetDataBase?.key));
}, [dataSourceItem, databaseConfigList]);
function creatCalcItem(key: string, data: string) { function creatCalcItem(key: string, data: string) {
const line = document.createElement('div'); // 需要每条数据一行,这样避免数据换行的时候获得的宽度不准确 const line = document.createElement('div'); // 需要每条数据一行,这样避免数据换行的时候获得的宽度不准确
@@ -209,10 +236,14 @@ const SqlDetail: React.FC<IProps> = ({
}; };
const separateSql = async (value: string) => { const separateSql = async (value: string) => {
if (!currentJdbcSourceItem?.key) {
return;
}
setResultTableLoading(true); setResultTableLoading(true);
const { code, data, msg } = await excuteSql({ const { code, data, msg } = await excuteSql({
sql: value, sql: value,
modelId, // modelId,
id: currentJdbcSourceItem.key,
}); });
setResultTableLoading(false); setResultTableLoading(false);
if (code === 200) { if (code === 200) {
@@ -378,6 +409,7 @@ const SqlDetail: React.FC<IProps> = ({
})[0]; })[0];
if (target) { if (target) {
// setJdbcSourceName(target.label); // setJdbcSourceName(target.label);
setCurrentJdbcSourceItem(target);
onJdbcSourceChange?.(Number(value)); onJdbcSourceChange?.(Number(value));
} }
}, },
@@ -387,7 +419,7 @@ const SqlDetail: React.FC<IProps> = ({
<Button style={{ marginRight: '15px', minWidth: '140px' }}> <Button style={{ marginRight: '15px', minWidth: '140px' }}>
<Space> <Space>
<CloudServerOutlined className={styles.sqlOprIcon} style={{ marginRight: 0 }} /> <CloudServerOutlined className={styles.sqlOprIcon} style={{ marginRight: 0 }} />
<span>{jdbcSourceItems[0]?.label}</span> <span>{currentJdbcSourceItem?.label}</span>
</Space> </Space>
</Button> </Button>
</Dropdown> </Dropdown>

View File

@@ -29,10 +29,10 @@ export const TYPE_OPTIONS = [
label: '主键', label: '主键',
value: EnumDataSourceType.PRIMARY, value: EnumDataSourceType.PRIMARY,
}, },
{ // {
label: '外键', // label: '外键',
value: EnumDataSourceType.FOREIGN, // value: EnumDataSourceType.FOREIGN,
}, // },
]; ];
export const AGG_OPTIONS = [ export const AGG_OPTIONS = [

View File

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

View File

@@ -11,7 +11,7 @@ type Props = {
const DomainManager: React.FC<Props> = () => { const DomainManager: React.FC<Props> = () => {
return ( return (
<> <>
<Helmet title={'模型管理-超音数'} /> <Helmet title={'语义模型-超音数'} />
<OverviewContainer mode={'domain'} /> <OverviewContainer mode={'domain'} />
</> </>
); );

View File

@@ -1,4 +1,4 @@
import { Form, Input } from 'antd'; import { Form, Input, Space, Row, Col } from 'antd';
import StandardFormRow from '@/components/StandardFormRow'; import StandardFormRow from '@/components/StandardFormRow';
import TagSelect from '@/components/TagSelect'; import TagSelect from '@/components/TagSelect';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
@@ -32,17 +32,17 @@ const MetricFilter: React.FC<Props> = ({ filterValues = {}, onFiltersChange }) =
}; };
const filterList = [ const filterList = [
{ // {
title: '指标类型', // title: '指标类型',
key: 'type', // key: 'type',
options: [ // options: [
{ // {
value: 'ATOMIC', // value: 'ATOMIC',
label: '原子指标', // label: '原子指标',
}, // },
{ value: 'DERIVED', label: '衍生指标' }, // { value: 'DERIVED', label: '衍生指标' },
], // ],
}, // },
{ {
title: '敏感度', title: '敏感度',
key: 'sensitiveLevel', key: 'sensitiveLevel',
@@ -56,7 +56,7 @@ const MetricFilter: React.FC<Props> = ({ filterValues = {}, onFiltersChange }) =
form={form} form={form}
colon={false} colon={false}
onValuesChange={(value, values) => { onValuesChange={(value, values) => {
if (value.name) { if (value.key) {
return; return;
} }
handleValuesChange(value, values); handleValuesChange(value, values);
@@ -64,21 +64,46 @@ const MetricFilter: React.FC<Props> = ({ filterValues = {}, onFiltersChange }) =
> >
<StandardFormRow key="search" block> <StandardFormRow key="search" block>
<div className={styles.searchBox}> <div className={styles.searchBox}>
<FormItem name={'key'} noStyle> <Row>
<Col flex="100px">
<span
style={{
fontSize: '18px',
fontWeight: 'bold',
position: 'relative',
top: '12px',
}}
>
</span>
</Col>
<Col flex="auto">
<FormItem name="key" noStyle>
<div className={styles.searchInput}> <div className={styles.searchInput}>
<Input.Search <Input.Search
placeholder="请输入需要查询指标的ID、指标名称、字段名称" placeholder="请输入需要查询指标的ID、指标名称、字段名称"
enterButton={<SearchOutlined style={{ marginTop: 5 }} />} enterButton={<SearchOutlined style={{ marginTop: 5 }} />}
onSearch={onSearch} onSearch={(value) => {
onSearch(value);
}}
/> />
</div> </div>
</FormItem> </FormItem>
</Col>
</Row>
</div> </div>
</StandardFormRow> </StandardFormRow>
<Space size={80}>
<StandardFormRow key="domainIds" title="所属主题域" block>
<FormItem name="domainIds">
<DomainTreeSelect />
</FormItem>
</StandardFormRow>
{filterList.map((item) => { {filterList.map((item) => {
const { title, key, options } = item; const { title, key, options } = item;
return ( return (
<StandardFormRow key={key} title={title} block> <StandardFormRow key={key} title={title} block>
<div style={{ marginLeft: -30 }}>
<FormItem name={key}> <FormItem name={key}>
<TagSelect reverseCheckAll single> <TagSelect reverseCheckAll single>
{options.map((item: any) => ( {options.map((item: any) => (
@@ -88,14 +113,11 @@ const MetricFilter: React.FC<Props> = ({ filterValues = {}, onFiltersChange }) =
))} ))}
</TagSelect> </TagSelect>
</FormItem> </FormItem>
</div>
</StandardFormRow> </StandardFormRow>
); );
})} })}
<StandardFormRow key="domainIds" title="所属主题域" block> </Space>
<FormItem name="domainIds">
<DomainTreeSelect />
</FormItem>
</StandardFormRow>
</Form> </Form>
); );
}; };

View File

@@ -28,7 +28,7 @@ type QueryMetricListParams = {
}; };
const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => { const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
const { selectDomainId } = domainManger; const { selectDomainId, selectModelId: modelId } = domainManger;
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false); const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
const [pagination, setPagination] = useState({ const [pagination, setPagination] = useState({
current: 1, current: 1,
@@ -45,13 +45,17 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
queryMetricList(); queryMetricList();
}, []); }, []);
const queryMetricList = async (params: QueryMetricListParams = {}) => { const queryMetricList = async (params: QueryMetricListParams = {}, disabledLoading = false) => {
if (!disabledLoading) {
setLoading(true); setLoading(true);
}
const { code, data, msg } = await queryMetric({ const { code, data, msg } = await queryMetric({
...pagination, ...pagination,
...params, ...params,
}); });
if (!disabledLoading) {
setLoading(false); setLoading(false);
}
const { list, pageSize, current, total } = data || {}; const { list, pageSize, current, total } = data || {};
let resData: any = {}; let resData: any = {};
if (code === 200) { if (code === 200) {
@@ -86,15 +90,15 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
dataIndex: 'name', dataIndex: 'name',
title: '指标名称', title: '指标名称',
}, },
{ // {
dataIndex: 'alias', // dataIndex: 'alias',
title: '别名', // title: '别名',
search: false, // search: false,
}, // },
{ // {
dataIndex: 'bizName', // dataIndex: 'bizName',
title: '字段名称', // title: '字段名称',
}, // },
{ {
dataIndex: 'modelName', dataIndex: 'modelName',
title: '所属模型', title: '所属模型',
@@ -114,14 +118,14 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
title: '描述', title: '描述',
search: false, search: false,
}, },
{ // {
dataIndex: 'type', // dataIndex: 'type',
title: '指标类型', // title: '指标类型',
valueEnum: { // valueEnum: {
ATOMIC: '原子指标', // ATOMIC: '原子指标',
DERIVED: '衍生指标', // DERIVED: '衍生指标',
}, // },
}, // },
{ {
dataIndex: 'updatedAt', dataIndex: 'updatedAt',
@@ -178,7 +182,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
]; ];
const handleFilterChange = async (filterParams: { const handleFilterChange = async (filterParams: {
name: string; key: string;
sensitiveLevel: string; sensitiveLevel: string;
type: string; type: string;
}) => { }) => {
@@ -190,7 +194,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
params.sensitiveLevel = sensitiveLevelValue; params.sensitiveLevel = sensitiveLevelValue;
params.type = typeValue; params.type = typeValue;
setFilterParams(params); setFilterParams(params);
await queryMetricList(params); await queryMetricList(params, filterParams.key ? false : true);
}; };
return ( return (
@@ -231,6 +235,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
<MetricInfoCreateForm <MetricInfoCreateForm
domainId={Number(selectDomainId)} domainId={Number(selectDomainId)}
createModalVisible={createModalVisible} createModalVisible={createModalVisible}
modelId={modelId}
metricItem={metricItem} metricItem={metricItem}
onSubmit={() => { onSubmit={() => {
setCreateModalVisible(false); setCreateModalVisible(false);

View File

@@ -1,11 +1,11 @@
import { Popover, message, Space } from 'antd'; import { Popover, message, Space } from 'antd';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { connect, Helmet, history, useParams, useRouteMatch, useLocation } from 'umi'; import { connect, Helmet, history, useParams } from 'umi';
import DomainListTree from './components/DomainList'; import DomainListTree from './components/DomainList';
import styles from './components/style.less'; import styles from './components/style.less';
import type { StateType } from './model'; import type { StateType } from './model';
import { DownOutlined } from '@ant-design/icons'; import { DownOutlined, LeftOutlined } from '@ant-design/icons';
import { ISemantic } from './data'; import { ISemantic } from './data';
import { getDomainList, getModelList } from './service'; import { getDomainList, getModelList } from './service';
import ChatSettingTab from './ChatSetting/ChatSettingTab'; import ChatSettingTab from './ChatSetting/ChatSettingTab';
@@ -81,6 +81,23 @@ const OverviewContainer: React.FC<Props> = ({ mode, domainManger, dispatch }) =>
useEffect(() => { useEffect(() => {
initProjectTree(); initProjectTree();
dispatch({
type: 'domainManger/queryDatabaseList',
});
return () => {
dispatch({
type: 'domainManger/setSelectDomain',
selectDomainId: 0,
selectDomainName: '',
domainData: undefined,
});
dispatch({
type: 'domainManger/setSelectModel',
selectModelId: 0,
selectModelName: '',
modelData: undefined,
});
};
}, []); }, []);
useEffect(() => { useEffect(() => {
@@ -88,12 +105,6 @@ const OverviewContainer: React.FC<Props> = ({ mode, domainManger, dispatch }) =>
return; return;
} }
queryModelList(); queryModelList();
dispatch({
type: 'domainManger/queryDatabaseByDomainId',
payload: {
domainId: selectDomainId,
},
});
}, [selectDomainId]); }, [selectDomainId]);
const queryModelList = async () => { const queryModelList = async () => {
@@ -125,7 +136,6 @@ const OverviewContainer: React.FC<Props> = ({ mode, domainManger, dispatch }) =>
return; return;
} }
setIsModel(false); setIsModel(false);
setActiveKey(menuKey);
}, [domainList, selectDomainId]); }, [domainList, selectDomainId]);
const initModelConfig = () => { const initModelConfig = () => {
@@ -155,8 +165,8 @@ const OverviewContainer: React.FC<Props> = ({ mode, domainManger, dispatch }) =>
}, [selectModelId]); }, [selectModelId]);
const pushUrlMenu = (domainId: number, modelId: number, menuKey: string) => { const pushUrlMenu = (domainId: number, modelId: number, menuKey: string) => {
const path = mode === 'domain' ? 'semanticModel' : 'chatSetting'; const path = mode === 'domain' ? 'model' : 'chatSetting/model';
history.push(`/${path}/model/${domainId}/${modelId || 0}/${menuKey}`); history.push(`/${path}/${domainId}/${modelId || 0}/${menuKey}`);
}; };
const handleModelChange = (model?: ISemantic.IModelItem) => { const handleModelChange = (model?: ISemantic.IModelItem) => {
@@ -190,9 +200,21 @@ const OverviewContainer: React.FC<Props> = ({ mode, domainManger, dispatch }) =>
return ( return (
<div className={styles.projectBody}> <div className={styles.projectBody}>
<Helmet title={'模型管理-超音数'} /> <Helmet title={'语义模型-超音数'} />
<div className={styles.projectManger}> <div className={styles.projectManger}>
<h2 className={styles.title}> <h2 className={styles.title}>
{!!selectModelId && (
<div
className={styles.backBtn}
onClick={() => {
cleanModelInfo(selectDomainId);
}}
>
<LeftOutlined />
</div>
)}
<div className={styles.navContainer}>
<Popover <Popover
zIndex={1000} zIndex={1000}
overlayInnerStyle={{ overlayInnerStyle={{
@@ -219,13 +241,13 @@ const OverviewContainer: React.FC<Props> = ({ mode, domainManger, dispatch }) =>
/> />
} }
trigger="click" trigger="click"
open={open} open={selectModelId ? false : open}
onOpenChange={handleOpenChange} onOpenChange={handleOpenChange}
> >
<div className={styles.domainSelector}> <div className={styles.domainSelector}>
<span className={styles.domainTitle}> <span className={styles.domainTitle}>
<Space> <Space>
{selectDomainName ? `当前主题域:${selectDomainName}` : '主题域信息'} {selectDomainName ? `${selectDomainName}` : '主题域信息'}
{selectModelName && ( {selectModelName && (
<> <>
<span style={{ position: 'relative', top: '-2px' }}> | </span> <span style={{ position: 'relative', top: '-2px' }}> | </span>
@@ -234,11 +256,14 @@ const OverviewContainer: React.FC<Props> = ({ mode, domainManger, dispatch }) =>
)} )}
</Space> </Space>
</span> </span>
{!selectModelId && (
<span className={styles.downIcon}> <span className={styles.downIcon}>
<DownOutlined /> <DownOutlined />
</span> </span>
)}
</div> </div>
</Popover> </Popover>
</div>
</h2> </h2>
{selectDomainId ? ( {selectDomainId ? (

View File

@@ -126,11 +126,11 @@ const NodeInfoDrawer: React.FC<Props> = ({
label: '敏感度', label: '敏感度',
value: SENSITIVE_LEVEL_ENUM[sensitiveLevel], value: SENSITIVE_LEVEL_ENUM[sensitiveLevel],
}, },
{ // {
label: '指标类型', // label: '指标类型',
value: MetricTypeWording[type], // value: MetricTypeWording[type],
hideItem: nodeType !== SemanticNodeType.METRIC, // hideItem: nodeType !== SemanticNodeType.METRIC,
}, // },
], ],
}, },
{ {

View File

@@ -363,6 +363,30 @@ const DomainManger: React.FC<Props> = ({
setVisibleModeOpen(false); setVisibleModeOpen(false);
} }
const lessNodeZoomRealAndMoveCenter = () => {
const bbox = graphRef.current.get('group').getBBox();
// 计算图形的中心点
const centerX = (bbox.minX + bbox.maxX) / 2;
const centerY = (bbox.minY + bbox.maxY) / 2;
// 获取画布的中心点
const canvasWidth = graphRef.current.get('width');
const canvasHeight = graphRef.current.get('height');
const canvasCenterX = canvasWidth / 2;
const canvasCenterY = canvasHeight / 2;
// 计算画布需要移动的距离
const dx = canvasCenterX - centerX;
const dy = canvasCenterY - centerY;
// 将画布移动到中心点
graphRef.current.translate(dx, dy);
// 将缩放比例设置为 1以画布中心点为中心进行缩放
graphRef.current.zoomTo(1, { x: canvasCenterX, y: canvasCenterY });
};
useEffect(() => { useEffect(() => {
if (!Array.isArray(graphData?.children)) { if (!Array.isArray(graphData?.children)) {
return; return;
@@ -474,9 +498,13 @@ const DomainManger: React.FC<Props> = ({
}); });
graphRef.current.data(graphData); graphRef.current.data(graphData);
graphRef.current.render(); graphRef.current.render();
graphRef.current.fitView([80, 80]);
// setAllActiveLegend(legend); const nodeCount = graphRef.current.getNodes().length;
if (nodeCount < 10) {
lessNodeZoomRealAndMoveCenter();
} else {
graphRef.current.fitView([80, 80]);
}
graphRef.current.on('node:click', (evt: any) => { graphRef.current.on('node:click', (evt: any) => {
const item = evt.item; // 被操作的节点 item const item = evt.item; // 被操作的节点 item

View File

@@ -28,7 +28,7 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({
onCancel, onCancel,
dispatch, dispatch,
}) => { }) => {
const { selectDomainId, dataBaseConfig, selectModelId } = domainManger; const { selectDomainId } = domainManger;
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false); const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
const [dataSourceModalVisible, setDataSourceModalVisible] = useState(false); const [dataSourceModalVisible, setDataSourceModalVisible] = useState(false);
@@ -77,7 +77,6 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({
centered centered
closable={false} closable={false}
> >
{dataBaseConfig && dataBaseConfig.id ? (
<Row gutter={16} style={{ marginTop: '0px' }}> <Row gutter={16} style={{ marginTop: '0px' }}>
<Col span={12}> <Col span={12}>
<Card <Card
@@ -119,24 +118,6 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({
</Card> </Card>
</Col> </Col>
</Row> </Row>
) : (
<Result
status="warning"
subTitle="创建数据源需要先完成数据库设置"
extra={
<Button
type="primary"
key="console"
onClick={() => {
history.replace(`/semanticModel/${selectDomainId}/0/dataBase`);
onCancel?.();
}}
>
</Button>
}
/>
)}
</Modal> </Modal>
{dataSourceModalVisible && ( {dataSourceModalVisible && (

View File

@@ -83,6 +83,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
{ {
dataIndex: 'id', dataIndex: 'id',
title: 'ID', title: 'ID',
width: 80,
order: 100, order: 100,
}, },
{ {
@@ -92,16 +93,19 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
{ {
dataIndex: 'alias', dataIndex: 'alias',
title: '别名', title: '别名',
width: 300,
ellipsis: true,
search: false, search: false,
}, },
{ {
dataIndex: 'bizName', dataIndex: 'bizName',
title: '字段名称', title: '字段名称',
order: 9, // order: 9,
}, },
{ {
dataIndex: 'sensitiveLevel', dataIndex: 'sensitiveLevel',
title: '敏感度', title: '敏感度',
width: 80,
valueEnum: SENSITIVE_LEVEL_ENUM, valueEnum: SENSITIVE_LEVEL_ENUM,
}, },

View File

@@ -64,6 +64,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
{ {
dataIndex: 'id', dataIndex: 'id',
title: 'ID', title: 'ID',
width: 80,
}, },
{ {
dataIndex: 'name', dataIndex: 'name',
@@ -72,6 +73,8 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
{ {
dataIndex: 'alias', dataIndex: 'alias',
title: '别名', title: '别名',
width: 300,
ellipsis: true,
search: false, search: false,
}, },
{ {
@@ -81,6 +84,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
{ {
dataIndex: 'sensitiveLevel', dataIndex: 'sensitiveLevel',
title: '敏感度', title: '敏感度',
width: 80,
valueEnum: SENSITIVE_LEVEL_ENUM, valueEnum: SENSITIVE_LEVEL_ENUM,
}, },
{ {
@@ -93,24 +97,14 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
title: '描述', title: '描述',
search: false, search: false,
}, },
{ // {
dataIndex: 'type', // dataIndex: 'type',
title: '指标类型', // title: '指标类型',
valueEnum: { // valueEnum: {
ATOMIC: '原子指标', // ATOMIC: '原子指标',
DERIVED: '衍生指标', // DERIVED: '衍生指标',
}, // },
// render: (type: any) => {
// switch (type) {
// case 'ATOMIC':
// return '原子指标';
// case 'DERIVED':
// return '衍生指标';
// default:
// return '未知';
// }
// }, // },
},
{ {
dataIndex: 'updatedAt', dataIndex: 'updatedAt',

View File

@@ -3,19 +3,22 @@ import type { ForwardRefRenderFunction } from 'react';
import { message, Form, Input, Select, Button, Space } from 'antd'; import { message, Form, Input, Select, Button, Space } from 'antd';
import { saveDatabase, testDatabaseConnect } from '../../service'; import { saveDatabase, testDatabaseConnect } from '../../service';
import { formLayout } from '@/components/FormHelper/utils'; import { formLayout } from '@/components/FormHelper/utils';
import SelectTMEPerson from '@/components/SelectTMEPerson';
import { ISemantic } from '../../data';
import styles from '../style.less'; import styles from '../style.less';
type Props = { type Props = {
domainId: number; domainId?: number;
dataBaseConfig: any; dataBaseConfig?: ISemantic.IDatabaseItem;
onSubmit: (params?: any) => void; hideSubmitBtn?: boolean;
onSubmit?: (params?: any) => void;
}; };
const FormItem = Form.Item; const FormItem = Form.Item;
const TextArea = Input.TextArea; const TextArea = Input.TextArea;
const DatabaseCreateForm: ForwardRefRenderFunction<any, Props> = ( const DatabaseCreateForm: ForwardRefRenderFunction<any, Props> = (
{ domainId, dataBaseConfig, onSubmit }, { domainId, dataBaseConfig, onSubmit, hideSubmitBtn = false },
ref, ref,
) => { ) => {
const [form] = Form.useForm(); const [form] = Form.useForm();
@@ -25,8 +28,10 @@ const DatabaseCreateForm: ForwardRefRenderFunction<any, Props> = (
useEffect(() => { useEffect(() => {
form.resetFields(); form.resetFields();
if (dataBaseConfig) {
form.setFieldsValue({ ...dataBaseConfig }); form.setFieldsValue({ ...dataBaseConfig });
setSelectedDbType(dataBaseConfig?.type); setSelectedDbType(dataBaseConfig?.type);
}
}, [dataBaseConfig]); }, [dataBaseConfig]);
const getFormValidateFields = async () => { const getFormValidateFields = async () => {
@@ -35,11 +40,14 @@ const DatabaseCreateForm: ForwardRefRenderFunction<any, Props> = (
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
getFormValidateFields, getFormValidateFields,
saveDatabaseConfig,
testDatabaseConnection,
})); }));
const saveDatabaseConfig = async () => { const saveDatabaseConfig = async () => {
const values = await form.validateFields(); const values = await form.validateFields();
const { code, msg } = await saveDatabase({ const { code, msg } = await saveDatabase({
...dataBaseConfig,
...values, ...values,
domainId, domainId,
}); });
@@ -115,6 +123,23 @@ const DatabaseCreateForm: ForwardRefRenderFunction<any, Props> = (
</FormItem> </FormItem>
</> </>
)} )}
{selectedDbType === 'mysql' && (
<FormItem
name="version"
label="数据库版本"
rules={[{ required: true, message: '请选择数据库版本' }]}
>
<Select
style={{ width: '100%' }}
placeholder="请选择数据库版本"
options={[
{ value: '5.7', label: '5.7' },
{ value: '8.0', label: '8.0' },
]}
/>
</FormItem>
)}
<FormItem <FormItem
name="username" name="username"
label="用户名" label="用户名"
@@ -128,12 +153,21 @@ const DatabaseCreateForm: ForwardRefRenderFunction<any, Props> = (
<FormItem name="database" label="数据库名称"> <FormItem name="database" label="数据库名称">
<Input placeholder="请输入数据库名称" /> <Input placeholder="请输入数据库名称" />
</FormItem> </FormItem>
<FormItem name="version" label="数据库版本"> <FormItem
<Input placeholder="请输入数据库版本" /> name="admins"
label="管理员"
// rules={[{ required: true, message: '请设定数据库连接管理者' }]}
>
<SelectTMEPerson placeholder="请邀请团队成员" />
</FormItem> </FormItem>
<FormItem name="viewers" label="使用者">
<SelectTMEPerson placeholder="请邀请团队成员" />
</FormItem>
<FormItem name="description" label="描述"> <FormItem name="description" label="描述">
<TextArea placeholder="请输入数据库描述" style={{ height: 100 }} /> <TextArea placeholder="请输入数据库描述" style={{ height: 100 }} />
</FormItem> </FormItem>
{!hideSubmitBtn && (
<FormItem> <FormItem>
<Space> <Space>
<Button <Button
@@ -156,6 +190,7 @@ const DatabaseCreateForm: ForwardRefRenderFunction<any, Props> = (
</Button> </Button>
</Space> </Space>
</FormItem> </FormItem>
)}
</Form> </Form>
</> </>
); );

View File

@@ -0,0 +1,79 @@
import React, { useState, useRef } from 'react';
import { Button, Modal, Space } from 'antd';
import DatabaseCreateForm from './DatabaseCreateForm';
import { ISemantic } from '../../data';
export type CreateFormProps = {
onCancel: () => void;
databaseItem?: ISemantic.IDatabaseItem;
open: boolean;
onSubmit: (values?: any) => void;
};
const DatabaseSettingModal: React.FC<CreateFormProps> = ({
onCancel,
databaseItem,
open,
onSubmit,
}) => {
const [testLoading, setTestLoading] = useState<boolean>(false);
const createFormRef = useRef<any>({});
const handleTestConnection = async () => {
setTestLoading(true);
await createFormRef.current.testDatabaseConnection();
setTestLoading(false);
};
const renderFooter = () => {
return (
<>
<Space>
<Button
type="primary"
loading={testLoading}
onClick={() => {
handleTestConnection();
}}
>
</Button>
<Button
type="primary"
onClick={() => {
createFormRef.current.saveDatabaseConfig();
}}
>
</Button>
</Space>
</>
);
};
return (
<Modal
width={1200}
destroyOnClose
title="数据库连接设置"
style={{ top: 48 }}
maskClosable={false}
open={open}
footer={renderFooter()}
onCancel={onCancel}
>
<DatabaseCreateForm
hideSubmitBtn={true}
ref={createFormRef}
dataBaseConfig={databaseItem}
onSubmit={() => {
onSubmit?.();
}}
/>
</Modal>
);
};
export default DatabaseSettingModal;

View File

@@ -0,0 +1,162 @@
import type { ActionType, ProColumns } from '@ant-design/pro-table';
import ProTable from '@ant-design/pro-table';
import { message, Button, Space, Popconfirm } from 'antd';
import React, { useRef, useState, useEffect } from 'react';
import DatabaseSettingModal from './DatabaseSettingModal';
import { ISemantic } from '../../data';
import { getDatabaseList, deleteDatabase } from '../../service';
import moment from 'moment';
import styles from '../style.less';
type Props = {};
const DatabaseTable: React.FC<Props> = ({}) => {
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
const [databaseItem, setDatabaseItem] = useState<ISemantic.IDatabaseItem>();
const [dataBaseList, setDataBaseList] = useState<any[]>([]);
const actionRef = useRef<ActionType>();
const queryDatabaseList = async () => {
const { code, data, msg } = await getDatabaseList();
if (code === 200) {
setDataBaseList(data);
} else {
message.error(msg);
}
};
useEffect(() => {
queryDatabaseList();
}, []);
const columns: ProColumns[] = [
{
dataIndex: 'id',
title: 'ID',
width: 80,
},
{
dataIndex: 'name',
title: '连接名称',
},
{
dataIndex: 'type',
title: '类型',
search: false,
},
{
dataIndex: 'createdBy',
title: '创建人',
search: false,
},
{
dataIndex: 'description',
title: '描述',
search: false,
},
{
dataIndex: 'updatedAt',
title: '更新时间',
search: false,
render: (value: any) => {
return value && value !== '-' ? moment(value).format('YYYY-MM-DD HH:mm:ss') : '-';
},
},
{
title: '操作',
dataIndex: 'x',
valueType: 'option',
render: (_, record) => {
if (!record.hasEditPermission) {
return <></>;
}
return (
<Space>
<a
key="dimensionEditBtn"
onClick={() => {
setDatabaseItem(record);
setCreateModalVisible(true);
}}
>
</a>
<Popconfirm
title="确认删除?"
okText="是"
cancelText="否"
onConfirm={async () => {
const { code, msg } = await deleteDatabase(record.id);
if (code === 200) {
setDatabaseItem(undefined);
queryDatabaseList();
} else {
message.error(msg);
}
}}
>
<a
key="dimensionDeleteEditBtn"
onClick={() => {
setDatabaseItem(record);
}}
>
</a>
</Popconfirm>
</Space>
);
},
},
];
return (
<div style={{ margin: 20 }}>
<ProTable
className={`${styles.classTable} ${styles.classTableSelectColumnAlignLeft}`}
actionRef={actionRef}
rowKey="id"
columns={columns}
dataSource={dataBaseList}
search={false}
tableAlertRender={() => {
return false;
}}
size="small"
options={{ reload: false, density: false, fullScreen: false }}
toolBarRender={() => [
<Button
key="create"
type="primary"
onClick={() => {
setDatabaseItem(undefined);
setCreateModalVisible(true);
}}
>
</Button>,
]}
/>
{createModalVisible && (
<DatabaseSettingModal
open={createModalVisible}
databaseItem={databaseItem}
onCancel={() => {
setCreateModalVisible(false);
}}
onSubmit={() => {
setCreateModalVisible(false);
queryDatabaseList();
}}
/>
)}
</div>
);
};
export default DatabaseTable;

View File

@@ -1,12 +1,13 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Button, Form, Input, Modal, Select } from 'antd'; import { Button, Form, Input, Modal, Select, Row, Col, Space, Tooltip } from 'antd';
import { SENSITIVE_LEVEL_OPTIONS } from '../constant'; import { SENSITIVE_LEVEL_OPTIONS } from '../constant';
import { formLayout } from '@/components/FormHelper/utils'; import { formLayout } from '@/components/FormHelper/utils';
import SqlEditor from '@/components/SqlEditor'; import SqlEditor from '@/components/SqlEditor';
import InfoTagList from './InfoTagList'; import InfoTagList from './InfoTagList';
import { ISemantic } from '../data'; import { ISemantic } from '../data';
import { createDimension, updateDimension } from '../service'; import { InfoCircleOutlined } from '@ant-design/icons';
// import DimensionValueSettingModal from './DimensionValueSettingModal'; import { createDimension, updateDimension, mockDimensionAlias } from '../service';
import { message } from 'antd'; import { message } from 'antd';
export type CreateFormProps = { export type CreateFormProps = {
@@ -37,8 +38,8 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
>([]); >([]);
const [form] = Form.useForm(); const [form] = Form.useForm();
const { setFieldsValue, resetFields } = form; const { setFieldsValue, resetFields } = form;
// const [dimensionValueSettingModalVisible, setDimensionValueSettingModalVisible] = const [llmLoading, setLlmLoading] = useState<boolean>(false);
// useState<boolean>(false);
const handleSubmit = async ( const handleSubmit = async (
isSilenceSubmit = false, isSilenceSubmit = false,
dimValueMaps?: ISemantic.IDimensionValueSettingItem[], dimValueMaps?: ISemantic.IDimensionValueSettingItem[],
@@ -48,6 +49,7 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
{ {
...fieldsValue, ...fieldsValue,
dimValueMaps: dimValueMaps || dimensionValueSettingList, dimValueMaps: dimValueMaps || dimensionValueSettingList,
alias: Array.isArray(fieldsValue.alias) ? fieldsValue.alias.join(',') : '',
}, },
isSilenceSubmit, isSilenceSubmit,
); );
@@ -75,7 +77,10 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
}; };
const setFormVal = () => { const setFormVal = () => {
setFieldsValue(dimensionItem); if (dimensionItem) {
const { alias } = dimensionItem;
setFieldsValue({ ...dimensionItem, alias: alias && alias.trim() ? alias.split(',') : [] });
}
}; };
useEffect(() => { useEffect(() => {
@@ -110,6 +115,24 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
); );
}; };
const generatorDimensionAlias = async () => {
const fieldsValue = await form.validateFields();
setLlmLoading(true);
const { code, data } = await mockDimensionAlias({
...dimensionItem,
...fieldsValue,
alias: fieldsValue.alias?.join(','),
});
setLlmLoading(false);
const formAlias = form.getFieldValue('alias');
setLlmLoading(false);
if (code === 200) {
form.setFieldValue('alias', Array.from(new Set([...formAlias, ...data])));
} else {
message.error('大语言模型解析异常');
}
};
const renderContent = () => { const renderContent = () => {
return ( return (
<> <>
@@ -131,7 +154,6 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
> >
<Input placeholder="名称不可重复" disabled={isEdit} /> <Input placeholder="名称不可重复" disabled={isEdit} />
</FormItem> </FormItem>
<FormItem <FormItem
hidden={isEdit} hidden={isEdit}
name="datasourceId" name="datasourceId"
@@ -146,8 +168,39 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
))} ))}
</Select> </Select>
</FormItem> </FormItem>
<FormItem name="alias" label="别名"> <FormItem label="别名">
<Input placeholder="多个别名用英文逗号隔开" /> <Row>
<Col flex="1 1 200px">
<FormItem name="alias" noStyle>
<Select
mode="tags"
placeholder="输入别名后回车确认,多别名输入、复制粘贴支持英文逗号自动分隔"
tokenSeparators={[',']}
maxTagCount={9}
/>
</FormItem>
</Col>
{isEdit && (
<Col flex="0 1 75px">
<Button
type="link"
size="small"
loading={llmLoading}
style={{ top: '2px' }}
onClick={() => {
generatorDimensionAlias();
}}
>
<Space>
<Tooltip title="智能填充将根据维度相关信息,使用大语言模型获取维度别名">
<InfoCircleOutlined />
</Tooltip>
</Space>
</Button>
</Col>
)}
</Row>
</FormItem> </FormItem>
<FormItem <FormItem
name="semanticType" name="semanticType"
@@ -185,16 +238,6 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
> >
<TextArea placeholder="请输入维度描述" /> <TextArea placeholder="请输入维度描述" />
</FormItem> </FormItem>
{/* <FormItem name="dimValueMaps" label="维度值设置">
<Button
type="primary"
onClick={() => {
setDimensionValueSettingModalVisible(true);
}}
>
设置
</Button>
</FormItem> */}
<FormItem <FormItem
name="expr" name="expr"
label="表达式" label="表达式"
@@ -223,22 +266,6 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
{renderContent()} {renderContent()}
</Form> </Form>
</Modal> </Modal>
{/* {dimensionValueSettingModalVisible && (
<DimensionValueSettingModal
dimensionValueSettingList={dimensionValueSettingList}
open={dimensionValueSettingModalVisible}
onCancel={() => {
setDimensionValueSettingModalVisible(false);
}}
onSubmit={(dimValueMaps) => {
if (isEdit) {
handleSubmit(true, dimValueMaps);
}
setDimensionValueSettingList(dimValueMaps);
setDimensionValueSettingModalVisible(false);
}}
/>
)} */}
</> </>
); );
}; };

View File

@@ -1,8 +1,9 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Button, Modal, message } from 'antd'; import { Button, Modal, message, Space, Tooltip } from 'antd';
import { InfoCircleOutlined } from '@ant-design/icons';
import { ISemantic } from '../data'; import { ISemantic } from '../data';
import CommonEditTable from './CommonEditTable'; import CommonEditTable from './CommonEditTable';
import { createDimension, updateDimension } from '../service'; import { updateDimension, mockDimensionValuesAlias } from '../service';
import { connect } from 'umi'; import { connect } from 'umi';
import type { StateType } from '../model'; import type { StateType } from '../model';
@@ -15,7 +16,7 @@ export type CreateFormProps = {
domainManger: StateType; domainManger: StateType;
}; };
type TableDataSource = { techName: string; bizName: string; alias: string }; type TableDataSource = { techName: string; bizName: string; alias?: string[] };
const DimensionInfoModal: React.FC<CreateFormProps> = ({ const DimensionInfoModal: React.FC<CreateFormProps> = ({
onCancel, onCancel,
@@ -28,16 +29,10 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
const [tableDataSource, setTableDataSource] = useState<TableDataSource[]>([]); const [tableDataSource, setTableDataSource] = useState<TableDataSource[]>([]);
const { selectDomainId } = domainManger; const { selectDomainId } = domainManger;
const [dimValueMaps, setDimValueMaps] = useState<ISemantic.IDimensionValueSettingItem[]>([]); const [dimValueMaps, setDimValueMaps] = useState<ISemantic.IDimensionValueSettingItem[]>([]);
const [llmLoading, setLlmLoading] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
const dataSource = dimensionValueSettingList.map((item) => { setTableDataSource(dimensionValueSettingList);
const { alias } = item;
return {
...item,
alias: Array.isArray(alias) ? alias.join(',') : '',
};
});
setTableDataSource(dataSource);
setDimValueMaps(dimensionValueSettingList); setDimValueMaps(dimensionValueSettingList);
}, [dimensionValueSettingList]); }, [dimensionValueSettingList]);
@@ -62,9 +57,37 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
message.error(msg); message.error(msg);
}; };
const generatorDimensionValue = async () => {
setLlmLoading(true);
const { code, data } = await mockDimensionValuesAlias({ ...dimensionItem });
setLlmLoading(false);
if (code === 200) {
if (Array.isArray(data)) {
setDimValueMaps([...dimValueMaps, ...data]);
setTableDataSource([...tableDataSource, ...data]);
}
} else {
message.error('大语言模型解析异常');
}
};
const renderFooter = () => { const renderFooter = () => {
return ( return (
<> <>
<Button
type="primary"
loading={llmLoading}
onClick={() => {
generatorDimensionValue();
}}
>
<Space>
<Tooltip title="智能填充将根据维度相关信息,使用大语言模型获取可能被使用的维度值">
<InfoCircleOutlined />
</Tooltip>
</Space>
</Button>
<Button onClick={onCancel}></Button> <Button onClick={onCancel}></Button>
<Button <Button
type="primary" type="primary"
@@ -83,6 +106,7 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
title: '技术名称', title: '技术名称',
dataIndex: 'techName', dataIndex: 'techName',
width: 200, width: 200,
tooltip: '数据库中存储的维度值数据。 比如数据库中维度平台的维度值有kw、qy等',
formItemProps: { formItemProps: {
fieldProps: { fieldProps: {
placeholder: '请填写技术名称', placeholder: '请填写技术名称',
@@ -100,6 +124,8 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
title: '业务名称', title: '业务名称',
dataIndex: 'bizName', dataIndex: 'bizName',
width: 200, width: 200,
tooltip:
'查询完成后,最终返回给用户的维度值信息。比如将技术名称kw转换成酷我平台,最终返回给用户是酷我平台',
fieldProps: { fieldProps: {
placeholder: '请填写业务名称', placeholder: '请填写业务名称',
}, },
@@ -116,15 +142,21 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
{ {
title: '别名', title: '别名',
dataIndex: 'alias', dataIndex: 'alias',
valueType: 'select',
width: 500,
tooltip:
'解析用户查询意图时,支持别名到技术名称的转换。比如用户输入kw、kuwo、酷我,完成设置后,都可以将其转换成技术名称kw',
fieldProps: { fieldProps: {
placeholder: '多个别名用英文逗号隔开', placeholder: '输入别名后回车确认,多别名输入、复制粘贴支持英文逗号自动分隔',
mode: 'tags',
maxTagCount: 5,
tokenSeparators: [','],
}, },
}, },
]; ];
return ( return (
<Modal <Modal
width={1000} width={1200}
destroyOnClose destroyOnClose
title="维度值设置" title="维度值设置"
style={{ top: 48 }} style={{ top: 48 }}
@@ -140,7 +172,7 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
const dimValueMaps = tableData.map((item: TableDataSource) => { const dimValueMaps = tableData.map((item: TableDataSource) => {
return { return {
...item, ...item,
alias: item.alias ? `${item.alias}`.split(',') : [], // alias: item.alias ? `${item.alias}`.split(',') : [],
}; };
}); });

View File

@@ -6,14 +6,16 @@ import ClassDataSourceTable from './ClassDataSourceTable';
import ClassDimensionTable from './ClassDimensionTable'; import ClassDimensionTable from './ClassDimensionTable';
import ClassMetricTable from './ClassMetricTable'; import ClassMetricTable from './ClassMetricTable';
import PermissionSection from './Permission/PermissionSection'; import PermissionSection from './Permission/PermissionSection';
import DatabaseSection from './Database/DatabaseSection'; // import DatabaseSection from './Database/DatabaseSection';
import EntitySettingSection from './Entity/EntitySettingSection'; import EntitySettingSection from './Entity/EntitySettingSection';
import ChatSettingSection from '../ChatSetting/ChatSettingSection';
import OverView from './OverView'; import OverView from './OverView';
import styles from './style.less'; import styles from './style.less';
import type { StateType } from '../model'; import type { StateType } from '../model';
import { LeftOutlined } from '@ant-design/icons'; import { LeftOutlined } from '@ant-design/icons';
import { ISemantic } from '../data'; import { ISemantic } from '../data';
import SemanticGraphCanvas from '../SemanticGraphCanvas'; import SemanticGraphCanvas from '../SemanticGraphCanvas';
import RecommendedQuestionsSection from '../components/Entity/RecommendedQuestionsSection';
import type { Dispatch } from 'umi'; import type { Dispatch } from 'umi';
@@ -50,11 +52,11 @@ const DomainManagerTab: React.FC<Props> = ({
/> />
), ),
}, },
{ // {
label: '数据库', // label: '数据库',
key: 'dataBase', // key: 'dataBase',
children: <DatabaseSection />, // children: <DatabaseSection />,
}, // },
{ {
label: '权限管理', label: '权限管理',
key: 'permissonSetting', key: 'permissonSetting',
@@ -93,13 +95,27 @@ const DomainManagerTab: React.FC<Props> = ({
key: 'entity', key: 'entity',
children: <EntitySettingSection />, children: <EntitySettingSection />,
}, },
{ {
label: '权限管理', label: '权限管理',
key: 'permissonSetting', key: 'permissonSetting',
children: <PermissionSection permissionTarget={'model'} />, children: <PermissionSection permissionTarget={'model'} />,
}, },
]; {
label: '问答设置',
key: 'chatSetting',
children: <ChatSettingSection />,
},
{
label: '推荐问题',
key: 'recommendedQuestions',
children: <RecommendedQuestionsSection />,
},
].filter((item) => {
if (window.RUNNING_ENV === 'semantic') {
return !['chatSetting', 'recommendedQuestions'].includes(item.key);
}
return item;
});
return ( return (
<> <>

View File

@@ -12,6 +12,7 @@ import { ISemantic } from '../data';
type Props = { type Props = {
value?: any; value?: any;
width?: number | string;
onChange?: () => void; onChange?: () => void;
treeSelectProps?: Record<string, any>; treeSelectProps?: Record<string, any>;
domainList: ISemantic.IDomainItem[]; domainList: ISemantic.IDomainItem[];
@@ -20,6 +21,7 @@ type Props = {
const DomainTreeSelect: FC<Props> = ({ const DomainTreeSelect: FC<Props> = ({
value, value,
width = 300,
onChange, onChange,
treeSelectProps = {}, treeSelectProps = {},
domainList, domainList,
@@ -51,7 +53,12 @@ const DomainTreeSelect: FC<Props> = ({
}, [domainList]); }, [domainList]);
return ( return (
<div className={styles.domainTreeSelect}> <div
className={styles.domainTreeSelect}
style={{
width,
}}
>
<TreeSelect <TreeSelect
showSearch showSearch
style={{ width: '100%' }} style={{ width: '100%' }}

View File

@@ -53,7 +53,7 @@ const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
const saveEntity = async () => { const saveEntity = async () => {
const values = await form.validateFields(); const values = await form.validateFields();
const { name } = values; const { name = '' } = values;
const { code, msg, data } = await updateModel({ const { code, msg, data } = await updateModel({
...modelData, ...modelData,
entity: { entity: {

View File

@@ -1,5 +1,5 @@
import { message, Space } from 'antd'; import { message, Space } from 'antd';
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect } from 'react';
import type { Dispatch } from 'umi'; import type { Dispatch } from 'umi';
import { connect } from 'umi'; import { connect } from 'umi';
import type { StateType } from '../../model'; import type { StateType } from '../../model';

View File

@@ -6,18 +6,25 @@ import {
Steps, Steps,
Input, Input,
Select, Select,
Radio,
Switch, Switch,
InputNumber, InputNumber,
message, message,
Result, Result,
Row,
Col,
Space,
Tooltip,
} from 'antd'; } from 'antd';
import { InfoCircleOutlined } from '@ant-design/icons';
import MetricMeasuresFormTable from './MetricMeasuresFormTable'; import MetricMeasuresFormTable from './MetricMeasuresFormTable';
import { SENSITIVE_LEVEL_OPTIONS } from '../constant'; import { SENSITIVE_LEVEL_OPTIONS } from '../constant';
import { formLayout } from '@/components/FormHelper/utils'; import { formLayout } from '@/components/FormHelper/utils';
import FormItemTitle from '@/components/FormHelper/FormItemTitle'; import FormItemTitle from '@/components/FormHelper/FormItemTitle';
import styles from './style.less'; import styles from './style.less';
import { getMeasureListByModelId } from '../service'; import { getMeasureListByModelId } from '../service';
import { creatExprMetric, updateExprMetric } from '../service'; import TableTitleTooltips from '../components/TableTitleTooltips';
import { creatExprMetric, updateExprMetric, mockMetricAlias } from '../service';
import { ISemantic } from '../data'; import { ISemantic } from '../data';
import { history } from 'umi'; import { history } from 'umi';
@@ -50,7 +57,11 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
const formValRef = useRef({} as any); const formValRef = useRef({} as any);
const [form] = Form.useForm(); const [form] = Form.useForm();
const updateFormVal = (val: any) => { const updateFormVal = (val: any) => {
formValRef.current = val; const formVal = {
...formValRef.current,
...val,
};
formValRef.current = formVal;
}; };
const [classMeasureList, setClassMeasureList] = useState<ISemantic.IMeasure[]>([]); const [classMeasureList, setClassMeasureList] = useState<ISemantic.IMeasure[]>([]);
@@ -60,7 +71,9 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
const [exprSql, setExprSql] = useState<string>(''); const [exprSql, setExprSql] = useState<string>('');
const [isPercentState, setIsPercentState] = useState<boolean>(false); const [isPercentState, setIsPercentState] = useState<boolean>(false);
const [isDecimalState, setIsDecimalState] = useState<boolean>(false);
const [hasMeasuresState, setHasMeasuresState] = useState<boolean>(true); const [hasMeasuresState, setHasMeasuresState] = useState<boolean>(true);
const [llmLoading, setLlmLoading] = useState<boolean>(false);
const forward = () => setCurrentStep(currentStep + 1); const forward = () => setCurrentStep(currentStep + 1);
const backward = () => setCurrentStep(currentStep - 1); const backward = () => setCurrentStep(currentStep - 1);
@@ -93,7 +106,6 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
expr: exprSql, expr: exprSql,
measures: exprTypeParamsState, measures: exprTypeParamsState,
}, },
dataFormatType: isPercentState ? 'percent' : '',
}; };
updateFormVal(submitForm); updateFormVal(submitForm);
if (currentStep < 1) { if (currentStep < 1) {
@@ -116,14 +128,16 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
alias, alias,
} = metricItem as any; } = metricItem as any;
const isPercent = dataFormatType === 'percent'; const isPercent = dataFormatType === 'percent';
const isDecimal = dataFormatType === 'decimal';
const initValue = { const initValue = {
id, id,
name, name,
bizName, bizName,
sensitiveLevel, sensitiveLevel,
description, description,
isPercent, // isPercent,
alias, dataFormatType,
alias: alias && alias.trim() ? alias.split(',') : [],
dataFormat: dataFormat || { dataFormat: dataFormat || {
decimalPlaces: 2, decimalPlaces: 2,
needMultiply100: false, needMultiply100: false,
@@ -138,6 +152,7 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
setExprTypeParamsState(typeParams.measures); setExprTypeParamsState(typeParams.measures);
setExprSql(typeParams.expr); setExprSql(typeParams.expr);
setIsPercentState(isPercent); setIsPercentState(isPercent);
setIsDecimalState(isDecimal);
}; };
useEffect(() => { useEffect(() => {
@@ -151,11 +166,15 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
modelId, modelId,
...fieldsValue, ...fieldsValue,
}; };
const { typeParams } = queryParams; const { typeParams, alias, dataFormatType } = queryParams;
queryParams.alias = Array.isArray(alias) ? alias.join(',') : '';
if (!typeParams?.expr) { if (!typeParams?.expr) {
message.error('请输入度量表达式'); message.error('请输入度量表达式');
return; return;
} }
if (!dataFormatType) {
delete queryParams.dataFormat;
}
if (!(Array.isArray(typeParams?.measures) && typeParams.measures.length > 0)) { if (!(Array.isArray(typeParams?.measures) && typeParams.measures.length > 0)) {
message.error('请添加一个度量'); message.error('请添加一个度量');
return; return;
@@ -173,6 +192,18 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
message.error(msg); message.error(msg);
}; };
const generatorMetricAlias = async () => {
setLlmLoading(true);
const { code, data } = await mockMetricAlias({ ...metricItem });
const formAlias = form.getFieldValue('alias');
setLlmLoading(false);
if (code === 200) {
form.setFieldValue('alias', Array.from(new Set([...formAlias, ...data])));
} else {
message.error('大语言模型解析异常');
}
};
const renderContent = () => { const renderContent = () => {
if (currentStep === 1) { if (currentStep === 1) {
return ( return (
@@ -212,8 +243,39 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
> >
<Input placeholder="名称不可重复" disabled={isEdit} /> <Input placeholder="名称不可重复" disabled={isEdit} />
</FormItem> </FormItem>
<FormItem name="alias" label="别名"> <FormItem label="别名">
<Input placeholder="多个别名用英文逗号隔开" /> <Row>
<Col flex="1 1 200px">
<FormItem name="alias" noStyle>
<Select
mode="tags"
placeholder="输入别名后回车确认,多别名输入、复制粘贴支持英文逗号自动分隔"
tokenSeparators={[',']}
maxTagCount={9}
/>
</FormItem>
</Col>
{isEdit && (
<Col flex="0 1 75px">
<Button
type="link"
loading={llmLoading}
size="small"
style={{ top: '2px' }}
onClick={() => {
generatorMetricAlias();
}}
>
<Space>
<Tooltip title="智能填充将根据指标相关信息,使用大语言模型获取指标别名">
<InfoCircleOutlined />
</Tooltip>
</Space>
</Button>
</Col>
)}
</Row>
</FormItem> </FormItem>
<FormItem <FormItem
name="sensitiveLevel" name="sensitiveLevel"
@@ -230,12 +292,47 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
</FormItem> </FormItem>
<FormItem <FormItem
name="description" name="description"
label="指标描述" label={
rules={[{ required: true, message: '请输入指标描述' }]} <TableTitleTooltips
title="业务口径"
overlayInnerStyle={{ width: 600 }}
tooltips={
<>
<p>
使使
</p>
<p>1. </p>
<p>2. </p>
<p>3. 使使</p>
<p>4. </p>
<p>
便
</p>
</>
}
/>
}
rules={[{ required: true, message: '请输入业务口径' }]}
> >
<TextArea placeholder="请输入指标描述" /> <TextArea placeholder="请输入业务口径" />
</FormItem> </FormItem>
<FormItem <FormItem
label={
<FormItemTitle
title={'数据格式化'}
// subTitle={'开启后指标数据展示时会根据配置进行格式化如0.02 -> 2%'}
/>
}
name="dataFormatType"
>
<Radio.Group buttonStyle="solid" size="middle">
<Radio.Button value=""></Radio.Button>
<Radio.Button value="decimal"></Radio.Button>
<Radio.Button value="percent"></Radio.Button>
</Radio.Group>
</FormItem>
{/* <FormItem
label={ label={
<FormItemTitle <FormItemTitle
title={'是否展示为百分比'} title={'是否展示为百分比'}
@@ -250,20 +347,24 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
form.setFieldValue(['dataFormat', 'needMultiply100'], checked); form.setFieldValue(['dataFormat', 'needMultiply100'], checked);
}} }}
/> />
</FormItem> </FormItem> */}
{isPercentState && ( {(isPercentState || isDecimalState) && (
<>
<FormItem <FormItem
label={ label={
<FormItemTitle <FormItemTitle
title={'小数位数'} title={'小数位数'}
subTitle={'对小数位数进行设置如保留两位0.021252 -> 2.12%'} subTitle={`对小数位数进行设置如保留两位0.021252 -> 2.12${
isPercentState ? '%' : ''
}`}
/> />
} }
name={['dataFormat', 'decimalPlaces']} name={['dataFormat', 'decimalPlaces']}
> >
<InputNumber placeholder="请输入需要保留小数位数" style={{ width: '300px' }} /> <InputNumber placeholder="请输入需要保留小数位数" style={{ width: '300px' }} />
</FormItem> </FormItem>
)}
{isPercentState && (
<>
<FormItem <FormItem
label={ label={
<FormItemTitle <FormItemTitle
@@ -331,11 +432,24 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
form={form} form={form}
initialValues={{ initialValues={{
...formValRef.current, ...formValRef.current,
dataFormatType: '',
}} }}
onValuesChange={(value) => { onValuesChange={(value, values: any) => {
const { isPercent } = value; const { isPercent, dataFormatType } = values;
if (isPercent !== undefined) { // if (isPercent !== undefined) {
setIsPercentState(isPercent); // setIsPercentState(isPercent);
// }
if (dataFormatType === 'percent') {
setIsPercentState(true);
setIsDecimalState(false);
}
if (dataFormatType === 'decimal') {
setIsPercentState(false);
setIsDecimalState(true);
}
if (!dataFormatType) {
setIsPercentState(false);
setIsDecimalState(false);
} }
}} }}
className={styles.form} className={styles.form}
@@ -352,7 +466,7 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
type="primary" type="primary"
key="console" key="console"
onClick={() => { onClick={() => {
history.replace(`/semanticModel/${domainId}/${modelId}/dataSource`); history.replace(`/model/${domainId}/${modelId}/dataSource`);
onCancel?.(); onCancel?.();
}} }}
> >

View File

@@ -1,18 +1,19 @@
import { Space, Tooltip } from 'antd'; import { Space, Tooltip } from 'antd';
import React from 'react'; import React, { ReactNode } from 'react';
import { ExclamationCircleOutlined } from '@ant-design/icons'; import { ExclamationCircleOutlined } from '@ant-design/icons';
type Props = { type Props = {
title: string; title: string;
tooltips: string; tooltips: string | ReactNode;
[key: string]: any;
}; };
const TableTitleTooltips: React.FC<Props> = ({ title, tooltips }) => { const TableTitleTooltips: React.FC<Props> = ({ title, tooltips, ...rest }) => {
return ( return (
<> <>
<Space> <Space>
<span>{title}</span> <span>{title}</span>
<Tooltip title={tooltips}> <Tooltip title={tooltips} {...rest}>
<ExclamationCircleOutlined /> <ExclamationCircleOutlined />
</Tooltip> </Tooltip>
</Space> </Space>

View File

@@ -31,16 +31,34 @@
.title { .title {
margin-bottom: 0; margin-bottom: 0;
padding: 20px; // padding: 20px;
font-size: 20px; font-size: 20px;
line-height: 34px; line-height: 34px;
border-bottom: 1px solid #d9d9d9; border-bottom: 1px solid #d9d9d9;
display: flex;
}
.navContainer {
padding: 20px;
padding-left: 30px;
}
.backBtn {
background-color: #f8f8f8;
padding: 5px;
color: #b0b4bc;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
&:hover {
background-color: #7599e5;
color: #fff;
}
} }
.tab { .tab {
:global { :global {
.ant-tabs-tab-btn { .ant-tabs-tab-btn {
font-size: 16px !important; font-size: 16px;
} }
.ant-tabs-nav-wrap { .ant-tabs-nav-wrap {
padding: 0 20px; padding: 0 20px;
@@ -50,6 +68,16 @@
} }
} }
} }
.chatSettingSectionTab {
:global {
.ant-tabs-tab-btn {
font-size: 14px;
}
.ant-tabs-nav-wrap {
padding: 0;
}
}
}
.mainTip { .mainTip {
padding: 20px; padding: 20px;
@@ -60,9 +88,7 @@
padding: 0 !important; padding: 0 !important;
} }
.ant-tabs-content-holder { .ant-tabs-content-holder {
margin-top: 20px; padding-top: 20px;
// overflow: scroll;
// height: calc(100vh - 175px);
} }
} }
@@ -96,7 +122,7 @@
} }
.domainTreeSelect { .domainTreeSelect {
width: 300px; // width: 300px;
} }
.domainList { .domainList {

View File

@@ -197,6 +197,28 @@ export declare namespace ISemantic {
datasource: IDataSourceItem; datasource: IDataSourceItem;
} }
type IDomainSchemaRelaList = IDomainSchemaRelaItem[]; type IDomainSchemaRelaList = IDomainSchemaRelaItem[];
interface IDatabaseItem {
createdBy?: string;
updatedBy?: string;
createdAt?: string;
updatedAt?: string;
id: number;
name: string;
admins: string[];
type: string;
url: string;
username: string;
password: string;
version: string;
hasEditPermission: boolean;
hasUsePermission: boolean;
host: string;
port: string;
database?: string;
description?: string;
}
type IDatabaseItemList = IDatabaseItem[];
} }
export declare namespace IChatConfig { export declare namespace IChatConfig {

View File

@@ -1,7 +1,7 @@
import type { Reducer, Effect } from 'umi'; import type { Reducer, Effect } from 'umi';
import { message } from 'antd'; import { message } from 'antd';
import { ISemantic } from './data'; import { ISemantic } from './data';
import { getDimensionList, queryMetric, excuteSql, getDatabaseByDomainId } from './service'; import { getDimensionList, queryMetric, excuteSql, getDatabaseList } from './service';
export type StateType = { export type StateType = {
current: number; current: number;
@@ -14,7 +14,7 @@ export type StateType = {
metricList: ISemantic.IMetricList; metricList: ISemantic.IMetricList;
searchParams: Record<string, any>; searchParams: Record<string, any>;
dataBaseResultColsMap: any; dataBaseResultColsMap: any;
dataBaseConfig: any; databaseConfigList: any[];
domainData?: ISemantic.IDomainItem; domainData?: ISemantic.IDomainItem;
modelData?: ISemantic.IDomainItem; modelData?: ISemantic.IDomainItem;
domainList: ISemantic.IDomainItem[]; domainList: ISemantic.IDomainItem[];
@@ -27,7 +27,7 @@ export type ModelType = {
queryDimensionList: Effect; queryDimensionList: Effect;
queryMetricList: Effect; queryMetricList: Effect;
queryDataBaseExcuteSql: Effect; queryDataBaseExcuteSql: Effect;
queryDatabaseByDomainId: Effect; queryDatabaseList: Effect;
}; };
reducers: { reducers: {
setSelectDomain: Reducer<StateType>; setSelectDomain: Reducer<StateType>;
@@ -36,7 +36,7 @@ export type ModelType = {
setPagination: Reducer<StateType>; setPagination: Reducer<StateType>;
setDimensionList: Reducer<StateType>; setDimensionList: Reducer<StateType>;
setDataBaseScriptColumn: Reducer<StateType>; setDataBaseScriptColumn: Reducer<StateType>;
setDataBaseConfig: Reducer<StateType>; setDatabaseConfigList: Reducer<StateType>;
setMetricList: Reducer<StateType>; setMetricList: Reducer<StateType>;
reset: Reducer<StateType>; reset: Reducer<StateType>;
}; };
@@ -55,7 +55,8 @@ export const defaultState: StateType = {
metricList: [], metricList: [],
domainData: undefined, domainData: undefined,
dataBaseResultColsMap: {}, dataBaseResultColsMap: {},
dataBaseConfig: {}, databaseConfigList: [],
// dataBaseConfig: {},
domainList: [], domainList: [],
}; };
@@ -108,13 +109,12 @@ const Model: ModelType = {
message.error(msg); message.error(msg);
} }
}, },
*queryDatabaseByDomainId({ payload }, { call, put }) { *queryDatabaseList({}, { call, put }) {
const domainId = payload.domainId; const { code, data, msg } = yield call(getDatabaseList);
const { code, data, msg } = yield call(getDatabaseByDomainId, domainId);
if (code === 200) { if (code === 200) {
yield put({ yield put({
type: 'setDataBaseConfig', type: 'setDatabaseConfigList',
payload: { dataBaseConfig: data }, payload: { databaseConfigList: data },
}); });
} else { } else {
message.error(msg); message.error(msg);
@@ -171,7 +171,7 @@ const Model: ModelType = {
}, },
}; };
}, },
setDataBaseConfig(state = defaultState, action) { setDatabaseConfigList(state = defaultState, action) {
return { return {
...state, ...state,
...action.payload, ...action.payload,

View File

@@ -72,6 +72,18 @@ export function updateDimension(data: any): Promise<any> {
}); });
} }
export function mockDimensionAlias(data: any): Promise<any> {
return request.post(`${process.env.API_BASE_URL}dimension/mockDimensionAlias`, {
data,
});
}
export function mockDimensionValuesAlias(data: any): Promise<any> {
return request.post(`${process.env.API_BASE_URL}dimension/mockDimensionValuesAlias`, {
data,
});
}
export function queryMetric(data: any): Promise<any> { export function queryMetric(data: any): Promise<any> {
const { domainId, modelId } = data; const { domainId, modelId } = data;
const queryParams = { const queryParams = {
@@ -101,6 +113,12 @@ export function updateExprMetric(data: any): Promise<any> {
}); });
} }
export function mockMetricAlias(data: any): Promise<any> {
return request.post(`${process.env.API_BASE_URL}metric/mockMetricAlias`, {
data,
});
}
export function getMeasureListByModelId(modelId: number): Promise<any> { export function getMeasureListByModelId(modelId: number): Promise<any> {
return request.get(`${process.env.API_BASE_URL}datasource/getMeasureListOfModel/${modelId}`); return request.get(`${process.env.API_BASE_URL}datasource/getMeasureListOfModel/${modelId}`);
} }
@@ -216,12 +234,6 @@ export function deleteDatasourceRela(domainId: any): Promise<any> {
}); });
} }
export function getDatabaseByDomainId(domainId: number): Promise<any> {
return request(`${process.env.API_BASE_URL}database/getDatabaseByDomainId/${domainId}`, {
method: 'GET',
});
}
export function getDomainSchemaRela(domainId: number): Promise<any> { export function getDomainSchemaRela(domainId: number): Promise<any> {
return request(`${process.env.API_BASE_URL}viewInfo/getDomainSchemaRela/${domainId}`, { return request(`${process.env.API_BASE_URL}viewInfo/getDomainSchemaRela/${domainId}`, {
method: 'GET', method: 'GET',
@@ -240,6 +252,18 @@ export type SaveDatabaseParams = {
description?: string; description?: string;
}; };
export function getDatabaseList(): Promise<any> {
return request(`${process.env.API_BASE_URL}database/getDatabaseList`, {
method: 'GET',
});
}
export function deleteDatabase(domainId: any): Promise<any> {
return request(`${process.env.API_BASE_URL}database/${domainId}`, {
method: 'DELETE',
});
}
export function saveDatabase(data: SaveDatabaseParams): Promise<any> { export function saveDatabase(data: SaveDatabaseParams): Promise<any> {
return request(`${process.env.API_BASE_URL}database/createOrUpdateDatabase`, { return request(`${process.env.API_BASE_URL}database/createOrUpdateDatabase`, {
method: 'POST', method: 'POST',
@@ -256,7 +280,7 @@ export function testDatabaseConnect(data: SaveDatabaseParams): Promise<any> {
type ExcuteSqlParams = { type ExcuteSqlParams = {
sql: string; sql: string;
modelId: number; id: number;
}; };
// 执行脚本 // 执行脚本