From 18aa14118c118fd1ae0968b2f74babdbd948760f Mon Sep 17 00:00:00 2001 From: tristanliu <37809633+sevenliu1896@users.noreply.github.com> Date: Thu, 9 Nov 2023 05:37:36 -0600 Subject: [PATCH] [improvement][semantic-fe] Redesigning the indicator homepage to incorporate trend charts and table functionality for indicators (#347) * [improvement][semantic-fe] Add model alias setting & Add view permission restrictions to the model permission management tab. [improvement][semantic-fe] Add permission control to the action buttons for the main domain; apply high sensitivity filtering to the authorization of metrics/dimensions. [improvement][semantic-fe] Optimize the editing mode in the dimension/metric/datasource components to use the modelId stored in the database for data, instead of relying on the data from the state manager. * [improvement][semantic-fe] Add time granularity setting in the data source configuration. * [improvement][semantic-fe] Dictionary import for dimension values supported in Q&A visibility * [improvement][semantic-fe] Modification of data source creation prompt wording" * [improvement][semantic-fe] metric market experience optimization * [improvement][semantic-fe] enhance the analysis of metric trends * [improvement][semantic-fe] optimize the presentation of metric trend permissions * [improvement][semantic-fe] add metric trend download functionality * [improvement][semantic-fe] fix the dimension initialization issue in metric correlation * [improvement][semantic-fe] Fix the issue of database changes not taking effect when creating based on an SQL data source. * [improvement][semantic-fe] Optimizing pagination logic and some CSS styles * [improvement][semantic-fe] Fixing the API for the indicator list by changing "current" to "pageNum" * [improvement][semantic-fe] Fixing the default value setting for the indicator list * [improvement][semantic-fe] Adding batch operations for indicators/dimensions/models * [improvement][semantic-fe] Replacing the single status update API for indicators/dimensions with a batch update API * [improvement][semantic-fe] Redesigning the indicator homepage to incorporate trend charts and table functionality for indicators --- .../packages/supersonic-fe/config/routes.ts | 36 +- .../src/components/MDatePicker/index.tsx | 6 + .../src/components/RemoteSelect/index.tsx | 1 - .../supersonic-fe/src/locales/zh-CN/menu.ts | 7 +- .../ChatSetting/ChatSettingSection.tsx | 2 +- .../src/pages/SemanticModel/Metric/Detail.tsx | 316 ++++++++++++ .../src/pages/SemanticModel/Metric/Market.tsx | 456 +++++++++++++++++ .../Metric/components/MetricTrend.tsx | 100 +++- .../components/MetricTrendDimensionFilter.tsx | 26 +- .../MetricTrendDimensionFilterContainer.tsx | 91 +++- .../Metric/components/MetricTrendSection.tsx | 327 +++++++++---- .../SemanticModel/Metric/components/Table.tsx | 74 +++ .../src/pages/SemanticModel/Metric/data.d.ts | 9 + .../src/pages/SemanticModel/Metric/index.tsx | 457 +----------------- .../src/pages/SemanticModel/Metric/style.less | 79 ++- .../pages/SemanticModel/OverviewContainer.tsx | 168 +++---- .../DimensionAndMetricRelationModal.tsx | 26 +- .../DimensionMetricRelationTableTransfer.tsx | 4 +- .../SemanticModel/components/DomainList.tsx | 24 +- .../components/DomainManagerTab.tsx | 61 ++- .../Entity/EntitySettingSection.tsx | 2 +- .../Entity/RecommendedQuestionsSection.tsx | 2 +- .../components/ModelCreateFormModal.tsx | 1 + .../pages/SemanticModel/components/style.less | 266 +++++----- .../src/pages/SemanticModel/constant.ts | 18 +- .../src/pages/SemanticModel/service.ts | 6 +- .../packages/supersonic-fe/src/typings.d.ts | 5 + 27 files changed, 1654 insertions(+), 916 deletions(-) create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/Detail.tsx create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/Market.tsx create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/components/Table.tsx diff --git a/webapp/packages/supersonic-fe/config/routes.ts b/webapp/packages/supersonic-fe/config/routes.ts index c7f5c4058..40a120873 100644 --- a/webapp/packages/supersonic-fe/config/routes.ts +++ b/webapp/packages/supersonic-fe/config/routes.ts @@ -35,23 +35,10 @@ const ROUTES = [ envEnableList: [ENV_KEY.CHAT], }, { - path: '/model', + path: '/model/:domainId?/:modelId?/:menuKey?', + component: './SemanticModel/DomainManager', name: 'semanticModel', envEnableList: [ENV_KEY.SEMANTIC], - routes: [ - { - path: '/model/:domainId?/:modelId?/:menuKey?', - component: './SemanticModel/DomainManager', - name: 'model', - envEnableList: [ENV_KEY.SEMANTIC], - }, - { - path: '/database', - name: 'database', - component: './SemanticModel/components/Database/DatabaseTable', - envEnableList: [ENV_KEY.SEMANTIC], - }, - ], }, { @@ -66,6 +53,25 @@ const ROUTES = [ name: 'metric', component: './SemanticModel/Metric', envEnableList: [ENV_KEY.SEMANTIC], + routes: [ + { + path: '/metric', + redirect: '/metric/market', + }, + { + path: '/metric/market', + component: './SemanticModel/Metric/Market', + hideInMenu: true, + envEnableList: [ENV_KEY.SEMANTIC], + }, + { + path: '/metric/detail/:metricId', + name: 'metricDetail', + hideInMenu: true, + component: './SemanticModel/Metric/Detail', + envEnableList: [ENV_KEY.SEMANTIC], + }, + ], }, { path: '/plugin', diff --git a/webapp/packages/supersonic-fe/src/components/MDatePicker/index.tsx b/webapp/packages/supersonic-fe/src/components/MDatePicker/index.tsx index b0b05bb0c..bcbeb6b8b 100644 --- a/webapp/packages/supersonic-fe/src/components/MDatePicker/index.tsx +++ b/webapp/packages/supersonic-fe/src/components/MDatePicker/index.tsx @@ -24,6 +24,7 @@ type Props = { showCurrentDataRangeString?: boolean; onDateRangeChange: (value: string[], from: any) => void; onDateRangeTypeChange?: (dateRangeType: DateRangeType) => void; + onInit?: (params: { dateStringRange: string[] }) => void; }; const { CheckableTag } = Tag; @@ -33,6 +34,7 @@ const MDatePicker: React.FC = ({ showCurrentDataRangeString = true, onDateRangeChange, onDateRangeTypeChange, + onInit, }: any) => { const getDynamicDefaultConfig = (dateRangeType: DateRangeType) => { const dynamicDefaultConfig = { @@ -151,6 +153,10 @@ const MDatePicker: React.FC = ({ } } } + useEffect(() => { + onInit?.({ dateRange: currentDateRange }); + }, []); + useEffect(() => { setSelectedDateRangeString(getSelectedDateRangeString()); }, [staticParams, dynamicParams, currentDateRange]); diff --git a/webapp/packages/supersonic-fe/src/components/RemoteSelect/index.tsx b/webapp/packages/supersonic-fe/src/components/RemoteSelect/index.tsx index 152c2c6d8..d60786c27 100644 --- a/webapp/packages/supersonic-fe/src/components/RemoteSelect/index.tsx +++ b/webapp/packages/supersonic-fe/src/components/RemoteSelect/index.tsx @@ -80,7 +80,6 @@ const DebounceSelect = forwardRef( if (disabledSearch) { return; } - console.log(!allowEmptyValue && !value, value, allowEmptyValue, 333); if (!allowEmptyValue && !value) return; fetchRef.current += 1; const fetchId = fetchRef.current; diff --git a/webapp/packages/supersonic-fe/src/locales/zh-CN/menu.ts b/webapp/packages/supersonic-fe/src/locales/zh-CN/menu.ts index 13c820348..07f2ac053 100644 --- a/webapp/packages/supersonic-fe/src/locales/zh-CN/menu.ts +++ b/webapp/packages/supersonic-fe/src/locales/zh-CN/menu.ts @@ -7,11 +7,10 @@ export default { 'menu.exception.not-permission': '403', 'menu.exception.not-find': '404', 'menu.exception.server-error': '500', - 'menu.semanticModel': '语义模型', - 'menu.semanticModel.model': '模型管理', - 'menu.semanticModel.database': '数据库连接', + 'menu.semanticModel': '语义建模', 'menu.metric': '指标市场', - 'menu.database': '数据库连接', + 'menu.metric.metricDetail': '指标详情页', + 'menu.database': '数据库管理', 'menu.chatSetting': '问答设置', 'menu.plugin': '插件市场', 'menu.login': '登录', diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/ChatSetting/ChatSettingSection.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/ChatSetting/ChatSettingSection.tsx index cf54f5e0e..ab755dc22 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/ChatSetting/ChatSettingSection.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/ChatSetting/ChatSettingSection.tsx @@ -35,7 +35,7 @@ const ChatSettingSection: React.FC = () => { // ]; return ( -
+
{/* void; + onEditBtnClick?: (metircData: any) => void; + [key: string]: any; +}; + +interface DescriptionItemProps { + title: string | ReactNode; + content: React.ReactNode; + icon: ReactNode; +} + +// type InfoListItemChildrenItem = { +// label: string; +// value: string; +// content?: ReactNode; +// hideItem?: boolean; +// }; + +// type InfoListItem = { +// title: string; +// hideItem?: boolean; +// render?: () => ReactNode; +// children?: InfoListItemChildrenItem[]; +// }; + +const DescriptionItem = ({ title, content, icon }: DescriptionItemProps) => ( + +
+ + {icon} + {content} + +
+
+); + +const MetricDetail: React.FC = ({ domainManger }) => { + const params: any = useParams(); + const metricId = params.metricId; + // const bizName = params.bizName; + + // const [infoList, setInfoList] = useState([]); + // const { selectModelName } = domainManger; + const [metircData, setMetircData] = useState(); + useEffect(() => { + queryMetricData(metricId); + }, [metricId]); + + const queryMetricData = async (metricId: string) => { + const { code, data, msg } = await getMetricData(metricId); + if (code === 200) { + setMetircData(data); + return; + } + message.error(msg); + }; + + // useEffect(() => { + // if (!metircData) { + // return; + // } + // const { + // alias, + // bizName, + // createdBy, + // createdAt, + // updatedAt, + // description, + // sensitiveLevel, + // modelName, + // } = metircData; + + // const list = [ + // { + // title: '基本信息', + // children: [ + // { + // label: '字段名称', + // value: bizName, + // }, + // { + // label: '别名', + // hideItem: !alias, + // value: alias || '-', + // }, + // { + // label: '所属模型', + // value: modelName, + // content: {modelName || selectModelName}, + // }, + + // { + // label: '描述', + // value: description, + // }, + // ], + // }, + // { + // title: '应用信息', + // children: [ + // { + // label: '敏感度', + // value: SENSITIVE_LEVEL_ENUM[sensitiveLevel], + // }, + // ], + // }, + // // { + // // title: '指标趋势', + // // render: () => ( + // // + // // + // // + // // + // // + // // ), + // // }, + // { + // title: '创建信息', + // children: [ + // { + // label: '创建人', + // value: createdBy, + // }, + // { + // label: '创建时间', + // value: createdAt ? moment(createdAt).format('YYYY-MM-DD HH:mm:ss') : '', + // }, + // { + // label: '更新时间', + // value: updatedAt ? moment(updatedAt).format('YYYY-MM-DD HH:mm:ss') : '', + // }, + // ], + // }, + // ]; + + // setInfoList(list); + // }, [metircData]); + + const tabItems: TabsProps['items'] = [ + { + key: 'metricTrend', + label: '图表', + children: , + }, + // { + // key: '2', + // label: 'Tab 2', + // children: 'Content of Tab Pane 2', + // }, + ]; + + const contentStyle: React.CSSProperties = { + minHeight: 120, + color: '#fff', + // marginRight: 15, + backgroundColor: '#fff', + }; + + // const siderStyle: React.CSSProperties = { + // width: '300px', + // backgroundColor: '#fff', + // }; + + return ( + + + +

+
+
{ + history.push(`/metric/market`); + }} + > + +
+ +
+ + + {metircData?.name} + {metircData?.alias && `[${metircData.alias}]`} + + {metircData?.name && ( + <> + | + {metircData?.bizName} + + )} + {metircData?.sensitiveLevel !== undefined && ( + + + {SENSITIVE_LEVEL_ENUM[metircData.sensitiveLevel]}敏感度 + + + )} + + {metircData?.description ? ( +
+ + + + {metircData?.description} + + +
+ ) : ( + <> + )} +
+
+
+ {metircData?.createdBy ? ( + <> + {/*
+ } + content={metircData?.modelName} + /> + + } + content={metircData?.description} + /> +
*/} +
+ } + content={metircData?.createdBy} + /> + + } + content={moment(metircData?.updatedAt).format('YYYY-MM-DD HH:mm:ss')} + /> +
+ + ) : ( + <> + )} +
+

+
+ +
+
+
+ {/* + <> +
+ {infoList.map((item) => { + const { children, title, render } = item; + return ( +
+

{title}

+ {render?.() || + (Array.isArray(children) && + children.map((childrenItem) => { + return ( + + + + + + ); + }))} + +
+ ); + })} +
+ +
*/} +
+ ); +}; + +export default connect(({ domainManger }: { domainManger: StateType }) => ({ + domainManger, +}))(MetricDetail); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/Market.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/Market.tsx new file mode 100644 index 000000000..5fa073809 --- /dev/null +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/Market.tsx @@ -0,0 +1,456 @@ +import type { ActionType, ProColumns } from '@ant-design/pro-table'; +import ProTable from '@ant-design/pro-table'; +import { message, Space, Popconfirm, Tag, Spin, Dropdown } from 'antd'; +import React, { useRef, useState, useEffect } from 'react'; +import type { Dispatch } from 'umi'; +import { connect, history, useModel } from 'umi'; +import type { StateType } from '../model'; +import { SENSITIVE_LEVEL_ENUM } from '../constant'; +import { queryMetric, deleteMetric, batchUpdateMetricStatus } from '../service'; +import MetricFilter from './components/MetricFilter'; +import MetricInfoCreateForm from '../components/MetricInfoCreateForm'; +import MetricCardList from './components/MetricCardList'; +import NodeInfoDrawer from '../SemanticGraph/components/NodeInfoDrawer'; +import { SemanticNodeType, StatusEnum } from '../enum'; +import moment from 'moment'; +import styles from './style.less'; +import { ISemantic } from '../data'; + +type Props = { + dispatch: Dispatch; + domainManger: StateType; +}; + +type QueryMetricListParams = { + id?: string; + name?: string; + bizName?: string; + sensitiveLevel?: string; + type?: string; + [key: string]: any; +}; + +const ClassMetricTable: React.FC = ({ domainManger, dispatch }) => { + const { initialState = {} } = useModel('@@initialState'); + + const { currentUser = {} } = initialState as any; + + const { selectDomainId, selectModelId: modelId } = domainManger; + const [createModalVisible, setCreateModalVisible] = useState(false); + const defaultPagination = { + current: 1, + pageSize: 20, + total: 0, + }; + const [pagination, setPagination] = useState(defaultPagination); + const [loading, setLoading] = useState(false); + const [dataSource, setDataSource] = useState([]); + const [metricItem, setMetricItem] = useState(); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + const [filterParams, setFilterParams] = useState>({ + showType: localStorage.getItem('metricMarketShowType') === '1' ? true : false, + }); + const [infoDrawerVisible, setInfoDrawerVisible] = useState(false); + const actionRef = useRef(); + + useEffect(() => { + queryMetricList(filterParams); + }, []); + + const queryBatchUpdateStatus = async (ids: React.Key[], status: StatusEnum) => { + if (Array.isArray(ids) && ids.length === 0) { + return; + } + const { code, msg } = await batchUpdateMetricStatus({ + ids, + status, + }); + if (code === 200) { + queryMetricList(filterParams); + return; + } + message.error(msg); + }; + + const queryMetricList = async (params: QueryMetricListParams = {}, disabledLoading = false) => { + if (!disabledLoading) { + setLoading(true); + } + const { code, data, msg } = await queryMetric({ + ...pagination, + ...params, + createdBy: params.onlyShowMe ? currentUser.name : null, + pageSize: params.showType ? 100 : params.pageSize || pagination.pageSize, + }); + setLoading(false); + const { list, pageSize, pageNum, total } = data || {}; + let resData: any = {}; + if (code === 200) { + if (!params.showType) { + setPagination({ + ...pagination, + pageSize: Math.min(pageSize, 100), + current: pageNum, + total, + }); + } + + setDataSource(list); + resData = { + data: list || [], + success: true, + }; + } else { + message.error(msg); + setDataSource([]); + resData = { + data: [], + total: 0, + success: false, + }; + } + return resData; + }; + + const deleteMetricQuery = async (id: number) => { + const { code, msg } = await deleteMetric(id); + if (code === 200) { + setMetricItem(undefined); + queryMetricList(filterParams); + } else { + message.error(msg); + } + }; + + const handleMetricEdit = (metricItem: ISemantic.IMetricItem) => { + setMetricItem(metricItem); + setCreateModalVisible(true); + }; + + const columns: ProColumns[] = [ + { + dataIndex: 'id', + title: 'ID', + }, + { + dataIndex: 'name', + title: '指标名称', + render: (_, record: any) => { + return ( + { + // setMetricItem(record); + // setInfoDrawerVisible(true); + history.push(`/metric/detail/${record.id}`); + }} + > + {record.name} + + ); + }, + }, + { + dataIndex: 'modelName', + title: '所属模型', + render: (_, record: any) => { + if (record.hasAdminRes) { + return ( + { + history.replace(`/model/${record.domainId}/${record.modelId}/metric`); + }} + > + {record.modelName} + + ); + } + return <> {record.modelName}; + }, + }, + { + dataIndex: 'sensitiveLevel', + title: '敏感度', + valueEnum: SENSITIVE_LEVEL_ENUM, + }, + { + dataIndex: 'status', + title: '状态', + width: 80, + search: false, + render: (status) => { + switch (status) { + case StatusEnum.ONLINE: + return 已启用; + case StatusEnum.OFFLINE: + return 未启用; + case StatusEnum.INITIALIZED: + return 初始化; + case StatusEnum.DELETED: + return 已删除; + default: + return 未知; + } + }, + }, + { + dataIndex: 'createdBy', + title: '创建人', + search: false, + }, + { + dataIndex: 'tags', + title: '标签', + search: false, + render: (tags) => { + if (Array.isArray(tags)) { + return ( + + {tags.map((tag) => ( + + {tag} + + ))} + + ); + } + return <>--; + }, + }, + { + dataIndex: 'description', + title: '描述', + search: false, + }, + { + dataIndex: 'updatedAt', + title: '更新时间', + search: false, + render: (value: any) => { + return value && value !== '-' ? moment(value).format('YYYY-MM-DD HH:mm:ss') : '-'; + }, + }, + { + title: '操作', + dataIndex: 'x', + valueType: 'option', + render: (_, record) => { + if (record.hasAdminRes) { + return ( + + { + handleMetricEdit(record); + }} + > + 编辑 + + + { + deleteMetricQuery(record.id); + }} + > + { + setMetricItem(record); + }} + > + 删除 + + + + ); + } else { + return <>; + } + }, + }, + ]; + + const handleFilterChange = async (filterParams: { + key: string; + sensitiveLevel: string; + type: string; + }) => { + const { sensitiveLevel, type } = filterParams; + const params: QueryMetricListParams = { ...filterParams }; + const sensitiveLevelValue = sensitiveLevel?.[0]; + const typeValue = type?.[0]; + + params.sensitiveLevel = sensitiveLevelValue; + params.type = typeValue; + setFilterParams(params); + await queryMetricList(params, filterParams.key ? false : true); + }; + + const rowSelection = { + onChange: (selectedRowKeys: React.Key[]) => { + setSelectedRowKeys(selectedRowKeys); + }, + getCheckboxProps: (record: ISemantic.IMetricItem) => ({ + disabled: !record.hasAdminRes, + }), + }; + + const dropdownButtonItems = [ + { + key: 'batchStart', + label: '批量启用', + }, + { + key: 'batchStop', + label: '批量停用', + }, + { + key: 'batchDelete', + label: ( + { + queryBatchUpdateStatus(selectedRowKeys, StatusEnum.DELETED); + }} + > + 批量删除 + + ), + }, + ]; + + const onMenuClick = ({ key }: { key: string }) => { + switch (key) { + case 'batchStart': + queryBatchUpdateStatus(selectedRowKeys, StatusEnum.ONLINE); + break; + case 'batchStop': + queryBatchUpdateStatus(selectedRowKeys, StatusEnum.OFFLINE); + break; + default: + break; + } + }; + + return ( + <> +
+ { + if (_.showType !== undefined) { + setLoading(true); + setDataSource([]); + } + handleFilterChange(values); + }} + /> +
+ <> + {filterParams.showType ? ( + + { + history.push(`/metric/detail/${metricItem.modelId}/${metricItem.bizName}`); + }} + onDeleteBtnClick={(metricItem: ISemantic.IMetricItem) => { + deleteMetricQuery(metricItem.id); + }} + onEditBtnClick={(metricItem: ISemantic.IMetricItem) => { + setMetricItem(metricItem); + setCreateModalVisible(true); + }} + /> + + ) : ( + { + return false; + }} + rowSelection={{ + type: 'checkbox', + ...rowSelection, + }} + toolBarRender={() => [ + + 批量操作 + , + ]} + loading={loading} + onChange={(data: any) => { + const { current, pageSize, total } = data; + const pagin = { + current, + pageSize, + total, + }; + setPagination(pagin); + queryMetricList({ ...pagin, ...filterParams }); + }} + size="small" + options={{ reload: false, density: false, fullScreen: false }} + /> + )} + + + {createModalVisible && ( + { + setCreateModalVisible(false); + queryMetricList(filterParams); + dispatch({ + type: 'domainManger/queryMetricList', + payload: { + domainId: selectDomainId, + }, + }); + }} + onCancel={() => { + setCreateModalVisible(false); + }} + /> + )} + {infoDrawerVisible && ( + { + setInfoDrawerVisible(false); + }} + width="100%" + open={infoDrawerVisible} + mask={true} + getContainer={false} + onEditBtnClick={(nodeData: any) => { + handleMetricEdit(nodeData); + }} + maskClosable={true} + onNodeChange={({ eventName }: { eventName: string }) => { + setInfoDrawerVisible(false); + }} + /> + )} + + ); +}; +export default connect(({ domainManger }: { domainManger: StateType }) => ({ + domainManger, +}))(ClassMetricTable); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/components/MetricTrend.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/components/MetricTrend.tsx index 2aabfceae..5f931d9a5 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/components/MetricTrend.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/components/MetricTrend.tsx @@ -9,6 +9,7 @@ import { DownloadOutlined } from '@ant-design/icons'; import type { ECharts } from 'echarts'; import * as echarts from 'echarts'; import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { groupBy, sum } from 'lodash'; import styles from '../style.less'; import moment from 'moment'; @@ -17,8 +18,6 @@ type Props = { tip?: string; data: any[]; fields: any[]; - // columnFieldName: string; - // valueFieldName: string; loading: boolean; isPer?: boolean; isPercent?: boolean; @@ -27,6 +26,8 @@ type Props = { height?: number; renderType?: string; decimalPlaces?: number; + rowNumber?: number; + groupByDimensionFieldName?: string; onDownload?: () => void; }; @@ -38,9 +39,10 @@ const TrendChart: React.FC = ({ loading, isPer, isPercent, - dateFieldName, + dateFieldName = 'sys_imp_date', // columnFieldName, - // valueFieldName, + rowNumber = 0, + groupByDimensionFieldName, dateFormat, height, renderType, @@ -64,32 +66,67 @@ const TrendChart: React.FC = ({ new Set( data .map((item) => - moment(`${(dateFieldName && item[dateFieldName]) || item.sys_imp_date}`).format( - dateFormat ?? 'YYYY-MM-DD', - ), + moment(`${dateFieldName && item[dateFieldName]}`).format(dateFormat ?? 'YYYY-MM-DD'), ) .sort((a, b) => { return moment(a).valueOf() - moment(b).valueOf(); }), ), ); - const seriesData = fields.map((field) => { - const fieldData = { - type: 'line', - name: field.name, - symbol: 'circle', - showSymbol: data.length === 1, - smooth: true, - data: data.reduce((itemData, item) => { - const target = item[field.column]; - if (target) { - itemData.push(target); - } - return itemData; - }, []), - }; - return fieldData; - }); + + const formatterSeriesData = () => { + if (groupByDimensionFieldName) { + const groupByMap = groupBy(data, groupByDimensionFieldName); + + const seriesData = Object.keys(groupByMap).map((fieldKey: string) => { + const dimensionDataList = groupByMap[fieldKey]; + const dimensionDataMapByDate = dimensionDataList.reduce((itemMap, item) => { + itemMap[item[dateFieldName]] = { ...item }; + return itemMap; + }, {}); + const dataList = xData.reduce((itemData: any[], dateString) => { + const dimensionDataMapItem = dimensionDataMapByDate[dateString]; + if (dimensionDataMapItem) { + itemData.push(dimensionDataMapItem[fields?.[0]?.column]); + } else { + itemData.push(0); + } + return itemData; + }, []); + return { + type: 'line', + name: fieldKey, + symbol: 'circle', + smooth: true, + sortNum: sum(dataList), + data: dataList, + }; + }); + if (rowNumber) { + return seriesData.sort((a, b) => b.sortNum - a.sortNum).slice(0, rowNumber); + } + return seriesData; + } + const seriesData = fields.map((field) => { + const fieldData = { + type: 'line', + name: field.name, + symbol: 'circle', + showSymbol: data.length === 1, + smooth: true, + data: data.reduce((itemData, item) => { + const target = item[field.column]; + if (target) { + itemData.push(target); + } + return itemData; + }, []), + }; + return fieldData; + }); + return seriesData; + }; + const seriesData = formatterSeriesData(); instanceObj.setOption({ legend: { @@ -178,7 +215,18 @@ const TrendChart: React.FC = ({ series: seriesData, }); instanceObj.resize(); - }, [data, fields, instance, isPer, isPercent, dateFieldName, decimalPlaces, renderType]); + }, [ + data, + fields, + instance, + isPer, + isPercent, + dateFieldName, + decimalPlaces, + renderType, + rowNumber, + groupByDimensionFieldName, + ]); useEffect(() => { if (!loading) { @@ -203,7 +251,7 @@ const TrendChart: React.FC = ({ 300 ? 9 : 6 }} + paragraph={{ rows: height && height > 300 ? 15 : 6 }} />
void; }; @@ -19,20 +19,19 @@ type Props = { export type FormData = { dimensionBizName: string; operator: OperatorEnum; - dimensionValue: string | string[]; + dimensionValue?: string | string[]; }; const MetricTrendDimensionFilter: React.FC = ({ dimensionOptions, modelId, value, + periodDate, onChange, }) => { const [form] = Form.useForm(); - const dimensionValueSearchRef = useRef(); const queryParams = useRef<{ dimensionBizName?: string }>({}); - const [formData, setFormData] = useState({ operator: OperatorEnum.IN } as FormData); useEffect(() => { @@ -47,6 +46,11 @@ const MetricTrendDimensionFilter: React.FC = ({ if (formData.dimensionBizName) { queryParams.current = { dimensionBizName: formData.dimensionBizName }; dimensionValueSearchRef.current?.emitSearch(''); + form.setFieldValue('dimensionValue', undefined); + setFormData({ + ...formData, + dimensionValue: undefined, + }); } }, [formData.dimensionBizName]); @@ -59,7 +63,17 @@ const MetricTrendDimensionFilter: React.FC = ({ ...queryParams.current, value: searchValue, modelId, + // dateInfo: {}, limit: 50, + ...(periodDate?.startDate + ? { + dateInfo: { + dateMode: 'BETWEEN', + startDate: periodDate.startDate, + endDate: periodDate.endDate, + }, + } + : {}), }); if (code === 200 && Array.isArray(data?.resultList)) { return data.resultList.slice(0, 50).map((item: any) => ({ @@ -109,7 +123,7 @@ const MetricTrendDimensionFilter: React.FC = ({ placeholder="请选择筛选维度" /> - + - ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase()) - } - mode="multiple" - placeholder="请选择下钻维度" - onChange={(value) => { - const params = { ...queryParams, dimensionGroup: value || [] }; - setQueryParams(params); - getMetricTrendData({ ...params }); - }} - /> - - - - - { - const { - dimensionBizName: bizName, - dimensionValue: value, - operator, - } = filterParams; - if (bizName && value && operator) { - const params = { - ...queryParams, - dimensionFilters: [ - { - bizName: 'user_name', - value: ['williamhliu', 'leooonli'], - operator: 'in', - }, - ], - }; - setQueryParams(params); - getMetricTrendData({ ...params }); - } - }} - /> - - */} = ({ nodeData }) => { /> + + + + ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase()) + } + placeholder="展示维度切换" + onChange={(value) => { + setGroupByDimensionFieldName(value); + }} + /> + */} + + + + + + + + {chartType === 'chart' && ( + + )} +
+ +
+ + { + setMetricRelationModalOpenState(false); + }} + onSubmit={(relations) => { + setDrillDownDimensions(relations); + setMetricRelationModalOpenState(false); + initDimensionData(metircData!); + }} /> - +
); }; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/components/Table.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/components/Table.tsx new file mode 100644 index 000000000..cd4b4f964 --- /dev/null +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/components/Table.tsx @@ -0,0 +1,74 @@ +import { Table } from 'antd'; +import type { ColumnsType } from 'antd/es/table'; +import React, { useEffect, useState } from 'react'; +import moment from 'moment'; +import styles from '../style.less'; +import { ColumnConfig } from '../data'; + +type Props = { + columnConfig?: ColumnConfig[]; + dataSource: any; + metricFieldName: string; + dateFieldName?: string; + loading?: boolean; +}; + +const MetricTable: React.FC = ({ + columnConfig, + dataSource, + dateFieldName = 'sys_imp_date', + metricFieldName, + loading = false, +}) => { + const [columns, setColumns] = useState>([]); + useEffect(() => { + if (Array.isArray(columnConfig)) { + const config: ColumnsType = columnConfig.map((item: ColumnConfig) => { + const { name, nameEn } = item; + if (nameEn === dateFieldName) { + return { + title: '日期', + dataIndex: nameEn, + key: nameEn, + width: 120, + fixed: 'left', + defaultSortOrder: 'descend', + sorter: (a, b) => moment(a[nameEn]).valueOf() - moment(b[nameEn]).valueOf(), + }; + } + if (nameEn === metricFieldName) { + return { + title: name, + dataIndex: nameEn, + key: nameEn, + sortDirections: ['descend'], + sorter: (a, b) => a[nameEn] - b[nameEn], + }; + } + return { + title: name, + key: nameEn, + dataIndex: nameEn, + }; + }); + setColumns(config); + } + }, [columnConfig]); + + return ( +
+ {Array.isArray(columns) && columns.length > 0 && ( + {}} + /> + )} + + ); +}; + +export default MetricTable; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/data.d.ts b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/data.d.ts index e69de29bb..ed3686815 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/data.d.ts +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/data.d.ts @@ -0,0 +1,9 @@ +export type ColumnConfig = { + name: string; + type: string; + nameEn: string; + showType: string; + authorized: boolean; + dataFormatType: null; + dataFormat: null; +}; 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 c8a2ad48f..3a4c6c82c 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/index.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/index.tsx @@ -1,456 +1,7 @@ -import type { ActionType, ProColumns } from '@ant-design/pro-table'; -import ProTable from '@ant-design/pro-table'; -import { message, Space, Popconfirm, Tag, Spin, Dropdown } from 'antd'; -import React, { useRef, useState, useEffect } from 'react'; -import type { Dispatch } from 'umi'; -import { connect, history, useModel } from 'umi'; -import type { StateType } from '../model'; -import { SENSITIVE_LEVEL_ENUM } from '../constant'; -import { queryMetric, deleteMetric, batchUpdateMetricStatus } from '../service'; -import MetricFilter from './components/MetricFilter'; -import MetricInfoCreateForm from '../components/MetricInfoCreateForm'; -import MetricCardList from './components/MetricCardList'; -import NodeInfoDrawer from '../SemanticGraph/components/NodeInfoDrawer'; -import { SemanticNodeType, StatusEnum } from '../enum'; -import moment from 'moment'; -import styles from './style.less'; -import { ISemantic } from '../data'; +import React from 'react'; -type Props = { - dispatch: Dispatch; - domainManger: StateType; +const market: React.FC = ({ children }) => { + return <>{children}; }; -type QueryMetricListParams = { - id?: string; - name?: string; - bizName?: string; - sensitiveLevel?: string; - type?: string; - [key: string]: any; -}; - -const ClassMetricTable: React.FC = ({ domainManger, dispatch }) => { - const { initialState = {} } = useModel('@@initialState'); - - const { currentUser = {} } = initialState as any; - - const { selectDomainId, selectModelId: modelId } = domainManger; - const [createModalVisible, setCreateModalVisible] = useState(false); - const defaultPagination = { - current: 1, - pageSize: 20, - total: 0, - }; - const [pagination, setPagination] = useState(defaultPagination); - const [loading, setLoading] = useState(false); - const [dataSource, setDataSource] = useState([]); - const [metricItem, setMetricItem] = useState(); - const [selectedRowKeys, setSelectedRowKeys] = useState([]); - const [filterParams, setFilterParams] = useState>({ - showType: localStorage.getItem('metricMarketShowType') === '1' ? true : false, - }); - const [infoDrawerVisible, setInfoDrawerVisible] = useState(false); - const actionRef = useRef(); - - useEffect(() => { - queryMetricList(filterParams); - }, []); - - const queryBatchUpdateStatus = async (ids: React.Key[], status: StatusEnum) => { - if (Array.isArray(ids) && ids.length === 0) { - return; - } - const { code, msg } = await batchUpdateMetricStatus({ - ids, - status, - }); - if (code === 200) { - queryMetricList(filterParams); - return; - } - message.error(msg); - }; - - const queryMetricList = async (params: QueryMetricListParams = {}, disabledLoading = false) => { - if (!disabledLoading) { - setLoading(true); - } - const { code, data, msg } = await queryMetric({ - ...pagination, - ...params, - createdBy: params.onlyShowMe ? currentUser.name : null, - pageSize: params.showType ? 100 : params.pageSize || pagination.pageSize, - }); - setLoading(false); - const { list, pageSize, pageNum, total } = data || {}; - let resData: any = {}; - if (code === 200) { - if (!params.showType) { - setPagination({ - ...pagination, - pageSize: Math.min(pageSize, 100), - current: pageNum, - total, - }); - } - - setDataSource(list); - resData = { - data: list || [], - success: true, - }; - } else { - message.error(msg); - setDataSource([]); - resData = { - data: [], - total: 0, - success: false, - }; - } - return resData; - }; - - const deleteMetricQuery = async (id: number) => { - const { code, msg } = await deleteMetric(id); - if (code === 200) { - setMetricItem(undefined); - queryMetricList(filterParams); - } else { - message.error(msg); - } - }; - - const handleMetricEdit = (metricItem: ISemantic.IMetricItem) => { - setMetricItem(metricItem); - setCreateModalVisible(true); - }; - - const columns: ProColumns[] = [ - { - dataIndex: 'id', - title: 'ID', - }, - { - dataIndex: 'name', - title: '指标名称', - render: (_, record: any) => { - return ( - { - setMetricItem(record); - setInfoDrawerVisible(true); - }} - > - {record.name} - - ); - }, - }, - { - dataIndex: 'modelName', - title: '所属模型', - render: (_, record: any) => { - if (record.hasAdminRes) { - return ( - { - history.replace(`/model/${record.domainId}/${record.modelId}/metric`); - }} - > - {record.modelName} - - ); - } - return <> {record.modelName}; - }, - }, - { - dataIndex: 'sensitiveLevel', - title: '敏感度', - valueEnum: SENSITIVE_LEVEL_ENUM, - }, - { - dataIndex: 'status', - title: '状态', - width: 80, - search: false, - render: (status) => { - switch (status) { - case StatusEnum.ONLINE: - return 已启用; - case StatusEnum.OFFLINE: - return 未启用; - case StatusEnum.INITIALIZED: - return 初始化; - case StatusEnum.DELETED: - return 已删除; - default: - return 未知; - } - }, - }, - { - dataIndex: 'createdBy', - title: '创建人', - search: false, - }, - { - dataIndex: 'tags', - title: '标签', - search: false, - render: (tags) => { - if (Array.isArray(tags)) { - return ( - - {tags.map((tag) => ( - - {tag} - - ))} - - ); - } - return <>--; - }, - }, - { - dataIndex: 'description', - title: '描述', - search: false, - }, - { - dataIndex: 'updatedAt', - title: '更新时间', - search: false, - render: (value: any) => { - return value && value !== '-' ? moment(value).format('YYYY-MM-DD HH:mm:ss') : '-'; - }, - }, - { - title: '操作', - dataIndex: 'x', - valueType: 'option', - render: (_, record) => { - if (record.hasAdminRes) { - return ( - - { - handleMetricEdit(record); - }} - > - 编辑 - - - { - deleteMetricQuery(record.id); - }} - > - { - setMetricItem(record); - }} - > - 删除 - - - - ); - } else { - return <>; - } - }, - }, - ]; - - const handleFilterChange = async (filterParams: { - key: string; - sensitiveLevel: string; - type: string; - }) => { - const { sensitiveLevel, type } = filterParams; - const params: QueryMetricListParams = { ...filterParams }; - const sensitiveLevelValue = sensitiveLevel?.[0]; - const typeValue = type?.[0]; - - params.sensitiveLevel = sensitiveLevelValue; - params.type = typeValue; - setFilterParams(params); - await queryMetricList(params, filterParams.key ? false : true); - }; - - const rowSelection = { - onChange: (selectedRowKeys: React.Key[]) => { - setSelectedRowKeys(selectedRowKeys); - }, - getCheckboxProps: (record: ISemantic.IMetricItem) => ({ - disabled: !record.hasAdminRes, - }), - }; - - const dropdownButtonItems = [ - { - key: 'batchStart', - label: '批量启用', - }, - { - key: 'batchStop', - label: '批量停用', - }, - { - key: 'batchDelete', - label: ( - { - queryBatchUpdateStatus(selectedRowKeys, StatusEnum.DELETED); - }} - > - 批量删除 - - ), - }, - ]; - - const onMenuClick = ({ key }: { key: string }) => { - switch (key) { - case 'batchStart': - queryBatchUpdateStatus(selectedRowKeys, StatusEnum.ONLINE); - break; - case 'batchStop': - queryBatchUpdateStatus(selectedRowKeys, StatusEnum.OFFLINE); - break; - default: - break; - } - }; - - return ( - <> -
- { - if (_.showType !== undefined) { - setLoading(true); - setDataSource([]); - } - handleFilterChange(values); - }} - /> -
- <> - {filterParams.showType ? ( - - { - setInfoDrawerVisible(true); - setMetricItem(metricItem); - }} - onDeleteBtnClick={(metricItem: ISemantic.IMetricItem) => { - deleteMetricQuery(metricItem.id); - }} - onEditBtnClick={(metricItem: ISemantic.IMetricItem) => { - setMetricItem(metricItem); - setCreateModalVisible(true); - }} - /> - - ) : ( - { - return false; - }} - rowSelection={{ - type: 'checkbox', - ...rowSelection, - }} - toolBarRender={() => [ - - 批量操作 - , - ]} - loading={loading} - onChange={(data: any) => { - const { current, pageSize, total } = data; - const pagin = { - current, - pageSize, - total, - }; - setPagination(pagin); - queryMetricList({ ...pagin, ...filterParams }); - }} - size="small" - options={{ reload: false, density: false, fullScreen: false }} - /> - )} - - - {createModalVisible && ( - { - setCreateModalVisible(false); - queryMetricList(filterParams); - dispatch({ - type: 'domainManger/queryMetricList', - payload: { - domainId: selectDomainId, - }, - }); - }} - onCancel={() => { - setCreateModalVisible(false); - }} - /> - )} - {infoDrawerVisible && ( - { - setInfoDrawerVisible(false); - }} - width="100%" - open={infoDrawerVisible} - mask={true} - getContainer={false} - onEditBtnClick={(nodeData: any) => { - handleMetricEdit(nodeData); - }} - maskClosable={true} - onNodeChange={({ eventName }: { eventName: string }) => { - setInfoDrawerVisible(false); - }} - /> - )} - - ); -}; -export default connect(({ domainManger }: { domainManger: StateType }) => ({ - domainManger, -}))(ClassMetricTable); +export default market; 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 e3b51c01c..5a0727363 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/style.less +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/style.less @@ -114,4 +114,81 @@ } } } -} \ No newline at end of file +} + +.metricDetail { + height: calc(100vh - 50px); + .title { + margin-bottom: 0; + // padding: 20px; + font-size: 20px; + line-height: 34px; + border-bottom: 1px solid #d9d9d9; + display: flex; + .titleLeft { + display: flex; + flex: 1 1 auto; + .navContainer { + // display: flex; + padding: 20px; + padding-left: 30px; + .description { + font-size: 12px; + line-height: 12px; + padding-top: 12px; + color: #546174 + } + } + .backBtn { + background-color: #f8f8f8; + padding: 5px; + color: #b0b4bc; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); + &:hover { + background-color: #7599e5; + color: #fff; + } + } + } + .titleRight { + display: flex; + flex: 0 1 200px; + .info { + margin: 15px; + } + } + } + + .tabContainer { + background-color: #fff; + padding: 0px 40px; + } + .metricInfoContent { + padding:25px; + .title { + margin-bottom: 12px; + font-size: 16px; + color: #0e73ff; + font-weight: bold; + position: relative; + &::before { + display: block; + position: absolute; + content: ""; + left: -10px; + top: 10px; + height: 14px; + width: 3px; + font-size: 0; + background: #0e73ff; + border-radius: 2px; + border: 1px solid #0e73ff; + } + } + } +} + diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/OverviewContainer.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/OverviewContainer.tsx index aece51948..9adc51a41 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/OverviewContainer.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/OverviewContainer.tsx @@ -202,109 +202,77 @@ const OverviewContainer: React.FC = ({ mode, domainManger, dispatch }) =>
-

- {!!selectModelId && ( -
{ - cleanModelInfo(selectDomainId); - }} - > - -
- )} - -
- { - setOpen(false); - const { id, name } = domainData; - cleanModelInfo(id); - dispatch({ - type: 'domainManger/setSelectDomain', - selectDomainId: id, - selectDomainName: name, - domainData, - }); +
+
+ + {selectDomainName ? `${selectDomainName}` : '主题域信息'} + {selectModelName && ( + <> + | + {selectModelName} + + )} + +
+ { + setOpen(false); + const { id, name } = domainData; + cleanModelInfo(id); + dispatch({ + type: 'domainManger/setSelectDomain', + selectDomainId: id, + selectDomainName: name, + domainData, + }); + }} + onTreeDataUpdate={() => { + initProjectTree(); + }} + /> +
+
+ {selectDomainId ? ( + <> + {mode === 'domain' ? ( + { + handleModelChange(model); }} - onTreeDataUpdate={() => { - initProjectTree(); + onBackDomainBtnClick={() => { + cleanModelInfo(selectDomainId); + }} + onMenuChange={(menuKey) => { + setActiveKey(menuKey); + pushUrlMenu(selectDomainId, selectModelId, menuKey); }} /> - } - trigger="click" - open={selectModelId ? false : open} - onOpenChange={handleOpenChange} - > -
- - - {selectDomainName ? `${selectDomainName}` : '主题域信息'} - {selectModelName && ( - <> - | - {selectModelName} - - )} - - - {!selectModelId && ( - - - - )} -
- -
-

- - {selectDomainId ? ( - <> - {mode === 'domain' ? ( - { - handleModelChange(model); - }} - onBackDomainBtnClick={() => { - cleanModelInfo(selectDomainId); - }} - onMenuChange={(menuKey) => { - setActiveKey(menuKey); - pushUrlMenu(selectDomainId, selectModelId, menuKey); - }} - /> - ) : ( - { - handleModelChange(model); - }} - onBackDomainBtnClick={() => { - cleanModelInfo(selectDomainId); - }} - onMenuChange={(menuKey) => { - setActiveKey(menuKey); - pushUrlMenu(selectDomainId, selectModelId, menuKey); - }} - /> - )} - - ) : ( -

请选择项目

- )} + ) : ( + { + handleModelChange(model); + }} + onBackDomainBtnClick={() => { + cleanModelInfo(selectDomainId); + }} + onMenuChange={(menuKey) => { + setActiveKey(menuKey); + pushUrlMenu(selectDomainId, selectModelId, menuKey); + }} + /> + )} + + ) : ( +

请选择项目

+ )} +
); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/DimensionAndMetricRelationModal.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/DimensionAndMetricRelationModal.tsx index f32187e65..b3617086f 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/DimensionAndMetricRelationModal.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/DimensionAndMetricRelationModal.tsx @@ -1,27 +1,44 @@ import React, { useEffect, useState } from 'react'; -import { Modal, Button } from 'antd'; +import { Modal, Button, message } from 'antd'; import DimensionMetricRelationTableTransfer from './DimensionMetricRelationTableTransfer'; import { ISemantic } from '../data'; - +import { updateExprMetric } from '../service'; import FormItemTitle from '@/components/FormHelper/FormItemTitle'; type Props = { onCancel: () => void; open: boolean; - metricItem: ISemantic.IMetricItem; + metricItem?: ISemantic.IMetricItem; relationsInitialValue?: ISemantic.IDrillDownDimensionItem[]; onSubmit: (relations: ISemantic.IDrillDownDimensionItem[]) => void; }; const DimensionAndMetricRelationModal: React.FC = ({ open, - metricItem, + metricItem = {}, relationsInitialValue, onCancel, onSubmit, }) => { const [relationList, setRelationList] = useState([]); + const saveMetric = async (relationList: any) => { + const queryParams = { + ...metricItem, + relateDimension: { + ...(metricItem?.relateDimension || {}), + drillDownDimensions: relationList, + }, + }; + const { code, msg } = await updateExprMetric(queryParams); + if (code === 200) { + // message.success('编辑指标成功'); + // onSubmit?.(queryParams); + return; + } + message.error(msg); + }; + const renderFooter = () => { return ( <> @@ -30,6 +47,7 @@ const DimensionAndMetricRelationModal: React.FC = ({ type="primary" onClick={() => { onSubmit(relationList); + saveMetric(relationList); }} > 完成 diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/DimensionMetricRelationTableTransfer.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/DimensionMetricRelationTableTransfer.tsx index e651aa2ec..295abb6f2 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/DimensionMetricRelationTableTransfer.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/DimensionMetricRelationTableTransfer.tsx @@ -33,7 +33,7 @@ const DimensionMetricRelationTableTransfer: React.FC = ({ onChange, }) => { const [targetKeys, setTargetKeys] = useState([]); - const { selectModelId: modelId, selectDomainId } = domainManger; + const { selectModelId: modelId } = domainManger; const [checkedMap, setCheckedMap] = useState>( {}, ); @@ -42,7 +42,7 @@ const DimensionMetricRelationTableTransfer: React.FC = ({ useEffect(() => { queryDimensionList(); - }, []); + }, [metricItem, relationsInitialValue]); const queryDimensionList = async () => { const { code, data, msg } = await getDimensionList({ modelId: metricItem?.modelId || modelId }); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/DomainList.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/DomainList.tsx index adf3b17ba..922fa9edf 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/DomainList.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/DomainList.tsx @@ -1,5 +1,5 @@ import { DownOutlined, PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons'; -import { Input, message, Tree, Popconfirm, Space, Tooltip, Row, Col } from 'antd'; +import { Input, message, Tree, Popconfirm, Space, Tooltip, Row, Col, Button } from 'antd'; import type { DataNode } from 'antd/lib/tree'; import { useEffect, useState } from 'react'; import type { FC, Key } from 'react'; @@ -119,7 +119,7 @@ const DomainListTree: FC = ({ return (
{ handleSelect(id, name); }} @@ -180,17 +180,31 @@ const DomainListTree: FC = ({ return (
-
+ + {/* */} + {/* */} {createDomainBtnVisible && ( - + + - ) : undefined - } + size="large" + tabBarExtraContent={{ + left: ( + <> + {!!selectModelId && ( +
{ + onBackDomainBtnClick?.(); + }} + > + +
+ )} + + ), + // right: isModel ? ( + // + // ) : undefined, + }} onChange={(menuKey: string) => { onMenuChange?.(menuKey); }} 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 0c2612202..6207986e2 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 @@ -43,7 +43,7 @@ const EntitySettingSection: React.FC = ({ domainManger }) => { }, [modelId]); return ( -
+
{ diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/RecommendedQuestionsSection.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/RecommendedQuestionsSection.tsx index 218774b7e..d13513e7c 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/RecommendedQuestionsSection.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/RecommendedQuestionsSection.tsx @@ -72,7 +72,7 @@ const RecommendedQuestionsSection: React.FC = ({ domainManger }) => { }, [modelId]); return ( -
+
= (props) => { subTitle={'配置之后,可在指标主页和问答指标卡处选择用来对指标进行下钻和过滤'} /> } + hidden={!basicInfo?.id} >