[improvement][semantic-fe] Added field type and metric type to the metric creation options. (#655)

* [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.
This commit is contained in:
tristanliu
2024-01-19 14:46:41 +08:00
committed by GitHub
parent 0abbd83f51
commit 7af5afc3eb
24 changed files with 625 additions and 393 deletions

View File

@@ -74,7 +74,7 @@ const BindMeasuresTable: React.FC<CreateFormProps> = ({
<>
<Button onClick={onCancel}></Button>
<Button type="primary" onClick={handleSubmit}>
</Button>
</>
);

View File

@@ -40,7 +40,7 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({
const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState<boolean>(false);
const [dataSourceEditOpen, setDataSourceEditOpen] = useState<boolean>(false);
const [currentDatabaseId, setCurrentDatabaseId] = useState<number>();
const [scriptColumns, setScriptColumns] = useState<any[]>([]);
const [scriptColumns, setScriptColumns] = useState<IDataSource.IExecuteSqlColumn[]>([]);
useEffect(() => {
if (!dataSourceItem?.id || !open) {
@@ -74,24 +74,36 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({
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 (
<>

View File

@@ -38,9 +38,9 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
>([]);
const [dimensionValueSettingModalVisible, setDimensionValueSettingModalVisible] =
useState<boolean>(false);
const [pagination, setPagination] = useState({
const [pagination] = useState({
current: 1,
pageSize: 20,
pageSize: 99999,
total: 0,
});
@@ -54,16 +54,9 @@ const ClassDimensionTable: React.FC<Props> = ({ 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<Props> = ({ domainManger, dispatch }) => {
},
};
// const dropdownButtonItems = [
// {
// key: 'batchStart',
// label: '批量启用',
// },
// {
// key: 'batchStop',
// label: '批量停用',
// },
// {
// key: 'batchDelete',
// label: (
// <Popconfirm
// title="确定批量删除吗?"
// onConfirm={() => {
// queryBatchUpdateStatus(selectedRowKeys, StatusEnum.DELETED);
// }}
// >
// <a>批量删除</a>
// </Popconfirm>
// ),
// },
// ];
const onMenuClick = (key: string) => {
switch (key) {
case 'batchStart':
@@ -372,10 +341,8 @@ const ClassDimensionTable: React.FC<Props> = ({ 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<Props> = ({ domainManger, dispatch }) => {
type: 'checkbox',
...rowSelection,
}}
onChange={(data: any) => {
const { current, pageSize, total } = data;
setPagination({
current,
pageSize,
total,
});
}}
tableAlertRender={() => {
return false;
}}

View File

@@ -1,6 +1,6 @@
import type { ActionType, ProColumns } from '@ant-design/pro-table';
import ProTable from '@ant-design/pro-table';
import { message, Button, Space, Popconfirm, 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<Props> = ({ 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<Props> = ({ domainManger, dispatch }) => {
hideInTable: true,
renderFormItem: () => <Input placeholder="请输入ID/指标名称/英文名称/标签" />,
},
{
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 <Tag color="success"></Tag>;
case StatusEnum.OFFLINE:
return <Tag color="warning"></Tag>;
case StatusEnum.INITIALIZED:
return <Tag color="processing"></Tag>;
case StatusEnum.DELETED:
return <Tag color="default"></Tag>;
default:
return <Tag color="default"></Tag>;
}
},
},
{
dataIndex: 'createdBy',
title: '创建人',
width: 100,
search: false,
},
{
dataIndex: 'tags',
title: '标签',
search: false,
render: (tags) => {
if (Array.isArray(tags)) {
return (
<Space size={2} wrap>
{tags.map((tag) => (
<Tag color="blue" key={tag}>
{tag}
</Tag>
))}
</Space>
);
}
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<Props> = ({ 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<Props> = ({ domainManger, dispatch }) => {
total,
});
}}
size="small"
size="large"
options={{ reload: false, density: false, fullScreen: false }}
toolBarRender={() => [
<Button

View File

@@ -1,15 +1,22 @@
import { useEffect, forwardRef, useImperativeHandle, useState } from 'react';
import type { ForwardRefRenderFunction } from 'react';
import { message, Form, Input, Select, Button, Space } from 'antd';
import { saveDatabase, testDatabaseConnect } from '../../service';
import {
saveDatabase,
testDatabaseConnect,
getDatabaseParameters,
getDatabaseDetail,
} from '../../service';
import { formLayout } from '@/components/FormHelper/utils';
import SelectTMEPerson from '@/components/SelectTMEPerson';
import { ConfigParametersItem } from '../../../System/types';
import { genneratorFormItemList } from '../../utils';
import { ISemantic } from '../../data';
import styles from '../style.less';
type Props = {
domainId?: number;
dataBaseConfig?: ISemantic.IDatabaseItem;
databaseId?: number;
hideSubmitBtn?: boolean;
onSubmit?: (params?: any) => void;
};
@@ -18,21 +25,59 @@ const FormItem = Form.Item;
const TextArea = Input.TextArea;
const DatabaseCreateForm: ForwardRefRenderFunction<any, Props> = (
{ domainId, dataBaseConfig, onSubmit, hideSubmitBtn = false },
{ domainId, databaseId, onSubmit, hideSubmitBtn = false },
ref,
) => {
const [form] = Form.useForm();
const [selectedDbType, setSelectedDbType] = useState<string>('h2');
const [selectedDbType, setSelectedDbType] = useState<string>('');
const [databaseOptions, setDatabaseOptions] = useState<{ value: string; label: string }[]>([]);
const [databaseConfig, setDatabaseConfig] = useState<Record<string, ConfigParametersItem[]>>({});
const [testLoading, setTestLoading] = useState<boolean>(false);
const [dataBaseDetail, setDataBaseDetail] = useState<ISemantic.IDatabaseItem>();
useEffect(() => {
form.resetFields();
if (dataBaseConfig) {
form.setFieldsValue({ ...dataBaseConfig });
setSelectedDbType(dataBaseConfig?.type);
if (dataBaseDetail) {
form.setFieldsValue({ ...dataBaseDetail });
setSelectedDbType(dataBaseDetail?.type);
}
}, [dataBaseConfig]);
}, [dataBaseDetail]);
useEffect(() => {
if (databaseId) {
queryDatabaseDetail(databaseId);
}
}, [databaseId]);
useEffect(() => {
queryDatabaseConfig();
}, []);
const queryDatabaseDetail = async (id: number) => {
const { code, msg, data } = await getDatabaseDetail(id);
if (code === 200) {
setDataBaseDetail(data);
return;
}
message.error(msg);
};
const queryDatabaseConfig = async () => {
const { code, msg, data } = await getDatabaseParameters();
if (code === 200) {
const options = Object.keys(data).map((sqlName: string) => {
return {
value: sqlName,
label: sqlName,
};
});
setDatabaseConfig(data);
setDatabaseOptions(options);
return;
}
message.error(msg);
};
const getFormValidateFields = async () => {
return await form.validateFields();
@@ -47,7 +92,7 @@ const DatabaseCreateForm: ForwardRefRenderFunction<any, Props> = (
const saveDatabaseConfig = async () => {
const values = await form.validateFields();
const { code, msg } = await saveDatabase({
...dataBaseConfig,
...(dataBaseDetail || {}),
...values,
domainId,
});
@@ -98,14 +143,12 @@ const DatabaseCreateForm: ForwardRefRenderFunction<any, Props> = (
<Select
style={{ width: '100%' }}
placeholder="请选择数据库类型"
options={[
{ value: 'h2', label: 'h2' },
{ value: 'mysql', label: 'mysql' },
{ value: 'clickhouse', label: 'clickhouse' },
]}
options={databaseOptions}
/>
</FormItem>
{selectedDbType === 'h2' ? (
{databaseConfig[selectedDbType] && genneratorFormItemList(databaseConfig[selectedDbType])}
{/* {selectedDbType === 'h2' ? (
<FormItem name="url" label="链接" rules={[{ required: true, message: '请输入链接' }]}>
<Input placeholder="请输入链接" />
</FormItem>
@@ -139,11 +182,10 @@ const DatabaseCreateForm: ForwardRefRenderFunction<any, Props> = (
]}
/>
</FormItem>
)}
<FormItem
)} */}
{/* <FormItem
name="username"
label="用户名"
// rules={[{ required: true, message: '请输入用户名' }]}
>
<Input placeholder="请输入用户名" />
</FormItem>
@@ -152,7 +194,7 @@ const DatabaseCreateForm: ForwardRefRenderFunction<any, Props> = (
</FormItem>
<FormItem name="database" label="数据库名称">
<Input placeholder="请输入数据库名称" />
</FormItem>
</FormItem> */}
<FormItem
name="admins"
label="管理员"

View File

@@ -55,7 +55,7 @@ const DatabaseSettingModal: React.FC<CreateFormProps> = ({
return (
<Modal
width={1200}
width={600}
destroyOnClose
title="数据库连接设置"
style={{ top: 48 }}
@@ -67,7 +67,8 @@ const DatabaseSettingModal: React.FC<CreateFormProps> = ({
<DatabaseCreateForm
hideSubmitBtn={true}
ref={createFormRef}
dataBaseConfig={databaseItem}
// dataBaseConfig={databaseItem}
databaseId={databaseItem?.id}
onSubmit={() => {
onSubmit?.();
}}

View File

@@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
import { Modal, Button, message } from 'antd';
import DimensionMetricRelationTableTransfer from './DimensionMetricRelationTableTransfer';
import { ISemantic } from '../data';
import { updateExprMetric } from '../service';
import { updateMetric } from '../service';
import FormItemTitle from '@/components/FormHelper/FormItemTitle';
type Props = {
@@ -15,7 +15,7 @@ type Props = {
const DimensionAndMetricRelationModal: React.FC<Props> = ({
open,
metricItem = {},
metricItem,
relationsInitialValue,
onCancel,
onSubmit,
@@ -30,7 +30,7 @@ const DimensionAndMetricRelationModal: React.FC<Props> = ({
drillDownDimensions: relationList,
},
};
const { code, msg } = await updateExprMetric(queryParams);
const { code, msg } = await updateMetric(queryParams);
if (code === 200) {
onSubmit(relationList);
return;
@@ -45,7 +45,11 @@ const DimensionAndMetricRelationModal: React.FC<Props> = ({
<Button
type="primary"
onClick={() => {
saveMetric(relationList);
if (metricItem?.id) {
saveMetric(relationList);
} else {
onSubmit(relationList);
}
}}
>

View File

@@ -1,4 +1,5 @@
import React from 'react';
import { Space } from 'antd';
type Props = {
title: string;
@@ -15,7 +16,12 @@ const FormLabelRequire: React.FC<Props> = ({ title, labelStyles = {} }) => {
title={title}
style={{ fontSize: '16px', ...labelStyles }}
>
{title}
<Space size={5}>
<span style={{ color: '#ff4d4f', fontSize: '18px', position: 'relative', top: 3 }}>
*
</span>
{title}
</Space>
</label>
</div>
</>

View File

@@ -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<CreateFormProps> = ({
datasourceId,
domainId,
@@ -67,10 +77,31 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
const [classMeasureList, setClassMeasureList] = useState<ISemantic.IMeasure[]>([]);
const [exprTypeParamsState, setExprTypeParamsState] = useState<ISemantic.IMeasure[]>([]);
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<string>('');
// const [exprTypeParamsState, setExprTypeParamsState] = useState<ISemantic.IMeasure[]>([]);
const [defineType, setDefineType] = useState(METRIC_DEFINE_TYPE.MEASURE);
const [createNewMetricList, setCreateNewMetricList] = useState<ISemantic.IMetricItem[]>([]);
const [fieldList, setFieldList] = useState<string[]>([]);
const [isPercentState, setIsPercentState] = useState<boolean>(false);
const [isDecimalState, setIsDecimalState] = useState<boolean>(false);
const [hasMeasuresState, setHasMeasuresState] = useState<boolean>(true);
@@ -87,10 +118,13 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
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<CreateFormProps> = ({
};
useEffect(() => {
queryClassMeasureList();
queryModelDetail();
queryMetricsToCreateNewMetric();
queryMetricTags();
}, []);
@@ -115,10 +150,8 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
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<CreateFormProps> = ({
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<CreateFormProps> = ({
...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<CreateFormProps> = ({
}
}, [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<CreateFormProps> = ({
},
...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<CreateFormProps> = ({
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 (
<MetricMeasuresFormTable
datasourceId={datasourceId}
typeParams={{
measures: exprTypeParamsState,
expr: exprSql,
}}
measuresList={classMeasureList}
onFieldChange={(typeParams: any) => {
setExprTypeParamsState([...typeParams]);
}}
onSqlChange={(sql: string) => {
setExprSql(sql);
}}
/>
<div>
<div
style={{
padding: '0 0 20px 24px',
// borderBottom: '1px solid #eee',
}}
>
<Radio.Group
buttonStyle="solid"
value={defineType}
onChange={(e) => {
setDefineType(e.target.value);
}}
>
<Radio.Button value={METRIC_DEFINE_TYPE.MEASURE}></Radio.Button>
<Radio.Button value={METRIC_DEFINE_TYPE.METRIC}></Radio.Button>
<Radio.Button value={METRIC_DEFINE_TYPE.FIELD}></Radio.Button>
</Radio.Group>
</div>
{defineType === METRIC_DEFINE_TYPE.MEASURE && (
<>
<MetricMeasuresFormTable
datasourceId={datasourceId}
typeParams={exprTypeParamsState[METRIC_DEFINE_TYPE.MEASURE]}
measuresList={classMeasureList}
onFieldChange={(measures: ISemantic.IMeasure[]) => {
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 && (
<>
<p className={styles.desc}>
<Tag color="#2499ef14" className={styles.markerTag}>
</Tag>
<Tag color="#2499ef14" className={styles.markerTag}>
</Tag>
</p>
<MetricMetricFormTable
typeParams={exprTypeParamsState[METRIC_DEFINE_TYPE.METRIC]}
metricList={createNewMetricList}
onFieldChange={(metrics: ISemantic.IMetricTypeParamsItem[]) => {
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 && (
<>
<MetricFieldFormTable
typeParams={exprTypeParamsState[METRIC_DEFINE_TYPE.FIELD]}
fieldList={fieldList}
onFieldChange={(fields: ISemantic.IFieldTypeParamsItem[]) => {
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,
},
};
});
}}
/>
</>
)}
</div>
);
}
@@ -456,9 +670,9 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
return (
<Modal
forceRender
width={1300}
width={800}
style={{ top: 48 }}
styles={{ padding: '32px 40px 48px' }}
// styles={{ padding: '32px 40px 48px' }}
destroyOnClose
title={`${isEdit ? '编辑' : '新建'}指标`}
maskClosable={false}

View File

@@ -1,15 +1,16 @@
import React, { useState, useRef, useEffect } from 'react';
import { Button, Input, Space } from 'antd';
import { Button, Input, Space, Tag } from 'antd';
import ProTable from '@ant-design/pro-table';
import ProCard from '@ant-design/pro-card';
import SqlEditor from '@/components/SqlEditor';
import BindMeasuresTable from './BindMeasuresTable';
import FormLabelRequire from './FormLabelRequire';
import styles from './style.less';
import { ISemantic } from '../data';
type Props = {
datasourceId?: number;
typeParams: ISemantic.ITypeParams;
typeParams: ISemantic.IMeasureTypeParams;
measuresList: ISemantic.IMeasure[];
onFieldChange: (measures: ISemantic.IMeasure[]) => void;
onSqlChange: (sql: string) => void;
@@ -25,7 +26,6 @@ const MetricMeasuresFormTable: React.FC<Props> = ({
onSqlChange,
}) => {
const actionRef = useRef<ActionType>();
const [measuresModalVisible, setMeasuresModalVisible] = useState<boolean>(false);
const [measuresParams, setMeasuresParams] = useState(
typeParams || {
@@ -44,6 +44,7 @@ const MetricMeasuresFormTable: React.FC<Props> = ({
{
dataIndex: 'bizName',
title: '度量名称',
tooltip: '由模型名称_字段名称拼接而来',
},
{
dataIndex: 'constraint',
@@ -74,6 +75,11 @@ const MetricMeasuresFormTable: React.FC<Props> = ({
);
},
},
{
dataIndex: 'agg',
title: '聚合函数',
},
{
title: '操作',
dataIndex: 'x',
@@ -105,7 +111,6 @@ const MetricMeasuresFormTable: React.FC<Props> = ({
<ProTable
actionRef={actionRef}
headerTitle={<FormLabelRequire title="度量列表" />}
tooltip="基于本主题域下所有数据源的度量来创建指标且该列表的度量为了加以区分均已加上数据源名称作为前缀选中度量后可基于这几个度量来写表达式若是选中的度量来自不同的数据源系统将会自动join来计算该指标"
rowKey="name"
columns={columns}
dataSource={measuresParams?.measures || []}
@@ -127,8 +132,24 @@ const MetricMeasuresFormTable: React.FC<Props> = ({
/>
<ProCard
title={<FormLabelRequire title="度量表达式" />}
tooltip="度量表达式由上面选择的度量组成如选择了度量A和B则可将表达式写成A+B"
// tooltip="由于度量已自带聚合函数,因此通过度量创建指标时,表达式中无需再写聚合函数,如
// 通过度量a和度量b来创建指标由于建模的时候度量a和度量b被指定了聚合函数SUM因此创建指标时表达式只需要写成 a+b, 而不需要带聚合函数"
>
<p
className={styles.desc}
style={{ border: 'unset', padding: 0, marginBottom: 20, marginLeft: 2 }}
>
<Tag color="#2499ef14" className={styles.markerTag}>
</Tag>
<Tag color="#2499ef14" className={styles.markerTag}>
</Tag>
:
通过指定了聚合函数SUM的度量a和度量b来创建指标 a+b
</p>
<SqlEditor
value={exprString}
onChange={(sql: string) => {
@@ -149,11 +170,12 @@ const MetricMeasuresFormTable: React.FC<Props> = ({
}
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,
};
});

View File

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