mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-10 19:51:00 +00:00
[improvement][project] global refactor , code format , support llm , support fuzzy detect ,support query filter and so on.
This commit is contained in:
@@ -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 = {
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
&-typing-bubble {
|
||||
width: fit-content;
|
||||
padding: 16px !important;
|
||||
padding: 8px 16px !important;
|
||||
}
|
||||
|
||||
&-text-bubble {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
const ENV_CONFIG = {};
|
||||
const ENV_CONFIG = {
|
||||
tmeAvatarUrl: 'http://tpp.tmeoa.com/photo/48/',
|
||||
};
|
||||
export default ENV_CONFIG;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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="数据源中文名"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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; // 暂存提示保存
|
||||
};
|
||||
|
||||
|
||||
16
webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/data.d.ts
vendored
Normal file
16
webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/data.d.ts
vendored
Normal 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>;
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 = [
|
||||
{
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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%'}
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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="类型"
|
||||
|
||||
@@ -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 || {},
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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();
|
||||
}}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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="敏感度"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user