From 7af5afc3eb6b43d45a875a53dc9a739b43db4de3 Mon Sep 17 00:00:00 2001 From: tristanliu <37809633+sevenliu1896@users.noreply.github.com> Date: Fri, 19 Jan 2024 14:46:41 +0800 Subject: [PATCH] [improvement][semantic-fe] Added field type and metric type to the metric creation options. (#655) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [improvement][semantic-fe] Add model alias setting & Add view permission restrictions to the model permission management tab. [improvement][semantic-fe] Add permission control to the action buttons for the main domain; apply high sensitivity filtering to the authorization of metrics/dimensions. [improvement][semantic-fe] Optimize the editing mode in the dimension/metric/datasource components to use the modelId stored in the database for data, instead of relying on the data from the state manager. * [improvement][semantic-fe] Add time granularity setting in the data source configuration. * [improvement][semantic-fe] Dictionary import for dimension values supported in Q&A visibility * [improvement][semantic-fe] Modification of data source creation prompt wording" * [improvement][semantic-fe] metric market experience optimization * [improvement][semantic-fe] enhance the analysis of metric trends * [improvement][semantic-fe] optimize the presentation of metric trend permissions * [improvement][semantic-fe] add metric trend download functionality * [improvement][semantic-fe] fix the dimension initialization issue in metric correlation * [improvement][semantic-fe] Fix the issue of database changes not taking effect when creating based on an SQL data source. * [improvement][semantic-fe] Optimizing pagination logic and some CSS styles * [improvement][semantic-fe] Fixing the API for the indicator list by changing "current" to "pageNum" * [improvement][semantic-fe] Fixing the default value setting for the indicator list * [improvement][semantic-fe] Adding batch operations for indicators/dimensions/models * [improvement][semantic-fe] Replacing the single status update API for indicators/dimensions with a batch update API * [improvement][semantic-fe] Redesigning the indicator homepage to incorporate trend charts and table functionality for indicators * [improvement][semantic-fe] Optimizing the logic for setting dimension values and editing data sources, and adding system settings functionality * [improvement][semantic-fe] Upgrading antd version to 5.x, extracting the batch operation button component, optimizing the interaction for system settings, and expanding the configuration generation types for list-to-select component. * [improvement][semantic-fe] Adding the ability to filter dimensions based on whether they are tags or not. * [improvement][semantic-fe] Adding the ability to edit relationships between models in the canvas. * [improvement][semantic-fe] Updating the datePicker component to use dayjs instead. * [improvement][semantic-fe] Fixing the issue with passing the model ID for dimensions in the indicator market. * [improvement][semantic-fe] Fixing the abnormal state of the popup when creating a model. * [improvement][semantic-fe] Adding permission logic for bulk operations in the indicator market. * [improvement][semantic-fe] Adding the ability to download and transpose data. * [improvement][semantic-fe] Fixing the initialization issue with the date selection component in the indicator details page when switching time granularity. * [improvement][semantic-fe] Fixing the logic error in the dimension value setting. * [improvement][semantic-fe] Fixing the synchronization issue with the question and answer settings information. * [improvement][semantic-fe] Optimizing the canvas functionality for better performance and user experience. * [improvement][semantic-fe] Optimizing the update process for drawing model relationship edges in the canvas. * [improvement][semantic-fe] Changing the line type for canvas connections. * [improvement][semantic-fe] Replacing the initialization variable from "semantic" to "headless". * [improvement][semantic-fe] Fixing the missing migration issue for default drill-down dimension configuration in model editing. Additionally, optimizing the data retrieval method for initializing fields in the model. * [improvement][semantic-fe] Updating the logic for the fieldName. * [improvement][semantic-fe] Adjusting the position of the metrics tab. * [improvement][semantic-fe] Changing the 字段名称 to 英文名称. * [improvement][semantic-fe] Fix metric measurement deletion. * [improvement][semantic-fe] UI optimization for metric details page. * [improvement][semantic-fe] UI optimization for metric details page. * [improvement][semantic-fe] UI adjustment for metric details page. * [improvement][semantic-fe] The granularity field in the time type of model editing now supports setting it as empty. * [improvement][semantic-fe] Added field type and metric type to the metric creation options. --- .../supersonic-fe/config/themeSettings.ts | 17 ++ webapp/packages/supersonic-fe/src/app.tsx | 13 +- .../components/FormHelper/FormItemTitle.tsx | 4 - .../RightContent/AvatarDropdown.tsx | 3 - .../components/DataSourceCreateForm.tsx | 25 +- .../components/DataSourceFieldForm.tsx | 19 +- .../src/pages/SemanticModel/Metric/Market.tsx | 65 +--- .../components/BindMeasuresTable.tsx | 2 +- .../components/ClassDataSourceTypeModal.tsx | 44 ++- .../components/ClassDimensionTable.tsx | 47 +-- .../components/ClassMetricTable.tsx | 89 ++---- .../Database/DatabaseCreateForm.tsx | 82 +++-- .../Database/DatabaseSettingModal.tsx | 5 +- .../DimensionAndMetricRelationModal.tsx | 12 +- .../components/FormLabelRequire.tsx | 8 +- .../components/MetricInfoCreateForm.tsx | 288 +++++++++++++++--- .../components/MetricMeasuresFormTable.tsx | 34 ++- .../pages/SemanticModel/components/style.less | 25 ++ .../src/pages/SemanticModel/constant.ts | 8 +- .../src/pages/SemanticModel/data.d.ts | 43 ++- .../src/pages/SemanticModel/service.ts | 22 +- .../src/pages/SemanticModel/utils.tsx | 70 +++++ .../supersonic-fe/src/pages/System/index.tsx | 87 +----- .../supersonic-fe/src/pages/System/types.ts | 6 +- 24 files changed, 625 insertions(+), 393 deletions(-) diff --git a/webapp/packages/supersonic-fe/config/themeSettings.ts b/webapp/packages/supersonic-fe/config/themeSettings.ts index bc8fd7c7e..32daca44d 100644 --- a/webapp/packages/supersonic-fe/config/themeSettings.ts +++ b/webapp/packages/supersonic-fe/config/themeSettings.ts @@ -49,4 +49,21 @@ const settings = { 'btn-disable-bg': 'rgba(0,10,36,0.04)', }; +export const configProviderTheme = { + components: { + Button: { + colorPrimary: '#3182ce', + }, + Radio: { + colorPrimary: '#3182ce', + }, + Table: { + headerBg: '#f9fafb', + headerColor: '#667085', + headerLineHeight: '38px', + headerSplitColor: '#f9fafb', + }, + }, +}; + export default settings; diff --git a/webapp/packages/supersonic-fe/src/app.tsx b/webapp/packages/supersonic-fe/src/app.tsx index 991a3e1d7..cfb5a7259 100644 --- a/webapp/packages/supersonic-fe/src/app.tsx +++ b/webapp/packages/supersonic-fe/src/app.tsx @@ -14,6 +14,7 @@ import { Copilot } from 'supersonic-chat-sdk'; import { getSystemConfig } from '@/services/user'; export { request } from './services/request'; import { ROUTE_AUTH_CODES } from '../config/routes'; +import { configProviderTheme } from '../config/themeSettings'; const replaceRoute = '/'; @@ -143,16 +144,8 @@ export const layout: RunTimeLayoutConfig = (params) => { menuHeaderRender: undefined, childrenRender: (dom: any) => { return ( - -
+
{dom} diff --git a/webapp/packages/supersonic-fe/src/components/FormHelper/FormItemTitle.tsx b/webapp/packages/supersonic-fe/src/components/FormHelper/FormItemTitle.tsx index 2e572ab1f..3cf34387a 100644 --- a/webapp/packages/supersonic-fe/src/components/FormHelper/FormItemTitle.tsx +++ b/webapp/packages/supersonic-fe/src/components/FormHelper/FormItemTitle.tsx @@ -17,16 +17,12 @@ const FormItemTitle: React.FC = ({ onSubTitleChange, }) => { return ( - //
- - //
{title}
{subTitleEditable ? ( { onSubTitleChange?.(title); }, diff --git a/webapp/packages/supersonic-fe/src/components/RightContent/AvatarDropdown.tsx b/webapp/packages/supersonic-fe/src/components/RightContent/AvatarDropdown.tsx index 2480173bf..711693a5b 100644 --- a/webapp/packages/supersonic-fe/src/components/RightContent/AvatarDropdown.tsx +++ b/webapp/packages/supersonic-fe/src/components/RightContent/AvatarDropdown.tsx @@ -4,7 +4,6 @@ import { useModel } from 'umi'; import HeaderDropdown from '../HeaderDropdown'; import styles from './index.less'; import TMEAvatar from '../TMEAvatar'; -import cx from 'classnames'; import { AUTH_TOKEN_KEY } from '@/common/constants'; import { history } from 'umi'; @@ -27,9 +26,7 @@ const { APP_TARGET } = process.env; const AvatarDropdown: React.FC = () => { const { initialState = {}, setInitialState } = useModel('@@initialState'); - const { currentUser = {} } = initialState as any; - const items = [ { label: ( 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 504a19997..3bcaf5343 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 @@ -9,7 +9,7 @@ import { updateModel, createModel, getColumns } from '../../service'; import type { Dispatch } from 'umi'; import type { StateType } from '../../model'; import { connect } from 'umi'; -import { IDataSource, ISemantic } from '../../data'; +import { ISemantic, IDataSource } from '../../data'; export type CreateFormProps = { domainManger: StateType; @@ -34,7 +34,7 @@ const initFormVal = { description: '', // 模型描述 }; -const ModelCreateForm: React.FC = ({ +const DataSourceCreateForm: React.FC = ({ domainManger, onCancel, createModalVisible, @@ -72,7 +72,9 @@ const ModelCreateForm: React.FC = ({ setHasEmptyNameField(hasEmpty); }, [fields]); - const [fieldColumns, setFieldColumns] = useState(scriptColumns || []); + const [fieldColumns, setFieldColumns] = useState( + scriptColumns || [], + ); useEffect(() => { if (scriptColumns) { setFieldColumns(scriptColumns); @@ -162,6 +164,12 @@ const ModelCreateForm: React.FC = ({ ...formValRef.current, ...fieldsValue, ...fieldsClassify, + fields: fieldColumns.map((item) => { + return { + fieldName: item.nameEn, + dataType: item.type, + }; + }), }; updateFormVal(submitForm); if (!saveState && currentStep < 1) { @@ -198,25 +206,22 @@ const ModelCreateForm: React.FC = ({ } }; - const initFields = (fieldsClassifyList: any[], columns: any[]) => { + const initFields = (fieldsClassifyList: any[], columns: IDataSource.IExecuteSqlColumn[]) => { if (Array.isArray(columns) && columns.length === 0) { setFields(fieldsClassifyList || []); return; } - const columnFields: any[] = columns.map((item: any) => { + const columnFields: any[] = columns.map((item: IDataSource.IExecuteSqlColumn) => { const { type, nameEn } = item; const oldItem = fieldsClassifyList.find((oItem) => { - // if (oItem.type === EnumDataSourceType.MEASURES) { - // return oItem.expr === item.nameEn; - // } return oItem.fieldName === item.nameEn; }) || {}; return { ...oldItem, bizName: nameEn, fieldName: nameEn, - sqlType: type, + dataType: type, }; }); setFields(columnFields || []); @@ -485,4 +490,4 @@ const ModelCreateForm: React.FC = ({ export default connect(({ domainManger }: { domainManger: StateType }) => ({ domainManger, -}))(ModelCreateForm); +}))(DataSourceCreateForm); diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/DataSourceFieldForm.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/DataSourceFieldForm.tsx index 41d1404f2..cdcfaf9a7 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/DataSourceFieldForm.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Datasource/components/DataSourceFieldForm.tsx @@ -16,7 +16,7 @@ import styles from '../style.less'; type FieldItem = { expr?: string; bizName: string; - sqlType: string; + dataType: string; name: string; type: EnumDataSourceType; agg?: string; @@ -47,7 +47,7 @@ const getCreateFieldName = (type: EnumDataSourceType) => { return isCreateName; }; -const FieldForm: React.FC = ({ fields, sql, onFieldChange, onSqlChange }) => { +const DataSourceFieldForm: React.FC = ({ fields, sql, onFieldChange, onSqlChange }) => { const handleFieldChange = (record: FieldItem, fieldName: string, value: any) => { onFieldChange(record.bizName, { ...record, @@ -61,13 +61,13 @@ const FieldForm: React.FC = ({ fields, sql, onFieldChange, onSqlChange }) dataIndex: 'fieldName', width: 100, }, - // { - // title: '数据类型', - // dataIndex: 'sqlType', - // width: 80, - // }, { title: '字段类型', + dataIndex: 'dataType', + width: 80, + }, + { + title: '语义类型', dataIndex: 'type', width: 100, render: (_: any, record: FieldItem) => { @@ -305,9 +305,6 @@ const FieldForm: React.FC = ({ fields, sql, onFieldChange, onSqlChange })
为了保障同一个模型下维度/指标列表唯一,消除歧义,若本模型下的多个数据源存在相同的字段名并且都勾选了快速创建,系统默认这些相同字段的指标维度是同一个,同时列表中将只显示第一次创建的指标/维度。
- // - // 为了保障同一个主题域下维度/指标列表唯一,消除歧义,若本主题域下的多个数据源存在相同的字段名并且都勾选了快速创建,系统默认这些相同字段的指标维度是同一个,同时列表中将只显示最后一次创建的指标/维度。 - // } /> @@ -329,4 +326,4 @@ const FieldForm: React.FC = ({ fields, sql, onFieldChange, onSqlChange }) ); }; -export default FieldForm; +export default DataSourceFieldForm; 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 57318e441..150670a32 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/Market.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/Market.tsx @@ -22,6 +22,7 @@ import styles from './style.less'; import { ISemantic } from '../data'; import BatchCtrlDropDownButton from '@/components/BatchCtrlDropDownButton'; import MetricStar from './components/MetricStar'; +import { ColumnsConfig } from '../components/MetricTableColumnRender'; type Props = { dispatch: Dispatch; @@ -166,22 +167,8 @@ const ClassMetricTable: React.FC = ({ domainManger, dispatch }) => { }, { dataIndex: 'name', - title: '指标名称', - render: (_, record: any) => { - const { id, isCollect } = record; - return ( - - - { - history.push(`/metric/detail/${record.id}`); - }} - > - {record.name} - - - ); - }, + title: '指标', + render: ColumnsConfig.metricInfo.render, }, { dataIndex: 'modelName', @@ -203,54 +190,12 @@ const ClassMetricTable: React.FC = ({ domainManger, dispatch }) => { return <> {record.modelName}; }, }, - { - dataIndex: 'sensitiveLevel', - title: '敏感度', - valueEnum: SENSITIVE_LEVEL_ENUM, - }, { dataIndex: 'status', title: '状态', width: 80, search: false, - render: (status) => { - switch (status) { - case StatusEnum.ONLINE: - return 已启用; - case StatusEnum.OFFLINE: - return 未启用; - case StatusEnum.INITIALIZED: - return 初始化; - case StatusEnum.DELETED: - return 已删除; - default: - return 未知; - } - }, - }, - { - dataIndex: 'createdBy', - title: '创建人', - search: false, - }, - { - dataIndex: 'tags', - title: '标签', - search: false, - render: (tags) => { - if (Array.isArray(tags)) { - return ( - - {tags.map((tag) => ( - - {tag} - - ))} - - ); - } - return <>--; - }, + render: ColumnsConfig.state.render, }, { dataIndex: 'description', @@ -403,6 +348,7 @@ const ClassMetricTable: React.FC = ({ domainManger, dispatch }) => { dataSource={dataSource} columns={columns} pagination={pagination} + size="large" tableAlertRender={() => { return false; }} @@ -435,7 +381,6 @@ const ClassMetricTable: React.FC = ({ domainManger, dispatch }) => { setPagination(pagin); queryMetricList({ ...pagin, ...filterParams }); }} - size="small" options={{ reload: false, density: false, fullScreen: false }} /> )} diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/BindMeasuresTable.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/BindMeasuresTable.tsx index 418b2117c..f821dd6f9 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/BindMeasuresTable.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/BindMeasuresTable.tsx @@ -74,7 +74,7 @@ const BindMeasuresTable: React.FC = ({ <> ); 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 df5e4a165..dbc247f55 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDataSourceTypeModal.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDataSourceTypeModal.tsx @@ -40,7 +40,7 @@ const ClassDataSourceTypeModal: React.FC = ({ const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false); const [dataSourceEditOpen, setDataSourceEditOpen] = useState(false); const [currentDatabaseId, setCurrentDatabaseId] = useState(); - const [scriptColumns, setScriptColumns] = useState([]); + const [scriptColumns, setScriptColumns] = useState([]); useEffect(() => { if (!dataSourceItem?.id || !open) { @@ -74,24 +74,36 @@ const ClassDataSourceTypeModal: React.FC = ({ useEffect(() => { // queryTableColumnListByScript(dataSourceItem); setSql(dataSourceItem?.modelDetail?.sqlQuery); + + const modelDetailFields = dataSourceItem?.modelDetail?.fields; + if (Array.isArray(modelDetailFields)) { + setScriptColumns( + modelDetailFields.map((item) => { + return { + nameEn: item.fieldName, + type: item.dataType, + }; + }), + ); + } }, [dataSourceItem]); - const fetchTaskResult = (params) => { - setScriptColumns(params.columns); - }; + // const fetchTaskResult = (params) => { + // setScriptColumns(params.columns); + // }; - const queryTableColumnListByScript = async (dataSource: IDataSource.IDataSourceItem) => { - if (!dataSource?.modelDetail?.sqlQuery) { - return; - } - const { code, data } = await excuteSql({ - sql: dataSource.modelDetail?.sqlQuery, - id: dataSource.databaseId, - }); - if (code === 200) { - fetchTaskResult(data); - } - }; + // const queryTableColumnListByScript = async (dataSource: IDataSource.IDataSourceItem) => { + // if (!dataSource?.modelDetail?.sqlQuery) { + // return; + // } + // const { code, data } = await excuteSql({ + // sql: dataSource.modelDetail?.sqlQuery, + // id: dataSource.databaseId, + // }); + // if (code === 200) { + // fetchTaskResult(data); + // } + // }; return ( <> 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 9fb3e5449..74295727c 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDimensionTable.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/ClassDimensionTable.tsx @@ -38,9 +38,9 @@ const ClassDimensionTable: React.FC = ({ domainManger, dispatch }) => { >([]); const [dimensionValueSettingModalVisible, setDimensionValueSettingModalVisible] = useState(false); - const [pagination, setPagination] = useState({ + const [pagination] = useState({ current: 1, - pageSize: 20, + pageSize: 99999, total: 0, }); @@ -54,16 +54,9 @@ const ClassDimensionTable: React.FC = ({ domainManger, dispatch }) => { modelId, }); setLoading(false); - const { list, pageSize, pageNum, total } = data || {}; + const { list } = data || {}; let resData: any = {}; if (code === 200) { - setPagination({ - ...pagination, - pageSize: Math.min(pageSize, 100), - current: pageNum, - total, - }); - resData = { data: list || [], success: true, @@ -327,30 +320,6 @@ const ClassDimensionTable: React.FC = ({ domainManger, dispatch }) => { }, }; - // const dropdownButtonItems = [ - // { - // key: 'batchStart', - // label: '批量启用', - // }, - // { - // key: 'batchStop', - // label: '批量停用', - // }, - // { - // key: 'batchDelete', - // label: ( - // { - // queryBatchUpdateStatus(selectedRowKeys, StatusEnum.DELETED); - // }} - // > - // 批量删除 - // - // ), - // }, - // ]; - const onMenuClick = (key: string) => { switch (key) { case 'batchStart': @@ -372,10 +341,8 @@ const ClassDimensionTable: React.FC = ({ domainManger, dispatch }) => { rowKey="id" columns={columns} request={queryDimensionList} - pagination={pagination} loading={loading} search={{ - span: 4, defaultCollapsed: false, collapseRender: () => { return <>; @@ -385,14 +352,6 @@ const ClassDimensionTable: React.FC = ({ domainManger, dispatch }) => { type: 'checkbox', ...rowSelection, }} - onChange={(data: any) => { - const { current, pageSize, total } = data; - setPagination({ - current, - pageSize, - total, - }); - }} tableAlertRender={() => { return false; }} 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 8599ba101..670d4cbda 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, Input, Tag } from 'antd'; +import { message, Button, Space, Popconfirm, Input } from 'antd'; import React, { useRef, useState } from 'react'; import type { Dispatch } from 'umi'; import { StatusEnum } from '../enum'; @@ -19,6 +19,7 @@ import BatchCtrlDropDownButton from '@/components/BatchCtrlDropDownButton'; import moment from 'moment'; import styles from './style.less'; import { ISemantic } from '../data'; +import { ColumnsConfig } from './MetricTableColumnRender'; type Props = { dispatch: Dispatch; @@ -92,16 +93,12 @@ const ClassMetricTable: React.FC = ({ domainManger, dispatch }) => { }; const columns: ProColumns[] = [ - { - dataIndex: 'id', - title: 'ID', - width: 80, - search: false, - }, { dataIndex: 'name', - title: '指标名称', + title: '指标', + width: '30%', search: false, + render: ColumnsConfig.metricInfo.render, }, { dataIndex: 'key', @@ -109,74 +106,25 @@ const ClassMetricTable: React.FC = ({ domainManger, dispatch }) => { hideInTable: true, renderFormItem: () => , }, - { - dataIndex: 'alias', - title: '别名', - width: 150, - ellipsis: true, - search: false, - }, - { - dataIndex: 'bizName', - title: '英文名称', - search: false, - }, { dataIndex: 'sensitiveLevel', title: '敏感度', - width: 80, + hideInTable: true, valueEnum: SENSITIVE_LEVEL_ENUM, }, - { - dataIndex: 'status', - title: '状态', - width: 80, - search: false, - render: (status) => { - switch (status) { - case StatusEnum.ONLINE: - return 已启用; - case StatusEnum.OFFLINE: - return 未启用; - case StatusEnum.INITIALIZED: - return 初始化; - case StatusEnum.DELETED: - return 已删除; - default: - return 未知; - } - }, - }, - { - dataIndex: 'createdBy', - title: '创建人', - width: 100, - search: false, - }, - { - dataIndex: 'tags', - title: '标签', - search: false, - render: (tags) => { - if (Array.isArray(tags)) { - return ( - - {tags.map((tag) => ( - - {tag} - - ))} - - ); - } - return <>--; - }, - }, { dataIndex: 'description', title: '描述', search: false, }, + { + dataIndex: 'status', + title: '状态', + width: 200, + search: false, + render: ColumnsConfig.state.render, + }, + { dataIndex: 'updatedAt', title: '更新时间', @@ -302,11 +250,8 @@ const ClassMetricTable: React.FC = ({ domainManger, dispatch }) => { actionRef={actionRef} rowKey="id" search={{ - span: 4, - defaultCollapsed: false, - collapseRender: () => { - return <>; - }, + optionRender: false, + collapsed: false, }} rowSelection={{ type: 'checkbox', @@ -327,7 +272,7 @@ const ClassMetricTable: React.FC = ({ domainManger, dispatch }) => { total, }); }} - size="small" + size="large" options={{ reload: false, density: false, fullScreen: false }} toolBarRender={() => [
diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/MetricInfoCreateForm.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/MetricInfoCreateForm.tsx index c04db3cfc..21c51cfeb 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/MetricInfoCreateForm.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/MetricInfoCreateForm.tsx @@ -15,19 +15,23 @@ import { Col, Space, Tooltip, + Tag, } from 'antd'; import { InfoCircleOutlined } from '@ant-design/icons'; import MetricMeasuresFormTable from './MetricMeasuresFormTable'; -import { SENSITIVE_LEVEL_OPTIONS } from '../constant'; +import { SENSITIVE_LEVEL_OPTIONS, METRIC_DEFINE_TYPE } from '../constant'; import { formLayout } from '@/components/FormHelper/utils'; import FormItemTitle from '@/components/FormHelper/FormItemTitle'; import styles from './style.less'; -import { getMeasureListByModelId, getModelDetail } from '../service'; +import { getMetricsToCreateNewMetric, getModelDetail } from '../service'; +import MetricMetricFormTable from './MetricMetricFormTable'; +import MetricFieldFormTable from './MetricFieldFormTable'; import DimensionAndMetricRelationModal from './DimensionAndMetricRelationModal'; import TableTitleTooltips from '../components/TableTitleTooltips'; -import { creatExprMetric, updateExprMetric, mockMetricAlias, getMetricTags } from '../service'; +import { createMetric, updateMetric, mockMetricAlias, getMetricTags } from '../service'; import { ISemantic } from '../data'; import { history } from 'umi'; +import { cloneDeep } from 'lodash'; export type CreateFormProps = { datasourceId?: number; @@ -44,6 +48,12 @@ const FormItem = Form.Item; const { TextArea } = Input; const { Option } = Select; +const queryParamsTypeParamsKey = { + [METRIC_DEFINE_TYPE.MEASURE]: 'metricDefineByMeasureParams', + [METRIC_DEFINE_TYPE.METRIC]: 'metricDefineByMetricParams', + [METRIC_DEFINE_TYPE.FIELD]: 'metricDefineByFieldParams', +}; + const MetricInfoCreateForm: React.FC = ({ datasourceId, domainId, @@ -67,10 +77,31 @@ const MetricInfoCreateForm: React.FC = ({ const [classMeasureList, setClassMeasureList] = useState([]); - const [exprTypeParamsState, setExprTypeParamsState] = useState([]); + const [exprTypeParamsState, setExprTypeParamsState] = useState<{ + [METRIC_DEFINE_TYPE.MEASURE]: ISemantic.IMeasureTypeParams; + [METRIC_DEFINE_TYPE.METRIC]: ISemantic.IMetricTypeParams; + [METRIC_DEFINE_TYPE.FIELD]: ISemantic.IFieldTypeParams; + }>({ + [METRIC_DEFINE_TYPE.MEASURE]: { + measures: [], + expr: '', + }, + [METRIC_DEFINE_TYPE.METRIC]: { + metrics: [], + expr: '', + }, + [METRIC_DEFINE_TYPE.FIELD]: { + fields: [], + expr: '', + }, + } as any); - const [exprSql, setExprSql] = useState(''); + // const [exprTypeParamsState, setExprTypeParamsState] = useState([]); + const [defineType, setDefineType] = useState(METRIC_DEFINE_TYPE.MEASURE); + + const [createNewMetricList, setCreateNewMetricList] = useState([]); + const [fieldList, setFieldList] = useState([]); const [isPercentState, setIsPercentState] = useState(false); const [isDecimalState, setIsDecimalState] = useState(false); const [hasMeasuresState, setHasMeasuresState] = useState(true); @@ -87,10 +118,13 @@ const MetricInfoCreateForm: React.FC = ({ const forward = () => setCurrentStep(currentStep + 1); const backward = () => setCurrentStep(currentStep - 1); - const queryClassMeasureList = async () => { + const queryModelDetail = async () => { // const { code, data } = await getMeasureListByModelId(modelId); const { code, data } = await getModelDetail({ modelId: modelId || metricItem?.modelId }); if (code === 200) { + if (Array.isArray(data?.modelDetail?.fields)) { + setFieldList(data.modelDetail.fields); + } if (Array.isArray(data?.modelDetail?.measures)) { setClassMeasureList(data.modelDetail.measures); if (datasourceId) { @@ -106,7 +140,8 @@ const MetricInfoCreateForm: React.FC = ({ }; useEffect(() => { - queryClassMeasureList(); + queryModelDetail(); + queryMetricsToCreateNewMetric(); queryMetricTags(); }, []); @@ -115,10 +150,8 @@ const MetricInfoCreateForm: React.FC = ({ const submitForm = { ...formValRef.current, ...fieldsValue, - typeParams: { - expr: exprSql, - measures: exprTypeParamsState, - }, + metricDefineType: defineType, + [queryParamsTypeParamsKey[defineType]]: exprTypeParamsState[defineType], }; updateFormVal(submitForm); if (currentStep < 1) { @@ -135,12 +168,16 @@ const MetricInfoCreateForm: React.FC = ({ bizName, description, sensitiveLevel, - typeParams: typeParams, + typeParams, dataFormat, dataFormatType, alias, tags, - } = metricItem as any; + metricDefineType, + metricDefineByMeasureParams, + metricDefineByMetricParams, + metricDefineByFieldParams, + } = metricItem; const isPercent = dataFormatType === 'percent'; const isDecimal = dataFormatType === 'decimal'; const initValue = { @@ -162,10 +199,39 @@ const MetricInfoCreateForm: React.FC = ({ ...formValRef.current, ...initValue, }; + if (metricDefineType === METRIC_DEFINE_TYPE.MEASURE) { + const { measures, expr } = metricDefineByMeasureParams || {}; + setExprTypeParamsState({ + ...exprTypeParamsState, + [METRIC_DEFINE_TYPE.MEASURE]: { + measures: measures || [], + expr: expr || '', + }, + }); + } + if (metricDefineType === METRIC_DEFINE_TYPE.METRIC) { + const { metrics, expr } = metricDefineByMetricParams || {}; + setExprTypeParamsState({ + ...exprTypeParamsState, + [METRIC_DEFINE_TYPE.METRIC]: { + metrics: metrics || [], + expr: expr || '', + }, + }); + } + if (metricDefineType === METRIC_DEFINE_TYPE.FIELD) { + const { fields, expr } = metricDefineByFieldParams || {}; + setExprTypeParamsState({ + ...exprTypeParamsState, + [METRIC_DEFINE_TYPE.FIELD]: { + fields: fields || [], + expr: expr || '', + }, + }); + } updateFormVal(editInitFormVal); form.setFieldsValue(initValue); - setExprTypeParamsState(typeParams.measures); - setExprSql(typeParams.expr); + setDefineType(metricDefineType); setIsPercentState(isPercent); setIsDecimalState(isDecimal); }; @@ -176,6 +242,37 @@ const MetricInfoCreateForm: React.FC = ({ } }, [metricItem]); + const isEmptyConditions = ( + metricDefineType: METRIC_DEFINE_TYPE, + metricDefineParams: + | ISemantic.IMeasureTypeParams + | ISemantic.IMetricTypeParams + | ISemantic.IFieldTypeParams, + ) => { + if (metricDefineType === METRIC_DEFINE_TYPE.MEASURE) { + const { measures } = (metricDefineParams as ISemantic.IMeasureTypeParams) || {}; + if (!(Array.isArray(measures) && measures.length > 0)) { + message.error('请添加一个度量'); + return true; + } + } + if (metricDefineType === METRIC_DEFINE_TYPE.METRIC) { + const { metrics } = (metricDefineParams as ISemantic.IMetricTypeParams) || {}; + if (!(Array.isArray(metrics) && metrics.length > 0)) { + message.error('请添加一个指标'); + return true; + } + } + if (metricDefineType === METRIC_DEFINE_TYPE.FIELD) { + const { fields } = (metricDefineParams as ISemantic.IFieldTypeParams) || {}; + if (!(Array.isArray(fields) && fields.length > 0)) { + message.error('请添加一个字段'); + return true; + } + } + return false; + }; + const saveMetric = async (fieldsValue: any) => { const queryParams = { modelId: isEdit ? metricItem.modelId : modelId, @@ -185,22 +282,22 @@ const MetricInfoCreateForm: React.FC = ({ }, ...fieldsValue, }; - const { typeParams, alias, dataFormatType } = queryParams; + const { alias, dataFormatType } = queryParams; queryParams.alias = Array.isArray(alias) ? alias.join(',') : ''; - if (!typeParams?.expr) { + if (!queryParams[queryParamsTypeParamsKey[defineType]]?.expr) { message.error('请输入度量表达式'); return; } if (!dataFormatType) { delete queryParams.dataFormat; } - if (!(Array.isArray(typeParams?.measures) && typeParams.measures.length > 0)) { - message.error('请添加一个度量'); + if (isEmptyConditions(defineType, queryParams[queryParamsTypeParamsKey[defineType]])) { return; } - let saveMetricQuery = creatExprMetric; + + let saveMetricQuery = createMetric; if (queryParams.id) { - saveMetricQuery = updateExprMetric; + saveMetricQuery = updateMetric; } const { code, msg } = await saveMetricQuery(queryParams); if (code === 200) { @@ -238,24 +335,141 @@ const MetricInfoCreateForm: React.FC = ({ message.error('获取指标标签失败'); } }; + const queryMetricsToCreateNewMetric = async () => { + const { code, data } = await getMetricsToCreateNewMetric({ modelId }); + if (code === 200) { + setCreateNewMetricList(data); + } else { + message.error('获取指标标签失败'); + } + }; const renderContent = () => { if (currentStep === 1) { return ( - { - setExprTypeParamsState([...typeParams]); - }} - onSqlChange={(sql: string) => { - setExprSql(sql); - }} - /> +
+
+ { + setDefineType(e.target.value); + }} + > + 按度量 + 按指标 + 按字段 + +
+ {defineType === METRIC_DEFINE_TYPE.MEASURE && ( + <> + { + setExprTypeParamsState((prevState) => { + return { + ...prevState, + [METRIC_DEFINE_TYPE.MEASURE]: { + ...prevState[METRIC_DEFINE_TYPE.MEASURE], + measures, + }, + }; + }); + }} + onSqlChange={(expr: string) => { + setExprTypeParamsState((prevState) => { + return { + ...prevState, + [METRIC_DEFINE_TYPE.MEASURE]: { + ...prevState[METRIC_DEFINE_TYPE.MEASURE], + expr, + }, + }; + }); + }} + /> + + )} + {defineType === METRIC_DEFINE_TYPE.METRIC && ( + <> +

+ 通过 + + 字段 + + 和 + + 度量 + + 创建的指标可用来创建新的指标 +

+ + { + setExprTypeParamsState((prevState) => { + return { + ...prevState, + [METRIC_DEFINE_TYPE.METRIC]: { + ...prevState[METRIC_DEFINE_TYPE.METRIC], + metrics, + }, + }; + }); + }} + onSqlChange={(expr: string) => { + setExprTypeParamsState((prevState) => { + return { + ...prevState, + [METRIC_DEFINE_TYPE.METRIC]: { + ...prevState[METRIC_DEFINE_TYPE.METRIC], + expr, + }, + }; + }); + }} + /> + + )} + {defineType === METRIC_DEFINE_TYPE.FIELD && ( + <> + { + setExprTypeParamsState((prevState) => { + return { + ...prevState, + [METRIC_DEFINE_TYPE.FIELD]: { + ...prevState[METRIC_DEFINE_TYPE.FIELD], + fields, + }, + }; + }); + }} + onSqlChange={(expr: string) => { + setExprTypeParamsState((prevState) => { + return { + ...prevState, + [METRIC_DEFINE_TYPE.FIELD]: { + ...prevState[METRIC_DEFINE_TYPE.FIELD], + expr, + }, + }; + }); + }} + /> + + )} +
); } @@ -456,9 +670,9 @@ const MetricInfoCreateForm: React.FC = ({ return ( void; onSqlChange: (sql: string) => void; @@ -25,7 +26,6 @@ const MetricMeasuresFormTable: React.FC = ({ onSqlChange, }) => { const actionRef = useRef(); - const [measuresModalVisible, setMeasuresModalVisible] = useState(false); const [measuresParams, setMeasuresParams] = useState( typeParams || { @@ -44,6 +44,7 @@ const MetricMeasuresFormTable: React.FC = ({ { dataIndex: 'bizName', title: '度量名称', + tooltip: '由模型名称_字段名称拼接而来', }, { dataIndex: 'constraint', @@ -74,6 +75,11 @@ const MetricMeasuresFormTable: React.FC = ({ ); }, }, + { + dataIndex: 'agg', + title: '聚合函数', + }, + { title: '操作', dataIndex: 'x', @@ -105,7 +111,6 @@ const MetricMeasuresFormTable: React.FC = ({ } - tooltip="基于本主题域下所有数据源的度量来创建指标,且该列表的度量为了加以区分,均已加上数据源名称作为前缀,选中度量后,可基于这几个度量来写表达式,若是选中的度量来自不同的数据源,系统将会自动join来计算该指标" rowKey="name" columns={columns} dataSource={measuresParams?.measures || []} @@ -127,8 +132,24 @@ const MetricMeasuresFormTable: React.FC = ({ /> } - tooltip="度量表达式由上面选择的度量组成,如选择了度量A和B,则可将表达式写成A+B" + // tooltip="由于度量已自带聚合函数,因此通过度量创建指标时,表达式中无需再写聚合函数,如 + // 通过度量a和度量b来创建指标,由于建模的时候度量a和度量b被指定了聚合函数SUM,因此创建指标时表达式只需要写成 a+b, 而不需要带聚合函数" > +

+ 在 + + 建模时 + + 度量已指定了 + + 聚合函数 + + ,在度量模式下,表达式无需再写聚合函数,如: + 通过指定了聚合函数SUM的度量a和度量b来创建指标,表达式只需要写成 a+b +

{ @@ -149,11 +170,12 @@ const MetricMeasuresFormTable: React.FC = ({ } selectedMeasuresList={measuresParams?.measures || []} onSubmit={async (values: any[]) => { - const measures = values.map(({ bizName, name, expr, datasourceId }) => { + const measures = values.map(({ bizName, name, expr, datasourceId, agg }) => { return { bizName, name, expr, + agg, datasourceId, }; }); 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 13e8366c2..c81fe39ac 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/style.less +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/components/style.less @@ -240,6 +240,9 @@ .ant-pro-table-search-query-filter { margin-bottom: 0; } + .ant-pro-query-filter { + padding-bottom: 0; + } .ant-pro-table-list-toolbar-container { padding-top: 0; } @@ -391,4 +394,26 @@ padding: 0px; } } +} + +.desc { + margin: 0; + padding: 25px; + color: #667085; + font-size: 14px; + border-bottom: 1px solid #eee; + border-top: 1px solid #eee; + margin-bottom: 10px; + .markerTag { + color: #2499ef; + font-size: 14px; + margin: 0 2px; + } +} + +.textLink { + color: #101828; + &:hover { + color: #69b1ff; + } } \ No newline at end of file diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/constant.ts b/webapp/packages/supersonic-fe/src/pages/SemanticModel/constant.ts index 2df495dbb..f8381c0e2 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/constant.ts +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/constant.ts @@ -37,7 +37,7 @@ export const IS_TAG_ENUM = { }; export const SENSITIVE_LEVEL_COLOR = { - [SENSITIVE_LEVEL.LOW]: 'lime', + [SENSITIVE_LEVEL.LOW]: 'geekblue', [SENSITIVE_LEVEL.MID]: 'warning', [SENSITIVE_LEVEL.HIGH]: 'error', }; @@ -71,3 +71,9 @@ export const DatePeridMap = { sys_imp_week: DateRangeType.WEEK, sys_imp_month: DateRangeType.MONTH, }; + +export enum METRIC_DEFINE_TYPE { + FIELD = 'FIELD', + MEASURE = 'MEASURE', + METRIC = 'METRIC', +} 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 c5f933a60..341d55dde 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/data.d.ts +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/data.d.ts @@ -14,6 +14,16 @@ export type SensitiveLevel = 0 | 1 | 2 | null; export type ToolBarSearchCallBack = (text: string) => void; export declare namespace IDataSource { + interface IExecuteSqlColumn { + name?: string; + type: string; + nameEn: string; + showType?: string; + authorized?: boolean; + dataFormatType?: string; + dataFormat?: string; + } + interface IIdentifiersItem { name: string; type: string; @@ -42,12 +52,17 @@ export declare namespace IDataSource { nameCh: string; isCreateMetric: number; } + + interface IDataSourceDetailFieldsItem { + dataType: string; + fieldName: string; + } interface IDataSourceDetail { queryType: string; sqlQuery: string; tableQuery: string; identifiers: IIdentifiersItem[]; - + fields: IDataSourceDetailFieldsItem[]; dimensions: IDimensionsItem[]; measures: IMeasuresItem[]; } @@ -168,11 +183,30 @@ export declare namespace ISemantic { isCreateMetric?: number; datasourceId: number; } - interface ITypeParams { + + interface IFieldTypeParamsItem { + fieldName: string; + } + + interface IMetricTypeParamsItem { + id: number; + bizName: string; + } + + interface IMeasureTypeParams { measures: IMeasure[]; expr: string; } + interface IMetricTypeParams { + expr: string; + metrics: IMetricTypeParamsItem[]; + } + interface IFieldTypeParams { + expr: string; + fields: IFieldTypeParamsItem[]; + } + interface IDrillDownDimensionItem { dimensionId: number; necessary?: boolean; @@ -201,7 +235,10 @@ export declare namespace ISemantic { hasAdminRes: boolean; type: string; tags: string[]; - typeParams: ITypeParams; + // typeParams: IMeasureTypeParams; + metricDefineByMeasureParams: IMeasureTypeParams; + metricDefineByFieldParams: IFieldTypeParams; + metricDefineByMetricParams: IMetricTypeParams; fullPath: string; dataFormatType: string; dataFormat: string; diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/service.ts b/webapp/packages/supersonic-fe/src/pages/SemanticModel/service.ts index 0db3ac6bd..d56ec61cf 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/service.ts +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/service.ts @@ -125,14 +125,14 @@ export function queryMetric(data: any): Promise { return request.post(`${process.env.API_BASE_URL}metric/queryMetric`, queryParams); } -export function creatExprMetric(data: any): Promise { - return request.post(`${process.env.API_BASE_URL}metric/creatExprMetric`, { +export function createMetric(data: any): Promise { + return request.post(`${process.env.API_BASE_URL}metric/createMetric`, { data, }); } -export function updateExprMetric(data: any): Promise { - return request.post(`${process.env.API_BASE_URL}metric/updateExprMetric`, { +export function updateMetric(data: any): Promise { + return request.post(`${process.env.API_BASE_URL}metric/updateMetric`, { data, }); } @@ -430,6 +430,12 @@ export function getModelDetail(data: any): Promise { return request.get(`${process.env.API_BASE_URL}model/getModel/${data.modelId}`); } +export function getMetricsToCreateNewMetric(data: any): Promise { + return request.get( + `${process.env.API_BASE_URL}metric/getMetricsToCreateNewMetric/${data.modelId}`, + ); +} + export function createDictTask(data: any): Promise { return request(`${process.env.CHAT_API_BASE_URL}dict/task`, { method: 'POST', @@ -546,3 +552,11 @@ export function metricStarState(data: { id: number; state: boolean }): Promise { + return request.get(`${process.env.API_BASE_URL}database/getDatabaseParameters`); +} + +export function getDatabaseDetail(id: number): Promise { + return request.get(`${process.env.API_BASE_URL}database/${id}`); +} diff --git a/webapp/packages/supersonic-fe/src/pages/SemanticModel/utils.tsx b/webapp/packages/supersonic-fe/src/pages/SemanticModel/utils.tsx index bd9a5563a..94d8ef481 100644 --- a/webapp/packages/supersonic-fe/src/pages/SemanticModel/utils.tsx +++ b/webapp/packages/supersonic-fe/src/pages/SemanticModel/utils.tsx @@ -1,6 +1,11 @@ import type { API } from '@/services/API'; import { ISemantic } from './data'; import type { DataNode } from 'antd/lib/tree'; +import { Form, Input, InputNumber, Switch, Select } from 'antd'; +import FormItemTitle from '@/components/FormHelper/FormItemTitle'; +import { ConfigParametersItem } from '../System/types'; +const FormItem = Form.Item; +const { TextArea } = Input; export const changeTreeData = (treeData: API.DomainList, auth?: boolean): DataNode[] => { return treeData.map((item: any) => { @@ -125,3 +130,68 @@ export const findLeafNodesFromDomainList = ( return leafNodes; }; + +export const genneratorFormItemList = (itemList: ConfigParametersItem[]) => { + return itemList.map((item) => { + const { dataType, name, comment, placeholder, description, require } = item; + let defaultItem = ; + switch (dataType) { + case 'string': + if (name === 'password') { + defaultItem = ; + } else { + defaultItem = ; + } + + break; + case 'longText': + defaultItem =