From 157c2999dce401752bd2f00781d63a2eb1043444 Mon Sep 17 00:00:00 2001 From: tristanliu <37809633+sevenliu1896@users.noreply.github.com> Date: Thu, 14 Sep 2023 12:14:02 +0800 Subject: [PATCH] [improvement][semantic-fe] Dictionary import for dimension values supported in Q&A visibility (#89) * [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 --- .../components/ClassDimensionTable.tsx | 11 +- .../components/ClassMetricTable.tsx | 11 +- .../Database/DatabaseCreateForm.tsx | 2 +- .../Entity/DimensionAndMetricVisibleModal.tsx | 2 +- .../DimensionMetricVisibleTableTransfer.tsx | 166 +++++++++++++++--- .../Entity/DimensionMetricVisibleTransfer.tsx | 2 +- .../pages/SemanticModel/components/style.less | 7 + .../src/pages/SemanticModel/enum.ts | 8 + .../src/pages/SemanticModel/service.ts | 21 +++ .../packages/supersonic-fe/src/utils/utils.ts | 14 +- 10 files changed, 208 insertions(+), 36 deletions(-) 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 7fd5be65d..31d85333c 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDimensionTable.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDimensionTable.tsx @@ -1,6 +1,6 @@ import type { ActionType, ProColumns } from '@ant-design/pro-table'; import ProTable from '@ant-design/pro-table'; -import { message, Button, Space, Popconfirm } from 'antd'; +import { message, Button, Space, Popconfirm, Input } from 'antd'; import React, { useRef, useState, useEffect } from 'react'; import type { Dispatch } from 'umi'; import { connect } from 'umi'; @@ -85,10 +85,18 @@ const ClassDimensionTable: React.FC = ({ domainManger, dispatch }) => { title: 'ID', width: 80, order: 100, + search: false, + }, + { + dataIndex: 'key', + title: '维度搜索', + hideInTable: true, + renderFormItem: () => , }, { dataIndex: 'name', title: '维度名称', + search: false, }, { dataIndex: 'alias', @@ -100,6 +108,7 @@ const ClassDimensionTable: React.FC = ({ domainManger, dispatch }) => { { dataIndex: 'bizName', title: '字段名称', + search: false, // order: 9, }, { 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 8be3c47b5..1826d83b3 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassMetricTable.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassMetricTable.tsx @@ -1,6 +1,6 @@ import type { ActionType, ProColumns } from '@ant-design/pro-table'; import ProTable from '@ant-design/pro-table'; -import { message, Button, Space, Popconfirm } from 'antd'; +import { message, Button, Space, Popconfirm, Input } from 'antd'; import React, { useRef, useState } from 'react'; import type { Dispatch } from 'umi'; import { connect } from 'umi'; @@ -65,10 +65,18 @@ const ClassMetricTable: React.FC = ({ domainManger, dispatch }) => { dataIndex: 'id', title: 'ID', width: 80, + search: false, }, { dataIndex: 'name', title: '指标名称', + search: false, + }, + { + dataIndex: 'key', + title: '指标搜索', + hideInTable: true, + renderFormItem: () => , }, { dataIndex: 'alias', @@ -80,6 +88,7 @@ const ClassMetricTable: React.FC = ({ domainManger, dispatch }) => { { dataIndex: 'bizName', title: '字段名称', + search: false, }, { dataIndex: 'sensitiveLevel', diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Database/DatabaseCreateForm.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Database/DatabaseCreateForm.tsx index cb0125543..37226690d 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Database/DatabaseCreateForm.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Database/DatabaseCreateForm.tsx @@ -143,7 +143,7 @@ const DatabaseCreateForm: ForwardRefRenderFunction = ( diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionAndMetricVisibleModal.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionAndMetricVisibleModal.tsx index 235d1ee11..cc9395a80 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionAndMetricVisibleModal.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionAndMetricVisibleModal.tsx @@ -197,7 +197,7 @@ const DimensionAndMetricVisibleModal: React.FC = ({ return ( <> void; [key: string]: any; }; +type TaskStateMap = Record; + const DimensionMetricVisibleTableTransfer: React.FC = ({ + domainManger, knowledgeInfosMap, onKnowledgeInfosMapChange, ...restProps }) => { + const { selectModelId: modelId } = domainManger; const [dimensionValueSettingModalVisible, setDimensionValueSettingModalVisible] = useState(false); - const [currentRecord, setCurrentRecord] = useState({}); + const [currentRecord, setCurrentRecord] = useState({} as RecordType); const [currentDimensionSettingFormData, setCurrentDimensionSettingFormData] = useState(); + const [recordLoadingMap, setRecordLoadingMap] = useState>({}); + + const [taskStateMap, setTaskStateMap] = useState({}); + + useEffect(() => { + queryDictLatestTaskList(); + }, []); + const updateKnowledgeInfosMap = (record: RecordType, updateData: Record) => { const { bizName, id } = record; const knowledgeMap = { @@ -56,6 +73,61 @@ const DimensionMetricVisibleTableTransfer: React.FC = ({ onKnowledgeInfosMapChange?.(knowledgeMap); }; + const queryDictLatestTaskList = async () => { + const { code, data } = await searchDictLatestTaskList({ + modelId, + }); + if (code !== 200) { + message.error('获取字典导入任务失败!'); + return; + } + const tastMap = data.reduce( + (stateMap: TaskStateMap, item: { dimId: number; status: DictTaskState }) => { + const { dimId, status } = item; + stateMap[dimId] = status; + return stateMap; + }, + {}, + ); + setTaskStateMap(tastMap); + }; + + const createDictTaskQuery = async (recordData: RecordType) => { + setRecordLoadingMap({ + ...recordLoadingMap, + [recordData.id]: true, + }); + const { code } = await createDictTask({ + updateMode: 'REALTIME_ADD', + modelAndDimPair: { + [modelId]: [recordData.id], + }, + }); + setRecordLoadingMap({ + ...recordLoadingMap, + [recordData.id]: false, + }); + if (code !== 200) { + message.error('字典导入任务创建失败!'); + return; + } + setTimeout(() => { + queryDictLatestTaskList(); + }, 2000); + }; + + const deleteDictTask = async (recordData: RecordType) => { + const { code } = await createDictTask({ + updateMode: 'REALTIME_DELETE', + modelAndDimPair: { + [modelId]: [recordData.id], + }, + }); + if (code !== 200) { + message.error('删除字典导入任务创建失败!'); + } + }; + let rightColumns: ColumnsType = [ { dataIndex: 'name', @@ -85,6 +157,9 @@ const DimensionMetricVisibleTableTransfer: React.FC = ({ checked={knowledgeInfosMap?.[bizName]?.searchEnable} onChange={(e: CheckboxChangeEvent) => { updateKnowledgeInfosMap(record, { searchEnable: e.target.checked }); + if (!e.target.checked) { + deleteDictTask(record); + } }} onClick={(event) => { event.stopPropagation(); @@ -95,28 +170,70 @@ const DimensionMetricVisibleTableTransfer: React.FC = ({ ); }, }, + { + dataIndex: 'taskState', + width: 130, + title: ( + + 导入字典状态 + { + queryDictLatestTaskList(); + }} + > + + + + + + ), + render: (_, record) => { + const { id, type } = record; + const target = taskStateMap[id]; + if (type === TransType.DIMENSION && target) { + return DictTaskState[target] || '未知状态'; + } + return '--'; + }, + }, { title: '操作', dataIndex: 'x', render: (_: any, record: RecordType) => { - const { type, bizName } = record; + const { type, bizName, id } = record; return type === TransType.DIMENSION ? ( - + + + + ) : ( <> ); @@ -197,4 +314,7 @@ const DimensionMetricVisibleTableTransfer: React.FC = ({ ); }; -export default DimensionMetricVisibleTableTransfer; +// export default DimensionMetricVisibleTableTransfer; +export default connect(({ domainManger }: { domainManger: StateType }) => ({ + domainManger, +}))(DimensionMetricVisibleTableTransfer); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionMetricVisibleTransfer.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionMetricVisibleTransfer.tsx index 40e8bfe2c..dea1eb576 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionMetricVisibleTransfer.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionMetricVisibleTransfer.tsx @@ -62,7 +62,7 @@ const DimensionMetricVisibleTransfer: React.FC = ({ showSearch titles={titles || ['不可见维度', '可见维度']} listStyle={{ - width: 500, + width: 720, height: 600, }} filterOption={(inputValue: string, item: any) => { 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 848bf5b78..d5c023dc0 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/style.less +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/style.less @@ -353,3 +353,10 @@ width: 100%; } } + +.taskStateRefreshIcon { + cursor: pointer; + &:hover { + color: #296DF3; + } +} \ No newline at end of file diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/enum.ts b/webapp/packages/supersonic-fe/src/pages/SemanticModel/enum.ts index 01e71630e..35f77e288 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/enum.ts +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/enum.ts @@ -18,3 +18,11 @@ export enum MetricTypeWording { ATOMIC = '原子指标', DERIVED = '衍生指标', } + +export enum DictTaskState { + ERROR = '错误', + PENDING = '等待', + RUNNING = '正在执行', + SUCCESS = '成功', + UNKNOWN = '未知', +} diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/service.ts b/webapp/packages/supersonic-fe/src/pages/SemanticModel/service.ts index 684901f00..4101aa825 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/service.ts +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/service.ts @@ -340,3 +340,24 @@ export function deleteModel(modelId: number): Promise { export function getModelDetail(data: any): Promise { return request.get(`${process.env.API_BASE_URL}model/getModel/${data.modelId}`); } + +export function createDictTask(data: any): Promise { + return request(`${process.env.CHAT_API_BASE_URL}dict/task`, { + method: 'POST', + data, + }); +} + +export function deleteDictTask(data: any): Promise { + return request(`${process.env.CHAT_API_BASE_URL}dict/task/delete`, { + method: 'POST', + data, + }); +} + +export function searchDictLatestTaskList(data: any): Promise { + return request(`${process.env.CHAT_API_BASE_URL}dict/task/search/latest`, { + method: 'POST', + data, + }); +} diff --git a/webapp/packages/supersonic-fe/src/utils/utils.ts b/webapp/packages/supersonic-fe/src/utils/utils.ts index 10be06336..93e66eabf 100644 --- a/webapp/packages/supersonic-fe/src/utils/utils.ts +++ b/webapp/packages/supersonic-fe/src/utils/utils.ts @@ -397,14 +397,6 @@ export function traverseRoutes(routes, env: string, result: any[] = []) { for (let i = 0; i < routes.length; i++) { const route = routes[i]; - if ( - (route.envEnableList && - (route.envEnableList.includes(env) || route.envEnableList.length === 0)) || - !route.envEnableList - ) { - result.push(route); - } - if (route.envRedirect) { route.redirect = route.envRedirect[env]; } @@ -418,6 +410,12 @@ export function traverseRoutes(routes, env: string, result: any[] = []) { routes: filteredRoutes, }); } + } else if ( + (route.envEnableList && + (route.envEnableList.includes(env) || route.envEnableList.length === 0)) || + !route.envEnableList + ) { + result.push(route); } } return result;