mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-12 04:27:39 +00:00
[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:
@@ -1,6 +1,6 @@
|
|||||||
import type { ActionType, ProColumns } from '@ant-design/pro-table';
|
import type { ActionType, ProColumns } from '@ant-design/pro-table';
|
||||||
import ProTable 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 React, { useRef, useState, useEffect } from 'react';
|
||||||
import type { Dispatch } from 'umi';
|
import type { Dispatch } from 'umi';
|
||||||
import { connect } from 'umi';
|
import { connect } from 'umi';
|
||||||
@@ -85,10 +85,18 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
title: 'ID',
|
title: 'ID',
|
||||||
width: 80,
|
width: 80,
|
||||||
order: 100,
|
order: 100,
|
||||||
|
search: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'key',
|
||||||
|
title: '维度搜索',
|
||||||
|
hideInTable: true,
|
||||||
|
renderFormItem: () => <Input placeholder="请输入ID/维度名称/字段名称" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
title: '维度名称',
|
title: '维度名称',
|
||||||
|
search: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'alias',
|
dataIndex: 'alias',
|
||||||
@@ -100,6 +108,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
{
|
{
|
||||||
dataIndex: 'bizName',
|
dataIndex: 'bizName',
|
||||||
title: '字段名称',
|
title: '字段名称',
|
||||||
|
search: false,
|
||||||
// order: 9,
|
// order: 9,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { ActionType, ProColumns } from '@ant-design/pro-table';
|
import type { ActionType, ProColumns } from '@ant-design/pro-table';
|
||||||
import ProTable 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 React, { useRef, useState } from 'react';
|
||||||
import type { Dispatch } from 'umi';
|
import type { Dispatch } from 'umi';
|
||||||
import { connect } from 'umi';
|
import { connect } from 'umi';
|
||||||
@@ -65,10 +65,18 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
dataIndex: 'id',
|
dataIndex: 'id',
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
width: 80,
|
width: 80,
|
||||||
|
search: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
title: '指标名称',
|
title: '指标名称',
|
||||||
|
search: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'key',
|
||||||
|
title: '指标搜索',
|
||||||
|
hideInTable: true,
|
||||||
|
renderFormItem: () => <Input placeholder="请输入ID/指标名称/字段名称" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'alias',
|
dataIndex: 'alias',
|
||||||
@@ -80,6 +88,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
{
|
{
|
||||||
dataIndex: 'bizName',
|
dataIndex: 'bizName',
|
||||||
title: '字段名称',
|
title: '字段名称',
|
||||||
|
search: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'sensitiveLevel',
|
dataIndex: 'sensitiveLevel',
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ const DatabaseCreateForm: ForwardRefRenderFunction<any, Props> = (
|
|||||||
<FormItem
|
<FormItem
|
||||||
name="username"
|
name="username"
|
||||||
label="用户名"
|
label="用户名"
|
||||||
rules={[{ required: true, message: '请输入用户名' }]}
|
// rules={[{ required: true, message: '请输入用户名' }]}
|
||||||
>
|
>
|
||||||
<Input placeholder="请输入用户名" />
|
<Input placeholder="请输入用户名" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ const DimensionAndMetricVisibleModal: React.FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Modal
|
||||||
width={1200}
|
width={1500}
|
||||||
destroyOnClose
|
destroyOnClose
|
||||||
title={settingTypeConfig.modalTitle}
|
title={settingTypeConfig.modalTitle}
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
|||||||
@@ -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 { ColumnsType, TableRowSelection } from 'antd/es/table/interface';
|
||||||
import type { TransferItem } from 'antd/es/transfer';
|
import type { TransferItem } from 'antd/es/transfer';
|
||||||
import type { CheckboxChangeEvent } from 'antd/es/checkbox';
|
import type { CheckboxChangeEvent } from 'antd/es/checkbox';
|
||||||
import difference from 'lodash/difference';
|
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 type { IChatConfig } from '../../data';
|
||||||
import DimensionValueSettingModal from './DimensionValueSettingModal';
|
import DimensionValueSettingModal from './DimensionValueSettingModal';
|
||||||
import TransTypeTag from '../TransTypeTag';
|
import TransTypeTag from '../TransTypeTag';
|
||||||
import { TransType } from '../../enum';
|
|
||||||
import TableTitleTooltips from '../../components/TableTitleTooltips';
|
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 {
|
interface RecordType {
|
||||||
id: number;
|
id: number;
|
||||||
key: string;
|
key: string;
|
||||||
@@ -19,22 +23,35 @@ interface RecordType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
domainManger: StateType;
|
||||||
knowledgeInfosMap?: IChatConfig.IKnowledgeInfosItemMap;
|
knowledgeInfosMap?: IChatConfig.IKnowledgeInfosItemMap;
|
||||||
onKnowledgeInfosMapChange?: (knowledgeInfosMap: IChatConfig.IKnowledgeInfosItemMap) => void;
|
onKnowledgeInfosMapChange?: (knowledgeInfosMap: IChatConfig.IKnowledgeInfosItemMap) => void;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type TaskStateMap = Record<string, DictTaskState>;
|
||||||
|
|
||||||
const DimensionMetricVisibleTableTransfer: React.FC<Props> = ({
|
const DimensionMetricVisibleTableTransfer: React.FC<Props> = ({
|
||||||
|
domainManger,
|
||||||
knowledgeInfosMap,
|
knowledgeInfosMap,
|
||||||
onKnowledgeInfosMapChange,
|
onKnowledgeInfosMapChange,
|
||||||
...restProps
|
...restProps
|
||||||
}) => {
|
}) => {
|
||||||
|
const { selectModelId: modelId } = domainManger;
|
||||||
const [dimensionValueSettingModalVisible, setDimensionValueSettingModalVisible] =
|
const [dimensionValueSettingModalVisible, setDimensionValueSettingModalVisible] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
const [currentRecord, setCurrentRecord] = useState<any>({});
|
const [currentRecord, setCurrentRecord] = useState<RecordType>({} as RecordType);
|
||||||
const [currentDimensionSettingFormData, setCurrentDimensionSettingFormData] =
|
const [currentDimensionSettingFormData, setCurrentDimensionSettingFormData] =
|
||||||
useState<IChatConfig.IKnowledgeConfig>();
|
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 updateKnowledgeInfosMap = (record: RecordType, updateData: Record<string, any>) => {
|
||||||
const { bizName, id } = record;
|
const { bizName, id } = record;
|
||||||
const knowledgeMap = {
|
const knowledgeMap = {
|
||||||
@@ -56,6 +73,61 @@ const DimensionMetricVisibleTableTransfer: React.FC<Props> = ({
|
|||||||
onKnowledgeInfosMapChange?.(knowledgeMap);
|
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> = [
|
let rightColumns: ColumnsType<RecordType> = [
|
||||||
{
|
{
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
@@ -85,6 +157,9 @@ const DimensionMetricVisibleTableTransfer: React.FC<Props> = ({
|
|||||||
checked={knowledgeInfosMap?.[bizName]?.searchEnable}
|
checked={knowledgeInfosMap?.[bizName]?.searchEnable}
|
||||||
onChange={(e: CheckboxChangeEvent) => {
|
onChange={(e: CheckboxChangeEvent) => {
|
||||||
updateKnowledgeInfosMap(record, { searchEnable: e.target.checked });
|
updateKnowledgeInfosMap(record, { searchEnable: e.target.checked });
|
||||||
|
if (!e.target.checked) {
|
||||||
|
deleteDictTask(record);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@@ -95,28 +170,70 @@ 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: '操作',
|
title: '操作',
|
||||||
dataIndex: 'x',
|
dataIndex: 'x',
|
||||||
render: (_: any, record: RecordType) => {
|
render: (_: any, record: RecordType) => {
|
||||||
const { type, bizName } = record;
|
const { type, bizName, id } = record;
|
||||||
return type === TransType.DIMENSION ? (
|
return type === TransType.DIMENSION ? (
|
||||||
<Button
|
<Space>
|
||||||
style={{ padding: 0 }}
|
<Button
|
||||||
key="editable"
|
style={{ padding: 0 }}
|
||||||
type="link"
|
key="importDictBtn"
|
||||||
disabled={!knowledgeInfosMap?.[bizName]?.searchEnable}
|
type="link"
|
||||||
onClick={(event) => {
|
disabled={!knowledgeInfosMap?.[bizName]?.searchEnable}
|
||||||
setCurrentRecord(record);
|
loading={!!recordLoadingMap[id]}
|
||||||
setCurrentDimensionSettingFormData(
|
onClick={(event) => {
|
||||||
knowledgeInfosMap?.[bizName]?.knowledgeAdvancedConfig,
|
createDictTaskQuery(record);
|
||||||
);
|
event.stopPropagation();
|
||||||
setDimensionValueSettingModalVisible(true);
|
}}
|
||||||
event.stopPropagation();
|
>
|
||||||
}}
|
导入字典
|
||||||
>
|
</Button>
|
||||||
可见维度值设置
|
<Button
|
||||||
</Button>
|
style={{ padding: 0 }}
|
||||||
|
key="editable"
|
||||||
|
type="link"
|
||||||
|
disabled={!knowledgeInfosMap?.[bizName]?.searchEnable}
|
||||||
|
onClick={(event) => {
|
||||||
|
setCurrentRecord(record);
|
||||||
|
setCurrentDimensionSettingFormData(
|
||||||
|
knowledgeInfosMap?.[bizName]?.knowledgeAdvancedConfig,
|
||||||
|
);
|
||||||
|
setDimensionValueSettingModalVisible(true);
|
||||||
|
event.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
可见维度值设置
|
||||||
|
</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);
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ const DimensionMetricVisibleTransfer: React.FC<Props> = ({
|
|||||||
showSearch
|
showSearch
|
||||||
titles={titles || ['不可见维度', '可见维度']}
|
titles={titles || ['不可见维度', '可见维度']}
|
||||||
listStyle={{
|
listStyle={{
|
||||||
width: 500,
|
width: 720,
|
||||||
height: 600,
|
height: 600,
|
||||||
}}
|
}}
|
||||||
filterOption={(inputValue: string, item: any) => {
|
filterOption={(inputValue: string, item: any) => {
|
||||||
|
|||||||
@@ -353,3 +353,10 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.taskStateRefreshIcon {
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
color: #296DF3;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,3 +18,11 @@ export enum MetricTypeWording {
|
|||||||
ATOMIC = '原子指标',
|
ATOMIC = '原子指标',
|
||||||
DERIVED = '衍生指标',
|
DERIVED = '衍生指标',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum DictTaskState {
|
||||||
|
ERROR = '错误',
|
||||||
|
PENDING = '等待',
|
||||||
|
RUNNING = '正在执行',
|
||||||
|
SUCCESS = '成功',
|
||||||
|
UNKNOWN = '未知',
|
||||||
|
}
|
||||||
|
|||||||
@@ -340,3 +340,24 @@ export function deleteModel(modelId: number): Promise<any> {
|
|||||||
export function getModelDetail(data: any): Promise<any> {
|
export function getModelDetail(data: any): Promise<any> {
|
||||||
return request.get(`${process.env.API_BASE_URL}model/getModel/${data.modelId}`);
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -397,14 +397,6 @@ export function traverseRoutes(routes, env: string, result: any[] = []) {
|
|||||||
for (let i = 0; i < routes.length; i++) {
|
for (let i = 0; i < routes.length; i++) {
|
||||||
const route = routes[i];
|
const route = routes[i];
|
||||||
|
|
||||||
if (
|
|
||||||
(route.envEnableList &&
|
|
||||||
(route.envEnableList.includes(env) || route.envEnableList.length === 0)) ||
|
|
||||||
!route.envEnableList
|
|
||||||
) {
|
|
||||||
result.push(route);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (route.envRedirect) {
|
if (route.envRedirect) {
|
||||||
route.redirect = route.envRedirect[env];
|
route.redirect = route.envRedirect[env];
|
||||||
}
|
}
|
||||||
@@ -418,6 +410,12 @@ export function traverseRoutes(routes, env: string, result: any[] = []) {
|
|||||||
routes: filteredRoutes,
|
routes: filteredRoutes,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else if (
|
||||||
|
(route.envEnableList &&
|
||||||
|
(route.envEnableList.includes(env) || route.envEnableList.length === 0)) ||
|
||||||
|
!route.envEnableList
|
||||||
|
) {
|
||||||
|
result.push(route);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
Reference in New Issue
Block a user