From 1d91a972da622382cc119d944edc5a9ab7ff98f6 Mon Sep 17 00:00:00 2001 From: tristanliu <37809633+sevenliu1896@users.noreply.github.com> Date: Tue, 12 Mar 2024 12:07:51 +0800 Subject: [PATCH] [improvement][headless-fe] Added the ability to export metrics and dimensions to a specific target. (#801) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [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 * [improvement][semantic-fe] Optimizing the logic for setting dimension values and editing data sources, and adding system settings functionality * [improvement][semantic-fe] Upgrading antd version to 5.x, extracting the batch operation button component, optimizing the interaction for system settings, and expanding the configuration generation types for list-to-select component. * [improvement][semantic-fe] Adding the ability to filter dimensions based on whether they are tags or not. * [improvement][semantic-fe] Adding the ability to edit relationships between models in the canvas. * [improvement][semantic-fe] Updating the datePicker component to use dayjs instead. * [improvement][semantic-fe] Fixing the issue with passing the model ID for dimensions in the indicator market. * [improvement][semantic-fe] Fixing the abnormal state of the popup when creating a model. * [improvement][semantic-fe] Adding permission logic for bulk operations in the indicator market. * [improvement][semantic-fe] Adding the ability to download and transpose data. * [improvement][semantic-fe] Fixing the initialization issue with the date selection component in the indicator details page when switching time granularity. * [improvement][semantic-fe] Fixing the logic error in the dimension value setting. * [improvement][semantic-fe] Fixing the synchronization issue with the question and answer settings information. * [improvement][semantic-fe] Optimizing the canvas functionality for better performance and user experience. * [improvement][semantic-fe] Optimizing the update process for drawing model relationship edges in the canvas. * [improvement][semantic-fe] Changing the line type for canvas connections. * [improvement][semantic-fe] Replacing the initialization variable from "semantic" to "headless". * [improvement][semantic-fe] Fixing the missing migration issue for default drill-down dimension configuration in model editing. Additionally, optimizing the data retrieval method for initializing fields in the model. * [improvement][semantic-fe] Updating the logic for the fieldName. * [improvement][semantic-fe] Adjusting the position of the metrics tab. * [improvement][semantic-fe] Changing the 字段名称 to 英文名称. * [improvement][semantic-fe] Fix metric measurement deletion. * [improvement][semantic-fe] UI optimization for metric details page. * [improvement][semantic-fe] UI optimization for metric details page. * [improvement][semantic-fe] UI adjustment for metric details page. * [improvement][semantic-fe] The granularity field in the time type of model editing now supports setting it as empty. * [improvement][semantic-fe] Added field type and metric type to the metric creation options. * [improvement][semantic-fe] The organization structure selection feature has been added to the permission management. * [improvement][semantic-fe] Improved user experience for the metric list. * [improvement][semantic-fe] fix update the metric list. * [improvement][headless-fe] Added view management functionality. * [improvement][headless-fe] The view management functionality has been added. This feature allows users to create, edit, and manage different views within the system. * [improvement][headless-fe] Added model editing side effect detection. * [improvement][headless-fe] Fixed the logic error in view editing. * [improvement][headless-fe] Fixed the issue with initializing dimension associations in metric settings. * [improvement][headless-fe] Added the ability to hide the Q&A settings entry point. * [improvement][headless-fe] Fixed the issue with selecting search results in metric field creation. * [improvement][headless-fe] Added search functionality to the field list in model editing. * [improvement][headless-fe] fix the field list in model editing * [improvement][headless-fe] Restructured the data for the dimension value settings interface. * [improvement][headless-fe] Added dynamic variable functionality to model creation based on SQL scripts. * [improvement][headless-fe] Added support for passing dynamic variables as parameters in the executeSql function. * [improvement][headless-fe] Resolved the issue where users were unable to select all options for dimensions, metrics, and fields in the metric generation process. * [improvement][headless-fe] Replaced the term "view" with "dataset" * [improvement][headless-fe] Added the ability to export metrics and dimensions to a specific target. --- .../packages/supersonic-fe/config/routes.ts | 27 + webapp/packages/supersonic-fe/package.json | 1 + .../BatchCtrlDropDownButton/index.tsx | 18 + .../supersonic-fe/src/locales/zh-CN/menu.ts | 2 + .../pages/SemanticModel/Insights/Detail.tsx | 151 +++++ .../pages/SemanticModel/Insights/Market.tsx | 375 ++++++++++++ .../Insights/components/ClassTagTable.tsx | 349 +++++++++++ .../Insights/components/Table.tsx | 28 + .../Insights/components/TagFilter.tsx | 138 +++++ .../Insights/components/TagInfoCreateForm.tsx | 565 ++++++++++++++++++ .../Insights/components/TagInfoSider.tsx | 258 ++++++++ .../Insights/components/TagTrendSection.tsx | 97 +++ .../pages/SemanticModel/Insights/constants.ts | 0 .../pages/SemanticModel/Insights/data.d.ts | 0 .../pages/SemanticModel/Insights/index.tsx | 7 + .../src/pages/SemanticModel/Insights/model.ts | 0 .../pages/SemanticModel/Insights/service.ts | 0 .../pages/SemanticModel/Insights/style.less | 340 +++++++++++ .../src/pages/SemanticModel/Insights/utils.ts | 0 .../src/pages/SemanticModel/Metric/Market.tsx | 12 +- .../SemanticModel/Metric/MetricInfoSider.tsx | 4 +- .../View/components/ViewTable.tsx | 5 +- .../components/ClassDataSourceTypeModal.tsx | 1 - .../components/ClassDimensionTable.tsx | 43 +- .../components/ClassMetricTable.tsx | 61 +- .../CommonDimensionInfoModal.tsx | 206 ------- .../CommonDimension/CommonDimensionTable.tsx | 196 ------ .../components/DimensionInfoModal.tsx | 60 +- .../components/DomainManagerTab.tsx | 13 +- .../IndicatorStar.tsx} | 15 +- .../components/MetricFieldFormTable.tsx | 17 +- .../components/MetricInfoCreateForm.tsx | 69 ++- .../components/MetricMeasuresFormTable.tsx | 2 +- .../components/MetricMetricFormTable.tsx | 4 +- .../components/MetricTableColumnRender.tsx | 310 ---------- .../SemanticModel/components/ModelTable.tsx | 6 +- .../components/TableColumnRender.tsx | 336 +++++++++++ .../components/TagDimensionFormTable.tsx | 174 ++++++ .../pages/SemanticModel/components/style.less | 31 - .../src/pages/SemanticModel/constant.ts | 12 + .../src/pages/SemanticModel/data.d.ts | 35 +- .../src/pages/SemanticModel/service.ts | 78 ++- 42 files changed, 3213 insertions(+), 833 deletions(-) create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/Detail.tsx create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/Market.tsx create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/components/ClassTagTable.tsx create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/components/Table.tsx create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/components/TagFilter.tsx create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/components/TagInfoCreateForm.tsx create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/components/TagInfoSider.tsx create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/components/TagTrendSection.tsx create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/constants.ts create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/data.d.ts create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/index.tsx create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/model.ts create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/service.ts create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/style.less create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/utils.ts delete mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/components/CommonDimension/CommonDimensionInfoModal.tsx delete mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/components/CommonDimension/CommonDimensionTable.tsx rename webapp/packages/supersonic-fe/src/pages/SemanticModel/{Metric/components/MetricStar.tsx => components/IndicatorStar.tsx} (64%) delete mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/components/MetricTableColumnRender.tsx create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/components/TableColumnRender.tsx create mode 100644 webapp/packages/supersonic-fe/src/pages/SemanticModel/components/TagDimensionFormTable.tsx diff --git a/webapp/packages/supersonic-fe/config/routes.ts b/webapp/packages/supersonic-fe/config/routes.ts index efeb253da..e867876ff 100644 --- a/webapp/packages/supersonic-fe/config/routes.ts +++ b/webapp/packages/supersonic-fe/config/routes.ts @@ -66,6 +66,33 @@ const ROUTES = [ }, ], }, + + { + path: '/tag', + name: 'tag', + component: './SemanticModel/Insights', + envEnableList: [ENV_KEY.SEMANTIC], + routes: [ + { + path: '/tag', + redirect: '/tag/market', + }, + { + path: '/tag/market', + component: './SemanticModel/Insights/Market', + hideInMenu: true, + envEnableList: [ENV_KEY.SEMANTIC], + }, + { + path: '/tag/detail/:tagId', + name: 'tagDetail', + hideInMenu: true, + component: './SemanticModel/Insights/Detail', + envEnableList: [ENV_KEY.SEMANTIC], + }, + ], + }, + { path: '/plugin', name: 'plugin', diff --git a/webapp/packages/supersonic-fe/package.json b/webapp/packages/supersonic-fe/package.json index df1cc8181..dfa6ecdcb 100644 --- a/webapp/packages/supersonic-fe/package.json +++ b/webapp/packages/supersonic-fe/package.json @@ -100,6 +100,7 @@ "react-syntax-highlighter": "^15.4.3", "sql-formatter": "^2.3.3", "supersonic-chat-sdk": "0.0.0", + "supersonic-insights-flow-components": "^1.4.6", "umi": "3.5", "umi-request": "^1.4.0" }, diff --git a/webapp/packages/supersonic-fe/src/components/BatchCtrlDropDownButton/index.tsx b/webapp/packages/supersonic-fe/src/components/BatchCtrlDropDownButton/index.tsx index 7a6a76bda..dab146284 100644 --- a/webapp/packages/supersonic-fe/src/components/BatchCtrlDropDownButton/index.tsx +++ b/webapp/packages/supersonic-fe/src/components/BatchCtrlDropDownButton/index.tsx @@ -5,6 +5,7 @@ import { StopOutlined, CloudDownloadOutlined, DeleteOutlined, + ExportOutlined, } from '@ant-design/icons'; export type BatchCtrlDropDownButtonProps = { @@ -14,6 +15,7 @@ export type BatchCtrlDropDownButtonProps = { downloadLoading?: boolean; disabledList?: string[]; hiddenList?: string[]; + extenderEnable?: boolean; }; const { RangePicker } = DatePicker; @@ -24,10 +26,21 @@ const BatchCtrlDropDownButton: FC = ({ downloadLoading, disabledList = [], hiddenList = [], + extenderEnable = false, }) => { const [popoverOpenState, setPopoverOpenState] = useState(false); const [pickerType, setPickerType] = useState('day'); const dateRangeRef = useRef([]); + + const exportTagButton = { + key: 'exportTagButton', + label: '导出为标签', + icon: , + disabled: disabledList?.includes('exportTagButton'), + }; + + const extenderList: any[] = extenderEnable ? [exportTagButton] : []; + const dropdownButtonItems: any[] = [ { key: 'batchStart', @@ -55,6 +68,11 @@ const BatchCtrlDropDownButton: FC = ({ icon: , disabled: disabledList?.includes('batchDownload'), }, + { + key: 'divider', + type: 'divider', + }, + ...extenderList, { key: 'batchDeleteDivider', type: 'divider', 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 7e6b5a776..9e216dfa3 100644 --- a/webapp/packages/supersonic-fe/src/locales/zh-CN/menu.ts +++ b/webapp/packages/supersonic-fe/src/locales/zh-CN/menu.ts @@ -10,6 +10,8 @@ export default { 'menu.semanticModel': '语义建模', 'menu.metric': '指标市场', 'menu.metric.metricDetail': '指标详情页', + 'menu.tag': '标签市场', + 'menu.tag.tagDetail': '标签详情页', 'menu.database': '数据库管理', 'menu.chatSetting': '问答设置', 'menu.plugin': '插件市场', diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/Detail.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/Detail.tsx new file mode 100644 index 000000000..05ad5e1f5 --- /dev/null +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/Detail.tsx @@ -0,0 +1,151 @@ +import { message, Tabs, Button, Space } from 'antd'; +import React, { useState, useEffect } from 'react'; +import { getTagData } from '../service'; +import { connect, useParams, history } from 'umi'; +import type { StateType } from '../model'; +import styles from './style.less'; +import { ArrowLeftOutlined } from '@ant-design/icons'; +import TagTrendSection from './components/TagTrendSection'; +import { ISemantic } from '../data'; +import TagInfoSider from './components/TagInfoSider'; +import { getDimensionList, queryMetric } from '../service'; +import type { TabsProps } from 'antd'; + +type Props = Record; + +const TagDetail: React.FC = () => { + const params: any = useParams(); + const tagId = params.tagId; + const [tagData, setTagData] = useState(); + const [dimensionMap, setDimensionMap] = useState>({}); + + const [metricMap, setMetricMap] = useState>({}); + + const [relationDimensionOptions, setRelationDimensionOptions] = useState< + { value: string; label: string; modelId: number }[] + >([]); + + useEffect(() => { + queryTagData(tagId); + }, [tagId]); + + const queryTagData = async (tagId: number) => { + const { code, data, msg } = await getTagData(tagId); + if (code === 200) { + queryDimensionList(data.modelId); + queryMetricList(data.modelId); + setTagData({ ...data }); + return; + } + message.error(msg); + }; + + const tabItems: TabsProps['items'] = [ + { + key: 'trend', + label: '图表', + children: ( + // <> + + ), + }, + // { + // key: 'metricCaliberInput', + // label: '基础信息', + // children: <>, + // }, + // { + // key: 'metricDataRemark', + // label: '备注', + // children: <>, + // }, + ]; + + const queryDimensionList = async (modelId: number) => { + const { code, data, msg } = await getDimensionList({ modelId }); + if (code === 200 && Array.isArray(data?.list)) { + const { list } = data; + setDimensionMap( + list.reduce( + (infoMap: Record, item: ISemantic.IDimensionItem) => { + infoMap[`${item.id}`] = item; + return infoMap; + }, + {}, + ), + ); + } else { + message.error(msg); + } + }; + + const queryMetricList = async (modelId: number) => { + const { code, data, msg } = await queryMetric({ + modelId: modelId, + }); + const { list } = data || {}; + if (code === 200) { + setMetricMap( + list.reduce( + (infoMap: Record, item: ISemantic.IMetricItem) => { + infoMap[`${item.id}`] = item; + return infoMap; + }, + {}, + ), + ); + } else { + message.error(msg); + } + }; + + return ( + <> +
+
+
+ { + history.push('/tag/market'); + }} + > + + + 返回列表页 + + + ), + }} + size="large" + className={styles.tagDetailTab} + /> +
+
+ +
+
+
+ + ); +}; + +export default connect(({ domainManger }: { domainManger: StateType }) => ({ + domainManger, +}))(TagDetail); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/Market.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/Market.tsx new file mode 100644 index 000000000..ef10ff3c1 --- /dev/null +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/Market.tsx @@ -0,0 +1,375 @@ +import type { ActionType, ProColumns } from '@ant-design/pro-table'; +import ProTable from '@ant-design/pro-table'; +import { message, Space, Popconfirm } from 'antd'; +import React, { useRef, useState, useEffect } from 'react'; +import type { Dispatch } from 'umi'; +import { connect, useModel } from 'umi'; +import type { StateType } from '../model'; +import { SENSITIVE_LEVEL_ENUM } from '../constant'; +import { getTagList, deleteTag, batchUpdateTagStatus } from '../service'; +import TagFilter from './components/TagFilter'; +import TagInfoCreateForm from './components/TagInfoCreateForm'; +import { SemanticNodeType, StatusEnum } from '../enum'; +import moment from 'moment'; +import styles from './style.less'; +import { ISemantic } from '../data'; +import BatchCtrlDropDownButton from '@/components/BatchCtrlDropDownButton'; +import { ColumnsConfig } from '../components/TableColumnRender'; + +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 [tagItem, setTagItem] = useState(); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + const [filterParams, setFilterParams] = useState>({ + showType: localStorage.getItem('metricMarketShowType') === '1' ? true : false, + }); + + const [downloadLoading, setDownloadLoading] = useState(false); + + const [hasAllPermission, setHasAllPermission] = useState(true); + + const actionRef = useRef(); + + useEffect(() => { + queryTagList(filterParams); + }, []); + + const queryBatchUpdateStatus = async (ids: React.Key[], status: StatusEnum) => { + if (Array.isArray(ids) && ids.length === 0) { + return; + } + const { code, msg } = await batchUpdateTagStatus({ + ids, + status, + }); + if (code === 200) { + queryTagList(filterParams); + return; + } + message.error(msg); + }; + + const queryTagList = async (params: QueryMetricListParams = {}, disabledLoading = false) => { + if (!disabledLoading) { + setLoading(true); + } + const { code, data, msg } = await getTagList({ + ...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 deleteTag(id); + if (code === 200) { + setTagItem(undefined); + queryTagList(filterParams); + } else { + message.error(msg); + } + }; + + const handleMetricEdit = (tagItem: ISemantic.ITagItem) => { + setTagItem(tagItem); + setCreateModalVisible(true); + }; + + const columnsConfig = ColumnsConfig({ + indicatorInfo: { + url: '/tag/detail/', + starType: 'tag', + }, + }); + + const columns: ProColumns[] = [ + { + dataIndex: 'id', + title: 'ID', + width: 80, + fixed: 'left', + search: false, + }, + { + dataIndex: 'name', + title: '标签', + width: 280, + fixed: 'left', + render: columnsConfig.indicatorInfo.render, + }, + { + dataIndex: 'sensitiveLevel', + title: '敏感度', + width: 150, + valueEnum: SENSITIVE_LEVEL_ENUM, + render: columnsConfig.sensitiveLevel.render, + }, + + { + dataIndex: 'description', + title: '描述', + search: false, + width: 300, + render: columnsConfig.description.render, + }, + { + dataIndex: 'status', + title: '状态', + width: 180, + search: false, + render: columnsConfig.state.render, + }, + { + dataIndex: 'createdBy', + title: '创建人', + // width: 150, + 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', + width: 180, + render: (_, record) => { + if (record.hasAdminRes) { + return ( + + { + handleMetricEdit(record); + }} + > + 编辑 + + + { + deleteMetricQuery(record.id); + }} + > + { + setTagItem(record); + }} + > + 删除 + + + + ); + } else { + return <>; + } + }, + }, + ]; + + const handleFilterChange = async (filterParams: { + key: string; + sensitiveLevel: string[]; + showFilter: string[]; + type: string; + }) => { + const { sensitiveLevel, type, showFilter } = filterParams; + const params: QueryMetricListParams = { ...filterParams }; + const sensitiveLevelValue = sensitiveLevel?.[0]; + const showFilterValue = showFilter?.[0]; + const typeValue = type?.[0]; + showFilterValue ? (params[showFilterValue] = true) : null; + params.sensitiveLevel = sensitiveLevelValue; + params.type = typeValue; + setFilterParams(params); + await queryTagList( + { + ...params, + ...defaultPagination, + }, + filterParams.key ? false : true, + ); + }; + + const rowSelection = { + onChange: (selectedRowKeys: React.Key[]) => { + const permissionList: boolean[] = []; + selectedRowKeys.forEach((id: React.Key) => { + const target = dataSource.find((item) => { + return item.id === id; + }); + if (target) { + permissionList.push(target.hasAdminRes); + } + }); + if (permissionList.includes(false)) { + setHasAllPermission(false); + } else { + setHasAllPermission(true); + } + setSelectedRowKeys(selectedRowKeys); + }, + // getCheckboxProps: (record: ISemantic.ITagItem) => ({ + // disabled: !record.hasAdminRes, + // }), + }; + + const onMenuClick = (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); + }} + /> +
+ <> + { + return false; + }} + sticky={{ offsetHeader: 0 }} + rowSelection={{ + type: 'checkbox', + ...rowSelection, + }} + toolBarRender={() => [ + { + queryBatchUpdateStatus(selectedRowKeys, StatusEnum.DELETED); + }} + hiddenList={['batchDownload']} + disabledList={hasAllPermission ? [] : ['batchStart', 'batchStop', 'batchDelete']} + onMenuClick={onMenuClick} + />, + ]} + loading={loading} + onChange={(data: any) => { + const { current, pageSize, total } = data; + const pagin = { + current, + pageSize, + total, + }; + setPagination(pagin); + queryTagList({ ...pagin, ...filterParams }); + }} + options={{ reload: false, density: false, fullScreen: false }} + /> + + + {createModalVisible && ( + { + setCreateModalVisible(false); + queryTagList({ ...filterParams, ...defaultPagination }); + }} + onCancel={() => { + setCreateModalVisible(false); + }} + /> + )} + + ); +}; +export default connect(({ domainManger }: { domainManger: StateType }) => ({ + domainManger, +}))(ClassMetricTable); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/components/ClassTagTable.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/components/ClassTagTable.tsx new file mode 100644 index 000000000..9d24be86f --- /dev/null +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Insights/components/ClassTagTable.tsx @@ -0,0 +1,349 @@ +import type { ActionType, ProColumns } from '@ant-design/pro-table'; +import ProTable from '@ant-design/pro-table'; +import { message, Button, Space, Popconfirm, Input, Select } from 'antd'; +import React, { useRef, useState, useEffect } from 'react'; +import type { Dispatch } from 'umi'; +import { StatusEnum } from '../../enum'; +import { connect } from 'umi'; +import type { StateType } from '../../model'; +import { SENSITIVE_LEVEL_ENUM, SENSITIVE_LEVEL_OPTIONS } from '../../constant'; +import { getTagList, deleteTag, batchUpdateTagStatus } from '../../service'; +import TagInfoCreateForm from './TagInfoCreateForm'; +import BatchCtrlDropDownButton from '@/components/BatchCtrlDropDownButton'; +import TableHeaderFilter from '../../components/TableHeaderFilter'; +import moment from 'moment'; +import styles from '../style.less'; +import { ISemantic } from '../../data'; +import { ColumnsConfig } from '../../components/TableColumnRender'; + +type Props = { + dispatch: Dispatch; + domainManger: StateType; +}; + +const ClassTagTable: React.FC = ({ domainManger, dispatch }) => { + const { selectModelId: modelId, selectDomainId } = domainManger; + const [createModalVisible, setCreateModalVisible] = useState(false); + const [tagItem, setTagItem] = useState(); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + const [tableData, setTableData] = useState([]); + const [loading, setLoading] = useState(false); + const defaultPagination = { + current: 1, + pageSize: 20, + total: 0, + }; + const [pagination, setPagination] = useState(defaultPagination); + + const [filterParams, setFilterParams] = useState>({}); + + const actionRef = useRef(); + + const queryBatchUpdateStatus = async (ids: React.Key[], status: StatusEnum) => { + if (Array.isArray(ids) && ids.length === 0) { + return; + } + const { code, msg } = await batchUpdateTagStatus({ + ids, + status, + }); + if (code === 200) { + queryTagList({ ...filterParams, ...defaultPagination }); + return; + } + message.error(msg); + }; + + useEffect(() => { + queryTagList({ ...filterParams, ...defaultPagination }); + }, [filterParams]); + + const queryTagList = async (params: any) => { + setLoading(true); + const { code, data, msg } = await getTagList({ + ...pagination, + ...params, + modelIds: [modelId], + }); + setLoading(false); + const { list, pageSize, pageNum, total } = data || {}; + if (code === 200) { + setPagination({ + ...pagination, + pageSize: Math.min(pageSize, 100), + current: pageNum, + total, + }); + setTableData(list); + } else { + message.error(msg); + setTableData([]); + } + }; + + const columnsConfig = ColumnsConfig(); + + const columns: ProColumns[] = [ + { + dataIndex: 'id', + title: 'ID', + width: 80, + fixed: 'left', + search: false, + }, + { + dataIndex: 'name', + title: '标签', + width: 280, + fixed: 'left', + // width: '30%', + search: false, + render: columnsConfig.indicatorInfo.render, + }, + { + dataIndex: 'key', + title: '标签搜索', + hideInTable: true, + }, + { + dataIndex: 'sensitiveLevel', + title: '敏感度', + width: 160, + valueEnum: SENSITIVE_LEVEL_ENUM, + render: columnsConfig.sensitiveLevel.render, + }, + + { + dataIndex: 'description', + title: '描述', + width: 300, + search: false, + render: columnsConfig.description.render, + }, + { + dataIndex: 'status', + title: '状态', + width: 160, + search: false, + render: columnsConfig.state.render, + }, + { + dataIndex: 'createdBy', + title: '创建人', + width: 150, + search: false, + }, + { + dataIndex: 'updatedAt', + title: '更新时间', + width: 180, + search: false, + render: (value: any) => { + return value && value !== '-' ? moment(value).format('YYYY-MM-DD HH:mm:ss') : '-'; + }, + }, + { + title: '操作', + dataIndex: 'x', + valueType: 'option', + width: 150, + render: (_, record) => { + return ( + + + {record.status === StatusEnum.ONLINE ? ( + + ) : ( + + )} + { + const { code, msg } = await deleteTag(record.id); + if (code === 200) { + setTagItem(undefined); + queryTagList({ ...filterParams, ...defaultPagination }); + } else { + message.error(msg); + } + }} + > + + + + ); + }, + }, + ]; + + const rowSelection = { + onChange: (selectedRowKeys: React.Key[]) => { + setSelectedRowKeys(selectedRowKeys); + }, + }; + + const onMenuClick = (key: string) => { + switch (key) { + case 'batchStart': + queryBatchUpdateStatus(selectedRowKeys, StatusEnum.ONLINE); + break; + case 'batchStop': + queryBatchUpdateStatus(selectedRowKeys, StatusEnum.OFFLINE); + break; + default: + break; + } + }; + + return ( + <> + { + setFilterParams((preState) => { + return { + ...preState, + key: value, + }; + }); + }} + /> + ), + }, + { + label: '敏感度', + component: ( + + + + + + + + + + + + +

+ 在录入标签时,请务必详细填写标签口径。口径描述对于理解标签的含义、计算方法和使用场景至关重要。一个清晰、准确的口径描述可以帮助其他用户更好地理解和使用该标签,避免因为误解而导致错误的数据分析和决策。在填写口径时,建议包括以下信息: +

+

1. 标签的计算方法:详细说明标签是如何计算的,包括涉及的公式、计算步骤等。

+

2. 数据来源:描述标签所依赖的数据来源,包括数据表、字段等信息。

+

3. 使用场景:说明该标签适用于哪些业务场景,以及如何在这些场景中使用该标签。

+

4. 任何其他相关信息:例如数据更新频率、数据质量要求等。

+

+ 请确保口径描述清晰、简洁且易于理解,以便其他用户能够快速掌握标签的核心要点。 +

+ + } + /> + } + rules={[{ required: true, message: '请输入业务口径' }]} + > +