diff --git a/webapp/packages/supersonic-fe/src/components/SelectPartner/index.tsx b/webapp/packages/supersonic-fe/src/components/SelectPartner/index.tsx index d6be0227f..f46880423 100644 --- a/webapp/packages/supersonic-fe/src/components/SelectPartner/index.tsx +++ b/webapp/packages/supersonic-fe/src/components/SelectPartner/index.tsx @@ -1,6 +1,6 @@ -import { Avatar, TreeSelect, Tag } from 'antd'; +import { TreeSelect, Tag } from 'antd'; import React, { useEffect, useState } from 'react'; -import { getDepartmentTree, getUserByDeptid } from './service'; +import { getUserByDeptid, getOrganizationTree } from './service'; import TMEAvatar from '@/components/TMEAvatar'; type Props = { @@ -11,6 +11,9 @@ type Props = { }; const isDisableCheckbox = (name: string, type: string) => { + if (!name) { + return false; + } const isPersonNode = name.includes('('); if (type === 'selectedPerson') { return !isPersonNode; @@ -25,18 +28,18 @@ const isDisableCheckbox = (name: string, type: string) => { }; // 转化树结构 -export function changeTreeData(treeData: any = [], type: string) { +export function changeTreeData(treeData: any = [], type: string, keyName = 'id') { return treeData.map((item: any) => { return { title: item.name, - value: item.key, - key: item.key, - isLeaf: !!item.emplid, - children: item?.subDepartments ? changeTreeData(item.subDepartments, type) : [], - disableCheckbox: isDisableCheckbox(item.name, type), - checkable: !isDisableCheckbox(item.name, type), - icon: item.name.includes('(') && ( - + value: item[keyName], + key: item[keyName], + isLeaf: !item.subOrganizations, + children: item.subOrganizations ? changeTreeData(item.subOrganizations, type, keyName) : [], + disableCheckbox: isDisableCheckbox(item.displayName, type), + checkable: !isDisableCheckbox(item.displayName, type), + icon: (item.displayName || '').includes('(') && ( + ), }; }); @@ -51,9 +54,12 @@ const SelectPartner: React.FC = ({ const [treeData, setTreeData] = useState([]); const getDetpartment = async () => { - const res = await getDepartmentTree(); - const data = changeTreeData(res.data, type); - setTreeData(data); + const { code, data } = await getOrganizationTree(); + if (code === 200) { + const changeData = changeTreeData(data, type); + setTreeData(changeData); + return; + } }; useEffect(() => { @@ -78,13 +84,21 @@ const SelectPartner: React.FC = ({ const onLoadData = (target: any) => { const { key } = target; const loadData = async () => { - const childData = await getUserByDeptid(key); - if (childData.data.length === 0) { - return; + const { code, data } = await getUserByDeptid(key); + if (code === 200) { + const list = data.reduce((userList: any[], item: any) => { + const { name, displayName } = item; + if (name && displayName) { + userList.push({ key: `${key}-${item.id}`, ...item }); + } + return userList; + }, []); + setTimeout(() => { + setTreeData((origin) => { + return updateTreeData(origin, key, changeTreeData(list, type, 'key')); + }); + }, 300); } - setTimeout(() => { - setTreeData((origin) => updateTreeData(origin, key, changeTreeData(childData.data, type))); - }, 300); }; return new Promise((resolve) => { loadData().then(() => { diff --git a/webapp/packages/supersonic-fe/src/components/SelectPartner/service.ts b/webapp/packages/supersonic-fe/src/components/SelectPartner/service.ts index 72760d033..94bac52d7 100644 --- a/webapp/packages/supersonic-fe/src/components/SelectPartner/service.ts +++ b/webapp/packages/supersonic-fe/src/components/SelectPartner/service.ts @@ -1,13 +1,12 @@ import { request } from 'umi'; -export async function getDepartmentTree() { - return request('/api/tpp/getDetpartmentTree', { - method: 'GET', - }); -} - export async function getUserByDeptid(id: any) { - return request(`/api/tpp/getUserByDeptid/${id}`, { + return request(`${process.env.AUTH_API_BASE_URL}user/getUserByOrg/${id}`, { + method: 'GET', + }); +} +export async function getOrganizationTree() { + return request(`${process.env.AUTH_API_BASE_URL}user/getOrganizationTree`, { method: 'GET', }); } diff --git a/webapp/packages/supersonic-fe/src/components/SelectTMEPerson/index.tsx b/webapp/packages/supersonic-fe/src/components/SelectTMEPerson/index.tsx index 2f7beff49..ada95acd3 100644 --- a/webapp/packages/supersonic-fe/src/components/SelectTMEPerson/index.tsx +++ b/webapp/packages/supersonic-fe/src/components/SelectTMEPerson/index.tsx @@ -30,15 +30,7 @@ const SelectTMEPerson: FC = ({ placeholder, value, isMultiple = true, onC } }, updater: (list) => { - const users = list.map((item: UserItem) => { - const { enName, chName, name } = item; - return { - ...item, - enName: enName || name, - chName: chName || name, - }; - }); - setUserList(users); + setUserList(list); }, cleanup: () => { setUserList([]); @@ -58,8 +50,8 @@ const SelectTMEPerson: FC = ({ placeholder, value, isMultiple = true, onC > {userList.map((item) => { return ( - - + + {item.displayName} ); diff --git a/webapp/packages/supersonic-fe/src/components/SelectTMEPerson/service.ts b/webapp/packages/supersonic-fe/src/components/SelectTMEPerson/service.ts index bc860b152..ea7286892 100644 --- a/webapp/packages/supersonic-fe/src/components/SelectTMEPerson/service.ts +++ b/webapp/packages/supersonic-fe/src/components/SelectTMEPerson/service.ts @@ -1,19 +1,15 @@ import request from 'umi-request'; -export type UserItem = { - enName?: string; +export interface UserItem { + id: number; + name: string; displayName: string; - chName?: string; - name?: string; email: string; -}; +} + export type GetAllUserRes = Result; // 获取所有用户 export async function getAllUser(): Promise { - const { APP_TARGET } = process.env; - if (APP_TARGET === 'inner') { - return request.get('/api/oa/user/all'); - } return request.get(`${process.env.AUTH_API_BASE_URL}user/getUserList`); } diff --git a/webapp/packages/supersonic-fe/src/components/SqlEditor/index.tsx b/webapp/packages/supersonic-fe/src/components/SqlEditor/index.tsx index 4796a2bdb..9b4989a79 100644 --- a/webapp/packages/supersonic-fe/src/components/SqlEditor/index.tsx +++ b/webapp/packages/supersonic-fe/src/components/SqlEditor/index.tsx @@ -49,11 +49,13 @@ export interface ISqlEditorProps { isRightTheme?: boolean; editorConfig?: IAceEditorProps; sizeChanged?: number; + isFullScreen?: boolean; fullScreenBtnVisible?: boolean; onSqlChange?: (sql: string) => void; onChange?: (sql: string) => void; onSelect?: (sql: string) => void; onCmdEnter?: () => void; + triggerBackToNormal?: () => void; } /** @@ -71,10 +73,12 @@ function SqlEditor(props: ISqlEditorProps) { sizeChanged, editorConfig, fullScreenBtnVisible = true, + isFullScreen = false, onSqlChange, onChange, onSelect, onCmdEnter, + triggerBackToNormal, } = props; const resize = useCallback( debounce(() => { @@ -118,11 +122,15 @@ function SqlEditor(props: ISqlEditorProps) { setHintsPopover(hints); }, [hints]); - const [isSqlIdeFullScreen, setIsSqlIdeFullScreen] = useState(false); + const [isSqlIdeFullScreen, setIsSqlIdeFullScreen] = useState(isFullScreen); + + useEffect(() => { + setIsSqlIdeFullScreen(isFullScreen); + }, [isFullScreen]); const handleNormalScreenSqlIde = () => { setIsSqlIdeFullScreen(false); - // setSqlEditorHeight(getDefaultSqlEditorHeight(screenSize)); + triggerBackToNormal?.(); }; return (
diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/ChatSetting.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/ChatSetting.tsx index 66386f7d7..0bf1f3ab4 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/ChatSetting.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/ChatSetting.tsx @@ -1,7 +1,7 @@ import { Tabs, Popover, message } from 'antd'; import React, { useEffect, useState } from 'react'; import { connect, Helmet, useParams, history } from 'umi'; -import ProjectListTree from './components/ProjectList'; +import DomainListTree from './components/DomainList'; import styles from './components/style.less'; import type { StateType } from './model'; import { DownOutlined } from '@ant-design/icons'; @@ -20,7 +20,6 @@ type Props = { }; const ChatSetting: React.FC = ({ domainManger, dispatch }) => { - window.RUNNING_ENV = 'chat'; const defaultTabKey = 'metric'; const params: any = useParams(); const menuKey = params.menuKey ? params.menuKey : defaultTabKey; @@ -159,7 +158,7 @@ const ChatSetting: React.FC = ({ domainManger, dispatch }) => { maxHeight: '800px', }} content={ - { setOpen(false); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/SqlDetail.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/SqlDetail.tsx index 42ec92ef4..72334174f 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/SqlDetail.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/SqlDetail.tsx @@ -467,21 +467,16 @@ const SqlDetail: React.FC = ({
- - - + // theme="monokai" + isRightTheme={isRight} + sizeChanged={editorSize} + onSqlChange={onSqlChange} + onSelect={onSelect} + />
diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/components/MetricFilter.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/components/MetricFilter.tsx index 02328c9d3..ee6f77d13 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/components/MetricFilter.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/components/MetricFilter.tsx @@ -3,9 +3,11 @@ import StandardFormRow from '@/components/StandardFormRow'; import TagSelect from '@/components/TagSelect'; import React, { useEffect } from 'react'; import { SENSITIVE_LEVEL_OPTIONS } from '../../constant'; +import { SearchOutlined } from '@ant-design/icons'; +import DomainTreeSelect from '../../components/DomainTreeSelect'; +import styles from '../style.less'; const FormItem = Form.Item; -const { Option } = Select; type Props = { filterValues?: any; @@ -25,10 +27,7 @@ const MetricFilter: React.FC = ({ filterValues = {}, onFiltersChange }) = onFiltersChange(value, values); }; - const onSearch = (value) => { - if (!value) { - return; - } + const onSearch = (value: any) => { onFiltersChange(value, form.getFieldsValue()); }; @@ -57,34 +56,24 @@ const MetricFilter: React.FC = ({ filterValues = {}, onFiltersChange }) = form={form} colon={false} onValuesChange={(value, values) => { - if (value.keywords || value.keywordsType) { + if (value.name) { return; } handleValuesChange(value, values); }} - initialValues={{ - keywordsType: 'name', - }} > - - - +
+ +
+ } + onSearch={onSearch} + /> +
- - - - +
{filterList.map((item) => { const { title, key, options } = item; @@ -102,6 +91,11 @@ const MetricFilter: React.FC = ({ filterValues = {}, onFiltersChange }) = ); })} + + + + + ); }; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/index.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/index.tsx index a04839196..da305e98e 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/index.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/index.tsx @@ -1,6 +1,6 @@ import type { ActionType, ProColumns } from '@ant-design/pro-table'; import ProTable from '@ant-design/pro-table'; -import { message, Space } from 'antd'; +import { message } from 'antd'; import React, { useRef, useState, useEffect } from 'react'; import type { Dispatch } from 'umi'; import { connect } from 'umi'; @@ -23,6 +23,7 @@ type QueryMetricListParams = { bizName?: string; sensitiveLevel?: string; type?: string; + [key: string]: any; }; const ClassMetricTable: React.FC = () => { @@ -31,8 +32,9 @@ const ClassMetricTable: React.FC = () => { pageSize: 20, total: 0, }); - + const [loading, setLoading] = useState(false); const [dataSource, setDataSource] = useState([]); + const [filterParams, setFilterParams] = useState>({}); const actionRef = useRef(); useEffect(() => { @@ -40,11 +42,13 @@ const ClassMetricTable: React.FC = () => { }, []); const queryMetricList = async (params: QueryMetricListParams = {}) => { + setLoading(true); const { code, data, msg } = await queryMetric({ - ...params, ...pagination, + ...params, }); - const { list, pageSize, current, total } = data; + setLoading(false); + const { list, pageSize, current, total } = data || {}; let resData: any = {}; if (code === 200) { setPagination({ @@ -87,6 +91,10 @@ const ClassMetricTable: React.FC = () => { dataIndex: 'bizName', title: '字段名称', }, + { + dataIndex: 'domainName', + title: '主题域', + }, { dataIndex: 'sensitiveLevel', title: '敏感度', @@ -105,7 +113,6 @@ const ClassMetricTable: React.FC = () => { { dataIndex: 'type', title: '指标类型', - // search: false, valueEnum: { ATOMIC: '原子指标', DERIVED: '衍生指标', @@ -123,25 +130,18 @@ const ClassMetricTable: React.FC = () => { ]; const handleFilterChange = async (filterParams: { - keywordsType: string; - keywords: string; + name: string; sensitiveLevel: string; type: string; }) => { - const params: QueryMetricListParams = {}; - const { keywordsType, keywords, sensitiveLevel, type } = filterParams; - if (keywordsType && keywords) { - params[keywordsType] = keywords; - } + const { sensitiveLevel, type } = filterParams; + const params: QueryMetricListParams = { ...filterParams }; const sensitiveLevelValue = sensitiveLevel?.[0]; const typeValue = type?.[0]; - if (sensitiveLevelValue) { - params.sensitiveLevel = sensitiveLevelValue; - } - if (type) { - params.type = typeValue; - } + params.sensitiveLevel = sensitiveLevelValue; + params.type = typeValue; + setFilterParams(params); await queryMetricList(params); }; @@ -157,7 +157,6 @@ const ClassMetricTable: React.FC = () => { = () => { tableAlertRender={() => { return false; }} + loading={loading} onChange={(data: any) => { const { current, pageSize, total } = data; - setPagination({ + const pagin = { current, pageSize, total, - }); + }; + setPagination(pagin); + queryMetricList({ ...pagin, ...filterParams }); }} size="small" options={{ reload: false, density: false, fullScreen: false }} - // toolBarRender={() => [ - // , - // ]} /> ); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/style.less b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/style.less index 363e8c2ae..df680eae5 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/style.less +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/style.less @@ -7,4 +7,77 @@ .metricTable { margin: 20px; +} + + + +.searchBox { + // margin-bottom: 12px; + background: #fff; + border-radius: 10px; + width: 500px; + margin: 0 auto; + .searchInput { + width: 100%; + border: 1px solid rgba(35, 104, 184, 0.6); + border-radius: 10px; + } + :global { + + .ant-select-auto-complete { + width: 100%; + } + .ant-input { + height: 50px; + padding: 0 15px; + color: #515a6e; + font-size: 14px; + line-height: 50px; + background: hsla(0, 0%, 100%, 0.2); + border: none; + border-top-left-radius: 10px; + border-bottom-left-radius: 10px; + caret-color: #296df3; + + &:focus { + border-right-width: 0 !important; + box-shadow: none; + } + } + + .ant-input-group-addon { + left: 0 !important; + padding: 0; + background: hsla(0, 0%, 100%, 0.2); + border: none; + border-top-left-radius: 0; + border-top-right-radius: 10px; + border-bottom-right-radius: 10px; + border-bottom-left-radius: 0; + + .ant-btn { + width: 72px; + height: 50px; + margin: 0; + color: rgba(35, 104, 184, 0.6); + font-size: 16px; + background-color: transparent; + background-color: transparent; + border: none; + box-shadow: none !important; + + &::after { + box-shadow: none !important; + } + + .anticon { + font-size: 28px; + + &:hover { + color: @primary-color; + } + } + } + } + } } \ No newline at end of file diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/ProjectManager.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/ProjectManager.tsx index 69bb7a12a..8feacfc92 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/ProjectManager.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/ProjectManager.tsx @@ -1,7 +1,7 @@ import { Tabs, Popover, message } from 'antd'; import React, { useEffect, useState } from 'react'; import { connect, Helmet, history, useParams } from 'umi'; -import ProjectListTree from './components/ProjectList'; +import DomainListTree from './components/DomainList'; import ClassDataSourceTable from './components/ClassDataSourceTable'; import ClassDimensionTable from './components/ClassDimensionTable'; import ClassMetricTable from './components/ClassMetricTable'; @@ -24,7 +24,6 @@ type Props = { }; const DomainManger: React.FC = ({ domainManger, dispatch }) => { - window.RUNNING_ENV = 'semantic'; const defaultTabKey = 'xflow'; const params: any = useParams(); const menuKey = params.menuKey ? params.menuKey : defaultTabKey; @@ -203,7 +202,7 @@ const DomainManger: React.FC = ({ domainManger, dispatch }) => { maxHeight: '800px', }} content={ - { setOpen(false); }} diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/components/ContextMenu.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/components/ContextMenu.tsx index 3619f129d..d4e46855b 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/components/ContextMenu.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/components/ContextMenu.tsx @@ -6,7 +6,6 @@ import { SemanticNodeType } from '../../enum'; import { SEMANTIC_NODE_TYPE_CONFIG } from '../../constant'; type InitContextMenuProps = { - graphShowType?: string; onMenuClick?: (key: string, item: Item) => void; }; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/components/GraphLegend.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/components/GraphLegend.tsx new file mode 100644 index 000000000..3edfd5844 --- /dev/null +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/components/GraphLegend.tsx @@ -0,0 +1,83 @@ +import { Space, Checkbox } from 'antd'; +import type { CheckboxValueType } from 'antd/es/checkbox/Group'; +import React, { useState, useEffect } from 'react'; + +import { connect } from 'umi'; +import type { StateType } from '../../model'; +import styles from '../style.less'; + +type Props = { + legendOptions: LegendOptionsItem[]; + value?: string[]; + domainManger: StateType; + onChange?: (ids: CheckboxValueType[]) => void; + defaultCheckAll?: boolean; + [key: string]: any; +}; + +type LegendOptionsItem = { + id: string; + label: string; +}; + +const GraphLegend: React.FC = ({ + legendOptions, + value, + defaultCheckAll = false, + onChange, +}) => { + const [groupValue, setGroupValue] = useState(value || []); + + useEffect(() => { + if (!defaultCheckAll) { + return; + } + if (!Array.isArray(legendOptions)) { + setGroupValue([]); + return; + } + setGroupValue( + legendOptions.map((item) => { + return item.id; + }), + ); + }, [legendOptions]); + + useEffect(() => { + if (!Array.isArray(value)) { + setGroupValue([]); + return; + } + setGroupValue(value); + }, [value]); + + const handleChange = (checkedValues: CheckboxValueType[]) => { + setGroupValue(checkedValues); + onChange?.(checkedValues); + }; + + return ( +
+ +
+
可见数据源
+
+ + {legendOptions.map((item) => { + return ( + + {item.label} + + ); + })} + +
+
+
+
+ ); +}; + +export default connect(({ domainManger }: { domainManger: StateType }) => ({ + domainManger, +}))(GraphLegend); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/components/GraphLegendVisibleModeItem.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/components/GraphLegendVisibleModeItem.tsx new file mode 100644 index 000000000..103147b7f --- /dev/null +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/components/GraphLegendVisibleModeItem.tsx @@ -0,0 +1,42 @@ +import { Segmented } from 'antd'; + +import React from 'react'; +import { SemanticNodeType } from '../../enum'; +import styles from '../style.less'; + +type Props = { + value?: SemanticNodeType; + onChange?: (value: SemanticNodeType) => void; + [key: string]: any; +}; + +const GraphLegendVisibleModeItem: React.FC = ({ value, onChange }) => { + return ( +
+ { + onChange?.(changeValue as SemanticNodeType); + }} + options={[ + { + value: '', + label: '全部', + }, + { + value: SemanticNodeType.DIMENSION, + label: '仅维度', + }, + { + value: SemanticNodeType.METRIC, + label: '仅指标', + }, + ]} + /> +
+ ); +}; + +export default GraphLegendVisibleModeItem; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/components/NodeInfoDrawer.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/components/NodeInfoDrawer.tsx index fa5d78e1d..46850eeff 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/components/NodeInfoDrawer.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/components/NodeInfoDrawer.tsx @@ -95,6 +95,7 @@ const NodeInfoDrawer: React.FC = ({ }, { label: '别名', + hideItem: !alias, value: alias || '-', }, { @@ -213,7 +214,34 @@ const NodeInfoDrawer: React.FC = ({ message.error(msg); } }; + const extraNode = ( +
+ + + { + handleDeleteConfirm(); + }} + > + + + +
+ ); return ( <> = ({ placement="right" mask={false} getContainer={false} - footer={ -
- - - - { - handleDeleteConfirm(); - }} - > - - - -
- } + footer={false} {...restProps} >
@@ -282,6 +283,7 @@ const NodeInfoDrawer: React.FC = ({ ); })}
+ {extraNode}
); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/components/ToolBar.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/components/ToolBar.tsx index 44d6bc508..abcae06f0 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/components/ToolBar.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/components/ToolBar.tsx @@ -1,8 +1,9 @@ import G6, { Graph } from '@antv/g6'; +import { IAbstractGraph as IGraph } from '@antv/g6-core'; import { createDom } from '@antv/dom-util'; import { ToolBarSearchCallBack } from '../../data'; const searchIconSvgPath = ``; - +const visibleModeIconSvgPath = ``; // const searchNode = (graph) => { // const toolBarSearchInput = document.getElementById('toolBarSearchInput') as HTMLInputElement; // const searchText = toolBarSearchInput.value.trim(); @@ -90,13 +91,29 @@ const searchInputDOM = (graph: Graph, onSearch: ToolBarSearchCallBack) => { return searchDom; }; -const initToolBar = ({ onSearch }: { onSearch: ToolBarSearchCallBack }) => { +function zoomGraph(graph, ratio) { + const width = graph.get('width'); + const height = graph.get('height'); + const centerX = width / 2; + const centerY = height / 2; + graph.zoom(ratio, { x: centerX, y: centerY }); +} + +const initToolBar = ({ + onSearch, + onClick, +}: { + onSearch: ToolBarSearchCallBack; + onClick?: (code: string, graph: IGraph) => void; +}) => { const toolBarInstance = new G6.ToolBar(); const config = toolBarInstance._cfgs; const defaultContentDomString = config.getContent(); const defaultContentDom = createDom(defaultContentDomString); // @ts-ignore - const elements = defaultContentDom.querySelectorAll('li[code="redo"], li[code="undo"]'); + const elements = defaultContentDom.querySelectorAll( + 'li[code="redo"], li[code="undo"], li[code="realZoom"]', + ); elements.forEach((element) => { element.remove(); }); @@ -113,7 +130,21 @@ const initToolBar = ({ onSearch }: { onSearch: ToolBarSearchCallBack }) => { ${searchIconSvgPath} `; - defaultContentDom.insertAdjacentHTML('afterbegin', searchBtnDom); + const visibleBtnDom = `
  • + +
  • `; + + defaultContentDom.insertAdjacentHTML('afterbegin', `${searchBtnDom}${visibleBtnDom}`); let searchInputContentVisible = true; const toolbar = new G6.ToolBar({ position: { x: 20, y: 20 }, @@ -122,6 +153,16 @@ const initToolBar = ({ onSearch }: { onSearch: ToolBarSearchCallBack }) => { const searchInput = searchInputDOM(graph as Graph, onSearch); const content = `
    ${defaultContentDom.outerHTML}
    `; const contentDom = createDom(content); + contentDom.addEventListener('click', (event: PointerEvent) => { + event.preventDefault(); + event.stopPropagation(); + return false; + }); + contentDom.addEventListener('dblclick', (event: PointerEvent) => { + event.preventDefault(); + event.stopPropagation(); + return false; + }); contentDom.appendChild(searchInput); return contentDom; }, @@ -133,11 +174,54 @@ const initToolBar = ({ onSearch }: { onSearch: ToolBarSearchCallBack }) => { searchText.style.display = visible; searchInputContentVisible = !searchInputContentVisible; } + } else if (code === 'visibleMode') { + const searchText = document.getElementById('searchInputContent'); + if (searchText) { + const visible = 'none'; + searchText.style.display = visible; + searchInputContentVisible = false; + } + } else if (code.includes('zoom')) { + const sensitivity = 0.1; // 设置缩放灵敏度,值越小,缩放越不敏感,默认值为 1 + const zoomInRatio = 1 - sensitivity; + const zoomOutRatio = 1 + sensitivity; + + if (code === 'zoomIn') { + zoomGraph(graph, zoomInRatio); + } else if (code === 'zoomOut') { + zoomGraph(graph, zoomOutRatio); + } + // else if (code === 'realZoom') { + // const width = graph.get('width'); + // const height = graph.get('height'); + // const centerX = width / 2; + // const centerY = height / 2; + // graph.moveTo(centerX, centerY); + // graph.zoomTo(1, { x: centerX, y: centerY }); + // } else if (code === 'autoZoom') { + // const width = graph.get('width'); + // const height = graph.get('height'); + // const centerX = width / 2; + // const centerY = height / 2; + // const centerModel = graph.getPointByCanvas(centerX, centerY); + + // // 调用 fitView + // graph.fitView(); + + // // 在 fitView 之后获取新的画布中心点 + // const newCenterCanvas = graph.getCanvasByPoint(centerModel.x, centerModel.y); + + // // 计算并调整画布的偏移量,使得画布中心点保持不变 + // const dx = centerX - newCenterCanvas.x; + // const dy = centerY - newCenterCanvas.y; + // graph.translate(dx, dy); + // } } else { // handleDefaultOperator public方法缺失graph作为参数传入,将graph挂载在cfgs上,源码通过get会获取到graph,完成默认code的执行逻辑 toolBarInstance._cfgs.graph = graph; toolBarInstance.handleDefaultOperator(code); } + onClick?.(code, graph); }, }); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/index.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/index.tsx index cd4176cdc..49911df5f 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/index.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/index.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState, useRef } from 'react'; import { connect } from 'umi'; import type { StateType } from '../model'; -import { IGroup } from '@antv/g-base'; +// import { IGroup } from '@antv/g-base'; import type { Dispatch } from 'umi'; import { typeConfigs, @@ -16,7 +16,7 @@ import { Item, TreeGraphData, NodeConfig, IItemBaseConfig } from '@antv/g6-core' import initToolBar from './components/ToolBar'; import initTooltips from './components/ToolTips'; import initContextMenu from './components/ContextMenu'; -import initLegend from './components/Legend'; +// import initLegend from './components/Legend'; import { SemanticNodeType } from '../enum'; import G6 from '@antv/g6'; import { ISemantic, IDataSource } from '../data'; @@ -26,11 +26,14 @@ import MetricInfoCreateForm from '../components/MetricInfoCreateForm'; import DeleteConfirmModal from './components/DeleteConfirmModal'; import ClassDataSourceTypeModal from '../components/ClassDataSourceTypeModal'; import GraphToolBar from './components/GraphToolBar'; -import { cloneDeep } from 'lodash'; +import GraphLegend from './components/GraphLegend'; +import GraphLegendVisibleModeItem from './components/GraphLegendVisibleModeItem'; + +// import { cloneDeep } from 'lodash'; type Props = { domainId: number; - graphShowType?: SemanticNodeType; + // graphShowType?: SemanticNodeType; domainManger: StateType; dispatch: Dispatch; }; @@ -39,7 +42,7 @@ const DomainManger: React.FC = ({ domainManger, domainId, // graphShowType = SemanticNodeType.DIMENSION, - graphShowType, + // graphShowType, dispatch, }) => { const ref = useRef(null); @@ -53,7 +56,7 @@ const DomainManger: React.FC = ({ const legendDataRef = useRef([]); const graphRef = useRef(null); - const legendDataFilterFunctions = useRef({}); + // const legendDataFilterFunctions = useRef({}); const [dimensionItem, setDimensionItem] = useState(); const [metricItem, setMetricItem] = useState(); @@ -70,30 +73,19 @@ const DomainManger: React.FC = ({ const [confirmModalOpenState, setConfirmModalOpenState] = useState(false); const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false); - // const toggleNodeVisibility = (graph: Graph, node: Item, visible: boolean) => { - // if (visible) { - // graph.showItem(node); - // } else { - // graph.hideItem(node); - // } - // }; + const visibleModeOpenRef = useRef(false); + const [visibleModeOpen, setVisibleModeOpen] = useState(false); + + const graphShowTypeRef = useRef(); + const [graphShowTypeState, setGraphShowTypeState] = useState(); + + const graphLegendDataSourceIds = useRef(); useEffect(() => { dimensionListRef.current = dimensionList; metricListRef.current = metricList; }, [dimensionList, metricList]); - // const toggleChildrenVisibility = (graph: Graph, node: Item, visible: boolean) => { - // const model = node.getModel(); - // if (Array.isArray(model.children)) { - // model.children.forEach((child) => { - // const childNode = graph.findById(child.id); - // toggleNodeVisibility(graph, childNode, visible); - // toggleChildrenVisibility(graph, childNode, visible); - // }); - // } - // }; - const handleSeachNode = (text: string) => { const filterData = dataSourceRef.current.reduce( (data: ISemantic.IDomainSchemaRelaList, item: ISemantic.IDomainSchemaRelaItem) => { @@ -117,12 +109,24 @@ const DomainManger: React.FC = ({ refreshGraphData(rootGraphData); }; - const changeGraphData = ( - dataSourceList: ISemantic.IDomainSchemaRelaList, - type?: SemanticNodeType, - ): TreeGraphData => { - const relationData = formatterRelationData({ dataSourceList, type, limit: 20 }); - const legendList = relationData.map((item: any) => { + const changeGraphData = (dataSourceList: ISemantic.IDomainSchemaRelaList): TreeGraphData => { + const relationData = formatterRelationData({ + dataSourceList, + type: graphShowTypeRef.current, + limit: 20, + showDataSourceId: graphLegendDataSourceIds.current, + }); + + const graphRootData = { + id: 'root', + name: domainManger.selectDomainName, + children: relationData, + }; + return graphRootData; + }; + + const initLegendData = (graphRootData: TreeGraphData) => { + const legendList = graphRootData?.children?.map((item: any) => { const { id, name } = item; return { id, @@ -131,13 +135,7 @@ const DomainManger: React.FC = ({ ...typeConfigs.datasource, }; }); - legendDataRef.current = legendList; - const graphRootData = { - id: 'root', - name: domainManger.selectDomainName, - children: relationData, - }; - return graphRootData; + legendDataRef.current = legendList as any; }; const queryDataSourceList = async (params: { @@ -153,8 +151,9 @@ const DomainManger: React.FC = ({ }), ); const graphRootData = changeGraphData(data); - setGraphData(graphRootData); dataSourceRef.current = data; + initLegendData(graphRootData); + setGraphData(graphRootData); return graphRootData; } return false; @@ -164,41 +163,42 @@ const DomainManger: React.FC = ({ }; useEffect(() => { + graphLegendDataSourceIds.current = undefined; graphRef.current = null; queryDataSourceList({ domainId }); - }, [domainId, graphShowType]); + }, [domainId]); - const getLegendDataFilterFunctions = () => { - legendDataRef.current.map((item: any) => { - const { id } = item; - legendDataFilterFunctions.current = { - ...legendDataFilterFunctions.current, - [id]: (d: any) => { - if (d.legendType === id) { - return true; - } - return false; - }, - }; - }); - }; + // const getLegendDataFilterFunctions = () => { + // legendDataRef.current.map((item: any) => { + // const { id } = item; + // legendDataFilterFunctions.current = { + // ...legendDataFilterFunctions.current, + // [id]: (d: any) => { + // if (d.legendType === id) { + // return true; + // } + // return false; + // }, + // }; + // }); + // }; - const setAllActiveLegend = (legend: any) => { - const legendCanvas = legend._cfgs.legendCanvas; - if (!legendCanvas) { - return; - } - // 从图例中找出node-group节点; - const group = legendCanvas.find((e: any) => e.get('name') === 'node-group'); - // 数据源的图例节点在node-group中的children中; - const groups = group.get('children'); - groups.forEach((itemGroup: any) => { - const labelText = itemGroup.find((e: any) => e.get('name') === 'circle-node-text'); - // legend中activateLegend事件触发在图例节点的Text上,方法中存在向上溯源的逻辑:const shapeGroup = shape.get('parent'); - // 因此复用实例方法时,在这里不能直接将图例节点传入,需要在节点的children中找任意一个元素作为入参; - legend.activateLegend(labelText); - }); - }; + // const setAllActiveLegend = (legend: any) => { + // const legendCanvas = legend._cfgs.legendCanvas; + // if (!legendCanvas) { + // return; + // } + // // 从图例中找出node-group节点; + // const group = legendCanvas.find((e: any) => e.get('name') === 'node-group'); + // // 数据源的图例节点在node-group中的children中; + // const groups = group.get('children'); + // groups.forEach((itemGroup: any) => { + // const labelText = itemGroup.find((e: any) => e.get('name') === 'circle-node-text'); + // // legend中activateLegend事件触发在图例节点的Text上,方法中存在向上溯源的逻辑:const shapeGroup = shape.get('parent'); + // // 因此复用实例方法时,在这里不能直接将图例节点传入,需要在节点的children中找任意一个元素作为入参; + // legend.activateLegend(labelText); + // }); + // }; const handleContextMenuClickEdit = (item: IItemBaseConfig) => { const targetData = item.model; @@ -273,7 +273,6 @@ const DomainManger: React.FC = ({ if (targetData.nodeType === SemanticNodeType.DIMENSION) { const targetItem = dimensionListRef.current.find((item) => item.id === targetData.uid); if (targetItem) { - // setDimensionItem({ ...targetItem }); setCurrentNodeData(targetItem); setConfirmModalOpenState(true); } else { @@ -283,7 +282,6 @@ const DomainManger: React.FC = ({ if (targetData.nodeType === SemanticNodeType.METRIC) { const targetItem = metricListRef.current.find((item) => item.id === targetData.uid); if (targetItem) { - // setMetricItem({ ...targetItem }); setCurrentNodeData(targetItem); setConfirmModalOpenState(true); } else { @@ -357,6 +355,16 @@ const DomainManger: React.FC = ({ }, }; + function handleToolBarClick(code: string) { + if (code === 'visibleMode') { + visibleModeOpenRef.current = !visibleModeOpenRef.current; + setVisibleModeOpen(visibleModeOpenRef.current); + return; + } + visibleModeOpenRef.current = false; + setVisibleModeOpen(false); + } + useEffect(() => { if (!Array.isArray(graphData?.children)) { return; @@ -371,17 +379,16 @@ const DomainManger: React.FC = ({ const graphNodeList = flatGraphDataNode(graphData.children); const graphConfigKey = graphNodeList.length > 20 ? 'dendrogram' : 'mindmap'; - getLegendDataFilterFunctions(); - const toolbar = initToolBar({ onSearch: handleSeachNode }); + // getLegendDataFilterFunctions(); + const toolbar = initToolBar({ onSearch: handleSeachNode, onClick: handleToolBarClick }); const tooltip = initTooltips(); const contextMenu = initContextMenu({ - graphShowType, onMenuClick: handleContextMenuClick, }); - const legend = initLegend({ - nodeData: legendDataRef.current, - filterFunctions: { ...legendDataFilterFunctions.current }, - }); + // const legend = initLegend({ + // nodeData: legendDataRef.current, + // filterFunctions: { ...legendDataFilterFunctions.current }, + // }); graphRef.current = new G6.TreeGraph({ container: 'semanticGraph', @@ -400,7 +407,10 @@ const DomainManger: React.FC = ({ 'drag-node', 'drag-canvas', // 'activate-relations', - 'zoom-canvas', + { + type: 'zoom-canvas', + sensitivity: 0.3, // 设置缩放灵敏度,值越小,缩放越不敏感,默认值为 1 + }, { type: 'activate-relations', trigger: 'mouseenter', // 触发方式,可以是 'mouseenter' 或 'click' @@ -429,45 +439,35 @@ const DomainManger: React.FC = ({ layout: { ...graphConfigMap[graphConfigKey].layout, }, - plugins: [legend, tooltip, toolbar, contextMenu], + plugins: [tooltip, toolbar, contextMenu], + // plugins: [legend, tooltip, toolbar, contextMenu], }); graphRef.current.set('initGraphData', graphData); graphRef.current.set('initDataSource', dataSourceRef.current); - const legendCanvas = legend._cfgs.legendCanvas; + // const legendCanvas = legend._cfgs.legendCanvas; // legend模式事件方法bindEvents会有点击图例空白清空选中的逻辑,在注册click事件前,先将click事件队列清空; - legend._cfgs.legendCanvas._events.click = []; - // legendCanvas.on('click', (e) => { - // const shape = e.target; - // const shapeGroup = shape.get('parent'); - // const shapeGroupId = shapeGroup?.cfg?.id; - // if (shapeGroupId) { - // const isActive = shapeGroup.get('active'); - // const targetNode = graphRef.current.findById(shapeGroupId); - // toggleNodeVisibility(graphRef.current, targetNode, isActive); - // toggleChildrenVisibility(graphRef.current, targetNode, isActive); - // } + // legend._cfgs.legendCanvas._events.click = []; + // legendCanvas.on('click', () => { + // // @ts-ignore findLegendItemsByState为Legend的 private方法,忽略ts校验 + // const activedNodeList = legend.findLegendItemsByState('active'); + // // 获取当前所有激活节点后进行数据遍历筛选; + // const activedNodeIds = activedNodeList.map((item: IGroup) => { + // return item.cfg.id; + // }); + // const graphDataClone = cloneDeep(graphData); + // const filterGraphDataChildren = Array.isArray(graphDataClone?.children) + // ? graphDataClone.children.reduce((children: TreeGraphData[], item: TreeGraphData) => { + // if (activedNodeIds.includes(item.id)) { + // children.push(item); + // } + // return children; + // }, []) + // : []; + // graphDataClone.children = filterGraphDataChildren; + // refreshGraphData(graphDataClone); // }); - legendCanvas.on('click', () => { - // @ts-ignore findLegendItemsByState为Legend的 private方法,忽略ts校验 - const activedNodeList = legend.findLegendItemsByState('active'); - // 获取当前所有激活节点后进行数据遍历筛选; - const activedNodeIds = activedNodeList.map((item: IGroup) => { - return item.cfg.id; - }); - const graphDataClone = cloneDeep(graphData); - const filterGraphDataChildren = Array.isArray(graphDataClone?.children) - ? graphDataClone.children.reduce((children: TreeGraphData[], item: TreeGraphData) => { - if (activedNodeIds.includes(item.id)) { - children.push(item); - } - return children; - }, []) - : []; - graphDataClone.children = filterGraphDataChildren; - refreshGraphData(graphDataClone); - }); graphRef.current.node(function (node: NodeConfig) { return getNodeConfigByType(node, { @@ -478,7 +478,7 @@ const DomainManger: React.FC = ({ graphRef.current.render(); graphRef.current.fitView([80, 80]); - setAllActiveLegend(legend); + // setAllActiveLegend(legend); graphRef.current.on('node:click', (evt: any) => { const item = evt.item; // 被操作的节点 item @@ -513,8 +513,11 @@ const DomainManger: React.FC = ({ } }, [graphData]); - const updateGraphData = async () => { - const graphRootData = await queryDataSourceList({ domainId }); + const updateGraphData = async (params?: { graphShowType?: SemanticNodeType }) => { + const graphRootData = await queryDataSourceList({ + domainId, + graphShowType: params?.graphShowType, + }); if (graphRootData) { refreshGraphData(graphRootData); } @@ -529,6 +532,27 @@ const DomainManger: React.FC = ({ return ( <> + { + graphLegendDataSourceIds.current = nodeIds; + const rootGraphData = changeGraphData(dataSourceRef.current); + refreshGraphData(rootGraphData); + }} + /> + {visibleModeOpen && ( + { + graphShowTypeRef.current = showType; + setGraphShowTypeState(showType); + const rootGraphData = changeGraphData(dataSourceRef.current); + refreshGraphData(rootGraphData); + }} + /> + )} + { setNodeDataSource(undefined); @@ -547,7 +571,7 @@ const DomainManger: React.FC = ({ />
    @@ -648,7 +672,7 @@ const DomainManger: React.FC = ({ onOkClick={() => { setConfirmModalOpenState(false); updateGraphData(); - graphShowType === SemanticNodeType.DIMENSION + graphShowTypeState === SemanticNodeType.DIMENSION ? dispatch({ type: 'domainManger/queryDimensionList', payload: { diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/style.less b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/style.less index 02383ba3b..7a780ddfe 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/style.less +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/style.less @@ -19,4 +19,36 @@ border: 1px solid #0e73ff; } } +} + +.graphLegend { + padding: 15px; + background: #45be940d; + position: absolute; + top: 100px; + left: 20px; + z-index: 1; + border: 1px solid #78c16d; + min-width: 190px; + .title { + text-align: center; + margin-bottom: 10px; + font-size: 10px; + font-weight: 500; + } + :global { + .ant-form-item { + margin-bottom: 2px; + } + } +} +.graphLegendVisibleModeItem { + padding: 3px; + background: #fff; + position: absolute; + top: 58px; + left: 20px; + z-index: 1; + border: 1px solid #eee; + min-width: 190px; } \ No newline at end of file diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/utils.ts b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/utils.ts index 1b77d9b10..bfa2742cc 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/utils.ts +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraph/utils.ts @@ -67,8 +67,9 @@ export const formatterRelationData = (params: { dataSourceList: ISemantic.IDomainSchemaRelaList; limit?: number; type?: SemanticNodeType; + showDataSourceId?: string[]; }): TreeGraphData[] => { - const { type, dataSourceList, limit } = params; + const { type, dataSourceList, limit, showDataSourceId } = params; const relationData = dataSourceList.reduce( (relationList: TreeGraphData[], item: ISemantic.IDomainSchemaRelaItem) => { const { datasource, dimensions, metrics } = item; @@ -86,20 +87,22 @@ export const formatterRelationData = (params: { const metricList = getMetricChildren(metrics, dataSourceNodeId, limit); childrenList = [...dimensionList, ...metricList]; } - relationList.push({ - ...datasource, - legendType: dataSourceNodeId, - id: dataSourceNodeId, - uid: id, - nodeType: SemanticNodeType.DATASOURCE, - size: 40, - children: [...childrenList], - style: { - lineWidth: 2, - fill: '#BDEFDB', - stroke: '#5AD8A6', - }, - }); + if (!showDataSourceId || showDataSourceId.includes(dataSourceNodeId)) { + relationList.push({ + ...datasource, + legendType: dataSourceNodeId, + id: dataSourceNodeId, + uid: id, + nodeType: SemanticNodeType.DATASOURCE, + size: 40, + children: [...childrenList], + style: { + lineWidth: 2, + fill: '#BDEFDB', + stroke: '#5AD8A6', + }, + }); + } return relationList; }, [], diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraphCanvas.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraphCanvas.tsx index 40a7a73e9..4fc864a0a 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraphCanvas.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticGraphCanvas.tsx @@ -1,10 +1,10 @@ -import { Radio } from 'antd'; +// import { Radio } from 'antd'; import React, { useState } from 'react'; import { connect } from 'umi'; import styles from './components/style.less'; import type { StateType } from './model'; import { SemanticNodeType } from './enum'; -import SemanticFlow from './SemanticFlows'; +// import SemanticFlow from './SemanticFlows'; import SemanticGraph from './SemanticGraph'; type Props = { @@ -12,7 +12,7 @@ type Props = { }; const SemanticGraphCanvas: React.FC = ({ domainManger }) => { - const [graphShowType, setGraphShowType] = useState(SemanticNodeType.DIMENSION); + // const [graphShowType, setGraphShowType] = useState(SemanticNodeType.DIMENSION); const { selectDomainId } = domainManger; return (
    @@ -32,15 +32,15 @@ const SemanticGraphCanvas: React.FC = ({ domainManger }) => {
    */}
    - {graphShowType === SemanticNodeType.DATASOURCE ? ( + {/* {graphShowType === SemanticNodeType.DATASOURCE ? (
    - ) : ( -
    - -
    - )} + ) : ( */} +
    + +
    + {/* )} */}
    ); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDimensionTable.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDimensionTable.tsx index 9e0a04102..856531da8 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDimensionTable.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDimensionTable.tsx @@ -42,7 +42,7 @@ const ClassDimensionTable: React.FC = ({ domainManger, dispatch }) => { ...pagination, domainId: selectDomainId, }); - const { list, pageSize, current, total } = data; + const { list, pageSize, current, total } = data || {}; let resData: any = {}; if (code === 200) { setPagination({ diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassMetricTable.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassMetricTable.tsx index 468507627..c6b9b5cd2 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassMetricTable.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassMetricTable.tsx @@ -35,7 +35,7 @@ const ClassMetricTable: React.FC = ({ domainManger, dispatch }) => { ...pagination, domainId: selectDomainId, }); - const { list, pageSize, current, total } = data; + const { list, pageSize, current, total } = data || {}; let resData: any = {}; if (code === 200) { setPagination({ diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ProjectList.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/DomainList.tsx similarity index 98% rename from webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ProjectList.tsx rename to webapp/packages/supersonic-fe/src/pages/SemanticModel/components/DomainList.tsx index 207951349..c9792fec4 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ProjectList.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/DomainList.tsx @@ -17,7 +17,7 @@ import { ISemantic } from '../data'; const { Search } = Input; -type ProjectListProps = { +type DomainListProps = { selectDomainId: number; selectDomainName: string; domainList: ISemantic.IDomainItem[]; @@ -43,7 +43,7 @@ const projectTreeFlat = (projectTree: DataNode[], filterValue: string): DataNode return newProjectTree; }; -const ProjectListTree: FC = ({ +const DomainListTree: FC = ({ selectDomainId, domainList, createDomainBtnVisible = true, @@ -184,7 +184,7 @@ const ProjectListTree: FC = ({ }; return ( -
    +
    void; + treeSelectProps?: Record; + domainList: ISemantic.IDomainItem[]; + dispatch: Dispatch; +}; + +const DomainTreeSelect: FC = ({ + value, + onChange, + treeSelectProps = {}, + domainList, + dispatch, +}) => { + const [domainTree, setDomainTree] = useState([]); + + const initProjectTree = async () => { + const { code, data, msg } = await getDomainList(); + if (code === 200) { + dispatch({ + type: 'domainManger/setDomainList', + payload: { domainList: data }, + }); + } else { + message.error(msg); + } + }; + + useEffect(() => { + if (domainList.length === 0) { + initProjectTree(); + } + }, []); + + useEffect(() => { + const treeData = addPathInTreeData(constructorClassTreeFromList(domainList)); + setDomainTree(treeData); + }, [domainList]); + + return ( +
    + +
    + ); +}; + +export default connect( + ({ + domainManger: { selectDomainId, selectDomainName, domainList }, + }: { + domainManger: StateType; + }) => ({ + selectDomainId, + selectDomainName, + domainList, + }), +)(DomainTreeSelect); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DefaultSettingForm.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DefaultSettingForm.tsx index 633b8227e..c1659473f 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DefaultSettingForm.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DefaultSettingForm.tsx @@ -33,8 +33,6 @@ const DefaultSettingForm: ForwardRefRenderFunction = ( ) => { const [form] = Form.useForm(); const [metricListOptions, setMetricListOptions] = useState([]); - const [unitState, setUnit] = useState(); - const [periodState, setPeriod] = useState(); const [dataItemListOptions, setDataItemListOptions] = useState([]); const formatEntityData = formatRichEntityDataListToIds(entityData); const getFormValidateFields = async () => { @@ -47,15 +45,10 @@ const DefaultSettingForm: ForwardRefRenderFunction = ( 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, @@ -172,6 +165,7 @@ const DefaultSettingForm: ForwardRefRenderFunction = ( initialValues={{ unit: 7, period: 'DAY', + timeMode: 'LAST', }} > */} )} - = ( } > - - {chatConfigType === ChatConfigType.DETAIL ? '前' : '最近'} - - { - setUnit(value); - form.setFieldValue('unit', value); - }} - /> - + {chatConfigType === ChatConfigType.DETAIL ? ( + + 前 + + ) : ( + <> + + + + + )} + + + + + + - - - -
    diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/EntityCreateForm.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/EntityCreateForm.tsx index 8ab1885dd..343a9f41a 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/EntityCreateForm.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/EntityCreateForm.tsx @@ -7,7 +7,7 @@ import { formLayout } from '@/components/FormHelper/utils'; import styles from '../style.less'; type Props = { - entityData?: { id: number; names: string[] }; + domainData?: ISemantic.IDomainItem; dimensionList: ISemantic.IDimensionList; domainId: number; onSubmit: () => void; @@ -16,7 +16,7 @@ type Props = { const FormItem = Form.Item; const EntityCreateForm: ForwardRefRenderFunction = ( - { entityData, dimensionList, domainId, onSubmit }, + { domainData, dimensionList, domainId, onSubmit }, ref, ) => { const [form] = Form.useForm(); @@ -27,15 +27,15 @@ const EntityCreateForm: ForwardRefRenderFunction = ( useEffect(() => { form.resetFields(); - if (!entityData) { + if (!domainData?.entity) { return; } - + const { entity } = domainData; form.setFieldsValue({ - ...entityData, - name: entityData.names.join(','), + ...entity, + name: entity.names.join(','), }); - }, [entityData]); + }, [domainData]); useImperativeHandle(ref, () => ({ getFormValidateFields, @@ -54,8 +54,8 @@ const EntityCreateForm: ForwardRefRenderFunction = ( const saveEntity = async () => { const values = await form.validateFields(); const { name } = values; - const { code, msg, data } = await updateDomain({ + ...domainData, entity: { ...values, names: name.split(','), diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/EntitySettingSection.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/EntitySettingSection.tsx index 74f2f2694..c407d942a 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/EntitySettingSection.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/EntitySettingSection.tsx @@ -6,7 +6,7 @@ import type { StateType } from '../../model'; import { getDomainDetail } from '../../service'; import ProCard from '@ant-design/pro-card'; import EntityCreateForm from './EntityCreateForm'; -import type { IChatConfig } from '../../data'; +import type { ISemantic } from '../../data'; type Props = { dispatch: Dispatch; @@ -14,9 +14,9 @@ type Props = { }; const EntitySettingSection: React.FC = ({ domainManger }) => { - const { selectDomainId, dimensionList, metricList } = domainManger; + const { selectDomainId, dimensionList } = domainManger; - const [entityData, setEntityData] = useState(); + const [domainData, setDomainData] = useState(); const entityCreateRef = useRef({}); @@ -26,9 +26,7 @@ const EntitySettingSection: React.FC = ({ domainManger }) => { }); if (code === 200) { - const { entity } = data; - - setEntityData(entity); + setDomainData(data); return; } @@ -52,7 +50,7 @@ const EntitySettingSection: React.FC = ({ domainManger }) => { { queryDomainData(); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/MetricInfoCreateForm.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/MetricInfoCreateForm.tsx index 434341d97..6e972ed3e 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/MetricInfoCreateForm.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/MetricInfoCreateForm.tsx @@ -20,6 +20,7 @@ import { getMeasureListByDomainId } from '../service'; import { creatExprMetric, updateExprMetric } from '../service'; import { ISemantic } from '../data'; import { history } from 'umi'; +import { check } from 'prettier'; export type CreateFormProps = { datasourceId?: number; @@ -97,7 +98,6 @@ const MetricInfoCreateForm: React.FC = ({ if (currentStep < 1) { forward(); } else { - // onSubmit?.(submitForm); await saveMetric(submitForm); } }; @@ -244,7 +244,11 @@ const MetricInfoCreateForm: React.FC = ({ name="isPercent" valuePropName="checked" > - + { + form.setFieldValue(['dataFormat', 'needMultiply100'], checked); + }} + /> {isPercentState && ( <> @@ -265,10 +269,6 @@ const MetricInfoCreateForm: React.FC = ({ title={'原始值是否乘以100'} subTitle={'如 原始值0.001 ->展示值0.1% '} /> - // 0.02%'} - // /> } name={['dataFormat', 'needMultiply100']} valuePropName="checked" diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Permission/PermissionAdminForm.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Permission/PermissionAdminForm.tsx index a2e478f4c..06afbc3c0 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Permission/PermissionAdminForm.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Permission/PermissionAdminForm.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { Form, Input, Switch, message } from 'antd'; -import SelectPartenr from '@/components/SelectPartner'; +import SelectPartner from '@/components/SelectPartner'; import SelectTMEPerson from '@/components/SelectTMEPerson'; import { connect } from 'umi'; import type { Dispatch } from 'umi'; @@ -110,7 +110,7 @@ const PermissionAdminForm: React.FC = ({ domainManger, onValuesChange }) <> {APP_TARGET === 'inner' && ( - = ({ const { dimensionList, metricList } = domainManger; const [form] = Form.useForm(); const basicInfoFormRef = useRef(null); - const [sourceDimensionList, setSourceDimensionList] = useState([]); - const [sourceMetricList, setSourceMetricList] = useState([]); const [selectedDimensionKeyList, setSelectedDimensionKeyList] = useState([]); const [selectedMetricKeyList, setSelectedMetricKeyList] = useState([]); - useEffect(() => { - const list = dimensionList.reduce((highList: any[], item: any) => { - const { name, bizName, sensitiveLevel } = item; - if (sensitiveLevel === 2) { - highList.push({ id: bizName, name, type: 'dimension' }); - } - return highList; - }, []); - setSourceDimensionList(list); - }, [dimensionList]); - - useEffect(() => { - const list = metricList.reduce((highList: any[], item: any) => { - const { name, bizName, sensitiveLevel } = item; - if (sensitiveLevel === 2) { - highList.push({ id: bizName, name, type: 'metric' }); - } - return highList; - }, []); - setSourceMetricList(list); - }, [metricList]); + const [selectedKeyList, setSelectedKeyList] = useState([]); const saveAuth = async () => { const basicInfoFormValues = await basicInfoFormRef.current.formRef.validateFields(); @@ -103,9 +83,24 @@ const PermissionCreateDrawer: React.FC = ({ dimensionFilterDescription, dimensionFilters: Array.isArray(dimensionFilters) ? dimensionFilters[0] || '' : '', }); + const dimensionAuth = permissonData?.authRules?.[0]?.dimensions || []; + const metricAuth = permissonData?.authRules?.[0]?.metrics || []; + setSelectedDimensionKeyList(dimensionAuth); + setSelectedMetricKeyList(metricAuth); - setSelectedDimensionKeyList(permissonData?.authRules?.[0]?.dimensions || []); - setSelectedMetricKeyList(permissonData?.authRules?.[0]?.metrics || []); + const dimensionKeys = dimensionList.reduce((dimensionChangeList: string[], item: any) => { + if (dimensionAuth.includes(item.bizName)) { + dimensionChangeList.push(wrapperTransTypeAndId(TransType.DIMENSION, item.id)); + } + return dimensionChangeList; + }, []); + const metricKeys = metricList.reduce((metricChangeList: string[], item: any) => { + if (metricAuth.includes(item.bizName)) { + metricChangeList.push(wrapperTransTypeAndId(TransType.METRIC, item.id)); + } + return metricChangeList; + }, []); + setSelectedKeyList([...dimensionKeys, ...metricKeys]); }, [permissonData]); const renderFooter = () => { @@ -138,7 +133,7 @@ const PermissionCreateDrawer: React.FC = ({ footer={renderFooter()} onClose={onCancel} > -
    +
    = ({ /> - + { + sourceList={[ + ...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), + }; + }), + ]} + targetList={selectedKeyList} + onChange={(newTargetKeys: string[]) => { + setSelectedKeyList(newTargetKeys); const dimensionKeyChangeList = dimensionList.reduce( (dimensionChangeList: string[], item: any) => { - if (bizNameList.includes(item.bizName)) { + if ( + newTargetKeys.includes(wrapperTransTypeAndId(TransType.DIMENSION, item.id)) + ) { dimensionChangeList.push(item.bizName); } return dimensionChangeList; @@ -165,7 +182,9 @@ const PermissionCreateDrawer: React.FC = ({ ); const metricKeyChangeList = metricList.reduce( (metricChangeList: string[], item: any) => { - if (bizNameList.includes(item.bizName)) { + if ( + newTargetKeys.includes(wrapperTransTypeAndId(TransType.METRIC, item.id)) + ) { metricChangeList.push(item.bizName); } return metricChangeList; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Permission/PermissionCreateForm.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Permission/PermissionCreateForm.tsx index afe5a686d..9da7c9fe0 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Permission/PermissionCreateForm.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Permission/PermissionCreateForm.tsx @@ -1,7 +1,7 @@ import { useEffect, useImperativeHandle, forwardRef } from 'react'; import { Form, Input } from 'antd'; import type { ForwardRefRenderFunction } from 'react'; -import SelectPartenr from '@/components/SelectPartner'; +import SelectPartner from '@/components/SelectPartner'; import SelectTMEPerson from '@/components/SelectTMEPerson'; import { formLayout } from '@/components/FormHelper/utils'; import styles from '../style.less'; @@ -54,7 +54,7 @@ const PermissionCreateForm: ForwardRefRenderFunction = ( {APP_TARGET === 'inner' && ( - = ({ domainManger }) => { }, [selectDomainId]); const queryDepartmentData = async () => { - const { code, data } = await getDepartmentTree(); + const { code, data } = await getOrganizationTree(); if (code === 200 || code === '0') { setDepartmentTreeData(data); } @@ -120,7 +120,7 @@ const PermissionTable: React.FC = ({ domainManger }) => { render: (_, record: any) => { const { authorizedUsers = [] } = record; const personNameList = tmePerson.reduce((enNames: string[], item: any) => { - const hasPerson = authorizedUsers.includes(item.enName); + const hasPerson = authorizedUsers.includes(item.name); if (hasPerson) { enNames.push(item.displayName); } diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/style.less b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/style.less index 0b2640d1e..98843fdc1 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/style.less +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/style.less @@ -95,7 +95,11 @@ } } -.projectList { +.domainTreeSelect { + width: 300px; +} + +.domainList { display: flex; flex-direction: column; width: 400px; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/data.d.ts b/webapp/packages/supersonic-fe/src/pages/SemanticModel/data.d.ts index 757edf084..a97c1f26e 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/data.d.ts +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/data.d.ts @@ -89,6 +89,7 @@ export declare namespace ISemantic { admins?: string[]; adminOrgs?: any[]; isOpen?: number; + entity?: { entityId: number; names: string[] }; dimensionCnt?: number; metricCnt?: number; } diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/service.ts b/webapp/packages/supersonic-fe/src/pages/SemanticModel/service.ts index 661cac9ee..70e109274 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/service.ts +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/service.ts @@ -1,7 +1,11 @@ import request from 'umi-request'; +const getRunningEnv = () => { + return window.location.pathname.includes('/chatSetting/') ? 'chat' : 'semantic'; +}; + export function getDomainList(): Promise { - if (window.RUNNING_ENV === 'chat') { + if (getRunningEnv() === 'chat') { return request.get(`${process.env.CHAT_API_BASE_URL}conf/domainList`); } return request.get(`${process.env.API_BASE_URL}domain/getDomainList`); @@ -40,10 +44,11 @@ export function updateDatasource(data: any): Promise { } export function getDimensionList(data: any): Promise { + const { domainId } = data; const queryParams = { - data: { current: 1, pageSize: 999999, ...data }, + data: { current: 1, pageSize: 999999, ...data, ...(domainId ? { domainIds: [domainId] } : {}) }, }; - if (window.RUNNING_ENV === 'chat') { + if (getRunningEnv() === 'chat') { return request.post(`${process.env.CHAT_API_BASE_URL}conf/dimension/page`, queryParams); } return request.post(`${process.env.API_BASE_URL}dimension/queryDimension`, queryParams); @@ -62,10 +67,11 @@ export function updateDimension(data: any): Promise { } export function queryMetric(data: any): Promise { + const { domainId } = data; const queryParams = { - data: { current: 1, pageSize: 999999, ...data }, + data: { current: 1, pageSize: 999999, ...data, ...(domainId ? { domainIds: [domainId] } : {}) }, }; - if (window.RUNNING_ENV === 'chat') { + if (getRunningEnv() === 'chat') { return request.post(`${process.env.CHAT_API_BASE_URL}conf/metric/page`, queryParams); } return request.post(`${process.env.API_BASE_URL}metric/queryMetric`, queryParams); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/utils.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/utils.tsx index 73b19381f..bd9a5563a 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/utils.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/utils.tsx @@ -2,7 +2,7 @@ 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[] => { +export const changeTreeData = (treeData: API.DomainList, auth?: boolean): DataNode[] => { return treeData.map((item: any) => { const newItem: DataNode = { ...item, @@ -14,7 +14,7 @@ export const changeTreeData = (treeData: API.ProjectList, auth?: boolean): DataN }); }; -export const addPathInTreeData = (treeData: API.ProjectList, loopPath: any[] = []): any => { +export const addPathInTreeData = (treeData: API.DomainList, loopPath: any[] = []): any => { return treeData.map((item: any) => { const { children, parentId = [] } = item; const path = loopPath.slice(); @@ -41,6 +41,7 @@ export const constructorClassTreeFromList = (list: any[], parentId: number = 0) nodeItem.children = children; } nodeItem.key = nodeItem.id; + nodeItem.value = nodeItem.id; nodeItem.title = nodeItem.name; nodeList.push(nodeItem); } @@ -49,7 +50,7 @@ export const constructorClassTreeFromList = (list: any[], parentId: number = 0) return tree; }; -export const treeParentKeyLists = (treeData: API.ProjectList): string[] => { +export const treeParentKeyLists = (treeData: API.DomainList): string[] => { let keys: string[] = []; treeData.forEach((item: any) => { if (item.children && item.children.length > 0) { @@ -67,10 +68,10 @@ export const findDepartmentTree: any = (treeData: any[], projectId: string) => { } let newStepList: any[] = []; const departmentData = treeData.find((item) => { - if (item.subDepartments) { - newStepList = newStepList.concat(item.subDepartments); + if (item.subOrganizations) { + newStepList = newStepList.concat(item.subOrganizations); } - return item.key === projectId; + return item.id === projectId; }); if (departmentData) { return departmentData;