mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-17 16:02:14 +00:00
[improvement][project] supersonic 0.6.0 version update (#16)
Co-authored-by: lexluo <lexluo@tencent.com>
This commit is contained in:
@@ -1,15 +1,18 @@
|
||||
import { Tabs, Popover } from 'antd';
|
||||
import { Tabs, Popover, message } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { connect, Helmet } from 'umi';
|
||||
import { connect, Helmet, useParams, history } from 'umi';
|
||||
import ProjectListTree from './components/ProjectList';
|
||||
import styles from './components/style.less';
|
||||
import type { StateType } from './model';
|
||||
import { DownOutlined } from '@ant-design/icons';
|
||||
import EntitySection from './components/Entity/EntitySection';
|
||||
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';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
type Props = {
|
||||
domainManger: StateType;
|
||||
dispatch: Dispatch;
|
||||
@@ -17,8 +20,15 @@ type Props = {
|
||||
|
||||
const ChatSetting: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
window.RUNNING_ENV = 'chat';
|
||||
const { selectDomainId, selectDomainName } = domainManger;
|
||||
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);
|
||||
@@ -37,16 +47,103 @@ const ChatSetting: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
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} />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={styles.projectBody}>
|
||||
<Helmet title={'问答设置-超音数'} />
|
||||
{/* 页面改版取消侧边栏转换为popover形式后,因为popover不触发则组件不加载,需要保留原本页面初始化需要ProjectListTree向model中写入首个主题域数据逻辑,在此引入但并不显示 */}
|
||||
<div style={{ display: 'none' }}>
|
||||
<ProjectListTree />
|
||||
</div>
|
||||
<div className={styles.projectManger}>
|
||||
<h2 className={styles.title}>
|
||||
<Popover
|
||||
@@ -57,6 +154,7 @@ const ChatSetting: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
}}
|
||||
content={
|
||||
<ProjectListTree
|
||||
createDomainBtnVisible={false}
|
||||
onTreeSelected={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
@@ -78,11 +176,16 @@ const ChatSetting: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
</h2>
|
||||
{selectDomainId ? (
|
||||
<>
|
||||
<Tabs className={styles.tab} defaultActiveKey="chatSetting" destroyInactiveTabPane>
|
||||
<TabPane className={styles.tabPane} tab="问答设置" key="chatSetting">
|
||||
<EntitySection />
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
<Tabs
|
||||
className={styles.tab}
|
||||
activeKey={activeKey}
|
||||
destroyInactiveTabPane
|
||||
onChange={(menuKey: string) => {
|
||||
setActiveKey(menuKey);
|
||||
pushUrlMenu(selectDomainId, menuKey);
|
||||
}}
|
||||
items={!isModel ? tabItem : isModelItem}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<h2 className={styles.mainTip}>请选择项目</h2>
|
||||
|
||||
@@ -174,7 +174,7 @@ const FieldForm: React.FC<Props> = ({ fields, onFieldChange }) => {
|
||||
[isCreateName]: 1,
|
||||
});
|
||||
}}
|
||||
placeholder="请输入中文名"
|
||||
placeholder="请填写名称"
|
||||
/>
|
||||
</Checkbox>
|
||||
);
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
import { Tabs, Popover } from 'antd';
|
||||
import { Tabs, Popover, message } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { connect, Helmet } from 'umi';
|
||||
import { connect, Helmet, history, useParams } from 'umi';
|
||||
import ProjectListTree from './components/ProjectList';
|
||||
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 OverView from './components/OverView';
|
||||
import styles from './components/style.less';
|
||||
import type { StateType } from './model';
|
||||
import { DownOutlined } from '@ant-design/icons';
|
||||
import SemanticFlow from './SemanticFlows';
|
||||
import { ISemantic } from './data';
|
||||
import { findLeafNodesFromDomainList } from './utils';
|
||||
import SemanticGraph from './SemanticGraph';
|
||||
import { getDomainList } from './service';
|
||||
import type { Dispatch } from 'umi';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
type Props = {
|
||||
domainManger: StateType;
|
||||
dispatch: Dispatch;
|
||||
@@ -22,14 +25,89 @@ type Props = {
|
||||
|
||||
const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
window.RUNNING_ENV = 'semantic';
|
||||
const { selectDomainId, selectDomainName } = domainManger;
|
||||
|
||||
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);
|
||||
|
||||
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({
|
||||
@@ -53,21 +131,68 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
}
|
||||
}, [selectDomainId]);
|
||||
|
||||
useEffect(() => {
|
||||
const width = document.getElementById('tab');
|
||||
const switchWarpper: any = document.getElementById('switch');
|
||||
if (width && switchWarpper) {
|
||||
switchWarpper.style.width = width.offsetWidth * 0.77 + 'px';
|
||||
}
|
||||
});
|
||||
const tabItem = [
|
||||
{
|
||||
label: '子主题域',
|
||||
key: 'overview',
|
||||
children: <OverView modelList={modelList} />,
|
||||
},
|
||||
{
|
||||
label: '权限管理',
|
||||
key: 'permissonSetting',
|
||||
children: <PermissionSection />,
|
||||
},
|
||||
];
|
||||
|
||||
const isModelItem = [
|
||||
// {
|
||||
// label: '关系可视化',
|
||||
// key: 'graph',
|
||||
// children: (
|
||||
// <div style={{ width: '100%', height: 'calc(100vh - 200px)' }}>
|
||||
// <SemanticGraph domainId={selectDomainId} />
|
||||
// </div>
|
||||
// ),
|
||||
// },
|
||||
{
|
||||
label: '可视化建模',
|
||||
key: 'xflow',
|
||||
children: (
|
||||
<div style={{ width: '100%', height: 'calc(100vh - 200px)' }}>
|
||||
<SemanticFlow />
|
||||
</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: 'permissonSetting',
|
||||
children: <PermissionSection />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={styles.projectBody}>
|
||||
<Helmet title={'语义建模-超音数'} />
|
||||
{/* 页面改版取消侧边栏转换为popover形式后,因为popover不触发则组件不加载,需要保留原本页面初始化需要ProjectListTree向model中写入首个主题域数据逻辑,在此引入但并不显示 */}
|
||||
<div style={{ display: 'none' }}>
|
||||
<ProjectListTree />
|
||||
</div>
|
||||
<div className={styles.projectManger}>
|
||||
<h2 className={styles.title}>
|
||||
<Popover
|
||||
@@ -81,6 +206,9 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
onTreeSelected={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
onTreeDataUpdate={() => {
|
||||
initProjectTree();
|
||||
}}
|
||||
/>
|
||||
}
|
||||
trigger="click"
|
||||
@@ -89,7 +217,7 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
>
|
||||
<div className={styles.domainSelector}>
|
||||
<span className={styles.domainTitle}>
|
||||
{selectDomainName ? `选择的主题域:${selectDomainName}` : '主题域信息'}
|
||||
{selectDomainName ? `当前主题域:${selectDomainName}` : '主题域信息'}
|
||||
</span>
|
||||
<span className={styles.downIcon}>
|
||||
<DownOutlined />
|
||||
@@ -99,33 +227,16 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
</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>
|
||||
<Tabs
|
||||
className={styles.tab}
|
||||
items={!isModel ? tabItem : isModelItem}
|
||||
activeKey={activeKey}
|
||||
destroyInactiveTabPane
|
||||
onChange={(menuKey: string) => {
|
||||
setActiveKey(menuKey);
|
||||
pushUrlMenu(selectDomainId, menuKey);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<h2 className={styles.mainTip}>请选择项目</h2>
|
||||
|
||||
@@ -37,21 +37,21 @@ const DataSourceRelationFormDrawer: React.FC<DataSourceRelationFormDrawerProps>
|
||||
const dataSourceFromIdentifiers = sourceData?.datasourceDetail?.identifiers || [];
|
||||
const dataSourceToIdentifiers = targetData?.datasourceDetail?.identifiers || [];
|
||||
const dataSourceToIdentifiersNames = dataSourceToIdentifiers.map((item) => {
|
||||
return item.name;
|
||||
return item.bizName;
|
||||
});
|
||||
const keyOptions = dataSourceFromIdentifiers.reduce((options: any[], item: any) => {
|
||||
const { name } = item;
|
||||
if (dataSourceToIdentifiersNames.includes(name)) {
|
||||
const { bizName } = item;
|
||||
if (dataSourceToIdentifiersNames.includes(bizName)) {
|
||||
options.push(item);
|
||||
}
|
||||
return options;
|
||||
}, []);
|
||||
setDataSourceOptions(
|
||||
keyOptions.map((item: any) => {
|
||||
const { name } = item;
|
||||
const { name, bizName } = item;
|
||||
return {
|
||||
label: name,
|
||||
value: name,
|
||||
value: bizName,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -156,6 +156,10 @@ const XflowJsonSchemaFormDrawerForm: React.FC<CreateFormProps> = (props) => {
|
||||
{
|
||||
<ClassDataSourceTypeModal
|
||||
open={createDataSourceModalOpen}
|
||||
onCancel={() => {
|
||||
resetSelectedNode();
|
||||
setCreateDataSourceModalOpen(false);
|
||||
}}
|
||||
onTypeChange={(type) => {
|
||||
if (type === 'fast') {
|
||||
setDataSourceModalVisible(true);
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import G6 from '@antv/g6';
|
||||
import '../style.less';
|
||||
// define the CSS with the id of your menu
|
||||
|
||||
// insertCss(`
|
||||
// #contextMenu {
|
||||
// position: absolute;
|
||||
// list-style-type: none;
|
||||
// padding: 10px 8px;
|
||||
// left: -150px;
|
||||
// background-color: rgba(255, 255, 255, 0.9);
|
||||
// border: 1px solid #e2e2e2;
|
||||
// border-radius: 4px;
|
||||
// font-size: 12px;
|
||||
// color: #545454;
|
||||
// }
|
||||
// #contextMenu li {
|
||||
// cursor: pointer;
|
||||
// list-style-type:none;
|
||||
// list-style: none;
|
||||
// margin-left: 0px;
|
||||
// }
|
||||
// #contextMenu li:hover {
|
||||
// color: #aaa;
|
||||
// }
|
||||
// `);
|
||||
|
||||
const initContextMenu = () => {
|
||||
const contextMenu = new G6.Menu({
|
||||
getContent(evt) {
|
||||
const itemType = evt!.item!.getType();
|
||||
console.log(this, evt?.item?._cfg, 333);
|
||||
const nodeData = evt?.item?._cfg?.model;
|
||||
const { name } = nodeData as any;
|
||||
if (nodeData) {
|
||||
const header = `${name}`;
|
||||
return `<div class="g6ContextMenuContainer">
|
||||
<h3>${header}</h3>
|
||||
<ul>
|
||||
<li title='2'>编辑</li>
|
||||
<li title='1'>删除</li>
|
||||
</ul>
|
||||
</div>`;
|
||||
}
|
||||
return `<div>当前节点信息获取失败</div>`;
|
||||
},
|
||||
handleMenuClick(target, item) {
|
||||
console.log(contextMenu, target, item);
|
||||
const graph = contextMenu._cfgs.graph;
|
||||
},
|
||||
// offsetX and offsetY include the padding of the parent container
|
||||
// 需要加上父级容器的 padding-left 16 与自身偏移量 10
|
||||
offsetX: 16 + 10,
|
||||
// 需要加上父级容器的 padding-top 24 、画布兄弟元素高度、与自身偏移量 10
|
||||
offsetY: 0,
|
||||
// the types of items that allow the menu show up
|
||||
// 在哪些类型的元素上响应
|
||||
itemTypes: ['node'],
|
||||
});
|
||||
|
||||
return contextMenu;
|
||||
};
|
||||
export default initContextMenu;
|
||||
@@ -7,6 +7,7 @@ import { message, Row, Col, Radio } from 'antd';
|
||||
import { getDatasourceList, getDomainSchemaRela } from '../service';
|
||||
import initToolBar from './components/ToolBar';
|
||||
import initTooltips from './components/ToolTips';
|
||||
import initContextMenu from './components/ContextMenu';
|
||||
import G6 from '@antv/g6';
|
||||
|
||||
type Props = {
|
||||
@@ -209,6 +210,7 @@ const DomainManger: React.FC<Props> = ({ domainManger, domainId }) => {
|
||||
|
||||
const toolbar = initToolBar();
|
||||
const tooltip = initTooltips();
|
||||
const contextMenu = initContextMenu();
|
||||
const legend = new G6.Legend({
|
||||
// container: 'legendContainer',
|
||||
data: {
|
||||
@@ -303,30 +305,30 @@ const DomainManger: React.FC<Props> = ({ domainManger, domainId }) => {
|
||||
// },
|
||||
},
|
||||
layout: {
|
||||
type: 'mindmap',
|
||||
direction: 'H',
|
||||
getId: function getId(d) {
|
||||
return d.id;
|
||||
},
|
||||
getHeight: function getHeight() {
|
||||
return 16;
|
||||
},
|
||||
getWidth: function getWidth() {
|
||||
return 16;
|
||||
},
|
||||
getVGap: function getVGap() {
|
||||
return 30;
|
||||
},
|
||||
getHGap: function getHGap() {
|
||||
return 100;
|
||||
},
|
||||
// type: 'dendrogram',
|
||||
// direction: 'LR',
|
||||
// nodeSep: 200,
|
||||
// rankSep: 300,
|
||||
// radial: true,
|
||||
// type: 'mindmap',
|
||||
// direction: 'H',
|
||||
// getId: function getId(d) {
|
||||
// return d.id;
|
||||
// },
|
||||
// getHeight: function getHeight() {
|
||||
// return 16;
|
||||
// },
|
||||
// getWidth: function getWidth() {
|
||||
// return 16;
|
||||
// },
|
||||
// getVGap: function getVGap() {
|
||||
// return 30;
|
||||
// },
|
||||
// getHGap: function getHGap() {
|
||||
// return 100;
|
||||
// },
|
||||
type: 'dendrogram',
|
||||
direction: 'LR',
|
||||
nodeSep: 200,
|
||||
rankSep: 300,
|
||||
radial: true,
|
||||
},
|
||||
plugins: [legend, tooltip, toolbar],
|
||||
plugins: [legend, tooltip, toolbar, contextMenu],
|
||||
});
|
||||
|
||||
const legendCanvas = legend._cfgs.legendCanvas;
|
||||
|
||||
@@ -757,3 +757,4 @@
|
||||
height: 16px !important;
|
||||
margin: 0 3px 4px;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,10 @@ const { Meta } = Card;
|
||||
type Props = {
|
||||
open: boolean;
|
||||
onTypeChange: (type: 'fast' | 'normal') => void;
|
||||
onCancel?: () => void;
|
||||
};
|
||||
|
||||
const ClassDataSourceTypeModal: React.FC<Props> = ({ open, onTypeChange }) => {
|
||||
const ClassDataSourceTypeModal: React.FC<Props> = ({ open, onTypeChange, onCancel }) => {
|
||||
const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false);
|
||||
useEffect(() => {
|
||||
setCreateDataSourceModalOpen(open);
|
||||
@@ -19,6 +20,7 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({ open, onTypeChange }) => {
|
||||
open={createDataSourceModalOpen}
|
||||
onCancel={() => {
|
||||
setCreateDataSourceModalOpen(false);
|
||||
onCancel?.();
|
||||
}}
|
||||
footer={null}
|
||||
centered
|
||||
|
||||
@@ -91,6 +91,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
{
|
||||
dataIndex: 'alias',
|
||||
title: '别名',
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
dataIndex: 'bizName',
|
||||
|
||||
@@ -71,6 +71,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
{
|
||||
dataIndex: 'alias',
|
||||
title: '别名',
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
dataIndex: 'bizName',
|
||||
@@ -91,6 +92,25 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
title: '描述',
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
dataIndex: 'type',
|
||||
title: '指标类型',
|
||||
// search: false,
|
||||
valueEnum: {
|
||||
ATOMIC: '原子指标',
|
||||
DERIVED: '衍生指标',
|
||||
},
|
||||
// render: (type: any) => {
|
||||
// switch (type) {
|
||||
// case 'ATOMIC':
|
||||
// return '原子指标';
|
||||
// case 'DERIVED':
|
||||
// return '衍生指标';
|
||||
// default:
|
||||
// return '未知';
|
||||
// }
|
||||
// },
|
||||
},
|
||||
|
||||
{
|
||||
dataIndex: 'updatedAt',
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Form, Input, Modal, Select } from 'antd';
|
||||
import { Button, Form, Input, Modal, Select, List } from 'antd';
|
||||
import { SENSITIVE_LEVEL_OPTIONS } from '../constant';
|
||||
import { formLayout } from '@/components/FormHelper/utils';
|
||||
import SqlEditor from '@/components/SqlEditor';
|
||||
import InfoTagList from './InfoTagList';
|
||||
import { message } from 'antd';
|
||||
|
||||
export type CreateFormProps = {
|
||||
@@ -31,6 +32,7 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
|
||||
users: [],
|
||||
effectiveTime: 1,
|
||||
});
|
||||
|
||||
const [form] = Form.useForm();
|
||||
const { setFieldsValue } = form;
|
||||
|
||||
@@ -45,6 +47,7 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
|
||||
};
|
||||
|
||||
const setFormVal = () => {
|
||||
console.log(dimensionItem, 'dimensionItem');
|
||||
setFieldsValue(dimensionItem);
|
||||
};
|
||||
|
||||
@@ -128,6 +131,9 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
|
||||
))}
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem name="defaultValues" label="默认值">
|
||||
<InfoTagList />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
name="description"
|
||||
label="维度描述"
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
import { useEffect, useState, forwardRef, useImperativeHandle } from 'react';
|
||||
import type { ForwardRefRenderFunction } from 'react';
|
||||
import FormItemTitle from '@/components/FormHelper/FormItemTitle';
|
||||
import { formLayout } from '@/components/FormHelper/utils';
|
||||
import { message, Form, Input, Select, Button, InputNumber } from 'antd';
|
||||
import { addDomainExtend, editDomainExtend } from '../../service';
|
||||
import {
|
||||
formatRichEntityDataListToIds,
|
||||
wrapperTransTypeAndId,
|
||||
splitListToTransTypeId,
|
||||
} from './utils';
|
||||
import styles from '../style.less';
|
||||
import { ISemantic } from '../../data';
|
||||
import { ChatConfigType, TransType } from '../../enum';
|
||||
import TransTypeTag from '../TransTypeTag';
|
||||
|
||||
type Props = {
|
||||
entityData: any;
|
||||
chatConfigKey: string;
|
||||
chatConfigType: ChatConfigType.DETAIL | ChatConfigType.AGG;
|
||||
metricList: ISemantic.IMetricItem[];
|
||||
dimensionList: ISemantic.IDimensionItem[];
|
||||
domainId: number;
|
||||
onSubmit: (params?: any) => void;
|
||||
};
|
||||
|
||||
const FormItem = Form.Item;
|
||||
const Option = Select.Option;
|
||||
|
||||
const DefaultSettingForm: ForwardRefRenderFunction<any, Props> = (
|
||||
{ metricList, dimensionList, domainId, entityData, chatConfigKey, chatConfigType, onSubmit },
|
||||
ref,
|
||||
) => {
|
||||
const [form] = Form.useForm();
|
||||
const [metricListOptions, setMetricListOptions] = useState<any>([]);
|
||||
const [unitState, setUnit] = useState<number | null>();
|
||||
const [periodState, setPeriod] = useState<string>();
|
||||
const [dataItemListOptions, setDataItemListOptions] = useState<any>([]);
|
||||
const formatEntityData = formatRichEntityDataListToIds(entityData);
|
||||
const getFormValidateFields = async () => {
|
||||
return await form.validateFields();
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getFormValidateFields,
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
form.resetFields();
|
||||
setUnit(null);
|
||||
setPeriod('');
|
||||
if (!entityData?.chatDefaultConfig) {
|
||||
return;
|
||||
}
|
||||
const { chatDefaultConfig, id } = formatEntityData;
|
||||
const { period, unit } = chatDefaultConfig;
|
||||
setUnit(unit);
|
||||
setPeriod(period);
|
||||
form.setFieldsValue({
|
||||
...chatDefaultConfig,
|
||||
id,
|
||||
});
|
||||
if (chatConfigType === ChatConfigType.DETAIL) {
|
||||
initDataItemValue(chatDefaultConfig);
|
||||
}
|
||||
}, [entityData, dataItemListOptions]);
|
||||
|
||||
const initDataItemValue = (chatDefaultConfig: {
|
||||
dimensionIds: number[];
|
||||
metricIds: number[];
|
||||
}) => {
|
||||
const { dimensionIds, metricIds } = chatDefaultConfig;
|
||||
const dimensionIdString = dimensionIds.map((dimensionId: number) => {
|
||||
return wrapperTransTypeAndId(TransType.DIMENSION, dimensionId);
|
||||
});
|
||||
const metricIdString = metricIds.map((metricId: number) => {
|
||||
return wrapperTransTypeAndId(TransType.METRIC, metricId);
|
||||
});
|
||||
form.setFieldsValue({
|
||||
dataItemIds: [...dimensionIdString, ...metricIdString],
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const metricOption = metricList.map((item: any) => {
|
||||
return {
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
};
|
||||
});
|
||||
setMetricListOptions(metricOption);
|
||||
}, [metricList]);
|
||||
|
||||
useEffect(() => {
|
||||
if (Array.isArray(dimensionList) && Array.isArray(metricList)) {
|
||||
const dimensionEnum = dimensionList.map((item: ISemantic.IDimensionItem) => {
|
||||
const { name, id, bizName } = item;
|
||||
return {
|
||||
name,
|
||||
label: (
|
||||
<>
|
||||
<TransTypeTag type={TransType.DIMENSION} />
|
||||
{name}
|
||||
</>
|
||||
),
|
||||
value: wrapperTransTypeAndId(TransType.DIMENSION, id),
|
||||
bizName,
|
||||
id,
|
||||
transType: TransType.DIMENSION,
|
||||
};
|
||||
});
|
||||
const metricEnum = metricList.map((item: ISemantic.IMetricItem) => {
|
||||
const { name, id, bizName } = item;
|
||||
return {
|
||||
name,
|
||||
label: (
|
||||
<>
|
||||
<TransTypeTag type={TransType.METRIC} />
|
||||
{name}
|
||||
</>
|
||||
),
|
||||
value: wrapperTransTypeAndId(TransType.METRIC, id),
|
||||
bizName,
|
||||
id,
|
||||
transType: TransType.METRIC,
|
||||
};
|
||||
});
|
||||
setDataItemListOptions([...dimensionEnum, ...metricEnum]);
|
||||
}
|
||||
}, [dimensionList, metricList]);
|
||||
|
||||
const saveEntity = async () => {
|
||||
const values = await form.validateFields();
|
||||
const { id, dataItemIds } = values;
|
||||
let dimensionConfig = {};
|
||||
if (dataItemIds) {
|
||||
const { dimensionIds, metricIds } = splitListToTransTypeId(dataItemIds);
|
||||
dimensionConfig = {
|
||||
dimensionIds,
|
||||
metricIds,
|
||||
};
|
||||
}
|
||||
let saveDomainExtendQuery = addDomainExtend;
|
||||
if (id) {
|
||||
saveDomainExtendQuery = editDomainExtend;
|
||||
}
|
||||
const params = {
|
||||
...formatEntityData,
|
||||
chatDefaultConfig: { ...values, ...dimensionConfig },
|
||||
};
|
||||
const { code, msg, data } = await saveDomainExtendQuery({
|
||||
[chatConfigKey]: params,
|
||||
domainId,
|
||||
id,
|
||||
});
|
||||
if (code === 200) {
|
||||
form.setFieldValue('id', data);
|
||||
onSubmit?.();
|
||||
message.success('保存成功');
|
||||
return;
|
||||
}
|
||||
message.error(msg);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form
|
||||
{...formLayout}
|
||||
form={form}
|
||||
layout="vertical"
|
||||
className={styles.form}
|
||||
initialValues={{
|
||||
unit: 7,
|
||||
period: 'DAY',
|
||||
}}
|
||||
>
|
||||
<FormItem hidden={true} name="id" label="ID">
|
||||
<Input placeholder="id" />
|
||||
</FormItem>
|
||||
|
||||
{chatConfigType === ChatConfigType.DETAIL && (
|
||||
<FormItem name="dataItemIds" label="展示维度/指标">
|
||||
<Select
|
||||
mode="multiple"
|
||||
allowClear
|
||||
style={{ width: '100%' }}
|
||||
optionLabelProp="name"
|
||||
filterOption={(inputValue: string, item: any) => {
|
||||
const { name } = item;
|
||||
if (name.includes(inputValue)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}}
|
||||
placeholder="请选择展示维度/指标信息"
|
||||
options={dataItemListOptions}
|
||||
/>
|
||||
</FormItem>
|
||||
)}
|
||||
{chatConfigType === ChatConfigType.AGG && (
|
||||
<FormItem
|
||||
name="metricIds"
|
||||
label={
|
||||
<FormItemTitle
|
||||
title={'指标'}
|
||||
subTitle={'问答搜索结果选择中,如果没有指定指标,将会采用默认指标进行展示'}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Select
|
||||
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}
|
||||
/>
|
||||
</FormItem>
|
||||
)}
|
||||
|
||||
<FormItem
|
||||
label={
|
||||
<FormItemTitle
|
||||
title={'时间范围'}
|
||||
subTitle={'问答搜索结果选择中,如果没有指定时间范围,将会采用默认时间范围'}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Input.Group compact>
|
||||
<span
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
lineHeight: '32px',
|
||||
marginRight: '8px',
|
||||
}}
|
||||
>
|
||||
{chatConfigType === ChatConfigType.DETAIL ? '前' : '最近'}
|
||||
</span>
|
||||
<InputNumber
|
||||
value={unitState}
|
||||
style={{ width: '120px' }}
|
||||
onChange={(value) => {
|
||||
setUnit(value);
|
||||
form.setFieldValue('unit', value);
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
value={periodState}
|
||||
style={{ width: '100px' }}
|
||||
onChange={(value) => {
|
||||
form.setFieldValue('period', value);
|
||||
setPeriod(value);
|
||||
}}
|
||||
>
|
||||
<Option value="DAY">天</Option>
|
||||
<Option value="WEEK">周</Option>
|
||||
<Option value="MONTH">月</Option>
|
||||
<Option value="YEAR">年</Option>
|
||||
</Select>
|
||||
</Input.Group>
|
||||
</FormItem>
|
||||
|
||||
<FormItem name="unit" hidden={true}>
|
||||
<InputNumber />
|
||||
</FormItem>
|
||||
<FormItem name="period" hidden={true}>
|
||||
<Input />
|
||||
</FormItem>
|
||||
|
||||
<FormItem>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
saveEntity();
|
||||
}}
|
||||
>
|
||||
保 存
|
||||
</Button>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default forwardRef(DefaultSettingForm);
|
||||
@@ -0,0 +1,205 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { Button, Modal, message, Tabs } from 'antd';
|
||||
|
||||
import { addDomainExtend, editDomainExtend } from '../../service';
|
||||
import DimensionMetricVisibleTransfer from './DimensionMetricVisibleTransfer';
|
||||
import { IChatConfig } from '../../data';
|
||||
import DimensionValueSettingForm from './DimensionValueSettingForm';
|
||||
import { TransType } from '../../enum';
|
||||
import { wrapperTransTypeAndId, formatRichEntityDataListToIds } from './utils';
|
||||
|
||||
type Props = {
|
||||
domainId: number;
|
||||
entityData: any;
|
||||
chatConfigKey: string;
|
||||
settingSourceList: any[];
|
||||
onCancel: () => void;
|
||||
visible: boolean;
|
||||
onSubmit: (params?: any) => void;
|
||||
};
|
||||
|
||||
const dimensionConfig = {
|
||||
blackIdListKey: 'blackDimIdList',
|
||||
visibleIdListKey: 'whiteDimIdList',
|
||||
modalTitle: '问答可见信息',
|
||||
titles: ['不可见维度/指标', '可见维度/指标'],
|
||||
};
|
||||
|
||||
const DimensionAndMetricVisibleModal: React.FC<Props> = ({
|
||||
domainId,
|
||||
visible,
|
||||
entityData = {},
|
||||
chatConfigKey,
|
||||
settingSourceList,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
}) => {
|
||||
const [selectedKeyList, setSelectedKeyList] = useState<string[]>([]);
|
||||
const settingTypeConfig = dimensionConfig;
|
||||
const formatEntityData = formatRichEntityDataListToIds(entityData);
|
||||
const [knowledgeInfosMap, setKnowledgeInfosMap] = useState<IChatConfig.IKnowledgeInfosItemMap>(
|
||||
{},
|
||||
);
|
||||
const formRef = useRef<any>();
|
||||
|
||||
const [globalKnowledgeConfigInitialValues, setGlobalKnowledgeConfigInitialValues] =
|
||||
useState<IChatConfig.IKnowledgeConfig>();
|
||||
|
||||
useEffect(() => {
|
||||
if (entityData?.visibility && Array.isArray(settingSourceList)) {
|
||||
const { whiteDimIdList, whiteMetricIdList } = entityData.visibility;
|
||||
const dimensionIdString = whiteDimIdList.map((dimensionId: number) => {
|
||||
return wrapperTransTypeAndId(TransType.DIMENSION, dimensionId);
|
||||
});
|
||||
const metricIdString = whiteMetricIdList.map((metricId: number) => {
|
||||
return wrapperTransTypeAndId(TransType.METRIC, metricId);
|
||||
});
|
||||
setSelectedKeyList([...dimensionIdString, ...metricIdString]);
|
||||
}
|
||||
if (entityData?.globalKnowledgeConfig) {
|
||||
setGlobalKnowledgeConfigInitialValues(entityData.globalKnowledgeConfig);
|
||||
}
|
||||
if (Array.isArray(entityData?.knowledgeInfos)) {
|
||||
const infoMap = entityData.knowledgeInfos.reduce(
|
||||
(maps: IChatConfig.IKnowledgeInfosItemMap, item: IChatConfig.IKnowledgeInfosItem) => {
|
||||
const { bizName } = item;
|
||||
maps[bizName] = item;
|
||||
return maps;
|
||||
},
|
||||
{},
|
||||
);
|
||||
setKnowledgeInfosMap(infoMap);
|
||||
}
|
||||
}, [entityData, settingSourceList]);
|
||||
|
||||
const saveEntity = async () => {
|
||||
const globalKnowledgeConfigFormFields = await formRef?.current?.getFormValidateFields?.();
|
||||
let globalKnowledgeConfig = entityData.globalKnowledgeConfig;
|
||||
if (globalKnowledgeConfigFormFields) {
|
||||
globalKnowledgeConfig = globalKnowledgeConfigFormFields;
|
||||
}
|
||||
const { id } = entityData;
|
||||
let saveDomainExtendQuery = addDomainExtend;
|
||||
if (id) {
|
||||
saveDomainExtendQuery = editDomainExtend;
|
||||
}
|
||||
|
||||
const blackIdListMap = settingSourceList.reduce(
|
||||
(ids, item) => {
|
||||
const { id, transType } = item;
|
||||
if (!selectedKeyList.includes(wrapperTransTypeAndId(transType, id))) {
|
||||
if (transType === TransType.DIMENSION) {
|
||||
ids.blackDimIdList.push(id);
|
||||
}
|
||||
if (transType === TransType.METRIC) {
|
||||
ids.blackMetricIdList.push(id);
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
},
|
||||
{
|
||||
blackDimIdList: [],
|
||||
blackMetricIdList: [],
|
||||
},
|
||||
);
|
||||
|
||||
const knowledgeInfos = Object.keys(knowledgeInfosMap).reduce(
|
||||
(infoList: IChatConfig.IKnowledgeInfosItem[], key: string) => {
|
||||
const target = knowledgeInfosMap[key];
|
||||
if (target.searchEnable) {
|
||||
infoList.push(target);
|
||||
}
|
||||
return infoList;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const params = {
|
||||
...formatEntityData,
|
||||
visibility: blackIdListMap,
|
||||
knowledgeInfos,
|
||||
...(globalKnowledgeConfig ? { globalKnowledgeConfig } : {}),
|
||||
};
|
||||
|
||||
const { code, msg } = await saveDomainExtendQuery({
|
||||
[chatConfigKey]: params,
|
||||
domainId,
|
||||
id,
|
||||
});
|
||||
if (code === 200) {
|
||||
onSubmit?.();
|
||||
message.success('保存成功');
|
||||
return;
|
||||
}
|
||||
message.error(msg);
|
||||
};
|
||||
|
||||
const handleTransferChange = (newTargetKeys: string[]) => {
|
||||
setSelectedKeyList(newTargetKeys);
|
||||
};
|
||||
|
||||
const renderFooter = () => {
|
||||
return (
|
||||
<>
|
||||
<Button onClick={onCancel}>取消</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
saveEntity();
|
||||
}}
|
||||
>
|
||||
完成
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const tabItem = [
|
||||
{
|
||||
label: '可见设置',
|
||||
key: 'visibleSetting',
|
||||
children: (
|
||||
<DimensionMetricVisibleTransfer
|
||||
onKnowledgeInfosMapChange={(knowledgeInfosMap) => {
|
||||
setKnowledgeInfosMap(knowledgeInfosMap);
|
||||
}}
|
||||
knowledgeInfosMap={knowledgeInfosMap}
|
||||
titles={settingTypeConfig.titles}
|
||||
sourceList={settingSourceList}
|
||||
targetList={selectedKeyList}
|
||||
onChange={(newTargetKeys) => {
|
||||
handleTransferChange(newTargetKeys);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: '全局维度值过滤',
|
||||
key: 'dimensionValueFilter',
|
||||
children: (
|
||||
<DimensionValueSettingForm
|
||||
initialValues={globalKnowledgeConfigInitialValues}
|
||||
ref={formRef}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
width={1200}
|
||||
destroyOnClose
|
||||
title={settingTypeConfig.modalTitle}
|
||||
maskClosable={false}
|
||||
open={visible}
|
||||
footer={renderFooter()}
|
||||
onCancel={onCancel}
|
||||
>
|
||||
<Tabs items={tabItem} defaultActiveKey="visibleSetting" />
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DimensionAndMetricVisibleModal;
|
||||
@@ -3,11 +3,13 @@ import type { ForwardRefRenderFunction } from 'react';
|
||||
import { Form, Button } from 'antd';
|
||||
import FormItemTitle from '@/components/FormHelper/FormItemTitle';
|
||||
import { formLayout } from '@/components/FormHelper/utils';
|
||||
import DimensionMetricVisibleModal from './DimensionMetricVisibleModal';
|
||||
import DimensionSearchVisibleModal from './DimensionSearchVisibleModal';
|
||||
import DimensionAndMetricVisibleModal from './DimensionAndMetricVisibleModal';
|
||||
import { TransType } from '../../enum';
|
||||
import { wrapperTransTypeAndId } from './utils';
|
||||
|
||||
type Props = {
|
||||
themeData: any;
|
||||
entityData: any;
|
||||
chatConfigKey: string;
|
||||
metricList: any[];
|
||||
dimensionList: any[];
|
||||
domainId: number;
|
||||
@@ -20,18 +22,17 @@ const DimensionMetricVisibleForm: ForwardRefRenderFunction<any, Props> = ({
|
||||
domainId,
|
||||
metricList,
|
||||
dimensionList,
|
||||
themeData,
|
||||
entityData,
|
||||
chatConfigKey,
|
||||
onSubmit,
|
||||
}) => {
|
||||
const [dimensionModalVisible, setDimensionModalVisible] = useState(false);
|
||||
const [dimensionSearchModalVisible, setDimensionSearchModalVisible] = useState(false);
|
||||
const [metricModalVisible, setMetricModalVisible] = useState<boolean>(false);
|
||||
return (
|
||||
<>
|
||||
<Form {...formLayout}>
|
||||
<FormItem
|
||||
label={
|
||||
<FormItemTitle title={'可见维度'} subTitle={'设置可见后,维度将允许在问答中被使用'} />
|
||||
<FormItemTitle title={'可见维度/指标'} subTitle={'设置可见后,将允许在问答中被使用'} />
|
||||
}
|
||||
>
|
||||
<Button
|
||||
@@ -43,44 +44,32 @@ const DimensionMetricVisibleForm: ForwardRefRenderFunction<any, Props> = ({
|
||||
设 置
|
||||
</Button>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label={
|
||||
<FormItemTitle title={'可见指标'} subTitle={'设置可见后,指标将允许在问答中被使用'} />
|
||||
}
|
||||
>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
setMetricModalVisible(true);
|
||||
}}
|
||||
>
|
||||
设 置
|
||||
</Button>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label={
|
||||
<FormItemTitle
|
||||
title={'可见维度值'}
|
||||
subTitle={'设置可见后,在可见维度设置的基础上,维度值将在搜索时可以被联想出来'}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
setDimensionSearchModalVisible(true);
|
||||
}}
|
||||
>
|
||||
设 置
|
||||
</Button>
|
||||
</FormItem>
|
||||
</Form>
|
||||
{dimensionModalVisible && (
|
||||
<DimensionMetricVisibleModal
|
||||
<DimensionAndMetricVisibleModal
|
||||
domainId={domainId}
|
||||
themeData={themeData}
|
||||
settingSourceList={dimensionList}
|
||||
settingType="dimension"
|
||||
entityData={entityData}
|
||||
chatConfigKey={chatConfigKey}
|
||||
settingSourceList={[
|
||||
...dimensionList.map((item) => {
|
||||
const transType = TransType.DIMENSION;
|
||||
const { id } = item;
|
||||
return {
|
||||
...item,
|
||||
transType,
|
||||
key: wrapperTransTypeAndId(transType, id),
|
||||
};
|
||||
}),
|
||||
...metricList.map((item) => {
|
||||
const transType = TransType.METRIC;
|
||||
const { id } = item;
|
||||
return {
|
||||
...item,
|
||||
transType,
|
||||
key: wrapperTransTypeAndId(transType, id),
|
||||
};
|
||||
}),
|
||||
]}
|
||||
visible={dimensionModalVisible}
|
||||
onCancel={() => {
|
||||
setDimensionModalVisible(false);
|
||||
@@ -91,43 +80,6 @@ const DimensionMetricVisibleForm: ForwardRefRenderFunction<any, Props> = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{dimensionSearchModalVisible && (
|
||||
<DimensionSearchVisibleModal
|
||||
domainId={domainId}
|
||||
settingSourceList={dimensionList.filter((item) => {
|
||||
const blackDimensionList = themeData.visibility?.blackDimIdList;
|
||||
if (Array.isArray(blackDimensionList)) {
|
||||
return !blackDimensionList.includes(item.id);
|
||||
}
|
||||
return false;
|
||||
})}
|
||||
themeData={themeData}
|
||||
visible={dimensionSearchModalVisible}
|
||||
onCancel={() => {
|
||||
setDimensionSearchModalVisible(false);
|
||||
}}
|
||||
onSubmit={() => {
|
||||
onSubmit?.({ from: 'dimensionSearchVisible' });
|
||||
setDimensionSearchModalVisible(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{metricModalVisible && (
|
||||
<DimensionMetricVisibleModal
|
||||
domainId={domainId}
|
||||
themeData={themeData}
|
||||
settingSourceList={metricList}
|
||||
settingType="metric"
|
||||
visible={metricModalVisible}
|
||||
onCancel={() => {
|
||||
setMetricModalVisible(false);
|
||||
}}
|
||||
onSubmit={() => {
|
||||
onSubmit?.();
|
||||
setMetricModalVisible(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
import { Space, Table, Transfer, Checkbox, Tooltip, Button } from 'antd';
|
||||
import type { ColumnsType, TableRowSelection } from 'antd/es/table/interface';
|
||||
import type { TransferItem } from 'antd/es/transfer';
|
||||
import type { CheckboxChangeEvent } from 'antd/es/checkbox';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons';
|
||||
import difference from 'lodash/difference';
|
||||
import React, { useState } from 'react';
|
||||
import type { IChatConfig } from '../../data';
|
||||
import DimensionValueSettingModal from './DimensionValueSettingModal';
|
||||
import TransTypeTag from '../TransTypeTag';
|
||||
import { TransType } from '../../enum';
|
||||
|
||||
interface RecordType {
|
||||
id: number;
|
||||
key: string;
|
||||
name: string;
|
||||
bizName: string;
|
||||
type: TransType.DIMENSION | TransType.METRIC;
|
||||
}
|
||||
|
||||
type Props = {
|
||||
knowledgeInfosMap: IChatConfig.IKnowledgeInfosItemMap;
|
||||
onKnowledgeInfosMapChange: (knowledgeInfosMap: IChatConfig.IKnowledgeInfosItemMap) => void;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
const DimensionMetricVisibleTableTransfer: React.FC<Props> = ({
|
||||
knowledgeInfosMap,
|
||||
onKnowledgeInfosMapChange,
|
||||
...restProps
|
||||
}) => {
|
||||
const [dimensionValueSettingModalVisible, setDimensionValueSettingModalVisible] =
|
||||
useState<boolean>(false);
|
||||
const [currentRecord, setCurrentRecord] = useState<any>({});
|
||||
const [currentDimensionSettingFormData, setCurrentDimensionSettingFormData] =
|
||||
useState<IChatConfig.IKnowledgeConfig>();
|
||||
|
||||
const updateKnowledgeInfosMap = (record: RecordType, updateData: Record<string, any>) => {
|
||||
const { bizName, id } = record;
|
||||
const knowledgeMap = {
|
||||
...knowledgeInfosMap,
|
||||
};
|
||||
const target = knowledgeMap[bizName];
|
||||
if (target) {
|
||||
knowledgeMap[bizName] = {
|
||||
...target,
|
||||
...updateData,
|
||||
};
|
||||
} else {
|
||||
knowledgeMap[bizName] = {
|
||||
itemId: id,
|
||||
bizName,
|
||||
...updateData,
|
||||
};
|
||||
}
|
||||
onKnowledgeInfosMapChange?.(knowledgeMap);
|
||||
};
|
||||
|
||||
const rightColumns: ColumnsType<RecordType> = [
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: '名称',
|
||||
},
|
||||
{
|
||||
dataIndex: 'type',
|
||||
width: 80,
|
||||
title: '类型',
|
||||
render: (type) => {
|
||||
return <TransTypeTag type={type} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'y',
|
||||
title: (
|
||||
<Space>
|
||||
<span>维度值可见</span>
|
||||
<Tooltip title="勾选可见后,维度值将在搜索时可以被联想出来">
|
||||
<ExclamationCircleOutlined />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
),
|
||||
width: 120,
|
||||
render: (_, record) => {
|
||||
const { type, bizName } = record;
|
||||
return type === TransType.DIMENSION ? (
|
||||
<Checkbox
|
||||
checked={knowledgeInfosMap[bizName]?.searchEnable}
|
||||
onChange={(e: CheckboxChangeEvent) => {
|
||||
updateKnowledgeInfosMap(record, { searchEnable: e.target.checked });
|
||||
}}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'x',
|
||||
render: (_, record) => {
|
||||
const { type, bizName } = record;
|
||||
return type === TransType.DIMENSION ? (
|
||||
<Button
|
||||
style={{ padding: 0 }}
|
||||
key="editable"
|
||||
type="link"
|
||||
disabled={!knowledgeInfosMap[bizName]?.searchEnable}
|
||||
onClick={(event) => {
|
||||
setCurrentRecord(record);
|
||||
setCurrentDimensionSettingFormData(
|
||||
knowledgeInfosMap[bizName]?.knowledgeAdvancedConfig,
|
||||
);
|
||||
setDimensionValueSettingModalVisible(true);
|
||||
event.stopPropagation();
|
||||
}}
|
||||
>
|
||||
可见维度值设置
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const leftColumns: ColumnsType<RecordType> = [
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: '名称',
|
||||
},
|
||||
{
|
||||
dataIndex: 'type',
|
||||
title: '类型',
|
||||
render: (type) => {
|
||||
return <TransTypeTag type={type} />;
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<Transfer {...restProps}>
|
||||
{({
|
||||
direction,
|
||||
filteredItems,
|
||||
onItemSelectAll,
|
||||
onItemSelect,
|
||||
selectedKeys: listSelectedKeys,
|
||||
}) => {
|
||||
const columns = direction === 'left' ? leftColumns : rightColumns;
|
||||
const rowSelection: TableRowSelection<TransferItem> = {
|
||||
onSelectAll(selected, selectedRows) {
|
||||
const treeSelectedKeys = selectedRows.map(({ key }) => key);
|
||||
const diffKeys = selected
|
||||
? difference(treeSelectedKeys, listSelectedKeys)
|
||||
: difference(listSelectedKeys, treeSelectedKeys);
|
||||
onItemSelectAll(diffKeys as string[], selected);
|
||||
},
|
||||
onSelect({ key }, selected) {
|
||||
onItemSelect(key as string, selected);
|
||||
},
|
||||
selectedRowKeys: listSelectedKeys,
|
||||
};
|
||||
|
||||
return (
|
||||
<Table
|
||||
rowSelection={rowSelection}
|
||||
columns={columns}
|
||||
dataSource={filteredItems as any}
|
||||
size="small"
|
||||
pagination={false}
|
||||
scroll={{ y: 450 }}
|
||||
onRow={({ key }) => ({
|
||||
onClick: () => {
|
||||
onItemSelect(key as string, !listSelectedKeys.includes(key as string));
|
||||
},
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Transfer>
|
||||
<DimensionValueSettingModal
|
||||
visible={dimensionValueSettingModalVisible}
|
||||
initialValues={currentDimensionSettingFormData}
|
||||
onSubmit={(formValues) => {
|
||||
updateKnowledgeInfosMap(currentRecord, { knowledgeAdvancedConfig: formValues });
|
||||
setDimensionValueSettingModalVisible(false);
|
||||
}}
|
||||
onCancel={() => {
|
||||
setDimensionValueSettingModalVisible(false);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DimensionMetricVisibleTableTransfer;
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Transfer, Tag } from 'antd';
|
||||
import { Tag } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { IChatConfig } from '../../data';
|
||||
import DimensionMetricVisibleTableTransfer from './DimensionMetricVisibleTableTransfer';
|
||||
|
||||
interface RecordType {
|
||||
key: string;
|
||||
@@ -8,14 +10,18 @@ interface RecordType {
|
||||
}
|
||||
|
||||
type Props = {
|
||||
knowledgeInfosMap: IChatConfig.IKnowledgeInfosItemMap;
|
||||
sourceList: any[];
|
||||
targetList: string[];
|
||||
titles?: string[];
|
||||
onKnowledgeInfosMapChange: (knowledgeInfosMap: IChatConfig.IKnowledgeInfosItemMap) => void;
|
||||
onChange?: (params?: any) => void;
|
||||
transferProps?: Record<string, any>;
|
||||
};
|
||||
|
||||
const DimensionMetricVisibleTransfer: React.FC<Props> = ({
|
||||
knowledgeInfosMap,
|
||||
onKnowledgeInfosMapChange,
|
||||
sourceList = [],
|
||||
targetList = [],
|
||||
titles,
|
||||
@@ -27,11 +33,13 @@ const DimensionMetricVisibleTransfer: React.FC<Props> = ({
|
||||
|
||||
useEffect(() => {
|
||||
setTransferData(
|
||||
sourceList.map(({ id, name, type }: any) => {
|
||||
sourceList.map(({ key, id, name, bizName, transType }) => {
|
||||
return {
|
||||
key: id,
|
||||
key,
|
||||
name,
|
||||
type,
|
||||
bizName,
|
||||
id,
|
||||
type: transType,
|
||||
};
|
||||
}),
|
||||
);
|
||||
@@ -48,13 +56,15 @@ const DimensionMetricVisibleTransfer: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Transfer
|
||||
<DimensionMetricVisibleTableTransfer
|
||||
knowledgeInfosMap={knowledgeInfosMap}
|
||||
onKnowledgeInfosMapChange={onKnowledgeInfosMapChange}
|
||||
dataSource={transferData}
|
||||
showSearch
|
||||
titles={titles || ['不可见维度', '可见维度']}
|
||||
listStyle={{
|
||||
width: 430,
|
||||
height: 500,
|
||||
width: 500,
|
||||
height: 600,
|
||||
}}
|
||||
filterOption={(inputValue: string, item: any) => {
|
||||
const { name } = item;
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||
import type { ForwardRefRenderFunction } from 'react';
|
||||
import { Form, Input } from 'antd';
|
||||
|
||||
import { formLayout } from '@/components/FormHelper/utils';
|
||||
import { isString } from 'lodash';
|
||||
import styles from '../style.less';
|
||||
|
||||
import SqlEditor from '@/components/SqlEditor';
|
||||
type Props = {
|
||||
initialValues: any;
|
||||
onSubmit?: () => void;
|
||||
};
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
const EntityCreateForm: ForwardRefRenderFunction<any, Props> = ({ initialValues }, ref) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const exchangeFields = ['blackList', 'whiteList', 'ruleList'];
|
||||
|
||||
const getFormValidateFields = async () => {
|
||||
const fields = await form.validateFields();
|
||||
const fieldValue = Object.keys(fields).reduce((formField, key: string) => {
|
||||
const targetValue = fields[key];
|
||||
if (isString(targetValue) && exchangeFields.includes(key)) {
|
||||
formField[key] = targetValue.split(',');
|
||||
} else {
|
||||
formField[key] = targetValue;
|
||||
}
|
||||
return formField;
|
||||
}, {});
|
||||
return {
|
||||
...fieldValue,
|
||||
};
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
form.resetFields();
|
||||
if (!initialValues) {
|
||||
return;
|
||||
}
|
||||
const fieldValue = Object.keys(initialValues).reduce((formField, key: string) => {
|
||||
const targetValue = initialValues[key];
|
||||
if (Array.isArray(targetValue) && exchangeFields.includes(key)) {
|
||||
formField[key] = targetValue.join(',');
|
||||
} else {
|
||||
formField[key] = targetValue;
|
||||
}
|
||||
return formField;
|
||||
}, {});
|
||||
form.setFieldsValue({
|
||||
...fieldValue,
|
||||
});
|
||||
}, [initialValues]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getFormValidateFields,
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form {...formLayout} form={form} layout="vertical" className={styles.form}>
|
||||
<FormItem name="blackList" label="黑名单">
|
||||
<Input placeholder="多个维度值用英文逗号隔开" />
|
||||
</FormItem>
|
||||
|
||||
<FormItem name="whiteList" label="白名单">
|
||||
<Input placeholder="多个维度值用英文逗号隔开" />
|
||||
</FormItem>
|
||||
|
||||
<FormItem name="ruleList" label="过滤规则">
|
||||
<SqlEditor height={'150px'} />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default forwardRef(EntityCreateForm);
|
||||
@@ -0,0 +1,56 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { Button, Modal } from 'antd';
|
||||
import DimensionValueSettingForm from './DimensionValueSettingForm';
|
||||
|
||||
type Props = {
|
||||
initialValues: any;
|
||||
onCancel?: () => void;
|
||||
visible: boolean;
|
||||
onSubmit?: (params?: any) => void;
|
||||
};
|
||||
const DimensionValueSettingModal: React.FC<Props> = ({
|
||||
initialValues,
|
||||
visible,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
}) => {
|
||||
const formRef = useRef<any>();
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const formValues = await formRef.current.getFormValidateFields();
|
||||
onSubmit?.(formValues);
|
||||
};
|
||||
|
||||
const renderFooter = () => {
|
||||
return (
|
||||
<>
|
||||
<Button onClick={onCancel}>取消</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
handleSubmit();
|
||||
}}
|
||||
>
|
||||
完成
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
width={600}
|
||||
destroyOnClose
|
||||
title={'维度值设置'}
|
||||
maskClosable={false}
|
||||
open={visible}
|
||||
footer={renderFooter()}
|
||||
onCancel={onCancel}
|
||||
>
|
||||
<DimensionValueSettingForm initialValues={initialValues} ref={formRef} />
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DimensionValueSettingModal;
|
||||
@@ -4,27 +4,24 @@ 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 { formatRichEntityDataListToIds } from './utils';
|
||||
import styles from '../style.less';
|
||||
|
||||
type Props = {
|
||||
entityData: IChatConfig.IEntity;
|
||||
metricList: ISemantic.IMetricList;
|
||||
entityData: IChatConfig.IChatRichConfig;
|
||||
dimensionList: ISemantic.IDimensionList;
|
||||
domainId: number;
|
||||
onSubmit: () => void;
|
||||
};
|
||||
|
||||
const FormItem = Form.Item;
|
||||
const TextArea = Input.TextArea;
|
||||
|
||||
const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
|
||||
{ entityData, metricList, dimensionList, domainId, onSubmit },
|
||||
{ entityData, dimensionList, domainId, onSubmit },
|
||||
ref,
|
||||
) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const [metricListOptions, setMetricListOptions] = useState<any>([]);
|
||||
const formatEntityData = formatRichEntityDataListToIds(entityData);
|
||||
const [dimensionListOptions, setDimensionListOptions] = useState<any>([]);
|
||||
|
||||
const getFormValidateFields = async () => {
|
||||
@@ -33,29 +30,19 @@ const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
|
||||
|
||||
useEffect(() => {
|
||||
form.resetFields();
|
||||
if (Object.keys(entityData).length === 0) {
|
||||
if (!entityData?.entity) {
|
||||
return;
|
||||
}
|
||||
const names = entityData.names || [];
|
||||
const formatEntityData = exChangeRichEntityListToIds(entityData);
|
||||
|
||||
form.setFieldsValue({
|
||||
...formatEntityData,
|
||||
name: names.join(','),
|
||||
...formatEntityData.entity,
|
||||
id: formatEntityData.id,
|
||||
});
|
||||
}, [entityData]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getFormValidateFields,
|
||||
}));
|
||||
useEffect(() => {
|
||||
const metricOption = metricList.map((item: ISemantic.IMetricItem) => {
|
||||
return {
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
};
|
||||
});
|
||||
setMetricListOptions(metricOption);
|
||||
}, [metricList]);
|
||||
|
||||
useEffect(() => {
|
||||
const dimensionEnum = dimensionList.map((item: ISemantic.IDimensionItem) => {
|
||||
@@ -75,10 +62,14 @@ const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
|
||||
saveDomainExtendQuery = editDomainExtend;
|
||||
}
|
||||
const { code, msg, data } = await saveDomainExtendQuery({
|
||||
entity: {
|
||||
...values,
|
||||
names: name.split(','),
|
||||
chatDetailConfig: {
|
||||
...formatEntityData,
|
||||
entity: {
|
||||
...values,
|
||||
names: name.split(','),
|
||||
},
|
||||
},
|
||||
id,
|
||||
domainId,
|
||||
});
|
||||
|
||||
@@ -99,66 +90,31 @@ const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
|
||||
</FormItem>
|
||||
<FormItem
|
||||
name="name"
|
||||
label="实体名称"
|
||||
rules={[{ required: true, message: '请输入实体名称' }]}
|
||||
label="实体别名"
|
||||
// rules={[{ required: true, message: '请输入实体别名' }]}
|
||||
>
|
||||
<TextArea
|
||||
placeholder="请输入实体名称,多个实体名称以英文逗号分隔"
|
||||
style={{ height: 100 }}
|
||||
/>
|
||||
<Input placeholder="请输入实体别名,多个名称以英文逗号分隔" />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
name="entityIds"
|
||||
name="entityId"
|
||||
label="唯一标识"
|
||||
rules={[{ required: true, message: '请选择实体标识' }]}
|
||||
// rules={[{ required: true, message: '请选择实体标识' }]}
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
// mode="multiple"
|
||||
allowClear
|
||||
style={{ width: '100%' }}
|
||||
filterOption={(inputValue: string, item: any) => {
|
||||
const { label } = item;
|
||||
if (label.includes(inputValue)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}}
|
||||
// filterOption={(inputValue: string, item: any) => {
|
||||
// const { label } = item;
|
||||
// if (label.includes(inputValue)) {
|
||||
// return true;
|
||||
// }
|
||||
// return false;
|
||||
// }}
|
||||
placeholder="请选择主体标识"
|
||||
options={dimensionListOptions}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem name={['detailData', 'dimensionIds']} label="维度信息">
|
||||
<Select
|
||||
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}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem name={['detailData', 'metricIds']} label="指标信息">
|
||||
<Select
|
||||
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}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<Button
|
||||
type="primary"
|
||||
|
||||
@@ -3,24 +3,28 @@ import React, { useState, useEffect, useRef } from 'react';
|
||||
import type { Dispatch } from 'umi';
|
||||
import { connect } from 'umi';
|
||||
import type { StateType } from '../../model';
|
||||
import { getDomainExtendConfig, getDomainExtendDetailConfig } from '../../service';
|
||||
import { getDomainExtendDetailConfig } from '../../service';
|
||||
import ProCard from '@ant-design/pro-card';
|
||||
import EntityCreateForm from './EntityCreateForm';
|
||||
import MetricSettingForm from './MetricSettingForm';
|
||||
import DefaultSettingForm from './DefaultSettingForm';
|
||||
import type { IChatConfig } from '../../data';
|
||||
import DimensionMetricVisibleForm from './DimensionMetricVisibleForm';
|
||||
import { ChatConfigType } from '../../enum';
|
||||
|
||||
type Props = {
|
||||
chatConfigType: ChatConfigType.DETAIL | ChatConfigType.AGG;
|
||||
dispatch: Dispatch;
|
||||
domainManger: StateType;
|
||||
};
|
||||
|
||||
const EntitySection: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
const EntitySection: React.FC<Props> = ({
|
||||
domainManger,
|
||||
dispatch,
|
||||
chatConfigType = ChatConfigType.DETAIL,
|
||||
}) => {
|
||||
const { selectDomainId, dimensionList, metricList } = domainManger;
|
||||
|
||||
const [entityData, setEntityData] = useState<IChatConfig.IEntity>({} as IChatConfig.IEntity);
|
||||
|
||||
const [themeData, setThemeData] = useState<any>({});
|
||||
const [entityData, setentityData] = useState<IChatConfig.IChatRichConfig>();
|
||||
|
||||
const entityCreateRef = useRef<any>({});
|
||||
|
||||
@@ -28,21 +32,19 @@ const EntitySection: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
const { code, data } = await getDomainExtendDetailConfig({
|
||||
domainId: selectDomainId,
|
||||
});
|
||||
// getDomainExtendConfig({
|
||||
// domainId: selectDomainId,
|
||||
// });
|
||||
|
||||
if (code === 200) {
|
||||
const target = data;
|
||||
if (target) {
|
||||
setThemeData(target);
|
||||
setEntityData({
|
||||
id: target.id,
|
||||
...target.entity,
|
||||
});
|
||||
const { chatAggRichConfig, chatDetailRichConfig, id, domainId } = data;
|
||||
if (chatConfigType === ChatConfigType.DETAIL) {
|
||||
setentityData({ ...chatDetailRichConfig, id, domainId });
|
||||
}
|
||||
if (chatConfigType === ChatConfigType.AGG) {
|
||||
setentityData({ ...chatAggRichConfig, id, domainId });
|
||||
}
|
||||
return;
|
||||
}
|
||||
message.error('获取主题域解析词失败');
|
||||
|
||||
message.error('获取问答设置信息失败');
|
||||
};
|
||||
|
||||
const initPage = async () => {
|
||||
@@ -56,9 +58,31 @@ const EntitySection: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
return (
|
||||
<div style={{ width: 800, margin: '0 auto' }}>
|
||||
<Space direction="vertical" style={{ width: '100%' }} size={20}>
|
||||
{chatConfigType === 'detail' && entityData && (
|
||||
<ProCard title="实体" bordered>
|
||||
<EntityCreateForm
|
||||
ref={entityCreateRef}
|
||||
domainId={Number(selectDomainId)}
|
||||
entityData={entityData}
|
||||
dimensionList={dimensionList.filter((item) => {
|
||||
const blackDimensionList = entityData?.visibility?.blackDimIdList;
|
||||
if (Array.isArray(blackDimensionList)) {
|
||||
return !blackDimensionList.includes(item.id);
|
||||
}
|
||||
return false;
|
||||
})}
|
||||
onSubmit={() => {
|
||||
queryThemeListData();
|
||||
}}
|
||||
/>
|
||||
</ProCard>
|
||||
)}
|
||||
<ProCard bordered title="问答可见">
|
||||
<DimensionMetricVisibleForm
|
||||
themeData={themeData}
|
||||
chatConfigKey={
|
||||
chatConfigType === ChatConfigType.DETAIL ? 'chatDetailConfig' : 'chatAggConfig'
|
||||
}
|
||||
entityData={entityData || {}}
|
||||
domainId={Number(selectDomainId)}
|
||||
metricList={metricList}
|
||||
dimensionList={dimensionList}
|
||||
@@ -75,44 +99,28 @@ const EntitySection: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
}}
|
||||
/>
|
||||
</ProCard>
|
||||
<ProCard bordered title="默认指标">
|
||||
<MetricSettingForm
|
||||
<ProCard bordered title="默认设置">
|
||||
<DefaultSettingForm
|
||||
domainId={Number(selectDomainId)}
|
||||
themeData={themeData}
|
||||
// metricList={metricList}
|
||||
metricList={metricList.filter((item) => {
|
||||
const blackMetricIdList = themeData.visibility?.blackMetricIdList;
|
||||
if (Array.isArray(blackMetricIdList)) {
|
||||
return !blackMetricIdList.includes(item.id);
|
||||
}
|
||||
return false;
|
||||
})}
|
||||
onSubmit={() => {
|
||||
queryThemeListData();
|
||||
}}
|
||||
/>
|
||||
</ProCard>
|
||||
<ProCard title="实体" bordered>
|
||||
<EntityCreateForm
|
||||
ref={entityCreateRef}
|
||||
domainId={Number(selectDomainId)}
|
||||
entityData={entityData}
|
||||
// metricList={metricList}
|
||||
metricList={metricList.filter((item) => {
|
||||
const blackMetricIdList = themeData.visibility?.blackMetricIdList;
|
||||
if (Array.isArray(blackMetricIdList)) {
|
||||
return !blackMetricIdList.includes(item.id);
|
||||
}
|
||||
return false;
|
||||
})}
|
||||
// dimensionList={dimensionList}
|
||||
entityData={entityData || {}}
|
||||
chatConfigType={chatConfigType}
|
||||
chatConfigKey={
|
||||
chatConfigType === ChatConfigType.DETAIL ? 'chatDetailConfig' : 'chatAggConfig'
|
||||
}
|
||||
dimensionList={dimensionList.filter((item) => {
|
||||
const blackDimensionList = themeData.visibility?.blackDimIdList;
|
||||
const blackDimensionList = entityData?.visibility?.blackDimIdList;
|
||||
if (Array.isArray(blackDimensionList)) {
|
||||
return !blackDimensionList.includes(item.id);
|
||||
}
|
||||
return false;
|
||||
})}
|
||||
metricList={metricList.filter((item) => {
|
||||
const blackMetricIdList = entityData?.visibility?.blackMetricIdList;
|
||||
if (Array.isArray(blackMetricIdList)) {
|
||||
return !blackMetricIdList.includes(item.id);
|
||||
}
|
||||
return false;
|
||||
})}
|
||||
onSubmit={() => {
|
||||
queryThemeListData();
|
||||
}}
|
||||
|
||||
@@ -1,28 +1,87 @@
|
||||
import { IChatConfig, ISemantic } from '../../data';
|
||||
import { TransType } from '../../enum';
|
||||
|
||||
type FormatResult = IChatConfig.IChatRichConfig & {
|
||||
chatDefaultConfig: {
|
||||
dimensionIds: number[];
|
||||
metricIds: number[];
|
||||
};
|
||||
};
|
||||
export const formatRichEntityDataListToIds = (
|
||||
entityData: IChatConfig.IChatRichConfig,
|
||||
): FormatResult => {
|
||||
if (!entityData?.chatDefaultConfig) {
|
||||
return {} as FormatResult;
|
||||
}
|
||||
const { chatDefaultConfig, entity } = entityData;
|
||||
|
||||
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) => {
|
||||
const { dimensions, metrics } = chatDefaultConfig || {};
|
||||
if (Array.isArray(dimensions)) {
|
||||
detailData.dimensionIds = dimensions.map((item: ISemantic.IDimensionItem) => {
|
||||
return item.id;
|
||||
});
|
||||
}
|
||||
if (Array.isArray(metricList)) {
|
||||
detailData.metricIds = metricList.map((item: ISemantic.IMetricItem) => {
|
||||
if (Array.isArray(metrics)) {
|
||||
detailData.metricIds = metrics.map((item: ISemantic.IMetricItem) => {
|
||||
return item.id;
|
||||
});
|
||||
}
|
||||
const entityIds = entityList.map((item) => {
|
||||
return item.id;
|
||||
});
|
||||
let entitySetting = {};
|
||||
if (entity) {
|
||||
const entityItem = entity.dimItem;
|
||||
const entityId = entityItem?.id || null;
|
||||
const names = entity.names || [];
|
||||
entitySetting = {
|
||||
entity: {
|
||||
...entity,
|
||||
entityId,
|
||||
name: names.join(','),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...entityData,
|
||||
entityIds,
|
||||
detailData,
|
||||
...entitySetting,
|
||||
chatDefaultConfig: {
|
||||
...chatDefaultConfig,
|
||||
...detailData,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const wrapperTransTypeAndId = (exTransType: TransType, id: number) => {
|
||||
return `${exTransType}-${id}`;
|
||||
};
|
||||
|
||||
export const splitListToTransTypeId = (dataItemIds: string[]) => {
|
||||
const idListMap = dataItemIds.reduce(
|
||||
(
|
||||
idMap: {
|
||||
dimensionIds: number[];
|
||||
metricIds: number[];
|
||||
},
|
||||
item: string,
|
||||
) => {
|
||||
const [transType, id] = item.split('-');
|
||||
if (id) {
|
||||
if (transType === TransType.DIMENSION) {
|
||||
idMap.dimensionIds.push(Number(id));
|
||||
}
|
||||
if (transType === TransType.METRIC) {
|
||||
idMap.metricIds.push(Number(id));
|
||||
}
|
||||
}
|
||||
return idMap;
|
||||
},
|
||||
{
|
||||
dimensionIds: [],
|
||||
metricIds: [],
|
||||
},
|
||||
);
|
||||
return idListMap;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import type { InputRef } from 'antd';
|
||||
import { Input as AntdInput, Tag, Tooltip } from 'antd';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import styles from './style.less';
|
||||
|
||||
type Props = {
|
||||
createBtnString?: string;
|
||||
value?: string[];
|
||||
onChange?: (valueList: string[]) => void;
|
||||
};
|
||||
|
||||
const InfoTagList: React.FC<Props> = ({ value, createBtnString = '新增', onChange }) => {
|
||||
const [tags, setTags] = useState<string[]>([]);
|
||||
const [inputVisible, setInputVisible] = useState(false);
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [editInputIndex, setEditInputIndex] = useState(-1);
|
||||
const [editInputValue, setEditInputValue] = useState('');
|
||||
const inputRef = useRef<InputRef>(null);
|
||||
const editInputRef = useRef<InputRef>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (Array.isArray(value)) {
|
||||
setTags([...value]);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
const handleTagChange = (tagList: string[]) => {
|
||||
console.log(tagList, 'tagList');
|
||||
onChange?.(tagList);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (inputVisible) {
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
}, [inputVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
editInputRef.current?.focus();
|
||||
}, [inputValue]);
|
||||
|
||||
const handleClose = (removedTag: string) => {
|
||||
const newTags = tags.filter((tag) => tag !== removedTag);
|
||||
handleTagChange?.(newTags);
|
||||
setTags(newTags);
|
||||
};
|
||||
|
||||
const showInput = () => {
|
||||
setInputVisible(true);
|
||||
};
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setInputValue(e.target.value);
|
||||
};
|
||||
|
||||
const handleInputConfirm = () => {
|
||||
if (inputValue && tags.indexOf(inputValue) === -1) {
|
||||
const newTags = [...tags, inputValue];
|
||||
handleTagChange?.(newTags);
|
||||
setTags(newTags);
|
||||
}
|
||||
setInputVisible(false);
|
||||
setInputValue('');
|
||||
};
|
||||
|
||||
const handleEditInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setEditInputValue(e.target.value);
|
||||
};
|
||||
|
||||
const handleEditInputConfirm = () => {
|
||||
const newTags = [...tags];
|
||||
newTags[editInputIndex] = editInputValue;
|
||||
setTags(newTags);
|
||||
handleTagChange?.(newTags);
|
||||
setEditInputIndex(-1);
|
||||
setInputValue('');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.infoTagList}>
|
||||
{tags.map((tag, index) => {
|
||||
if (editInputIndex === index) {
|
||||
return (
|
||||
<AntdInput
|
||||
ref={editInputRef}
|
||||
key={tag}
|
||||
size="small"
|
||||
className={styles.tagInput}
|
||||
value={editInputValue}
|
||||
onChange={handleEditInputChange}
|
||||
onBlur={handleEditInputConfirm}
|
||||
onPressEnter={handleEditInputConfirm}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const isLongTag = tag.length > 20;
|
||||
|
||||
const tagElem = (
|
||||
<Tag
|
||||
className={styles.editTag}
|
||||
key={tag}
|
||||
closable={true}
|
||||
onClose={() => handleClose(tag)}
|
||||
>
|
||||
<span
|
||||
onDoubleClick={(e) => {
|
||||
setEditInputIndex(index);
|
||||
setEditInputValue(tag);
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
|
||||
</span>
|
||||
</Tag>
|
||||
);
|
||||
return isLongTag ? (
|
||||
<Tooltip title={tag} key={tag}>
|
||||
{tagElem}
|
||||
</Tooltip>
|
||||
) : (
|
||||
tagElem
|
||||
);
|
||||
})}
|
||||
{inputVisible && (
|
||||
<AntdInput
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
size="small"
|
||||
className={styles.tagInput}
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
onBlur={handleInputConfirm}
|
||||
onPressEnter={handleInputConfirm}
|
||||
/>
|
||||
)}
|
||||
{!inputVisible && (
|
||||
<Tag className={styles.siteTagPlus} onClick={showInput}>
|
||||
<PlusOutlined /> {createBtnString}
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfoTagList;
|
||||
@@ -88,6 +88,7 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
||||
typeParams: typeParams,
|
||||
dataFormat,
|
||||
dataFormatType,
|
||||
alias,
|
||||
} = metricItem as any;
|
||||
const isPercent = dataFormatType === 'percent';
|
||||
const initValue = {
|
||||
@@ -97,6 +98,7 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
||||
sensitiveLevel,
|
||||
description,
|
||||
isPercent,
|
||||
alias,
|
||||
dataFormat: dataFormat || {
|
||||
decimalPlaces: 2,
|
||||
needMultiply100: false,
|
||||
|
||||
@@ -72,7 +72,7 @@ const MetricMeasuresFormTable: React.FC<Props> = ({
|
||||
dataIndex: 'constraint',
|
||||
title: '限定条件',
|
||||
tooltip:
|
||||
'所用于过滤的维度需要存在于"维度"列表,不需要加where关键字。比如:维度A="值1" and 维度B="值2"',
|
||||
'该限定条件用于在计算指标时限定口径,作用于度量,所用于过滤的维度必须在创建数据源的时候被标记为日期或者维度,不需要加where关键字。比如:维度A="值1" and 维度B="值2"',
|
||||
render: (_: any, record: any) => {
|
||||
const { constraint, name } = record;
|
||||
const { measures } = measuresParams;
|
||||
@@ -128,7 +128,7 @@ const MetricMeasuresFormTable: React.FC<Props> = ({
|
||||
<ProTable
|
||||
actionRef={actionRef}
|
||||
headerTitle="度量列表"
|
||||
tooltip="一般用于在“指标”列表已有指标的基础上加工新指标,比如:指标NEW1=指标A/100,指标NEW2=指标B/指标C。(若需用到多个已有指标,可以点击右上角“增加度量”)"
|
||||
tooltip="基于本主题域下所有数据源的度量来创建指标,且该列表的度量为了加以区分,均已加上数据源名称作为前缀,选中度量后,可基于这几个度量来写表达式,若是选中的度量来自不同的数据源,系统将会自动join来计算该指标"
|
||||
rowKey="name"
|
||||
columns={columns}
|
||||
dataSource={measuresParams?.measures || []}
|
||||
@@ -150,7 +150,7 @@ const MetricMeasuresFormTable: React.FC<Props> = ({
|
||||
/>
|
||||
<ProCard
|
||||
title={'度量表达式'}
|
||||
tooltip="若为指标NEW1,则填写:指标A/100。若为指标NEW2,则填写:指标B/指标C"
|
||||
tooltip="度量表达式由上面选择的度量组成,如选择了度量A和B,则可将表达式写成A+B"
|
||||
>
|
||||
<SqlEditor
|
||||
value={exprString}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { CheckCard } from '@ant-design/pro-components';
|
||||
import React from 'react';
|
||||
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 styles from './style.less';
|
||||
|
||||
type Props = {
|
||||
modelList: ISemantic.IDomainItem[];
|
||||
domainManger: StateType;
|
||||
dispatch: Dispatch;
|
||||
};
|
||||
|
||||
const OverView: React.FC<Props> = ({ domainManger, dispatch, modelList }) => {
|
||||
const { selectDomainId } = domainManger;
|
||||
|
||||
const extraNode = (model: ISemantic.IDomainItem) => {
|
||||
const { metricCnt, dimensionCnt } = model;
|
||||
return (
|
||||
<div className={styles.overviewExtraContainer}>
|
||||
<div className={styles.extraWrapper}>
|
||||
<div className={styles.extraStatistic}>
|
||||
<div className={styles.extraTitle}>维度数</div>
|
||||
<div className={styles.extraValue}>
|
||||
<span className="ant-statistic-content-value">{formatNumber(dimensionCnt || 0)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.extraWrapper}>
|
||||
<div className={styles.extraStatistic}>
|
||||
<div className={styles.extraTitle}>指标数</div>
|
||||
<div className={styles.extraValue}>
|
||||
<span className="ant-statistic-content-value">{formatNumber(metricCnt || 0)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<CheckCard.Group value={selectDomainId} defaultValue={selectDomainId}>
|
||||
{modelList &&
|
||||
modelList.map((model: ISemantic.IDomainItem) => {
|
||||
return (
|
||||
<CheckCard
|
||||
avatar={icon}
|
||||
title={model.name}
|
||||
key={model.id}
|
||||
value={model.id}
|
||||
// description={model.description || '模型描述...'}
|
||||
description={extraNode(model)}
|
||||
onClick={() => {
|
||||
const { id, name } = model;
|
||||
dispatch({
|
||||
type: 'domainManger/setSelectDomain',
|
||||
selectDomainId: id,
|
||||
selectDomainName: name,
|
||||
domainData: model,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</CheckCard.Group>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ domainManger }: { domainManger: StateType }) => ({
|
||||
domainManger,
|
||||
}))(OverView);
|
||||
@@ -6,23 +6,26 @@ import type { FC, Key } from 'react';
|
||||
import { connect } from 'umi';
|
||||
import type { Dispatch } from 'umi';
|
||||
import type { StateType } from '../model';
|
||||
import { getDomainList, createDomain, updateDomain, deleteDomain } from '../service';
|
||||
import { createDomain, updateDomain, deleteDomain } from '../service';
|
||||
import { treeParentKeyLists } from '../utils';
|
||||
import ProjectInfoFormProps from './ProjectInfoForm';
|
||||
import { constructorClassTreeFromList, addPathInTreeData } from '../utils';
|
||||
import { PlusCircleOutlined } from '@ant-design/icons';
|
||||
|
||||
import styles from './style.less';
|
||||
import { ISemantic } from '../data';
|
||||
|
||||
const { Search } = Input;
|
||||
|
||||
type ProjectListProps = {
|
||||
selectDomainId: string;
|
||||
selectDomainId: number;
|
||||
selectDomainName: string;
|
||||
domainList: ISemantic.IDomainItem[];
|
||||
createDomainBtnVisible?: boolean;
|
||||
dispatch: Dispatch;
|
||||
onCreateDomainBtnClick?: () => void;
|
||||
onTreeSelected?: () => void;
|
||||
onTreeDataUpdate?: () => void;
|
||||
};
|
||||
|
||||
const projectTreeFlat = (projectTree: DataNode[], filterValue: string): DataNode[] => {
|
||||
@@ -42,9 +45,11 @@ const projectTreeFlat = (projectTree: DataNode[], filterValue: string): DataNode
|
||||
|
||||
const ProjectListTree: FC<ProjectListProps> = ({
|
||||
selectDomainId,
|
||||
domainList,
|
||||
createDomainBtnVisible = true,
|
||||
onCreateDomainBtnClick,
|
||||
onTreeSelected,
|
||||
onTreeDataUpdate,
|
||||
dispatch,
|
||||
}) => {
|
||||
const [projectTree, setProjectTree] = useState<DataNode[]>([]);
|
||||
@@ -54,40 +59,19 @@ const ProjectListTree: FC<ProjectListProps> = ({
|
||||
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
|
||||
const [classList, setClassList] = useState<any[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const treeData = addPathInTreeData(constructorClassTreeFromList(domainList));
|
||||
setProjectTree(treeData);
|
||||
setClassList(domainList);
|
||||
setExpandedKeys(treeParentKeyLists(treeData));
|
||||
}, [domainList]);
|
||||
|
||||
const onSearch = (value: any) => {
|
||||
setFliterValue(value);
|
||||
};
|
||||
|
||||
const initProjectTree = async () => {
|
||||
const { code, data, msg } = await getDomainList();
|
||||
if (code === 200) {
|
||||
const treeData = addPathInTreeData(constructorClassTreeFromList(data));
|
||||
setProjectTree(treeData);
|
||||
setClassList(data);
|
||||
setExpandedKeys(treeParentKeyLists(treeData));
|
||||
const firstRootNode = data.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 {
|
||||
message.error(msg);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
initProjectTree();
|
||||
}, []);
|
||||
|
||||
const handleSelect = (selectedKeys: string, projectName: string) => {
|
||||
if (selectedKeys === selectDomainId) {
|
||||
if (`${selectedKeys}` === `${selectDomainId}`) {
|
||||
return;
|
||||
}
|
||||
const targetNodeData = classList.filter((item: any) => {
|
||||
@@ -108,7 +92,7 @@ const ProjectListTree: FC<ProjectListProps> = ({
|
||||
if (res.code === 200) {
|
||||
message.success('编辑分类成功');
|
||||
setProjectInfoModalVisible(false);
|
||||
initProjectTree();
|
||||
onTreeDataUpdate?.();
|
||||
} else {
|
||||
message.error(res.msg);
|
||||
}
|
||||
@@ -120,7 +104,7 @@ const ProjectListTree: FC<ProjectListProps> = ({
|
||||
} else if (values.modelType === 'edit') {
|
||||
await editProject(values);
|
||||
}
|
||||
initProjectTree();
|
||||
onTreeDataUpdate?.();
|
||||
setProjectInfoModalVisible(false);
|
||||
};
|
||||
|
||||
@@ -130,7 +114,7 @@ const ProjectListTree: FC<ProjectListProps> = ({
|
||||
if (res.code === 200) {
|
||||
message.success('编辑项目成功');
|
||||
setProjectInfoModalVisible(false);
|
||||
initProjectTree();
|
||||
onTreeDataUpdate?.();
|
||||
} else {
|
||||
message.error(res.msg);
|
||||
}
|
||||
@@ -210,18 +194,20 @@ const ProjectListTree: FC<ProjectListProps> = ({
|
||||
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>
|
||||
{createDomainBtnVisible && (
|
||||
<Col flex="0 1 50px">
|
||||
<Tooltip title="新增顶级域">
|
||||
<PlusCircleOutlined
|
||||
onClick={() => {
|
||||
setProjectInfoParams({ type: 'top', modelType: 'add' });
|
||||
setProjectInfoModalVisible(true);
|
||||
onCreateDomainBtnClick?.();
|
||||
}}
|
||||
className={styles.addBtn}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
|
||||
<Tree
|
||||
@@ -249,8 +235,13 @@ const ProjectListTree: FC<ProjectListProps> = ({
|
||||
};
|
||||
|
||||
export default connect(
|
||||
({ domainManger: { selectDomainId, selectDomainName } }: { domainManger: StateType }) => ({
|
||||
({
|
||||
domainManger: { selectDomainId, selectDomainName, domainList },
|
||||
}: {
|
||||
domainManger: StateType;
|
||||
}) => ({
|
||||
selectDomainId,
|
||||
selectDomainName,
|
||||
domainList,
|
||||
}),
|
||||
)(ProjectListTree);
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Tag } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
import { TransType } from '../enum';
|
||||
|
||||
type Props = {
|
||||
type: TransType;
|
||||
};
|
||||
|
||||
const TransTypeTag: React.FC<Props> = ({ type }) => {
|
||||
return (
|
||||
<>
|
||||
{type === TransType.DIMENSION ? (
|
||||
<Tag color="blue">{'维度'}</Tag>
|
||||
) : type === 'metric' ? (
|
||||
<Tag color="orange">{'指标'}</Tag>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TransTypeTag;
|
||||
@@ -247,3 +247,54 @@
|
||||
color:#296DF3;
|
||||
}
|
||||
}
|
||||
|
||||
.overviewExtraContainer {
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
.extraWrapper {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
.extraStatistic {
|
||||
display: inline-flex;
|
||||
color: rgba(42, 46, 54, 0.65);
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.5714285714285714;
|
||||
list-style: none;
|
||||
.extraTitle {
|
||||
font-size: 12px;
|
||||
margin-inline-end: 6px;
|
||||
margin-block-end: 0;
|
||||
margin-bottom: 4px;
|
||||
color: rgba(42, 46, 54, 0.45);
|
||||
}
|
||||
.extraValue {
|
||||
font-size: 12px;
|
||||
color: rgba(42, 46, 54, 0.65);
|
||||
display: inline-block;
|
||||
direction: ltr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.infoTagList{
|
||||
.siteTagPlus {
|
||||
background: #fff;
|
||||
border-style: dashed;
|
||||
}
|
||||
.editTag {
|
||||
user-select: none;
|
||||
}
|
||||
.tagInput {
|
||||
width: 78px;
|
||||
margin-right: 8px;
|
||||
vertical-align: top;
|
||||
}
|
||||
[data-theme="dark"] .siteTagPlus {
|
||||
background: transparent;
|
||||
border-style: dashed;
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,29 @@ export declare namespace IDataSource {
|
||||
}
|
||||
|
||||
export declare namespace ISemantic {
|
||||
interface IDomainItem {
|
||||
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;
|
||||
dimensionCnt?: number;
|
||||
metricCnt?: number;
|
||||
}
|
||||
|
||||
interface IDimensionItem {
|
||||
createdBy: string;
|
||||
updatedBy: string;
|
||||
@@ -143,4 +166,56 @@ export declare namespace IChatConfig {
|
||||
metricList: ISemantic.IMetricList;
|
||||
};
|
||||
}
|
||||
|
||||
interface IConfig {
|
||||
id: any;
|
||||
domainId: number;
|
||||
domainName: string;
|
||||
chatAggRichConfig: IChatRichConfig;
|
||||
chatDetailRichConfig: IChatRichConfig;
|
||||
bizName: string;
|
||||
statusEnum: string;
|
||||
createdBy: string;
|
||||
updatedBy: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
interface IKnowledgeInfosItem {
|
||||
itemId: number;
|
||||
bizName: string;
|
||||
type?: string;
|
||||
searchEnable?: boolean;
|
||||
knowledgeAdvancedConfig?: IKnowledgeConfig;
|
||||
}
|
||||
|
||||
type IKnowledgeInfosItemMap = Record<IKnowledgeInfosItem.bizName, IKnowledgeInfosItem>;
|
||||
|
||||
interface IKnowledgeConfig {
|
||||
blackList: string[];
|
||||
whiteList: string[];
|
||||
ruleList: string[];
|
||||
}
|
||||
|
||||
interface IChatRichConfig {
|
||||
id?: number;
|
||||
visibility: {
|
||||
blackDimIdList: number[];
|
||||
blackMetricIdList: number[];
|
||||
whiteDimIdList: number[];
|
||||
whiteMetricIdList: number[];
|
||||
};
|
||||
entity: {
|
||||
names: string[];
|
||||
dimItem: ISemantic.IDimensionItem;
|
||||
};
|
||||
knowledgeInfos: IKnowledgeInfosItem[];
|
||||
globalKnowledgeConfig: IKnowledgeConfig;
|
||||
chatDefaultConfig: {
|
||||
dimensions: ISemantic.IDimensionList;
|
||||
metrics: ISemantic.IMetricList;
|
||||
unit: number;
|
||||
period: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
export enum ChatConfigType {
|
||||
DETAIL = 'detail',
|
||||
AGG = 'agg',
|
||||
}
|
||||
|
||||
export enum TransType {
|
||||
DIMENSION = 'dimension',
|
||||
METRIC = 'metric',
|
||||
}
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import type { Reducer, Effect } from 'umi';
|
||||
import { message } from 'antd';
|
||||
import { ISemantic } from './data';
|
||||
import { getDimensionList, queryMetric, excuteSql, getDatabaseByDomainId } from './service';
|
||||
|
||||
export type StateType = {
|
||||
current: number;
|
||||
pageSize: number;
|
||||
selectDomainId: any;
|
||||
selectDomainId: number;
|
||||
selectDomainName: string;
|
||||
dimensionList: any[];
|
||||
metricList: any[];
|
||||
dimensionList: ISemantic.IDimensionList;
|
||||
metricList: ISemantic.IMetricList;
|
||||
searchParams: Record<string, any>;
|
||||
domainData: any;
|
||||
dataBaseResultColsMap: any;
|
||||
dataBaseConfig: any;
|
||||
domainData?: ISemantic.IDomainItem;
|
||||
domainList: ISemantic.IDomainItem[];
|
||||
};
|
||||
|
||||
export type ModelType = {
|
||||
@@ -26,6 +28,7 @@ export type ModelType = {
|
||||
};
|
||||
reducers: {
|
||||
setSelectDomain: Reducer<StateType>;
|
||||
setDomainList: Reducer<StateType>;
|
||||
setPagination: Reducer<StateType>;
|
||||
setDimensionList: Reducer<StateType>;
|
||||
setDataBaseScriptColumn: Reducer<StateType>;
|
||||
@@ -38,14 +41,15 @@ export type ModelType = {
|
||||
export const defaultState: StateType = {
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
selectDomainId: undefined,
|
||||
selectDomainId: 0,
|
||||
selectDomainName: '',
|
||||
searchParams: {},
|
||||
dimensionList: [],
|
||||
metricList: [],
|
||||
domainData: {},
|
||||
domainData: undefined,
|
||||
dataBaseResultColsMap: {},
|
||||
dataBaseConfig: {},
|
||||
domainList: [],
|
||||
};
|
||||
|
||||
const Model: ModelType = {
|
||||
@@ -119,6 +123,12 @@ const Model: ModelType = {
|
||||
domainData: action.domainData,
|
||||
};
|
||||
},
|
||||
setDomainList(state = defaultState, action) {
|
||||
return {
|
||||
...state,
|
||||
...action.payload,
|
||||
};
|
||||
},
|
||||
setPagination(state = defaultState, action) {
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { API } from '@/services/API';
|
||||
|
||||
import { ISemantic } from './data';
|
||||
import type { DataNode } from 'antd/lib/tree';
|
||||
|
||||
export const changeTreeData = (treeData: API.ProjectList, auth?: boolean): DataNode[] => {
|
||||
@@ -77,3 +77,50 @@ export const findDepartmentTree: any = (treeData: any[], projectId: string) => {
|
||||
}
|
||||
return findDepartmentTree(newStepList, projectId);
|
||||
};
|
||||
|
||||
const isDescendant = (
|
||||
node: ISemantic.IDomainItem,
|
||||
parentId: number,
|
||||
nodes: ISemantic.IDomainItem[],
|
||||
): boolean => {
|
||||
// 如果当前节点的 parentId 与指定的 parentId 相同,则说明它是指定节点的子节点
|
||||
if (node.parentId === parentId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 递归检查当前节点的父节点是否是指定节点的子节点
|
||||
const parentNode = nodes.find((n) => n.id === node.parentId);
|
||||
if (parentNode) {
|
||||
return isDescendant(parentNode, parentId, nodes);
|
||||
}
|
||||
|
||||
// 如果找不到父节点,则说明当前节点不是指定节点的子孙节点
|
||||
return false;
|
||||
};
|
||||
|
||||
export const findLeafNodesFromDomainList = (
|
||||
nodes: ISemantic.IDomainItem[],
|
||||
id: number | null = null,
|
||||
): ISemantic.IDomainItem[] => {
|
||||
const leafNodes: ISemantic.IDomainItem[] = [];
|
||||
|
||||
// 遍历所有节点
|
||||
for (const node of nodes) {
|
||||
let isLeaf = true;
|
||||
|
||||
// 检查当前节点是否有子节点
|
||||
for (const childNode of nodes) {
|
||||
if (childNode.parentId === node.id) {
|
||||
isLeaf = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果当前节点是叶子节点,并且满足指定的 id 条件,则将其添加到结果数组中
|
||||
if (isLeaf && (id === null || isDescendant(node, id, nodes))) {
|
||||
leafNodes.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
return leafNodes;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user