Merge pull request #29 from sevenliu1896/master

[improvement][semantic-fe] Restructured the code to extract the quest…
This commit is contained in:
tristanliu
2023-08-15 10:43:14 +08:00
committed by GitHub
48 changed files with 1104 additions and 863 deletions

6
package-lock.json generated Normal file
View File

@@ -0,0 +1,6 @@
{
"name": "github-supersonic",
"lockfileVersion": 2,
"requires": true,
"packages": {}
}

View File

@@ -15,9 +15,9 @@ const ROUTES = [
envEnableList: [ENV_KEY.CHAT],
},
{
path: '/chatSetting/:modelId?/:menuKey?',
path: '/chatSetting/model/:domainId?/:modelId?/:menuKey?',
component: './SemanticModel/ChatSetting/ChatSetting',
name: 'chatSetting',
component: './SemanticModel/ChatSetting',
envEnableList: [ENV_KEY.CHAT],
},
{
@@ -27,9 +27,9 @@ const ROUTES = [
envEnableList: [ENV_KEY.CHAT],
},
{
path: '/semanticModel/:modelId?/:menuKey?',
path: '/semanticModel/model/:domainId?/:modelId?/:menuKey?',
component: './SemanticModel/DomainManager',
name: 'semanticModel',
component: './SemanticModel/ProjectManager',
envEnableList: [ENV_KEY.SEMANTIC],
},
{
@@ -47,10 +47,10 @@ const ROUTES = [
},
{
path: '/',
redirect: APP_TARGET === 'inner' ? '/semanticModel' : '/chat',
redirect: APP_TARGET === 'inner' ? '/semanticModel/model/' : '/chat',
envRedirect: {
[ENV_KEY.CHAT]: '/chat',
[ENV_KEY.SEMANTIC]: '/semanticModel',
[ENV_KEY.SEMANTIC]: '/semanticModel/model',
},
},
{

View File

@@ -1,15 +1,3 @@
export const EnumTransDbType = {
mysql: 'mysql',
tdw: 'tdw',
clickhouse: 'clickhouse',
kafka: 'kafka',
binlog: 'binlog',
hbase: 'hbase',
kugou_datahub: 'kugou_datahub',
aiting_datahub: 'aiting_datahub',
http: 'http',
};
export const EnumTransModelType = {
edit: '编辑',
add: '新增',
@@ -29,20 +17,3 @@ export const EnumDescSensitivity = {
label: '高',
},
};
export const EnumDbTypeOwnKeys = {
mysql: ['ip', 'port', 'dbName', 'username', 'password'],
clickhouse: ['ip', 'port', 'dbName', 'username', 'password'],
tdw: ['dbName', 'username', 'password'],
kafka: ['bootstrap', 'dbName', 'username', 'password'],
binlog: ['ip', 'port', 'dbName', 'username', 'password'],
hbase: ['config'],
kugou_datahub: ['config'],
aiting_datahub: ['config'],
http: ['url'],
};
export enum EnumDashboardType {
DIR = 0, // 目录
DASHBOARD = 1, // 看板
}

View File

@@ -1,205 +0,0 @@
import { Tabs, Popover, message } from 'antd';
import React, { useEffect, useState } from 'react';
import { connect, Helmet, useParams, history } from 'umi';
import DomainListTree from './components/DomainList';
import styles from './components/style.less';
import type { StateType } from './model';
import { DownOutlined } from '@ant-design/icons';
import EntitySection from './components/Entity/EntitySection';
import RecommendedQuestionsSection from './components/Entity/RecommendedQuestionsSection';
import { ISemantic } from './data';
import { getDomainList } from './service';
import OverView from './components/OverView';
import { findLeafNodesFromDomainList } from './utils';
import { ChatConfigType } from './enum';
import type { Dispatch } from 'umi';
type Props = {
domainManger: StateType;
dispatch: Dispatch;
};
const ChatSetting: React.FC<Props> = ({ domainManger, dispatch }) => {
const defaultTabKey = 'metric';
const params: any = useParams();
const menuKey = params.menuKey ? params.menuKey : defaultTabKey;
const modelId = params.modelId;
const { selectDomainId, selectDomainName, domainList } = domainManger;
const [modelList, setModelList] = useState<ISemantic.IDomainItem[]>([]);
const [open, setOpen] = useState(false);
const [isModel, setIsModel] = useState<boolean>(false);
const [activeKey, setActiveKey] = useState<string>(menuKey);
const handleOpenChange = (newOpen: boolean) => {
setOpen(newOpen);
};
useEffect(() => {
if (selectDomainId) {
dispatch({
type: 'domainManger/queryDimensionList',
payload: {
domainId: selectDomainId,
},
});
dispatch({
type: 'domainManger/queryMetricList',
payload: {
domainId: selectDomainId,
},
});
pushUrlMenu(selectDomainId, menuKey);
}
}, [selectDomainId]);
useEffect(() => {
if (!selectDomainId) {
return;
}
const list = findLeafNodesFromDomainList(domainList, selectDomainId);
setModelList(list);
if (Array.isArray(list) && list.length > 0) {
setIsModel(false);
pushUrlMenu(selectDomainId, 'overview');
setActiveKey('overview');
} else {
setIsModel(true);
const currentMenuKey = menuKey === 'overview' ? defaultTabKey : menuKey;
pushUrlMenu(selectDomainId, currentMenuKey);
setActiveKey(currentMenuKey);
}
}, [domainList, selectDomainId]);
const initSelectedDomain = (domainList: ISemantic.IDomainItem[]) => {
const targetNode = domainList.filter((item: any) => {
return `${item.id}` === modelId;
})[0];
if (!targetNode) {
const firstRootNode = domainList.filter((item: any) => {
return item.parentId === 0;
})[0];
if (firstRootNode) {
const { id, name } = firstRootNode;
dispatch({
type: 'domainManger/setSelectDomain',
selectDomainId: id,
selectDomainName: name,
domainData: firstRootNode,
});
}
} else {
const { id, name } = targetNode;
dispatch({
type: 'domainManger/setSelectDomain',
selectDomainId: id,
selectDomainName: name,
domainData: targetNode,
});
}
};
const initProjectTree = async () => {
const { code, data, msg } = await getDomainList();
if (code === 200) {
if (!selectDomainId) {
initSelectedDomain(data);
}
dispatch({
type: 'domainManger/setDomainList',
payload: { domainList: data },
});
} else {
message.error(msg);
}
};
useEffect(() => {
initProjectTree();
}, []);
const pushUrlMenu = (domainId: number, menuKey: string) => {
history.push(`/chatSetting/${domainId}/${menuKey}`);
};
const tabItem = [
{
label: '子主题域',
key: 'overview',
children: <OverView modelList={modelList} />,
},
];
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 className={styles.projectBody}>
<Helmet title={'问答设置-超音数'} />
<div className={styles.projectManger}>
<h2 className={styles.title}>
<Popover
zIndex={1000}
overlayInnerStyle={{
overflow: 'scroll',
maxHeight: '800px',
}}
content={
<DomainListTree
createDomainBtnVisible={false}
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}
activeKey={activeKey}
destroyInactiveTabPane
onChange={(menuKey: string) => {
setActiveKey(menuKey);
pushUrlMenu(selectDomainId, menuKey);
}}
items={!isModel ? tabItem : isModelItem}
/>
</>
) : (
<h2 className={styles.mainTip}></h2>
)}
</div>
</div>
);
};
export default connect(({ domainManger }: { domainManger: StateType }) => ({
domainManger,
}))(ChatSetting);

View File

@@ -0,0 +1,22 @@
import React from 'react';
import { connect, Helmet } from 'umi';
import type { StateType } from '../model';
import OverviewContainer from '../OverviewContainer';
import type { Dispatch } from 'umi';
type Props = {
domainManger: StateType;
dispatch: Dispatch;
};
const ChatSetting: React.FC<Props> = () => {
return (
<>
<Helmet title={'模型管理-超音数'} />
<OverviewContainer mode={'chatSetting'} />
</>
);
};
export default connect(({ domainManger }: { domainManger: StateType }) => ({
domainManger,
}))(ChatSetting);

View File

@@ -0,0 +1,102 @@
import { Tabs, Button } from 'antd';
import React from 'react';
import { connect } from 'umi';
import styles from '../components/style.less';
import type { StateType } from '../model';
import { LeftOutlined } from '@ant-design/icons';
import EntitySection from '../components/Entity/EntitySection';
import RecommendedQuestionsSection from '../components/Entity/RecommendedQuestionsSection';
import { ISemantic } from '../data';
import OverView from '../components/OverView';
import { ChatConfigType } from '../enum';
import type { Dispatch } from 'umi';
type Props = {
isModel: boolean;
activeKey: string;
modelList: ISemantic.IModelItem[];
handleModelChange: (model?: ISemantic.IModelItem) => void;
onBackDomainBtnClick?: () => void;
onMenuChange?: (menuKey: string) => void;
domainManger: StateType;
dispatch: Dispatch;
};
const ChatSetting: React.FC<Props> = ({
isModel,
activeKey,
modelList,
handleModelChange,
onBackDomainBtnClick,
onMenuChange,
}) => {
const defaultTabKey = 'metric';
const isModelItem = [
{
label: '指标模式',
key: 'metric',
children: <EntitySection chatConfigType={ChatConfigType.AGG} />,
},
{
label: '实体模式',
key: 'dimenstion',
children: <EntitySection chatConfigType={ChatConfigType.DETAIL} />,
},
{
label: '推荐问题',
key: 'recommendedQuestions',
children: <RecommendedQuestionsSection />,
},
];
const tabItem = [
{
label: '模型',
key: 'overview',
children: (
<OverView
modelList={modelList}
disabledEdit={true}
onModelChange={(model) => {
handleModelChange(model);
}}
/>
),
},
];
return (
<>
<Tabs
className={styles.tab}
items={isModel ? isModelItem : tabItem}
activeKey={activeKey || defaultTabKey}
destroyInactiveTabPane
tabBarExtraContent={
isModel ? (
<Button
type="primary"
icon={<LeftOutlined />}
onClick={() => {
onBackDomainBtnClick?.();
}}
style={{ marginRight: 10 }}
>
</Button>
) : undefined
}
onChange={(menuKey: string) => {
onMenuChange?.(menuKey);
}}
/>
</>
);
};
export default connect(({ domainManger }: { domainManger: StateType }) => ({
domainManger,
}))(ChatSetting);

View File

@@ -10,14 +10,12 @@ import { createDatasource, updateDatasource, getColumns } from '../../service';
import type { Dispatch } from 'umi';
import type { StateType } from '../../model';
import { connect } from 'umi';
import { isUndefined } from 'lodash';
export type CreateFormProps = {
domainManger: StateType;
dispatch: Dispatch;
createModalVisible: boolean;
sql?: string;
domainId: number;
dataSourceItem: DataInstanceItem | any;
onCancel?: () => void;
onSubmit?: (dataSourceInfo: any) => void;
@@ -37,7 +35,6 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
domainManger,
onCancel,
createModalVisible,
domainId,
scriptColumns,
sql = '',
onSubmit,
@@ -51,7 +48,7 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
const [hasEmptyNameField, setHasEmptyNameField] = useState<boolean>(false);
const formValRef = useRef(initFormVal as any);
const [form] = Form.useForm();
const { dataBaseConfig } = domainManger;
const { dataBaseConfig, selectModelId: modelId } = domainManger;
const updateFormVal = (val: any) => {
formValRef.current = val;
};
@@ -163,7 +160,7 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
databaseId: dataSourceItem?.databaseId || dataBaseConfig.id,
queryType: basicInfoFormMode === 'fast' ? 'table_query' : 'sql_query',
tableQuery: dbName && tableName ? `${dbName}.${tableName}` : '',
domainId,
modelId,
};
const queryDatasource = isEdit ? updateDatasource : createDatasource;
const { code, msg, data } = await queryDatasource(queryParams);

View File

@@ -18,7 +18,6 @@ import FullScreen from '@/components/FullScreen';
import SqlEditor from '@/components/SqlEditor';
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';
@@ -33,7 +32,6 @@ type IProps = {
domainManger: StateType;
dispatch: Dispatch;
dataSourceItem: DataInstanceItem;
domainId: number;
onUpdateSql?: (sql: string) => void;
sql?: string;
onSubmitSuccess?: (dataSourceInfo: any) => void;
@@ -59,12 +57,11 @@ const SqlDetail: React.FC<IProps> = ({
domainManger,
dataSourceItem,
onSubmitSuccess,
domainId,
sql = '',
onUpdateSql,
onJdbcSourceChange,
}) => {
const { dataBaseConfig } = domainManger;
const { dataBaseConfig, selectModelId: modelId } = domainManger;
const [resultTable, setResultTable] = useState<ResultTableItem[]>([]);
const [resultTableLoading, setResultTableLoading] = useState(false);
const [resultCols, setResultCols] = useState<ResultColItem[]>([]);
@@ -82,8 +79,6 @@ const SqlDetail: React.FC<IProps> = ({
y: 200,
});
// const [dataSourceResult, setDataSourceResult] = useState<any>({});
const [runState, setRunState] = useState<boolean | undefined>();
const [taskLog, setTaskLog] = useState('');
@@ -93,7 +88,6 @@ const SqlDetail: React.FC<IProps> = ({
const [isSqlIdeFullScreen, setIsSqlIdeFullScreen] = useState<boolean>(false);
const [isSqlResFullScreen, setIsSqlResFullScreen] = useState<boolean>(false);
// const [sqlParams, setSqlParams] = useState<SqlParamsItem[]>([]);
const resultInnerWrap = useRef<HTMLDivElement>();
const [editorSize, setEditorSize] = useState<number>(0);
@@ -104,18 +98,6 @@ const SqlDetail: React.FC<IProps> = ({
const [isRight, setIsRight] = useState(false);
const [scriptColumns, setScriptColumns] = useState<any[]>([]);
// const [jdbcSourceName, setJdbcSourceName] = useState<string>(() => {
// const sourceId = dataSourceItem.databaseId;
// if (sourceId) {
// const target: any = jdbcSourceItems.filter((item: any) => {
// return item.key === Number(sourceId);
// })[0];
// if (target) {
// return target.label;
// }
// }
// return 'ClickHouse';
// });
useEffect(() => {
setJdbcSourceItems([
@@ -127,21 +109,6 @@ const SqlDetail: React.FC<IProps> = ({
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'); // 需要每条数据一行,这样避免数据换行的时候获得的宽度不准确
const child = document.createElement('span');
@@ -245,7 +212,7 @@ const SqlDetail: React.FC<IProps> = ({
setResultTableLoading(true);
const { code, data, msg } = await excuteSql({
sql: value,
domainId,
modelId,
});
setResultTableLoading(false);
if (code === 200) {
@@ -521,7 +488,6 @@ const SqlDetail: React.FC<IProps> = ({
{dataSourceModalVisible && (
<DataSourceCreateForm
sql={sql}
domainId={domainId}
dataSourceItem={dataSourceItem}
scriptColumns={scriptColumns}
onCancel={() => {

View File

@@ -22,14 +22,13 @@ type TableRef = {
type Props = {
initialValues: any;
domainId: number;
onSubmitSuccess?: (dataSourceInfo: any) => void;
};
const { TabPane } = Tabs;
const LIST_KEY = 'list';
const SqlSide: React.FC<Props> = ({ initialValues, domainId, onSubmitSuccess }) => {
const SqlSide: React.FC<Props> = ({ initialValues, onSubmitSuccess }) => {
const defaultPanes: Panes[] = [
{
key: '数据源查询',
@@ -98,8 +97,6 @@ const SqlSide: React.FC<Props> = ({ initialValues, domainId, onSubmitSuccess })
<SqlDetail
onSubmitSuccess={onSubmitSuccess}
dataSourceItem={dataSourceItem}
oprType={pane.type}
domainId={domainId}
onUpdateSql={(sql: string) => {
updateTabSql(sql, pane.key);
}}

View File

@@ -7,13 +7,12 @@ import { RightOutlined, LeftOutlined } from '@ant-design/icons';
type Props = {
initialValues: any;
domainId: number;
onSubmitSuccess?: (dataSourceInfo: any) => void;
};
const DEFAULT_RIGHT_SIZE = '300px';
const DataExploreView: React.FC<Props> = ({ initialValues, domainId, onSubmitSuccess }) => {
const DataExploreView: React.FC<Props> = ({ initialValues, onSubmitSuccess }) => {
const [collapsed, setCollapsed] = useState(false);
useEffect(() => {
@@ -51,11 +50,7 @@ const DataExploreView: React.FC<Props> = ({ initialValues, domainId, onSubmitSuc
{collapsed ? <LeftOutlined /> : <RightOutlined />}
</div>
)}
<SqlSide
initialValues={initialValues}
domainId={domainId}
onSubmitSuccess={onSubmitSuccess}
/>
<SqlSide initialValues={initialValues} onSubmitSuccess={onSubmitSuccess} />
</div>
<Pane initialSize={0} />

View File

@@ -0,0 +1,22 @@
import React from 'react';
import { connect, Helmet } from 'umi';
import type { StateType } from './model';
import OverviewContainer from './OverviewContainer';
import type { Dispatch } from 'umi';
type Props = {
domainManger: StateType;
dispatch: Dispatch;
};
const DomainManager: React.FC<Props> = () => {
return (
<>
<Helmet title={'模型管理-超音数'} />
<OverviewContainer mode={'domain'} />
</>
);
};
export default connect(({ domainManger }: { domainManger: StateType }) => ({
domainManger,
}))(DomainManager);

View File

@@ -1,4 +1,4 @@
import { Form, Select, Input } from 'antd';
import { Form, Input } from 'antd';
import StandardFormRow from '@/components/StandardFormRow';
import TagSelect from '@/components/TagSelect';
import React, { useEffect } from 'react';
@@ -64,7 +64,7 @@ const MetricFilter: React.FC<Props> = ({ filterValues = {}, onFiltersChange }) =
>
<StandardFormRow key="search" block>
<div className={styles.searchBox}>
<FormItem name={'name'} noStyle>
<FormItem name={'key'} noStyle>
<div className={styles.searchInput}>
<Input.Search
placeholder="请输入需要查询指标的ID、指标名称、字段名称"

View File

@@ -1,16 +1,17 @@
import type { ActionType, ProColumns } from '@ant-design/pro-table';
import ProTable from '@ant-design/pro-table';
import { message } from 'antd';
import { message, Space, Popconfirm } from 'antd';
import React, { useRef, useState, useEffect } from 'react';
import type { Dispatch } from 'umi';
import { connect } from 'umi';
import type { StateType } from '../model';
import { SENSITIVE_LEVEL_ENUM } from '../constant';
import { queryMetric } from '../service';
import { queryMetric, deleteMetric } from '../service';
import MetricFilter from './components/MetricFilter';
import MetricInfoCreateForm from '../components/MetricInfoCreateForm';
import moment from 'moment';
import styles from './style.less';
import { IDataSource, ISemantic } from '../data';
type Props = {
dispatch: Dispatch;
@@ -26,14 +27,17 @@ type QueryMetricListParams = {
[key: string]: any;
};
const ClassMetricTable: React.FC<Props> = () => {
const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
const { selectDomainId } = domainManger;
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
const [pagination, setPagination] = useState({
current: 1,
pageSize: 20,
total: 0,
});
const [loading, setLoading] = useState<boolean>(false);
const [dataSource, setDataSource] = useState<any[]>([]);
const [dataSource, setDataSource] = useState<IDataSource.IDataSourceItem[]>([]);
const [metricItem, setMetricItem] = useState<ISemantic.IMetricItem>();
const [filterParams, setFilterParams] = useState<Record<string, any>>({});
const actionRef = useRef<ActionType>();
@@ -92,8 +96,8 @@ const ClassMetricTable: React.FC<Props> = () => {
title: '字段名称',
},
{
dataIndex: 'domainName',
title: '主题域',
dataIndex: 'modelName',
title: '所属模型',
},
{
dataIndex: 'sensitiveLevel',
@@ -127,6 +131,50 @@ const ClassMetricTable: React.FC<Props> = () => {
return value && value !== '-' ? moment(value).format('YYYY-MM-DD HH:mm:ss') : '-';
},
},
{
title: '操作',
dataIndex: 'x',
valueType: 'option',
render: (_, record) => {
return (
<Space>
<a
key="metricEditBtn"
onClick={() => {
setMetricItem(record);
setCreateModalVisible(true);
}}
>
</a>
<Popconfirm
title="确认删除?"
okText="是"
cancelText="否"
onConfirm={async () => {
const { code, msg } = await deleteMetric(record.id);
if (code === 200) {
setMetricItem(undefined);
actionRef.current?.reload();
} else {
message.error(msg);
}
}}
>
<a
key="metricDeleteBtn"
onClick={() => {
setMetricItem(record);
}}
>
</a>
</Popconfirm>
</Space>
);
},
},
];
const handleFilterChange = async (filterParams: {
@@ -179,6 +227,26 @@ const ClassMetricTable: React.FC<Props> = () => {
size="small"
options={{ reload: false, density: false, fullScreen: false }}
/>
{createModalVisible && (
<MetricInfoCreateForm
domainId={Number(selectDomainId)}
createModalVisible={createModalVisible}
metricItem={metricItem}
onSubmit={() => {
setCreateModalVisible(false);
actionRef?.current?.reload();
dispatch({
type: 'domainManger/queryMetricList',
payload: {
domainId: selectDomainId,
},
});
}}
onCancel={() => {
setCreateModalVisible(false);
}}
/>
)}
</>
);
};

View File

@@ -0,0 +1,290 @@
import { Popover, message, Space } from 'antd';
import React, { useEffect, useState } from 'react';
import { connect, Helmet, history, useParams, useRouteMatch, useLocation } from 'umi';
import DomainListTree from './components/DomainList';
import styles from './components/style.less';
import type { StateType } from './model';
import { DownOutlined } from '@ant-design/icons';
import { ISemantic } from './data';
import { getDomainList, getModelList } from './service';
import ChatSettingTab from './ChatSetting/ChatSettingTab';
import DomainManagerTab from './components/DomainManagerTab';
import type { Dispatch } from 'umi';
type Props = {
mode: 'domain' | 'chatSetting';
domainManger: StateType;
dispatch: Dispatch;
};
const OverviewContainer: React.FC<Props> = ({ mode, domainManger, dispatch }) => {
const params: any = useParams();
const domainId = params.domainId;
const modelId = params.modelId;
const menuKey = params.menuKey ? params.menuKey : !Number(modelId) ? 'overview' : '';
const { selectDomainId, selectModelId, selectDomainName, selectModelName, domainList } =
domainManger;
const [modelList, setModelList] = useState<ISemantic.IDomainItem[]>([]);
const [isModel, setIsModel] = useState<boolean>(false);
const [open, setOpen] = useState(false);
const [activeKey, setActiveKey] = useState<string>(menuKey);
const handleOpenChange = (newOpen: boolean) => {
setOpen(newOpen);
};
const initSelectedDomain = (domainList: ISemantic.IDomainItem[]) => {
const targetNode = domainList.filter((item: any) => {
return `${item.id}` === domainId;
})[0];
if (!targetNode) {
const firstRootNode = domainList.filter((item: any) => {
return item.parentId === 0;
})[0];
if (firstRootNode) {
const { id, name } = firstRootNode;
dispatch({
type: 'domainManger/setSelectDomain',
selectDomainId: id,
selectDomainName: name,
domainData: firstRootNode,
});
setActiveKey(menuKey);
pushUrlMenu(id, 0, menuKey);
}
} else {
const { id, name } = targetNode;
dispatch({
type: 'domainManger/setSelectDomain',
selectDomainId: id,
selectDomainName: name,
domainData: targetNode,
});
}
};
const initProjectTree = async () => {
const { code, data, msg } = await getDomainList();
if (code === 200) {
initSelectedDomain(data);
dispatch({
type: 'domainManger/setDomainList',
payload: { domainList: data },
});
} else {
message.error(msg);
}
};
useEffect(() => {
initProjectTree();
}, []);
useEffect(() => {
if (!selectDomainId) {
return;
}
queryModelList();
dispatch({
type: 'domainManger/queryDatabaseByDomainId',
payload: {
domainId: selectDomainId,
},
});
}, [selectDomainId]);
const queryModelList = async () => {
const { code, data } = await getModelList(selectDomainId);
if (code === 200) {
setModelList(data);
const model = data.filter((item: any) => {
return `${item.id}` === modelId;
})[0];
if (model) {
const { id, name } = model;
dispatch({
type: 'domainManger/setSelectModel',
selectModelId: id,
selectModelName: name,
modelData: model,
});
setActiveKey(menuKey);
setIsModel(true);
pushUrlMenu(selectDomainId, id, menuKey);
}
} else {
message.error('获取模型列表失败!');
}
};
useEffect(() => {
if (!selectDomainId) {
return;
}
setIsModel(false);
setActiveKey(menuKey);
}, [domainList, selectDomainId]);
const initModelConfig = () => {
setIsModel(true);
const currentMenuKey = menuKey === 'overview' ? '' : menuKey;
pushUrlMenu(selectDomainId, selectModelId, currentMenuKey);
setActiveKey(currentMenuKey);
};
useEffect(() => {
if (!selectModelId) {
return;
}
initModelConfig();
dispatch({
type: 'domainManger/queryDimensionList',
payload: {
modelId: selectModelId,
},
});
dispatch({
type: 'domainManger/queryMetricList',
payload: {
modelId: selectModelId,
},
});
}, [selectModelId]);
const pushUrlMenu = (domainId: number, modelId: number, menuKey: string) => {
const path = mode === 'domain' ? 'semanticModel' : 'chatSetting';
history.push(`/${path}/model/${domainId}/${modelId || 0}/${menuKey}`);
};
const handleModelChange = (model?: ISemantic.IModelItem) => {
queryModelList();
if (!model) {
return;
}
if (`${model.id}` === `${selectModelId}`) {
initModelConfig();
}
const { id, name } = model;
dispatch({
type: 'domainManger/setSelectModel',
selectModelId: id,
selectModelName: name,
modelData: model,
});
};
const cleanModelInfo = (domainId: number) => {
setIsModel(false);
pushUrlMenu(domainId, 0, 'overview');
setActiveKey('overview');
dispatch({
type: 'domainManger/setSelectModel',
selectModelId: 0,
selectModelName: '',
modelData: undefined,
});
};
return (
<div className={styles.projectBody}>
<Helmet title={'模型管理-超音数'} />
<div className={styles.projectManger}>
<h2 className={styles.title}>
<Popover
zIndex={1000}
overlayInnerStyle={{
overflow: 'scroll',
maxHeight: '800px',
}}
content={
<DomainListTree
createDomainBtnVisible={mode === 'domain' ? true : false}
onTreeSelected={(domainData) => {
setOpen(false);
const { id, name } = domainData;
cleanModelInfo(id);
dispatch({
type: 'domainManger/setSelectDomain',
selectDomainId: id,
selectDomainName: name,
domainData,
});
}}
onTreeDataUpdate={() => {
initProjectTree();
}}
/>
}
trigger="click"
open={open}
onOpenChange={handleOpenChange}
>
<div className={styles.domainSelector}>
<span className={styles.domainTitle}>
<Space>
{selectDomainName ? `当前主题域:${selectDomainName}` : '主题域信息'}
{selectModelName && (
<>
<span style={{ position: 'relative', top: '-2px' }}> | </span>
<span style={{ fontSize: 16, color: '#296DF3' }}>{selectModelName}</span>
</>
)}
</Space>
</span>
<span className={styles.downIcon}>
<DownOutlined />
</span>
</div>
</Popover>
</h2>
{selectDomainId ? (
<>
{mode === 'domain' ? (
<DomainManagerTab
isModel={isModel}
activeKey={activeKey}
modelList={modelList}
handleModelChange={(model) => {
handleModelChange(model);
}}
onBackDomainBtnClick={() => {
cleanModelInfo(selectDomainId);
}}
onMenuChange={(menuKey) => {
setActiveKey(menuKey);
pushUrlMenu(selectDomainId, selectModelId, menuKey);
}}
/>
) : (
<ChatSettingTab
isModel={isModel}
activeKey={activeKey}
modelList={modelList}
handleModelChange={(model) => {
handleModelChange(model);
}}
onBackDomainBtnClick={() => {
cleanModelInfo(selectDomainId);
}}
onMenuChange={(menuKey) => {
setActiveKey(menuKey);
pushUrlMenu(selectDomainId, selectModelId, menuKey);
}}
/>
)}
</>
) : (
<h2 className={styles.mainTip}></h2>
)}
</div>
</div>
);
};
export default connect(({ domainManger }: { domainManger: StateType }) => ({
domainManger,
}))(OverviewContainer);

View File

@@ -1,251 +0,0 @@
import { Tabs, Popover, message } from 'antd';
import React, { useEffect, useState } from 'react';
import { connect, Helmet, history, useParams } from 'umi';
import DomainListTree from './components/DomainList';
import ClassDataSourceTable from './components/ClassDataSourceTable';
import ClassDimensionTable from './components/ClassDimensionTable';
import ClassMetricTable from './components/ClassMetricTable';
import PermissionSection from './components/Permission/PermissionSection';
import DatabaseSection from './components/Database/DatabaseSection';
import EntitySettingSection from './components/Entity/EntitySettingSection';
import OverView from './components/OverView';
import styles from './components/style.less';
import type { StateType } from './model';
import { DownOutlined } from '@ant-design/icons';
import { ISemantic } from './data';
import { findLeafNodesFromDomainList } from './utils';
import SemanticGraphCanvas from './SemanticGraphCanvas';
import { getDomainList } from './service';
import type { Dispatch } from 'umi';
type Props = {
domainManger: StateType;
dispatch: Dispatch;
};
const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
const defaultTabKey = 'xflow';
const params: any = useParams();
const menuKey = params.menuKey ? params.menuKey : defaultTabKey;
const modelId = params.modelId;
const { selectDomainId, selectDomainName, domainList } = domainManger;
const [modelList, setModelList] = useState<ISemantic.IDomainItem[]>([]);
const [isModel, setIsModel] = useState<boolean>(false);
const [open, setOpen] = useState(false);
const [activeKey, setActiveKey] = useState<string>(menuKey);
useEffect(() => {
setActiveKey(menuKey);
}, [menuKey]);
const initSelectedDomain = (domainList: ISemantic.IDomainItem[]) => {
const targetNode = domainList.filter((item: any) => {
return `${item.id}` === modelId;
})[0];
if (!targetNode) {
const firstRootNode = domainList.filter((item: any) => {
return item.parentId === 0;
})[0];
if (firstRootNode) {
const { id, name } = firstRootNode;
dispatch({
type: 'domainManger/setSelectDomain',
selectDomainId: id,
selectDomainName: name,
domainData: firstRootNode,
});
}
} else {
const { id, name } = targetNode;
dispatch({
type: 'domainManger/setSelectDomain',
selectDomainId: id,
selectDomainName: name,
domainData: targetNode,
});
}
};
const initProjectTree = async () => {
const { code, data, msg } = await getDomainList();
if (code === 200) {
if (!selectDomainId) {
initSelectedDomain(data);
}
dispatch({
type: 'domainManger/setDomainList',
payload: { domainList: data },
});
} else {
message.error(msg);
}
};
useEffect(() => {
initProjectTree();
}, []);
useEffect(() => {
if (!selectDomainId) {
return;
}
const list = findLeafNodesFromDomainList(domainList, selectDomainId);
setModelList(list);
if (Array.isArray(list) && list.length > 0) {
setIsModel(false);
pushUrlMenu(selectDomainId, 'overview');
setActiveKey('overview');
} else {
setIsModel(true);
const currentMenuKey = menuKey === 'overview' ? defaultTabKey : menuKey;
pushUrlMenu(selectDomainId, currentMenuKey);
setActiveKey(currentMenuKey);
}
}, [domainList, selectDomainId]);
const handleOpenChange = (newOpen: boolean) => {
setOpen(newOpen);
};
const pushUrlMenu = (domainId: number, menuKey: string) => {
history.push(`/semanticModel/${domainId}/${menuKey}`);
};
useEffect(() => {
if (selectDomainId) {
dispatch({
type: 'domainManger/queryDimensionList',
payload: {
domainId: selectDomainId,
},
});
dispatch({
type: 'domainManger/queryMetricList',
payload: {
domainId: selectDomainId,
},
});
dispatch({
type: 'domainManger/queryDatabaseByDomainId',
payload: {
domainId: selectDomainId,
},
});
}
}, [selectDomainId]);
const tabItem = [
{
label: '子主题域',
key: 'overview',
children: <OverView modelList={modelList} />,
},
{
label: '权限管理',
key: 'permissonSetting',
children: <PermissionSection />,
},
];
const isModelItem = [
{
label: '画布',
key: 'xflow',
children: (
<div style={{ width: '100%', marginTop: -20 }}>
<SemanticGraphCanvas />
</div>
),
},
{
label: '数据库',
key: 'dataBase',
children: <DatabaseSection />,
},
{
label: '数据源',
key: 'dataSource',
children: <ClassDataSourceTable />,
},
{
label: '维度',
key: 'dimenstion',
children: <ClassDimensionTable key={selectDomainId} />,
},
{
label: '指标',
key: 'metric',
children: <ClassMetricTable />,
},
{
label: '实体',
key: 'entity',
children: <EntitySettingSection />,
},
{
label: '权限管理',
key: 'permissonSetting',
children: <PermissionSection />,
},
];
return (
<div className={styles.projectBody}>
<Helmet title={'模型管理-超音数'} />
<div className={styles.projectManger}>
<h2 className={styles.title}>
<Popover
zIndex={1000}
overlayInnerStyle={{
overflow: 'scroll',
maxHeight: '800px',
}}
content={
<DomainListTree
onTreeSelected={() => {
setOpen(false);
}}
onTreeDataUpdate={() => {
initProjectTree();
}}
/>
}
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}
items={!isModel ? tabItem : isModelItem}
activeKey={activeKey}
destroyInactiveTabPane
onChange={(menuKey: string) => {
setActiveKey(menuKey);
pushUrlMenu(selectDomainId, menuKey);
}}
/>
</>
) : (
<h2 className={styles.mainTip}></h2>
)}
</div>
</div>
);
};
export default connect(({ domainManger }: { domainManger: StateType }) => ({
domainManger,
}))(DomainManger);

View File

@@ -13,7 +13,6 @@ import {
deleteDatasource,
getDimensionList,
createOrUpdateViewInfo,
getViewInfoList,
deleteDatasourceRela,
} from '../service';
import { message } from 'antd';
@@ -85,8 +84,8 @@ export namespace GraphApi {
export const loadDataSourceData = async (args: NsGraph.IGraphMeta) => {
const { domainManger, graphConfig } = args.meta;
const { selectDomainId } = domainManger;
const { code, data = [] } = await getDatasourceList({ domainId: selectDomainId });
const { selectModelId } = domainManger;
const { code, data = [] } = await getDatasourceList({ modelId: selectModelId });
const dataSourceMap = data.reduce(
(itemMap: Record<string, IDataSource.IDataSourceItem>, item: IDataSource.IDataSourceItem) => {
const { id, name } = item;
@@ -161,8 +160,8 @@ export namespace GraphApi {
export const loadDimensionData = async (args: NsGraph.IGraphMeta) => {
const { domainManger } = args.meta;
const { domainId } = domainManger;
const { code, data } = await getDimensionList({ domainId });
const { selectModelId } = domainManger;
const { code, data } = await getDimensionList({ modelId: selectModelId });
if (code === 200) {
const { list } = data;
const nodes: NsGraph.INodeConfig[] = list.map((item: any) => {
@@ -210,7 +209,7 @@ export namespace GraphApi {
const { domainManger, graphConfig } = graphMeta.meta;
const { code, msg } = await createOrUpdateViewInfo({
id: graphConfig?.id,
domainId: domainManger.selectDomainId,
modelId: domainManger.selectModelId,
type: 'datasource',
config: JSON.stringify(tempGraphData),
});

View File

@@ -18,7 +18,6 @@ const DeleteConfirmModal: React.FC<Props> = ({
}) => {
const [confirmLoading, setConfirmLoading] = useState(false);
const deleteNode = async () => {
setConfirmLoading(true);
const { id, nodeType } = nodeData;
let deleteQuery;
if (nodeType === SemanticNodeType.DIMENSION) {
@@ -34,6 +33,7 @@ const DeleteConfirmModal: React.FC<Props> = ({
message.error('当前节点类型不是维度,指标,数据源中的一种,请确认节点数据');
return;
}
setConfirmLoading(true);
const { code, msg } = await deleteQuery(id);
setConfirmLoading(false);
if (code === 200) {

View File

@@ -32,7 +32,6 @@ import GraphLegendVisibleModeItem from './components/GraphLegendVisibleModeItem'
// import { cloneDeep } from 'lodash';
type Props = {
domainId: number;
// graphShowType?: SemanticNodeType;
domainManger: StateType;
dispatch: Dispatch;
@@ -40,7 +39,6 @@ type Props = {
const DomainManger: React.FC<Props> = ({
domainManger,
domainId,
// graphShowType = SemanticNodeType.DIMENSION,
// graphShowType,
dispatch,
@@ -65,7 +63,7 @@ const DomainManger: React.FC<Props> = ({
const [dataSourceInfoList, setDataSourceInfoList] = useState<IDataSource.IDataSourceItem[]>([]);
const { dimensionList, metricList } = domainManger;
const { dimensionList, metricList, selectModelId: modelId, selectDomainId } = domainManger;
const dimensionListRef = useRef<ISemantic.IDimensionItem[]>([]);
const metricListRef = useRef<ISemantic.IMetricItem[]>([]);
@@ -139,10 +137,10 @@ const DomainManger: React.FC<Props> = ({
};
const queryDataSourceList = async (params: {
domainId: number;
modelId: number;
graphShowType?: SemanticNodeType;
}) => {
const { code, data } = await getDomainSchemaRela(params.domainId);
const { code, data } = await getDomainSchemaRela(params.modelId);
if (code === 200) {
if (data) {
setDataSourceInfoList(
@@ -165,8 +163,8 @@ const DomainManger: React.FC<Props> = ({
useEffect(() => {
graphLegendDataSourceIds.current = undefined;
graphRef.current = null;
queryDataSourceList({ domainId });
}, [domainId]);
queryDataSourceList({ modelId });
}, [modelId]);
// const getLegendDataFilterFunctions = () => {
// legendDataRef.current.map((item: any) => {
@@ -273,7 +271,7 @@ const DomainManger: React.FC<Props> = ({
if (targetData.nodeType === SemanticNodeType.DIMENSION) {
const targetItem = dimensionListRef.current.find((item) => item.id === targetData.uid);
if (targetItem) {
setCurrentNodeData(targetItem);
setCurrentNodeData({ ...targetData, ...targetItem });
setConfirmModalOpenState(true);
} else {
message.error('获取维度初始化数据失败');
@@ -282,7 +280,7 @@ const DomainManger: React.FC<Props> = ({
if (targetData.nodeType === SemanticNodeType.METRIC) {
const targetItem = metricListRef.current.find((item) => item.id === targetData.uid);
if (targetItem) {
setCurrentNodeData(targetItem);
setCurrentNodeData({ ...targetData, ...targetItem });
setConfirmModalOpenState(true);
} else {
message.error('获取指标初始化数据失败');
@@ -515,7 +513,7 @@ const DomainManger: React.FC<Props> = ({
const updateGraphData = async (params?: { graphShowType?: SemanticNodeType }) => {
const graphRootData = await queryDataSourceList({
domainId,
modelId,
graphShowType: params?.graphShowType,
});
if (graphRootData) {
@@ -571,7 +569,7 @@ const DomainManger: React.FC<Props> = ({
/>
<div
ref={ref}
key={`${domainId}`}
key={`${modelId}`}
id="semanticGraph"
style={{ width: '100%', height: 'calc(100vh - 175px)', position: 'relative' }}
/>
@@ -595,7 +593,7 @@ const DomainManger: React.FC<Props> = ({
dispatch({
type: 'domainManger/queryMetricList',
payload: {
domainId,
modelId,
},
});
}
@@ -603,7 +601,7 @@ const DomainManger: React.FC<Props> = ({
dispatch({
type: 'domainManger/queryDimensionList',
payload: {
domainId,
modelId,
},
});
}
@@ -612,7 +610,7 @@ const DomainManger: React.FC<Props> = ({
{createDimensionModalVisible && (
<DimensionInfoModal
domainId={domainId}
modelId={modelId}
bindModalVisible={createDimensionModalVisible}
dimensionItem={dimensionItem}
dataSourceList={nodeDataSource ? [nodeDataSource] : dataSourceInfoList}
@@ -622,7 +620,7 @@ const DomainManger: React.FC<Props> = ({
dispatch({
type: 'domainManger/queryDimensionList',
payload: {
domainId,
modelId,
},
});
}}
@@ -633,7 +631,8 @@ const DomainManger: React.FC<Props> = ({
)}
{createMetricModalVisible && (
<MetricInfoCreateForm
domainId={domainId}
domainId={selectDomainId}
modelId={modelId}
key={metricItem?.id}
datasourceId={nodeDataSource?.id}
createModalVisible={createMetricModalVisible}
@@ -644,7 +643,7 @@ const DomainManger: React.FC<Props> = ({
dispatch({
type: 'domainManger/queryMetricList',
payload: {
domainId,
modelId,
},
});
}}
@@ -676,13 +675,13 @@ const DomainManger: React.FC<Props> = ({
? dispatch({
type: 'domainManger/queryDimensionList',
payload: {
domainId,
modelId,
},
})
: dispatch({
type: 'domainManger/queryMetricList',
payload: {
domainId,
modelId,
},
});
}}

View File

@@ -38,7 +38,7 @@ const SemanticGraphCanvas: React.FC<Props> = ({ domainManger }) => {
</div>
) : ( */}
<div style={{ width: '100%' }}>
<SemanticGraph domainId={selectDomainId} />
<SemanticGraph />
</div>
{/* )} */}
</div>

View File

@@ -113,6 +113,7 @@ const BindMeasuresTable: React.FC<CreateFormProps> = ({
size="small"
search={false}
options={false}
scroll={{ y: 800 }}
/>
</Modal>
);

View File

@@ -15,7 +15,7 @@ type Props = {
};
const ClassDataSourceTable: React.FC<Props> = ({ dispatch, domainManger }) => {
const { selectDomainId } = domainManger;
const { selectModelId } = domainManger;
const [dataSourceItem, setDataSourceItem] = useState<any>();
const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false);
@@ -59,7 +59,7 @@ const ClassDataSourceTable: React.FC<Props> = ({ dispatch, domainManger }) => {
return (
<Space>
<a
key="classEditBtn"
key="datasourceEditBtn"
onClick={() => {
setDataSourceItem(record);
setCreateDataSourceModalOpen(true);
@@ -82,7 +82,7 @@ const ClassDataSourceTable: React.FC<Props> = ({ dispatch, domainManger }) => {
}}
>
<a
key="classEditBtn"
key="datasourceDeleteBtn"
onClick={() => {
setDataSourceItem(record);
}}
@@ -127,7 +127,7 @@ const ClassDataSourceTable: React.FC<Props> = ({ dispatch, domainManger }) => {
actionRef={actionRef}
rowKey="id"
columns={columns}
params={{ domainId: selectDomainId }}
params={{ modelId: selectModelId }}
request={queryDataSourceList}
pagination={false}
search={false}

View File

@@ -28,7 +28,7 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({
onCancel,
dispatch,
}) => {
const { selectDomainId, dataBaseConfig } = domainManger;
const { selectDomainId, dataBaseConfig, selectModelId } = domainManger;
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
const [dataSourceModalVisible, setDataSourceModalVisible] = useState(false);
@@ -128,7 +128,7 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({
type="primary"
key="console"
onClick={() => {
history.replace(`/semanticModel/${selectDomainId}/dataBase`);
history.replace(`/semanticModel/${selectDomainId}/0/dataBase`);
onCancel?.();
}}
>
@@ -173,7 +173,6 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({
>
<DataSource
initialValues={dataSourceItem}
domainId={Number(selectDomainId)}
onSubmitSuccess={() => {
setCreateModalVisible(false);
onSubmit?.();

View File

@@ -1,101 +0,0 @@
import { Modal, Card, Row, Col, Result, Button } from 'antd';
import { ConsoleSqlOutlined, CoffeeOutlined } from '@ant-design/icons';
import React, { useState, useEffect } from 'react';
import { history, connect } from 'umi';
import type { StateType } from '../model';
const { Meta } = Card;
type Props = {
open: boolean;
domainManger: StateType;
onTypeChange: (type: 'fast' | 'normal') => void;
onCancel?: () => void;
};
const ClassDataSourceTypeModal: React.FC<Props> = ({
open,
onTypeChange,
domainManger,
onCancel,
}) => {
const { selectDomainId, dataBaseConfig } = domainManger;
const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false);
useEffect(() => {
setCreateDataSourceModalOpen(open);
}, [open]);
return (
<>
<Modal
open={createDataSourceModalOpen}
onCancel={() => {
setCreateDataSourceModalOpen(false);
onCancel?.();
}}
footer={null}
centered
closable={false}
>
{dataBaseConfig && dataBaseConfig.id ? (
<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>
) : (
<Result
status="warning"
subTitle="创建数据源需要先完成数据库设置"
extra={
<Button
type="primary"
key="console"
onClick={() => {
history.replace(`/semanticModel/${selectDomainId}/dataBase`);
onCancel?.();
}}
>
</Button>
}
/>
)}
</Modal>
</>
);
};
export default connect(({ domainManger }: { domainManger: StateType }) => ({
domainManger,
}))(ClassDataSourceTypeModal);

View File

@@ -19,7 +19,7 @@ type Props = {
};
const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
const { selectDomainId } = domainManger;
const { selectModelId: modelId } = domainManger;
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
const [dimensionItem, setDimensionItem] = useState<ISemantic.IDimensionItem>();
const [dataSourceList, setDataSourceList] = useState<any[]>([]);
@@ -40,7 +40,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
const { code, data, msg } = await getDimensionList({
...params,
...pagination,
domainId: selectDomainId,
modelId,
});
const { list, pageSize, current, total } = data || {};
let resData: any = {};
@@ -67,7 +67,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
};
const queryDataSourceList = async () => {
const { code, data, msg } = await getDatasourceList({ domainId: selectDomainId });
const { code, data, msg } = await getDatasourceList({ modelId });
if (code === 200) {
setDataSourceList(data);
} else {
@@ -77,7 +77,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
useEffect(() => {
queryDataSourceList();
}, [selectDomainId]);
}, [modelId]);
const columns: ProColumns[] = [
{
@@ -139,7 +139,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
return (
<Space>
<a
key="classEditBtn"
key="dimensionEditBtn"
onClick={() => {
setDimensionItem(record);
setCreateModalVisible(true);
@@ -148,7 +148,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
</a>
<a
key="classEditBtn"
key="dimensionValueEditBtn"
onClick={() => {
setDimensionItem(record);
setDimensionValueSettingModalVisible(true);
@@ -176,7 +176,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
}}
>
<a
key="classEditBtn"
key="dimensionDeleteEditBtn"
onClick={() => {
setDimensionItem(record);
}}
@@ -195,7 +195,6 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
<ProTable
className={`${styles.classTable} ${styles.classTableSelectColumnAlignLeft}`}
actionRef={actionRef}
// headerTitle="维度列表"
rowKey="id"
columns={columns}
request={queryDimensionList}
@@ -236,7 +235,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
{createModalVisible && (
<DimensionInfoModal
domainId={selectDomainId}
modelId={modelId}
bindModalVisible={createModalVisible}
dimensionItem={dimensionItem}
dataSourceList={dataSourceList}
@@ -246,7 +245,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
dispatch({
type: 'domainManger/queryDimensionList',
payload: {
domainId: selectDomainId,
modelId,
},
});
return;
@@ -269,7 +268,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
dispatch({
type: 'domainManger/queryDimensionList',
payload: {
domainId: selectDomainId,
modelId,
},
});
setDimensionValueSettingModalVisible(false);

View File

@@ -12,6 +12,7 @@ import MetricInfoCreateForm from './MetricInfoCreateForm';
import moment from 'moment';
import styles from './style.less';
import { ISemantic } from '../data';
type Props = {
dispatch: Dispatch;
@@ -19,9 +20,9 @@ type Props = {
};
const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
const { selectDomainId } = domainManger;
const { selectModelId: modelId, selectDomainId } = domainManger;
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
const [metricItem, setMetricItem] = useState<any>();
const [metricItem, setMetricItem] = useState<ISemantic.IMetricItem>();
const [pagination, setPagination] = useState({
current: 1,
pageSize: 20,
@@ -33,7 +34,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
const { code, data, msg } = await queryMetric({
...params,
...pagination,
domainId: selectDomainId,
modelId,
});
const { list, pageSize, current, total } = data || {};
let resData: any = {};
@@ -95,7 +96,6 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
{
dataIndex: 'type',
title: '指标类型',
// search: false,
valueEnum: {
ATOMIC: '原子指标',
DERIVED: '衍生指标',
@@ -128,7 +128,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
return (
<Space>
<a
key="classEditBtn"
key="metricEditBtn"
onClick={() => {
setMetricItem(record);
setCreateModalVisible(true);
@@ -152,7 +152,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
}}
>
<a
key="classEditBtn"
key="metricDeleteBtn"
onClick={() => {
setMetricItem(record);
}}
@@ -171,7 +171,6 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
<ProTable
className={`${styles.classTable} ${styles.classTableSelectColumnAlignLeft}`}
actionRef={actionRef}
// headerTitle="指标列表"
rowKey="id"
search={{
span: 4,
@@ -181,7 +180,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
},
}}
columns={columns}
params={{ domainId: selectDomainId }}
params={{ modelId }}
request={queryMetricList}
pagination={pagination}
tableAlertRender={() => {
@@ -212,7 +211,8 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
/>
{createModalVisible && (
<MetricInfoCreateForm
domainId={Number(selectDomainId)}
domainId={selectDomainId}
modelId={Number(modelId)}
createModalVisible={createModalVisible}
metricItem={metricItem}
onSubmit={() => {
@@ -221,7 +221,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
dispatch({
type: 'domainManger/queryMetricList',
payload: {
domainId: selectDomainId,
modelId,
},
});
}}

View File

@@ -1,7 +1,7 @@
import { useEffect, forwardRef, useImperativeHandle, useState } from 'react';
import type { ForwardRefRenderFunction } from 'react';
import { message, Form, Input, Select, Button, Space } from 'antd';
import { saveDatabase, getDatabaseByDomainId, testDatabaseConnect } from '../../service';
import { saveDatabase, testDatabaseConnect } from '../../service';
import { formLayout } from '@/components/FormHelper/utils';
import styles from '../style.less';

View File

@@ -10,7 +10,7 @@ import { createDimension, updateDimension } from '../service';
import { message } from 'antd';
export type CreateFormProps = {
domainId: number;
modelId: number;
dimensionItem?: ISemantic.IDimensionItem;
onCancel: () => void;
bindModalVisible: boolean;
@@ -24,7 +24,7 @@ const { Option } = Select;
const { TextArea } = Input;
const DimensionInfoModal: React.FC<CreateFormProps> = ({
domainId,
modelId,
onCancel,
bindModalVisible,
dimensionItem,
@@ -55,7 +55,7 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
const saveDimension = async (fieldsValue: any, isSilenceSubmit = false) => {
const queryParams = {
domainId,
modelId,
type: 'categorical',
...fieldsValue,
};

View File

@@ -24,7 +24,7 @@ type DomainListProps = {
createDomainBtnVisible?: boolean;
dispatch: Dispatch;
onCreateDomainBtnClick?: () => void;
onTreeSelected?: () => void;
onTreeSelected?: (targetNodeData: ISemantic.IDomainItem) => void;
onTreeDataUpdate?: () => void;
};
@@ -57,7 +57,7 @@ const DomainListTree: FC<DomainListProps> = ({
const [projectInfoParams, setProjectInfoParams] = useState<any>({});
const [filterValue, setFliterValue] = useState<string>('');
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
const [classList, setClassList] = useState<any[]>([]);
const [classList, setClassList] = useState<ISemantic.IDomainItem[]>([]);
useEffect(() => {
const treeData = addPathInTreeData(constructorClassTreeFromList(domainList));
@@ -77,13 +77,7 @@ const DomainListTree: FC<DomainListProps> = ({
const targetNodeData = classList.filter((item: any) => {
return item.id === selectedKeys;
})[0];
onTreeSelected?.();
dispatch({
type: 'domainManger/setSelectDomain',
selectDomainId: selectedKeys,
selectDomainName: projectName,
domainData: targetNodeData,
});
onTreeSelected?.(targetNodeData);
};
const editProject = async (values: any) => {
@@ -134,7 +128,7 @@ const DomainListTree: FC<DomainListProps> = ({
</span>
{createDomainBtnVisible && (
<span className={styles.operation}>
{Array.isArray(path) && path.length < 3 && (
{Array.isArray(path) && path.length < 2 && (
<PlusOutlined
className={styles.icon}
onClick={() => {

View File

@@ -0,0 +1,135 @@
import { Tabs, Button } from 'antd';
import React from 'react';
import { connect } from 'umi';
import ClassDataSourceTable from './ClassDataSourceTable';
import ClassDimensionTable from './ClassDimensionTable';
import ClassMetricTable from './ClassMetricTable';
import PermissionSection from './Permission/PermissionSection';
import DatabaseSection from './Database/DatabaseSection';
import EntitySettingSection from './Entity/EntitySettingSection';
import OverView from './OverView';
import styles from './style.less';
import type { StateType } from '../model';
import { LeftOutlined } from '@ant-design/icons';
import { ISemantic } from '../data';
import SemanticGraphCanvas from '../SemanticGraphCanvas';
import type { Dispatch } from 'umi';
type Props = {
isModel: boolean;
activeKey: string;
modelList: ISemantic.IModelItem[];
handleModelChange: (model?: ISemantic.IModelItem) => void;
onBackDomainBtnClick?: () => void;
onMenuChange?: (menuKey: string) => void;
domainManger: StateType;
dispatch: Dispatch;
};
const DomainManagerTab: React.FC<Props> = ({
isModel,
activeKey,
modelList,
handleModelChange,
onBackDomainBtnClick,
onMenuChange,
}) => {
const defaultTabKey = 'xflow';
const tabItem = [
{
label: '模型',
key: 'overview',
children: (
<OverView
modelList={modelList}
onModelChange={(model) => {
handleModelChange(model);
}}
/>
),
},
{
label: '数据库',
key: 'dataBase',
children: <DatabaseSection />,
},
{
label: '权限管理',
key: 'permissonSetting',
children: <PermissionSection permissionTarget={'domain'} />,
},
];
const isModelItem = [
{
label: '画布',
key: 'xflow',
children: (
<div style={{ width: '100%', marginTop: -20 }}>
<SemanticGraphCanvas />
</div>
),
},
{
label: '数据源',
key: 'dataSource',
children: <ClassDataSourceTable />,
},
{
label: '维度',
key: 'dimenstion',
children: <ClassDimensionTable />,
},
{
label: '指标',
key: 'metric',
children: <ClassMetricTable />,
},
{
label: '实体',
key: 'entity',
children: <EntitySettingSection />,
},
{
label: '权限管理',
key: 'permissonSetting',
children: <PermissionSection permissionTarget={'model'} />,
},
];
return (
<>
<Tabs
className={styles.tab}
items={!isModel ? tabItem : isModelItem}
activeKey={activeKey || defaultTabKey}
destroyInactiveTabPane
tabBarExtraContent={
isModel ? (
<Button
type="primary"
icon={<LeftOutlined />}
onClick={() => {
onBackDomainBtnClick?.();
}}
style={{ marginRight: 10 }}
>
</Button>
) : undefined
}
onChange={(menuKey: string) => {
onMenuChange?.(menuKey);
}}
/>
</>
);
};
export default connect(({ domainManger }: { domainManger: StateType }) => ({
domainManger,
}))(DomainManagerTab);

View File

@@ -143,7 +143,7 @@ const DefaultSettingForm: ForwardRefRenderFunction<any, Props> = (
};
const { code, msg, data } = await saveDomainExtendQuery({
[chatConfigKey]: params,
domainId,
// domainId,
id,
});
if (code === 200) {

View File

@@ -126,7 +126,7 @@ const DimensionAndMetricVisibleModal: React.FC<Props> = ({
const { code, msg } = await saveDomainExtendQuery({
[chatConfigKey]: params,
domainId,
// domainId,
id,
});
if (code === 200) {

View File

@@ -1,22 +1,22 @@
import { useEffect, useState, forwardRef, useImperativeHandle } from 'react';
import type { ForwardRefRenderFunction } from 'react';
import { message, Form, Input, Select, Button } from 'antd';
import { updateDomain } from '../../service';
import { updateModel } from '../../service';
import type { ISemantic } from '../../data';
import { formLayout } from '@/components/FormHelper/utils';
import styles from '../style.less';
type Props = {
domainData?: ISemantic.IDomainItem;
modelData?: ISemantic.IModelItem;
dimensionList: ISemantic.IDimensionList;
domainId: number;
modelId: number;
onSubmit: () => void;
};
const FormItem = Form.Item;
const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
{ domainData, dimensionList, domainId, onSubmit },
{ modelData, dimensionList, modelId, onSubmit },
ref,
) => {
const [form] = Form.useForm();
@@ -27,15 +27,15 @@ const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
useEffect(() => {
form.resetFields();
if (!domainData?.entity) {
if (!modelData?.entity) {
return;
}
const { entity } = domainData;
const { entity } = modelData;
form.setFieldsValue({
...entity,
name: entity.names.join(','),
});
}, [domainData]);
}, [modelData]);
useImperativeHandle(ref, () => ({
getFormValidateFields,
@@ -54,14 +54,14 @@ const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
const saveEntity = async () => {
const values = await form.validateFields();
const { name } = values;
const { code, msg, data } = await updateDomain({
...domainData,
const { code, msg, data } = await updateModel({
...modelData,
entity: {
...values,
names: name.split(','),
},
id: domainId,
domainId,
id: modelId,
modelId,
});
if (code === 200) {
@@ -79,20 +79,11 @@ const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
<FormItem hidden={true} name="id" label="ID">
<Input placeholder="id" />
</FormItem>
<FormItem
name="name"
label="实体别名"
// rules={[{ required: true, message: '请输入实体别名' }]}
>
<FormItem name="name" label="实体别名">
<Input placeholder="请输入实体别名,多个名称以英文逗号分隔" />
</FormItem>
<FormItem
name="entityId"
label="唯一标识"
// rules={[{ required: true, message: '请选择实体标识' }]}
>
<FormItem name="entityId" label="唯一标识">
<Select
// mode="multiple"
allowClear
style={{ width: '100%' }}
// filterOption={(inputValue: string, item: any) => {

View File

@@ -22,22 +22,22 @@ const EntitySection: React.FC<Props> = ({
dispatch,
chatConfigType = ChatConfigType.DETAIL,
}) => {
const { selectDomainId, dimensionList, metricList } = domainManger;
const { selectDomainId, selectModelId: modelId, dimensionList, metricList } = domainManger;
const [entityData, setentityData] = useState<IChatConfig.IChatRichConfig>();
const [entityData, setEntityData] = useState<IChatConfig.IChatRichConfig>();
const queryThemeListData: any = async () => {
const { code, data } = await getDomainExtendDetailConfig({
domainId: selectDomainId,
modelId,
});
if (code === 200) {
const { chatAggRichConfig, chatDetailRichConfig, id, domainId } = data;
const { chatAggRichConfig, chatDetailRichConfig, id, domainId, modelId } = data;
if (chatConfigType === ChatConfigType.DETAIL) {
setentityData({ ...chatDetailRichConfig, id, domainId });
setEntityData({ ...chatDetailRichConfig, id, domainId, modelId });
}
if (chatConfigType === ChatConfigType.AGG) {
setentityData({ ...chatAggRichConfig, id, domainId });
setEntityData({ ...chatAggRichConfig, id, domainId, modelId });
}
return;
}
@@ -50,8 +50,11 @@ const EntitySection: React.FC<Props> = ({
};
useEffect(() => {
if (!modelId) {
return;
}
initPage();
}, [selectDomainId]);
}, [modelId]);
return (
<div style={{ width: 800, margin: '0 auto' }}>

View File

@@ -3,7 +3,7 @@ import React, { useState, useEffect, useRef } from 'react';
import type { Dispatch } from 'umi';
import { connect } from 'umi';
import type { StateType } from '../../model';
import { getDomainDetail } from '../../service';
import { getModelDetail } from '../../service';
import ProCard from '@ant-design/pro-card';
import EntityCreateForm from './EntityCreateForm';
import type { ISemantic } from '../../data';
@@ -14,19 +14,19 @@ type Props = {
};
const EntitySettingSection: React.FC<Props> = ({ domainManger }) => {
const { selectDomainId, dimensionList } = domainManger;
const { dimensionList, selectModelId: modelId } = domainManger;
const [domainData, setDomainData] = useState<ISemantic.IDomainItem>();
const [modelData, setModelData] = useState<ISemantic.IModelItem>();
const entityCreateRef = useRef<any>({});
const queryDomainData: any = async () => {
const { code, data } = await getDomainDetail({
domainId: selectDomainId,
const { code, data } = await getModelDetail({
modelId,
});
if (code === 200) {
setDomainData(data);
setModelData(data);
return;
}
@@ -40,7 +40,7 @@ const EntitySettingSection: React.FC<Props> = ({ domainManger }) => {
useEffect(() => {
initPage();
}, [selectDomainId]);
}, [modelId]);
return (
<div style={{ width: 800, margin: '0 auto' }}>
@@ -49,8 +49,8 @@ const EntitySettingSection: React.FC<Props> = ({ domainManger }) => {
<ProCard title="实体" bordered>
<EntityCreateForm
ref={entityCreateRef}
domainId={Number(selectDomainId)}
domainData={domainData}
modelId={Number(modelId)}
modelData={modelData}
dimensionList={dimensionList}
onSubmit={() => {
queryDomainData();

View File

@@ -12,14 +12,14 @@ type Props = {
};
const RecommendedQuestionsSection: React.FC<Props> = ({ domainManger }) => {
const { selectDomainId } = domainManger;
const { selectModelId: modelId } = domainManger;
const [questionData, setQuestionData] = useState<string[]>([]);
const [currentRecordId, setCurrentRecordId] = useState<number>(0);
const queryThemeListData: any = async () => {
const { code, data } = await getDomainExtendConfig({
domainId: selectDomainId,
modelId,
});
if (code === 200) {
@@ -51,7 +51,7 @@ const RecommendedQuestionsSection: React.FC<Props> = ({ domainManger }) => {
return { question };
}),
id: currentRecordId,
domainId: selectDomainId,
modelId,
});
if (code === 200) {
@@ -65,8 +65,11 @@ const RecommendedQuestionsSection: React.FC<Props> = ({ domainManger }) => {
};
useEffect(() => {
if (!modelId) {
return;
}
initPage();
}, [selectDomainId]);
}, [modelId]);
return (
<div style={{ width: 800, margin: '0 auto' }}>

View File

@@ -16,15 +16,15 @@ import { SENSITIVE_LEVEL_OPTIONS } from '../constant';
import { formLayout } from '@/components/FormHelper/utils';
import FormItemTitle from '@/components/FormHelper/FormItemTitle';
import styles from './style.less';
import { getMeasureListByDomainId } from '../service';
import { getMeasureListByModelId } from '../service';
import { creatExprMetric, updateExprMetric } from '../service';
import { ISemantic } from '../data';
import { history } from 'umi';
import { check } from 'prettier';
export type CreateFormProps = {
datasourceId?: number;
domainId: number;
modelId: number;
createModalVisible: boolean;
metricItem: any;
onCancel?: () => void;
@@ -39,6 +39,7 @@ const { Option } = Select;
const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
datasourceId,
domainId,
modelId,
onCancel,
createModalVisible,
metricItem,
@@ -65,7 +66,7 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
const backward = () => setCurrentStep(currentStep - 1);
const queryClassMeasureList = async () => {
const { code, data } = await getMeasureListByDomainId(domainId);
const { code, data } = await getMeasureListByModelId(modelId);
if (code === 200) {
setClassMeasureList(data);
if (datasourceId) {
@@ -147,7 +148,7 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
const saveMetric = async (fieldsValue: any) => {
const queryParams = {
domainId,
modelId,
...fieldsValue,
};
const { typeParams } = queryParams;
@@ -351,7 +352,7 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
type="primary"
key="console"
onClick={() => {
history.replace(`/semanticModel/${domainId}/dataSource`);
history.replace(`/semanticModel/${domainId}/${modelId}/dataSource`);
onCancel?.();
}}
>

View File

@@ -0,0 +1,95 @@
import React, { useState, useEffect } from 'react';
import { Form, Button, Modal, Input, Switch } from 'antd';
import styles from './style.less';
import { message } from 'antd';
import { formLayout } from '@/components/FormHelper/utils';
import { createModel, updateModel } from '../service';
const FormItem = Form.Item;
export type ModelCreateFormModalProps = {
domainId: number;
basicInfo: any;
onCancel: () => void;
onSubmit: (values: any) => void;
};
const ModelCreateFormModal: React.FC<ModelCreateFormModalProps> = (props) => {
const { basicInfo, domainId, onCancel, onSubmit } = props;
const [formVals, setFormVals] = useState<any>(basicInfo);
const [saveLoading, setSaveLoading] = useState(false);
const [form] = Form.useForm();
useEffect(() => {
form.setFieldsValue(basicInfo);
}, [basicInfo]);
const handleConfirm = async () => {
const fieldsValue = await form.validateFields();
const columnsValue = { ...fieldsValue, isUnique: 1, domainId };
const submitData = { ...formVals, ...columnsValue };
setFormVals(submitData);
setSaveLoading(true);
const { code, msg } = await (!submitData.id ? createModel : updateModel)(submitData);
setSaveLoading(false);
if (code === 200) {
onSubmit?.(submitData);
} else {
message.error(msg);
}
};
const footer = (
<>
<Button onClick={onCancel}></Button>
<Button type="primary" loading={saveLoading} onClick={handleConfirm}>
</Button>
</>
);
return (
<Modal
width={640}
bodyStyle={{ padding: '32px 40px 48px' }}
destroyOnClose
title={'模型信息'}
open={true}
footer={footer}
onCancel={onCancel}
>
<Form
{...formLayout}
form={form}
initialValues={{
...formVals,
}}
className={styles.form}
>
<FormItem
name="name"
label="模型名称"
rules={[{ required: true, message: '请输入模型名称!' }]}
>
<Input placeholder="模型名称不可重复" />
</FormItem>
<FormItem
name="bizName"
label="模型英文名称"
rules={[{ required: true, message: '请输入模型英文名称!' }]}
>
<Input placeholder="请输入模型英文名称" />
</FormItem>
<FormItem name="description" label="模型描述">
<Input.TextArea placeholder="模型描述" />
</FormItem>
<FormItem name="isUnique" label="是否唯一" hidden={true}>
<Switch size="small" checked={true} />
</FormItem>
</Form>
</Modal>
);
};
export default ModelCreateFormModal;

View File

@@ -1,23 +1,36 @@
import { CheckCard } from '@ant-design/pro-components';
import React from 'react';
import React, { useState } from 'react';
import { Button, Dropdown, message, Popconfirm } from 'antd';
import { PlusOutlined, EllipsisOutlined } from '@ant-design/icons';
import { ISemantic } from '../data';
import { connect } from 'umi';
import icon from '../../../assets/icon/cloudEditor.svg';
import type { Dispatch } from 'umi';
import type { StateType } from '../model';
import { formatNumber } from '../../../utils/utils';
import { deleteModel } from '../service';
import ModelCreateFormModal from './ModelCreateFormModal';
import styles from './style.less';
type Props = {
modelList: ISemantic.IDomainItem[];
disabledEdit?: boolean;
modelList: ISemantic.IModelItem[];
onModelChange?: (model?: ISemantic.IModelItem) => void;
domainManger: StateType;
dispatch: Dispatch;
};
const OverView: React.FC<Props> = ({ domainManger, dispatch, modelList }) => {
const { selectDomainId } = domainManger;
const OverView: React.FC<Props> = ({
modelList,
disabledEdit = false,
onModelChange,
domainManger,
}) => {
const { selectDomainId, selectModelId } = domainManger;
const [currentModel, setCurrentModel] = useState<any>({});
const [modelCreateFormModalVisible, setModelCreateFormModalVisible] = useState<boolean>(false);
const extraNode = (model: ISemantic.IDomainItem) => {
const descNode = (model: ISemantic.IDomainItem) => {
const { metricCnt, dimensionCnt } = model;
return (
<div className={styles.overviewExtraContainer}>
@@ -40,33 +53,103 @@ const OverView: React.FC<Props> = ({ domainManger, dispatch, modelList }) => {
</div>
);
};
const extraNode = (model: ISemantic.IDomainItem) => {
return (
<Dropdown
placement="top"
menu={{
onClick: ({ key, domEvent }) => {
domEvent.stopPropagation();
if (key === 'edit') {
setCurrentModel(model);
setModelCreateFormModalVisible(true);
}
},
items: [
{
label: '编辑',
key: 'edit',
},
{
label: (
<Popconfirm
title="确认删除?"
okText="是"
cancelText="否"
onConfirm={async () => {
const { code, msg } = await deleteModel(model.id);
if (code === 200) {
onModelChange?.();
} else {
message.error(msg);
}
}}
>
<a key="modelDeleteBtn"></a>
</Popconfirm>
),
key: 'delete',
},
],
}}
>
<EllipsisOutlined
style={{ fontSize: 22, color: 'rgba(0,0,0,0.5)' }}
onClick={(e) => e.stopPropagation()}
/>
</Dropdown>
);
};
return (
<>
<CheckCard.Group value={selectDomainId} defaultValue={selectDomainId}>
<div style={{ padding: '0px 20px 20px' }}>
{!disabledEdit && (
<div style={{ paddingBottom: '20px' }}>
<Button
onClick={() => {
setModelCreateFormModalVisible(true);
}}
type="primary"
>
<PlusOutlined />
</Button>
</div>
)}
<CheckCard.Group value={selectModelId} defaultValue={selectModelId}>
{modelList &&
modelList.map((model: ISemantic.IDomainItem) => {
return (
<CheckCard
avatar={icon}
title={model.name}
title={`${model.name}`}
key={model.id}
value={model.id}
// description={model.description || '模型描述...'}
description={extraNode(model)}
description={descNode(model)}
extra={!disabledEdit && extraNode(model)}
onClick={() => {
const { id, name } = model;
dispatch({
type: 'domainManger/setSelectDomain',
selectDomainId: id,
selectDomainName: name,
domainData: model,
});
onModelChange?.(model);
}}
/>
);
})}
</CheckCard.Group>
</>
{modelCreateFormModalVisible && (
<ModelCreateFormModal
domainId={selectDomainId}
basicInfo={currentModel}
onSubmit={() => {
setModelCreateFormModalVisible(false);
onModelChange?.();
}}
onCancel={() => {
setModelCreateFormModalVisible(false);
}}
/>
)}
</div>
);
};

View File

@@ -6,10 +6,11 @@ import { connect } from 'umi';
import type { Dispatch } from 'umi';
import type { StateType } from '../../model';
import FormItemTitle from '@/components/FormHelper/FormItemTitle';
import { updateDomain, getDomainDetail } from '../../service';
import { updateDomain, updateModel, getDomainDetail, getModelDetail } from '../../service';
import styles from '../style.less';
type Props = {
permissionTarget: 'model' | 'domain';
dispatch: Dispatch;
domainManger: StateType;
onSubmit?: (data?: any) => void;
@@ -18,15 +19,22 @@ type Props = {
const FormItem = Form.Item;
const PermissionAdminForm: React.FC<Props> = ({ domainManger, onValuesChange }) => {
const PermissionAdminForm: React.FC<Props> = ({
permissionTarget,
domainManger,
onValuesChange,
}) => {
const [form] = Form.useForm();
const [isOpenState, setIsOpenState] = useState<boolean>(true);
const [classDetail, setClassDetail] = useState<any>({});
const { selectDomainId } = domainManger;
const { selectModelId: modelId, selectDomainId } = domainManger;
const { APP_TARGET } = process.env;
const queryClassDetail = async (domainId: number) => {
const { code, msg, data } = await getDomainDetail({ domainId });
const queryClassDetail = async () => {
const selectId = permissionTarget === 'model' ? modelId : selectDomainId;
const { code, msg, data } = await (permissionTarget === 'model'
? getModelDetail
: getDomainDetail)({ modelId: selectId });
if (code === 200) {
setClassDetail(data);
const fieldsValue = {
@@ -44,8 +52,8 @@ const PermissionAdminForm: React.FC<Props> = ({ domainManger, onValuesChange })
};
useEffect(() => {
queryClassDetail(selectDomainId);
}, [selectDomainId]);
queryClassDetail();
}, [modelId]);
const saveAuth = async () => {
const values = await form.validateFields();
@@ -57,9 +65,10 @@ const PermissionAdminForm: React.FC<Props> = ({ domainManger, onValuesChange })
viewers,
isOpen: isOpen ? 1 : 0,
};
const { code, msg } = await updateDomain(queryClassData);
const { code, msg } = await (permissionTarget === 'model' ? updateModel : updateDomain)(
queryClassData,
);
if (code === 200) {
// message.success('保存成功');
return;
}
message.error(msg);
@@ -123,16 +132,6 @@ const PermissionAdminForm: React.FC<Props> = ({ domainManger, onValuesChange })
</FormItem>
</>
)}
{/* <FormItem>
<Button
type="primary"
onClick={() => {
saveAuth();
}}
>
保 存
</Button>
</FormItem> */}
</Form>
</>
);

View File

@@ -14,7 +14,6 @@ import styles from '../style.less';
type Props = {
domainManger: StateType;
permissonData: any;
domainId: number;
onCancel: () => void;
visible: boolean;
onSubmit: (params?: any) => void;
@@ -25,11 +24,10 @@ const PermissionCreateDrawer: React.FC<Props> = ({
domainManger,
visible,
permissonData,
domainId,
onCancel,
onSubmit,
}) => {
const { dimensionList, metricList } = domainManger;
const { dimensionList, metricList, selectModelId: modelId } = domainManger;
const [form] = Form.useForm();
const basicInfoFormRef = useRef<any>(null);
const [selectedDimensionKeyList, setSelectedDimensionKeyList] = useState<string[]>([]);
@@ -65,7 +63,7 @@ const PermissionCreateDrawer: React.FC<Props> = ({
metrics: selectedMetricKeyList,
},
],
domainId,
modelId,
});
if (code === 200) {
@@ -136,11 +134,7 @@ const PermissionCreateDrawer: React.FC<Props> = ({
<div style={{ overflow: 'auto', margin: '0 auto', width: '1200px' }}>
<Space direction="vertical" style={{ width: '100%' }} size={20}>
<ProCard title="基本信息" bordered>
<PermissionCreateForm
ref={basicInfoFormRef}
permissonData={permissonData}
domainId={domainId}
/>
<PermissionCreateForm ref={basicInfoFormRef} permissonData={permissonData} />
</ProCard>
<ProCard title="列权限" bordered tooltip="仅对敏感度为高的指标/维度进行授权">

View File

@@ -6,7 +6,6 @@ import SelectTMEPerson from '@/components/SelectTMEPerson';
import { formLayout } from '@/components/FormHelper/utils';
import styles from '../style.less';
type Props = {
domainId: number;
permissonData: any;
onSubmit?: (data?: any) => void;
onValuesChange?: (value, values) => void;

View File

@@ -8,20 +8,20 @@ import PermissionTable from './PermissionTable';
import PermissionAdminForm from './PermissionAdminForm';
type Props = {
permissionTarget: 'model' | 'domain';
dispatch: Dispatch;
domainManger: StateType;
};
const PermissionSection: React.FC<Props> = () => {
const PermissionSection: React.FC<Props> = ({ permissionTarget }) => {
return (
<>
<div>
<Space direction="vertical" style={{ width: '100%' }} size={20}>
<ProCard title="邀请成员" bordered>
<PermissionAdminForm />
<PermissionAdminForm permissionTarget={permissionTarget} />
</ProCard>
<PermissionTable />
{permissionTarget === 'model' && <PermissionTable />}
</Space>
</div>
</>

View File

@@ -19,7 +19,7 @@ type Props = {
const PermissionTable: React.FC<Props> = ({ domainManger }) => {
const { APP_TARGET } = process.env;
const isInner = APP_TARGET === 'inner';
const { dimensionList, metricList, selectDomainId } = domainManger;
const { dimensionList, metricList, selectModelId: modelId } = domainManger;
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
const [permissonData, setPermissonData] = useState<any>({});
@@ -37,7 +37,7 @@ const PermissionTable: React.FC<Props> = ({ domainManger }) => {
const actionRef = useRef<ActionType>();
const queryListData = async () => {
const { code, data } = await getGroupAuthInfo(selectDomainId);
const { code, data } = await getGroupAuthInfo(modelId);
if (code === 200) {
setIntentionList(data);
return;
@@ -46,10 +46,10 @@ const PermissionTable: React.FC<Props> = ({ domainManger }) => {
};
useEffect(() => {
if (selectDomainId) {
if (modelId) {
queryListData();
}
}, [selectDomainId]);
}, [modelId]);
const queryDepartmentData = async () => {
const { code, data } = await getOrganizationTree();
@@ -184,7 +184,7 @@ const PermissionTable: React.FC<Props> = ({ domainManger }) => {
return (
<Space>
<a
key="classEditBtn"
key="permissionEditBtn"
onClick={() => {
setPermissonData(record);
setCreateModalVisible(true);
@@ -216,7 +216,7 @@ const PermissionTable: React.FC<Props> = ({ domainManger }) => {
cancelText="否"
onConfirm={async () => {
const { code, msg } = await removeGroupAuth({
domainId: record.domainId,
modelId: record.modelId,
groupId: record.groupId,
});
if (code === 200) {
@@ -228,7 +228,7 @@ const PermissionTable: React.FC<Props> = ({ domainManger }) => {
}}
>
<a
key="classEditBtn"
key="permissionDeleteBtn"
onClick={() => {
setPermissonData(record);
}}
@@ -277,7 +277,6 @@ const PermissionTable: React.FC<Props> = ({ domainManger }) => {
/>
{createModalVisible && (
<PermissionCreateDrawer
domainId={Number(selectDomainId)}
visible={createModalVisible}
permissonData={permissonData}
onSubmit={() => {

View File

@@ -25,7 +25,6 @@ const ProjectInfoForm: React.FC<ProjectInfoFormProps> = (props) => {
const handleConfirm = async () => {
const fieldsValue = await form.validateFields();
// const columnsValue = { ...fieldsValue, isUnique: fieldsValue.isUnique === true ? 1 : 0 };
const columnsValue = { ...fieldsValue, isUnique: 1 };
setFormVals({ ...formVals, ...columnsValue });
setSaveLoading(true);
@@ -97,24 +96,11 @@ const ProjectInfoForm: React.FC<ProjectInfoFormProps> = (props) => {
>
<Input placeholder="请输入主题域英文名称" />
</FormItem>
<FormItem name="description" label="主题域描述">
<FormItem name="description" label="主题域描述" hidden={true}>
<Input.TextArea placeholder="主题域描述" />
</FormItem>
<FormItem name="isUnique" label="是否唯一" hidden={true}>
<Switch
size="small"
checked={true}
// onChange={(checked) => {
// setFormVals({ ...formVals, isUnique: checked });
// }}
/>
{/* <Switch
size="small"
checked={formVals.isUnique ? true : false}
onChange={(checked) => {
setFormVals({ ...formVals, isUnique: checked });
}}
/> */}
<Switch size="small" checked={true} />
</FormItem>
</Form>
</Modal>

View File

@@ -94,6 +94,30 @@ export declare namespace ISemantic {
metricCnt?: number;
}
interface IModelItem {
createdBy?: string;
updatedBy?: string;
createdAt?: string;
updatedAt?: string;
id: number;
name: string;
bizName: string;
description: any;
status?: number;
typeEnum?: any;
sensitiveLevel?: number;
parentId: number;
fullPath?: string;
viewers?: any[];
viewOrgs?: any[];
admins?: string[];
adminOrgs?: any[];
isOpen?: number;
entity?: { entityId: number; names: string[] };
dimensionCnt?: number;
metricCnt?: number;
}
interface IDimensionItem {
createdBy: string;
updatedBy: string;

View File

@@ -6,14 +6,17 @@ import { getDimensionList, queryMetric, excuteSql, getDatabaseByDomainId } from
export type StateType = {
current: number;
pageSize: number;
selectModelId: number;
selectDomainId: number;
selectDomainName: string;
selectModelName: string;
dimensionList: ISemantic.IDimensionList;
metricList: ISemantic.IMetricList;
searchParams: Record<string, any>;
dataBaseResultColsMap: any;
dataBaseConfig: any;
domainData?: ISemantic.IDomainItem;
modelData?: ISemantic.IDomainItem;
domainList: ISemantic.IDomainItem[];
};
@@ -28,6 +31,7 @@ export type ModelType = {
};
reducers: {
setSelectDomain: Reducer<StateType>;
setSelectModel: Reducer<StateType>;
setDomainList: Reducer<StateType>;
setPagination: Reducer<StateType>;
setDimensionList: Reducer<StateType>;
@@ -42,7 +46,10 @@ export const defaultState: StateType = {
current: 1,
pageSize: 20,
selectDomainId: 0,
selectModelId: 0,
modelData: undefined,
selectDomainName: '',
selectModelName: '',
searchParams: {},
dimensionList: [],
metricList: [],
@@ -123,6 +130,14 @@ const Model: ModelType = {
domainData: action.domainData,
};
},
setSelectModel(state = defaultState, action) {
return {
...state,
selectModelId: action.selectModelId,
selectModelName: action.selectModelName,
modelData: action.modelData,
};
},
setDomainList(state = defaultState, action) {
return {
...state,

View File

@@ -12,11 +12,11 @@ export function getDomainList(): Promise<any> {
}
export function getDatasourceList(data: any): Promise<any> {
return request.get(`${process.env.API_BASE_URL}datasource/getDatasourceList/${data.domainId}`);
return request.get(`${process.env.API_BASE_URL}datasource/getDatasourceList/${data.modelId}`);
}
export function getDomainDetail(data: any): Promise<any> {
return request.get(`${process.env.API_BASE_URL}domain/getDomain/${data.domainId}`);
return request.get(`${process.env.API_BASE_URL}domain/getDomain/${data.modelId}`);
}
export function createDomain(data: any): Promise<any> {
@@ -44,9 +44,15 @@ export function updateDatasource(data: any): Promise<any> {
}
export function getDimensionList(data: any): Promise<any> {
const { domainId } = data;
const { domainId, modelId } = data;
const queryParams = {
data: { current: 1, pageSize: 999999, ...data, ...(domainId ? { domainIds: [domainId] } : {}) },
data: {
current: 1,
pageSize: 999999,
...data,
...(domainId ? { domainIds: [domainId] } : {}),
...(modelId ? { modelIds: [modelId] } : {}),
},
};
if (getRunningEnv() === 'chat') {
return request.post(`${process.env.CHAT_API_BASE_URL}conf/dimension/page`, queryParams);
@@ -67,9 +73,15 @@ export function updateDimension(data: any): Promise<any> {
}
export function queryMetric(data: any): Promise<any> {
const { domainId } = data;
const { domainId, modelId } = data;
const queryParams = {
data: { current: 1, pageSize: 999999, ...data, ...(domainId ? { domainIds: [domainId] } : {}) },
data: {
current: 1,
pageSize: 999999,
...data,
...(domainId ? { domainIds: [domainId] } : {}),
...(modelId ? { modelIds: [modelId] } : {}),
},
};
if (getRunningEnv() === 'chat') {
return request.post(`${process.env.CHAT_API_BASE_URL}conf/metric/page`, queryParams);
@@ -89,8 +101,8 @@ export function updateExprMetric(data: any): Promise<any> {
});
}
export function getMeasureListByDomainId(domainId: number): Promise<any> {
return request.get(`${process.env.API_BASE_URL}datasource/getMeasureListOfDomain/${domainId}`);
export function getMeasureListByModelId(modelId: number): Promise<any> {
return request.get(`${process.env.API_BASE_URL}datasource/getMeasureListOfModel/${modelId}`);
}
export function deleteDatasource(id: any): Promise<any> {
@@ -117,12 +129,10 @@ export function deleteDomain(id: any): Promise<any> {
});
}
export function getGroupAuthInfo(id: number): Promise<any> {
export function getGroupAuthInfo(modelId: number): Promise<any> {
return request(`${process.env.AUTH_API_BASE_URL}queryGroup`, {
method: 'GET',
params: {
domainId: id,
},
params: { modelId },
});
}
@@ -169,7 +179,7 @@ export function getDomainExtendConfig(data: any): Promise<any> {
}
export function getDomainExtendDetailConfig(data: any): Promise<any> {
return request(`${process.env.CHAT_API_BASE_URL}conf/richDesc/${data.domainId}`, {
return request(`${process.env.CHAT_API_BASE_URL}conf/richDesc/${data.modelId}`, {
method: 'GET',
});
}
@@ -246,7 +256,7 @@ export function testDatabaseConnect(data: SaveDatabaseParams): Promise<any> {
type ExcuteSqlParams = {
sql: string;
domainId: number;
modelId: number;
};
// 执行脚本
@@ -272,3 +282,37 @@ export function getColumns(dbId: number, dbName: string, tableName: string): Pro
method: 'GET',
});
}
export function getModelList(domainId: number): Promise<any> {
if (getRunningEnv() === 'chat') {
return request(`${process.env.CHAT_API_BASE_URL}conf/modelList/${domainId}`, {
method: 'GET',
});
}
return request(`${process.env.API_BASE_URL}model/getModelList/${domainId}`, {
method: 'GET',
});
}
export function createModel(data: any): Promise<any> {
return request(`${process.env.API_BASE_URL}model/createModel`, {
method: 'POST',
data,
});
}
export function updateModel(data: any): Promise<any> {
return request(`${process.env.API_BASE_URL}model/updateModel`, {
method: 'POST',
data,
});
}
export function deleteModel(modelId: number): Promise<any> {
return request(`${process.env.API_BASE_URL}model/deleteModel/${modelId}`, {
method: 'DELETE',
});
}
export function getModelDetail(data: any): Promise<any> {
return request.get(`${process.env.API_BASE_URL}model/getModel/${data.modelId}`);
}

View File

@@ -175,12 +175,12 @@ declare namespace API {
comment: string; // 项目描述
creator: string; // 项目创建人
projectType: number; // 项目类别 0-为私有项目 1-为公共项目
childProjectList?: ProjectList;
children?: ProjectList;
childDomainList?: DomainList;
children?: DomainList;
value: string;
};
export type ProjectList = ProjectItem[];
export type DomainList = ProjectItem[];
// 数据实例详情
export type DataInstanceDetail = {