mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-17 16:02:14 +00:00
[improvement][semantic-fe] Added an editing component to set filtering rules for Q&A. Now, the SQL editor will be accompanied by a list for display and control, to resolve ambiguity when using comma-separated values.
[improvement][semantic-fe] Improved validation logic and prompt copywriting for data source/dimension/metric editing and creation. [improvement][semantic-fe] Improved user experience for visual modeling. Now, when using the legend to control the display/hide of data sources and their associated metric dimensions, the canvas will be re-layout based on the activated data source in the legend. Co-authored-by: tristanliu <tristanliu@tencent.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
export const presetsTagDomString = (text: string, color: string = 'blue') => {
|
||||
return `<span class="ant-tag ant-tag-${color}">${text}</span>`;
|
||||
};
|
||||
@@ -176,6 +176,9 @@ const ClassDataSourceTable: React.FC<Props> = ({ dispatch, domainManger }) => {
|
||||
{
|
||||
<ClassDataSourceTypeModal
|
||||
open={createDataSourceModalOpen}
|
||||
onCancel={() => {
|
||||
setCreateDataSourceModalOpen(false);
|
||||
}}
|
||||
onTypeChange={(type) => {
|
||||
if (type === 'fast') {
|
||||
setDataSourceModalVisible(true);
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
import { Modal, Card, Row, Col } from 'antd';
|
||||
import { Modal, Card, Row, Col, Result, Button } from 'antd';
|
||||
import { ConsoleSqlOutlined, CoffeeOutlined } from '@ant-design/icons';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { history, connect } from 'umi';
|
||||
import type { Dispatch } from 'umi';
|
||||
import type { StateType } from '../model';
|
||||
const { Meta } = Card;
|
||||
type Props = {
|
||||
open: boolean;
|
||||
domainManger: StateType;
|
||||
onTypeChange: (type: 'fast' | 'normal') => void;
|
||||
onCancel?: () => void;
|
||||
};
|
||||
|
||||
const ClassDataSourceTypeModal: React.FC<Props> = ({ open, onTypeChange, onCancel }) => {
|
||||
const ClassDataSourceTypeModal: React.FC<Props> = ({
|
||||
open,
|
||||
onTypeChange,
|
||||
domainManger,
|
||||
onCancel,
|
||||
}) => {
|
||||
const { selectDomainId, dataBaseConfig } = domainManger;
|
||||
const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false);
|
||||
useEffect(() => {
|
||||
setCreateDataSourceModalOpen(open);
|
||||
@@ -26,45 +36,67 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({ open, onTypeChange, onCance
|
||||
centered
|
||||
closable={false}
|
||||
>
|
||||
<Row gutter={16} style={{ marginTop: '0px' }}>
|
||||
<Col span={12}>
|
||||
<Card
|
||||
hoverable
|
||||
style={{ height: 220 }}
|
||||
onClick={() => {
|
||||
onTypeChange('fast');
|
||||
setCreateDataSourceModalOpen(false);
|
||||
}}
|
||||
cover={
|
||||
<CoffeeOutlined
|
||||
width={240}
|
||||
style={{ paddingTop: '45px', height: 120, fontSize: '48px', color: '#1890ff' }}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Meta title="快速创建" description="自动进行数据源可视化创建" />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Card
|
||||
onClick={() => {
|
||||
onTypeChange('normal');
|
||||
setCreateDataSourceModalOpen(false);
|
||||
}}
|
||||
hoverable
|
||||
style={{ height: 220 }}
|
||||
cover={
|
||||
<ConsoleSqlOutlined
|
||||
style={{ paddingTop: '45px', height: 120, fontSize: '48px', color: '#1890ff' }}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Meta title="SQL脚本" description="自定义SQL脚本创建数据源" />
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
{dataBaseConfig && dataBaseConfig.id ? (
|
||||
<Row gutter={16} style={{ marginTop: '0px' }}>
|
||||
<Col span={12}>
|
||||
<Card
|
||||
hoverable
|
||||
style={{ height: 220 }}
|
||||
onClick={() => {
|
||||
onTypeChange('fast');
|
||||
setCreateDataSourceModalOpen(false);
|
||||
}}
|
||||
cover={
|
||||
<CoffeeOutlined
|
||||
width={240}
|
||||
style={{ paddingTop: '45px', height: 120, fontSize: '48px', color: '#1890ff' }}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Meta title="快速创建" description="自动进行数据源可视化创建" />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Card
|
||||
onClick={() => {
|
||||
onTypeChange('normal');
|
||||
setCreateDataSourceModalOpen(false);
|
||||
}}
|
||||
hoverable
|
||||
style={{ height: 220 }}
|
||||
cover={
|
||||
<ConsoleSqlOutlined
|
||||
style={{ paddingTop: '45px', height: 120, fontSize: '48px', color: '#1890ff' }}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Meta title="SQL脚本" description="自定义SQL脚本创建数据源" />
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
) : (
|
||||
<Result
|
||||
status="warning"
|
||||
subTitle="创建数据源需要先完成数据库设置"
|
||||
extra={
|
||||
<Button
|
||||
type="primary"
|
||||
key="console"
|
||||
onClick={() => {
|
||||
history.replace(`/semanticModel/${selectDomainId}/dataBase`);
|
||||
onCancel?.();
|
||||
}}
|
||||
>
|
||||
去设置
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default ClassDataSourceTypeModal;
|
||||
|
||||
export default connect(({ domainManger }: { domainManger: StateType }) => ({
|
||||
domainManger,
|
||||
}))(ClassDataSourceTypeModal);
|
||||
|
||||
@@ -6,14 +6,9 @@ import type { Dispatch } from 'umi';
|
||||
import { connect } from 'umi';
|
||||
import type { StateType } from '../model';
|
||||
import { SENSITIVE_LEVEL_ENUM } from '../constant';
|
||||
import {
|
||||
getDatasourceList,
|
||||
getDimensionList,
|
||||
createDimension,
|
||||
updateDimension,
|
||||
deleteDimension,
|
||||
} from '../service';
|
||||
import { getDatasourceList, getDimensionList, deleteDimension } from '../service';
|
||||
import DimensionInfoModal from './DimensionInfoModal';
|
||||
import { ISemantic } from '../data';
|
||||
import moment from 'moment';
|
||||
import styles from './style.less';
|
||||
|
||||
@@ -25,7 +20,7 @@ type Props = {
|
||||
const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
const { selectDomainId } = domainManger;
|
||||
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
|
||||
const [dimensionItem, setDimensionItem] = useState<any>();
|
||||
const [dimensionItem, setDimensionItem] = useState<ISemantic.IDimensionItem>();
|
||||
const [dataSourceList, setDataSourceList] = useState<any[]>([]);
|
||||
const [pagination, setPagination] = useState({
|
||||
current: 1,
|
||||
@@ -45,7 +40,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
let resData: any = {};
|
||||
if (code === 200) {
|
||||
setPagination({
|
||||
pageSize,
|
||||
pageSize: Math.min(pageSize, 100),
|
||||
current,
|
||||
total,
|
||||
});
|
||||
@@ -175,36 +170,6 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
},
|
||||
];
|
||||
|
||||
const saveDimension = async (fieldsValue: any, reloadState: boolean = true) => {
|
||||
const queryParams = {
|
||||
domainId: selectDomainId,
|
||||
type: 'categorical',
|
||||
...fieldsValue,
|
||||
};
|
||||
let saveDimensionQuery = createDimension;
|
||||
if (queryParams.id) {
|
||||
saveDimensionQuery = updateDimension;
|
||||
}
|
||||
|
||||
const { code, msg } = await saveDimensionQuery(queryParams);
|
||||
|
||||
if (code === 200) {
|
||||
setCreateModalVisible(false);
|
||||
if (reloadState) {
|
||||
message.success('编辑维度成功');
|
||||
actionRef?.current?.reload();
|
||||
}
|
||||
dispatch({
|
||||
type: 'domainManger/queryDimensionList',
|
||||
payload: {
|
||||
domainId: selectDomainId,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
message.error(msg);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ProTable
|
||||
@@ -251,10 +216,21 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
|
||||
{createModalVisible && (
|
||||
<DimensionInfoModal
|
||||
domainId={selectDomainId}
|
||||
bindModalVisible={createModalVisible}
|
||||
dimensionItem={dimensionItem}
|
||||
dataSourceList={dataSourceList}
|
||||
onSubmit={saveDimension}
|
||||
onSubmit={() => {
|
||||
setCreateModalVisible(false);
|
||||
actionRef?.current?.reload();
|
||||
dispatch({
|
||||
type: 'domainManger/queryDimensionList',
|
||||
payload: {
|
||||
domainId: selectDomainId,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}}
|
||||
onCancel={() => {
|
||||
setCreateModalVisible(false);
|
||||
}}
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { Dispatch } from 'umi';
|
||||
import { connect } from 'umi';
|
||||
import type { StateType } from '../model';
|
||||
import { SENSITIVE_LEVEL_ENUM } from '../constant';
|
||||
import { creatExprMetric, updateExprMetric, queryMetric, deleteMetric } from '../service';
|
||||
import { queryMetric, deleteMetric } from '../service';
|
||||
|
||||
import MetricInfoCreateForm from './MetricInfoCreateForm';
|
||||
|
||||
@@ -39,7 +39,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
let resData: any = {};
|
||||
if (code === 200) {
|
||||
setPagination({
|
||||
pageSize,
|
||||
pageSize: Math.min(pageSize, 100),
|
||||
current,
|
||||
total,
|
||||
});
|
||||
@@ -166,36 +166,36 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
},
|
||||
];
|
||||
|
||||
const saveMetric = async (fieldsValue: any, reloadState: boolean = true) => {
|
||||
const queryParams = {
|
||||
domainId: selectDomainId,
|
||||
...fieldsValue,
|
||||
};
|
||||
if (queryParams.typeParams && !queryParams.typeParams.expr) {
|
||||
message.error('度量表达式不能为空');
|
||||
return;
|
||||
}
|
||||
let saveMetricQuery = creatExprMetric;
|
||||
if (queryParams.id) {
|
||||
saveMetricQuery = updateExprMetric;
|
||||
}
|
||||
const { code, msg } = await saveMetricQuery(queryParams);
|
||||
if (code === 200) {
|
||||
message.success('编辑指标成功');
|
||||
setCreateModalVisible(false);
|
||||
if (reloadState) {
|
||||
actionRef?.current?.reload();
|
||||
}
|
||||
dispatch({
|
||||
type: 'domainManger/queryMetricList',
|
||||
payload: {
|
||||
domainId: selectDomainId,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
message.error(msg);
|
||||
};
|
||||
// const saveMetric = async (fieldsValue: any, reloadState: boolean = true) => {
|
||||
// const queryParams = {
|
||||
// domainId: selectDomainId,
|
||||
// ...fieldsValue,
|
||||
// };
|
||||
// if (queryParams.typeParams && !queryParams.typeParams.expr) {
|
||||
// message.error('度量表达式不能为空');
|
||||
// return;
|
||||
// }
|
||||
// let saveMetricQuery = creatExprMetric;
|
||||
// if (queryParams.id) {
|
||||
// saveMetricQuery = updateExprMetric;
|
||||
// }
|
||||
// const { code, msg } = await saveMetricQuery(queryParams);
|
||||
// if (code === 200) {
|
||||
// message.success('编辑指标成功');
|
||||
// setCreateModalVisible(false);
|
||||
// if (reloadState) {
|
||||
// actionRef?.current?.reload();
|
||||
// }
|
||||
// dispatch({
|
||||
// type: 'domainManger/queryMetricList',
|
||||
// payload: {
|
||||
// domainId: selectDomainId,
|
||||
// },
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
// message.error(msg);
|
||||
// };
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -246,8 +246,15 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
domainId={Number(selectDomainId)}
|
||||
createModalVisible={createModalVisible}
|
||||
metricItem={metricItem}
|
||||
onSubmit={(values) => {
|
||||
saveMetric(values);
|
||||
onSubmit={() => {
|
||||
setCreateModalVisible(false);
|
||||
actionRef?.current?.reload();
|
||||
dispatch({
|
||||
type: 'domainManger/queryMetricList',
|
||||
payload: {
|
||||
domainId: selectDomainId,
|
||||
},
|
||||
});
|
||||
}}
|
||||
onCancel={() => {
|
||||
setCreateModalVisible(false);
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { List, Collapse, Button } from 'antd';
|
||||
import { uuid } from '@/utils/utils';
|
||||
import SqlEditor from '@/components/SqlEditor';
|
||||
|
||||
import styles from './style.less';
|
||||
|
||||
const { Panel } = Collapse;
|
||||
|
||||
type Props = {
|
||||
title?: string;
|
||||
defaultCollapse?: boolean;
|
||||
value?: string[];
|
||||
onChange?: (list: string[]) => void;
|
||||
};
|
||||
|
||||
type ListItem = {
|
||||
id: string;
|
||||
sql: string;
|
||||
};
|
||||
|
||||
type List = ListItem[];
|
||||
|
||||
const CommonEditList: React.FC<Props> = ({ title, defaultCollapse = false, value, onChange }) => {
|
||||
const [listDataSource, setListDataSource] = useState<List>([]);
|
||||
const [currentSql, setCurrentSql] = useState<string>('');
|
||||
const [activeKey, setActiveKey] = useState<string>();
|
||||
const [currentRecord, setCurrentRecord] = useState<ListItem>();
|
||||
|
||||
useEffect(() => {
|
||||
if (Array.isArray(value)) {
|
||||
const list = value.map((sql: string) => {
|
||||
return {
|
||||
id: uuid(),
|
||||
sql,
|
||||
};
|
||||
});
|
||||
setListDataSource(list);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
const handleListChange = (listDataSource: List) => {
|
||||
const sqlList = listDataSource.map((item) => {
|
||||
return item.sql;
|
||||
});
|
||||
onChange?.(sqlList);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.commonEditList}>
|
||||
<Collapse
|
||||
activeKey={activeKey}
|
||||
defaultActiveKey={defaultCollapse ? ['editor'] : undefined}
|
||||
onChange={() => {}}
|
||||
ghost
|
||||
>
|
||||
<Panel
|
||||
header={title}
|
||||
key="editor"
|
||||
extra={
|
||||
activeKey ? (
|
||||
<Button
|
||||
key="saveBtn"
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
if (!currentRecord && !currentSql) {
|
||||
setActiveKey(undefined);
|
||||
return;
|
||||
}
|
||||
if (currentRecord) {
|
||||
const list = [...listDataSource].map((item) => {
|
||||
if (item.id === currentRecord.id) {
|
||||
return {
|
||||
...item,
|
||||
sql: currentSql,
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
setListDataSource(list);
|
||||
handleListChange(list);
|
||||
} else {
|
||||
const list = [
|
||||
...listDataSource,
|
||||
{
|
||||
id: uuid(),
|
||||
sql: currentSql,
|
||||
},
|
||||
];
|
||||
setListDataSource(list);
|
||||
handleListChange(list);
|
||||
}
|
||||
|
||||
setActiveKey(undefined);
|
||||
}}
|
||||
>
|
||||
确认
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="primary"
|
||||
key="createBtn"
|
||||
onClick={() => {
|
||||
setCurrentRecord(undefined);
|
||||
setCurrentSql('');
|
||||
setActiveKey('editor');
|
||||
}}
|
||||
>
|
||||
新增
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
showArrow={false}
|
||||
>
|
||||
<div>
|
||||
<SqlEditor
|
||||
value={currentSql}
|
||||
height={'150px'}
|
||||
onChange={(sql) => {
|
||||
setCurrentSql(sql);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Panel>
|
||||
</Collapse>
|
||||
<List
|
||||
itemLayout="horizontal"
|
||||
dataSource={listDataSource || []}
|
||||
renderItem={(item) => (
|
||||
<List.Item
|
||||
actions={[
|
||||
<a
|
||||
key="list-loadmore-edit"
|
||||
onClick={() => {
|
||||
setCurrentSql(item.sql);
|
||||
setCurrentRecord(item);
|
||||
setActiveKey('editor');
|
||||
}}
|
||||
>
|
||||
编辑
|
||||
</a>,
|
||||
<a
|
||||
key="list-loadmore-more"
|
||||
onClick={() => {
|
||||
const list = [...listDataSource].filter(({ id }) => {
|
||||
return item.id !== id;
|
||||
});
|
||||
handleListChange(list);
|
||||
setListDataSource(list);
|
||||
}}
|
||||
>
|
||||
删除
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
<List.Item.Meta title={item.sql} />
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommonEditList;
|
||||
@@ -0,0 +1,10 @@
|
||||
.commonEditList {
|
||||
:global {
|
||||
.ant-collapse-header {
|
||||
padding: 0 !important;
|
||||
}
|
||||
.ant-collapse {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Form, Input, Modal, Select, List } from 'antd';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Button, Form, Input, Modal, Select } from 'antd';
|
||||
import { SENSITIVE_LEVEL_OPTIONS } from '../constant';
|
||||
import { formLayout } from '@/components/FormHelper/utils';
|
||||
import SqlEditor from '@/components/SqlEditor';
|
||||
import InfoTagList from './InfoTagList';
|
||||
import { ISemantic } from '../data';
|
||||
import { createDimension, updateDimension } from '../service';
|
||||
import { message } from 'antd';
|
||||
|
||||
export type CreateFormProps = {
|
||||
dimensionItem: any;
|
||||
domainId: number;
|
||||
dimensionItem?: ISemantic.IDimensionItem;
|
||||
onCancel: () => void;
|
||||
bindModalVisible: boolean;
|
||||
dataSourceList: any[];
|
||||
onSubmit: (values: any) => Promise<any>;
|
||||
onSubmit: (values?: any) => void;
|
||||
};
|
||||
|
||||
const FormItem = Form.Item;
|
||||
@@ -20,42 +23,56 @@ const { Option } = Select;
|
||||
const { TextArea } = Input;
|
||||
|
||||
const DimensionInfoModal: React.FC<CreateFormProps> = ({
|
||||
domainId,
|
||||
onCancel,
|
||||
bindModalVisible,
|
||||
dimensionItem,
|
||||
dataSourceList,
|
||||
onSubmit: handleUpdate,
|
||||
}) => {
|
||||
const isEdit = dimensionItem?.id;
|
||||
const [formVals, setFormVals] = useState<any>({
|
||||
roleCode: '',
|
||||
users: [],
|
||||
effectiveTime: 1,
|
||||
});
|
||||
const isEdit = !!dimensionItem?.id;
|
||||
|
||||
const [form] = Form.useForm();
|
||||
const { setFieldsValue } = form;
|
||||
const { setFieldsValue, resetFields } = form;
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const fieldsValue = await form.validateFields();
|
||||
setFormVals({ ...fieldsValue });
|
||||
try {
|
||||
await handleUpdate(fieldsValue);
|
||||
} catch (error) {
|
||||
message.error('保存失败,接口调用出错');
|
||||
await saveDimension(fieldsValue);
|
||||
};
|
||||
|
||||
const saveDimension = async (fieldsValue: any) => {
|
||||
const queryParams = {
|
||||
domainId,
|
||||
type: 'categorical',
|
||||
...fieldsValue,
|
||||
};
|
||||
let saveDimensionQuery = createDimension;
|
||||
if (queryParams.id) {
|
||||
saveDimensionQuery = updateDimension;
|
||||
}
|
||||
const { code, msg } = await saveDimensionQuery(queryParams);
|
||||
if (code === 200) {
|
||||
message.success('编辑维度成功');
|
||||
handleUpdate(fieldsValue);
|
||||
return;
|
||||
}
|
||||
message.error(msg);
|
||||
};
|
||||
|
||||
const setFormVal = () => {
|
||||
console.log(dimensionItem, 'dimensionItem');
|
||||
setFieldsValue(dimensionItem);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (dimensionItem) {
|
||||
setFormVal();
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
}, [dimensionItem]);
|
||||
if (!isEdit && Array.isArray(dataSourceList) && dataSourceList[0]?.id) {
|
||||
setFieldsValue({ datasourceId: dataSourceList[0].id });
|
||||
}
|
||||
}, [dimensionItem, dataSourceList]);
|
||||
|
||||
const renderFooter = () => {
|
||||
return (
|
||||
@@ -141,7 +158,12 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
|
||||
>
|
||||
<TextArea placeholder="请输入维度描述" />
|
||||
</FormItem>
|
||||
<FormItem name="expr" label="表达式" rules={[{ required: true, message: '请输入表达式' }]}>
|
||||
<FormItem
|
||||
name="expr"
|
||||
label="表达式"
|
||||
tooltip="表达式中的字段必须在创建数据源的时候被标记为日期或者维度"
|
||||
rules={[{ required: true, message: '请输入表达式' }]}
|
||||
>
|
||||
<SqlEditor height={'150px'} />
|
||||
</FormItem>
|
||||
</>
|
||||
@@ -162,9 +184,11 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
|
||||
<Form
|
||||
{...formLayout}
|
||||
form={form}
|
||||
initialValues={{
|
||||
...formVals,
|
||||
}}
|
||||
initialValues={
|
||||
{
|
||||
// ...formVals,
|
||||
}
|
||||
}
|
||||
>
|
||||
{renderContent()}
|
||||
</Form>
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Modal, message } from 'antd';
|
||||
import { addDomainExtend, editDomainExtend, getDomainExtendDetailConfig } from '../../service';
|
||||
import DimensionMetricVisibleTransfer from './DimensionMetricVisibleTransfer';
|
||||
import { exChangeRichEntityListToIds } from './utils';
|
||||
|
||||
type Props = {
|
||||
domainId: number;
|
||||
themeData: any;
|
||||
settingType: 'dimension' | 'metric';
|
||||
settingSourceList: any[];
|
||||
onCancel: () => void;
|
||||
visible: boolean;
|
||||
onSubmit: (params?: any) => void;
|
||||
};
|
||||
|
||||
const dimensionConfig = {
|
||||
blackIdListKey: 'blackDimIdList',
|
||||
visibleIdListKey: 'whiteDimIdList',
|
||||
modalTitle: '问答可见维度信息',
|
||||
titles: ['不可见维度', '可见维度'],
|
||||
};
|
||||
|
||||
const metricConfig = {
|
||||
blackIdListKey: 'blackMetricIdList',
|
||||
visibleIdListKey: 'whiteMetricIdList',
|
||||
modalTitle: '问答可见指标信息',
|
||||
titles: ['不可见指标', '可见指标'],
|
||||
};
|
||||
|
||||
const DimensionMetricVisibleModal: React.FC<Props> = ({
|
||||
domainId,
|
||||
visible,
|
||||
themeData = {},
|
||||
settingType,
|
||||
settingSourceList,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
}) => {
|
||||
const [sourceList, setSourceList] = useState<any[]>([]);
|
||||
const [selectedKeyList, setSelectedKeyList] = useState<string[]>([]);
|
||||
const settingTypeConfig = settingType === 'dimension' ? dimensionConfig : metricConfig;
|
||||
useEffect(() => {
|
||||
const list = settingSourceList.map((item: any) => {
|
||||
const { id, name } = item;
|
||||
return { id, name, type: settingType };
|
||||
});
|
||||
setSourceList(list);
|
||||
}, [settingSourceList]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedKeyList(themeData.visibility?.[settingTypeConfig.visibleIdListKey] || []);
|
||||
}, [themeData]);
|
||||
|
||||
const saveEntity = async () => {
|
||||
const { id, entity } = themeData;
|
||||
let saveDomainExtendQuery = addDomainExtend;
|
||||
if (id) {
|
||||
saveDomainExtendQuery = editDomainExtend;
|
||||
}
|
||||
const blackIdList = settingSourceList.reduce((list, item: any) => {
|
||||
const { id: targetId } = item;
|
||||
if (!selectedKeyList.includes(targetId)) {
|
||||
list.push(targetId);
|
||||
}
|
||||
return list;
|
||||
}, []);
|
||||
const entityParams = exChangeRichEntityListToIds(entity);
|
||||
themeData.entity = entityParams;
|
||||
const params = {
|
||||
...themeData,
|
||||
visibility: themeData.visibility || {},
|
||||
};
|
||||
params.visibility[settingTypeConfig.blackIdListKey] = blackIdList;
|
||||
|
||||
if (!params.visibility.blackDimIdList) {
|
||||
params.visibility.blackDimIdList = [];
|
||||
}
|
||||
if (!params.visibility.blackMetricIdList) {
|
||||
params.visibility.blackMetricIdList = [];
|
||||
}
|
||||
|
||||
const { code, msg } = await saveDomainExtendQuery({
|
||||
...params,
|
||||
id,
|
||||
domainId,
|
||||
});
|
||||
if (code === 200) {
|
||||
onSubmit?.();
|
||||
message.success('保存成功');
|
||||
return;
|
||||
}
|
||||
message.error(msg);
|
||||
};
|
||||
|
||||
const handleTransferChange = (newTargetKeys: string[]) => {
|
||||
setSelectedKeyList(newTargetKeys);
|
||||
};
|
||||
|
||||
const renderFooter = () => {
|
||||
return (
|
||||
<>
|
||||
<Button onClick={onCancel}>取消</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
saveEntity();
|
||||
}}
|
||||
>
|
||||
完成
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
width={1200}
|
||||
destroyOnClose
|
||||
title={settingTypeConfig.modalTitle}
|
||||
maskClosable={false}
|
||||
open={visible}
|
||||
footer={renderFooter()}
|
||||
onCancel={onCancel}
|
||||
>
|
||||
<DimensionMetricVisibleTransfer
|
||||
titles={settingTypeConfig.titles}
|
||||
sourceList={sourceList}
|
||||
targetList={selectedKeyList}
|
||||
onChange={(newTargetKeys) => {
|
||||
handleTransferChange(newTargetKeys);
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DimensionMetricVisibleModal;
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Space, Table, Transfer, Checkbox, Tooltip, Button } from 'antd';
|
||||
import { Table, Transfer, Checkbox, Button } 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';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons';
|
||||
import difference from 'lodash/difference';
|
||||
import React, { useState } from 'react';
|
||||
import type { IChatConfig } from '../../data';
|
||||
import DimensionValueSettingModal from './DimensionValueSettingModal';
|
||||
import TransTypeTag from '../TransTypeTag';
|
||||
import { TransType } from '../../enum';
|
||||
import TableTitleTooltips from '../../components/TableTitleTooltips';
|
||||
|
||||
interface RecordType {
|
||||
id: number;
|
||||
@@ -72,12 +72,10 @@ const DimensionMetricVisibleTableTransfer: React.FC<Props> = ({
|
||||
{
|
||||
dataIndex: 'y',
|
||||
title: (
|
||||
<Space>
|
||||
<span>维度值可见</span>
|
||||
<Tooltip title="勾选可见后,维度值将在搜索时可以被联想出来">
|
||||
<ExclamationCircleOutlined />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
<TableTitleTooltips
|
||||
title="维度值可见"
|
||||
tooltips="勾选可见后,维度值将在搜索时可以被联想出来"
|
||||
/>
|
||||
),
|
||||
width: 120,
|
||||
render: (_, record) => {
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Modal, message, Space } from 'antd';
|
||||
import ProCard from '@ant-design/pro-card';
|
||||
import { addDomainExtend, editDomainExtend } from '../../service';
|
||||
import DimensionMetricVisibleTransfer from './DimensionMetricVisibleTransfer';
|
||||
import SqlEditor from '@/components/SqlEditor';
|
||||
type Props = {
|
||||
domainId: number;
|
||||
themeData: any;
|
||||
settingSourceList: any[];
|
||||
onCancel: () => void;
|
||||
visible: boolean;
|
||||
onSubmit: (params?: any) => void;
|
||||
};
|
||||
|
||||
const DimensionSearchVisibleModal: React.FC<Props> = ({
|
||||
domainId,
|
||||
themeData,
|
||||
visible,
|
||||
settingSourceList,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
}) => {
|
||||
const [sourceList, setSourceList] = useState<any[]>([]);
|
||||
const [selectedKeyList, setSelectedKeyList] = useState<string[]>([]);
|
||||
const [dictRules, setDictRules] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
const knowledgeInfos = themeData?.knowledgeInfos;
|
||||
if (Array.isArray(knowledgeInfos)) {
|
||||
const target = knowledgeInfos[0];
|
||||
if (Array.isArray(target?.ruleList)) {
|
||||
setDictRules(target.ruleList[0]);
|
||||
}
|
||||
const selectKeys = knowledgeInfos.map((item: any) => {
|
||||
return item.itemId;
|
||||
});
|
||||
setSelectedKeyList(selectKeys);
|
||||
}
|
||||
}, [themeData]);
|
||||
|
||||
useEffect(() => {
|
||||
const list = settingSourceList.map((item: any) => {
|
||||
const { id, name } = item;
|
||||
return { id, name, type: 'dimension' };
|
||||
});
|
||||
setSourceList(list);
|
||||
}, [settingSourceList]);
|
||||
|
||||
const saveDictBatch = async () => {
|
||||
const knowledgeInfos = selectedKeyList.map((key: string) => {
|
||||
return {
|
||||
itemId: key,
|
||||
type: 'DIMENSION',
|
||||
isDictInfo: true,
|
||||
ruleList: dictRules ? [dictRules] : [],
|
||||
};
|
||||
});
|
||||
const id = themeData?.id;
|
||||
let saveDomainExtendQuery = addDomainExtend;
|
||||
if (id) {
|
||||
saveDomainExtendQuery = editDomainExtend;
|
||||
}
|
||||
const { code, msg } = await saveDomainExtendQuery({
|
||||
knowledgeInfos,
|
||||
domainId,
|
||||
id,
|
||||
});
|
||||
|
||||
if (code === 200) {
|
||||
message.success('保存可见维度值成功');
|
||||
onSubmit?.();
|
||||
return;
|
||||
}
|
||||
message.error(msg);
|
||||
};
|
||||
|
||||
const saveDictSetting = async () => {
|
||||
await saveDictBatch();
|
||||
};
|
||||
|
||||
const handleTransferChange = (newTargetKeys: string[]) => {
|
||||
setSelectedKeyList(newTargetKeys);
|
||||
};
|
||||
|
||||
const renderFooter = () => {
|
||||
return (
|
||||
<>
|
||||
<Button onClick={onCancel}>取消</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
saveDictSetting();
|
||||
}}
|
||||
>
|
||||
完成
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
width={1200}
|
||||
destroyOnClose
|
||||
title={'可见维度值设置'}
|
||||
maskClosable={false}
|
||||
open={visible}
|
||||
footer={renderFooter()}
|
||||
onCancel={onCancel}
|
||||
>
|
||||
<Space direction="vertical" style={{ width: '100%' }} size={20}>
|
||||
<ProCard bordered title="可见设置">
|
||||
<DimensionMetricVisibleTransfer
|
||||
titles={['不可见维度值', '可见维度值']}
|
||||
sourceList={sourceList}
|
||||
targetList={selectedKeyList}
|
||||
onChange={(newTargetKeys) => {
|
||||
handleTransferChange(newTargetKeys);
|
||||
}}
|
||||
/>
|
||||
</ProCard>
|
||||
<ProCard bordered title="维度值过滤">
|
||||
<SqlEditor
|
||||
height={'150px'}
|
||||
value={dictRules}
|
||||
onChange={(sql: string) => {
|
||||
setDictRules(sql);
|
||||
}}
|
||||
/>
|
||||
</ProCard>
|
||||
</Space>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DimensionSearchVisibleModal;
|
||||
@@ -5,7 +5,7 @@ import { Form, Input } from 'antd';
|
||||
import { formLayout } from '@/components/FormHelper/utils';
|
||||
import { isString } from 'lodash';
|
||||
import styles from '../style.less';
|
||||
|
||||
import CommonEditList from '../../components/CommonEditList/index';
|
||||
import SqlEditor from '@/components/SqlEditor';
|
||||
type Props = {
|
||||
initialValues: any;
|
||||
@@ -17,7 +17,7 @@ const FormItem = Form.Item;
|
||||
const EntityCreateForm: ForwardRefRenderFunction<any, Props> = ({ initialValues }, ref) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const exchangeFields = ['blackList', 'whiteList', 'ruleList'];
|
||||
const exchangeFields = ['blackList', 'whiteList'];
|
||||
|
||||
const getFormValidateFields = async () => {
|
||||
const fields = await form.validateFields();
|
||||
@@ -69,8 +69,9 @@ const EntityCreateForm: ForwardRefRenderFunction<any, Props> = ({ initialValues
|
||||
<Input placeholder="多个维度值用英文逗号隔开" />
|
||||
</FormItem>
|
||||
|
||||
<FormItem name="ruleList" label="过滤规则">
|
||||
<SqlEditor height={'150px'} />
|
||||
<FormItem name="ruleList">
|
||||
{/* <SqlEditor height={'150px'} /> */}
|
||||
<CommonEditList title="过滤规则" />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</>
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
import { useEffect, useState, forwardRef, useImperativeHandle } from 'react';
|
||||
import type { ForwardRefRenderFunction } from 'react';
|
||||
import FormItemTitle from '@/components/FormHelper/FormItemTitle';
|
||||
import { formLayout } from '@/components/FormHelper/utils';
|
||||
import { message, Form, Input, Select, Button, InputNumber } from 'antd';
|
||||
import { addDomainExtend, editDomainExtend } from '../../service';
|
||||
|
||||
import styles from '../style.less';
|
||||
type Props = {
|
||||
themeData: any;
|
||||
metricList: any[];
|
||||
domainId: number;
|
||||
onSubmit: (params?: any) => void;
|
||||
};
|
||||
|
||||
const FormItem = Form.Item;
|
||||
const Option = Select.Option;
|
||||
|
||||
const MetricSettingForm: ForwardRefRenderFunction<any, Props> = (
|
||||
{ metricList, domainId, themeData: uniqueMetricData, onSubmit },
|
||||
ref,
|
||||
) => {
|
||||
const [form] = Form.useForm();
|
||||
const [metricListOptions, setMetricListOptions] = useState<any>([]);
|
||||
const [unitState, setUnit] = useState<number | null>();
|
||||
const [periodState, setPeriod] = useState<string>();
|
||||
const getFormValidateFields = async () => {
|
||||
return await form.validateFields();
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getFormValidateFields,
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
form.resetFields();
|
||||
setUnit(null);
|
||||
setPeriod('');
|
||||
if (Object.keys(uniqueMetricData).length === 0) {
|
||||
return;
|
||||
}
|
||||
const { defaultMetrics = [], id } = uniqueMetricData;
|
||||
const defaultMetric = defaultMetrics[0];
|
||||
const recordId = id === -1 ? undefined : id;
|
||||
if (defaultMetric) {
|
||||
const { period, unit } = defaultMetric;
|
||||
setUnit(unit);
|
||||
setPeriod(period);
|
||||
form.setFieldsValue({
|
||||
...defaultMetric,
|
||||
id: recordId,
|
||||
});
|
||||
} else {
|
||||
form.setFieldsValue({
|
||||
id: recordId,
|
||||
});
|
||||
}
|
||||
}, [uniqueMetricData]);
|
||||
|
||||
useEffect(() => {
|
||||
const metricOption = metricList.map((item: any) => {
|
||||
return {
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
};
|
||||
});
|
||||
setMetricListOptions(metricOption);
|
||||
}, [metricList]);
|
||||
|
||||
const saveEntity = async () => {
|
||||
const values = await form.validateFields();
|
||||
const { id } = values;
|
||||
let saveDomainExtendQuery = addDomainExtend;
|
||||
if (id) {
|
||||
saveDomainExtendQuery = editDomainExtend;
|
||||
}
|
||||
const { code, msg, data } = await saveDomainExtendQuery({
|
||||
defaultMetrics: [{ ...values }],
|
||||
domainId,
|
||||
id,
|
||||
});
|
||||
|
||||
if (code === 200) {
|
||||
form.setFieldValue('id', data);
|
||||
onSubmit?.();
|
||||
message.success('保存成功');
|
||||
return;
|
||||
}
|
||||
message.error(msg);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form
|
||||
{...formLayout}
|
||||
form={form}
|
||||
layout="vertical"
|
||||
className={styles.form}
|
||||
initialValues={{
|
||||
unit: 7,
|
||||
period: 'DAY',
|
||||
}}
|
||||
>
|
||||
<FormItem hidden={true} name="id" label="ID">
|
||||
<Input placeholder="id" />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
name={'metricId'}
|
||||
label={
|
||||
<FormItemTitle
|
||||
title={'指标'}
|
||||
subTitle={'问答搜索结果选择中,如果没有指定指标,将会采用默认指标进行展示'}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Select
|
||||
allowClear
|
||||
showSearch
|
||||
style={{ width: '100%' }}
|
||||
filterOption={(inputValue: string, item: any) => {
|
||||
const { label } = item;
|
||||
if (label.includes(inputValue)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}}
|
||||
placeholder="请选择展示指标信息"
|
||||
options={metricListOptions}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label={
|
||||
<FormItemTitle
|
||||
title={'时间范围'}
|
||||
subTitle={'问答搜索结果选择中,如果没有指定时间范围,将会采用默认时间范围'}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Input.Group compact>
|
||||
<span
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
lineHeight: '32px',
|
||||
marginRight: '8px',
|
||||
}}
|
||||
>
|
||||
最近
|
||||
</span>
|
||||
<InputNumber
|
||||
value={unitState}
|
||||
style={{ width: '120px' }}
|
||||
onChange={(value) => {
|
||||
setUnit(value);
|
||||
form.setFieldValue('unit', value);
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
value={periodState}
|
||||
style={{ width: '100px' }}
|
||||
onChange={(value) => {
|
||||
form.setFieldValue('period', value);
|
||||
setPeriod(value);
|
||||
}}
|
||||
>
|
||||
<Option value="DAY">天</Option>
|
||||
<Option value="WEEK">周</Option>
|
||||
<Option value="MONTH">月</Option>
|
||||
<Option value="YEAR">年</Option>
|
||||
</Select>
|
||||
</Input.Group>
|
||||
</FormItem>
|
||||
|
||||
<FormItem name="unit" hidden={true}>
|
||||
<InputNumber />
|
||||
</FormItem>
|
||||
<FormItem name="period" hidden={true}>
|
||||
<Input />
|
||||
</FormItem>
|
||||
|
||||
<FormItem>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
saveEntity();
|
||||
}}
|
||||
>
|
||||
保 存
|
||||
</Button>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default forwardRef(MetricSettingForm);
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
labelStyles?: CSSStyleSheet;
|
||||
};
|
||||
|
||||
const FormLabelRequire: React.FC<Props> = ({ title, labelStyles = {} }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="ant-col ant-form-item-label">
|
||||
<label
|
||||
htmlFor="description"
|
||||
className="ant-form-item-required"
|
||||
title={title}
|
||||
style={{ fontSize: '16px', ...labelStyles }}
|
||||
>
|
||||
{title}
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default FormLabelRequire;
|
||||
@@ -26,7 +26,6 @@ const InfoTagList: React.FC<Props> = ({ value, createBtnString = '新增', onCha
|
||||
}, [value]);
|
||||
|
||||
const handleTagChange = (tagList: string[]) => {
|
||||
console.log(tagList, 'tagList');
|
||||
onChange?.(tagList);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,18 +1,33 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Form, Button, Modal, Steps, Input, Select, Switch, InputNumber } from 'antd';
|
||||
import {
|
||||
Form,
|
||||
Button,
|
||||
Modal,
|
||||
Steps,
|
||||
Input,
|
||||
Select,
|
||||
Switch,
|
||||
InputNumber,
|
||||
message,
|
||||
Result,
|
||||
} from 'antd';
|
||||
import MetricMeasuresFormTable from './MetricMeasuresFormTable';
|
||||
import { SENSITIVE_LEVEL_OPTIONS } from '../constant';
|
||||
import { formLayout } from '@/components/FormHelper/utils';
|
||||
import FormItemTitle from '@/components/FormHelper/FormItemTitle';
|
||||
import styles from './style.less';
|
||||
import { getMeasureListByDomainId } from '../service';
|
||||
import { creatExprMetric, updateExprMetric } from '../service';
|
||||
import { ISemantic } from '../data';
|
||||
import { history } from 'umi';
|
||||
|
||||
export type CreateFormProps = {
|
||||
datasourceId?: number;
|
||||
domainId: number;
|
||||
createModalVisible: boolean;
|
||||
metricItem: any;
|
||||
onCancel?: () => void;
|
||||
onSubmit: (values: any) => void;
|
||||
onSubmit?: (values: any) => void;
|
||||
};
|
||||
|
||||
const { Step } = Steps;
|
||||
@@ -21,6 +36,7 @@ const { TextArea } = Input;
|
||||
const { Option } = Select;
|
||||
|
||||
const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
||||
datasourceId,
|
||||
domainId,
|
||||
onCancel,
|
||||
createModalVisible,
|
||||
@@ -31,17 +47,18 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
const formValRef = useRef({} as any);
|
||||
const [form] = Form.useForm();
|
||||
const updateFormVal = (val: SaveDataSetForm) => {
|
||||
const updateFormVal = (val: any) => {
|
||||
formValRef.current = val;
|
||||
};
|
||||
|
||||
const [classMeasureList, setClassMeasureList] = useState<any[]>([]);
|
||||
const [classMeasureList, setClassMeasureList] = useState<ISemantic.IMeasure[]>([]);
|
||||
|
||||
const [exprTypeParamsState, setExprTypeParamsState] = useState<any>([]);
|
||||
const [exprTypeParamsState, setExprTypeParamsState] = useState<ISemantic.IMeasure[]>([]);
|
||||
|
||||
const [exprSql, setExprSql] = useState<string>('');
|
||||
|
||||
const [isPercentState, setIsPercentState] = useState<boolean>(false);
|
||||
const [hasMeasuresState, setHasMeasuresState] = useState<boolean>(true);
|
||||
|
||||
const forward = () => setCurrentStep(currentStep + 1);
|
||||
const backward = () => setCurrentStep(currentStep - 1);
|
||||
@@ -50,6 +67,12 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
||||
const { code, data } = await getMeasureListByDomainId(domainId);
|
||||
if (code === 200) {
|
||||
setClassMeasureList(data);
|
||||
if (datasourceId) {
|
||||
const hasMeasures = data.some(
|
||||
(item: ISemantic.IMeasure) => item.datasourceId === datasourceId,
|
||||
);
|
||||
setHasMeasuresState(hasMeasures);
|
||||
}
|
||||
return;
|
||||
}
|
||||
setClassMeasureList([]);
|
||||
@@ -74,7 +97,8 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
||||
if (currentStep < 1) {
|
||||
forward();
|
||||
} else {
|
||||
onSubmit?.(submitForm);
|
||||
// onSubmit?.(submitForm);
|
||||
await saveMetric(submitForm);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -118,15 +142,41 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
||||
useEffect(() => {
|
||||
if (isEdit) {
|
||||
initData();
|
||||
} else {
|
||||
// initFields([]);
|
||||
}
|
||||
}, [metricItem]);
|
||||
|
||||
const saveMetric = async (fieldsValue: any) => {
|
||||
const queryParams = {
|
||||
domainId,
|
||||
...fieldsValue,
|
||||
};
|
||||
const { typeParams } = queryParams;
|
||||
if (!typeParams?.expr) {
|
||||
message.error('请输入度量表达式');
|
||||
return;
|
||||
}
|
||||
if (!(Array.isArray(typeParams?.measures) && typeParams.measures.length > 0)) {
|
||||
message.error('请添加一个度量');
|
||||
return;
|
||||
}
|
||||
let saveMetricQuery = creatExprMetric;
|
||||
if (queryParams.id) {
|
||||
saveMetricQuery = updateExprMetric;
|
||||
}
|
||||
const { code, msg } = await saveMetricQuery(queryParams);
|
||||
if (code === 200) {
|
||||
message.success('编辑指标成功');
|
||||
onSubmit?.(queryParams);
|
||||
return;
|
||||
}
|
||||
message.error(msg);
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
if (currentStep === 1) {
|
||||
return (
|
||||
<MetricMeasuresFormTable
|
||||
datasourceId={datasourceId}
|
||||
typeParams={{
|
||||
measures: exprTypeParamsState,
|
||||
expr: exprSql,
|
||||
@@ -231,6 +281,9 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
||||
);
|
||||
};
|
||||
const renderFooter = () => {
|
||||
if (!hasMeasuresState) {
|
||||
return <Button onClick={onCancel}>取消</Button>;
|
||||
}
|
||||
if (currentStep === 1) {
|
||||
return (
|
||||
<>
|
||||
@@ -266,26 +319,47 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
||||
footer={renderFooter()}
|
||||
onCancel={onCancel}
|
||||
>
|
||||
<Steps style={{ marginBottom: 28 }} size="small" current={currentStep}>
|
||||
<Step title="基本信息" />
|
||||
<Step title="度量信息" />
|
||||
</Steps>
|
||||
<Form
|
||||
{...formLayout}
|
||||
form={form}
|
||||
initialValues={{
|
||||
...formValRef.current,
|
||||
}}
|
||||
onValuesChange={(value) => {
|
||||
const { isPercent } = value;
|
||||
if (isPercent !== undefined) {
|
||||
setIsPercentState(isPercent);
|
||||
{hasMeasuresState ? (
|
||||
<>
|
||||
<Steps style={{ marginBottom: 28 }} size="small" current={currentStep}>
|
||||
<Step title="基本信息" />
|
||||
<Step title="度量信息" />
|
||||
</Steps>
|
||||
<Form
|
||||
{...formLayout}
|
||||
form={form}
|
||||
initialValues={{
|
||||
...formValRef.current,
|
||||
}}
|
||||
onValuesChange={(value) => {
|
||||
const { isPercent } = value;
|
||||
if (isPercent !== undefined) {
|
||||
setIsPercentState(isPercent);
|
||||
}
|
||||
}}
|
||||
className={styles.form}
|
||||
>
|
||||
{renderContent()}
|
||||
</Form>
|
||||
</>
|
||||
) : (
|
||||
<Result
|
||||
status="warning"
|
||||
subTitle="当前数据源缺少度量,无法创建指标。请前往数据源配置中,将字段设置为度量"
|
||||
extra={
|
||||
<Button
|
||||
type="primary"
|
||||
key="console"
|
||||
onClick={() => {
|
||||
history.replace(`/semanticModel/${domainId}/dataSource`);
|
||||
onCancel?.();
|
||||
}}
|
||||
>
|
||||
去创建
|
||||
</Button>
|
||||
}
|
||||
}}
|
||||
className={styles.form}
|
||||
>
|
||||
{renderContent()}
|
||||
</Form>
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,17 +4,21 @@ import ProTable from '@ant-design/pro-table';
|
||||
import ProCard from '@ant-design/pro-card';
|
||||
import SqlEditor from '@/components/SqlEditor';
|
||||
import BindMeasuresTable from './BindMeasuresTable';
|
||||
import FormLabelRequire from './FormLabelRequire';
|
||||
import { ISemantic } from '../data';
|
||||
|
||||
type Props = {
|
||||
typeParams: any;
|
||||
measuresList: any[];
|
||||
onFieldChange: (measures: any[]) => void;
|
||||
datasourceId?: number;
|
||||
typeParams: ISemantic.ITypeParams;
|
||||
measuresList: ISemantic.IMeasure[];
|
||||
onFieldChange: (measures: ISemantic.IMeasure[]) => void;
|
||||
onSqlChange: (sql: string) => void;
|
||||
};
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
const MetricMeasuresFormTable: React.FC<Props> = ({
|
||||
datasourceId,
|
||||
typeParams,
|
||||
measuresList,
|
||||
onFieldChange,
|
||||
@@ -127,7 +131,7 @@ const MetricMeasuresFormTable: React.FC<Props> = ({
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<ProTable
|
||||
actionRef={actionRef}
|
||||
headerTitle="度量列表"
|
||||
headerTitle={<FormLabelRequire title="度量列表" />}
|
||||
tooltip="基于本主题域下所有数据源的度量来创建指标,且该列表的度量为了加以区分,均已加上数据源名称作为前缀,选中度量后,可基于这几个度量来写表达式,若是选中的度量来自不同的数据源,系统将会自动join来计算该指标"
|
||||
rowKey="name"
|
||||
columns={columns}
|
||||
@@ -149,7 +153,7 @@ const MetricMeasuresFormTable: React.FC<Props> = ({
|
||||
]}
|
||||
/>
|
||||
<ProCard
|
||||
title={'度量表达式'}
|
||||
title={<FormLabelRequire title="度量表达式" />}
|
||||
tooltip="度量表达式由上面选择的度量组成,如选择了度量A和B,则可将表达式写成A+B"
|
||||
>
|
||||
<SqlEditor
|
||||
@@ -165,7 +169,11 @@ const MetricMeasuresFormTable: React.FC<Props> = ({
|
||||
</Space>
|
||||
{measuresModalVisible && (
|
||||
<BindMeasuresTable
|
||||
measuresList={measuresList}
|
||||
measuresList={
|
||||
datasourceId && Array.isArray(measuresList)
|
||||
? measuresList.filter((item) => item.datasourceId === datasourceId)
|
||||
: measuresList
|
||||
}
|
||||
selectedMeasuresList={measuresParams?.measures || []}
|
||||
onSubmit={async (values: any[]) => {
|
||||
const measures = values.map(({ bizName, name, expr, datasourceId }) => {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Space, Tooltip } from 'antd';
|
||||
import React from 'react';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons';
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
tooltips: string;
|
||||
};
|
||||
|
||||
const TableTitleTooltips: React.FC<Props> = ({ title, tooltips }) => {
|
||||
return (
|
||||
<>
|
||||
<Space>
|
||||
<span>{title}</span>
|
||||
<Tooltip title={tooltips}>
|
||||
<ExclamationCircleOutlined />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableTitleTooltips;
|
||||
@@ -297,4 +297,18 @@
|
||||
background: transparent;
|
||||
border-style: dashed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.semanticGraphCanvas {
|
||||
position: relative;
|
||||
.toolbar{
|
||||
position: absolute;
|
||||
width: 200px;
|
||||
z-index: 999;
|
||||
right: 0;
|
||||
top: 5px;
|
||||
}
|
||||
.canvasContainer {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user