[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
This commit is contained in:
tristanliu
2023-09-14 12:14:02 +08:00
committed by GitHub
parent b6d984475c
commit 157c2999dc
10 changed files with 208 additions and 36 deletions

View File

@@ -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<Props> = ({ domainManger, dispatch }) => {
title: 'ID',
width: 80,
order: 100,
search: false,
},
{
dataIndex: 'key',
title: '维度搜索',
hideInTable: true,
renderFormItem: () => <Input placeholder="请输入ID/维度名称/字段名称" />,
},
{
dataIndex: 'name',
title: '维度名称',
search: false,
},
{
dataIndex: 'alias',
@@ -100,6 +108,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
{
dataIndex: 'bizName',
title: '字段名称',
search: false,
// order: 9,
},
{

View File

@@ -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<Props> = ({ domainManger, dispatch }) => {
dataIndex: 'id',
title: 'ID',
width: 80,
search: false,
},
{
dataIndex: 'name',
title: '指标名称',
search: false,
},
{
dataIndex: 'key',
title: '指标搜索',
hideInTable: true,
renderFormItem: () => <Input placeholder="请输入ID/指标名称/字段名称" />,
},
{
dataIndex: 'alias',
@@ -80,6 +88,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
{
dataIndex: 'bizName',
title: '字段名称',
search: false,
},
{
dataIndex: 'sensitiveLevel',

View File

@@ -143,7 +143,7 @@ const DatabaseCreateForm: ForwardRefRenderFunction<any, Props> = (
<FormItem
name="username"
label="用户名"
rules={[{ required: true, message: '请输入用户名' }]}
// rules={[{ required: true, message: '请输入用户名' }]}
>
<Input placeholder="请输入用户名" />
</FormItem>

View File

@@ -197,7 +197,7 @@ const DimensionAndMetricVisibleModal: React.FC<Props> = ({
return (
<>
<Modal
width={1200}
width={1500}
destroyOnClose
title={settingTypeConfig.modalTitle}
maskClosable={false}

View File

@@ -1,15 +1,19 @@
import { Table, Transfer, Checkbox, Button, Tag } from 'antd';
import { Table, Transfer, Checkbox, Button, Space, message, Tooltip } from 'antd';
import type { ColumnsType, TableRowSelection } from 'antd/es/table/interface';
import type { TransferItem } from 'antd/es/transfer';
import type { CheckboxChangeEvent } from 'antd/es/checkbox';
import difference from 'lodash/difference';
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { connect } from 'umi';
import type { StateType } from '../../model';
import type { IChatConfig } from '../../data';
import DimensionValueSettingModal from './DimensionValueSettingModal';
import TransTypeTag from '../TransTypeTag';
import { TransType } from '../../enum';
import TableTitleTooltips from '../../components/TableTitleTooltips';
import { SemanticNodeType } from '../../enum';
import { RedoOutlined } from '@ant-design/icons';
import { SemanticNodeType, DictTaskState, TransType } from '../../enum';
import { createDictTask, searchDictLatestTaskList } from '@/pages/SemanticModel/service';
import styles from '../style.less';
interface RecordType {
id: number;
key: string;
@@ -19,22 +23,35 @@ interface RecordType {
}
type Props = {
domainManger: StateType;
knowledgeInfosMap?: IChatConfig.IKnowledgeInfosItemMap;
onKnowledgeInfosMapChange?: (knowledgeInfosMap: IChatConfig.IKnowledgeInfosItemMap) => void;
[key: string]: any;
};
type TaskStateMap = Record<string, DictTaskState>;
const DimensionMetricVisibleTableTransfer: React.FC<Props> = ({
domainManger,
knowledgeInfosMap,
onKnowledgeInfosMapChange,
...restProps
}) => {
const { selectModelId: modelId } = domainManger;
const [dimensionValueSettingModalVisible, setDimensionValueSettingModalVisible] =
useState<boolean>(false);
const [currentRecord, setCurrentRecord] = useState<any>({});
const [currentRecord, setCurrentRecord] = useState<RecordType>({} as RecordType);
const [currentDimensionSettingFormData, setCurrentDimensionSettingFormData] =
useState<IChatConfig.IKnowledgeConfig>();
const [recordLoadingMap, setRecordLoadingMap] = useState<Record<string, boolean>>({});
const [taskStateMap, setTaskStateMap] = useState<TaskStateMap>({});
useEffect(() => {
queryDictLatestTaskList();
}, []);
const updateKnowledgeInfosMap = (record: RecordType, updateData: Record<string, any>) => {
const { bizName, id } = record;
const knowledgeMap = {
@@ -56,6 +73,61 @@ const DimensionMetricVisibleTableTransfer: React.FC<Props> = ({
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<RecordType> = [
{
dataIndex: 'name',
@@ -85,6 +157,9 @@ const DimensionMetricVisibleTableTransfer: React.FC<Props> = ({
checked={knowledgeInfosMap?.[bizName]?.searchEnable}
onChange={(e: CheckboxChangeEvent) => {
updateKnowledgeInfosMap(record, { searchEnable: e.target.checked });
if (!e.target.checked) {
deleteDictTask(record);
}
}}
onClick={(event) => {
event.stopPropagation();
@@ -95,12 +170,53 @@ const DimensionMetricVisibleTableTransfer: React.FC<Props> = ({
);
},
},
{
dataIndex: 'taskState',
width: 130,
title: (
<Space>
<span
className={styles.taskStateRefreshIcon}
onClick={() => {
queryDictLatestTaskList();
}}
>
<Tooltip title="刷新字典任务状态">
<RedoOutlined />
</Tooltip>
</span>
</Space>
),
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 ? (
<Space>
<Button
style={{ padding: 0 }}
key="importDictBtn"
type="link"
disabled={!knowledgeInfosMap?.[bizName]?.searchEnable}
loading={!!recordLoadingMap[id]}
onClick={(event) => {
createDictTaskQuery(record);
event.stopPropagation();
}}
>
</Button>
<Button
style={{ padding: 0 }}
key="editable"
@@ -117,6 +233,7 @@ const DimensionMetricVisibleTableTransfer: React.FC<Props> = ({
>
</Button>
</Space>
) : (
<></>
);
@@ -197,4 +314,7 @@ const DimensionMetricVisibleTableTransfer: React.FC<Props> = ({
);
};
export default DimensionMetricVisibleTableTransfer;
// export default DimensionMetricVisibleTableTransfer;
export default connect(({ domainManger }: { domainManger: StateType }) => ({
domainManger,
}))(DimensionMetricVisibleTableTransfer);

View File

@@ -62,7 +62,7 @@ const DimensionMetricVisibleTransfer: React.FC<Props> = ({
showSearch
titles={titles || ['不可见维度', '可见维度']}
listStyle={{
width: 500,
width: 720,
height: 600,
}}
filterOption={(inputValue: string, item: any) => {

View File

@@ -353,3 +353,10 @@
width: 100%;
}
}
.taskStateRefreshIcon {
cursor: pointer;
&:hover {
color: #296DF3;
}
}

View File

@@ -18,3 +18,11 @@ export enum MetricTypeWording {
ATOMIC = '原子指标',
DERIVED = '衍生指标',
}
export enum DictTaskState {
ERROR = '错误',
PENDING = '等待',
RUNNING = '正在执行',
SUCCESS = '成功',
UNKNOWN = '未知',
}

View File

@@ -340,3 +340,24 @@ export function deleteModel(modelId: number): Promise<any> {
export function getModelDetail(data: any): Promise<any> {
return request.get(`${process.env.API_BASE_URL}model/getModel/${data.modelId}`);
}
export function createDictTask(data: any): Promise<any> {
return request(`${process.env.CHAT_API_BASE_URL}dict/task`, {
method: 'POST',
data,
});
}
export function deleteDictTask(data: any): Promise<any> {
return request(`${process.env.CHAT_API_BASE_URL}dict/task/delete`, {
method: 'POST',
data,
});
}
export function searchDictLatestTaskList(data: any): Promise<any> {
return request(`${process.env.CHAT_API_BASE_URL}dict/task/search/latest`, {
method: 'POST',
data,
});
}

View File

@@ -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;