mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-10 11:07:06 +00:00
[improvement][headless-fe] Added view management functionality. (#701)
* [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. * [improvement][semantic-fe] The organization structure selection feature has been added to the permission management. * [improvement][semantic-fe] Improved user experience for the metric list. * [improvement][semantic-fe] fix update the metric list. * [improvement][headless-fe] Added view management functionality.
This commit is contained in:
@@ -40,13 +40,7 @@ const ROUTES = [
|
||||
name: 'semanticModel',
|
||||
envEnableList: [ENV_KEY.SEMANTIC],
|
||||
},
|
||||
{
|
||||
path: '/database',
|
||||
name: 'database',
|
||||
hideInMenu: true,
|
||||
component: './SemanticModel/components/Database/DatabaseTable',
|
||||
envEnableList: [ENV_KEY.SEMANTIC],
|
||||
},
|
||||
|
||||
{
|
||||
path: '/metric',
|
||||
name: 'metric',
|
||||
@@ -85,6 +79,13 @@ const ROUTES = [
|
||||
hideInMenu: true,
|
||||
component: './Login',
|
||||
},
|
||||
{
|
||||
path: '/database',
|
||||
name: 'database',
|
||||
// hideInMenu: true,
|
||||
component: './SemanticModel/components/Database/DatabaseTable',
|
||||
envEnableList: [ENV_KEY.SEMANTIC],
|
||||
},
|
||||
{
|
||||
path: '/system',
|
||||
name: 'system',
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { InputNumber } from 'antd';
|
||||
|
||||
const DisabledWheelNumberInput: React.FC<any> = ({ ...rest }) => {
|
||||
const ref = useRef<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
ref.current.addEventListener('wheel', handleWheel);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleWheel = (event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
return <InputNumber ref={ref} {...rest} />;
|
||||
};
|
||||
|
||||
export default DisabledWheelNumberInput;
|
||||
@@ -298,7 +298,7 @@ const DataSourceFieldForm: React.FC<Props> = ({ fields, sql, onFieldChange, onSq
|
||||
|
||||
return (
|
||||
<>
|
||||
<Alert
|
||||
{/* <Alert
|
||||
style={{ marginBottom: '10px' }}
|
||||
banner
|
||||
message={
|
||||
@@ -306,7 +306,7 @@ const DataSourceFieldForm: React.FC<Props> = ({ fields, sql, onFieldChange, onSq
|
||||
为了保障同一个模型下维度/指标列表唯一,消除歧义,若本模型下的多个数据源存在相同的字段名并且都勾选了快速创建,系统默认这些相同字段的指标维度是同一个,同时列表中将只显示第一次创建的指标/维度。
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
/> */}
|
||||
<Table<FieldItem>
|
||||
dataSource={fields}
|
||||
columns={columns}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { message } from 'antd';
|
||||
import { message, Tabs, Button, Space } from 'antd';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { getMetricData, getDimensionList, getDrillDownDimension } from '../service';
|
||||
import { connect, useParams } from 'umi';
|
||||
import { connect, useParams, history } from 'umi';
|
||||
import type { StateType } from '../model';
|
||||
import styles from './style.less';
|
||||
import { ArrowLeftOutlined } from '@ant-design/icons';
|
||||
import MetricTrendSection from '@/pages/SemanticModel/Metric/components/MetricTrendSection';
|
||||
import { ISemantic } from '../data';
|
||||
import DimensionAndMetricRelationModal from '../components/DimensionAndMetricRelationModal';
|
||||
import MetricInfoSider from './MetricInfoSider';
|
||||
import type { TabsProps } from 'antd';
|
||||
|
||||
type Props = Record<string, any>;
|
||||
|
||||
@@ -17,6 +19,9 @@ const MetricDetail: React.FC<Props> = () => {
|
||||
const [metricRelationModalOpenState, setMetricRelationModalOpenState] = useState<boolean>(false);
|
||||
const [metircData, setMetircData] = useState<ISemantic.IMetricItem>();
|
||||
const [dimensionList, setDimensionList] = useState<ISemantic.IDimensionItem[]>([]);
|
||||
const [drillDownDimension, setDrillDownDimension] = useState<ISemantic.IDrillDownDimensionItem[]>(
|
||||
[],
|
||||
);
|
||||
const [relationDimensionOptions, setRelationDimensionOptions] = useState<
|
||||
{ value: string; label: string; modelId: number }[]
|
||||
>([]);
|
||||
@@ -38,6 +43,7 @@ const MetricDetail: React.FC<Props> = () => {
|
||||
const queryDrillDownDimension = async (metricId: number) => {
|
||||
const { code, data, msg } = await getDrillDownDimension(metricId);
|
||||
if (code === 200 && Array.isArray(data)) {
|
||||
setDrillDownDimension(data);
|
||||
const ids = data.map((item) => item.dimensionId);
|
||||
queryDimensionList(ids);
|
||||
return data;
|
||||
@@ -70,15 +76,57 @@ const MetricDetail: React.FC<Props> = () => {
|
||||
return [];
|
||||
};
|
||||
|
||||
const tabItems: TabsProps['items'] = [
|
||||
{
|
||||
key: 'metricTrend',
|
||||
label: '图表',
|
||||
children: (
|
||||
<MetricTrendSection
|
||||
metircData={metircData}
|
||||
relationDimensionOptions={relationDimensionOptions}
|
||||
dimensionList={dimensionList}
|
||||
/>
|
||||
),
|
||||
},
|
||||
// {
|
||||
// key: 'metricCaliberInput',
|
||||
// label: '基础信息',
|
||||
// children: <></>,
|
||||
// },
|
||||
// {
|
||||
// key: 'metricDataRemark',
|
||||
// label: '备注',
|
||||
// children: <></>,
|
||||
// },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.metricDetailWrapper}>
|
||||
<div className={styles.metricDetail}>
|
||||
<div className={styles.tabContainer}>
|
||||
<MetricTrendSection
|
||||
metircData={metircData}
|
||||
relationDimensionOptions={relationDimensionOptions}
|
||||
dimensionList={dimensionList}
|
||||
<Tabs
|
||||
defaultActiveKey="metricTrend"
|
||||
items={tabItems}
|
||||
tabBarExtraContent={{
|
||||
right: (
|
||||
<Button
|
||||
size="middle"
|
||||
type="link"
|
||||
key="backListBtn"
|
||||
onClick={() => {
|
||||
history.push('/metric/market');
|
||||
}}
|
||||
>
|
||||
<Space>
|
||||
<ArrowLeftOutlined />
|
||||
返回列表页
|
||||
</Space>
|
||||
</Button>
|
||||
),
|
||||
}}
|
||||
size="large"
|
||||
className={styles.metricDetailTab}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.siderContainer}>
|
||||
@@ -91,20 +139,20 @@ const MetricDetail: React.FC<Props> = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DimensionAndMetricRelationModal
|
||||
metricItem={metircData}
|
||||
relationsInitialValue={drillDownDimension}
|
||||
open={metricRelationModalOpenState}
|
||||
onCancel={() => {
|
||||
setMetricRelationModalOpenState(false);
|
||||
}}
|
||||
onSubmit={(relations) => {
|
||||
queryMetricData(metricId);
|
||||
queryDrillDownDimension(metricId);
|
||||
setMetricRelationModalOpenState(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<DimensionAndMetricRelationModal
|
||||
metricItem={metircData}
|
||||
relationsInitialValue={metircData?.relateDimension?.drillDownDimensions}
|
||||
open={metricRelationModalOpenState}
|
||||
onCancel={() => {
|
||||
setMetricRelationModalOpenState(false);
|
||||
}}
|
||||
onSubmit={(relations) => {
|
||||
queryMetricData(metricId);
|
||||
queryDrillDownDimension(metricId);
|
||||
setMetricRelationModalOpenState(false);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -21,7 +21,6 @@ import moment from 'moment';
|
||||
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 = {
|
||||
@@ -161,13 +160,19 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
};
|
||||
|
||||
const columns: ProColumns[] = [
|
||||
// {
|
||||
// dataIndex: 'id',
|
||||
// title: 'ID',
|
||||
// },
|
||||
{
|
||||
dataIndex: 'id',
|
||||
title: 'ID',
|
||||
width: 80,
|
||||
fixed: 'left',
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: '指标',
|
||||
// width: '20%',
|
||||
width: 280,
|
||||
fixed: 'left',
|
||||
render: ColumnsConfig.metricInfo.render,
|
||||
},
|
||||
{
|
||||
@@ -190,16 +195,32 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
return <> {record.modelName}</>;
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'sensitiveLevel',
|
||||
title: '敏感度',
|
||||
width: 150,
|
||||
valueEnum: SENSITIVE_LEVEL_ENUM,
|
||||
render: ColumnsConfig.sensitiveLevel.render,
|
||||
},
|
||||
|
||||
{
|
||||
dataIndex: 'description',
|
||||
title: '描述',
|
||||
search: false,
|
||||
width: 300,
|
||||
render: ColumnsConfig.description.render,
|
||||
},
|
||||
{
|
||||
dataIndex: 'status',
|
||||
title: '状态',
|
||||
width: 120,
|
||||
width: 180,
|
||||
search: false,
|
||||
render: ColumnsConfig.state.render,
|
||||
},
|
||||
{
|
||||
dataIndex: 'description',
|
||||
title: '描述',
|
||||
dataIndex: 'createdBy',
|
||||
title: '创建人',
|
||||
// width: 150,
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
@@ -214,6 +235,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
title: '操作',
|
||||
dataIndex: 'x',
|
||||
valueType: 'option',
|
||||
width: 180,
|
||||
render: (_, record) => {
|
||||
if (record.hasAdminRes) {
|
||||
return (
|
||||
@@ -334,7 +356,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
metricList={dataSource}
|
||||
disabledEdit={true}
|
||||
onMetricChange={(metricItem: ISemantic.IMetricItem) => {
|
||||
history.push(`/metric/detail/${metricItem.modelId}/${metricItem.bizName}`);
|
||||
history.push(`/metric/detail/${metricItem.id}`);
|
||||
}}
|
||||
onDeleteBtnClick={(metricItem: ISemantic.IMetricItem) => {
|
||||
deleteMetricQuery(metricItem.id);
|
||||
@@ -355,6 +377,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
columns={columns}
|
||||
pagination={pagination}
|
||||
size="large"
|
||||
scroll={{ x: 1500 }}
|
||||
tableAlertRender={() => {
|
||||
return false;
|
||||
}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Tag, Space, Tooltip } from 'antd';
|
||||
import { Tag, Space, Tooltip, Typography } from 'antd';
|
||||
import React from 'react';
|
||||
import { connect } from 'umi';
|
||||
import type { StateType } from '../model';
|
||||
@@ -11,13 +11,15 @@ import {
|
||||
PartitionOutlined,
|
||||
PlusOutlined,
|
||||
AreaChartOutlined,
|
||||
DeleteOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import styles from './style.less';
|
||||
import { isString } from 'lodash';
|
||||
import { SENSITIVE_LEVEL_ENUM, SENSITIVE_LEVEL_COLOR } from '../constant';
|
||||
import { ISemantic } from '../data';
|
||||
import MetricStar from './components/MetricStar';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
type Props = {
|
||||
metircData: ISemantic.IMetricItem;
|
||||
domainManger: StateType;
|
||||
@@ -40,7 +42,6 @@ const MetricInfoSider: React.FC<Props> = ({
|
||||
<Space>
|
||||
<MetricStar metricId={metircData?.id} initState={metircData?.isCollect} />
|
||||
{metircData?.name}
|
||||
{metircData?.alias && `[${metircData.alias}]`}
|
||||
{metircData?.hasAdminRes && (
|
||||
<span
|
||||
className={styles.gotoMetricListIcon}
|
||||
@@ -105,6 +106,44 @@ const MetricInfoSider: React.FC<Props> = ({
|
||||
</Space>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{isArrayOfValues(metircData?.tags) && (
|
||||
<div className={styles.item}>
|
||||
<span className={styles.itemLable}>别名: </span>
|
||||
<span className={styles.itemValue}>
|
||||
<Space size={2} wrap>
|
||||
{isString(metircData?.alias) &&
|
||||
metircData?.alias.split(',').map((aliasName: string) => {
|
||||
return (
|
||||
<Tag
|
||||
color="#eee"
|
||||
key={aliasName}
|
||||
style={{
|
||||
borderRadius: 44,
|
||||
maxWidth: 90,
|
||||
minWidth: 40,
|
||||
backgroundColor: 'rgba(18, 31, 67, 0.04)',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
maxWidth: 80,
|
||||
color: 'rgb(95, 116, 141)',
|
||||
textAlign: 'center',
|
||||
fontSize: 12,
|
||||
}}
|
||||
ellipsis={{ tooltip: aliasName }}
|
||||
>
|
||||
{aliasName}
|
||||
</Text>
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles.item}>
|
||||
<span className={styles.itemLable}>描述: </span>
|
||||
<span className={styles.itemValue}>{metircData?.description}</span>
|
||||
|
||||
@@ -98,11 +98,11 @@ const MetricFilter: React.FC<Props> = ({ initFilterValues = {}, onFiltersChange
|
||||
</div>
|
||||
</StandardFormRow>
|
||||
<Space size={40}>
|
||||
<StandardFormRow key="showType" title="切换为卡片" block>
|
||||
{/* <StandardFormRow key="showType" title="切换为卡片" block>
|
||||
<FormItem name="showType" valuePropName="checked">
|
||||
<Switch size="small" />
|
||||
</FormItem>
|
||||
</StandardFormRow>
|
||||
</StandardFormRow> */}
|
||||
{/* <StandardFormRow key="onlyShowMe" title="仅显示我的" block>
|
||||
<FormItem name="onlyShowMe" valuePropName="checked">
|
||||
<Switch size="small" />
|
||||
|
||||
@@ -5,15 +5,17 @@ import RemoteSelect, { RemoteSelectImperativeHandle } from '@/components/RemoteS
|
||||
import { queryDimValue } from '@/pages/SemanticModel/service';
|
||||
import { OperatorEnum } from '@/pages/SemanticModel/enum';
|
||||
import { isString } from 'lodash';
|
||||
import { isArrayOfValues } from '@/utils/utils';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
type Props = {
|
||||
dimensionOptions: OptionsItem[];
|
||||
dimensionOptions: (OptionsItem & { modelId: number })[];
|
||||
modelId: number;
|
||||
periodDate?: { startDate: string; endDate: string; dateField: string };
|
||||
value?: FormData;
|
||||
onChange?: (value: FormData) => void;
|
||||
afterSolt?: React.ReactNode;
|
||||
};
|
||||
|
||||
export type FormData = {
|
||||
@@ -24,16 +26,15 @@ export type FormData = {
|
||||
|
||||
const MetricTrendDimensionFilter: React.FC<Props> = ({
|
||||
dimensionOptions,
|
||||
modelId,
|
||||
value,
|
||||
periodDate,
|
||||
afterSolt,
|
||||
onChange,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const dimensionValueSearchRef = useRef<RemoteSelectImperativeHandle>();
|
||||
const queryParams = useRef<{ dimensionBizName?: string }>({});
|
||||
const [formData, setFormData] = useState<FormData>({ operator: OperatorEnum.IN } as FormData);
|
||||
|
||||
useEffect(() => {
|
||||
if (!value) {
|
||||
return;
|
||||
@@ -59,7 +60,10 @@ const MetricTrendDimensionFilter: React.FC<Props> = ({
|
||||
return;
|
||||
}
|
||||
const { dimensionBizName } = queryParams.current;
|
||||
const targetOptions = dimensionOptions.find((item) => item.value === dimensionBizName) || {};
|
||||
const targetOptions = dimensionOptions.find((item) => item.value === dimensionBizName);
|
||||
if (!targetOptions) {
|
||||
return;
|
||||
}
|
||||
const { code, data } = await queryDimValue({
|
||||
...queryParams.current,
|
||||
value: searchValue,
|
||||
@@ -95,7 +99,6 @@ const MetricTrendDimensionFilter: React.FC<Props> = ({
|
||||
}}
|
||||
onValuesChange={(value, values) => {
|
||||
const { operator, dimensionValue } = values;
|
||||
|
||||
if (multipleValueOperator.includes(operator) && isString(dimensionValue)) {
|
||||
const tempDimensionValue = [dimensionValue];
|
||||
setFormData({ ...values, dimensionValue: tempDimensionValue });
|
||||
@@ -152,12 +155,14 @@ const MetricTrendDimensionFilter: React.FC<Props> = ({
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
disabled={!(formData.dimensionBizName && isArrayOfValues(formData.dimensionValue))}
|
||||
onClick={() => {
|
||||
const formValues = form.getFieldsValue();
|
||||
onChange?.(formValues);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
{afterSolt}
|
||||
</Space>
|
||||
</Form>
|
||||
);
|
||||
|
||||
@@ -4,11 +4,12 @@ import MetricTrendDimensionFilter from './MetricTrendDimensionFilter';
|
||||
import type { FormData } from './MetricTrendDimensionFilter';
|
||||
|
||||
type Props = {
|
||||
dimensionOptions: OptionsItem[];
|
||||
dimensionOptions: (OptionsItem & { modelId: number })[];
|
||||
modelId: number;
|
||||
value?: FormData;
|
||||
periodDate?: { startDate: string; endDate: string; dateField: string };
|
||||
onChange?: (value: FormData[]) => void;
|
||||
afterSolt?: React.ReactNode;
|
||||
};
|
||||
|
||||
type DimensionOptionsMapItem = {
|
||||
@@ -20,6 +21,7 @@ const MetricTrendDimensionFilterContainer: React.FC<Props> = ({
|
||||
dimensionOptions,
|
||||
modelId,
|
||||
periodDate,
|
||||
afterSolt,
|
||||
value,
|
||||
onChange,
|
||||
}) => {
|
||||
@@ -56,6 +58,7 @@ const MetricTrendDimensionFilterContainer: React.FC<Props> = ({
|
||||
setFilterData(data);
|
||||
onChange?.(data);
|
||||
}}
|
||||
afterSolt={afterSolt}
|
||||
/>
|
||||
<Space size={8} wrap style={{ marginTop: 10 }}>
|
||||
{filterData.map((item: FormData, index: number) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { message, Row, Col, Button, Space, Select, Form, Tooltip } from 'antd';
|
||||
import { queryStruct } from '@/pages/SemanticModel/service';
|
||||
import { DownloadOutlined, PoweroffOutlined, ArrowLeftOutlined } from '@ant-design/icons';
|
||||
import { DownloadOutlined, PoweroffOutlined, SearchOutlined } from '@ant-design/icons';
|
||||
import TrendChart from '@/pages/SemanticModel/Metric/components/MetricTrend';
|
||||
import MetricTrendDimensionFilterContainer from './MetricTrendDimensionFilterContainer';
|
||||
import MDatePicker from '@/components/MDatePicker';
|
||||
@@ -11,7 +11,6 @@ import StandardFormRow from '@/components/StandardFormRow';
|
||||
import MetricTable from './Table';
|
||||
import { ColumnConfig } from '../data';
|
||||
import dayjs from 'dayjs';
|
||||
import { history } from 'umi';
|
||||
import { ISemantic } from '../../data';
|
||||
import { DateFieldMap } from '@/pages/SemanticModel/constant';
|
||||
import ProCard from '@ant-design/pro-card';
|
||||
@@ -119,6 +118,9 @@ const MetricTrendSection: React.FC<Props> = ({
|
||||
setMetricColumnConfig(targetConfig);
|
||||
}
|
||||
setDownloadBtnDisabledState(false);
|
||||
if (dimensionGroup[dimensionGroup.length - 1]) {
|
||||
setGroupByDimensionFieldName(dimensionGroup[dimensionGroup.length - 1]);
|
||||
}
|
||||
} else {
|
||||
if (code === 401 || code === 400) {
|
||||
setAuthMessage(msg);
|
||||
@@ -135,7 +137,7 @@ const MetricTrendSection: React.FC<Props> = ({
|
||||
if (metircData?.id) {
|
||||
getMetricTrendData({ ...queryParams });
|
||||
}
|
||||
}, [metircData, periodDate]);
|
||||
}, [metircData]);
|
||||
|
||||
return (
|
||||
<div className={styles.metricTrendSection}>
|
||||
@@ -186,8 +188,6 @@ const MetricTrendSection: React.FC<Props> = ({
|
||||
onChange={(value) => {
|
||||
const params = { ...queryParams, dimensionGroup: value || [] };
|
||||
setQueryParams(params);
|
||||
getMetricTrendData({ ...params });
|
||||
setGroupByDimensionFieldName(value[value.length - 1]);
|
||||
}}
|
||||
/>
|
||||
</FormItem>
|
||||
@@ -212,8 +212,20 @@ const MetricTrendSection: React.FC<Props> = ({
|
||||
dimensionFilters,
|
||||
};
|
||||
setQueryParams(params);
|
||||
getMetricTrendData({ ...params });
|
||||
}}
|
||||
afterSolt={
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SearchOutlined />}
|
||||
size="middle"
|
||||
loading={metricTrendLoading}
|
||||
onClick={() => {
|
||||
getMetricTrendData({ ...queryParams });
|
||||
}}
|
||||
>
|
||||
查 询
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</FormItem>
|
||||
</StandardFormRow>
|
||||
@@ -221,31 +233,21 @@ const MetricTrendSection: React.FC<Props> = ({
|
||||
</Col>
|
||||
<Col flex="0 1" />
|
||||
</Row>
|
||||
<Button
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 20,
|
||||
right: 20,
|
||||
}}
|
||||
size="middle"
|
||||
type="link"
|
||||
key="backListBtn"
|
||||
onClick={() => {
|
||||
history.push('/metric/market');
|
||||
}}
|
||||
>
|
||||
<Space>
|
||||
<ArrowLeftOutlined />
|
||||
返回列表页
|
||||
</Space>
|
||||
</Button>
|
||||
{/* <div className={styles.btnWrapper}>
|
||||
{/* <Row style={{ paddingLeft: 82, paddingBottom: 8 }}>
|
||||
|
||||
</div> */}
|
||||
</Row> */}
|
||||
</div>
|
||||
{authMessage && <div style={{ color: '#d46b08', marginBottom: 15 }}>{authMessage}</div>}
|
||||
|
||||
<div className={styles.sectionBox}>
|
||||
<ProCard size="small" title="数据趋势">
|
||||
<ProCard
|
||||
size="small"
|
||||
title={
|
||||
<>
|
||||
<span>数据趋势</span>
|
||||
{authMessage && <div style={{ color: '#d46b08' }}>{authMessage}</div>}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<TrendChart
|
||||
data={metricTrendData}
|
||||
isPer={
|
||||
@@ -272,7 +274,7 @@ const MetricTrendSection: React.FC<Props> = ({
|
||||
</ProCard>
|
||||
</div>
|
||||
|
||||
<div className={styles.sectionBox}>
|
||||
<div className={styles.sectionBox} style={{ paddingBottom: 0 }}>
|
||||
<ProCard
|
||||
size="small"
|
||||
title="数据明细"
|
||||
@@ -307,7 +309,7 @@ const MetricTrendSection: React.FC<Props> = ({
|
||||
</Space.Compact>
|
||||
}
|
||||
>
|
||||
<div style={{ minHeight: '528px' }}>
|
||||
<div style={{ minHeight: '450px' }}>
|
||||
<MetricTable
|
||||
loading={metricTrendLoading}
|
||||
columnConfig={tableColumnConfig}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Table } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import moment from 'moment';
|
||||
import styles from '../style.less';
|
||||
import { ColumnConfig } from '../data';
|
||||
|
||||
type Props = {
|
||||
@@ -57,16 +56,15 @@ const MetricTable: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<div style={{ height: '100%' }}>
|
||||
{Array.isArray(columns) && columns.length > 0 && (
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={dataSource}
|
||||
scroll={{ x: 200, y: 700 }}
|
||||
// pagination={{ defaultPageSize: 20 }}
|
||||
loading={loading}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
)}
|
||||
{/* {Array.isArray(columns) && columns.length > 0 && ( */}
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={dataSource}
|
||||
scroll={{ x: 200, y: 700 }}
|
||||
loading={loading}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
{/* )} */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -121,6 +121,23 @@
|
||||
.metricDetailWrapper {
|
||||
height: calc(100vh - 56px);
|
||||
overflow: scroll;
|
||||
.metricDetailTab {
|
||||
:global {
|
||||
.ant-tabs-nav {
|
||||
background-color: rgb(255, 255, 255);
|
||||
transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
|
||||
margin: 10px 20px 0 20px;
|
||||
padding: 0 20px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.ant-tabs-tab {
|
||||
padding: 12px 0;
|
||||
color: #344767;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.metricDetail {
|
||||
display: flex;
|
||||
padding: 0px;
|
||||
@@ -158,27 +175,27 @@
|
||||
.siderContainer {
|
||||
background-color: rgb(255, 255, 255);
|
||||
width: 450px;
|
||||
min-height: 100vh;
|
||||
margin: 20px 20px 20px 0;
|
||||
min-height: calc(100vh - 78px);
|
||||
margin: 10px 20px 20px 0;
|
||||
border-radius: 6px;
|
||||
box-shadow: rgba(0, 0, 0, 0.08) 6px 0px 16px 0px, rgba(0, 0, 0, 0.12) 3px 0px 6px -4px, rgba(0, 0, 0, 0.05) 9px 0px 28px 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.metricTrendSection {
|
||||
.sectionBox {
|
||||
margin: 20px;
|
||||
padding: 10px;
|
||||
box-shadow: #888888 0px 0px 1px, rgba(29, 41, 57, 0.08) 0px 1px 3px;
|
||||
background-color: rgb(255, 255, 255);
|
||||
transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
|
||||
border-radius: 6px;
|
||||
background-image: none;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sectionBox {
|
||||
margin: 20px;
|
||||
padding: 10px;
|
||||
box-shadow: #888888 0px 0px 1px, rgba(29, 41, 57, 0.08) 0px 1px 3px;
|
||||
background-color: rgb(255, 255, 255);
|
||||
transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
|
||||
border-radius: 6px;
|
||||
background-image: none;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
.metricInfoSider{
|
||||
padding: 24px;
|
||||
color: #344767;
|
||||
|
||||
@@ -103,12 +103,14 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
const filterData = dataSourceRef.current.reduce(
|
||||
(data: ISemantic.IDomainSchemaRelaList, item: ISemantic.IDomainSchemaRelaItem) => {
|
||||
const { dimensions, metrics } = item;
|
||||
const dimensionsList = dimensions.filter((dimension) => {
|
||||
return dimension.name.includes(text);
|
||||
});
|
||||
const metricsList = metrics.filter((metric) => {
|
||||
return metric.name.includes(text);
|
||||
});
|
||||
const dimensionsList =
|
||||
dimensions?.filter((dimension) => {
|
||||
return dimension.name.includes(text);
|
||||
}) || [];
|
||||
const metricsList =
|
||||
metrics?.filter((metric) => {
|
||||
return metric.name.includes(text);
|
||||
}) || [];
|
||||
data.push({
|
||||
...item,
|
||||
dimensions: dimensionsList,
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import { ISemantic } from '../../data';
|
||||
|
||||
import { TransType } from '../../enum';
|
||||
import DimensionMetricVisibleTransfer from '../../components/Entity/DimensionMetricVisibleTransfer';
|
||||
import { wrapperTransTypeAndId } from '../../components/Entity/utils';
|
||||
|
||||
export type ModelCreateFormModalProps = {
|
||||
dimensionList: ISemantic.IDimensionItem[];
|
||||
metricList: ISemantic.IMetricItem[];
|
||||
modelId?: number;
|
||||
selectedTransferKeys: React.Key[];
|
||||
onCancel: () => void;
|
||||
onSubmit: (values: any, selectedKeys: React.Key[]) => void;
|
||||
};
|
||||
|
||||
const DimensionMetricTransferModal: React.FC<ModelCreateFormModalProps> = ({
|
||||
modelId,
|
||||
selectedTransferKeys,
|
||||
metricList,
|
||||
dimensionList,
|
||||
onSubmit,
|
||||
}) => {
|
||||
const [sourceList, setSourceList] = useState<any[]>([]);
|
||||
const [selectedItemList, setSelectedItemList] = useState<any[]>([]);
|
||||
|
||||
const addItemKey = (item: any, transType: TransType) => {
|
||||
const { id } = item;
|
||||
const key = wrapperTransTypeAndId(transType, id);
|
||||
return {
|
||||
...item,
|
||||
transType,
|
||||
key,
|
||||
};
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const sourceDimensionList = dimensionList.reduce((mergeList: any[], item) => {
|
||||
mergeList.push(addItemKey(item, TransType.DIMENSION));
|
||||
return mergeList;
|
||||
}, []);
|
||||
|
||||
const hasDimensionList = selectedItemList
|
||||
.filter((item) => {
|
||||
return item.typeEnum === TransType.DIMENSION;
|
||||
})
|
||||
.reduce((modelDimensionList: ISemantic.IDimensionItem[], item) => {
|
||||
const hasItem = sourceDimensionList.find((dataListItem: ISemantic.IDimensionItem) => {
|
||||
return dataListItem.id === item.id;
|
||||
});
|
||||
if (!hasItem) {
|
||||
modelDimensionList.push(addItemKey(item, TransType.DIMENSION));
|
||||
}
|
||||
return modelDimensionList;
|
||||
}, []);
|
||||
|
||||
const sourceMetricList = metricList.reduce((mergeList: any[], item) => {
|
||||
mergeList.push(addItemKey(item, TransType.METRIC));
|
||||
return mergeList;
|
||||
}, []);
|
||||
|
||||
const hasMetricList = selectedItemList
|
||||
.filter((item) => {
|
||||
return item.typeEnum === TransType.METRIC;
|
||||
})
|
||||
.reduce((modelMetricList: ISemantic.IMetricItem[], item) => {
|
||||
const hasItem = sourceMetricList.find((dataListItem: ISemantic.IMetricItem) => {
|
||||
return dataListItem.id === item.id;
|
||||
});
|
||||
if (!hasItem) {
|
||||
modelMetricList.push(addItemKey(item, TransType.METRIC));
|
||||
}
|
||||
return modelMetricList;
|
||||
}, []);
|
||||
|
||||
setSourceList([
|
||||
...sourceDimensionList,
|
||||
...sourceMetricList,
|
||||
...hasDimensionList,
|
||||
...hasMetricList,
|
||||
]);
|
||||
}, [dimensionList, metricList]);
|
||||
|
||||
return (
|
||||
<DimensionMetricVisibleTransfer
|
||||
titles={['未关联维度/指标', '已关联维度/指标']}
|
||||
listStyle={{
|
||||
width: 520,
|
||||
height: 600,
|
||||
}}
|
||||
targetList={selectedTransferKeys}
|
||||
sourceList={sourceList}
|
||||
onChange={(newTargetKeys: string[]) => {
|
||||
const removeDimensionList: ISemantic.IDimensionItem[] = [];
|
||||
const removeMetricList: ISemantic.IMetricItem[] = [];
|
||||
const dimensionItemChangeList = dimensionList.reduce(
|
||||
(dimensionChangeList: any[], item: any) => {
|
||||
if (newTargetKeys.includes(wrapperTransTypeAndId(TransType.DIMENSION, item.id))) {
|
||||
dimensionChangeList.push(item);
|
||||
} else {
|
||||
removeDimensionList.push(item.id);
|
||||
}
|
||||
return dimensionChangeList;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const metricItemChangeList = metricList.reduce((metricChangeList: any[], item: any) => {
|
||||
if (newTargetKeys.includes(wrapperTransTypeAndId(TransType.METRIC, item.id))) {
|
||||
metricChangeList.push(item);
|
||||
} else {
|
||||
removeMetricList.push(item.id);
|
||||
}
|
||||
return metricChangeList;
|
||||
}, []);
|
||||
|
||||
setSelectedItemList([...dimensionItemChangeList, ...metricItemChangeList]);
|
||||
|
||||
// 如果不是当前选中model中的指标或者维度,则先从本地数据中删除,避免后续请求数据更新时产生视觉上的界面闪烁
|
||||
const preUpdateSourceData = sourceList.filter((item) => {
|
||||
const { typeEnum, id } = item;
|
||||
if (typeEnum === TransType.DIMENSION) {
|
||||
if (modelId !== item.modelId && removeDimensionList.includes(id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (typeEnum === TransType.METRIC) {
|
||||
if (modelId !== item.modelId && removeMetricList.includes(id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
setSourceList([...preUpdateSourceData]);
|
||||
|
||||
const viewModelConfigs = [...dimensionItemChangeList, ...metricItemChangeList].reduce(
|
||||
(config, item) => {
|
||||
const { modelId, id, typeEnum } = item;
|
||||
if (config[modelId]) {
|
||||
if (typeEnum === TransType.DIMENSION) {
|
||||
config[modelId].dimensions.push(id);
|
||||
}
|
||||
if (typeEnum === TransType.METRIC) {
|
||||
config[modelId].metrics.push(id);
|
||||
}
|
||||
} else {
|
||||
config[modelId] = {
|
||||
id: modelId,
|
||||
metrics: typeEnum === TransType.METRIC ? [id] : [],
|
||||
dimensions: typeEnum === TransType.DIMENSION ? [id] : [],
|
||||
};
|
||||
}
|
||||
return config;
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
onSubmit?.(viewModelConfigs, newTargetKeys);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DimensionMetricTransferModal;
|
||||
@@ -0,0 +1,773 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
Form,
|
||||
Button,
|
||||
Modal,
|
||||
Steps,
|
||||
Input,
|
||||
Select,
|
||||
Radio,
|
||||
Switch,
|
||||
InputNumber,
|
||||
message,
|
||||
Result,
|
||||
Row,
|
||||
Col,
|
||||
Space,
|
||||
Tooltip,
|
||||
Tag,
|
||||
} from 'antd';
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import MetricMeasuresFormTable from './MetricMeasuresFormTable';
|
||||
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 { getMetricsToCreateNewMetric, getModelDetail, getDrillDownDimension } from '../service';
|
||||
import MetricMetricFormTable from './MetricMetricFormTable';
|
||||
import MetricFieldFormTable from './MetricFieldFormTable';
|
||||
import DimensionAndMetricRelationModal from './DimensionAndMetricRelationModal';
|
||||
import TableTitleTooltips from '../components/TableTitleTooltips';
|
||||
import { createMetric, updateMetric, mockMetricAlias, getMetricTags } from '../service';
|
||||
import { ISemantic } from '../data';
|
||||
import { history } from 'umi';
|
||||
|
||||
export type CreateFormProps = {
|
||||
datasourceId?: number;
|
||||
domainId: number;
|
||||
modelId: number;
|
||||
createModalVisible: boolean;
|
||||
metricItem: any;
|
||||
onCancel?: () => void;
|
||||
onSubmit?: (values: any) => void;
|
||||
};
|
||||
|
||||
const { Step } = Steps;
|
||||
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,
|
||||
modelId,
|
||||
onCancel,
|
||||
createModalVisible,
|
||||
metricItem,
|
||||
onSubmit,
|
||||
}) => {
|
||||
const isEdit = !!metricItem?.id;
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
const formValRef = useRef({} as any);
|
||||
const [form] = Form.useForm();
|
||||
const updateFormVal = (val: any) => {
|
||||
const formVal = {
|
||||
...formValRef.current,
|
||||
...val,
|
||||
};
|
||||
formValRef.current = formVal;
|
||||
};
|
||||
|
||||
const [classMeasureList, setClassMeasureList] = 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 [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);
|
||||
const [llmLoading, setLlmLoading] = useState<boolean>(false);
|
||||
|
||||
const [tagOptions, setTagOptions] = useState<{ label: string; value: string }[]>([]);
|
||||
|
||||
const [metricRelationModalOpenState, setMetricRelationModalOpenState] = useState<boolean>(false);
|
||||
|
||||
const [drillDownDimensions, setDrillDownDimensions] = useState<
|
||||
ISemantic.IDrillDownDimensionItem[]
|
||||
>([]);
|
||||
|
||||
const [drillDownDimensionsConfig, setDrillDownDimensionsConfig] = useState<
|
||||
ISemantic.IDrillDownDimensionItem[]
|
||||
>([]);
|
||||
|
||||
const forward = () => setCurrentStep(currentStep + 1);
|
||||
const backward = () => setCurrentStep(currentStep - 1);
|
||||
|
||||
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) {
|
||||
const hasMeasures = data.some(
|
||||
(item: ISemantic.IMeasure) => item.datasourceId === datasourceId,
|
||||
);
|
||||
setHasMeasuresState(hasMeasures);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
setClassMeasureList([]);
|
||||
};
|
||||
|
||||
const queryDrillDownDimension = async (metricId: number) => {
|
||||
const { code, data, msg } = await getDrillDownDimension(metricId);
|
||||
if (code === 200 && Array.isArray(data)) {
|
||||
setDrillDownDimensionsConfig(data);
|
||||
}
|
||||
if (code !== 200) {
|
||||
message.error(msg);
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
queryModelDetail();
|
||||
queryMetricsToCreateNewMetric();
|
||||
queryMetricTags();
|
||||
}, []);
|
||||
|
||||
const handleNext = async () => {
|
||||
const fieldsValue = await form.validateFields();
|
||||
const submitForm = {
|
||||
...formValRef.current,
|
||||
...fieldsValue,
|
||||
metricDefineType: defineType,
|
||||
[queryParamsTypeParamsKey[defineType]]: exprTypeParamsState[defineType],
|
||||
};
|
||||
updateFormVal(submitForm);
|
||||
if (currentStep < 1) {
|
||||
forward();
|
||||
} else {
|
||||
await saveMetric(submitForm);
|
||||
}
|
||||
};
|
||||
|
||||
const initData = () => {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
bizName,
|
||||
description,
|
||||
sensitiveLevel,
|
||||
typeParams,
|
||||
dataFormat,
|
||||
dataFormatType,
|
||||
alias,
|
||||
tags,
|
||||
metricDefineType,
|
||||
metricDefineByMeasureParams,
|
||||
metricDefineByMetricParams,
|
||||
metricDefineByFieldParams,
|
||||
} = metricItem;
|
||||
const isPercent = dataFormatType === 'percent';
|
||||
const isDecimal = dataFormatType === 'decimal';
|
||||
const initValue = {
|
||||
id,
|
||||
name,
|
||||
bizName,
|
||||
sensitiveLevel,
|
||||
description,
|
||||
tags,
|
||||
// isPercent,
|
||||
dataFormatType: dataFormatType || '',
|
||||
alias: alias && alias.trim() ? alias.split(',') : [],
|
||||
dataFormat: dataFormat || {
|
||||
decimalPlaces: 2,
|
||||
needMultiply100: false,
|
||||
},
|
||||
};
|
||||
const editInitFormVal = {
|
||||
...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);
|
||||
setDefineType(metricDefineType);
|
||||
setIsPercentState(isPercent);
|
||||
setIsDecimalState(isDecimal);
|
||||
queryDrillDownDimension(metricItem?.id);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isEdit) {
|
||||
initData();
|
||||
}
|
||||
}, [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,
|
||||
relateDimension: {
|
||||
...(metricItem?.relateDimension || {}),
|
||||
drillDownDimensions,
|
||||
},
|
||||
...fieldsValue,
|
||||
};
|
||||
const { alias, dataFormatType } = queryParams;
|
||||
queryParams.alias = Array.isArray(alias) ? alias.join(',') : '';
|
||||
if (!queryParams[queryParamsTypeParamsKey[defineType]]?.expr) {
|
||||
message.error('请输入度量表达式');
|
||||
return;
|
||||
}
|
||||
if (!dataFormatType) {
|
||||
delete queryParams.dataFormat;
|
||||
}
|
||||
if (isEmptyConditions(defineType, queryParams[queryParamsTypeParamsKey[defineType]])) {
|
||||
return;
|
||||
}
|
||||
|
||||
let saveMetricQuery = createMetric;
|
||||
if (queryParams.id) {
|
||||
saveMetricQuery = updateMetric;
|
||||
}
|
||||
const { code, msg } = await saveMetricQuery(queryParams);
|
||||
if (code === 200) {
|
||||
message.success('编辑指标成功');
|
||||
onSubmit?.(queryParams);
|
||||
return;
|
||||
}
|
||||
message.error(msg);
|
||||
};
|
||||
|
||||
const generatorMetricAlias = async () => {
|
||||
setLlmLoading(true);
|
||||
const { code, data } = await mockMetricAlias({ ...metricItem });
|
||||
const formAlias = form.getFieldValue('alias');
|
||||
setLlmLoading(false);
|
||||
if (code === 200) {
|
||||
form.setFieldValue('alias', Array.from(new Set([...formAlias, ...data])));
|
||||
} else {
|
||||
message.error('大语言模型解析异常');
|
||||
}
|
||||
};
|
||||
|
||||
const queryMetricTags = async () => {
|
||||
const { code, data } = await getMetricTags();
|
||||
if (code === 200) {
|
||||
// form.setFieldValue('alias', Array.from(new Set([...formAlias, ...data])));
|
||||
setTagOptions(
|
||||
Array.isArray(data)
|
||||
? data.map((tag: string) => {
|
||||
return { label: tag, value: tag };
|
||||
})
|
||||
: [],
|
||||
);
|
||||
} else {
|
||||
message.error('获取指标标签失败');
|
||||
}
|
||||
};
|
||||
const queryMetricsToCreateNewMetric = async () => {
|
||||
const { code, data } = await getMetricsToCreateNewMetric({
|
||||
modelId: modelId || metricItem?.modelId,
|
||||
});
|
||||
if (code === 200) {
|
||||
setCreateNewMetricList(data);
|
||||
} else {
|
||||
message.error('获取指标标签失败');
|
||||
}
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
if (currentStep === 1) {
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormItem hidden={true} name="id" label="ID">
|
||||
<Input placeholder="id" />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
name="name"
|
||||
label="指标名称"
|
||||
rules={[{ required: true, message: '请输入指标名称' }]}
|
||||
>
|
||||
<Input placeholder="名称不可重复" />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
name="bizName"
|
||||
label="英文名称"
|
||||
rules={[{ required: true, message: '请输入英文名称' }]}
|
||||
>
|
||||
<Input placeholder="名称不可重复" disabled={isEdit} />
|
||||
</FormItem>
|
||||
<FormItem label="别名">
|
||||
<Row>
|
||||
<Col flex="1 1 200px">
|
||||
<FormItem name="alias" noStyle>
|
||||
<Select
|
||||
mode="tags"
|
||||
placeholder="输入别名后回车确认,多别名输入、复制粘贴支持英文逗号自动分隔"
|
||||
tokenSeparators={[',']}
|
||||
maxTagCount={9}
|
||||
/>
|
||||
</FormItem>
|
||||
</Col>
|
||||
{isEdit && (
|
||||
<Col flex="0 1 75px">
|
||||
<Button
|
||||
type="link"
|
||||
loading={llmLoading}
|
||||
size="small"
|
||||
style={{ top: '2px' }}
|
||||
onClick={() => {
|
||||
generatorMetricAlias();
|
||||
}}
|
||||
>
|
||||
<Space>
|
||||
智能填充
|
||||
<Tooltip title="智能填充将根据指标相关信息,使用大语言模型获取指标别名">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</Button>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
</FormItem>
|
||||
<FormItem name="tags" label="标签">
|
||||
<Select
|
||||
mode="tags"
|
||||
placeholder="输入别名后回车确认,多别名输入、复制粘贴支持英文逗号自动分隔"
|
||||
tokenSeparators={[',']}
|
||||
maxTagCount={9}
|
||||
options={tagOptions}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
name="sensitiveLevel"
|
||||
label="敏感度"
|
||||
rules={[{ required: true, message: '请选择敏感度' }]}
|
||||
>
|
||||
<Select placeholder="请选择敏感度">
|
||||
{SENSITIVE_LEVEL_OPTIONS.map((item) => (
|
||||
<Option key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
name="description"
|
||||
label={
|
||||
<TableTitleTooltips
|
||||
title="业务口径"
|
||||
overlayInnerStyle={{ width: 600 }}
|
||||
tooltips={
|
||||
<>
|
||||
<p>
|
||||
在录入指标时,请务必详细填写指标口径。口径描述对于理解指标的含义、计算方法和使用场景至关重要。一个清晰、准确的口径描述可以帮助其他用户更好地理解和使用该指标,避免因为误解而导致错误的数据分析和决策。在填写口径时,建议包括以下信息:
|
||||
</p>
|
||||
<p>1. 指标的计算方法:详细说明指标是如何计算的,包括涉及的公式、计算步骤等。</p>
|
||||
<p>2. 数据来源:描述指标所依赖的数据来源,包括数据表、字段等信息。</p>
|
||||
<p>3. 使用场景:说明该指标适用于哪些业务场景,以及如何在这些场景中使用该指标。</p>
|
||||
<p>4. 任何其他相关信息:例如数据更新频率、数据质量要求等。</p>
|
||||
<p>
|
||||
请确保口径描述清晰、简洁且易于理解,以便其他用户能够快速掌握指标的核心要点。
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
}
|
||||
rules={[{ required: true, message: '请输入业务口径' }]}
|
||||
>
|
||||
<TextArea placeholder="请输入业务口径" />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label={
|
||||
<FormItemTitle
|
||||
title={'下钻维度配置'}
|
||||
subTitle={'配置下钻维度后,将可以在指标卡中进行下钻'}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
setMetricRelationModalOpenState(true);
|
||||
}}
|
||||
>
|
||||
设 置
|
||||
</Button>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label={
|
||||
<FormItemTitle
|
||||
title={'数据格式化'}
|
||||
// subTitle={'开启后,指标数据展示时会根据配置进行格式化,如0.02 -> 2%'}
|
||||
/>
|
||||
}
|
||||
name="dataFormatType"
|
||||
>
|
||||
<Radio.Group buttonStyle="solid" size="middle">
|
||||
<Radio.Button value="">默认</Radio.Button>
|
||||
<Radio.Button value="decimal">小数</Radio.Button>
|
||||
<Radio.Button value="percent">百分比</Radio.Button>
|
||||
</Radio.Group>
|
||||
</FormItem>
|
||||
|
||||
{(isPercentState || isDecimalState) && (
|
||||
<FormItem
|
||||
label={
|
||||
<FormItemTitle
|
||||
title={'小数位数'}
|
||||
subTitle={`对小数位数进行设置,如保留两位,0.021252 -> 0.02${
|
||||
isPercentState ? '%' : ''
|
||||
}`}
|
||||
/>
|
||||
}
|
||||
name={['dataFormat', 'decimalPlaces']}
|
||||
>
|
||||
<InputNumber placeholder="请输入需要保留小数位数" style={{ width: '300px' }} />
|
||||
</FormItem>
|
||||
)}
|
||||
{isPercentState && (
|
||||
<>
|
||||
<FormItem
|
||||
label={
|
||||
<FormItemTitle
|
||||
title={'原始值是否乘以100'}
|
||||
subTitle={'如 原始值0.001 ->展示值0.1% '}
|
||||
/>
|
||||
}
|
||||
name={['dataFormat', 'needMultiply100']}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</FormItem>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
const renderFooter = () => {
|
||||
if (!hasMeasuresState) {
|
||||
return <Button onClick={onCancel}>取消</Button>;
|
||||
}
|
||||
if (currentStep === 1) {
|
||||
return (
|
||||
<>
|
||||
<Button style={{ float: 'left' }} onClick={backward}>
|
||||
上一步
|
||||
</Button>
|
||||
<Button onClick={onCancel}>取消</Button>
|
||||
<Button type="primary" onClick={handleNext}>
|
||||
完成
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Button onClick={onCancel}>取消</Button>
|
||||
<Button type="primary" onClick={handleNext}>
|
||||
下一步
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<Modal
|
||||
forceRender
|
||||
width={800}
|
||||
style={{ top: 48 }}
|
||||
// styles={{ padding: '32px 40px 48px' }}
|
||||
destroyOnClose
|
||||
title={`${isEdit ? '编辑' : '新建'}指标`}
|
||||
maskClosable={false}
|
||||
open={createModalVisible}
|
||||
footer={renderFooter()}
|
||||
onCancel={onCancel}
|
||||
>
|
||||
{hasMeasuresState ? (
|
||||
<>
|
||||
<Steps style={{ marginBottom: 28 }} size="small" current={currentStep}>
|
||||
<Step title="基本信息" />
|
||||
<Step title="度量信息" />
|
||||
</Steps>
|
||||
<Form
|
||||
{...formLayout}
|
||||
form={form}
|
||||
initialValues={{
|
||||
...formValRef.current,
|
||||
dataFormatType: '',
|
||||
}}
|
||||
onValuesChange={(value, values: any) => {
|
||||
const { isPercent, dataFormatType } = values;
|
||||
// if (isPercent !== undefined) {
|
||||
// setIsPercentState(isPercent);
|
||||
// }
|
||||
if (dataFormatType === 'percent') {
|
||||
setIsPercentState(true);
|
||||
setIsDecimalState(false);
|
||||
}
|
||||
if (dataFormatType === 'decimal') {
|
||||
setIsPercentState(false);
|
||||
setIsDecimalState(true);
|
||||
}
|
||||
if (!dataFormatType) {
|
||||
setIsPercentState(false);
|
||||
setIsDecimalState(false);
|
||||
}
|
||||
}}
|
||||
className={styles.form}
|
||||
>
|
||||
{renderContent()}
|
||||
</Form>
|
||||
<DimensionAndMetricRelationModal
|
||||
metricItem={metricItem}
|
||||
relationsInitialValue={drillDownDimensionsConfig}
|
||||
open={metricRelationModalOpenState}
|
||||
onCancel={() => {
|
||||
setMetricRelationModalOpenState(false);
|
||||
}}
|
||||
onSubmit={(relations) => {
|
||||
setDrillDownDimensions(relations);
|
||||
setMetricRelationModalOpenState(false);
|
||||
}}
|
||||
onRefreshRelationData={() => {
|
||||
queryDrillDownDimension(metricItem?.id);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Result
|
||||
status="warning"
|
||||
subTitle="当前数据源缺少度量,无法创建指标。请前往数据源配置中,将字段设置为度量"
|
||||
extra={
|
||||
<Button
|
||||
type="primary"
|
||||
key="console"
|
||||
onClick={() => {
|
||||
history.replace(`/model/${domainId}/${modelId || metricItem?.modelId}/dataSource`);
|
||||
onCancel?.();
|
||||
}}
|
||||
>
|
||||
去创建
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default MetricInfoCreateForm;
|
||||
@@ -0,0 +1,236 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { Form, Button, Modal, Input, Select, Steps } from 'antd';
|
||||
import styles from '../../components/style.less';
|
||||
import { message } from 'antd';
|
||||
import { formLayout } from '@/components/FormHelper/utils';
|
||||
import { createView, updateView } from '../../service';
|
||||
import { ISemantic } from '../../data';
|
||||
import { isString } from 'lodash';
|
||||
import ViewModelConfigTable from './ViewModelConfigTable';
|
||||
import ViewModelConfigTransfer from './ViewModelConfigTransfer';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
export type ModelCreateFormModalProps = {
|
||||
domainId: number;
|
||||
viewItem: any;
|
||||
modelList: ISemantic.IModelItem[];
|
||||
onCancel: () => void;
|
||||
onSubmit: (values: any) => void;
|
||||
};
|
||||
const { Step } = Steps;
|
||||
const ViewCreateFormModal: React.FC<ModelCreateFormModalProps> = ({
|
||||
viewItem,
|
||||
domainId,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
modelList,
|
||||
}) => {
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
|
||||
const [formVals, setFormVals] = useState<ISemantic.IModelItem>({
|
||||
...viewItem,
|
||||
currentModel: modelList[0]?.id,
|
||||
});
|
||||
|
||||
const [submitData, setSubmitData] = useState({});
|
||||
const [saveLoading, setSaveLoading] = useState<boolean>(false);
|
||||
const [modalWidth, setModalWidth] = useState<number>(800);
|
||||
const [selectedModelItem, setSelectedModelItem] = useState<ISemantic.IModelItem | undefined>(
|
||||
modelList[0],
|
||||
);
|
||||
const [form] = Form.useForm();
|
||||
const configTableRef = useRef<any>();
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({
|
||||
...viewItem,
|
||||
});
|
||||
}, [viewItem]);
|
||||
|
||||
const handleConfirm = async () => {
|
||||
const fieldsValue = await form.validateFields();
|
||||
const viewModelConfigsMap = configTableRef?.current.getViewModelConfigs() || {};
|
||||
const queryData: ISemantic.IModelItem = {
|
||||
...formVals,
|
||||
...fieldsValue,
|
||||
...submitData,
|
||||
viewDetail: {
|
||||
viewModelConfigs: Object.values(viewModelConfigsMap),
|
||||
},
|
||||
domainId,
|
||||
};
|
||||
setFormVals(queryData);
|
||||
setSaveLoading(true);
|
||||
const { code, msg } = await (!queryData.id ? createView : updateView)(queryData);
|
||||
setSaveLoading(false);
|
||||
if (code === 200) {
|
||||
onSubmit?.(queryData);
|
||||
} else {
|
||||
message.error(msg);
|
||||
}
|
||||
};
|
||||
|
||||
const footer = (
|
||||
<>
|
||||
<Button onClick={onCancel}>取消</Button>
|
||||
<Button type="primary" loading={saveLoading} onClick={handleConfirm}>
|
||||
确定
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
|
||||
const forward = () => {
|
||||
setModalWidth(1200);
|
||||
setCurrentStep(currentStep + 1);
|
||||
};
|
||||
const backward = () => {
|
||||
setModalWidth(800);
|
||||
setCurrentStep(currentStep - 1);
|
||||
};
|
||||
|
||||
const handleNext = async () => {
|
||||
const fieldsValue = await form.validateFields();
|
||||
const submitForm = {
|
||||
...submitData,
|
||||
...fieldsValue,
|
||||
};
|
||||
setSubmitData(submitForm);
|
||||
if (currentStep < 1) {
|
||||
forward();
|
||||
} else {
|
||||
// await saveMetric(submitForm);
|
||||
}
|
||||
};
|
||||
|
||||
const renderFooter = () => {
|
||||
if (currentStep === 1) {
|
||||
return (
|
||||
<>
|
||||
<Button style={{ float: 'left' }} onClick={backward}>
|
||||
上一步
|
||||
</Button>
|
||||
<Button onClick={onCancel}>取消</Button>
|
||||
{/* <Button type="primary" onClick={handleNext}> */}
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
handleConfirm();
|
||||
}}
|
||||
>
|
||||
完成
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Button onClick={onCancel}>取消</Button>
|
||||
<Button type="primary" onClick={handleNext}>
|
||||
下一步
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
if (currentStep === 1) {
|
||||
return (
|
||||
<div>
|
||||
<FormItem
|
||||
name="currentModel"
|
||||
label="选择模型"
|
||||
rules={[{ required: true, message: '请选择模型!' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择模型,获取当前模型下指标维度信息"
|
||||
onChange={(val) => {
|
||||
const modelItem = modelList.find((item) => item.id === val);
|
||||
setSelectedModelItem(modelItem);
|
||||
}}
|
||||
options={modelList.map((item) => {
|
||||
return { label: item.name, value: item.id };
|
||||
})}
|
||||
/>
|
||||
</FormItem>
|
||||
<ViewModelConfigTransfer
|
||||
modelItem={selectedModelItem}
|
||||
viewItem={viewItem}
|
||||
ref={configTableRef}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormItem
|
||||
name="name"
|
||||
label="视图名称"
|
||||
rules={[{ required: true, message: '请输入视图名称!' }]}
|
||||
>
|
||||
<Input placeholder="视图名称不可重复" />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
name="bizName"
|
||||
label="视图英文名称"
|
||||
rules={[{ required: true, message: '请输入视图英文名称!' }]}
|
||||
>
|
||||
<Input placeholder="请输入视图英文名称" />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
name="alias"
|
||||
label="别名"
|
||||
getValueFromEvent={(value) => {
|
||||
return Array.isArray(value) ? value.join(',') : '';
|
||||
}}
|
||||
getValueProps={(value) => {
|
||||
return {
|
||||
value: isString(value) ? value.split(',') : [],
|
||||
};
|
||||
}}
|
||||
>
|
||||
<Select
|
||||
mode="tags"
|
||||
placeholder="输入别名后回车确认,多别名输入、复制粘贴支持英文逗号自动分隔"
|
||||
tokenSeparators={[',']}
|
||||
maxTagCount={9}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem name="description" label="视图描述">
|
||||
<Input.TextArea placeholder="视图描述" />
|
||||
</FormItem>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
width={modalWidth}
|
||||
destroyOnClose
|
||||
title={'视图信息'}
|
||||
open={true}
|
||||
// footer={footer}
|
||||
footer={renderFooter()}
|
||||
onCancel={onCancel}
|
||||
>
|
||||
<Steps style={{ marginBottom: 28 }} size="small" current={currentStep}>
|
||||
<Step title="基本信息" />
|
||||
<Step title="关联信息" />
|
||||
</Steps>
|
||||
<Form
|
||||
{...formLayout}
|
||||
form={form}
|
||||
initialValues={{
|
||||
...formVals,
|
||||
}}
|
||||
onValuesChange={(value, values) => {}}
|
||||
className={styles.form}
|
||||
>
|
||||
{renderContent()}
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ViewCreateFormModal;
|
||||
@@ -0,0 +1,188 @@
|
||||
import { message } from 'antd';
|
||||
// import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import React, { forwardRef, useImperativeHandle, useState, useEffect } from 'react';
|
||||
import type { Ref } from 'react';
|
||||
import DimensionMetricTransferModal from './DimensionMetricTransferModal';
|
||||
// import styles from '../../components/style.less';
|
||||
import { TransType } from '../../enum';
|
||||
import { getDimensionList, queryMetric } from '../../service';
|
||||
import { wrapperTransTypeAndId } from '../../components/Entity/utils';
|
||||
import { ISemantic } from '../../data';
|
||||
import { isArrayOfValues } from '@/utils/utils';
|
||||
|
||||
type Props = {
|
||||
// modelList: ISemantic.IModelItem[];
|
||||
viewItem: ISemantic.IViewItem;
|
||||
modelItem?: ISemantic.IModelItem;
|
||||
[key: string]: any;
|
||||
};
|
||||
const ViewModelConfigTransfer: React.FC<Props> = forwardRef(
|
||||
({ viewItem, modelItem }: Props, ref: Ref<any>) => {
|
||||
// const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||
const [selectedTransferKeys, setSelectedTransferKeys] = useState<React.Key[]>([]);
|
||||
|
||||
const [viewModelConfigsMap, setViewModelConfigsMap] = useState({});
|
||||
|
||||
const [dimensionList, setDimensionList] = useState<ISemantic.IDimensionItem[]>([]);
|
||||
const [metricList, setMetricList] = useState<ISemantic.IMetricItem[]>([]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getViewModelConfigs: () => {
|
||||
return viewModelConfigsMap;
|
||||
},
|
||||
}));
|
||||
|
||||
const queryDimensionListByIds = async (ids: number[]) => {
|
||||
if (!isArrayOfValues(ids)) {
|
||||
queryDimensionList([]);
|
||||
return;
|
||||
}
|
||||
const { code, data, msg } = await getDimensionList({ ids });
|
||||
if (code === 200 && Array.isArray(data?.list)) {
|
||||
queryDimensionList(data.list);
|
||||
} else {
|
||||
message.error(msg);
|
||||
}
|
||||
};
|
||||
|
||||
const queryMetricListByIds = async (ids: number[]) => {
|
||||
if (!isArrayOfValues(ids)) {
|
||||
queryMetricList([]);
|
||||
return;
|
||||
}
|
||||
const { code, data, msg } = await queryMetric({ ids });
|
||||
if (code === 200 && Array.isArray(data?.list)) {
|
||||
queryMetricList(data.list);
|
||||
} else {
|
||||
message.error(msg);
|
||||
}
|
||||
};
|
||||
|
||||
const queryDimensionList = async (selectedDimensionList: ISemantic.IDimensionItem[]) => {
|
||||
const { code, data, msg } = await getDimensionList({ modelId: modelItem?.id });
|
||||
if (code === 200 && Array.isArray(data?.list)) {
|
||||
const mergeList = selectedDimensionList.reduce(
|
||||
(modelDimensionList: ISemantic.IDimensionItem[], item) => {
|
||||
const hasItem = data.list.find((dataListItem: ISemantic.IDimensionItem) => {
|
||||
return dataListItem.id === item.id;
|
||||
});
|
||||
if (!hasItem) {
|
||||
return [item, ...modelDimensionList];
|
||||
}
|
||||
return modelDimensionList;
|
||||
},
|
||||
data.list,
|
||||
);
|
||||
setDimensionList(mergeList);
|
||||
} else {
|
||||
message.error(msg);
|
||||
}
|
||||
};
|
||||
|
||||
const queryMetricList = async (selectedMetricList: ISemantic.IMetricItem[]) => {
|
||||
const { code, data, msg } = await queryMetric({ modelId: modelItem?.id });
|
||||
if (code === 200 && Array.isArray(data?.list)) {
|
||||
const mergeList = selectedMetricList.reduce(
|
||||
(modelMetricList: ISemantic.IMetricItem[], item) => {
|
||||
const hasItem = data.list.find((dataListItem: ISemantic.IMetricItem) => {
|
||||
return dataListItem.id === item.id;
|
||||
});
|
||||
if (!hasItem) {
|
||||
return [item, ...modelMetricList];
|
||||
}
|
||||
return modelMetricList;
|
||||
},
|
||||
data.list,
|
||||
);
|
||||
setMetricList(mergeList);
|
||||
} else {
|
||||
message.error(msg);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const viewModelConfigs = viewItem?.viewDetail?.viewModelConfigs;
|
||||
if (Array.isArray(viewModelConfigs)) {
|
||||
const idList: number[] = [];
|
||||
const transferKeys: React.Key[] = [];
|
||||
const viewConfigMap = {};
|
||||
const allMetrics: number[] = [];
|
||||
const allDimensions: number[] = [];
|
||||
viewModelConfigs.forEach((item: ISemantic.IViewModelConfigItem) => {
|
||||
const { id, metrics, dimensions } = item;
|
||||
idList.push(id);
|
||||
allMetrics.push(...metrics);
|
||||
allDimensions.push(...dimensions);
|
||||
viewConfigMap[id] = { ...item };
|
||||
if (Array.isArray(metrics)) {
|
||||
metrics.forEach((metricId: number) => {
|
||||
transferKeys.push(wrapperTransTypeAndId(TransType.METRIC, metricId));
|
||||
});
|
||||
}
|
||||
if (Array.isArray(dimensions)) {
|
||||
dimensions.forEach((dimensionId: number) => {
|
||||
transferKeys.push(wrapperTransTypeAndId(TransType.DIMENSION, dimensionId));
|
||||
});
|
||||
}
|
||||
});
|
||||
setSelectedTransferKeys(transferKeys);
|
||||
// setSelectedRowKeys(idList);
|
||||
setViewModelConfigsMap(viewConfigMap);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const viewModelConfigs = isArrayOfValues(Object.values(viewModelConfigsMap))
|
||||
? (Object.values(viewModelConfigsMap) as ISemantic.IViewModelConfigItem[])
|
||||
: viewItem?.viewDetail?.viewModelConfigs;
|
||||
if (isArrayOfValues(viewModelConfigs)) {
|
||||
const allMetrics: number[] = [];
|
||||
const allDimensions: number[] = [];
|
||||
viewModelConfigs.forEach((item: ISemantic.IViewModelConfigItem) => {
|
||||
const { metrics, dimensions } = item;
|
||||
allMetrics.push(...metrics);
|
||||
allDimensions.push(...dimensions);
|
||||
});
|
||||
queryDimensionListByIds(allDimensions);
|
||||
queryMetricListByIds(allMetrics);
|
||||
} else {
|
||||
queryDimensionList([]);
|
||||
queryMetricList([]);
|
||||
}
|
||||
}, [modelItem]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DimensionMetricTransferModal
|
||||
modelId={modelItem?.id}
|
||||
dimensionList={dimensionList}
|
||||
metricList={metricList}
|
||||
selectedTransferKeys={selectedTransferKeys}
|
||||
onSubmit={(
|
||||
submitData: Record<string, ISemantic.IViewModelConfigItem>,
|
||||
selectedKeys: React.Key[],
|
||||
) => {
|
||||
const viewModelConfigs = Object.values(submitData) as ISemantic.IViewModelConfigItem[];
|
||||
|
||||
if (isArrayOfValues(viewModelConfigs)) {
|
||||
const allMetrics: number[] = [];
|
||||
const allDimensions: number[] = [];
|
||||
viewModelConfigs.forEach((item: ISemantic.IViewModelConfigItem) => {
|
||||
const { metrics, dimensions } = item;
|
||||
allMetrics.push(...metrics);
|
||||
allDimensions.push(...dimensions);
|
||||
});
|
||||
queryDimensionListByIds(allDimensions);
|
||||
queryMetricListByIds(allMetrics);
|
||||
}
|
||||
setViewModelConfigsMap(submitData);
|
||||
setSelectedTransferKeys(selectedKeys);
|
||||
}}
|
||||
onCancel={() => {}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default ViewModelConfigTransfer;
|
||||
@@ -0,0 +1,231 @@
|
||||
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 React, { useRef, useState, useEffect } from 'react';
|
||||
import { StatusEnum } from '../../enum';
|
||||
import type { Dispatch } from 'umi';
|
||||
import { connect } from 'umi';
|
||||
import type { StateType } from '../../model';
|
||||
import { deleteView, updateView, getViewList } from '../../service';
|
||||
import ViewCreateFormModal from './ViewCreateFormModal';
|
||||
import moment from 'moment';
|
||||
import styles from '../../components/style.less';
|
||||
import { ISemantic } from '../../data';
|
||||
import { ColumnsConfig } from '../../components/MetricTableColumnRender';
|
||||
|
||||
type Props = {
|
||||
disabledEdit?: boolean;
|
||||
modelList: ISemantic.IModelItem[];
|
||||
dispatch: Dispatch;
|
||||
domainManger: StateType;
|
||||
};
|
||||
|
||||
const ViewTable: React.FC<Props> = ({ disabledEdit = false, modelList, domainManger }) => {
|
||||
const { selectDomainId } = domainManger;
|
||||
const [viewItem, setViewItem] = useState<ISemantic.IViewItem>();
|
||||
const [saveLoading, setSaveLoading] = useState<boolean>(false);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false);
|
||||
const actionRef = useRef<ActionType>();
|
||||
|
||||
const updateViewStatus = async (modelData: ISemantic.IViewItem) => {
|
||||
setSaveLoading(true);
|
||||
const { code, msg } = await updateView({
|
||||
...modelData,
|
||||
});
|
||||
setSaveLoading(false);
|
||||
if (code === 200) {
|
||||
queryViewList();
|
||||
} else {
|
||||
message.error(msg);
|
||||
}
|
||||
};
|
||||
|
||||
const [viewList, setViewList] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
queryViewList();
|
||||
}, []);
|
||||
|
||||
const queryViewList = async () => {
|
||||
setLoading(true);
|
||||
const { code, data, msg } = await getViewList(selectDomainId);
|
||||
setLoading(false);
|
||||
if (code === 200) {
|
||||
setViewList(data);
|
||||
} else {
|
||||
message.error(msg);
|
||||
}
|
||||
};
|
||||
|
||||
const columns: ProColumns[] = [
|
||||
{
|
||||
dataIndex: 'id',
|
||||
title: 'ID',
|
||||
width: 80,
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: '视图名称',
|
||||
search: false,
|
||||
// render: (_, record) => {
|
||||
// return <a>{_}</a>;
|
||||
// },
|
||||
},
|
||||
{
|
||||
dataIndex: 'alias',
|
||||
title: '别名',
|
||||
width: 150,
|
||||
ellipsis: true,
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
dataIndex: 'bizName',
|
||||
title: '英文名称',
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
dataIndex: 'status',
|
||||
title: '状态',
|
||||
search: false,
|
||||
render: ColumnsConfig.state.render,
|
||||
},
|
||||
{
|
||||
dataIndex: 'createdBy',
|
||||
title: '创建人',
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
dataIndex: 'description',
|
||||
title: '描述',
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
dataIndex: 'updatedAt',
|
||||
title: '更新时间',
|
||||
search: false,
|
||||
render: (value: any) => {
|
||||
return value && value !== '-' ? moment(value).format('YYYY-MM-DD HH:mm:ss') : '-';
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (!disabledEdit) {
|
||||
columns.push({
|
||||
title: '操作',
|
||||
dataIndex: 'x',
|
||||
valueType: 'option',
|
||||
width: 150,
|
||||
render: (_, record) => {
|
||||
return (
|
||||
<Space className={styles.ctrlBtnContainer}>
|
||||
<a
|
||||
key="metricEditBtn"
|
||||
onClick={() => {
|
||||
setViewItem(record);
|
||||
setCreateDataSourceModalOpen(true);
|
||||
}}
|
||||
>
|
||||
编辑
|
||||
</a>
|
||||
{record.status === StatusEnum.ONLINE ? (
|
||||
<Button
|
||||
type="link"
|
||||
key="editStatusOfflineBtn"
|
||||
onClick={() => {
|
||||
updateViewStatus({
|
||||
...record,
|
||||
status: StatusEnum.OFFLINE,
|
||||
});
|
||||
}}
|
||||
>
|
||||
停用
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="link"
|
||||
key="editStatusOnlineBtn"
|
||||
onClick={() => {
|
||||
updateViewStatus({
|
||||
...record,
|
||||
status: StatusEnum.ONLINE,
|
||||
});
|
||||
}}
|
||||
>
|
||||
启用
|
||||
</Button>
|
||||
)}
|
||||
<Popconfirm
|
||||
title="确认删除?"
|
||||
okText="是"
|
||||
cancelText="否"
|
||||
onConfirm={async () => {
|
||||
const { code, msg } = await deleteView(record.id);
|
||||
if (code === 200) {
|
||||
queryViewList();
|
||||
} else {
|
||||
message.error(msg);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<a key="modelDeleteBtn">删除</a>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ProTable
|
||||
className={`${styles.classTable} ${styles.classTableSelectColumnAlignLeft}`}
|
||||
actionRef={actionRef}
|
||||
rowKey="id"
|
||||
search={false}
|
||||
columns={columns}
|
||||
loading={loading}
|
||||
dataSource={viewList}
|
||||
tableAlertRender={() => {
|
||||
return false;
|
||||
}}
|
||||
size="small"
|
||||
options={{ reload: false, density: false, fullScreen: false }}
|
||||
toolBarRender={() =>
|
||||
disabledEdit
|
||||
? [<></>]
|
||||
: [
|
||||
<Button
|
||||
key="create"
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
setViewItem(undefined);
|
||||
setCreateDataSourceModalOpen(true);
|
||||
}}
|
||||
>
|
||||
创建视图
|
||||
</Button>,
|
||||
]
|
||||
}
|
||||
/>
|
||||
{createDataSourceModalOpen && (
|
||||
<ViewCreateFormModal
|
||||
domainId={selectDomainId}
|
||||
viewItem={viewItem}
|
||||
modelList={modelList}
|
||||
onSubmit={() => {
|
||||
queryViewList();
|
||||
setCreateDataSourceModalOpen(false);
|
||||
}}
|
||||
onCancel={() => {
|
||||
setCreateDataSourceModalOpen(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default connect(({ domainManger }: { domainManger: StateType }) => ({
|
||||
domainManger,
|
||||
}))(ViewTable);
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { ISemantic } from '../data';
|
||||
import { connect } from 'umi';
|
||||
import type { Dispatch } from 'umi';
|
||||
import type { StateType } from '../model';
|
||||
import ViewTable from './components/ViewTable';
|
||||
|
||||
type Props = {
|
||||
disabledEdit?: boolean;
|
||||
modelList: ISemantic.IModelItem[];
|
||||
onModelChange?: (model?: ISemantic.IModelItem) => void;
|
||||
domainManger: StateType;
|
||||
dispatch: Dispatch;
|
||||
};
|
||||
|
||||
const View: React.FC<Props> = ({ modelList, disabledEdit = false, onModelChange }) => {
|
||||
return (
|
||||
<div style={{ padding: '15px 20px' }}>
|
||||
<ViewTable modelList={modelList} disabledEdit={disabledEdit} onModelChange={onModelChange} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ domainManger }: { domainManger: StateType }) => ({
|
||||
domainManger,
|
||||
}))(View);
|
||||
@@ -6,7 +6,7 @@ import type { Dispatch } from 'umi';
|
||||
import { connect } from 'umi';
|
||||
import type { StateType } from '../model';
|
||||
import { StatusEnum } from '../enum';
|
||||
import { SENSITIVE_LEVEL_ENUM } from '../constant';
|
||||
import { SENSITIVE_LEVEL_ENUM, SENSITIVE_LEVEL_OPTIONS } from '../constant';
|
||||
import {
|
||||
getModelList,
|
||||
getDimensionList,
|
||||
@@ -15,10 +15,11 @@ import {
|
||||
} from '../service';
|
||||
import DimensionInfoModal from './DimensionInfoModal';
|
||||
import DimensionValueSettingModal from './DimensionValueSettingModal';
|
||||
// import { updateDimension } from '../service';
|
||||
import { ISemantic, IDataSource } from '../data';
|
||||
import TableHeaderFilter from './TableHeaderFilter';
|
||||
import moment from 'moment';
|
||||
import BatchCtrlDropDownButton from '@/components/BatchCtrlDropDownButton';
|
||||
import { ColumnsConfig } from './MetricTableColumnRender';
|
||||
import styles from './style.less';
|
||||
|
||||
type Props = {
|
||||
@@ -31,6 +32,8 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
|
||||
const [dimensionItem, setDimensionItem] = useState<ISemantic.IDimensionItem>();
|
||||
const [dataSourceList, setDataSourceList] = useState<IDataSource.IDataSourceItem[]>([]);
|
||||
const [tableData, setTableData] = useState<ISemantic.IMetricItem[]>([]);
|
||||
const [filterParams, setFilterParams] = useState<Record<string, any>>({});
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||
const [dimensionValueSettingList, setDimensionValueSettingList] = useState<
|
||||
@@ -38,38 +41,37 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
>([]);
|
||||
const [dimensionValueSettingModalVisible, setDimensionValueSettingModalVisible] =
|
||||
useState<boolean>(false);
|
||||
const [pagination] = useState({
|
||||
|
||||
const defaultPagination = {
|
||||
current: 1,
|
||||
pageSize: 99999,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
});
|
||||
};
|
||||
const [pagination, setPagination] = useState(defaultPagination);
|
||||
|
||||
const actionRef = useRef<ActionType>();
|
||||
|
||||
const queryDimensionList = async (params: any) => {
|
||||
setLoading(true);
|
||||
const { code, data, msg } = await getDimensionList({
|
||||
...params,
|
||||
...pagination,
|
||||
...params,
|
||||
modelId,
|
||||
});
|
||||
setLoading(false);
|
||||
const { list } = data || {};
|
||||
let resData: any = {};
|
||||
const { list, pageSize, pageNum, total } = data || {};
|
||||
if (code === 200) {
|
||||
resData = {
|
||||
data: list || [],
|
||||
success: true,
|
||||
};
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageSize: Math.min(pageSize, 100),
|
||||
current: pageNum,
|
||||
total,
|
||||
});
|
||||
setTableData(list);
|
||||
} else {
|
||||
message.error(msg);
|
||||
resData = {
|
||||
data: [],
|
||||
total: 0,
|
||||
success: false,
|
||||
};
|
||||
setTableData([]);
|
||||
}
|
||||
return resData;
|
||||
};
|
||||
|
||||
const queryDataSourceList = async () => {
|
||||
@@ -81,25 +83,14 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
queryDimensionList({ ...filterParams, ...defaultPagination });
|
||||
}, [filterParams]);
|
||||
|
||||
useEffect(() => {
|
||||
queryDataSourceList();
|
||||
}, [modelId]);
|
||||
|
||||
// const updateDimensionStatus = async (dimensionData: ISemantic.IDimensionItem) => {
|
||||
// const { code, msg } = await updateDimension(dimensionData);
|
||||
// if (code === 200) {
|
||||
// actionRef?.current?.reload();
|
||||
// dispatch({
|
||||
// type: 'domainManger/queryDimensionList',
|
||||
// payload: {
|
||||
// modelId,
|
||||
// },
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
// message.error(msg);
|
||||
// };
|
||||
|
||||
const queryBatchUpdateStatus = async (ids: React.Key[], status: StatusEnum) => {
|
||||
if (Array.isArray(ids) && ids.length === 0) {
|
||||
return;
|
||||
@@ -111,7 +102,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
});
|
||||
setLoading(false);
|
||||
if (code === 200) {
|
||||
actionRef?.current?.reload();
|
||||
queryDimensionList({ ...filterParams, ...defaultPagination });
|
||||
dispatch({
|
||||
type: 'domainManger/queryDimensionList',
|
||||
payload: {
|
||||
@@ -127,6 +118,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
{
|
||||
dataIndex: 'id',
|
||||
title: 'ID',
|
||||
fixed: 'left',
|
||||
width: 80,
|
||||
order: 100,
|
||||
search: false,
|
||||
@@ -135,46 +127,26 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
dataIndex: 'key',
|
||||
title: '维度搜索',
|
||||
hideInTable: true,
|
||||
renderFormItem: () => <Input placeholder="请输入ID/维度名称/英文名称" />,
|
||||
},
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: '维度名称',
|
||||
title: '维度',
|
||||
fixed: 'left',
|
||||
width: 280,
|
||||
render: ColumnsConfig.dimensionInfo.render,
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
dataIndex: 'alias',
|
||||
title: '别名',
|
||||
width: 150,
|
||||
ellipsis: true,
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
dataIndex: 'bizName',
|
||||
title: '英文名称',
|
||||
search: false,
|
||||
// order: 9,
|
||||
},
|
||||
{
|
||||
dataIndex: 'sensitiveLevel',
|
||||
title: '敏感度',
|
||||
width: 80,
|
||||
width: 150,
|
||||
valueEnum: SENSITIVE_LEVEL_ENUM,
|
||||
render: ColumnsConfig.sensitiveLevel.render,
|
||||
},
|
||||
{
|
||||
dataIndex: 'isTag',
|
||||
title: '是否为标签',
|
||||
// search: false,
|
||||
renderFormItem: () => (
|
||||
<Select
|
||||
placeholder="请选择标签状态"
|
||||
allowClear
|
||||
options={[
|
||||
{ value: 1, label: '是' },
|
||||
{ value: 0, label: '否' },
|
||||
]}
|
||||
/>
|
||||
),
|
||||
width: 120,
|
||||
render: (isTag) => {
|
||||
switch (isTag) {
|
||||
case 0:
|
||||
@@ -189,27 +161,14 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
{
|
||||
dataIndex: 'status',
|
||||
title: '状态',
|
||||
width: 80,
|
||||
width: 150,
|
||||
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>;
|
||||
}
|
||||
},
|
||||
render: ColumnsConfig.state.render,
|
||||
},
|
||||
{
|
||||
dataIndex: 'createdBy',
|
||||
title: '创建人',
|
||||
width: 100,
|
||||
width: 180,
|
||||
search: false,
|
||||
},
|
||||
|
||||
@@ -217,6 +176,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
dataIndex: 'description',
|
||||
title: '描述',
|
||||
search: false,
|
||||
render: ColumnsConfig.description.render,
|
||||
},
|
||||
|
||||
{
|
||||
@@ -233,7 +193,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
title: '操作',
|
||||
dataIndex: 'x',
|
||||
valueType: 'option',
|
||||
width: 200,
|
||||
width: 250,
|
||||
render: (_, record) => {
|
||||
return (
|
||||
<Space className={styles.ctrlBtnContainer}>
|
||||
@@ -292,7 +252,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
const { code, msg } = await deleteDimension(record.id);
|
||||
if (code === 200) {
|
||||
setDimensionItem(undefined);
|
||||
actionRef.current?.reload();
|
||||
queryDimensionList({ ...filterParams, ...defaultPagination });
|
||||
} else {
|
||||
message.error(msg);
|
||||
}
|
||||
@@ -336,27 +296,102 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
return (
|
||||
<>
|
||||
<ProTable
|
||||
className={`${styles.classTable} ${styles.classTableSelectColumnAlignLeft}`}
|
||||
className={`${styles.classTable} ${styles.classTableSelectColumnAlignLeft} ${styles.disabledSearchTable}`}
|
||||
actionRef={actionRef}
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
request={queryDimensionList}
|
||||
loading={loading}
|
||||
search={{
|
||||
defaultCollapsed: false,
|
||||
collapseRender: () => {
|
||||
return <></>;
|
||||
},
|
||||
}}
|
||||
headerTitle={
|
||||
<TableHeaderFilter
|
||||
components={[
|
||||
{
|
||||
label: '维度搜索',
|
||||
component: (
|
||||
<Input.Search
|
||||
style={{ width: 280 }}
|
||||
placeholder="请输入ID/维度名称/英文名称"
|
||||
onSearch={(value) => {
|
||||
setFilterParams((preState) => {
|
||||
return {
|
||||
...preState,
|
||||
key: value,
|
||||
};
|
||||
});
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: '敏感度',
|
||||
component: (
|
||||
<Select
|
||||
style={{ width: 140 }}
|
||||
options={SENSITIVE_LEVEL_OPTIONS}
|
||||
placeholder="请选择敏感度"
|
||||
allowClear
|
||||
onChange={(value) => {
|
||||
setFilterParams((preState) => {
|
||||
return {
|
||||
...preState,
|
||||
sensitiveLevel: value,
|
||||
};
|
||||
});
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: '标签状态',
|
||||
component: (
|
||||
<Select
|
||||
style={{ width: 145 }}
|
||||
placeholder="请选择标签状态"
|
||||
allowClear
|
||||
onChange={(value) => {
|
||||
setFilterParams((preState) => {
|
||||
return {
|
||||
...preState,
|
||||
isTag: value,
|
||||
};
|
||||
});
|
||||
}}
|
||||
options={[
|
||||
{ value: 1, label: '是' },
|
||||
{ value: 0, label: '否' },
|
||||
]}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
search={false}
|
||||
// search={{
|
||||
// optionRender: false,
|
||||
// collapsed: false,
|
||||
// }}
|
||||
rowSelection={{
|
||||
type: 'checkbox',
|
||||
...rowSelection,
|
||||
}}
|
||||
dataSource={tableData}
|
||||
pagination={pagination}
|
||||
tableAlertRender={() => {
|
||||
return false;
|
||||
}}
|
||||
size="small"
|
||||
size="large"
|
||||
scroll={{ x: 1500 }}
|
||||
options={{ reload: false, density: false, fullScreen: false }}
|
||||
onChange={(data: any) => {
|
||||
const { current, pageSize, total } = data;
|
||||
const currentPagin = {
|
||||
current,
|
||||
pageSize,
|
||||
total,
|
||||
};
|
||||
setPagination(currentPagin);
|
||||
queryDimensionList({ ...filterParams, ...currentPagin });
|
||||
}}
|
||||
toolBarRender={() => [
|
||||
<Button
|
||||
key="create"
|
||||
@@ -388,7 +423,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
dataSourceList={dataSourceList}
|
||||
onSubmit={() => {
|
||||
setCreateModalVisible(false);
|
||||
actionRef?.current?.reload();
|
||||
queryDimensionList({ ...filterParams, ...defaultPagination });
|
||||
dispatch({
|
||||
type: 'domainManger/queryDimensionList',
|
||||
payload: {
|
||||
@@ -417,7 +452,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
setDimensionValueSettingModalVisible(false);
|
||||
}}
|
||||
onSubmit={() => {
|
||||
actionRef?.current?.reload();
|
||||
queryDimensionList({ ...filterParams, ...defaultPagination });
|
||||
dispatch({
|
||||
type: 'domainManger/queryDimensionList',
|
||||
payload: {
|
||||
|
||||
@@ -13,9 +13,9 @@ import {
|
||||
batchUpdateMetricStatus,
|
||||
batchDownloadMetric,
|
||||
} from '../service';
|
||||
|
||||
import MetricInfoCreateForm from './MetricInfoCreateForm';
|
||||
import BatchCtrlDropDownButton from '@/components/BatchCtrlDropDownButton';
|
||||
import TableHeaderFilter from './TableHeaderFilter';
|
||||
import moment from 'moment';
|
||||
import styles from './style.less';
|
||||
import { ISemantic } from '../data';
|
||||
@@ -39,6 +39,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
total: 0,
|
||||
};
|
||||
const [pagination, setPagination] = useState(defaultPagination);
|
||||
|
||||
const [filterParams, setFilterParams] = useState<Record<string, any>>({});
|
||||
|
||||
const actionRef = useRef<ActionType>();
|
||||
@@ -94,10 +95,19 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
};
|
||||
|
||||
const columns: ProColumns[] = [
|
||||
{
|
||||
dataIndex: 'id',
|
||||
title: 'ID',
|
||||
width: 80,
|
||||
fixed: 'left',
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: '指标',
|
||||
width: '30%',
|
||||
width: 280,
|
||||
fixed: 'left',
|
||||
// width: '30%',
|
||||
search: false,
|
||||
render: ColumnsConfig.metricInfo.render,
|
||||
},
|
||||
@@ -105,54 +115,35 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
dataIndex: 'key',
|
||||
title: '指标搜索',
|
||||
hideInTable: true,
|
||||
renderFormItem: () => (
|
||||
<Input.Search
|
||||
placeholder="请输入ID/指标名称/英文名称/标签"
|
||||
onSearch={(value) => {
|
||||
setFilterParams((preState) => {
|
||||
return {
|
||||
...preState,
|
||||
key: value,
|
||||
};
|
||||
});
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
dataIndex: 'sensitiveLevel',
|
||||
title: '敏感度',
|
||||
hideInTable: true,
|
||||
width: 160,
|
||||
valueEnum: SENSITIVE_LEVEL_ENUM,
|
||||
renderFormItem: () => (
|
||||
<Select
|
||||
options={SENSITIVE_LEVEL_OPTIONS}
|
||||
placeholder="请选择敏感度"
|
||||
allowClear
|
||||
onChange={(value) => {
|
||||
setFilterParams((preState) => {
|
||||
return {
|
||||
...preState,
|
||||
sensitiveLevel: value,
|
||||
};
|
||||
});
|
||||
}}
|
||||
/>
|
||||
),
|
||||
render: ColumnsConfig.sensitiveLevel.render,
|
||||
},
|
||||
|
||||
{
|
||||
dataIndex: 'description',
|
||||
title: '描述',
|
||||
width: 300,
|
||||
search: false,
|
||||
render: ColumnsConfig.description.render,
|
||||
},
|
||||
{
|
||||
dataIndex: 'status',
|
||||
title: '状态',
|
||||
width: 200,
|
||||
width: 160,
|
||||
search: false,
|
||||
render: ColumnsConfig.state.render,
|
||||
},
|
||||
|
||||
{
|
||||
dataIndex: 'createdBy',
|
||||
title: '创建人',
|
||||
width: 150,
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
dataIndex: 'updatedAt',
|
||||
title: '更新时间',
|
||||
@@ -274,14 +265,53 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
return (
|
||||
<>
|
||||
<ProTable
|
||||
className={`${styles.classTable} ${styles.classTableSelectColumnAlignLeft}`}
|
||||
className={`${styles.classTable} ${styles.classTableSelectColumnAlignLeft} ${styles.disabledSearchTable} `}
|
||||
actionRef={actionRef}
|
||||
headerTitle={
|
||||
<TableHeaderFilter
|
||||
components={[
|
||||
{
|
||||
label: '指标搜索',
|
||||
component: (
|
||||
<Input.Search
|
||||
style={{ width: 280 }}
|
||||
placeholder="请输入ID/指标名称/英文名称/标签"
|
||||
onSearch={(value) => {
|
||||
setFilterParams((preState) => {
|
||||
return {
|
||||
...preState,
|
||||
key: value,
|
||||
};
|
||||
});
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: '敏感度',
|
||||
component: (
|
||||
<Select
|
||||
style={{ width: 140 }}
|
||||
options={SENSITIVE_LEVEL_OPTIONS}
|
||||
placeholder="请选择敏感度"
|
||||
allowClear
|
||||
onChange={(value) => {
|
||||
setFilterParams((preState) => {
|
||||
return {
|
||||
...preState,
|
||||
sensitiveLevel: value,
|
||||
};
|
||||
});
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
search={{
|
||||
optionRender: false,
|
||||
collapsed: false,
|
||||
}}
|
||||
search={false}
|
||||
rowSelection={{
|
||||
type: 'checkbox',
|
||||
...rowSelection,
|
||||
@@ -303,6 +333,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
setPagination(currentPagin);
|
||||
queryMetricList({ ...filterParams, ...currentPagin });
|
||||
}}
|
||||
scroll={{ x: 1500 }}
|
||||
sticky={{ offsetHeader: 0 }}
|
||||
size="large"
|
||||
options={{ reload: false, density: false, fullScreen: false }}
|
||||
|
||||
@@ -120,7 +120,7 @@ const DatabaseTable: React.FC<Props> = ({}) => {
|
||||
return (
|
||||
<div style={{ margin: 20 }}>
|
||||
<ProTable
|
||||
className={`${styles.classTable} ${styles.classTableSelectColumnAlignLeft}`}
|
||||
// className={`${styles.classTable}`}
|
||||
actionRef={actionRef}
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
|
||||
@@ -11,6 +11,7 @@ type Props = {
|
||||
metricItem?: ISemantic.IMetricItem;
|
||||
relationsInitialValue?: ISemantic.IDrillDownDimensionItem[];
|
||||
onSubmit: (relations: ISemantic.IDrillDownDimensionItem[]) => void;
|
||||
onRefreshRelationData?: () => void;
|
||||
};
|
||||
|
||||
const DimensionAndMetricRelationModal: React.FC<Props> = ({
|
||||
@@ -19,6 +20,7 @@ const DimensionAndMetricRelationModal: React.FC<Props> = ({
|
||||
relationsInitialValue,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
onRefreshRelationData,
|
||||
}) => {
|
||||
const [relationList, setRelationList] = useState<ISemantic.IDrillDownDimensionItem[]>([]);
|
||||
|
||||
@@ -30,9 +32,11 @@ const DimensionAndMetricRelationModal: React.FC<Props> = ({
|
||||
drillDownDimensions: relationList,
|
||||
},
|
||||
};
|
||||
|
||||
const { code, msg } = await updateMetric(queryParams);
|
||||
if (code === 200) {
|
||||
onSubmit(relationList);
|
||||
onRefreshRelationData?.();
|
||||
return;
|
||||
}
|
||||
message.error(msg);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Table, Transfer, Checkbox, message } from 'antd';
|
||||
import { Table, Transfer, Checkbox, message, Tooltip } from 'antd';
|
||||
import type { ColumnsType, TableRowSelection } from 'antd/es/table/interface';
|
||||
import type { TransferItem } from 'antd/es/transfer';
|
||||
import type { CheckboxChangeEvent } from 'antd/es/checkbox';
|
||||
@@ -9,13 +9,15 @@ import type { StateType } from '../model';
|
||||
import TransTypeTag from './TransTypeTag';
|
||||
import TableTitleTooltips from '../components/TableTitleTooltips';
|
||||
import { ISemantic } from '../data';
|
||||
import { getDimensionList, getDimensionInModelCluster } from '../service';
|
||||
import { EnvironmentOutlined } from '@ant-design/icons';
|
||||
import { getDimensionInModelCluster } from '../service';
|
||||
import { SemanticNodeType, TransType } from '../enum';
|
||||
|
||||
interface RecordType {
|
||||
id: number;
|
||||
key: string;
|
||||
name: string;
|
||||
disabled?: boolean;
|
||||
transType: TransType.DIMENSION | TransType.METRIC;
|
||||
}
|
||||
|
||||
@@ -39,7 +41,7 @@ const DimensionMetricRelationTableTransfer: React.FC<Props> = ({
|
||||
);
|
||||
|
||||
const [dimensionList, setDimensionList] = useState<ISemantic.IDimensionItem[]>([]);
|
||||
|
||||
const [transferData, setTransferData] = useState<RecordType[]>([]);
|
||||
useEffect(() => {
|
||||
queryDimensionList();
|
||||
}, [metricItem, relationsInitialValue]);
|
||||
@@ -53,16 +55,29 @@ const DimensionMetricRelationTableTransfer: React.FC<Props> = ({
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const data = dimensionList.map((item) => {
|
||||
const transType = TransType.DIMENSION;
|
||||
const { id } = item;
|
||||
return {
|
||||
...item,
|
||||
transType,
|
||||
disabled: checkedMap[id]?.inheritFromModel,
|
||||
key: `${id}`,
|
||||
};
|
||||
});
|
||||
setTransferData(data);
|
||||
}, [checkedMap, dimensionList]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!Array.isArray(relationsInitialValue)) {
|
||||
return;
|
||||
}
|
||||
const ids = relationsInitialValue.map((item) => `${item.dimensionId}`);
|
||||
const relationMap = relationsInitialValue.reduce((relationCheckedMap, item: any) => {
|
||||
const { dimensionId, necessary } = item;
|
||||
const { dimensionId } = item;
|
||||
relationCheckedMap[dimensionId] = {
|
||||
dimensionId: Number(dimensionId),
|
||||
necessary: necessary,
|
||||
...item,
|
||||
};
|
||||
return relationCheckedMap;
|
||||
}, {});
|
||||
@@ -101,11 +116,15 @@ const DimensionMetricRelationTableTransfer: React.FC<Props> = ({
|
||||
(relationList: ISemantic.IDrillDownDimensionItem[], dimensionId: string) => {
|
||||
const target = relationCheckedMap[dimensionId];
|
||||
if (target) {
|
||||
if (target.inheritFromModel === true && !target.necessary) {
|
||||
return relationList;
|
||||
}
|
||||
relationList.push(target);
|
||||
} else {
|
||||
relationList.push({
|
||||
dimensionId: Number(dimensionId),
|
||||
necessary: false,
|
||||
inheritFromModel: false,
|
||||
});
|
||||
}
|
||||
return relationList;
|
||||
@@ -175,15 +194,7 @@ const DimensionMetricRelationTableTransfer: React.FC<Props> = ({
|
||||
<Transfer
|
||||
showSearch
|
||||
titles={['未关联维度', '已关联维度']}
|
||||
dataSource={dimensionList.map((item) => {
|
||||
const transType = TransType.DIMENSION;
|
||||
const { id } = item;
|
||||
return {
|
||||
...item,
|
||||
transType,
|
||||
key: `${id}`,
|
||||
};
|
||||
})}
|
||||
dataSource={transferData}
|
||||
listStyle={{
|
||||
width: 500,
|
||||
height: 600,
|
||||
@@ -207,11 +218,16 @@ const DimensionMetricRelationTableTransfer: React.FC<Props> = ({
|
||||
onItemSelectAll,
|
||||
onItemSelect,
|
||||
selectedKeys: listSelectedKeys,
|
||||
disabled: listDisabled,
|
||||
}) => {
|
||||
const columns = direction === 'left' ? leftColumns : rightColumns;
|
||||
const rowSelection: TableRowSelection<TransferItem> = {
|
||||
getCheckboxProps: (item) => ({ disabled: listDisabled || item.disabled }),
|
||||
onSelectAll(selected, selectedRows) {
|
||||
const treeSelectedKeys = selectedRows.map(({ key }) => key);
|
||||
const treeSelectedKeys = selectedRows
|
||||
.filter((item) => !item.disabled)
|
||||
.map(({ key }) => key);
|
||||
|
||||
const diffKeys = selected
|
||||
? difference(treeSelectedKeys, listSelectedKeys)
|
||||
: difference(listSelectedKeys, treeSelectedKeys);
|
||||
@@ -221,6 +237,16 @@ const DimensionMetricRelationTableTransfer: React.FC<Props> = ({
|
||||
onItemSelect(key as string, selected);
|
||||
},
|
||||
selectedRowKeys: listSelectedKeys,
|
||||
renderCell: function (checked, record, index, originNode) {
|
||||
if (checkedMap[record.id]?.inheritFromModel === true) {
|
||||
return (
|
||||
<Tooltip title="来自模型默认设置维度">
|
||||
<EnvironmentOutlined style={{ color: '#0958d9' }} />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
return originNode;
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -229,10 +255,17 @@ const DimensionMetricRelationTableTransfer: React.FC<Props> = ({
|
||||
columns={columns}
|
||||
dataSource={filteredItems as any}
|
||||
size="small"
|
||||
rowClassName={(record) => {
|
||||
if (checkedMap[record.id]?.inheritFromModel) {
|
||||
return 'inherit-from-model-row';
|
||||
}
|
||||
return '';
|
||||
}}
|
||||
pagination={false}
|
||||
scroll={{ y: 450 }}
|
||||
onRow={({ key }) => ({
|
||||
onRow={({ key, disabled: itemDisabled }) => ({
|
||||
onClick: () => {
|
||||
if (itemDisabled || listDisabled) return;
|
||||
onItemSelect(key as string, !listSelectedKeys.includes(key as string));
|
||||
},
|
||||
})}
|
||||
|
||||
@@ -114,7 +114,7 @@ const DomainListTree: FC<DomainListProps> = ({
|
||||
};
|
||||
|
||||
const titleRender = (node: any) => {
|
||||
const { id, name, path, hasEditPermission } = node as any;
|
||||
const { id, name, path, hasEditPermission, parentId } = node as any;
|
||||
return (
|
||||
<div className={styles.projectItem}>
|
||||
<span
|
||||
@@ -126,7 +126,7 @@ const DomainListTree: FC<DomainListProps> = ({
|
||||
{name}
|
||||
</span>
|
||||
{createDomainBtnVisible && hasEditPermission && (
|
||||
<span className={styles.operation}>
|
||||
<span className={`${styles.operation} ${parentId ? styles.rowHover : ''}`}>
|
||||
{Array.isArray(path) && path.length < 2 && (
|
||||
<PlusOutlined
|
||||
className={styles.icon}
|
||||
@@ -179,7 +179,7 @@ const DomainListTree: FC<DomainListProps> = ({
|
||||
return (
|
||||
<div className={styles.domainList}>
|
||||
<div className={styles.searchContainer}>
|
||||
<Row style={{ gap: 20 }}>
|
||||
<Row style={{ gap: 10 }}>
|
||||
<Col flex="1 1 215px">
|
||||
<Search
|
||||
allowClear
|
||||
|
||||
@@ -13,8 +13,9 @@ import { HomeOutlined, FundViewOutlined } from '@ant-design/icons';
|
||||
import { ISemantic } from '../data';
|
||||
import SemanticGraphCanvas from '../SemanticGraphCanvas';
|
||||
import RecommendedQuestionsSection from '../components/Entity/RecommendedQuestionsSection';
|
||||
import CommonDimensionTable from './CommonDimension/CommonDimensionTable';
|
||||
import DatabaseTable from '../components/Database/DatabaseTable';
|
||||
import View from '../View';
|
||||
// import CommonDimensionTable from './CommonDimension/CommonDimensionTable';
|
||||
// import DatabaseTable from '../components/Database/DatabaseTable';
|
||||
|
||||
import type { Dispatch } from 'umi';
|
||||
|
||||
@@ -54,6 +55,18 @@ const DomainManagerTab: React.FC<Props> = ({
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: '视图管理',
|
||||
key: 'viewManange',
|
||||
children: (
|
||||
<View
|
||||
modelList={modelList}
|
||||
onModelChange={(model) => {
|
||||
handleModelChange(model);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: '画布',
|
||||
key: 'xflow',
|
||||
@@ -68,11 +81,11 @@ const DomainManagerTab: React.FC<Props> = ({
|
||||
key: 'permissonSetting',
|
||||
children: <PermissionSection permissionTarget={'domain'} />,
|
||||
},
|
||||
{
|
||||
label: '数据库管理',
|
||||
key: 'database',
|
||||
children: <DatabaseTable />,
|
||||
},
|
||||
// {
|
||||
// label: '数据库管理',
|
||||
// key: 'database',
|
||||
// children: <DatabaseTable />,
|
||||
// },
|
||||
// {
|
||||
// label: '公共维度',
|
||||
// key: 'commonDimension',
|
||||
@@ -88,12 +101,12 @@ const DomainManagerTab: React.FC<Props> = ({
|
||||
|
||||
const isModelItem = [
|
||||
{
|
||||
label: '指标',
|
||||
label: '指标管理',
|
||||
key: 'metric',
|
||||
children: <ClassMetricTable />,
|
||||
},
|
||||
{
|
||||
label: '维度',
|
||||
label: '维度管理',
|
||||
key: 'dimenstion',
|
||||
children: <ClassDimensionTable />,
|
||||
},
|
||||
|
||||
@@ -23,12 +23,8 @@ type Props = {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
// type TaskStateMap = Record<string, DictTaskState>;
|
||||
|
||||
const DimensionMetricVisibleTableTransfer: React.FC<Props> = ({
|
||||
// domainManger,
|
||||
knowledgeInfosMap,
|
||||
// onKnowledgeInfosMapChange,
|
||||
...restProps
|
||||
}) => {
|
||||
let rightColumns: ColumnsType<RecordType> = [
|
||||
@@ -58,6 +54,11 @@ const DimensionMetricVisibleTableTransfer: React.FC<Props> = ({
|
||||
return <TransTypeTag type={type} />;
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
dataIndex: 'modelName',
|
||||
title: '所属模型',
|
||||
},
|
||||
];
|
||||
if (!knowledgeInfosMap) {
|
||||
rightColumns = leftColumns;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { IChatConfig } from '../../data';
|
||||
import DimensionMetricVisibleTableTransfer from './DimensionMetricVisibleTableTransfer';
|
||||
|
||||
interface RecordType {
|
||||
key: string;
|
||||
key: React.Key;
|
||||
name: string;
|
||||
type: 'dimension' | 'metric';
|
||||
}
|
||||
@@ -11,11 +11,10 @@ interface RecordType {
|
||||
type Props = {
|
||||
knowledgeInfosMap?: IChatConfig.IKnowledgeInfosItemMap;
|
||||
sourceList: any[];
|
||||
targetList: string[];
|
||||
targetList: React.Key[];
|
||||
titles?: string[];
|
||||
onKnowledgeInfosMapChange?: (knowledgeInfosMap: IChatConfig.IKnowledgeInfosItemMap) => void;
|
||||
onChange?: (params?: any) => void;
|
||||
// transferProps?: Record<string, any>;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
@@ -29,25 +28,27 @@ const DimensionMetricVisibleTransfer: React.FC<Props> = ({
|
||||
...rest
|
||||
}) => {
|
||||
const [transferData, setTransferData] = useState<RecordType[]>([]);
|
||||
const [targetKeys, setTargetKeys] = useState<string[]>(targetList);
|
||||
const [targetKeys, setTargetKeys] = useState<React.Key[]>(targetList);
|
||||
|
||||
useEffect(() => {
|
||||
setTransferData(
|
||||
sourceList.map(({ key, id, name, bizName, transType }) => {
|
||||
sourceList.map(({ key, id, name, bizName, transType, modelName }) => {
|
||||
return {
|
||||
key,
|
||||
name,
|
||||
bizName,
|
||||
id,
|
||||
modelName,
|
||||
type: transType,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}, [sourceList]);
|
||||
|
||||
useEffect(() => {
|
||||
setTargetKeys(targetList);
|
||||
}, [targetList]);
|
||||
const keyList: React.Key[] = sourceList.map((item) => item.key);
|
||||
const filterTargetList = targetList.filter((key: React.Key) => {
|
||||
return keyList.includes(key);
|
||||
});
|
||||
setTargetKeys(filterTargetList);
|
||||
}, [sourceList, targetList]);
|
||||
|
||||
const handleChange = (newTargetKeys: string[]) => {
|
||||
setTargetKeys(newTargetKeys);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { message, Space } from 'antd';
|
||||
import { useEffect, useState, forwardRef, useImperativeHandle, Ref } from 'react';
|
||||
import type { ForwardRefRenderFunction } from 'react';
|
||||
import type { Dispatch } from 'umi';
|
||||
import { connect } from 'umi';
|
||||
import type { StateType } from '../../model';
|
||||
|
||||
@@ -23,7 +23,7 @@ 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 { getMetricsToCreateNewMetric, getModelDetail } from '../service';
|
||||
import { getMetricsToCreateNewMetric, getModelDetail, getDrillDownDimension } from '../service';
|
||||
import MetricMetricFormTable from './MetricMetricFormTable';
|
||||
import MetricFieldFormTable from './MetricFieldFormTable';
|
||||
import DimensionAndMetricRelationModal from './DimensionAndMetricRelationModal';
|
||||
@@ -31,7 +31,6 @@ import TableTitleTooltips from '../components/TableTitleTooltips';
|
||||
import { createMetric, updateMetric, mockMetricAlias, getMetricTags } from '../service';
|
||||
import { ISemantic } from '../data';
|
||||
import { history } from 'umi';
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
export type CreateFormProps = {
|
||||
datasourceId?: number;
|
||||
@@ -113,7 +112,11 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
||||
|
||||
const [drillDownDimensions, setDrillDownDimensions] = useState<
|
||||
ISemantic.IDrillDownDimensionItem[]
|
||||
>(metricItem?.relateDimension?.drillDownDimensions || []);
|
||||
>([]);
|
||||
|
||||
const [drillDownDimensionsConfig, setDrillDownDimensionsConfig] = useState<
|
||||
ISemantic.IDrillDownDimensionItem[]
|
||||
>([]);
|
||||
|
||||
const forward = () => setCurrentStep(currentStep + 1);
|
||||
const backward = () => setCurrentStep(currentStep - 1);
|
||||
@@ -139,6 +142,17 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
||||
setClassMeasureList([]);
|
||||
};
|
||||
|
||||
const queryDrillDownDimension = async (metricId: number) => {
|
||||
const { code, data, msg } = await getDrillDownDimension(metricId);
|
||||
if (code === 200 && Array.isArray(data)) {
|
||||
setDrillDownDimensionsConfig(data);
|
||||
}
|
||||
if (code !== 200) {
|
||||
message.error(msg);
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
queryModelDetail();
|
||||
queryMetricsToCreateNewMetric();
|
||||
@@ -234,6 +248,7 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
||||
setDefineType(metricDefineType);
|
||||
setIsPercentState(isPercent);
|
||||
setIsDecimalState(isDecimal);
|
||||
queryDrillDownDimension(metricItem?.id);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -719,7 +734,7 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
||||
</Form>
|
||||
<DimensionAndMetricRelationModal
|
||||
metricItem={metricItem}
|
||||
relationsInitialValue={drillDownDimensions}
|
||||
relationsInitialValue={drillDownDimensionsConfig}
|
||||
open={metricRelationModalOpenState}
|
||||
onCancel={() => {
|
||||
setMetricRelationModalOpenState(false);
|
||||
@@ -728,6 +743,9 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
||||
setDrillDownDimensions(relations);
|
||||
setMetricRelationModalOpenState(false);
|
||||
}}
|
||||
onRefreshRelationData={() => {
|
||||
queryDrillDownDimension(metricItem?.id);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Space, Tag, Typography } from 'antd';
|
||||
import { StatusEnum } from '../enum';
|
||||
import { SENSITIVE_LEVEL_ENUM, SENSITIVE_LEVEL_COLOR } from '../constant';
|
||||
import { TagsOutlined, UserOutlined, FieldNumberOutlined, ReadOutlined } from '@ant-design/icons';
|
||||
import { TagsOutlined, ReadOutlined } from '@ant-design/icons';
|
||||
import { history } from 'umi';
|
||||
import { ISemantic } from '../data';
|
||||
import { isString } from 'lodash';
|
||||
@@ -9,51 +9,167 @@ import { isArrayOfValues } from '@/utils/utils';
|
||||
import styles from './style.less';
|
||||
import MetricStar from '../Metric/components/MetricStar';
|
||||
|
||||
const { Text, Link } = Typography;
|
||||
const { Text, Paragraph } = Typography;
|
||||
|
||||
export const ColumnsConfig = {
|
||||
metricInfo: {
|
||||
description: {
|
||||
render: (_, record: ISemantic.IMetricItem) => {
|
||||
const { name, alias, bizName, sensitiveLevel, createdBy, tags, id, isCollect } = record;
|
||||
const { description } = record;
|
||||
return (
|
||||
<Paragraph
|
||||
ellipsis={{ tooltip: description, rows: 3 }}
|
||||
style={{ width: 250, marginBottom: 0 }}
|
||||
>
|
||||
{description}
|
||||
</Paragraph>
|
||||
);
|
||||
},
|
||||
},
|
||||
dimensionInfo: {
|
||||
render: (_, record: ISemantic.IDimensionItem) => {
|
||||
const { name, alias, bizName } = record;
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<Space>
|
||||
<MetricStar metricId={id} initState={isCollect} />
|
||||
<span style={{ fontWeight: 500 }}>{name}</span>
|
||||
</Space>
|
||||
</div>
|
||||
<div style={{ color: '#5f748d', fontSize: 14, marginTop: 5, marginLeft: 0 }}>
|
||||
{bizName}
|
||||
</div>
|
||||
|
||||
{alias && (
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<Space direction="vertical" size={4}>
|
||||
{alias && (
|
||||
<Space size={4} style={{ color: '#5f748d', fontSize: 12, margin: '5px 0 5px 0' }}>
|
||||
<ReadOutlined />
|
||||
<div style={{ width: 'max-content' }}>别名:</div>
|
||||
<span style={{ marginLeft: 2 }}>
|
||||
<Space size={[0, 8]} wrap>
|
||||
{isString(alias) &&
|
||||
alias.split(',').map((aliasName: string) => {
|
||||
return (
|
||||
<Tag
|
||||
color="#eee"
|
||||
key={aliasName}
|
||||
style={{
|
||||
borderRadius: 44,
|
||||
maxWidth: 90,
|
||||
minWidth: 40,
|
||||
backgroundColor: 'rgba(18, 31, 67, 0.04)',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
maxWidth: 80,
|
||||
color: 'rgb(95, 116, 141)',
|
||||
textAlign: 'center',
|
||||
fontSize: 12,
|
||||
}}
|
||||
ellipsis={{ tooltip: aliasName }}
|
||||
>
|
||||
{aliasName}
|
||||
</Text>
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
</span>
|
||||
</Space>
|
||||
)}
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
metricInfo: {
|
||||
render: (_, record: ISemantic.IMetricItem) => {
|
||||
const { name, alias, bizName, tags, id, isCollect } = record;
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<Space>
|
||||
<a
|
||||
className={styles.textLink}
|
||||
style={{ fontWeight: 500, marginRight: 10 }}
|
||||
onClick={() => {
|
||||
style={{ fontWeight: 500 }}
|
||||
onClick={(event: any) => {
|
||||
history.push(`/metric/detail/${id}`);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}}
|
||||
href={`/webapp/metric/detail/${id}`}
|
||||
>
|
||||
{name}
|
||||
</a>
|
||||
<Tag
|
||||
<MetricStar metricId={id} initState={isCollect} />
|
||||
{/* <Tag
|
||||
color={SENSITIVE_LEVEL_COLOR[sensitiveLevel]}
|
||||
style={{ lineHeight: '16px', position: 'relative', top: '-1px' }}
|
||||
>
|
||||
{SENSITIVE_LEVEL_ENUM[sensitiveLevel]}
|
||||
</Tag>
|
||||
</Tag> */}
|
||||
</Space>
|
||||
</div>
|
||||
<div style={{ color: '#5f748d', fontSize: 14, marginTop: 5 }}>{bizName}</div>
|
||||
<div style={{ color: '#5f748d', fontSize: 14, marginTop: 5, marginLeft: 0 }}>
|
||||
{bizName}
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<Space direction="vertical" size={4}>
|
||||
{alias && (
|
||||
<Space size={4} style={{ color: '#5f748d', fontSize: 12 }}>
|
||||
<ReadOutlined />
|
||||
<div style={{ width: 'max-content' }}>别名:</div>
|
||||
<span style={{ marginLeft: 2 }}>
|
||||
<Space size={[0, 8]} wrap>
|
||||
{isString(alias) &&
|
||||
alias.split(',').map((aliasName: string) => {
|
||||
{(alias || isArrayOfValues(tags)) && (
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<Space direction="vertical" size={4}>
|
||||
{alias && (
|
||||
<Space size={4} style={{ color: '#5f748d', fontSize: 12, margin: '5px 0 5px 0' }}>
|
||||
<ReadOutlined />
|
||||
<div style={{ width: 'max-content' }}>别名:</div>
|
||||
<span style={{ marginLeft: 2 }}>
|
||||
<Space size={[0, 8]} wrap>
|
||||
{isString(alias) &&
|
||||
alias.split(',').map((aliasName: string) => {
|
||||
return (
|
||||
<Tag
|
||||
color="#eee"
|
||||
key={aliasName}
|
||||
style={{
|
||||
borderRadius: 44,
|
||||
maxWidth: 90,
|
||||
minWidth: 40,
|
||||
backgroundColor: 'rgba(18, 31, 67, 0.04)',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
maxWidth: 80,
|
||||
color: 'rgb(95, 116, 141)',
|
||||
textAlign: 'center',
|
||||
fontSize: 12,
|
||||
}}
|
||||
ellipsis={{ tooltip: aliasName }}
|
||||
>
|
||||
{aliasName}
|
||||
</Text>
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
</span>
|
||||
</Space>
|
||||
)}
|
||||
|
||||
{isArrayOfValues(tags) && (
|
||||
<Space size={4} style={{ color: '#5f748d', fontSize: 12, margin: '5px 0 5px 0' }}>
|
||||
<TagsOutlined />
|
||||
<div style={{ width: 'max-content' }}>标签:</div>
|
||||
<span style={{ marginLeft: 2 }}>
|
||||
<Space size={[0, 8]} wrap>
|
||||
{tags.map((tag: string) => {
|
||||
return (
|
||||
<Tag
|
||||
color="#eee"
|
||||
key={aliasName}
|
||||
key={tag}
|
||||
style={{
|
||||
borderRadius: 44,
|
||||
maxWidth: 90,
|
||||
@@ -68,93 +184,90 @@ export const ColumnsConfig = {
|
||||
textAlign: 'center',
|
||||
fontSize: 12,
|
||||
}}
|
||||
ellipsis={{ tooltip: aliasName }}
|
||||
ellipsis={{ tooltip: tag }}
|
||||
>
|
||||
{aliasName}
|
||||
{tag}
|
||||
</Text>
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
</span>
|
||||
</Space>
|
||||
)}
|
||||
|
||||
{isArrayOfValues(tags) && (
|
||||
<Space size={2} style={{ color: '#5f748d', fontSize: 12 }}>
|
||||
<TagsOutlined />
|
||||
<div style={{ width: 'max-content' }}>标签:</div>
|
||||
<span style={{ marginLeft: 2 }}>
|
||||
<Space size={[0, 8]} wrap>
|
||||
{tags.map((tag: string) => {
|
||||
return (
|
||||
<Tag
|
||||
color="#eee"
|
||||
key={tag}
|
||||
style={{
|
||||
borderRadius: 44,
|
||||
maxWidth: 90,
|
||||
minWidth: 40,
|
||||
backgroundColor: 'rgba(18, 31, 67, 0.04)',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
maxWidth: 80,
|
||||
color: 'rgb(95, 116, 141)',
|
||||
textAlign: 'center',
|
||||
fontSize: 12,
|
||||
}}
|
||||
ellipsis={{ tooltip: tag }}
|
||||
>
|
||||
{tag}
|
||||
</Text>
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
</span>
|
||||
</Space>
|
||||
)}
|
||||
<Space size={10}>
|
||||
<Space
|
||||
size={2}
|
||||
style={{ color: '#5f748d', fontSize: 12, position: 'relative', top: '1px' }}
|
||||
>
|
||||
<FieldNumberOutlined style={{ fontSize: 16, position: 'relative', top: '1px' }} />
|
||||
<span>:</span>
|
||||
<span style={{ marginLeft: 0 }}>{id}</span>
|
||||
</Space>
|
||||
<Space size={2} style={{ color: '#5f748d', fontSize: 12 }}>
|
||||
<UserOutlined />
|
||||
<span>:</span>
|
||||
<span style={{ marginLeft: 0 }}>{createdBy}</span>
|
||||
</Space>
|
||||
</Space>
|
||||
</span>
|
||||
</Space>
|
||||
)}
|
||||
{/* <Space size={10}>
|
||||
<Space
|
||||
size={2}
|
||||
style={{ color: '#5f748d', fontSize: 12, position: 'relative', top: '1px' }}
|
||||
>
|
||||
<FieldNumberOutlined style={{ fontSize: 16, position: 'relative', top: '1px' }} />
|
||||
<span>:</span>
|
||||
<span style={{ marginLeft: 0 }}>{id}</span>
|
||||
</Space>
|
||||
<Space size={2} style={{ color: '#5f748d', fontSize: 12 }}>
|
||||
<UserOutlined />
|
||||
<span>:</span>
|
||||
<span style={{ marginLeft: 0 }}>{createdBy}</span>
|
||||
</Space>
|
||||
</Space> */}
|
||||
</Space>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
sensitiveLevel: {
|
||||
render: (_, record: ISemantic.IMetricItem) => {
|
||||
const { sensitiveLevel } = record;
|
||||
return SENSITIVE_LEVEL_COLOR[sensitiveLevel] ? (
|
||||
<Tag
|
||||
color={SENSITIVE_LEVEL_COLOR[sensitiveLevel]}
|
||||
style={{
|
||||
borderRadius: '40px',
|
||||
padding: '2px 16px',
|
||||
fontSize: '13px',
|
||||
// color: '#8ca3ba',
|
||||
}}
|
||||
>
|
||||
{SENSITIVE_LEVEL_ENUM[sensitiveLevel]}
|
||||
</Tag>
|
||||
) : (
|
||||
<Tag
|
||||
style={{
|
||||
borderRadius: '40px',
|
||||
padding: '2px 16px',
|
||||
fontSize: '13px',
|
||||
}}
|
||||
>
|
||||
未知
|
||||
</Tag>
|
||||
);
|
||||
},
|
||||
},
|
||||
state: {
|
||||
render: (status) => {
|
||||
let tagProps = {
|
||||
let tagProps: { color: string; label: string; style?: any } = {
|
||||
color: 'default',
|
||||
label: '未知',
|
||||
style: {},
|
||||
};
|
||||
switch (status) {
|
||||
case StatusEnum.ONLINE:
|
||||
tagProps = {
|
||||
color: 'success',
|
||||
// color: '#87d068',
|
||||
// color: 'success',
|
||||
color: 'geekblue',
|
||||
label: '已启用',
|
||||
};
|
||||
break;
|
||||
case StatusEnum.OFFLINE:
|
||||
tagProps = {
|
||||
color: 'warning',
|
||||
color: 'default',
|
||||
label: '未启用',
|
||||
style: {
|
||||
color: 'rgb(95, 116, 141)',
|
||||
fontWeight: 400,
|
||||
},
|
||||
};
|
||||
break;
|
||||
case StatusEnum.INITIALIZED:
|
||||
@@ -180,6 +293,7 @@ export const ColumnsConfig = {
|
||||
padding: '2px 16px',
|
||||
fontSize: '13px',
|
||||
fontWeight: 500,
|
||||
...tagProps.style,
|
||||
}}
|
||||
>
|
||||
{tagProps.label}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { connect } from 'umi';
|
||||
import type { StateType } from '../model';
|
||||
import { deleteModel, updateModel } from '../service';
|
||||
import ClassDataSourceTypeModal from './ClassDataSourceTypeModal';
|
||||
import { ColumnsConfig } from './MetricTableColumnRender';
|
||||
|
||||
import moment from 'moment';
|
||||
import styles from './style.less';
|
||||
@@ -85,20 +86,7 @@ const ModelTable: React.FC<Props> = ({ modelList, disabledEdit = false, onModelC
|
||||
dataIndex: 'status',
|
||||
title: '状态',
|
||||
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>;
|
||||
}
|
||||
},
|
||||
render: ColumnsConfig.state.render,
|
||||
},
|
||||
{
|
||||
dataIndex: 'createdBy',
|
||||
|
||||
@@ -15,7 +15,7 @@ type Props = {
|
||||
|
||||
const OverView: React.FC<Props> = ({ modelList, disabledEdit = false, onModelChange }) => {
|
||||
return (
|
||||
<div style={{ padding: '20px' }}>
|
||||
<div style={{ padding: '15px 20px' }}>
|
||||
<ModelTable modelList={modelList} disabledEdit={disabledEdit} onModelChange={onModelChange} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -102,7 +102,7 @@ const PermissionAdminForm: React.FC<Props> = ({
|
||||
>
|
||||
<SelectTMEPerson placeholder="请邀请团队成员" />
|
||||
</FormItem>
|
||||
{/* {APP_TARGET === 'inner' && ( */}
|
||||
{/* {APP_TARGET === 'inner'} */}
|
||||
<FormItem name="adminOrgs" label="按组织">
|
||||
<SelectPartner
|
||||
type="selectedDepartment"
|
||||
@@ -111,7 +111,6 @@ const PermissionAdminForm: React.FC<Props> = ({
|
||||
}}
|
||||
/>
|
||||
</FormItem>
|
||||
{/* )} */}
|
||||
<Form.Item
|
||||
label={
|
||||
<FormItemTitle
|
||||
@@ -128,7 +127,7 @@ const PermissionAdminForm: React.FC<Props> = ({
|
||||
</Form.Item>
|
||||
{!isOpenState && (
|
||||
<>
|
||||
{/* {APP_TARGET === 'inner' && ( */}
|
||||
{/* {APP_TARGET === 'inner' && } */}
|
||||
<FormItem name="viewOrgs" label="按组织">
|
||||
<SelectPartner
|
||||
type="selectedDepartment"
|
||||
@@ -137,7 +136,6 @@ const PermissionAdminForm: React.FC<Props> = ({
|
||||
}}
|
||||
/>
|
||||
</FormItem>
|
||||
{/* )} */}
|
||||
<FormItem name="viewers" label="按个人">
|
||||
<SelectTMEPerson placeholder="请选择需要授权的个人" />
|
||||
</FormItem>
|
||||
|
||||
@@ -32,7 +32,6 @@ const PermissionCreateDrawer: React.FC<Props> = ({
|
||||
const basicInfoFormRef = useRef<any>(null);
|
||||
const [selectedDimensionKeyList, setSelectedDimensionKeyList] = useState<string[]>([]);
|
||||
const [selectedMetricKeyList, setSelectedMetricKeyList] = useState<string[]>([]);
|
||||
|
||||
const [selectedKeyList, setSelectedKeyList] = useState<string[]>([]);
|
||||
|
||||
const saveAuth = async () => {
|
||||
|
||||
@@ -8,7 +8,7 @@ import styles from '../style.less';
|
||||
type Props = {
|
||||
permissonData: any;
|
||||
onSubmit?: (data?: any) => void;
|
||||
onValuesChange?: (value, values) => void;
|
||||
onValuesChange?: (value: any, values: any) => void;
|
||||
};
|
||||
|
||||
const FormItem = Form.Item;
|
||||
@@ -17,7 +17,6 @@ const PermissionCreateForm: ForwardRefRenderFunction<any, Props> = (
|
||||
{ permissonData, onValuesChange },
|
||||
ref,
|
||||
) => {
|
||||
const { APP_TARGET } = process.env;
|
||||
const [form] = Form.useForm();
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
@@ -51,7 +50,6 @@ const PermissionCreateForm: ForwardRefRenderFunction<any, Props> = (
|
||||
<FormItem name="name" label="名称" rules={[{ required: true, message: '请输入名称' }]}>
|
||||
<Input placeholder="请输入名称" />
|
||||
</FormItem>
|
||||
{/* {APP_TARGET === 'inner' && ( */}
|
||||
<FormItem name="authorizedDepartmentIds" label="按组织">
|
||||
<SelectPartner
|
||||
type="selectedDepartment"
|
||||
@@ -60,7 +58,6 @@ const PermissionCreateForm: ForwardRefRenderFunction<any, Props> = (
|
||||
}}
|
||||
/>
|
||||
</FormItem>
|
||||
{/* )} */}
|
||||
|
||||
<FormItem name="authorizedUsers" label="按个人">
|
||||
<SelectTMEPerson placeholder="请选择需要授权的个人" />
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Space } from 'antd';
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
import styles from './style.less';
|
||||
|
||||
type Props = {
|
||||
components: {
|
||||
label: string;
|
||||
component: ReactNode;
|
||||
}[];
|
||||
};
|
||||
|
||||
const TableHeaderFilter: React.FC<Props> = ({ components }) => {
|
||||
return (
|
||||
<>
|
||||
<Space className={styles.tableHeaderTitle} size={20}>
|
||||
{components.map(({ label, component }) => (
|
||||
<Space size={8} key={`TableHeaderFilter-${label}`}>
|
||||
<span className={styles.headerTitleLabel}>{label}:</span>
|
||||
{component}
|
||||
</Space>
|
||||
))}
|
||||
</Space>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableHeaderFilter;
|
||||
@@ -75,44 +75,47 @@
|
||||
line-height: 34px;
|
||||
text-align: right;
|
||||
background: #fff;
|
||||
// border-bottom: 1px solid #d9d9d9;
|
||||
|
||||
|
||||
.title {
|
||||
float: left;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.search {
|
||||
// width: calc(100% - 100px);
|
||||
width: 100%;
|
||||
margin: 10px 0 10px 10px;
|
||||
width: 205px;
|
||||
margin: 10px 0 10px 16px;
|
||||
}
|
||||
|
||||
.tree {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
|
||||
padding-right: 18px;
|
||||
.projectItem {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
cursor: auto;
|
||||
line-height: 30px;
|
||||
height: 30px;
|
||||
line-height: 25px;
|
||||
height: 25px;
|
||||
color: rgb(95, 116, 141);
|
||||
font-weight: 500;
|
||||
.projectItemTitle {
|
||||
font-size: 16px;
|
||||
font-size: 15px;
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
|
||||
}
|
||||
|
||||
.operation {
|
||||
.icon {
|
||||
margin-left: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.rowHover {
|
||||
display: none;
|
||||
}
|
||||
&:hover {
|
||||
.rowHover {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,6 +182,8 @@
|
||||
}
|
||||
.ant-tabs-tab {
|
||||
padding-bottom:15px;
|
||||
color: rgb(95, 116, 141);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
.backBtn {
|
||||
@@ -237,6 +242,10 @@
|
||||
}
|
||||
|
||||
|
||||
.disabledSearchTable {
|
||||
padding-top:20px;
|
||||
}
|
||||
|
||||
.classTable {
|
||||
// padding: 0 20px;
|
||||
:global {
|
||||
@@ -419,4 +428,15 @@
|
||||
&:hover {
|
||||
color: #69b1ff;
|
||||
}
|
||||
}
|
||||
|
||||
.tableHeaderTitle {
|
||||
margin-left: 20px;
|
||||
.headerTitleLabel {
|
||||
color: #667085;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
width: max-content;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
@@ -37,9 +37,11 @@ export const IS_TAG_ENUM = {
|
||||
};
|
||||
|
||||
export const SENSITIVE_LEVEL_COLOR = {
|
||||
[SENSITIVE_LEVEL.LOW]: 'geekblue',
|
||||
[SENSITIVE_LEVEL.MID]: 'warning',
|
||||
[SENSITIVE_LEVEL.HIGH]: 'error',
|
||||
[SENSITIVE_LEVEL.LOW]: 'default',
|
||||
// [SENSITIVE_LEVEL.MID]: 'orange',
|
||||
[SENSITIVE_LEVEL.MID]: 'geekblue',
|
||||
[SENSITIVE_LEVEL.HIGH]: 'volcano',
|
||||
// [SENSITIVE_LEVEL.HIGH]: '#1677ff',
|
||||
};
|
||||
|
||||
export const SEMANTIC_NODE_TYPE_CONFIG = {
|
||||
|
||||
@@ -139,6 +139,31 @@ export declare namespace ISemantic {
|
||||
updatedAt: ISODateString;
|
||||
}
|
||||
|
||||
interface IViewModelConfigItem {
|
||||
id: number;
|
||||
includesAll: boolean;
|
||||
metrics: number[];
|
||||
dimensions: number[];
|
||||
}
|
||||
|
||||
interface IViewItem {
|
||||
createdBy: UserName;
|
||||
updatedBy: UserName;
|
||||
createdAt: ISODateString;
|
||||
updatedAt: ISODateString;
|
||||
id: number;
|
||||
name: string;
|
||||
bizName: string;
|
||||
description: string;
|
||||
status?: StatusEnum;
|
||||
typeEnum?: any;
|
||||
sensitiveLevel: number;
|
||||
domainId: number;
|
||||
viewDetail: {
|
||||
viewModelConfigs: IViewModelConfigItem[];
|
||||
};
|
||||
}
|
||||
|
||||
interface IDimensionItem {
|
||||
createdBy: string;
|
||||
updatedBy: string;
|
||||
@@ -209,6 +234,7 @@ export declare namespace ISemantic {
|
||||
|
||||
interface IDrillDownDimensionItem {
|
||||
dimensionId: number;
|
||||
inheritFromModel?: boolean;
|
||||
necessary?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,14 @@ export enum ChatConfigType {
|
||||
}
|
||||
|
||||
export enum TransType {
|
||||
DIMENSION = 'dimension',
|
||||
METRIC = 'metric',
|
||||
DIMENSION = 'DIMENSION',
|
||||
METRIC = 'METRIC',
|
||||
}
|
||||
|
||||
export enum SemanticNodeType {
|
||||
DATASOURCE = 'datasource',
|
||||
DIMENSION = 'dimension',
|
||||
METRIC = 'metric',
|
||||
DATASOURCE = 'DATASOURCE',
|
||||
DIMENSION = 'DIMENSION',
|
||||
METRIC = 'METRIC',
|
||||
}
|
||||
|
||||
export enum MetricTypeWording {
|
||||
|
||||
@@ -514,7 +514,7 @@ export async function queryStruct({
|
||||
args: null,
|
||||
},
|
||||
],
|
||||
orders: [],
|
||||
orders: [{ column: dateField, direction: 'desc' }],
|
||||
metricFilters: [],
|
||||
params: [],
|
||||
dateInfo: {
|
||||
@@ -560,3 +560,29 @@ export function getDatabaseParameters(): Promise<any> {
|
||||
export function getDatabaseDetail(id: number): Promise<any> {
|
||||
return request.get(`${process.env.API_BASE_URL}database/${id}`);
|
||||
}
|
||||
|
||||
export function getViewList(domainId: number): Promise<any> {
|
||||
return request(`${process.env.API_BASE_URL}view/getViewList`, {
|
||||
method: 'GET',
|
||||
params: { domainId },
|
||||
});
|
||||
}
|
||||
|
||||
export function createView(data: any): Promise<any> {
|
||||
return request(`${process.env.API_BASE_URL}view`, {
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
export function updateView(data: any): Promise<any> {
|
||||
return request(`${process.env.API_BASE_URL}view`, {
|
||||
method: 'PUT',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteView(viewId: number): Promise<any> {
|
||||
return request(`${process.env.API_BASE_URL}view/${viewId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ 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 DisabledWheelNumberInput from '@/components/DisabledWheelNumberInput';
|
||||
import { ConfigParametersItem } from '../System/types';
|
||||
const FormItem = Form.Item;
|
||||
const { TextArea } = Input;
|
||||
@@ -20,7 +21,7 @@ export const changeTreeData = (treeData: API.DomainList, auth?: boolean): DataNo
|
||||
};
|
||||
|
||||
export const addPathInTreeData = (treeData: API.DomainList, loopPath: any[] = []): any => {
|
||||
return treeData.map((item: any) => {
|
||||
return treeData?.map((item: any) => {
|
||||
const { children, parentId = [] } = item;
|
||||
const path = loopPath.slice();
|
||||
path.push(parentId);
|
||||
@@ -39,7 +40,7 @@ export const addPathInTreeData = (treeData: API.DomainList, loopPath: any[] = []
|
||||
};
|
||||
|
||||
export const constructorClassTreeFromList = (list: any[], parentId: number = 0) => {
|
||||
const tree = list.reduce((nodeList, nodeItem) => {
|
||||
const tree = list?.reduce((nodeList, nodeItem) => {
|
||||
if (nodeItem.parentId == parentId) {
|
||||
const children = constructorClassTreeFromList(list, nodeItem.id);
|
||||
if (children.length) {
|
||||
@@ -47,7 +48,7 @@ export const constructorClassTreeFromList = (list: any[], parentId: number = 0)
|
||||
}
|
||||
nodeItem.key = nodeItem.id;
|
||||
nodeItem.value = nodeItem.id;
|
||||
nodeItem.title = nodeItem.name;
|
||||
nodeItem.title = nodeItem.name || nodeItem.categoryName;
|
||||
nodeList.push(nodeItem);
|
||||
}
|
||||
return nodeList;
|
||||
@@ -57,7 +58,7 @@ export const constructorClassTreeFromList = (list: any[], parentId: number = 0)
|
||||
|
||||
export const treeParentKeyLists = (treeData: API.DomainList): string[] => {
|
||||
let keys: string[] = [];
|
||||
treeData.forEach((item: any) => {
|
||||
treeData?.forEach((item: any) => {
|
||||
if (item.children && item.children.length > 0) {
|
||||
keys.push(item.id);
|
||||
keys = keys.concat(treeParentKeyLists(item.children));
|
||||
@@ -133,7 +134,8 @@ export const findLeafNodesFromDomainList = (
|
||||
|
||||
export const genneratorFormItemList = (itemList: ConfigParametersItem[]) => {
|
||||
return itemList.map((item) => {
|
||||
const { dataType, name, comment, placeholder, description, require } = item;
|
||||
const { dataType, name, comment, placeholder, description, require, value } = item;
|
||||
|
||||
let defaultItem = <Input />;
|
||||
switch (dataType) {
|
||||
case 'string':
|
||||
@@ -148,7 +150,10 @@ export const genneratorFormItemList = (itemList: ConfigParametersItem[]) => {
|
||||
defaultItem = <TextArea placeholder={placeholder} style={{ height: 100 }} />;
|
||||
break;
|
||||
case 'number':
|
||||
defaultItem = <InputNumber placeholder={placeholder} style={{ width: '100%' }} />;
|
||||
// defaultItem = <InputNumber placeholder={placeholder} style={{ width: '100%' }} />;
|
||||
defaultItem = (
|
||||
<DisabledWheelNumberInput placeholder={placeholder} style={{ width: '100%' }} />
|
||||
);
|
||||
break;
|
||||
case 'bool':
|
||||
return (
|
||||
|
||||
BIN
webapp/packages/supersonic-fe/webapp_1225.zip
Normal file
BIN
webapp/packages/supersonic-fe/webapp_1225.zip
Normal file
Binary file not shown.
Reference in New Issue
Block a user