[improvement][semantic-fe] Adding the ability to edit relationships between models in the canvas. (#431)

* [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.
This commit is contained in:
tristanliu
2023-11-27 21:26:55 +08:00
committed by GitHub
parent 52eca178d3
commit 27ebda3439
32 changed files with 3134 additions and 437 deletions

View File

@@ -68,10 +68,6 @@ const BindMeasuresTable: React.FC<CreateFormProps> = ({
dataIndex: 'agg',
title: '算子类型',
},
{
dataIndex: 'datasourceName',
title: '所属数据源',
},
];
const renderFooter = () => {
return (

View File

@@ -1,167 +0,0 @@
import type { ActionType, ProColumns } from '@ant-design/pro-table';
import ProTable from '@ant-design/pro-table';
import { message, Button, Space, Popconfirm } from 'antd';
import React, { useRef, useState } from 'react';
import type { Dispatch } from 'umi';
import { connect } from 'umi';
import ClassDataSourceTypeModal from './ClassDataSourceTypeModal';
import type { StateType } from '../model';
import { getDatasourceList, deleteDatasource } from '../service';
import moment from 'moment';
type Props = {
dispatch: Dispatch;
domainManger: StateType;
};
const ClassDataSourceTable: React.FC<Props> = ({ dispatch, domainManger }) => {
const { selectModelId } = domainManger;
const [dataSourceItem, setDataSourceItem] = useState<any>();
const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false);
const actionRef = useRef<ActionType>();
const columns: ProColumns[] = [
{
dataIndex: 'id',
title: 'ID',
},
{
dataIndex: 'name',
title: '数据源名称',
},
{
dataIndex: 'bizName',
title: '英文名称',
},
{
dataIndex: 'createdBy',
title: '创建人',
},
{
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') : '-';
},
},
{
title: '操作',
dataIndex: 'x',
valueType: 'option',
width: 100,
render: (_, record) => {
return (
<Space>
<a
key="datasourceEditBtn"
onClick={() => {
setDataSourceItem(record);
setCreateDataSourceModalOpen(true);
}}
>
</a>
<Popconfirm
title="确认删除?"
okText="是"
cancelText="否"
onConfirm={async () => {
const { code, msg } = await deleteDatasource(record.id);
if (code === 200) {
setDataSourceItem(undefined);
actionRef.current?.reload();
} else {
message.error(msg);
}
}}
>
<a
key="datasourceDeleteBtn"
onClick={() => {
setDataSourceItem(record);
}}
>
</a>
</Popconfirm>
</Space>
);
},
},
];
const queryDataSourceList = async (params: any) => {
dispatch({
type: 'domainManger/setPagination',
payload: {
...params,
},
});
const { code, data, msg } = await getDatasourceList({ ...params });
let resData: any = {};
if (code === 200) {
resData = {
data: data || [],
success: true,
};
} else {
message.error(msg);
resData = {
data: [],
total: 0,
success: false,
};
}
return resData;
};
return (
<>
<ProTable
actionRef={actionRef}
rowKey="id"
columns={columns}
params={{ modelId: selectModelId }}
request={queryDataSourceList}
pagination={false}
search={false}
size="small"
options={{ reload: false, density: false, fullScreen: false }}
toolBarRender={() => [
<Button
key="create"
type="primary"
onClick={() => {
setDataSourceItem(undefined);
setCreateDataSourceModalOpen(true);
}}
>
</Button>,
]}
/>
{createDataSourceModalOpen && (
<ClassDataSourceTypeModal
open={createDataSourceModalOpen}
onCancel={() => {
setCreateDataSourceModalOpen(false);
}}
dataSourceItem={dataSourceItem}
onSubmit={() => {
actionRef.current?.reload();
}}
/>
)}
</>
);
};
export default connect(({ domainManger }: { domainManger: StateType }) => ({
domainManger,
}))(ClassDataSourceTable);

View File

@@ -1,4 +1,4 @@
import { Button, Drawer, Result, Modal, Card, Row, Col } from 'antd';
import { Drawer, Modal, Card, Row, Col } from 'antd';
import { ConsoleSqlOutlined, CoffeeOutlined } from '@ant-design/icons';
import React, { useState, useEffect } from 'react';
import type { Dispatch } from 'umi';
@@ -47,7 +47,7 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({
setCreateDataSourceModalOpen(open);
return;
}
if (dataSourceItem?.datasourceDetail?.queryType === 'table_query') {
if (dataSourceItem?.modelDetail?.queryType === 'table_query') {
setDataSourceModalVisible(true);
} else {
setCreateModalVisible(true);
@@ -80,16 +80,16 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({
};
const queryTableColumnListByScript = async (dataSource: IDataSource.IDataSourceItem) => {
if (!dataSource) {
if (!dataSource?.modelDetail?.sqlQuery) {
return;
}
const { code, data, msg } = await excuteSql({
sql: dataSource.datasourceDetail?.sqlQuery,
const { code, data } = await excuteSql({
sql: dataSource.modelDetail?.sqlQuery,
id: dataSource.databaseId,
});
if (code === 200) {
fetchTaskResult(data);
setSql(dataSource?.datasourceDetail?.sqlQuery);
setSql(dataSource?.modelDetail?.sqlQuery);
}
};
@@ -124,7 +124,7 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({
/>
}
>
<Meta title="快速创建" description="自动进行数据源可视化创建" />
<Meta title="快速创建" description="自动进行模型可视化创建" />
</Card>
</Col>
<Col span={12}>
@@ -143,7 +143,7 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({
/>
}
>
<Meta title="SQL脚本" description="自定义SQL脚本创建数据源" />
<Meta title="SQL脚本" description="自定义SQL脚本创建模型" />
</Card>
</Col>
</Row>
@@ -207,7 +207,6 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({
setSql(sql);
setScriptColumns(columns);
setCurrentDatabaseId(databaseId);
onSubmit?.();
setDataSourceEditOpen(false);
}}
/>

View File

@@ -8,14 +8,14 @@ import type { StateType } from '../model';
import { StatusEnum } from '../enum';
import { SENSITIVE_LEVEL_ENUM } from '../constant';
import {
getDatasourceList,
getModelList,
getDimensionList,
deleteDimension,
batchUpdateDimensionStatus,
} from '../service';
import DimensionInfoModal from './DimensionInfoModal';
import DimensionValueSettingModal from './DimensionValueSettingModal';
import { updateDimension } from '../service';
// import { updateDimension } from '../service';
import { ISemantic, IDataSource } from '../data';
import moment from 'moment';
import BatchCtrlDropDownButton from '@/components/BatchCtrlDropDownButton';
@@ -27,7 +27,7 @@ type Props = {
};
const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
const { selectModelId: modelId } = domainManger;
const { selectModelId: modelId, selectDomainId: domainId } = domainManger;
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
const [dimensionItem, setDimensionItem] = useState<ISemantic.IDimensionItem>();
const [dataSourceList, setDataSourceList] = useState<IDataSource.IDataSourceItem[]>([]);
@@ -80,7 +80,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
};
const queryDataSourceList = async () => {
const { code, data, msg } = await getDatasourceList({ modelId });
const { code, data, msg } = await getModelList(domainId);
if (code === 200) {
setDataSourceList(data);
} else {
@@ -92,20 +92,20 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
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 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) {
@@ -213,11 +213,6 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
}
},
},
{
dataIndex: 'datasourceName',
title: '数据源名称',
search: false,
},
{
dataIndex: 'createdBy',
title: '创建人',
@@ -299,6 +294,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
title="确认删除?"
okText="是"
cancelText="否"
placement="left"
onConfirm={async () => {
const { code, msg } = await deleteDimension(record.id);
if (code === 200) {
@@ -331,29 +327,29 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
},
};
const dropdownButtonItems = [
{
key: 'batchStart',
label: '批量启用',
},
{
key: 'batchStop',
label: '批量停用',
},
{
key: 'batchDelete',
label: (
<Popconfirm
title="确定批量删除吗?"
onConfirm={() => {
queryBatchUpdateStatus(selectedRowKeys, StatusEnum.DELETED);
}}
>
<a></a>
</Popconfirm>
),
},
];
// const dropdownButtonItems = [
// {
// key: 'batchStart',
// label: '批量启用',
// },
// {
// key: 'batchStop',
// label: '批量停用',
// },
// {
// key: 'batchDelete',
// label: (
// <Popconfirm
// title="确定批量删除吗?"
// onConfirm={() => {
// queryBatchUpdateStatus(selectedRowKeys, StatusEnum.DELETED);
// }}
// >
// <a>批量删除</a>
// </Popconfirm>
// ),
// },
// ];
const onMenuClick = (key: string) => {
switch (key) {

View File

@@ -155,20 +155,6 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
>
<Input placeholder="名称不可重复" disabled={isEdit} />
</FormItem>
<FormItem
hidden={isEdit}
name="datasourceId"
label="所属数据源"
rules={[{ required: true, message: '请选择所属数据源' }]}
>
<Select placeholder="请选择数据源" disabled={isEdit}>
{dataSourceList.map((item) => (
<Option key={item.id} value={item.id}>
{item.name}
</Option>
))}
</Select>
</FormItem>
<FormItem label="别名">
<Row>
<Col flex="1 1 200px">

View File

@@ -9,7 +9,7 @@ import type { StateType } from '../model';
import TransTypeTag from './TransTypeTag';
import TableTitleTooltips from '../components/TableTitleTooltips';
import { ISemantic } from '../data';
import { getDimensionList } from '../service';
import { getDimensionList, getDimensionInModelCluster } from '../service';
import { SemanticNodeType, TransType } from '../enum';
interface RecordType {
@@ -45,9 +45,9 @@ const DimensionMetricRelationTableTransfer: React.FC<Props> = ({
}, [metricItem, relationsInitialValue]);
const queryDimensionList = async () => {
const { code, data, msg } = await getDimensionList({ modelId: metricItem?.modelId || modelId });
if (code === 200 && Array.isArray(data?.list)) {
setDimensionList(data.list);
const { code, data, msg } = await getDimensionInModelCluster(metricItem?.modelId || modelId);
if (code === 200 && Array.isArray(data)) {
setDimensionList(data);
} else {
message.error(msg);
}

View File

@@ -2,11 +2,9 @@ import { Tabs, Breadcrumb, Space } from 'antd';
import React from 'react';
import { connect, history } from 'umi';
import ClassDataSourceTable from './ClassDataSourceTable';
import ClassDimensionTable from './ClassDimensionTable';
import ClassMetricTable from './ClassMetricTable';
import PermissionSection from './Permission/PermissionSection';
// import EntitySettingSection from './Entity/EntitySettingSection';
import ChatSettingSection from '../ChatSetting/ChatSettingSection';
import OverView from './OverView';
import styles from './style.less';
@@ -38,7 +36,7 @@ const DomainManagerTab: React.FC<Props> = ({
onBackDomainBtnClick,
onMenuChange,
}) => {
const defaultTabKey = 'xflow';
const defaultTabKey = 'dimenstion';
const { selectDomainId, domainList, selectModelId, selectModelName, selectDomainName } =
domainManger;
@@ -55,6 +53,15 @@ const DomainManagerTab: React.FC<Props> = ({
/>
),
},
{
label: '画布',
key: 'xflow',
children: (
<div style={{ width: '100%' }}>
<SemanticGraphCanvas />
</div>
),
},
{
label: '权限管理',
key: 'permissonSetting',
@@ -74,21 +81,6 @@ const DomainManagerTab: React.FC<Props> = ({
});
const isModelItem = [
{
label: '画布',
key: 'xflow',
children: (
<div style={{ width: '100%' }}>
<SemanticGraphCanvas />
</div>
),
},
{
label: '数据源',
key: 'dataSource',
children: <ClassDataSourceTable />,
},
{
label: '维度',
key: 'dimenstion',
@@ -99,11 +91,6 @@ const DomainManagerTab: React.FC<Props> = ({
key: 'metric',
children: <ClassMetricTable />,
},
// {
// label: '实体',
// key: 'entity',
// children: <EntitySettingSection />,
// },
{
label: '权限管理',
key: 'permissonSetting',

View File

@@ -22,7 +22,7 @@ import { SENSITIVE_LEVEL_OPTIONS } from '../constant';
import { formLayout } from '@/components/FormHelper/utils';
import FormItemTitle from '@/components/FormHelper/FormItemTitle';
import styles from './style.less';
import { getMeasureListByModelId } from '../service';
import { getMeasureListByModelId, getModelDetail } from '../service';
import DimensionAndMetricRelationModal from './DimensionAndMetricRelationModal';
import TableTitleTooltips from '../components/TableTitleTooltips';
import { creatExprMetric, updateExprMetric, mockMetricAlias, getMetricTags } from '../service';
@@ -88,16 +88,19 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
const backward = () => setCurrentStep(currentStep - 1);
const queryClassMeasureList = async () => {
const { code, data } = await getMeasureListByModelId(modelId);
// const { code, data } = await getMeasureListByModelId(modelId);
const { code, data } = await getModelDetail({ modelId });
if (code === 200) {
setClassMeasureList(data);
if (datasourceId) {
const hasMeasures = data.some(
(item: ISemantic.IMeasure) => item.datasourceId === datasourceId,
);
setHasMeasuresState(hasMeasures);
if (Array.isArray(data?.modelDetail?.measures)) {
setClassMeasureList(data);
if (datasourceId) {
const hasMeasures = data.some(
(item: ISemantic.IMeasure) => item.datasourceId === datasourceId,
);
setHasMeasuresState(hasMeasures);
}
return;
}
return;
}
setClassMeasureList([]);
};

View File

@@ -96,7 +96,7 @@ const ModelCreateFormModal: React.FC<ModelCreateFormModalProps> = (props) => {
return (
<Modal
width={640}
styles={{ padding: '32px 40px 48px' }}
// styles={{ padding: '32px 40px 48px' }}
destroyOnClose
title={'模型信息'}
open={true}
@@ -125,7 +125,18 @@ const ModelCreateFormModal: React.FC<ModelCreateFormModalProps> = (props) => {
>
<Input placeholder="请输入模型英文名称" />
</FormItem>
<FormItem name="alias" label="别名">
<FormItem
name="alias"
label="别名"
getValueFromEvent={(value) => {
return value.join(',');
}}
getValueProps={(value) => {
return {
value: value.split(','),
};
}}
>
<Select
mode="tags"
placeholder="输入别名后回车确认,多别名输入、复制粘贴支持英文逗号自动分隔"

View File

@@ -7,6 +7,7 @@ import type { Dispatch } from 'umi';
import { connect } from 'umi';
import type { StateType } from '../model';
import { deleteModel, updateModel } from '../service';
import ClassDataSourceTypeModal from './ClassDataSourceTypeModal';
import ModelCreateFormModal from './ModelCreateFormModal';
@@ -33,6 +34,7 @@ const ModelTable: React.FC<Props> = ({
const [modelCreateFormModalVisible, setModelCreateFormModalVisible] = useState<boolean>(false);
const [modelItem, setModelItem] = useState<ISemantic.IModelItem>();
const [saveLoading, setSaveLoading] = useState<boolean>(false);
const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false);
const actionRef = useRef<ActionType>();
const updateModelStatus = async (modelData: ISemantic.IModelItem) => {
@@ -141,7 +143,8 @@ const ModelTable: React.FC<Props> = ({
key="metricEditBtn"
onClick={() => {
setModelItem(record);
setModelCreateFormModalVisible(true);
// setModelCreateFormModalVisible(true);
setCreateDataSourceModalOpen(true);
}}
>
@@ -217,7 +220,9 @@ const ModelTable: React.FC<Props> = ({
type="primary"
onClick={() => {
setModelItem(undefined);
setModelCreateFormModalVisible(true);
// setModelCreateFormModalVisible(true);
setCreateDataSourceModalOpen(true);
}}
>
@@ -225,6 +230,20 @@ const ModelTable: React.FC<Props> = ({
]
}
/>
{createDataSourceModalOpen && (
<ClassDataSourceTypeModal
open={createDataSourceModalOpen}
dataSourceItem={modelItem}
onSubmit={() => {
// actionRef.current?.reload();
// setCreateDataSourceModalOpen(false);
onModelChange?.();
}}
onCancel={() => {
setCreateDataSourceModalOpen(false);
}}
/>
)}
{modelCreateFormModalVisible && (
<ModelCreateFormModal
domainId={selectDomainId}

View File

@@ -235,6 +235,7 @@
.classTable {
// padding: 0 20px;
:global {
.ant-pro-table-search-query-filter {
margin-bottom: 0;
@@ -253,6 +254,9 @@
.ant-table-selection-column {
text-align: left;
}
.ant-pro-card-body {
padding: 0;
}
}
}