semantic-fe visual modeling pr (#21)

* [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.

* [improvement][semantic-fe] Submitted a new version of the visual modeling tool.
[improvement][semantic-fe] Fixed an issue with the initialization of YoY and MoM metrics in Q&A settings.
[improvement][semantic-fe] Added a version field to the database settings.
[improvement][semantic-fe] 1. Added the ability to set YoY and MoM metrics in Q&A settings.2. Moved dimension value editing from the dimension editing window to the dimension list.

---------

Co-authored-by: tristanliu <tristanliu@tencent.com>
This commit is contained in:
tristanliu
2023-07-31 11:23:37 +08:00
committed by GitHub
parent e2b2d31429
commit 0ac652c5d9
50 changed files with 2375 additions and 1188 deletions

View File

@@ -11,7 +11,7 @@ import {
} from './utils';
import styles from '../style.less';
import { ISemantic } from '../../data';
import { ChatConfigType, TransType } from '../../enum';
import { ChatConfigType, TransType, SemanticNodeType } from '../../enum';
import TransTypeTag from '../TransTypeTag';
type Props = {
@@ -99,7 +99,7 @@ const DefaultSettingForm: ForwardRefRenderFunction<any, Props> = (
name,
label: (
<>
<TransTypeTag type={TransType.DIMENSION} />
<TransTypeTag type={SemanticNodeType.DIMENSION} />
{name}
</>
),
@@ -115,7 +115,7 @@ const DefaultSettingForm: ForwardRefRenderFunction<any, Props> = (
name,
label: (
<>
<TransTypeTag type={TransType.METRIC} />
<TransTypeTag type={SemanticNodeType.METRIC} />
{name}
</>
),
@@ -198,30 +198,56 @@ const DefaultSettingForm: ForwardRefRenderFunction<any, Props> = (
</FormItem>
)}
{chatConfigType === ChatConfigType.AGG && (
<FormItem
name="metricIds"
label={
<FormItemTitle
title={'指标'}
subTitle={'问答搜索结果选择中,如果没有指定指标,将会采用默认指标进行展示'}
<>
{/* <FormItem
name="metricIds"
label={
<FormItemTitle
title={'指标'}
subTitle={'问答搜索结果选择中,如果没有指定指标,将会采用默认指标进行展示'}
/>
}
>
<Select
mode="multiple"
allowClear
style={{ width: '100%' }}
filterOption={(inputValue: string, item: any) => {
const { label } = item;
if (label.includes(inputValue)) {
return true;
}
return false;
}}
placeholder="请选择展示指标信息"
options={metricListOptions}
/>
}
>
<Select
mode="multiple"
allowClear
style={{ width: '100%' }}
filterOption={(inputValue: string, item: any) => {
const { label } = item;
if (label.includes(inputValue)) {
return true;
}
return false;
}}
placeholder="请选择展示指标信息"
options={metricListOptions}
/>
</FormItem>
</FormItem>
<FormItem
name="ratioMetricIds"
label={
<FormItemTitle
title={'同环比指标'}
subTitle={'问答搜索含有指定的指标,将会同时计算该指标最后一天的同环比'}
/>
}
>
<Select
mode="multiple"
allowClear
style={{ width: '100%' }}
filterOption={(inputValue: string, item: any) => {
const { label } = item;
if (label.includes(inputValue)) {
return true;
}
return false;
}}
placeholder="请选择同环比指标"
options={metricListOptions}
/>
</FormItem> */}
</>
)}
<FormItem

View File

@@ -15,7 +15,7 @@ type Props = {
settingSourceList: any[];
onCancel: () => void;
visible: boolean;
onSubmit: (params?: any) => void;
onSubmit: (params?: { isSilenceSubmit?: boolean }) => void;
};
const dimensionConfig = {
@@ -72,7 +72,8 @@ const DimensionAndMetricVisibleModal: React.FC<Props> = ({
}
}, [entityData, settingSourceList]);
const saveEntity = async () => {
const saveEntity = async (submitData: any, isSilenceSubmit = false) => {
const { selectedKeyList, knowledgeInfosMap } = submitData;
const globalKnowledgeConfigFormFields = await formRef?.current?.getFormValidateFields?.();
let globalKnowledgeConfig = entityData.globalKnowledgeConfig;
if (globalKnowledgeConfigFormFields) {
@@ -127,8 +128,10 @@ const DimensionAndMetricVisibleModal: React.FC<Props> = ({
id,
});
if (code === 200) {
onSubmit?.();
message.success('保存成功');
if (!isSilenceSubmit) {
message.success('保存成功');
}
onSubmit?.({ isSilenceSubmit });
return;
}
message.error(msg);
@@ -145,7 +148,7 @@ const DimensionAndMetricVisibleModal: React.FC<Props> = ({
<Button
type="primary"
onClick={() => {
saveEntity();
saveEntity({ selectedKeyList, knowledgeInfosMap });
}}
>
@@ -160,8 +163,9 @@ const DimensionAndMetricVisibleModal: React.FC<Props> = ({
key: 'visibleSetting',
children: (
<DimensionMetricVisibleTransfer
onKnowledgeInfosMapChange={(knowledgeInfosMap) => {
setKnowledgeInfosMap(knowledgeInfosMap);
onKnowledgeInfosMapChange={(knowledgeInfos) => {
setKnowledgeInfosMap(knowledgeInfos);
saveEntity({ selectedKeyList, knowledgeInfosMap: knowledgeInfos }, true);
}}
knowledgeInfosMap={knowledgeInfosMap}
titles={settingTypeConfig.titles}
@@ -169,6 +173,7 @@ const DimensionAndMetricVisibleModal: React.FC<Props> = ({
targetList={selectedKeyList}
onChange={(newTargetKeys) => {
handleTransferChange(newTargetKeys);
saveEntity({ selectedKeyList: newTargetKeys, knowledgeInfosMap }, true);
}}
/>
),
@@ -177,10 +182,12 @@ const DimensionAndMetricVisibleModal: React.FC<Props> = ({
label: '全局维度值过滤',
key: 'dimensionValueFilter',
children: (
<DimensionValueSettingForm
initialValues={globalKnowledgeConfigInitialValues}
ref={formRef}
/>
<div style={{ margin: '0 auto', width: '975px' }}>
<DimensionValueSettingForm
initialValues={globalKnowledgeConfigInitialValues}
ref={formRef}
/>
</div>
),
},
];

View File

@@ -74,9 +74,11 @@ const DimensionMetricVisibleForm: ForwardRefRenderFunction<any, Props> = ({
onCancel={() => {
setDimensionModalVisible(false);
}}
onSubmit={() => {
onSubmit={(params) => {
onSubmit?.();
setDimensionModalVisible(false);
if (!params?.isSilenceSubmit) {
setDimensionModalVisible(false);
}
}}
/>
)}

View File

@@ -5,8 +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';
import CommonEditList from '../../components/CommonEditList';
type Props = {
initialValues: any;
onSubmit?: () => void;

View File

@@ -1,14 +1,13 @@
import { useEffect, useState, forwardRef, useImperativeHandle } from 'react';
import type { ForwardRefRenderFunction } from 'react';
import { message, Form, Input, Select, Button } from 'antd';
import { addDomainExtend, editDomainExtend } from '../../service';
import type { ISemantic, IChatConfig } from '../../data';
import { updateDomain } from '../../service';
import type { ISemantic } from '../../data';
import { formLayout } from '@/components/FormHelper/utils';
import { formatRichEntityDataListToIds } from './utils';
import styles from '../style.less';
type Props = {
entityData: IChatConfig.IChatRichConfig;
entityData?: { id: number; names: string[] };
dimensionList: ISemantic.IDimensionList;
domainId: number;
onSubmit: () => void;
@@ -21,22 +20,20 @@ const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
ref,
) => {
const [form] = Form.useForm();
const formatEntityData = formatRichEntityDataListToIds(entityData);
const [dimensionListOptions, setDimensionListOptions] = useState<any>([]);
const getFormValidateFields = async () => {
return await form.validateFields();
};
useEffect(() => {
form.resetFields();
if (!entityData?.entity) {
if (!entityData) {
return;
}
form.setFieldsValue({
...formatEntityData.entity,
id: formatEntityData.id,
...entityData,
name: entityData.names.join(','),
});
}, [entityData]);
@@ -56,20 +53,14 @@ const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
const saveEntity = async () => {
const values = await form.validateFields();
const { id, name } = values;
let saveDomainExtendQuery = addDomainExtend;
if (id) {
saveDomainExtendQuery = editDomainExtend;
}
const { code, msg, data } = await saveDomainExtendQuery({
chatDetailConfig: {
...formatEntityData,
entity: {
...values,
names: name.split(','),
},
const { name } = values;
const { code, msg, data } = await updateDomain({
entity: {
...values,
names: name.split(','),
},
id,
id: domainId,
domainId,
});

View File

@@ -5,7 +5,7 @@ import { connect } from 'umi';
import type { StateType } from '../../model';
import { getDomainExtendDetailConfig } from '../../service';
import ProCard from '@ant-design/pro-card';
import EntityCreateForm from './EntityCreateForm';
import DefaultSettingForm from './DefaultSettingForm';
import type { IChatConfig } from '../../data';
import DimensionMetricVisibleForm from './DimensionMetricVisibleForm';
@@ -26,8 +26,6 @@ const EntitySection: React.FC<Props> = ({
const [entityData, setentityData] = useState<IChatConfig.IChatRichConfig>();
const entityCreateRef = useRef<any>({});
const queryThemeListData: any = async () => {
const { code, data } = await getDomainExtendDetailConfig({
domainId: selectDomainId,
@@ -58,25 +56,6 @@ const EntitySection: React.FC<Props> = ({
return (
<div style={{ width: 800, margin: '0 auto' }}>
<Space direction="vertical" style={{ width: '100%' }} size={20}>
{chatConfigType === 'detail' && entityData && (
<ProCard title="实体" bordered>
<EntityCreateForm
ref={entityCreateRef}
domainId={Number(selectDomainId)}
entityData={entityData}
dimensionList={dimensionList.filter((item) => {
const blackDimensionList = entityData?.visibility?.blackDimIdList;
if (Array.isArray(blackDimensionList)) {
return !blackDimensionList.includes(item.id);
}
return false;
})}
onSubmit={() => {
queryThemeListData();
}}
/>
</ProCard>
)}
<ProCard bordered title="问答可见">
<DimensionMetricVisibleForm
chatConfigKey={

View File

@@ -0,0 +1,69 @@
import { message, Space } from 'antd';
import React, { useState, useEffect, useRef } from 'react';
import type { Dispatch } from 'umi';
import { connect } from 'umi';
import type { StateType } from '../../model';
import { getDomainDetail } from '../../service';
import ProCard from '@ant-design/pro-card';
import EntityCreateForm from './EntityCreateForm';
import type { IChatConfig } from '../../data';
type Props = {
dispatch: Dispatch;
domainManger: StateType;
};
const EntitySettingSection: React.FC<Props> = ({ domainManger }) => {
const { selectDomainId, dimensionList, metricList } = domainManger;
const [entityData, setEntityData] = useState<IChatConfig.IChatRichConfig>();
const entityCreateRef = useRef<any>({});
const queryDomainData: any = async () => {
const { code, data } = await getDomainDetail({
domainId: selectDomainId,
});
if (code === 200) {
const { entity } = data;
setEntityData(entity);
return;
}
message.error('获取问答设置信息失败');
};
const initPage = async () => {
queryDomainData();
};
useEffect(() => {
initPage();
}, [selectDomainId]);
return (
<div style={{ width: 800, margin: '0 auto' }}>
<Space direction="vertical" style={{ width: '100%' }} size={20}>
{
<ProCard title="实体" bordered>
<EntityCreateForm
ref={entityCreateRef}
domainId={Number(selectDomainId)}
entityData={entityData}
dimensionList={dimensionList}
onSubmit={() => {
queryDomainData();
}}
/>
</ProCard>
}
</Space>
</div>
);
};
export default connect(({ domainManger }: { domainManger: StateType }) => ({
domainManger,
}))(EntitySettingSection);

View File

@@ -0,0 +1,87 @@
import { message } from 'antd';
import React, { useState, useEffect } from 'react';
import { connect } from 'umi';
import type { StateType } from '../../model';
import { getDomainExtendConfig, addDomainExtend, editDomainExtend } from '../../service';
import ProCard from '@ant-design/pro-card';
import TextAreaCommonEditList from '../../components/CommonEditList/TextArea';
type Props = {
domainManger: StateType;
};
const RecommendedQuestionsSection: React.FC<Props> = ({ domainManger }) => {
const { selectDomainId } = domainManger;
const [questionData, setQuestionData] = useState<string[]>([]);
const [currentRecordId, setCurrentRecordId] = useState<number>(0);
const queryThemeListData: any = async () => {
const { code, data } = await getDomainExtendConfig({
domainId: selectDomainId,
});
if (code === 200) {
const target = data?.[0] || {};
if (Array.isArray(target.recommendedQuestions)) {
setQuestionData(
target.recommendedQuestions.map((item: { question: string }) => {
return item.question;
}),
);
setCurrentRecordId(target.id || 0);
} else {
setQuestionData([]);
setCurrentRecordId(0);
}
return;
}
message.error('获取问答设置信息失败');
};
const saveEntity = async (list: string[]) => {
let saveDomainExtendQuery = addDomainExtend;
if (currentRecordId) {
saveDomainExtendQuery = editDomainExtend;
}
const { code, msg } = await saveDomainExtendQuery({
recommendedQuestions: list.map((question: string) => {
return { question };
}),
id: currentRecordId,
domainId: selectDomainId,
});
if (code === 200) {
return;
}
message.error(msg);
};
const initPage = async () => {
queryThemeListData();
};
useEffect(() => {
initPage();
}, [selectDomainId]);
return (
<div style={{ width: 800, margin: '0 auto' }}>
<ProCard bordered title="问题推荐列表">
<TextAreaCommonEditList
value={questionData}
onChange={(list) => {
saveEntity(list);
setQuestionData(list);
}}
/>
</ProCard>
</div>
);
};
export default connect(({ domainManger }: { domainManger: StateType }) => ({
domainManger,
}))(RecommendedQuestionsSection);

View File

@@ -18,8 +18,9 @@ export const formatRichEntityDataListToIds = (
const detailData: {
dimensionIds: number[];
metricIds: number[];
} = { dimensionIds: [], metricIds: [] };
const { dimensions, metrics } = chatDefaultConfig || {};
ratioMetricIds: number[];
} = { dimensionIds: [], metricIds: [], ratioMetricIds: [] };
const { dimensions, metrics, ratioMetrics } = chatDefaultConfig || {};
if (Array.isArray(dimensions)) {
detailData.dimensionIds = dimensions.map((item: ISemantic.IDimensionItem) => {
return item.id;
@@ -30,6 +31,12 @@ export const formatRichEntityDataListToIds = (
return item.id;
});
}
if (Array.isArray(ratioMetrics)) {
detailData.ratioMetricIds = ratioMetrics.map((item: ISemantic.IMetricItem) => {
return item.id;
});
}
let entitySetting = {};
if (entity) {
const entityItem = entity.dimItem;