diff --git a/webapp/packages/supersonic-fe/config/routes.ts b/webapp/packages/supersonic-fe/config/routes.ts index 40a120873..892d34366 100644 --- a/webapp/packages/supersonic-fe/config/routes.ts +++ b/webapp/packages/supersonic-fe/config/routes.ts @@ -22,12 +22,12 @@ const ROUTES = [ component: './ChatPage', envEnableList: [ENV_KEY.CHAT], }, - { - path: '/chatSetting/model/:domainId?/:modelId?/:menuKey?', - component: './SemanticModel/ChatSetting/ChatSetting', - name: 'chatSetting', - envEnableList: [ENV_KEY.CHAT], - }, + // { + // path: '/chatSetting/model/:domainId?/:modelId?/:menuKey?', + // component: './SemanticModel/ChatSetting/ChatSetting', + // name: 'chatSetting', + // envEnableList: [ENV_KEY.CHAT], + // }, { path: '/agent', name: 'agent', @@ -40,7 +40,6 @@ const ROUTES = [ name: 'semanticModel', envEnableList: [ENV_KEY.SEMANTIC], }, - { path: '/database', name: 'database', @@ -86,6 +85,12 @@ const ROUTES = [ hideInMenu: true, component: './Login', }, + { + path: '/system', + name: 'system', + hideInMenu: true, + component: './System', + }, { path: '/', redirect: APP_TARGET === 'inner' ? '/model' : '/chat', diff --git a/webapp/packages/supersonic-fe/package.json b/webapp/packages/supersonic-fe/package.json index f09d513e4..7c28a2010 100644 --- a/webapp/packages/supersonic-fe/package.json +++ b/webapp/packages/supersonic-fe/package.json @@ -28,7 +28,7 @@ "prettier": "prettier -c --write \"src/**/*\"", "start": "npm run start:osdev", "start:dev": "cross-env REACT_APP_ENV=dev MOCK=none APP_TARGET=inner umi dev", - "start:osdev": "cross-env REACT_APP_ENV=dev PORT=9000 MOCK=none APP_TARGET=opensource umi dev", + "start:osdev": "cross-env NODE_OPTIONS=--openssl-legacy-provider REACT_APP_ENV=dev PORT=9000 MOCK=none APP_TARGET=opensource umi dev", "start:no-mock": "cross-env MOCK=none umi dev", "start:no-ui": "cross-env UMI_UI=none umi dev", "start:pre": "cross-env REACT_APP_ENV=pre umi dev", diff --git a/webapp/packages/supersonic-fe/src/components/FormHelper/FormItemTitle.tsx b/webapp/packages/supersonic-fe/src/components/FormHelper/FormItemTitle.tsx index 70343c3f3..09bca627f 100644 --- a/webapp/packages/supersonic-fe/src/components/FormHelper/FormItemTitle.tsx +++ b/webapp/packages/supersonic-fe/src/components/FormHelper/FormItemTitle.tsx @@ -1,6 +1,7 @@ import { Space } from 'antd'; +import { ReactNode } from 'react'; export interface IProps { - title: string; + title: string | ReactNode; subTitle?: string; } diff --git a/webapp/packages/supersonic-fe/src/components/RightContent/index.tsx b/webapp/packages/supersonic-fe/src/components/RightContent/index.tsx index 96427ab1f..3c84e8f0e 100644 --- a/webapp/packages/supersonic-fe/src/components/RightContent/index.tsx +++ b/webapp/packages/supersonic-fe/src/components/RightContent/index.tsx @@ -1,8 +1,9 @@ import { Space } from 'antd'; -import React from 'react'; -import { useModel } from 'umi'; +import React, { useEffect, useState } from 'react'; +import { useModel, history } from 'umi'; import Avatar from './AvatarDropdown'; - +import { SettingOutlined } from '@ant-design/icons'; +import { getSystemConfig } from '@/services/user'; import styles from './index.less'; import cx from 'classnames'; @@ -10,14 +11,29 @@ export type SiderTheme = 'light' | 'dark'; const GlobalHeaderRight: React.FC = () => { const { initialState } = useModel('@@initialState'); + const [hasSettingPermisson, setHasSettingPermisson] = useState(false); + useEffect(() => { + querySystemConfig(); + }, []); if (!initialState || !initialState.settings) { return null; } + const { currentUser = {} } = initialState as any; - const { navTheme, layout } = initialState.settings; + const { layout } = initialState.settings; let className = styles.right; + const querySystemConfig = async () => { + const { code, data } = await getSystemConfig(); + if (code === 200) { + const { admins } = data; + if (admins.includes(currentUser?.staffName)) { + setHasSettingPermisson(true); + } + } + }; + if (layout === 'top' || layout === 'mix') { className = cx(styles.right, styles.dark); } @@ -25,8 +41,19 @@ const GlobalHeaderRight: React.FC = () => { function handleLogin() {} return ( - + + {hasSettingPermisson && ( + { + history.push(`/system`); + }} + > + + + )} ); }; diff --git a/webapp/packages/supersonic-fe/src/global.less b/webapp/packages/supersonic-fe/src/global.less index 6b09c9430..28d25ced7 100644 --- a/webapp/packages/supersonic-fe/src/global.less +++ b/webapp/packages/supersonic-fe/src/global.less @@ -28,6 +28,8 @@ html, body, #root { height: 100%; + font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Microsoft YaHei,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; + -webkit-font-smoothing:antialiased; } .colorWeak { 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 07f2ac053..7e6b5a776 100644 --- a/webapp/packages/supersonic-fe/src/locales/zh-CN/menu.ts +++ b/webapp/packages/supersonic-fe/src/locales/zh-CN/menu.ts @@ -14,6 +14,7 @@ export default { 'menu.chatSetting': '问答设置', 'menu.plugin': '插件市场', 'menu.login': '登录', + 'menu.system': '系统设置', 'menu.chat': '问答对话', 'menu.agent': '智能助理', }; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/DataSourceCreateForm.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/DataSourceCreateForm.tsx index 059d76c6b..9eed5d3a6 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/DataSourceCreateForm.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/DataSourceCreateForm.tsx @@ -1,10 +1,9 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { ReactNode, useEffect, useRef, useState } from 'react'; import { Form, Button, Modal, Steps, message } from 'antd'; import DataSourceBasicForm from './DataSourceBasicForm'; import FieldForm from './DataSourceFieldForm'; import { formLayout } from '@/components/FormHelper/utils'; import { EnumDataSourceType } from '../constants'; -// import type { DataInstanceItem } from '../data'; import styles from '../style.less'; import { createDatasource, updateDatasource, getColumns } from '../../service'; import type { Dispatch } from 'umi'; @@ -17,13 +16,15 @@ export type CreateFormProps = { dispatch: Dispatch; createModalVisible: boolean; sql?: string; - databaseItem?: any; + databaseId?: number; dataSourceItem: IDataSource.IDataSourceItem; onCancel?: () => void; onSubmit?: (dataSourceInfo: any) => void; scriptColumns?: any[] | undefined; basicInfoFormMode?: 'normal' | 'fast'; onDataBaseTableChange?: (tableName: string) => void; + onDataSourceBtnClick?: () => void; + // modalSolt: ReactNode; }; const { Step } = Steps; @@ -41,8 +42,10 @@ const DataSourceCreateForm: React.FC = ({ sql = '', onSubmit, dataSourceItem, - databaseItem, + databaseId, basicInfoFormMode, + onDataSourceBtnClick, + children, }) => { const isEdit = !!dataSourceItem?.id; const [fields, setFields] = useState([]); @@ -145,7 +148,7 @@ const DataSourceCreateForm: React.FC = ({ ); return classify; }; - const handleNext = async () => { + const handleNext = async (saveState: boolean = false) => { const fieldsValue = await form.validateFields(); const fieldsClassify = getFieldsClassify(fields); @@ -155,7 +158,7 @@ const DataSourceCreateForm: React.FC = ({ ...fieldsClassify, }; updateFormVal(submitForm); - if (currentStep < 1) { + if (!saveState && currentStep < 1) { forward(); } else { setSaveLoading(true); @@ -163,7 +166,7 @@ const DataSourceCreateForm: React.FC = ({ const queryParams = { ...submitForm, sqlQuery: sql, - databaseId: databaseItem?.key || dataSourceItem?.databaseId || formDatabaseId, + databaseId: databaseId || dataSourceItem?.databaseId || formDatabaseId, queryType: basicInfoFormMode === 'fast' ? 'table_query' : 'sql_query', tableQuery: dbName && tableName ? `${dbName}.${tableName}` : '', modelId: isEdit ? dataSourceItem.modelId : modelId, @@ -191,7 +194,6 @@ const DataSourceCreateForm: React.FC = ({ return { ...oldItem, bizName: nameEn, - // name, sqlType: type, }; }); @@ -264,7 +266,7 @@ const DataSourceCreateForm: React.FC = ({ } else { initFields([], fieldColumns); } - }, [dataSourceItem]); + }, [dataSourceItem, fieldColumns]); const handleFieldChange = (fieldName: string, data: any) => { const result = fields.map((field) => { @@ -285,7 +287,6 @@ const DataSourceCreateForm: React.FC = ({ const { code, data, msg } = await getColumns(databaseId, dbName, tableName); if (code === 200) { const list = data?.resultList || []; - // setTableNameList(list); const columns = list.map((item: any) => { const { dataType, name } = item; return { @@ -293,7 +294,6 @@ const DataSourceCreateForm: React.FC = ({ type: dataType, }; }); - // setFields(columns); initFields([], columns); setFieldColumns(columns); return columns; @@ -327,14 +327,25 @@ const DataSourceCreateForm: React.FC = ({ - + + + ); @@ -342,9 +353,26 @@ const DataSourceCreateForm: React.FC = ({ return ( <> - + {isEdit && ( + + )} ); }; @@ -383,6 +411,7 @@ const DataSourceCreateForm: React.FC = ({ > {renderContent()} + {children} ); }; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/SqlDetail.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/SqlDetail.tsx index 1a601d47b..e58486d2c 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/SqlDetail.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/SqlDetail.tsx @@ -18,7 +18,7 @@ import FullScreen from '@/components/FullScreen'; import SqlEditor from '@/components/SqlEditor'; import type { TaskResultItem, TaskResultColumn } from '../data'; import { excuteSql } from '@/pages/SemanticModel/service'; -import DataSourceCreateForm from './DataSourceCreateForm'; +// import DataSourceCreateForm from './DataSourceCreateForm'; import type { Dispatch } from 'umi'; import type { StateType } from '../../model'; import styles from '../style.less'; @@ -29,13 +29,19 @@ import 'ace-builds/src-min-noconflict/theme-monokai'; import 'ace-builds/src-min-noconflict/mode-sql'; import { IDataSource, ISemantic } from '../../data'; +export type DataSourceSubmitData = { + sql: string; + databaseId: number; + columns: any[]; +}; + type IProps = { domainManger: StateType; dispatch: Dispatch; dataSourceItem: IDataSource.IDataSourceItem; onUpdateSql?: (sql: string) => void; sql?: string; - onSubmitSuccess?: (dataSourceInfo: any) => void; + onSubmitSuccess?: (dataSourceInfo: DataSourceSubmitData) => void; }; type ResultTableItem = Record; @@ -48,7 +54,7 @@ type ResultColItem = { type ScreenSize = 'small' | 'middle' | 'large'; -type JdbcSourceItems = { +type DatabaseItem = { label: string; key: number; }; @@ -60,7 +66,7 @@ const SqlDetail: React.FC = ({ sql = '', onUpdateSql, }) => { - const { databaseConfigList, selectModelId: modelId } = domainManger; + const { databaseConfigList } = domainManger; const [resultTable, setResultTable] = useState([]); const [resultTableLoading, setResultTableLoading] = useState(false); const [resultCols, setResultCols] = useState([]); @@ -69,9 +75,9 @@ const SqlDetail: React.FC = ({ pageSize: 20, total: 0, }); - const [jdbcSourceItems, setJdbcSourceItems] = useState([]); - const [currentJdbcSourceItem, setCurrentJdbcSourceItem] = useState(); - const [dataSourceModalVisible, setDataSourceModalVisible] = useState(false); + const [dataBaseItems, setDataBaseItems] = useState([]); + const [currentDatabaseItem, setCurrentDatabaseItem] = useState(); + // const [dataSourceModalVisible, setDataSourceModalVisible] = useState(false); const [tableScroll, setTableScroll] = useState({ scrollToFirstRowOnChange: true, @@ -107,7 +113,7 @@ const SqlDetail: React.FC = ({ disabled: !item.hasUsePermission, }; }); - setJdbcSourceItems(list); + setDataBaseItems(list); let targetDataBase = list[0]; if (dataSourceItem?.id) { const { databaseId } = dataSourceItem; @@ -116,12 +122,12 @@ const SqlDetail: React.FC = ({ targetDataBase = target; } } - setCurrentJdbcSourceItem(targetDataBase); + setCurrentDatabaseItem(targetDataBase); }, [dataSourceItem, databaseConfigList]); useEffect(() => { setRunState(undefined); - }, [currentJdbcSourceItem]); + }, [currentDatabaseItem, sql]); function creatCalcItem(key: string, data: string) { const line = document.createElement('div'); // 需要每条数据一行,这样避免数据换行的时候获得的宽度不准确 @@ -223,18 +229,16 @@ const SqlDetail: React.FC = ({ }; const separateSql = async (value: string) => { - if (!currentJdbcSourceItem?.key) { + if (!currentDatabaseItem?.key) { return; } setResultTableLoading(true); const { code, data, msg } = await excuteSql({ sql: value, - // modelId, - id: currentJdbcSourceItem.key, + id: currentDatabaseItem.key, }); setResultTableLoading(false); if (code === 200) { - // setDataSourceResult(data); fetchTaskResult(data); setRunState(true); } else { @@ -268,13 +272,13 @@ const SqlDetail: React.FC = ({ return isPartial ? separateSql(partialSql) : separateSql(sql); }; - const showDataSetModal = () => { - setDataSourceModalVisible(true); - }; + // const showDataSetModal = () => { + // setDataSourceModalVisible(true); + // }; - const startCreatDataSource = async () => { - showDataSetModal(); - }; + // const startCreatDataSource = async () => { + // showDataSetModal(); + // }; const updateNormalResScroll = () => { const node = resultInnerWrap?.current; @@ -386,14 +390,14 @@ const SqlDetail: React.FC = ({ { const value = e.key; - const target: any = jdbcSourceItems.filter((item: any) => { + const target: any = dataBaseItems.filter((item: any) => { return item.key === Number(value); })[0]; if (target) { - setCurrentJdbcSourceItem(target); + setCurrentDatabaseItem(target); } }, }} @@ -402,7 +406,7 @@ const SqlDetail: React.FC = ({ @@ -464,7 +468,7 @@ const SqlDetail: React.FC = ({
- { + {/* { - } + } */} +
- - {dataSourceModalVisible && ( - { - setDataSourceModalVisible(false); - }} - onSubmit={(dataSourceInfo: any) => { - setDataSourceModalVisible(false); - onSubmitSuccess?.(dataSourceInfo); - }} - createModalVisible={dataSourceModalVisible} - /> - )} ); }; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/SqlSide.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/SqlSide.tsx index 64dcd6653..b94388ed0 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/SqlSide.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/SqlSide.tsx @@ -1,6 +1,6 @@ import React, { useState, useRef, useEffect } from 'react'; import { Tabs } from 'antd'; -import SqlDetail from './SqlDetail'; +import SqlDetail, { DataSourceSubmitData } from './SqlDetail'; import styles from '../style.less'; @@ -22,7 +22,7 @@ type TableRef = { type Props = { initialValues: any; - onSubmitSuccess?: (dataSourceInfo: any) => void; + onSubmitSuccess?: (dataSourceInfo: DataSourceSubmitData) => void; }; const { TabPane } = Tabs; @@ -105,7 +105,6 @@ const SqlSide: React.FC = ({ initialValues, onSubmitSuccess }) => { })}
- {/* */} ); }; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/index.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/index.tsx index cc4c353fc..0a4cdb346 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/index.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/index.tsx @@ -4,10 +4,11 @@ import SqlSide from './components/SqlSide'; import Pane from 'react-split-pane/lib/Pane'; import styles from './style.less'; import { RightOutlined, LeftOutlined } from '@ant-design/icons'; +import { DataSourceSubmitData } from './components/SqlDetail'; type Props = { initialValues: any; - onSubmitSuccess?: (dataSourceInfo: any) => void; + onSubmitSuccess?: (dataSourceInfo: DataSourceSubmitData) => void; }; const DEFAULT_RIGHT_SIZE = '300px'; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/Market.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/Market.tsx index 5fa073809..31ecd3a58 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/Market.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/Market.tsx @@ -1,26 +1,50 @@ 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 { + message, + Space, + Popconfirm, + Tag, + Spin, + Dropdown, + DatePicker, + Popover, + Button, + Radio, +} 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 { + queryMetric, + deleteMetric, + batchUpdateMetricStatus, + batchDownloadMetric, +} 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 moment, { Moment } from 'moment'; import styles from './style.less'; import { ISemantic } from '../data'; +import { + PlaySquareOutlined, + StopOutlined, + CloudDownloadOutlined, + DeleteOutlined, +} from '@ant-design/icons'; type Props = { dispatch: Dispatch; domainManger: StateType; }; +const { RangePicker } = DatePicker; + type QueryMetricListParams = { id?: string; name?: string; @@ -51,6 +75,12 @@ const ClassMetricTable: React.FC = ({ domainManger, dispatch }) => { showType: localStorage.getItem('metricMarketShowType') === '1' ? true : false, }); const [infoDrawerVisible, setInfoDrawerVisible] = useState(false); + const [popoverOpenState, setPopoverOpenState] = useState(false); + const [pickerType, setPickerType] = useState('day'); + + const dateRangeRef = useRef([]); + const [downloadLoading, setDownloadLoading] = useState(false); + const actionRef = useRef(); useEffect(() => { @@ -122,6 +152,24 @@ const ClassMetricTable: React.FC = ({ domainManger, dispatch }) => { } }; + const downloadMetricQuery = async (ids: React.Key[], dateStringList: string[]) => { + if (Array.isArray(ids) && ids.length > 0) { + setDownloadLoading(true); + const [startDate, endDate] = dateStringList; + await batchDownloadMetric({ + metricIds: ids, + dateInfo: { + dateMode: 'BETWEEN', + startDate, + endDate, + period: pickerType.toUpperCase(), + }, + }); + setDownloadLoading(false); + setPopoverOpenState(false); + } + }; + const handleMetricEdit = (metricItem: ISemantic.IMetricItem) => { setMetricItem(metricItem); setCreateModalVisible(true); @@ -139,8 +187,6 @@ const ClassMetricTable: React.FC = ({ domainManger, dispatch }) => { return ( { - // setMetricItem(record); - // setInfoDrawerVisible(true); history.push(`/metric/detail/${record.id}`); }} > @@ -297,14 +343,25 @@ const ClassMetricTable: React.FC = ({ domainManger, dispatch }) => { }), }; - const dropdownButtonItems = [ + const dropdownButtonItems: any[] = [ { key: 'batchStart', label: '批量启用', + icon: , }, { key: 'batchStop', label: '批量停用', + icon: , + }, + { + key: 'batchDownload', + // label: '批量下载', + label: 批量下载, + icon: , + }, + { + type: 'divider', }, { key: 'batchDelete', @@ -318,6 +375,7 @@ const ClassMetricTable: React.FC = ({ domainManger, dispatch }) => { 批量删除 ), + icon: , }, ]; @@ -329,11 +387,69 @@ const ClassMetricTable: React.FC = ({ domainManger, dispatch }) => { case 'batchStop': queryBatchUpdateStatus(selectedRowKeys, StatusEnum.OFFLINE); break; + case 'batchDownload': + setPopoverOpenState(true); default: break; } }; - + const popoverConfig = { + title: '选择下载区间', + content: ( + + { + setPickerType(e.target.value); + }} + > + 按日 + 按周 + 按月 + + { + dateRangeRef.current = date; + return; + }} + picker={pickerType as any} + allowClear={true} + /> +
+ + + +
+
+ ), + }; return ( <>
@@ -383,12 +499,21 @@ const ClassMetricTable: React.FC = ({ domainManger, dispatch }) => { ...rowSelection, }} toolBarRender={() => [ - { + setPopoverOpenState(open); + }} > - 批量操作 - , + + 批量操作 + + , ]} loading={loading} onChange={(data: any) => { diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/components/MetricCardList.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/components/MetricCardList.tsx index dd062cd8f..673a972cf 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/components/MetricCardList.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/components/MetricCardList.tsx @@ -104,7 +104,7 @@ const MetricCardList: React.FC = ({ }; return ( -
+
{metricList && metricList.map((metricItem: ISemantic.IMetricItem) => { diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDataSourceTable.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDataSourceTable.tsx index c4e3cebae..663d435bc 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDataSourceTable.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDataSourceTable.tsx @@ -146,7 +146,7 @@ const ClassDataSourceTable: React.FC = ({ dispatch, domainManger }) => { , ]} /> - { + {createDataSourceModalOpen && ( { @@ -157,7 +157,7 @@ const ClassDataSourceTable: React.FC = ({ dispatch, domainManger }) => { actionRef.current?.reload(); }} /> - } + )} ); }; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDataSourceTypeModal.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDataSourceTypeModal.tsx index 531732971..1ca970b67 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDataSourceTypeModal.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDataSourceTypeModal.tsx @@ -2,8 +2,9 @@ import { Button, Drawer, Result, Modal, Card, Row, Col } from 'antd'; import { ConsoleSqlOutlined, CoffeeOutlined } from '@ant-design/icons'; import React, { useState, useEffect } from 'react'; import type { Dispatch } from 'umi'; -import { history, connect } from 'umi'; +import { connect } from 'umi'; import DataSourceCreateForm from '../Datasource/components/DataSourceCreateForm'; +import { excuteSql } from '../service'; import type { StateType } from '../model'; import DataSource from '../Datasource'; import { IDataSource } from '../data'; @@ -33,8 +34,12 @@ const ClassDataSourceTypeModal: React.FC = ({ const [dataSourceModalVisible, setDataSourceModalVisible] = useState(false); const [fastModeSql, setFastModeSql] = useState(''); + const [sql, setSql] = useState(''); - const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false); + const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false); + const [dataSourceEditOpen, setDataSourceEditOpen] = useState(false); + const [currentDatabaseId, setCurrentDatabaseId] = useState(); + const [scriptColumns, setScriptColumns] = useState([]); useEffect(() => { if (!dataSourceItem || !open) { @@ -65,6 +70,28 @@ const ClassDataSourceTypeModal: React.FC = ({ onCancel?.(); }; + useEffect(() => { + queryTableColumnListByScript(dataSourceItem); + }, [dataSourceItem]); + + const fetchTaskResult = (params) => { + setScriptColumns(params.columns); + }; + + const queryTableColumnListByScript = async (dataSource: IDataSource.IDataSourceItem) => { + if (!dataSource) { + return; + } + const { code, data, msg } = await excuteSql({ + sql: dataSource.datasourceDetail?.sqlQuery, + id: dataSource.databaseId, + }); + if (code === 200) { + fetchTaskResult(data); + setSql(dataSource?.datasourceDetail?.sqlQuery); + } + }; + return ( <> = ({ /> )} {createModalVisible && ( - { + { setCreateModalVisible(false); handleCancel(); }} - footer={null} + onSubmit={() => { + setCreateModalVisible(false); + onSubmit?.(); + }} + createModalVisible={createModalVisible} + onDataSourceBtnClick={() => { + setDataSourceEditOpen(true); + }} > - { - setCreateModalVisible(false); - onSubmit?.(); + { + setDataSourceEditOpen(false); }} - /> - + footer={null} + > + { + const { columns, sql, databaseId } = dataSourceInfo; + setSql(sql); + setScriptColumns(columns); + setCurrentDatabaseId(databaseId); + onSubmit?.(); + setDataSourceEditOpen(false); + }} + /> + + )} ); 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 a1bc7fa7b..779abf73f 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDimensionTable.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDimensionTable.tsx @@ -423,6 +423,12 @@ const ClassDimensionTable: React.FC = ({ domainManger, dispatch }) => { open={dimensionValueSettingModalVisible} dimensionItem={dimensionItem} onCancel={() => { + dispatch({ + type: 'domainManger/queryDimensionList', + payload: { + modelId, + }, + }); setDimensionValueSettingModalVisible(false); }} onSubmit={() => { diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/CommonEditList/index.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/CommonEditList/index.tsx index 6b5128a57..6a9737849 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/CommonEditList/index.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/CommonEditList/index.tsx @@ -61,7 +61,7 @@ const CommonEditList: React.FC = ({ title, defaultCollapse = false, value activeKey ? ( ) : ( - + {menuKey === 'default' && ( + <> + + + + )} ); }; @@ -154,6 +160,38 @@ const DimensionInfoModal: React.FC = ({ }, }, ]; + + const tabItem = [ + { + label: '维度值填充', + key: 'default', + children: ( + { + const dimValueMaps = tableData.map((item: TableDataSource) => { + return { + ...item, + }; + }); + + setDimValueMaps(dimValueMaps); + }} + /> + ), + }, + { + label: '维度值设置', + key: 'setting', + children: , + }, + ]; + + const handleMenuChange = (key: string) => { + setMenuKey(key); + }; + return ( = ({ footer={renderFooter()} onCancel={onCancel} > - { - const dimValueMaps = tableData.map((item: TableDataSource) => { - return { - ...item, - // alias: item.alias ? `${item.alias}`.split(',') : [], - }; - }); - - setDimValueMaps(dimValueMaps); + { + handleMenuChange(menuKey); }} /> @@ -185,4 +217,4 @@ const DimensionInfoModal: React.FC = ({ export default connect(({ domainManger }: { domainManger: StateType }) => ({ domainManger, -}))(DimensionInfoModal); +}))(DimensionValueSettingModal); 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 deleted file mode 100644 index cc9395a80..000000000 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionAndMetricVisibleModal.tsx +++ /dev/null @@ -1,221 +0,0 @@ -import React, { useEffect, useState, useRef } from 'react'; -import { Modal, message, Tabs, Button } from 'antd'; - -import { addDomainExtend, editDomainExtend } from '../../service'; -import DimensionMetricVisibleTransfer from './DimensionMetricVisibleTransfer'; -import { IChatConfig } from '../../data'; -import DimensionValueSettingForm from './DimensionValueSettingForm'; -import { TransType } from '../../enum'; -import { wrapperTransTypeAndId, formatRichEntityDataListToIds } from './utils'; - -type Props = { - domainId: number; - entityData: any; - chatConfigKey: string; - settingSourceList: any[]; - onCancel: () => void; - visible: boolean; - onSubmit: (params?: { isSilenceSubmit?: boolean }) => void; -}; - -const dimensionConfig = { - blackIdListKey: 'blackDimIdList', - visibleIdListKey: 'whiteDimIdList', - modalTitle: '问答可见信息', - titles: ['不可见维度/指标', '可见维度/指标'], -}; - -const DimensionAndMetricVisibleModal: React.FC = ({ - domainId, - visible, - entityData = {}, - chatConfigKey, - settingSourceList, - onCancel, - onSubmit, -}) => { - const [selectedKeyList, setSelectedKeyList] = useState([]); - const settingTypeConfig = dimensionConfig; - const formatEntityData = formatRichEntityDataListToIds(entityData); - const [knowledgeInfosMap, setKnowledgeInfosMap] = useState( - {}, - ); - - const [activeKey, setActiveKey] = useState('visibleSetting'); - const formRef = useRef(); - - const [globalKnowledgeConfigInitialValues, setGlobalKnowledgeConfigInitialValues] = - useState(); - - useEffect(() => { - if (entityData?.visibility && Array.isArray(settingSourceList)) { - const { whiteDimIdList, whiteMetricIdList } = entityData.visibility; - const dimensionIdString = whiteDimIdList.map((dimensionId: number) => { - return wrapperTransTypeAndId(TransType.DIMENSION, dimensionId); - }); - const metricIdString = whiteMetricIdList.map((metricId: number) => { - return wrapperTransTypeAndId(TransType.METRIC, metricId); - }); - setSelectedKeyList([...dimensionIdString, ...metricIdString]); - } - if (entityData?.globalKnowledgeConfig) { - setGlobalKnowledgeConfigInitialValues(entityData.globalKnowledgeConfig); - } - if (Array.isArray(entityData?.knowledgeInfos)) { - const infoMap = entityData.knowledgeInfos.reduce( - (maps: IChatConfig.IKnowledgeInfosItemMap, item: IChatConfig.IKnowledgeInfosItem) => { - const { bizName } = item; - maps[bizName] = item; - return maps; - }, - {}, - ); - setKnowledgeInfosMap(infoMap); - } - }, [entityData, settingSourceList]); - - const saveEntity = async (submitData: any, isSilenceSubmit = false) => { - const { selectedKeyList, knowledgeInfosMap } = submitData; - const globalKnowledgeConfigFormFields = await formRef?.current?.getFormValidateFields?.(); - let globalKnowledgeConfig = entityData.globalKnowledgeConfig; - if (globalKnowledgeConfigFormFields) { - globalKnowledgeConfig = globalKnowledgeConfigFormFields; - } - const { id, modelId } = entityData; - let saveDomainExtendQuery = addDomainExtend; - if (id) { - saveDomainExtendQuery = editDomainExtend; - } - - const blackIdListMap = settingSourceList.reduce( - (ids, item) => { - const { id, transType } = item; - if (!selectedKeyList.includes(wrapperTransTypeAndId(transType, id))) { - if (transType === TransType.DIMENSION) { - ids.blackDimIdList.push(id); - } - if (transType === TransType.METRIC) { - ids.blackMetricIdList.push(id); - } - } - return ids; - }, - { - blackDimIdList: [], - blackMetricIdList: [], - }, - ); - - const knowledgeInfos = Object.keys(knowledgeInfosMap).reduce( - (infoList: IChatConfig.IKnowledgeInfosItem[], key: string) => { - const target = knowledgeInfosMap[key]; - if (target.searchEnable) { - infoList.push(target); - } - return infoList; - }, - [], - ); - - const params = { - ...formatEntityData, - visibility: blackIdListMap, - knowledgeInfos, - ...(globalKnowledgeConfig ? { globalKnowledgeConfig } : {}), - }; - - const { code, msg } = await saveDomainExtendQuery({ - [chatConfigKey]: params, - id, - modelId, - }); - if (code === 200) { - if (!isSilenceSubmit) { - message.success('保存成功'); - } - onSubmit?.({ isSilenceSubmit }); - return; - } - message.error(msg); - }; - - const handleTransferChange = (newTargetKeys: string[]) => { - setSelectedKeyList(newTargetKeys); - }; - - const renderFooter = () => { - return ( - <> - - - - ); - }; - - const tabItem = [ - { - label: '可见设置', - key: 'visibleSetting', - children: ( - { - setKnowledgeInfosMap(knowledgeInfos); - saveEntity({ selectedKeyList, knowledgeInfosMap: knowledgeInfos }, true); - }} - knowledgeInfosMap={knowledgeInfosMap} - titles={settingTypeConfig.titles} - sourceList={settingSourceList} - targetList={selectedKeyList} - onChange={(newTargetKeys) => { - handleTransferChange(newTargetKeys); - saveEntity({ selectedKeyList: newTargetKeys, knowledgeInfosMap }, true); - }} - /> - ), - }, - { - label: '全局维度值过滤', - key: 'dimensionValueFilter', - children: ( -
- -
- ), - }, - ]; - - return ( - <> - - { - setActiveKey(key); - }} - /> - - - ); -}; - -export default DimensionAndMetricVisibleModal; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionMetricVisibleForm.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionMetricVisibleForm.tsx deleted file mode 100644 index ac8a92f7c..000000000 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionMetricVisibleForm.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { useState, forwardRef } from 'react'; -import type { ForwardRefRenderFunction } from 'react'; -import { Form, Button } from 'antd'; -import FormItemTitle from '@/components/FormHelper/FormItemTitle'; -import { formLayout } from '@/components/FormHelper/utils'; -import DimensionAndMetricVisibleModal from './DimensionAndMetricVisibleModal'; -import { TransType } from '../../enum'; -import { wrapperTransTypeAndId } from './utils'; - -type Props = { - entityData: any; - chatConfigKey: string; - metricList: any[]; - dimensionList: any[]; - domainId: number; - onSubmit: (params?: any) => void; -}; - -const FormItem = Form.Item; - -const DimensionMetricVisibleForm: ForwardRefRenderFunction = ({ - domainId, - metricList, - dimensionList, - entityData, - chatConfigKey, - onSubmit, -}) => { - const [dimensionModalVisible, setDimensionModalVisible] = useState(false); - return ( - <> -
- - } - > - - -
- {dimensionModalVisible && ( - { - const transType = TransType.DIMENSION; - const { id } = item; - return { - ...item, - transType, - key: wrapperTransTypeAndId(transType, id), - }; - }), - ...metricList.map((item) => { - const transType = TransType.METRIC; - const { id } = item; - return { - ...item, - transType, - key: wrapperTransTypeAndId(transType, id), - }; - }), - ]} - visible={dimensionModalVisible} - onCancel={() => { - setDimensionModalVisible(false); - }} - onSubmit={(params) => { - onSubmit?.(); - if (!params?.isSilenceSubmit) { - setDimensionModalVisible(false); - } - }} - /> - )} - - ); -}; - -export default forwardRef(DimensionMetricVisibleForm); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionMetricVisibleTableTransfer.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionMetricVisibleTableTransfer.tsx index f7f169784..1dffa5992 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionMetricVisibleTableTransfer.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionMetricVisibleTableTransfer.tsx @@ -37,96 +37,96 @@ const DimensionMetricVisibleTableTransfer: React.FC = ({ onKnowledgeInfosMapChange, ...restProps }) => { - const { selectModelId: modelId } = domainManger; - const [dimensionValueSettingModalVisible, setDimensionValueSettingModalVisible] = - useState(false); - const [currentRecord, setCurrentRecord] = useState({} as RecordType); - const [currentDimensionSettingFormData, setCurrentDimensionSettingFormData] = - useState(); + // const { selectModelId: modelId } = domainManger; + // const [dimensionValueSettingModalVisible, setDimensionValueSettingModalVisible] = + // useState(false); + // const [currentRecord, setCurrentRecord] = useState({} as RecordType); + // const [currentDimensionSettingFormData, setCurrentDimensionSettingFormData] = + // useState(); - const [recordLoadingMap, setRecordLoadingMap] = useState>({}); + // const [recordLoadingMap, setRecordLoadingMap] = useState>({}); - const [taskStateMap, setTaskStateMap] = useState({}); + // const [taskStateMap, setTaskStateMap] = useState({}); - useEffect(() => { - queryDictLatestTaskList(); - }, []); + // useEffect(() => { + // queryDictLatestTaskList(); + // }, []); - const updateKnowledgeInfosMap = (record: RecordType, updateData: Record) => { - const { bizName, id } = record; - const knowledgeMap = { - ...knowledgeInfosMap, - }; - const target = knowledgeMap[bizName]; - if (target) { - knowledgeMap[bizName] = { - ...target, - ...updateData, - }; - } else { - knowledgeMap[bizName] = { - itemId: id, - bizName, - ...updateData, - }; - } - onKnowledgeInfosMapChange?.(knowledgeMap); - }; + // const updateKnowledgeInfosMap = (record: RecordType, updateData: Record) => { + // const { bizName, id } = record; + // const knowledgeMap = { + // ...knowledgeInfosMap, + // }; + // const target = knowledgeMap[bizName]; + // if (target) { + // knowledgeMap[bizName] = { + // ...target, + // ...updateData, + // }; + // } else { + // knowledgeMap[bizName] = { + // itemId: id, + // bizName, + // ...updateData, + // }; + // } + // 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 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 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('删除字典导入任务创建失败!'); - } - }; + // const deleteDictTask = async (recordData: RecordType) => { + // const { code } = await createDictTask({ + // updateMode: 'REALTIME_DELETE', + // modelAndDimPair: { + // [modelId]: [recordData.id], + // }, + // }); + // if (code !== 200) { + // message.error('删除字典导入任务创建失败!'); + // } + // }; let rightColumns: ColumnsType = [ { @@ -141,104 +141,104 @@ const DimensionMetricVisibleTableTransfer: React.FC = ({ return ; }, }, - { - dataIndex: 'y', - title: ( - - ), - width: 120, - render: (_: any, record: RecordType) => { - const { type, bizName } = record; - return type === TransType.DIMENSION ? ( - { - updateKnowledgeInfosMap(record, { searchEnable: e.target.checked }); - if (!e.target.checked) { - deleteDictTask(record); - } - }} - onClick={(event) => { - event.stopPropagation(); - }} - /> - ) : ( - <> - ); - }, - }, - { - 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, id } = record; - return type === TransType.DIMENSION ? ( - - - - - ) : ( - <> - ); - }, - }, + // { + // dataIndex: 'y', + // title: ( + // + // ), + // width: 120, + // render: (_: any, record: RecordType) => { + // const { type, bizName } = record; + // return type === TransType.DIMENSION ? ( + // { + // updateKnowledgeInfosMap(record, { searchEnable: e.target.checked }); + // if (!e.target.checked) { + // deleteDictTask(record); + // } + // }} + // onClick={(event) => { + // event.stopPropagation(); + // }} + // /> + // ) : ( + // <> + // ); + // }, + // }, + // { + // 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, id } = record; + // return type === TransType.DIMENSION ? ( + // + // + // + // + // ) : ( + // <> + // ); + // }, + // }, ]; const leftColumns: ColumnsType = [ @@ -299,7 +299,7 @@ const DimensionMetricVisibleTableTransfer: React.FC = ({ ); }} - { @@ -309,12 +309,11 @@ const DimensionMetricVisibleTableTransfer: React.FC = ({ onCancel={() => { setDimensionValueSettingModalVisible(false); }} - /> + /> */} ); }; -// export default DimensionMetricVisibleTableTransfer; export default connect(({ domainManger }: { domainManger: StateType }) => ({ domainManger, }))(DimensionMetricVisibleTableTransfer); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionValueSettingForm.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionValueSettingForm.tsx index adaf1a58a..7521fe483 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionValueSettingForm.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionValueSettingForm.tsx @@ -1,29 +1,117 @@ -import { useEffect, forwardRef, useImperativeHandle } from 'react'; +import { useState, useEffect, forwardRef, useImperativeHandle } from 'react'; import type { ForwardRefRenderFunction } from 'react'; -import { Form, Input } from 'antd'; - +import { Form, Input, Switch, Space, Button, Divider, Tooltip, message } from 'antd'; +import FormItemTitle from '@/components/FormHelper/FormItemTitle'; +import { RedoOutlined, InfoCircleOutlined } from '@ant-design/icons'; import { formLayout } from '@/components/FormHelper/utils'; +import { DictTaskState, TransType } from '../../enum'; +import { + getDomainExtendDetailConfig, + addDomainExtend, + editDomainExtend, + searchDictLatestTaskList, + createDictTask, +} from '../../service'; +import type { IChatConfig, ISemantic } from '../../data'; import { isString } from 'lodash'; import styles from '../style.less'; import CommonEditList from '../../components/CommonEditList'; + type Props = { - initialValues: any; + modelId: number; + dimensionItem: ISemantic.IDimensionItem; onSubmit?: () => void; }; +type TaskStateMap = Record; + const FormItem = Form.Item; -const EntityCreateForm: ForwardRefRenderFunction = ({ initialValues }, ref) => { +const DimensionValueSettingForm: ForwardRefRenderFunction = ( + { modelId, dimensionItem }, + ref, +) => { const [form] = Form.useForm(); const exchangeFields = ['blackList', 'whiteList']; + const [modelRichConfigData, setModelRichConfigData] = useState(); + const [dimensionVisible, setDimensionVisible] = useState(false); + const [taskStateMap, setTaskStateMap] = useState({}); + const [saveLoading, setSaveLoading] = useState(false); + const [refreshLoading, setRefreshLoading] = useState(false); + + const queryThemeListData: any = async () => { + const { code, data } = await getDomainExtendDetailConfig({ + modelId, + }); + + if (code === 200) { + setModelRichConfigData(data); + const targetKnowledgeInfos = data?.chatAggRichConfig?.knowledgeInfos || []; + const targetConfig = targetKnowledgeInfos.find( + (item: IChatConfig.IKnowledgeInfosItem) => item.itemId === dimensionItem.id, + ); + if (targetConfig) { + const { knowledgeAdvancedConfig, searchEnable } = targetConfig; + setDimensionVisible(searchEnable); + const { blackList, whiteList, ruleList } = knowledgeAdvancedConfig; + form.setFieldsValue({ + blackList: blackList.join(','), + whiteList: whiteList.join(','), + ruleList: ruleList || [], + }); + } + return; + } + + message.error('获取问答设置信息失败'); + }; + + useEffect(() => { + queryThemeListData(); + queryDictLatestTaskList(); + }, []); + + const taskRender = (dimension: ISemantic.IDimensionItem) => { + const { id, type } = dimension; + const target = taskStateMap[id]; + if (type === TransType.DIMENSION && target) { + return DictTaskState[target] || '未知状态'; + } + return '--'; + }; + + const queryDictLatestTaskList = async () => { + setRefreshLoading(true); + const { code, data } = await searchDictLatestTaskList({ + modelId, + }); + setRefreshLoading(false); + 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 getFormValidateFields = async () => { const fields = await form.validateFields(); const fieldValue = Object.keys(fields).reduce((formField, key: string) => { const targetValue = fields[key]; - if (isString(targetValue) && exchangeFields.includes(key)) { - formField[key] = targetValue.split(','); + if (exchangeFields.includes(key)) { + if (isString(targetValue)) { + formField[key] = targetValue.split(','); + } else { + formField[key] = []; + } } else { formField[key] = targetValue; } @@ -34,47 +122,201 @@ const EntityCreateForm: ForwardRefRenderFunction = ({ initialValues }; }; - useEffect(() => { - form.resetFields(); - if (!initialValues) { - return; - } - const fieldValue = Object.keys(initialValues).reduce((formField, key: string) => { - const targetValue = initialValues[key]; - if (Array.isArray(targetValue) && exchangeFields.includes(key)) { - formField[key] = targetValue.join(','); - } else { - formField[key] = targetValue; - } - return formField; - }, {}); - form.setFieldsValue({ - ...fieldValue, - }); - }, [initialValues]); - useImperativeHandle(ref, () => ({ getFormValidateFields, })); + const createDictTaskQuery = async (dimension: ISemantic.IDimensionItem) => { + const { code } = await createDictTask({ + updateMode: 'REALTIME_ADD', + modelAndDimPair: { + [modelId]: [dimension.id], + }, + }); + + if (code !== 200) { + message.error('字典导入任务创建失败!'); + return; + } + setTimeout(() => { + queryDictLatestTaskList(); + }, 2000); + }; + + const saveEntity = async (searchEnable = dimensionVisible) => { + setSaveLoading(true); + const globalKnowledgeConfigFormFields: any = await getFormValidateFields(); + const tempData = { ...modelRichConfigData }; + const targetKnowledgeInfos = modelRichConfigData?.chatAggRichConfig?.knowledgeInfos; + let knowledgeInfos: IChatConfig.IKnowledgeInfosItem[] = []; + if (Array.isArray(targetKnowledgeInfos)) { + knowledgeInfos = targetKnowledgeInfos.reduce( + ( + knowledgeInfosList: IChatConfig.IKnowledgeInfosItem[], + item: IChatConfig.IKnowledgeInfosItem, + ) => { + if (item.itemId === dimensionItem.id) { + knowledgeInfosList.push({ + ...item, + knowledgeAdvancedConfig: { + ...item.knowledgeAdvancedConfig, + ...globalKnowledgeConfigFormFields, + }, + searchEnable, + }); + } else { + knowledgeInfosList.push({ + ...item, + }); + } + return knowledgeInfosList; + }, + [], + ); + } + + const { id, modelId, chatAggRichConfig } = tempData; + const saveParams = { + id, + modelId, + chatAggConfig: { + ...chatAggRichConfig, + knowledgeInfos, + }, + }; + let saveDomainExtendQuery = addDomainExtend; + if (id) { + saveDomainExtendQuery = editDomainExtend; + } + + const { code, msg } = await saveDomainExtendQuery({ + ...saveParams, + }); + setSaveLoading(false); + if (code === 200) { + return; + } + message.error(msg); + }; + return ( <> -
- - - + + + + 维度值可见 + { + saveEntity(value); + setDimensionVisible(value); + }} + /> + + + } + subTitle={'设置可见后,维度值将在搜索时可以被联想出来'} + /> + } + > + {dimensionVisible && ( + + - - - + + + + + - - {/* */} - + {taskRender(dimensionItem)} + + )} + {dimensionVisible && ( + <> + +
+
+ 维度值过滤 + + + +
+ + + + + + + + + + + + +
+ + )} ); }; -export default forwardRef(EntityCreateForm); +export default forwardRef(DimensionValueSettingForm); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionValueSettingModal.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionValueSettingModal.tsx deleted file mode 100644 index f803f636b..000000000 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/DimensionValueSettingModal.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React, { useRef } from 'react'; -import { Button, Modal } from 'antd'; -import DimensionValueSettingForm from './DimensionValueSettingForm'; - -type Props = { - initialValues: any; - onCancel?: () => void; - visible: boolean; - onSubmit?: (params?: any) => void; -}; -const DimensionValueSettingModal: React.FC = ({ - initialValues, - visible, - onCancel, - onSubmit, -}) => { - const formRef = useRef(); - - const handleSubmit = async () => { - const formValues = await formRef.current.getFormValidateFields(); - onSubmit?.(formValues); - }; - - const renderFooter = () => { - return ( - <> - - - - ); - }; - return ( - <> - - - - - ); -}; - -export default DimensionValueSettingModal; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/EntitySection.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/EntitySection.tsx index c26295f43..9ce09d274 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/EntitySection.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/Entity/EntitySection.tsx @@ -8,7 +8,6 @@ import ProCard from '@ant-design/pro-card'; import DefaultSettingForm from './DefaultSettingForm'; import type { IChatConfig } from '../../data'; -import DimensionMetricVisibleForm from './DimensionMetricVisibleForm'; import { ChatConfigType } from '../../enum'; type Props = { @@ -19,7 +18,6 @@ type Props = { const EntitySection: React.FC = ({ domainManger, - dispatch, chatConfigType = ChatConfigType.DETAIL, }) => { const { selectDomainId, selectModelId: modelId, dimensionList, metricList } = domainManger; @@ -59,28 +57,6 @@ const EntitySection: React.FC = ({ return (
- - { - if (params.from === 'dimensionSearchVisible') { - dispatch({ - type: 'domainManger/queryDimensionList', - payload: { - domainId: selectDomainId, - }, - }); - } - queryThemeListData(); - }} - /> - = ({ }, { dataIndex: 'name', - title: '指标名称', + title: '模型名称', search: false, render: (_, record) => { return ( @@ -73,9 +73,9 @@ const ModelTable: React.FC = ({ }, { dataIndex: 'key', - title: '指标搜索', + title: '模型搜索', hideInTable: true, - renderFormItem: () => , + renderFormItem: () => , }, { dataIndex: 'alias', diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/OverView.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/OverView.tsx index ea8613b19..e116b1bde 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/OverView.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/OverView.tsx @@ -1,17 +1,9 @@ -import { CheckCard } from '@ant-design/pro-components'; -import React, { useState } from 'react'; -import { Button, Dropdown, message, Popconfirm } from 'antd'; -import { PlusOutlined, EllipsisOutlined } from '@ant-design/icons'; +import React from 'react'; import { ISemantic } from '../data'; import { connect } from 'umi'; -import icon from '../../../assets/icon/cloudEditor.svg'; import type { Dispatch } from 'umi'; import type { StateType } from '../model'; -import { formatNumber } from '../../../utils/utils'; -import { deleteModel } from '../service'; -import ModelCreateFormModal from './ModelCreateFormModal'; import ModelTable from './ModelTable'; -import styles from './style.less'; type Props = { disabledEdit?: boolean; @@ -21,136 +13,10 @@ type Props = { dispatch: Dispatch; }; -const OverView: React.FC = ({ - modelList, - disabledEdit = false, - onModelChange, - domainManger, -}) => { - // const { selectDomainId, selectModelId } = domainManger; - // const [currentModel, setCurrentModel] = useState({}); - // const [modelCreateFormModalVisible, setModelCreateFormModalVisible] = useState(false); - - // const descNode = (model: ISemantic.IDomainItem) => { - // const { metricCnt, dimensionCnt } = model; - // return ( - //
- //
- //
- //
维度数
- //
- // {formatNumber(dimensionCnt || 0)} - //
- //
- //
- //
- //
- //
指标数
- //
- // {formatNumber(metricCnt || 0)} - //
- //
- //
- //
- // ); - // }; - - // const extraNode = (model: ISemantic.IDomainItem) => { - // return ( - // { - // domEvent.stopPropagation(); - // if (key === 'edit') { - // setCurrentModel(model); - // setModelCreateFormModalVisible(true); - // } - // }, - // items: [ - // { - // label: '编辑', - // key: 'edit', - // }, - // { - // label: ( - // { - // const { code, msg } = await deleteModel(model.id); - // if (code === 200) { - // onModelChange?.(); - // } else { - // message.error(msg); - // } - // }} - // > - // 删除 - // - // ), - // key: 'delete', - // }, - // ], - // }} - // > - // e.stopPropagation()} - // /> - // - // ); - // }; - +const OverView: React.FC = ({ modelList, disabledEdit = false, onModelChange }) => { return ( -
+
- {/* {!disabledEdit && ( -
- -
- )} - - - {modelList && - modelList.map((model: ISemantic.IDomainItem) => { - return ( - { - onModelChange?.(model); - }} - /> - ); - })} - */} - {/* {modelCreateFormModalVisible && ( - { - setModelCreateFormModalVisible(false); - onModelChange?.(); - }} - onCancel={() => { - setModelCreateFormModalVisible(false); - }} - /> - )} */}
); }; 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 2657a4e6e..eb43d8a3f 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/style.less +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/style.less @@ -210,7 +210,11 @@ .classTable { :global { .ant-pro-table-search-query-filter { - padding-left: 0 !important; + // padding-left: 0 !important; + margin-bottom: 0; + } + .ant-pro-table-list-toolbar-container { + padding-top: 0; } .ant-table-tbody > tr.ant-table-row-selected > td { background: none; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/data.d.ts b/webapp/packages/supersonic-fe/src/pages/SemanticModel/data.d.ts index 8434ab62c..07c60d53a 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/data.d.ts +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/data.d.ts @@ -278,10 +278,11 @@ export declare namespace IChatConfig { interface IConfig { id: any; - domainId: number; - domainName: string; + modelId: number; + modelName: string; chatAggRichConfig: IChatRichConfig; chatDetailRichConfig: IChatRichConfig; + recommendedQuestions: { question: string }[]; bizName: string; statusEnum: string; createdBy: string; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/service.ts b/webapp/packages/supersonic-fe/src/pages/SemanticModel/service.ts index 6e60a3c14..843b79a32 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/service.ts +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/service.ts @@ -1,6 +1,4 @@ import request from 'umi-request'; -import axios from 'axios'; -import { AUTH_TOKEN_KEY } from '@/common/constants'; import moment from 'moment'; const getRunningEnv = () => { @@ -128,6 +126,16 @@ export function batchUpdateDimensionStatus(data: any): Promise { }); } +export async function batchDownloadMetric(data: any): Promise { + const response = await request.post(`${process.env.API_BASE_URL}query/download/batch`, { + responseType: 'blob', + getResponse: true, + data, + }); + + downloadStruct(response.data); +} + export function mockMetricAlias(data: any): Promise { return request.post(`${process.env.API_BASE_URL}metric/mockMetricAlias`, { data, diff --git a/webapp/packages/supersonic-fe/src/pages/System/constants.ts b/webapp/packages/supersonic-fe/src/pages/System/constants.ts new file mode 100644 index 000000000..e69de29bb diff --git a/webapp/packages/supersonic-fe/src/pages/System/index.tsx b/webapp/packages/supersonic-fe/src/pages/System/index.tsx new file mode 100644 index 000000000..6fbd8a604 --- /dev/null +++ b/webapp/packages/supersonic-fe/src/pages/System/index.tsx @@ -0,0 +1,138 @@ +import styles from './style.less'; +import { Button, Form, Input, InputNumber, message, Space, Switch, Divider } from 'antd'; +import React, { useState, useEffect } from 'react'; +import { getSystemConfig, saveSystemConfig } from '@/services/user'; +import ProCard from '@ant-design/pro-card'; +import SelectTMEPerson from '@/components/SelectTMEPerson'; +import { SystemConfigParametersItem, SystemConfig } from './types'; + +const FormItem = Form.Item; +const { TextArea } = Input; +const System: React.FC = () => { + const [systemConfig, setSystemConfig] = useState([]); + + const [configSource, setConfigSource] = useState(); + + useEffect(() => { + querySystemConfig(); + }, []); + const [form] = Form.useForm(); + const querySystemConfig = async () => { + const { code, data, msg } = await getSystemConfig(); + if (code === 200) { + const { parameters, admins } = data; + setSystemConfig(parameters); + setInitData(admins, parameters); + setConfigSource(data); + } else { + message.error(msg); + } + }; + + const setInitData = (admins: string[], systemConfig: SystemConfigParametersItem[]) => { + const fieldsValue = systemConfig.reduce( + (fields, item) => { + const { name, value } = item; + return { + ...fields, + [name]: value, + }; + }, + { admins }, + ); + form.setFieldsValue(fieldsValue); + }; + + const querySaveSystemConfig = async () => { + const submitData = await form.validateFields(); + const { code, msg } = await saveSystemConfig({ + ...configSource, + admins: submitData.admins, + parameters: systemConfig.map((item) => { + const { name } = item; + if (submitData[name] !== undefined) { + return { + ...item, + value: submitData[name], + }; + } + return item; + }), + }); + if (code === 200) { + message.success('保存成功'); + } else { + message.error(msg); + } + }; + + return ( + <> +
+ + + + } + > +
+ + + + + {systemConfig.map((item: SystemConfigParametersItem) => { + const { dataType, name, comment } = item; + let defaultItem = ; + switch (dataType) { + case 'string': + defaultItem =