mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-12 04:27:39 +00:00
[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:
@@ -224,6 +224,7 @@ ol {
|
|||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
h3 {
|
h3 {
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
|
margin: 0;
|
||||||
border-bottom: 1px solid #4E86F5;
|
border-bottom: 1px solid #4E86F5;
|
||||||
}
|
}
|
||||||
li {
|
li {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import React, { useState } from 'react';
|
|||||||
import { Form, Input, Spin, Select, message } from 'antd';
|
import { Form, Input, Spin, Select, message } from 'antd';
|
||||||
import type { FormInstance } from 'antd/lib/form';
|
import type { FormInstance } from 'antd/lib/form';
|
||||||
import { getDbNames, getTables } from '../../service';
|
import { getDbNames, getTables } from '../../service';
|
||||||
import SqlEditor from '@/components/SqlEditor';
|
|
||||||
import { ISemantic } from '../../data';
|
import { ISemantic } from '../../data';
|
||||||
|
import { isString } from 'lodash';
|
||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
@@ -109,28 +109,40 @@ const DataSourceBasicForm: React.FC<Props> = ({ isEdit, databaseConfigList, mode
|
|||||||
|
|
||||||
<FormItem
|
<FormItem
|
||||||
name="name"
|
name="name"
|
||||||
label="数据源中文名"
|
label="模型中文名"
|
||||||
rules={[{ required: true, message: '请输入数据源中文名' }]}
|
rules={[{ required: true, message: '请输入模型中文名' }]}
|
||||||
>
|
>
|
||||||
<Input placeholder="名称不可重复" />
|
<Input placeholder="名称不可重复" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem
|
<FormItem
|
||||||
name="bizName"
|
name="bizName"
|
||||||
label="数据源英文名"
|
label="模型英文名"
|
||||||
rules={[{ required: true, message: '请输入数据源英文名' }]}
|
rules={[{ required: true, message: '请输入模型英文名' }]}
|
||||||
>
|
>
|
||||||
<Input placeholder="名称不可重复" disabled={isEdit} />
|
<Input placeholder="名称不可重复" disabled={isEdit} />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem name="description" label="数据源描述">
|
<FormItem
|
||||||
<TextArea placeholder="请输入数据源描述" />
|
name="alias"
|
||||||
</FormItem>
|
label="别名"
|
||||||
{/* <FormItem
|
getValueFromEvent={(value) => {
|
||||||
name="filterSql"
|
return value.join(',');
|
||||||
label="过滤SQL"
|
}}
|
||||||
tooltip="主要用于词典导入场景, 对维度值进行过滤 格式: field1 = 'xxx' and field2 = 'yyy'"
|
getValueProps={(value) => {
|
||||||
|
return {
|
||||||
|
value: isString(value) ? value.split(',') : [],
|
||||||
|
};
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<SqlEditor height={'150px'} />
|
<Select
|
||||||
</FormItem> */}
|
mode="tags"
|
||||||
|
placeholder="输入别名后回车确认,多别名输入、复制粘贴支持英文逗号自动分隔"
|
||||||
|
tokenSeparators={[',']}
|
||||||
|
maxTagCount={9}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem name="description" label="模型描述">
|
||||||
|
<TextArea placeholder="请输入模型描述" />
|
||||||
|
</FormItem>
|
||||||
</Spin>
|
</Spin>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import DataSourceFieldForm from './DataSourceFieldForm';
|
|||||||
import { formLayout } from '@/components/FormHelper/utils';
|
import { formLayout } from '@/components/FormHelper/utils';
|
||||||
import { EnumDataSourceType } from '../constants';
|
import { EnumDataSourceType } from '../constants';
|
||||||
import styles from '../style.less';
|
import styles from '../style.less';
|
||||||
import { createDatasource, updateDatasource, getColumns } from '../../service';
|
import { updateModel, createModel, getColumns } from '../../service';
|
||||||
import type { Dispatch } from 'umi';
|
import type { Dispatch } from 'umi';
|
||||||
import type { StateType } from '../../model';
|
import type { StateType } from '../../model';
|
||||||
import { connect } from 'umi';
|
import { connect } from 'umi';
|
||||||
@@ -29,9 +29,9 @@ export type CreateFormProps = {
|
|||||||
const { Step } = Steps;
|
const { Step } = Steps;
|
||||||
|
|
||||||
const initFormVal = {
|
const initFormVal = {
|
||||||
name: '', // 数据源名称
|
name: '', // 模型名称
|
||||||
bizName: '', // 数据源英文名
|
bizName: '', // 模型英文名
|
||||||
description: '', // 数据源描述
|
description: '', // 模型描述
|
||||||
};
|
};
|
||||||
|
|
||||||
const DataSourceCreateForm: React.FC<CreateFormProps> = ({
|
const DataSourceCreateForm: React.FC<CreateFormProps> = ({
|
||||||
@@ -56,7 +56,7 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
|
|||||||
const [formDatabaseId, setFormDatabaseId] = useState<number>();
|
const [formDatabaseId, setFormDatabaseId] = useState<number>();
|
||||||
const formValRef = useRef(initFormVal as any);
|
const formValRef = useRef(initFormVal as any);
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const { databaseConfigList, selectModelId: modelId } = domainManger;
|
const { databaseConfigList, selectModelId: modelId, selectDomainId } = domainManger;
|
||||||
const updateFormVal = (val: any) => {
|
const updateFormVal = (val: any) => {
|
||||||
formValRef.current = val;
|
formValRef.current = val;
|
||||||
};
|
};
|
||||||
@@ -170,18 +170,22 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
|
|||||||
const { dbName, tableName } = submitForm;
|
const { dbName, tableName } = submitForm;
|
||||||
const queryParams = {
|
const queryParams = {
|
||||||
...submitForm,
|
...submitForm,
|
||||||
sqlQuery: sql,
|
|
||||||
databaseId: databaseId || dataSourceItem?.databaseId || formDatabaseId,
|
databaseId: databaseId || dataSourceItem?.databaseId || formDatabaseId,
|
||||||
queryType: basicInfoFormMode === 'fast' ? 'table_query' : 'sql_query',
|
|
||||||
tableQuery: dbName && tableName ? `${dbName}.${tableName}` : '',
|
|
||||||
modelId: isEdit ? dataSourceItem.modelId : modelId,
|
modelId: isEdit ? dataSourceItem.modelId : modelId,
|
||||||
filterSql: sqlFilter,
|
filterSql: sqlFilter,
|
||||||
|
domainId: isEdit ? dataSourceItem.domainId : selectDomainId,
|
||||||
|
modelDetail: {
|
||||||
|
...submitForm,
|
||||||
|
queryType: basicInfoFormMode === 'fast' ? 'table_query' : 'sql_query',
|
||||||
|
tableQuery: dbName && tableName ? `${dbName}.${tableName}` : '',
|
||||||
|
sqlQuery: sql,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const queryDatasource = isEdit ? updateDatasource : createDatasource;
|
const queryDatasource = isEdit ? updateModel : createModel;
|
||||||
const { code, msg, data } = await queryDatasource(queryParams);
|
const { code, msg, data } = await queryDatasource(queryParams);
|
||||||
setSaveLoading(false);
|
setSaveLoading(false);
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
message.success('保存数据源成功!');
|
message.success('保存模型成功!');
|
||||||
onSubmit?.({
|
onSubmit?.({
|
||||||
...queryParams,
|
...queryParams,
|
||||||
...data,
|
...data,
|
||||||
@@ -196,7 +200,13 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
|
|||||||
const initFields = (fieldsClassifyList: any[], columns: any[]) => {
|
const initFields = (fieldsClassifyList: any[], columns: any[]) => {
|
||||||
const columnFields: any[] = columns.map((item: any) => {
|
const columnFields: any[] = columns.map((item: any) => {
|
||||||
const { type, nameEn } = item;
|
const { type, nameEn } = item;
|
||||||
const oldItem = fieldsClassifyList.find((oItem) => oItem.bizName === item.nameEn) || {};
|
const oldItem =
|
||||||
|
fieldsClassifyList.find((oItem) => {
|
||||||
|
if (oItem.type === EnumDataSourceType.MEASURES) {
|
||||||
|
return oItem.expr === item.nameEn;
|
||||||
|
}
|
||||||
|
return oItem.bizName === item.nameEn;
|
||||||
|
}) || {};
|
||||||
return {
|
return {
|
||||||
...oldItem,
|
...oldItem,
|
||||||
bizName: nameEn,
|
bizName: nameEn,
|
||||||
@@ -225,7 +235,7 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const initData = async () => {
|
const initData = async () => {
|
||||||
const { queryType, tableQuery } = dataSourceItem.datasourceDetail;
|
const { queryType, tableQuery } = dataSourceItem?.modelDetail || {};
|
||||||
let tableQueryInitValue = {};
|
let tableQueryInitValue = {};
|
||||||
let columns = fieldColumns || [];
|
let columns = fieldColumns || [];
|
||||||
if (queryType === 'table_query') {
|
if (queryType === 'table_query') {
|
||||||
@@ -241,9 +251,9 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const formatterInitData = (columns: any[], extendParams: Record<string, any> = {}) => {
|
const formatterInitData = (columns: any[], extendParams: Record<string, any> = {}) => {
|
||||||
const { id, name, bizName, description, datasourceDetail, databaseId, filterSql } =
|
const { id, name, bizName, description, modelDetail, databaseId, filterSql, alias } =
|
||||||
dataSourceItem as any;
|
dataSourceItem as any;
|
||||||
const { dimensions, identifiers, measures } = datasourceDetail;
|
const { dimensions, identifiers, measures } = modelDetail || {};
|
||||||
const initValue = {
|
const initValue = {
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
@@ -251,8 +261,8 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
|
|||||||
description,
|
description,
|
||||||
databaseId,
|
databaseId,
|
||||||
filterSql,
|
filterSql,
|
||||||
|
alias,
|
||||||
...extendParams,
|
...extendParams,
|
||||||
// ...tableQueryInitValue,
|
|
||||||
};
|
};
|
||||||
const editInitFormVal = {
|
const editInitFormVal = {
|
||||||
...formValRef.current,
|
...formValRef.current,
|
||||||
@@ -270,7 +280,7 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { queryType } = dataSourceItem?.datasourceDetail || {};
|
const { queryType } = dataSourceItem?.modelDetail || {};
|
||||||
if (queryType === 'table_query') {
|
if (queryType === 'table_query') {
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
initData();
|
initData();
|
||||||
@@ -281,7 +291,7 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
|
|||||||
}, [dataSourceItem]);
|
}, [dataSourceItem]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { queryType } = dataSourceItem?.datasourceDetail || {};
|
const { queryType } = dataSourceItem?.modelDetail || {};
|
||||||
if (queryType !== 'table_query') {
|
if (queryType !== 'table_query') {
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
initData();
|
initData();
|
||||||
@@ -358,6 +368,8 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
|
|||||||
上一步
|
上一步
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={onCancel}>取 消</Button>
|
<Button onClick={onCancel}>取 消</Button>
|
||||||
|
{(dataSourceItem?.modelDetail?.queryType === 'sql_query' ||
|
||||||
|
basicInfoFormMode !== 'fast') && (
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -366,6 +378,7 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
|
|||||||
>
|
>
|
||||||
数据源编辑
|
数据源编辑
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -385,11 +398,12 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
|
|||||||
<Button onClick={onCancel}>取消</Button>
|
<Button onClick={onCancel}>取消</Button>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={() => {
|
onClick={async () => {
|
||||||
handleNext();
|
|
||||||
if (!isEdit && Array.isArray(fields) && fields.length === 0) {
|
if (!isEdit && Array.isArray(fields) && fields.length === 0) {
|
||||||
|
await form.validateFields();
|
||||||
onOpenDataSourceEdit?.();
|
onOpenDataSourceEdit?.();
|
||||||
}
|
}
|
||||||
|
handleNext();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
下一步
|
下一步
|
||||||
@@ -401,7 +415,6 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleNext(true);
|
handleNext(true);
|
||||||
}}
|
}}
|
||||||
// disabled={hasEmptyNameField}
|
|
||||||
>
|
>
|
||||||
保 存
|
保 存
|
||||||
</Button>
|
</Button>
|
||||||
@@ -414,9 +427,8 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
|
|||||||
<Modal
|
<Modal
|
||||||
forceRender
|
forceRender
|
||||||
width={1300}
|
width={1300}
|
||||||
// styles={{ padding: '32px 40px 48px' }}
|
|
||||||
destroyOnClose
|
destroyOnClose
|
||||||
title={`${isEdit ? '编辑' : '新建'}数据源`}
|
title={`${isEdit ? '编辑' : '新建'}模型`}
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
open={createModalVisible}
|
open={createModalVisible}
|
||||||
footer={renderFooter()}
|
footer={renderFooter()}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import styles from '../style.less';
|
import styles from '../style.less';
|
||||||
|
|
||||||
type FieldItem = {
|
type FieldItem = {
|
||||||
|
expr?: string;
|
||||||
bizName: string;
|
bizName: string;
|
||||||
sqlType: string;
|
sqlType: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -144,7 +145,7 @@ const FieldForm: React.FC<Props> = ({ fields, sql, onFieldChange, onSqlChange })
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (type === EnumDataSourceType.MEASURES) {
|
if (type === EnumDataSourceType.MEASURES) {
|
||||||
const agg = fields.find((field) => field.bizName === record.bizName)?.agg;
|
const agg = fields.find((field) => field.expr === record.expr)?.agg;
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
placeholder="度量算子"
|
placeholder="度量算子"
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ const SqlSide: React.FC<Props> = ({ initialValues, onSubmitSuccess }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialValues) {
|
if (initialValues) {
|
||||||
updateTabSql(initialValues?.datasourceDetail?.sqlQuery || '', '数据源查询');
|
updateTabSql(initialValues?.modelDetail?.sqlQuery || '', '数据源查询');
|
||||||
}
|
}
|
||||||
}, [initialValues]);
|
}, [initialValues]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import moment from 'moment';
|
|
||||||
import { message, Row, Col, Button, Space, Select, Form, Tooltip, Radio } from 'antd';
|
import { message, Row, Col, Button, Space, Select, Form, Tooltip, Radio } from 'antd';
|
||||||
import {
|
import {
|
||||||
queryStruct,
|
queryStruct,
|
||||||
@@ -20,7 +19,7 @@ import { DateRangeType, DateSettingType } from '@/components/MDatePicker/type';
|
|||||||
import StandardFormRow from '@/components/StandardFormRow';
|
import StandardFormRow from '@/components/StandardFormRow';
|
||||||
import MetricTable from './Table';
|
import MetricTable from './Table';
|
||||||
import { ColumnConfig } from '../data';
|
import { ColumnConfig } from '../data';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import { ISemantic } from '../../data';
|
import { ISemantic } from '../../data';
|
||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
@@ -50,6 +49,7 @@ const MetricTrendSection: React.FC<Props> = ({ metircData }) => {
|
|||||||
const [relationDimensionOptions, setRelationDimensionOptions] = useState<
|
const [relationDimensionOptions, setRelationDimensionOptions] = useState<
|
||||||
{ value: string; label: string }[]
|
{ value: string; label: string }[]
|
||||||
>([]);
|
>([]);
|
||||||
|
const [dimensionList, setDimensionList] = useState<ISemantic.IDimensionItem[]>([]);
|
||||||
const [queryParams, setQueryParams] = useState<any>({});
|
const [queryParams, setQueryParams] = useState<any>({});
|
||||||
const [downloadBtnDisabledState, setDownloadBtnDisabledState] = useState<boolean>(true);
|
const [downloadBtnDisabledState, setDownloadBtnDisabledState] = useState<boolean>(true);
|
||||||
// const [showDimensionOptions, setShowDimensionOptions] = useState<any[]>([]);
|
// const [showDimensionOptions, setShowDimensionOptions] = useState<any[]>([]);
|
||||||
@@ -58,8 +58,8 @@ const MetricTrendSection: React.FC<Props> = ({ metircData }) => {
|
|||||||
endDate: string;
|
endDate: string;
|
||||||
dateField: string;
|
dateField: string;
|
||||||
}>({
|
}>({
|
||||||
startDate: moment().subtract('6', 'days').format('YYYY-MM-DD'),
|
startDate: dayjs().subtract(6, 'days').format('YYYY-MM-DD'),
|
||||||
endDate: moment().format('YYYY-MM-DD'),
|
endDate: dayjs().format('YYYY-MM-DD'),
|
||||||
dateField: dateFieldMap[DateRangeType.DAY],
|
dateField: dateFieldMap[DateRangeType.DAY],
|
||||||
});
|
});
|
||||||
const [rowNumber, setRowNumber] = useState<number>(5);
|
const [rowNumber, setRowNumber] = useState<number>(5);
|
||||||
@@ -69,7 +69,7 @@ const MetricTrendSection: React.FC<Props> = ({ metircData }) => {
|
|||||||
const [groupByDimensionFieldName, setGroupByDimensionFieldName] = useState<string>();
|
const [groupByDimensionFieldName, setGroupByDimensionFieldName] = useState<string>();
|
||||||
|
|
||||||
const getMetricTrendData = async (params: any = { download: false }) => {
|
const getMetricTrendData = async (params: any = { download: false }) => {
|
||||||
const { download, dimensionGroup, dimensionFilters } = params;
|
const { download, dimensionGroup = [], dimensionFilters = [] } = params;
|
||||||
if (download) {
|
if (download) {
|
||||||
setDownloadLoding(true);
|
setDownloadLoding(true);
|
||||||
} else {
|
} else {
|
||||||
@@ -80,8 +80,26 @@ const MetricTrendSection: React.FC<Props> = ({ metircData }) => {
|
|||||||
}
|
}
|
||||||
const { modelId, bizName, name } = metircData;
|
const { modelId, bizName, name } = metircData;
|
||||||
indicatorFields.current = [{ name, column: bizName }];
|
indicatorFields.current = [{ name, column: bizName }];
|
||||||
|
|
||||||
|
const dimensionFiltersBizNameList = dimensionFilters.map((item) => {
|
||||||
|
return item.bizName;
|
||||||
|
});
|
||||||
|
|
||||||
|
const bizNameList = Array.from(new Set([...dimensionFiltersBizNameList, ...dimensionGroup]));
|
||||||
|
|
||||||
|
const modelIds = dimensionList.reduce(
|
||||||
|
(idList: number[], item: ISemantic.IDimensionItem) => {
|
||||||
|
if (bizNameList.includes(item.bizName)) {
|
||||||
|
idList.push(item.modelId);
|
||||||
|
}
|
||||||
|
return idList;
|
||||||
|
},
|
||||||
|
[modelId],
|
||||||
|
);
|
||||||
|
|
||||||
const res = await queryStruct({
|
const res = await queryStruct({
|
||||||
modelId,
|
// modelId,
|
||||||
|
modelIds: Array.from(new Set(modelIds)),
|
||||||
bizName,
|
bizName,
|
||||||
groups: dimensionGroup,
|
groups: dimensionGroup,
|
||||||
dimensionFilters,
|
dimensionFilters,
|
||||||
@@ -123,9 +141,15 @@ const MetricTrendSection: React.FC<Props> = ({ metircData }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryDimensionList = async (modelId: number) => {
|
const queryDimensionList = async (ids: number[]) => {
|
||||||
const { code, data, msg } = await getDimensionList({ modelId });
|
const { code, data, msg } = await getDimensionList({ ids });
|
||||||
if (code === 200 && Array.isArray(data?.list)) {
|
if (code === 200 && Array.isArray(data?.list)) {
|
||||||
|
setDimensionList(data.list);
|
||||||
|
setRelationDimensionOptions(
|
||||||
|
data.list.map((item: ISemantic.IMetricItem) => {
|
||||||
|
return { label: item.name, value: item.bizName };
|
||||||
|
}),
|
||||||
|
);
|
||||||
return data.list;
|
return data.list;
|
||||||
}
|
}
|
||||||
message.error(msg);
|
message.error(msg);
|
||||||
@@ -135,26 +159,18 @@ const MetricTrendSection: React.FC<Props> = ({ metircData }) => {
|
|||||||
const queryDrillDownDimension = async (metricId: number) => {
|
const queryDrillDownDimension = async (metricId: number) => {
|
||||||
const { code, data, msg } = await getDrillDownDimension(metricId);
|
const { code, data, msg } = await getDrillDownDimension(metricId);
|
||||||
if (code === 200 && Array.isArray(data)) {
|
if (code === 200 && Array.isArray(data)) {
|
||||||
|
const ids = data.map((item) => item.dimensionId);
|
||||||
|
queryDimensionList(ids);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
if (code !== 200) {
|
||||||
message.error(msg);
|
message.error(msg);
|
||||||
|
}
|
||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
const initDimensionData = async (metricItem: ISemantic.IMetricItem) => {
|
const initDimensionData = async (metricItem: ISemantic.IMetricItem) => {
|
||||||
const dimensionList = await queryDimensionList(metricItem.modelId);
|
await queryDrillDownDimension(metricItem.id);
|
||||||
const drillDownDimension = await queryDrillDownDimension(metricItem.id);
|
|
||||||
const drillDownDimensionIds = drillDownDimension.map(
|
|
||||||
(item: ISemantic.IDrillDownDimensionItem) => item.dimensionId,
|
|
||||||
);
|
|
||||||
const drillDownDimensionList = dimensionList.filter((metricItem: ISemantic.IMetricItem) => {
|
|
||||||
return drillDownDimensionIds.includes(metricItem.id);
|
|
||||||
});
|
|
||||||
setRelationDimensionOptions(
|
|
||||||
drillDownDimensionList.map((item: ISemantic.IMetricItem) => {
|
|
||||||
return { label: item.name, value: item.bizName };
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -163,7 +179,7 @@ const MetricTrendSection: React.FC<Props> = ({ metircData }) => {
|
|||||||
initDimensionData(metircData);
|
initDimensionData(metircData);
|
||||||
setDrillDownDimensions(metircData?.relateDimension?.drillDownDimensions || []);
|
setDrillDownDimensions(metircData?.relateDimension?.drillDownDimensions || []);
|
||||||
}
|
}
|
||||||
}, [metircData, periodDate]);
|
}, [metircData?.id, periodDate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ backgroundColor: '#fff', marginTop: 20 }}>
|
<div style={{ backgroundColor: '#fff', marginTop: 20 }}>
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ const DataSourceRelationFormDrawer: React.FC<DataSourceRelationFormDrawerProps>
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { sourceData, targetData } = nodeDataSource;
|
const { sourceData, targetData } = nodeDataSource;
|
||||||
const dataSourceFromIdentifiers = sourceData?.datasourceDetail?.identifiers || [];
|
const dataSourceFromIdentifiers = sourceData?.modelDetail?.identifiers || [];
|
||||||
const dataSourceToIdentifiers = targetData?.datasourceDetail?.identifiers || [];
|
const dataSourceToIdentifiers = targetData?.modelDetail?.identifiers || [];
|
||||||
const dataSourceToIdentifiersNames = dataSourceToIdentifiers.map((item) => {
|
const dataSourceToIdentifiersNames = dataSourceToIdentifiers.map((item) => {
|
||||||
return item.bizName;
|
return item.bizName;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ const XflowJsonSchemaFormDrawerForm: React.FC<CreateFormProps> = (props) => {
|
|||||||
if (!payload) {
|
if (!payload) {
|
||||||
setCreateDataSourceModalOpen(true);
|
setCreateDataSourceModalOpen(true);
|
||||||
} else {
|
} else {
|
||||||
if (payload?.datasourceDetail?.queryType === 'table_query') {
|
if (payload?.modelDetail?.queryType === 'table_query') {
|
||||||
setDataSourceModalVisible(true);
|
setDataSourceModalVisible(true);
|
||||||
} else {
|
} else {
|
||||||
setCreateModalVisible(true);
|
setCreateModalVisible(true);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { cloneDeep } from 'lodash';
|
|||||||
import type { IDataSource } from '../data';
|
import type { IDataSource } from '../data';
|
||||||
import { SemanticNodeType } from '../enum';
|
import { SemanticNodeType } from '../enum';
|
||||||
import {
|
import {
|
||||||
getDatasourceList,
|
getModelList,
|
||||||
deleteDatasource,
|
deleteDatasource,
|
||||||
getDimensionList,
|
getDimensionList,
|
||||||
createOrUpdateViewInfo,
|
createOrUpdateViewInfo,
|
||||||
@@ -84,8 +84,8 @@ export namespace GraphApi {
|
|||||||
|
|
||||||
export const loadDataSourceData = async (args: NsGraph.IGraphMeta) => {
|
export const loadDataSourceData = async (args: NsGraph.IGraphMeta) => {
|
||||||
const { domainManger, graphConfig } = args.meta;
|
const { domainManger, graphConfig } = args.meta;
|
||||||
const { selectModelId } = domainManger;
|
const { selectDomainId } = domainManger;
|
||||||
const { code, data = [] } = await getDatasourceList({ modelId: selectModelId });
|
const { code, data = [] } = await getModelList(selectDomainId);
|
||||||
const dataSourceMap = data.reduce(
|
const dataSourceMap = data.reduce(
|
||||||
(itemMap: Record<string, IDataSource.IDataSourceItem>, item: IDataSource.IDataSourceItem) => {
|
(itemMap: Record<string, IDataSource.IDataSourceItem>, item: IDataSource.IDataSourceItem) => {
|
||||||
const { id, name } = item;
|
const { id, name } = item;
|
||||||
|
|||||||
@@ -0,0 +1,802 @@
|
|||||||
|
import G6 from '@antv/g6';
|
||||||
|
|
||||||
|
const colors = {
|
||||||
|
B: '#5B8FF9',
|
||||||
|
R: '#F46649',
|
||||||
|
Y: '#EEBC20',
|
||||||
|
G: '#5BD8A6',
|
||||||
|
DI: '#A7A7A7',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 自定义节点、边
|
||||||
|
export const flowRectNodeRegister = () => {
|
||||||
|
/**
|
||||||
|
* 自定义节点
|
||||||
|
*/
|
||||||
|
G6.registerNode(
|
||||||
|
'flow-rect',
|
||||||
|
{
|
||||||
|
shapeType: 'flow-rect',
|
||||||
|
draw(cfg, group) {
|
||||||
|
const {
|
||||||
|
name = '',
|
||||||
|
variableName,
|
||||||
|
variableValue,
|
||||||
|
variableUp,
|
||||||
|
label,
|
||||||
|
collapsed,
|
||||||
|
currency,
|
||||||
|
status,
|
||||||
|
rate,
|
||||||
|
} = cfg;
|
||||||
|
|
||||||
|
const grey = '#CED4D9';
|
||||||
|
const rectConfig = {
|
||||||
|
width: 202,
|
||||||
|
height: 60,
|
||||||
|
lineWidth: 1,
|
||||||
|
fontSize: 12,
|
||||||
|
fill: '#fff',
|
||||||
|
radius: 4,
|
||||||
|
stroke: grey,
|
||||||
|
opacity: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const nodeOrigin = {
|
||||||
|
x: -rectConfig.width / 2,
|
||||||
|
y: -rectConfig.height / 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
const textConfig = {
|
||||||
|
textAlign: 'left',
|
||||||
|
textBaseline: 'bottom',
|
||||||
|
};
|
||||||
|
|
||||||
|
const rect = group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: nodeOrigin.x,
|
||||||
|
y: nodeOrigin.y,
|
||||||
|
...rectConfig,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const rectBBox = rect.getBBox();
|
||||||
|
|
||||||
|
// label title
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
...textConfig,
|
||||||
|
x: 12 + nodeOrigin.x,
|
||||||
|
y: 20 + nodeOrigin.y,
|
||||||
|
text: name.length > 28 ? name.substr(0, 28) + '...' : name,
|
||||||
|
fontSize: 12,
|
||||||
|
opacity: 0.85,
|
||||||
|
fill: '#000',
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'name-shape',
|
||||||
|
});
|
||||||
|
|
||||||
|
// price
|
||||||
|
const price = group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
...textConfig,
|
||||||
|
x: 12 + nodeOrigin.x,
|
||||||
|
y: rectBBox.maxY - 12,
|
||||||
|
text: label,
|
||||||
|
fontSize: 16,
|
||||||
|
fill: '#000',
|
||||||
|
opacity: 0.85,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// label currency
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
...textConfig,
|
||||||
|
x: price.getBBox().maxX + 5,
|
||||||
|
y: rectBBox.maxY - 12,
|
||||||
|
text: currency,
|
||||||
|
fontSize: 12,
|
||||||
|
fill: '#000',
|
||||||
|
opacity: 0.75,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// percentage
|
||||||
|
const percentText = group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
...textConfig,
|
||||||
|
x: rectBBox.maxX - 8,
|
||||||
|
y: rectBBox.maxY - 12,
|
||||||
|
text: `${((variableValue || 0) * 100).toFixed(2)}%`,
|
||||||
|
fontSize: 12,
|
||||||
|
textAlign: 'right',
|
||||||
|
fill: colors[status],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// percentage triangle
|
||||||
|
const symbol = variableUp ? 'triangle' : 'triangle-down';
|
||||||
|
const triangle = group.addShape('marker', {
|
||||||
|
attrs: {
|
||||||
|
...textConfig,
|
||||||
|
x: percentText.getBBox().minX - 10,
|
||||||
|
y: rectBBox.maxY - 12 - 6,
|
||||||
|
symbol,
|
||||||
|
r: 6,
|
||||||
|
fill: colors[status],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// variable name
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
...textConfig,
|
||||||
|
x: triangle.getBBox().minX - 4,
|
||||||
|
y: rectBBox.maxY - 12,
|
||||||
|
text: variableName,
|
||||||
|
fontSize: 12,
|
||||||
|
textAlign: 'right',
|
||||||
|
fill: '#000',
|
||||||
|
opacity: 0.45,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// bottom line background
|
||||||
|
const bottomBackRect = group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: nodeOrigin.x,
|
||||||
|
y: rectBBox.maxY - 4,
|
||||||
|
width: rectConfig.width,
|
||||||
|
height: 4,
|
||||||
|
radius: [0, 0, rectConfig.radius, rectConfig.radius],
|
||||||
|
fill: '#E0DFE3',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// bottom percent
|
||||||
|
const bottomRect = group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: nodeOrigin.x,
|
||||||
|
y: rectBBox.maxY - 4,
|
||||||
|
width: rate * rectBBox.width,
|
||||||
|
height: 4,
|
||||||
|
radius: [0, 0, 0, rectConfig.radius],
|
||||||
|
fill: colors[status],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// collapse rect
|
||||||
|
if (cfg.children && cfg.children.length) {
|
||||||
|
group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: rectConfig.width / 2 - 8,
|
||||||
|
y: -8,
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
stroke: 'rgba(0, 0, 0, 0.25)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fill: '#fff',
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'collapse-back',
|
||||||
|
modelId: cfg.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
// collpase text
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: rectConfig.width / 2,
|
||||||
|
y: -1,
|
||||||
|
textAlign: 'center',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
text: collapsed ? '+' : '-',
|
||||||
|
fontSize: 16,
|
||||||
|
cursor: 'pointer',
|
||||||
|
fill: 'rgba(0, 0, 0, 0.25)',
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'collapse-text',
|
||||||
|
modelId: cfg.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.drawLinkPoints(cfg, group);
|
||||||
|
return rect;
|
||||||
|
},
|
||||||
|
update(cfg, item) {
|
||||||
|
const { level, status, name } = cfg;
|
||||||
|
const group = item.getContainer();
|
||||||
|
let mask = group.find((ele) => ele.get('name') === 'mask-shape');
|
||||||
|
let maskLabel = group.find((ele) => ele.get('name') === 'mask-label-shape');
|
||||||
|
if (level === 0) {
|
||||||
|
group.get('children').forEach((child) => {
|
||||||
|
if (child.get('name')?.includes('collapse')) return;
|
||||||
|
child.hide();
|
||||||
|
});
|
||||||
|
if (!mask) {
|
||||||
|
mask = group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: -101,
|
||||||
|
y: -30,
|
||||||
|
width: 202,
|
||||||
|
height: 60,
|
||||||
|
opacity: 0,
|
||||||
|
fill: colors[status],
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'mask-shape',
|
||||||
|
});
|
||||||
|
maskLabel = group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
fill: '#fff',
|
||||||
|
fontSize: 20,
|
||||||
|
x: 0,
|
||||||
|
y: 10,
|
||||||
|
text: name.length > 28 ? name.substr(0, 16) + '...' : name,
|
||||||
|
textAlign: 'center',
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'mask-label-shape',
|
||||||
|
});
|
||||||
|
const collapseRect = group.find((ele) => ele.get('name') === 'collapse-back');
|
||||||
|
const collapseText = group.find((ele) => ele.get('name') === 'collapse-text');
|
||||||
|
collapseRect?.toFront();
|
||||||
|
collapseText?.toFront();
|
||||||
|
} else {
|
||||||
|
mask.show();
|
||||||
|
maskLabel.show();
|
||||||
|
}
|
||||||
|
mask.animate({ opacity: 1 }, 200);
|
||||||
|
maskLabel.animate({ opacity: 1 }, 200);
|
||||||
|
return mask;
|
||||||
|
} else {
|
||||||
|
group.get('children').forEach((child) => {
|
||||||
|
if (child.get('name')?.includes('collapse')) return;
|
||||||
|
child.show();
|
||||||
|
});
|
||||||
|
mask?.animate(
|
||||||
|
{ opacity: 0 },
|
||||||
|
{
|
||||||
|
duration: 200,
|
||||||
|
callback: () => mask.hide(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
maskLabel?.animate(
|
||||||
|
{ opacity: 0 },
|
||||||
|
{
|
||||||
|
duration: 200,
|
||||||
|
callback: () => maskLabel.hide(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.updateLinkPoints(cfg, group);
|
||||||
|
},
|
||||||
|
setState(name, value, item) {
|
||||||
|
if (name === 'collapse') {
|
||||||
|
const group = item.getContainer();
|
||||||
|
const collapseText = group.find((e) => e.get('name') === 'collapse-text');
|
||||||
|
if (collapseText) {
|
||||||
|
if (!value) {
|
||||||
|
collapseText.attr({
|
||||||
|
text: '-',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
collapseText.attr({
|
||||||
|
text: '+',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getAnchorPoints() {
|
||||||
|
return [
|
||||||
|
[0, 0.5],
|
||||||
|
[1, 0.5],
|
||||||
|
];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'rect',
|
||||||
|
);
|
||||||
|
|
||||||
|
G6.registerEdge(
|
||||||
|
'flow-cubic',
|
||||||
|
{
|
||||||
|
getControlPoints(cfg) {
|
||||||
|
let controlPoints = cfg.controlPoints; // 指定controlPoints
|
||||||
|
if (!controlPoints || !controlPoints.length) {
|
||||||
|
const { startPoint, endPoint, sourceNode, targetNode } = cfg;
|
||||||
|
const {
|
||||||
|
x: startX,
|
||||||
|
y: startY,
|
||||||
|
coefficientX,
|
||||||
|
coefficientY,
|
||||||
|
} = sourceNode ? sourceNode.getModel() : startPoint;
|
||||||
|
const { x: endX, y: endY } = targetNode ? targetNode.getModel() : endPoint;
|
||||||
|
let curveStart = (endX - startX) * coefficientX;
|
||||||
|
let curveEnd = (endY - startY) * coefficientY;
|
||||||
|
curveStart = curveStart > 40 ? 40 : curveStart;
|
||||||
|
curveEnd = curveEnd < -30 ? curveEnd : -30;
|
||||||
|
controlPoints = [
|
||||||
|
{ x: startPoint.x + curveStart, y: startPoint.y },
|
||||||
|
{ x: endPoint.x + curveEnd, y: endPoint.y },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return controlPoints;
|
||||||
|
},
|
||||||
|
getPath(points) {
|
||||||
|
const path = [];
|
||||||
|
path.push(['M', points[0].x, points[0].y]);
|
||||||
|
path.push([
|
||||||
|
'C',
|
||||||
|
points[1].x,
|
||||||
|
points[1].y,
|
||||||
|
points[2].x,
|
||||||
|
points[2].y,
|
||||||
|
points[3].x,
|
||||||
|
points[3].y,
|
||||||
|
]);
|
||||||
|
return path;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'single-line',
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const COLLAPSE_ICON = function COLLAPSE_ICON(x, y, r) {
|
||||||
|
return [
|
||||||
|
['M', x - r, y],
|
||||||
|
['a', r, r, 0, 1, 0, r * 2, 0],
|
||||||
|
['a', r, r, 0, 1, 0, -r * 2, 0],
|
||||||
|
['M', x - r + 4, y],
|
||||||
|
['L', x - r + 2 * r - 4, y],
|
||||||
|
];
|
||||||
|
};
|
||||||
|
const EXPAND_ICON = function EXPAND_ICON(x, y, r) {
|
||||||
|
return [
|
||||||
|
['M', x - r, y],
|
||||||
|
['a', r, r, 0, 1, 0, r * 2, 0],
|
||||||
|
['a', r, r, 0, 1, 0, -r * 2, 0],
|
||||||
|
['M', x - r + 4, y],
|
||||||
|
['L', x - r + 2 * r - 4, y],
|
||||||
|
['M', x - r + r, y - r + 4],
|
||||||
|
['L', x, y + r - 4],
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cardNodeRegister = (graph) => {
|
||||||
|
const ERROR_COLOR = '#F5222D';
|
||||||
|
const getNodeConfig = (node) => {
|
||||||
|
if (node.nodeError) {
|
||||||
|
return {
|
||||||
|
basicColor: ERROR_COLOR,
|
||||||
|
fontColor: '#FFF',
|
||||||
|
borderColor: ERROR_COLOR,
|
||||||
|
bgColor: '#E66A6C',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let config = {
|
||||||
|
basicColor: '#5B8FF9',
|
||||||
|
fontColor: '#5B8FF9',
|
||||||
|
borderColor: '#5B8FF9',
|
||||||
|
bgColor: '#C6E5FF',
|
||||||
|
};
|
||||||
|
switch (node.type) {
|
||||||
|
case 'root': {
|
||||||
|
config = {
|
||||||
|
basicColor: '#E3E6E8',
|
||||||
|
fontColor: 'rgba(0,0,0,0.85)',
|
||||||
|
borderColor: '#E3E6E8',
|
||||||
|
bgColor: '#5b8ff9',
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
const nodeBasicMethod = {
|
||||||
|
createNodeBox: (group, config, w, h, isRoot) => {
|
||||||
|
/* 最外面的大矩形 */
|
||||||
|
const container = group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: w,
|
||||||
|
height: h,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'big-rect-shape',
|
||||||
|
});
|
||||||
|
if (!isRoot) {
|
||||||
|
/* 左边的小圆点 */
|
||||||
|
group.addShape('circle', {
|
||||||
|
attrs: {
|
||||||
|
x: 3,
|
||||||
|
y: h / 2,
|
||||||
|
r: 6,
|
||||||
|
fill: config.basicColor,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'left-dot-shape',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/* 矩形 */
|
||||||
|
group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: 3,
|
||||||
|
y: 0,
|
||||||
|
width: w - 19,
|
||||||
|
height: h,
|
||||||
|
fill: config.bgColor,
|
||||||
|
stroke: config.borderColor,
|
||||||
|
radius: 2,
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'rect-shape',
|
||||||
|
});
|
||||||
|
|
||||||
|
/* 左边的粗线 */
|
||||||
|
group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: 3,
|
||||||
|
y: 0,
|
||||||
|
width: 3,
|
||||||
|
height: h,
|
||||||
|
fill: config.basicColor,
|
||||||
|
radius: 1.5,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'left-border-shape',
|
||||||
|
});
|
||||||
|
return container;
|
||||||
|
},
|
||||||
|
/* 生成树上的 marker */
|
||||||
|
createNodeMarker: (group, collapsed, x, y) => {
|
||||||
|
group.addShape('circle', {
|
||||||
|
attrs: {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
r: 13,
|
||||||
|
fill: 'rgba(47, 84, 235, 0.05)',
|
||||||
|
opacity: 0,
|
||||||
|
zIndex: -2,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'collapse-icon-bg',
|
||||||
|
});
|
||||||
|
group.addShape('marker', {
|
||||||
|
attrs: {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
r: 7,
|
||||||
|
symbol: collapsed ? EXPAND_ICON : COLLAPSE_ICON,
|
||||||
|
stroke: 'rgba(0,0,0,0.25)',
|
||||||
|
fill: 'rgba(0,0,0,0)',
|
||||||
|
lineWidth: 1,
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'collapse-icon',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
afterDraw: (cfg, group) => {
|
||||||
|
/* 操作 marker 的背景色显示隐藏 */
|
||||||
|
const icon = group.find((element) => element.get('name') === 'collapse-icon');
|
||||||
|
if (icon) {
|
||||||
|
const bg = group.find((element) => element.get('name') === 'collapse-icon-bg');
|
||||||
|
icon.on('mouseenter', () => {
|
||||||
|
bg.attr('opacity', 1);
|
||||||
|
graph.get('canvas').draw();
|
||||||
|
});
|
||||||
|
icon.on('mouseleave', () => {
|
||||||
|
bg.attr('opacity', 0);
|
||||||
|
graph.get('canvas').draw();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/* ip 显示 */
|
||||||
|
const ipBox = group.find((element) => element.get('name') === 'ip-box');
|
||||||
|
if (ipBox) {
|
||||||
|
/* ip 复制的几个元素 */
|
||||||
|
const ipLine = group.find((element) => element.get('name') === 'ip-cp-line');
|
||||||
|
const ipBG = group.find((element) => element.get('name') === 'ip-cp-bg');
|
||||||
|
const ipIcon = group.find((element) => element.get('name') === 'ip-cp-icon');
|
||||||
|
const ipCPBox = group.find((element) => element.get('name') === 'ip-cp-box');
|
||||||
|
|
||||||
|
const onMouseEnter = () => {
|
||||||
|
ipLine.attr('opacity', 1);
|
||||||
|
ipBG.attr('opacity', 1);
|
||||||
|
ipIcon.attr('opacity', 1);
|
||||||
|
graph.get('canvas').draw();
|
||||||
|
};
|
||||||
|
const onMouseLeave = () => {
|
||||||
|
ipLine.attr('opacity', 0);
|
||||||
|
ipBG.attr('opacity', 0);
|
||||||
|
ipIcon.attr('opacity', 0);
|
||||||
|
graph.get('canvas').draw();
|
||||||
|
};
|
||||||
|
ipBox.on('mouseenter', () => {
|
||||||
|
onMouseEnter();
|
||||||
|
});
|
||||||
|
ipBox.on('mouseleave', () => {
|
||||||
|
onMouseLeave();
|
||||||
|
});
|
||||||
|
ipCPBox.on('mouseenter', () => {
|
||||||
|
onMouseEnter();
|
||||||
|
});
|
||||||
|
ipCPBox.on('mouseleave', () => {
|
||||||
|
onMouseLeave();
|
||||||
|
});
|
||||||
|
ipCPBox.on('click', () => {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setState: (name, value, item) => {
|
||||||
|
const hasOpacityClass = [
|
||||||
|
'ip-cp-line',
|
||||||
|
'ip-cp-bg',
|
||||||
|
'ip-cp-icon',
|
||||||
|
'ip-cp-box',
|
||||||
|
'ip-box',
|
||||||
|
'collapse-icon-bg',
|
||||||
|
];
|
||||||
|
const group = item.getContainer();
|
||||||
|
const childrens = group.get('children');
|
||||||
|
graph.setAutoPaint(false);
|
||||||
|
if (name === 'emptiness') {
|
||||||
|
if (value) {
|
||||||
|
childrens.forEach((shape) => {
|
||||||
|
if (hasOpacityClass.indexOf(shape.get('name')) > -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
shape.attr('opacity', 0.4);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
childrens.forEach((shape) => {
|
||||||
|
if (hasOpacityClass.indexOf(shape.get('name')) > -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
shape.attr('opacity', 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
graph.setAutoPaint(true);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
G6.registerNode('card-node', {
|
||||||
|
draw: (cfg, group) => {
|
||||||
|
const config = getNodeConfig(cfg);
|
||||||
|
const isRoot = cfg.dataType === 'root';
|
||||||
|
const nodeError = cfg.nodeError;
|
||||||
|
/* the biggest rect */
|
||||||
|
const container = nodeBasicMethod.createNodeBox(group, config, 243, 64, isRoot);
|
||||||
|
|
||||||
|
if (cfg.dataType !== 'root') {
|
||||||
|
/* the type text */
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
text: cfg.dataType,
|
||||||
|
x: 3,
|
||||||
|
y: -10,
|
||||||
|
fontSize: 12,
|
||||||
|
textAlign: 'left',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
fill: 'rgba(0,0,0,0.65)',
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'type-text-shape',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cfg.ip) {
|
||||||
|
/* ip start */
|
||||||
|
/* ipBox */
|
||||||
|
const ipRect = group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
fill: nodeError ? null : '#FFF',
|
||||||
|
stroke: nodeError ? 'rgba(255,255,255,0.65)' : null,
|
||||||
|
radius: 2,
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'ip-container-shape',
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ip */
|
||||||
|
const ipText = group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
text: cfg.ip,
|
||||||
|
x: 0,
|
||||||
|
y: 19,
|
||||||
|
fontSize: 12,
|
||||||
|
textAlign: 'left',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
fill: nodeError ? 'rgba(255,255,255,0.85)' : 'rgba(0,0,0,0.65)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'ip-text-shape',
|
||||||
|
});
|
||||||
|
|
||||||
|
const ipBBox = ipText.getBBox();
|
||||||
|
/* the distance from the IP to the right is 12px */
|
||||||
|
ipText.attr({
|
||||||
|
x: 224 - 12 - ipBBox.width,
|
||||||
|
});
|
||||||
|
/* ipBox */
|
||||||
|
ipRect.attr({
|
||||||
|
x: 224 - 12 - ipBBox.width - 4,
|
||||||
|
y: ipBBox.minY - 5,
|
||||||
|
width: ipBBox.width + 8,
|
||||||
|
height: ipBBox.height + 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
/* a transparent shape on the IP for click listener */
|
||||||
|
group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
stroke: '',
|
||||||
|
cursor: 'pointer',
|
||||||
|
x: 224 - 12 - ipBBox.width - 4,
|
||||||
|
y: ipBBox.minY - 5,
|
||||||
|
width: ipBBox.width + 8,
|
||||||
|
height: ipBBox.height + 10,
|
||||||
|
fill: '#fff',
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'ip-box',
|
||||||
|
});
|
||||||
|
|
||||||
|
/* copyIpLine */
|
||||||
|
group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: 194,
|
||||||
|
y: 7,
|
||||||
|
width: 1,
|
||||||
|
height: 24,
|
||||||
|
fill: '#E3E6E8',
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'ip-cp-line',
|
||||||
|
});
|
||||||
|
/* copyIpBG */
|
||||||
|
group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: 195,
|
||||||
|
y: 8,
|
||||||
|
width: 22,
|
||||||
|
height: 22,
|
||||||
|
fill: '#FFF',
|
||||||
|
cursor: 'pointer',
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'ip-cp-bg',
|
||||||
|
});
|
||||||
|
/* copyIpIcon */
|
||||||
|
group.addShape('image', {
|
||||||
|
attrs: {
|
||||||
|
x: 200,
|
||||||
|
y: 13,
|
||||||
|
height: 12,
|
||||||
|
width: 10,
|
||||||
|
img: 'https://os.alipayobjects.com/rmsportal/DFhnQEhHyPjSGYW.png',
|
||||||
|
cursor: 'pointer',
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'ip-cp-icon',
|
||||||
|
});
|
||||||
|
/* a transparent rect on the icon area for click listener */
|
||||||
|
group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: 195,
|
||||||
|
y: 8,
|
||||||
|
width: 22,
|
||||||
|
height: 22,
|
||||||
|
fill: '#FFF',
|
||||||
|
cursor: 'pointer',
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'ip-cp-box',
|
||||||
|
tooltip: 'Copy the IP',
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ip end */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* name */
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
text: cfg.name,
|
||||||
|
x: 19,
|
||||||
|
y: 19,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 700,
|
||||||
|
textAlign: 'left',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
fill: config.fontColor,
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'name-text-shape',
|
||||||
|
});
|
||||||
|
|
||||||
|
group.addShape('image', {
|
||||||
|
attrs: {
|
||||||
|
x: 19,
|
||||||
|
y: 19,
|
||||||
|
height: 30,
|
||||||
|
width: 30,
|
||||||
|
img: '/icons/vector.svg',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fill: '#fff',
|
||||||
|
// opacity: 0,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'type-cp-icon',
|
||||||
|
});
|
||||||
|
|
||||||
|
group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 90,
|
||||||
|
height: 90,
|
||||||
|
fill: '#FFF',
|
||||||
|
cursor: 'pointer',
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'ip-cp-boxxx',
|
||||||
|
tooltip: 'Copy the IP',
|
||||||
|
});
|
||||||
|
|
||||||
|
/* the description text */
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
text: cfg.keyInfo,
|
||||||
|
x: 19,
|
||||||
|
y: 45,
|
||||||
|
fontSize: 14,
|
||||||
|
textAlign: 'left',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
fill: config.fontColor,
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'bottom-text-shape',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (nodeError) {
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: 191,
|
||||||
|
y: 62,
|
||||||
|
text: '⚠️',
|
||||||
|
fill: '#000',
|
||||||
|
fontSize: 18,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'error-text-shape',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasChildren = cfg.children && cfg.children.length > 0;
|
||||||
|
if (hasChildren) {
|
||||||
|
nodeBasicMethod.createNodeMarker(group, cfg.collapsed, 236, 32);
|
||||||
|
}
|
||||||
|
return container;
|
||||||
|
},
|
||||||
|
afterDraw: nodeBasicMethod.afterDraw,
|
||||||
|
setState: nodeBasicMethod.setState,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -21,10 +21,16 @@ export const getMenuConfig = (props?: InitContextMenuProps) => {
|
|||||||
<li title='编辑' key='edit' >编辑</li>
|
<li title='编辑' key='edit' >编辑</li>
|
||||||
<li title='删除' key='delete' >删除</li>
|
<li title='删除' key='delete' >删除</li>
|
||||||
`;
|
`;
|
||||||
|
// if (nodeType === SemanticNodeType.DATASOURCE) {
|
||||||
|
// ulNode = `
|
||||||
|
// <li title='新增维度' key='createDimension' >新增维度</li>
|
||||||
|
// <li title='新增指标' key='createMetric' >新增指标</li>
|
||||||
|
// <li title='编辑' key='editDatasource' >编辑</li>
|
||||||
|
// <li title='删除' key='deleteDatasource' >删除</li>
|
||||||
|
// `;
|
||||||
|
// }
|
||||||
if (nodeType === SemanticNodeType.DATASOURCE) {
|
if (nodeType === SemanticNodeType.DATASOURCE) {
|
||||||
ulNode = `
|
ulNode = `
|
||||||
<li title='新增维度' key='createDimension' >新增维度</li>
|
|
||||||
<li title='新增指标' key='createMetric' >新增指标</li>
|
|
||||||
<li title='编辑' key='editDatasource' >编辑</li>
|
<li title='编辑' key='editDatasource' >编辑</li>
|
||||||
<li title='删除' key='deleteDatasource' >删除</li>
|
<li title='删除' key='deleteDatasource' >删除</li>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ const GraphToolBar: React.FC<Props> = ({ onClick }) => {
|
|||||||
onClick?.({ eventName: 'createDatabase' });
|
onClick?.({ eventName: 'createDatabase' });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
新建数据源
|
新建模型
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
{/* <Button
|
||||||
key="createDimensionBtn"
|
key="createDimensionBtn"
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
size="small"
|
size="small"
|
||||||
@@ -55,7 +55,7 @@ const GraphToolBar: React.FC<Props> = ({ onClick }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
新建指标
|
新建指标
|
||||||
</Button>
|
</Button> */}
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,249 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Form, Button, Drawer, Space, Input, Select, message, Popconfirm } from 'antd';
|
||||||
|
import { formLayout } from '@/components/FormHelper/utils';
|
||||||
|
import { createOrUpdateModelRela, deleteModelRela } from '../../service';
|
||||||
|
|
||||||
|
export type ModelRelationFormDrawerProps = {
|
||||||
|
domainId: number;
|
||||||
|
nodeModel: any;
|
||||||
|
relationData: any;
|
||||||
|
open: boolean;
|
||||||
|
onSave?: () => void;
|
||||||
|
onDelete?: () => void;
|
||||||
|
onClose?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
|
const ModelRelationFormDrawer: React.FC<ModelRelationFormDrawerProps> = ({
|
||||||
|
domainId,
|
||||||
|
open,
|
||||||
|
nodeModel,
|
||||||
|
relationData,
|
||||||
|
onSave,
|
||||||
|
onDelete,
|
||||||
|
onClose,
|
||||||
|
}) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [saveLoading, setSaveLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [sourcePrimaryOptions, setSourcePrimaryOptions] = useState<OptionsItem[]>([]);
|
||||||
|
const [targetPrimaryOptions, setTargetPrimaryOptions] = useState<OptionsItem[]>([]);
|
||||||
|
|
||||||
|
const [deleteLoading, setDeleteLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!relationData?.id) {
|
||||||
|
form.resetFields();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { joinConditions = [] } = relationData;
|
||||||
|
const firstCondition = joinConditions[0] || {};
|
||||||
|
const formData = {
|
||||||
|
...relationData,
|
||||||
|
...firstCondition,
|
||||||
|
};
|
||||||
|
form.setFieldsValue(formData);
|
||||||
|
}, [relationData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const { sourceData, targetData } = nodeModel;
|
||||||
|
const dataSourceFromIdentifiers = sourceData?.modelDetail?.identifiers || [];
|
||||||
|
const dataSourceToIdentifiers = targetData?.modelDetail?.identifiers || [];
|
||||||
|
|
||||||
|
const sourceOptions = dataSourceFromIdentifiers.map((item: any) => {
|
||||||
|
return {
|
||||||
|
label: `${item.bizName}${item.name ? `(${item.name})` : ''}`,
|
||||||
|
value: item.bizName,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const targetOptions = dataSourceToIdentifiers.map((item: any) => {
|
||||||
|
return {
|
||||||
|
label: `${item.bizName}${item.name ? `(${item.name})` : ''}`,
|
||||||
|
value: item.bizName,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setSourcePrimaryOptions(sourceOptions);
|
||||||
|
setTargetPrimaryOptions(targetOptions);
|
||||||
|
}, [nodeModel]);
|
||||||
|
|
||||||
|
const renderContent = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FormItem hidden={true} name="id" label="ID">
|
||||||
|
<Input placeholder="id" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="起始数据源:">
|
||||||
|
<span style={{ color: '#296df3', fontWeight: 500 }}>{nodeModel?.sourceData?.name}</span>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="目标数据源:">
|
||||||
|
<span style={{ color: '#296df3', fontWeight: 500 }}>{nodeModel?.targetData?.name}</span>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
name="leftField"
|
||||||
|
label="起始关联字段:"
|
||||||
|
rules={[{ required: true, message: '请选择关联主键' }]}
|
||||||
|
>
|
||||||
|
<Select placeholder="请选择关联主键" options={sourcePrimaryOptions} />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
name="rightField"
|
||||||
|
label="目标关联字段:"
|
||||||
|
rules={[{ required: true, message: '请选择关联主键' }]}
|
||||||
|
>
|
||||||
|
<Select placeholder="请选择关联主键" options={targetPrimaryOptions} />
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem
|
||||||
|
name="operator"
|
||||||
|
label="算子"
|
||||||
|
rules={[{ required: true, message: '请选择关联算子' }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
placeholder="请选择关联算子"
|
||||||
|
options={[
|
||||||
|
{ label: '=', value: '=' },
|
||||||
|
{ label: '!=', value: '!=' },
|
||||||
|
{ label: '>', value: '>' },
|
||||||
|
{ label: '>=', value: '>=' },
|
||||||
|
{ label: '<', value: '<' },
|
||||||
|
{ label: '<=', value: '<=' },
|
||||||
|
// { label: 'IN', value: 'IN' },
|
||||||
|
// { label: 'NOT_IN', value: 'NOT_IN' },
|
||||||
|
// { label: 'EQUALS', value: 'EQUALS' },
|
||||||
|
// { label: 'BETWEEN', value: 'BETWEEN' },
|
||||||
|
// { label: 'GREATER_THAN', value: 'GREATER_THAN' },
|
||||||
|
// { label: 'GREATER_THAN_EQUALS', value: 'GREATER_THAN_EQUALS' },
|
||||||
|
// { label: 'IS_NULL', value: 'IS_NULL' },
|
||||||
|
// { label: 'IS_NOT_NULL', value: 'IS_NOT_NULL' },
|
||||||
|
// { label: 'LIKE', value: 'LIKE' },
|
||||||
|
// { label: 'MINOR_THAN', value: 'MINOR_THAN' },
|
||||||
|
// { label: 'MINOR_THAN_EQUALS', value: 'MINOR_THAN_EQUALS' },
|
||||||
|
// { label: 'NOT_EQUALS', value: 'NOT_EQUALS' },
|
||||||
|
// { label: 'SQL_PART', value: 'SQL_PART' },
|
||||||
|
// { label: 'EXISTS', value: 'EXISTS' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem
|
||||||
|
name="joinType"
|
||||||
|
label="join类型"
|
||||||
|
rules={[{ required: true, message: '请选择join类型' }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
placeholder="请选择join类型"
|
||||||
|
options={[
|
||||||
|
{ label: 'left join', value: 'left join' },
|
||||||
|
{ label: 'inner join', value: 'inner join' },
|
||||||
|
{ label: 'right join', value: 'right join' },
|
||||||
|
{ label: 'outer join', value: 'outer join' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveRelation = async () => {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
setSaveLoading(true);
|
||||||
|
const { code, msg } = await createOrUpdateModelRela({
|
||||||
|
id: values.id,
|
||||||
|
domainId,
|
||||||
|
fromModelId: nodeModel?.sourceData?.uid,
|
||||||
|
toModelId: nodeModel?.targetData?.uid,
|
||||||
|
joinType: values.joinType,
|
||||||
|
joinConditions: [
|
||||||
|
{
|
||||||
|
...values,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
setSaveLoading(false);
|
||||||
|
if (code === 200) {
|
||||||
|
message.success('保存成功');
|
||||||
|
onSave?.();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.error(msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteRelation = async () => {
|
||||||
|
setDeleteLoading(true);
|
||||||
|
const { code, msg } = await deleteModelRela(relationData?.id);
|
||||||
|
setDeleteLoading(false);
|
||||||
|
if (code === 200) {
|
||||||
|
message.success('删除成功');
|
||||||
|
onDelete?.();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.error(msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderFooter = () => {
|
||||||
|
return (
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
onClose?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
取 消
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Popconfirm
|
||||||
|
title="确定删除吗?"
|
||||||
|
onCancel={(e) => {
|
||||||
|
e?.stopPropagation();
|
||||||
|
}}
|
||||||
|
onConfirm={() => {
|
||||||
|
if (relationData?.id) {
|
||||||
|
deleteRelation();
|
||||||
|
} else {
|
||||||
|
onDelete?.();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="primary" danger loading={deleteLoading}>
|
||||||
|
删 除
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
loading={saveLoading}
|
||||||
|
onClick={() => {
|
||||||
|
saveRelation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
保 存
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
forceRender
|
||||||
|
width={400}
|
||||||
|
destroyOnClose
|
||||||
|
getContainer={false}
|
||||||
|
title={'数据源关联信息'}
|
||||||
|
mask={false}
|
||||||
|
open={open}
|
||||||
|
footer={renderFooter()}
|
||||||
|
onClose={() => {
|
||||||
|
onClose?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form {...formLayout} form={form}>
|
||||||
|
{renderContent()}
|
||||||
|
</Form>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModelRelationFormDrawer;
|
||||||
@@ -7,7 +7,6 @@ import type { StateType } from '../../model';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import styles from '../style.less';
|
import styles from '../style.less';
|
||||||
import TransTypeTag from '../../components/TransTypeTag';
|
import TransTypeTag from '../../components/TransTypeTag';
|
||||||
import MetricTrendSection from '@/pages/SemanticModel/Metric/components/MetricTrendSection';
|
|
||||||
import { SENSITIVE_LEVEL_ENUM } from '../../constant';
|
import { SENSITIVE_LEVEL_ENUM } from '../../constant';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -106,16 +105,6 @@ const NodeInfoDrawer: React.FC<Props> = ({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: '指标趋势',
|
|
||||||
render: () => (
|
|
||||||
<Row key={`metricTrendSection`} style={{ marginBottom: 10, display: 'flex' }}>
|
|
||||||
<Col span={24}>
|
|
||||||
<MetricTrendSection nodeData={nodeData} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: '创建信息',
|
title: '创建信息',
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -7,15 +7,26 @@ const initTooltips = () => {
|
|||||||
offsetY: 10,
|
offsetY: 10,
|
||||||
fixToNode: [1, 0.5],
|
fixToNode: [1, 0.5],
|
||||||
// 允许出现 tooltip 的 item 类型
|
// 允许出现 tooltip 的 item 类型
|
||||||
itemTypes: ['node'],
|
itemTypes: ['node', 'edge'],
|
||||||
|
shouldBegin: (e) => {
|
||||||
|
const model = e!.item!.getModel();
|
||||||
|
const eleType = e!.item!.getType();
|
||||||
|
if (eleType === 'node' || (eleType === 'edge' && model.sourceAnchor)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
// 自定义 tooltip 内容
|
// 自定义 tooltip 内容
|
||||||
getContent: (e) => {
|
getContent: (e) => {
|
||||||
|
const eleType = e!.item!.getType();
|
||||||
const outDiv = document.createElement('div');
|
const outDiv = document.createElement('div');
|
||||||
outDiv.style.width = 'fit-content';
|
outDiv.style.width = 'fit-content';
|
||||||
outDiv.style.height = 'fit-content';
|
outDiv.style.height = 'fit-content';
|
||||||
const model = e!.item!.getModel();
|
const model = e!.item!.getModel();
|
||||||
|
const { name, bizName, createdBy, updatedAt, description, sourceAnchor } = model;
|
||||||
const { name, bizName, createdBy, updatedAt, description } = model;
|
if (eleType === 'edge' && sourceAnchor) {
|
||||||
|
return '点击编辑模型关系';
|
||||||
|
}
|
||||||
const list = [
|
const list = [
|
||||||
{
|
{
|
||||||
label: '名称:',
|
label: '名称:',
|
||||||
@@ -41,7 +52,7 @@ const initTooltips = () => {
|
|||||||
const listHtml = list.reduce((htmlString, item) => {
|
const listHtml = list.reduce((htmlString, item) => {
|
||||||
const { label, value } = item;
|
const { label, value } = item;
|
||||||
if (value) {
|
if (value) {
|
||||||
htmlString += `<p style="margin-bottom:0">
|
htmlString += `<p style="margin-bottom:0;margin-top:0">
|
||||||
<span>${label} </span>
|
<span>${label} </span>
|
||||||
<span>${value}</span>
|
<span>${value}</span>
|
||||||
</p>`;
|
</p>`;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React, { useEffect, useState, useRef } from 'react';
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
import { connect } from 'umi';
|
import { connect } from 'umi';
|
||||||
import type { StateType } from '../model';
|
import type { StateType } from '../model';
|
||||||
// import { IGroup } from '@antv/g-base';
|
|
||||||
import type { Dispatch } from 'umi';
|
import type { Dispatch } from 'umi';
|
||||||
import {
|
import {
|
||||||
typeConfigs,
|
typeConfigs,
|
||||||
@@ -11,8 +10,15 @@ import {
|
|||||||
flatGraphDataNode,
|
flatGraphDataNode,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
import { getDomainSchemaRela } from '../service';
|
import {
|
||||||
import { Item, TreeGraphData, NodeConfig, IItemBaseConfig } from '@antv/g6-core';
|
getDomainSchemaRela,
|
||||||
|
getModelRelaList,
|
||||||
|
getViewInfoList,
|
||||||
|
createOrUpdateViewInfo,
|
||||||
|
deleteViewInfo,
|
||||||
|
} from '../service';
|
||||||
|
import { jsonParse } from '@/utils/utils';
|
||||||
|
import { Item, TreeGraphData, NodeConfig, IItemBaseConfig, EdgeConfig } from '@antv/g6-core';
|
||||||
import initToolBar from './components/ToolBar';
|
import initToolBar from './components/ToolBar';
|
||||||
import initTooltips from './components/ToolTips';
|
import initTooltips from './components/ToolTips';
|
||||||
import initContextMenu from './components/ContextMenu';
|
import initContextMenu from './components/ContextMenu';
|
||||||
@@ -28,21 +34,14 @@ import ClassDataSourceTypeModal from '../components/ClassDataSourceTypeModal';
|
|||||||
import GraphToolBar from './components/GraphToolBar';
|
import GraphToolBar from './components/GraphToolBar';
|
||||||
import GraphLegend from './components/GraphLegend';
|
import GraphLegend from './components/GraphLegend';
|
||||||
import GraphLegendVisibleModeItem from './components/GraphLegendVisibleModeItem';
|
import GraphLegendVisibleModeItem from './components/GraphLegendVisibleModeItem';
|
||||||
|
import ModelRelationFormDrawer from './components/ModelRelationFormDrawer';
|
||||||
// import { cloneDeep } from 'lodash';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
// graphShowType?: SemanticNodeType;
|
|
||||||
domainManger: StateType;
|
domainManger: StateType;
|
||||||
dispatch: Dispatch;
|
dispatch: Dispatch;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DomainManger: React.FC<Props> = ({
|
const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||||
domainManger,
|
|
||||||
// graphShowType = SemanticNodeType.DIMENSION,
|
|
||||||
// graphShowType,
|
|
||||||
dispatch,
|
|
||||||
}) => {
|
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
const dataSourceRef = useRef<ISemantic.IDomainSchemaRelaList>([]);
|
const dataSourceRef = useRef<ISemantic.IDomainSchemaRelaList>([]);
|
||||||
const [graphData, setGraphData] = useState<TreeGraphData>();
|
const [graphData, setGraphData] = useState<TreeGraphData>();
|
||||||
@@ -54,7 +53,7 @@ const DomainManger: React.FC<Props> = ({
|
|||||||
|
|
||||||
const legendDataRef = useRef<any[]>([]);
|
const legendDataRef = useRef<any[]>([]);
|
||||||
const graphRef = useRef<any>(null);
|
const graphRef = useRef<any>(null);
|
||||||
// const legendDataFilterFunctions = useRef<any>({});
|
|
||||||
const [dimensionItem, setDimensionItem] = useState<ISemantic.IDimensionItem>();
|
const [dimensionItem, setDimensionItem] = useState<ISemantic.IDimensionItem>();
|
||||||
|
|
||||||
const [metricItem, setMetricItem] = useState<ISemantic.IMetricItem>();
|
const [metricItem, setMetricItem] = useState<ISemantic.IMetricItem>();
|
||||||
@@ -77,6 +76,21 @@ const DomainManger: React.FC<Props> = ({
|
|||||||
const graphShowTypeRef = useRef<SemanticNodeType>();
|
const graphShowTypeRef = useRef<SemanticNodeType>();
|
||||||
const [graphShowTypeState, setGraphShowTypeState] = useState<SemanticNodeType>();
|
const [graphShowTypeState, setGraphShowTypeState] = useState<SemanticNodeType>();
|
||||||
|
|
||||||
|
const [modelRelationDrawerOpen, setModelRelationDrawerOpen] = useState<boolean>(false);
|
||||||
|
const [nodeModel, setNodeModel] = useState<{
|
||||||
|
sourceData: ISemantic.IModelItem;
|
||||||
|
targetData: ISemantic.IModelItem;
|
||||||
|
}>({ sourceData: {} as ISemantic.IModelItem, targetData: {} as ISemantic.IModelItem });
|
||||||
|
|
||||||
|
// const [relationData, setRelationData] = useState<any[]>([]);
|
||||||
|
const relationDataListRef = useRef<any>([]);
|
||||||
|
|
||||||
|
const [relationConfig, setRelationConfig] = useState<any>({});
|
||||||
|
|
||||||
|
const [currentRelationDataItem, setCurrentRelationDataItem] = useState<any>({});
|
||||||
|
|
||||||
|
const [currentEdgeItem, setCurrentEdgeItem] = useState<any>();
|
||||||
|
|
||||||
const graphLegendDataSourceIds = useRef<string[]>();
|
const graphLegendDataSourceIds = useRef<string[]>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -137,15 +151,15 @@ const DomainManger: React.FC<Props> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const queryDataSourceList = async (params: {
|
const queryDataSourceList = async (params: {
|
||||||
modelId: number;
|
domainId: number;
|
||||||
graphShowType?: SemanticNodeType;
|
graphShowType?: SemanticNodeType;
|
||||||
}) => {
|
}) => {
|
||||||
const { code, data } = await getDomainSchemaRela(params.modelId);
|
const { code, data } = await getDomainSchemaRela(params.domainId);
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
if (data) {
|
if (data) {
|
||||||
setDataSourceInfoList(
|
setDataSourceInfoList(
|
||||||
data.map((item: ISemantic.IDomainSchemaRelaItem) => {
|
data.map((item: ISemantic.IDomainSchemaRelaItem) => {
|
||||||
return item.datasource;
|
return item.model;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const graphRootData = changeGraphData(data);
|
const graphRootData = changeGraphData(data);
|
||||||
@@ -160,11 +174,69 @@ const DomainManger: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getRelationConfig = async (domainId: number) => {
|
||||||
|
const { code, data, msg } = await getViewInfoList(domainId);
|
||||||
|
if (code === 200) {
|
||||||
|
const target = data[0];
|
||||||
|
if (target) {
|
||||||
|
const { config } = target;
|
||||||
|
const parseConfig = jsonParse(config, []);
|
||||||
|
setRelationConfig(target);
|
||||||
|
parseConfig.forEach((item) => {
|
||||||
|
graphRef?.current?.addItem('edge', item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message.error(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteRelationConfig = async (recordId: number) => {
|
||||||
|
const { code, data, msg } = await deleteViewInfo(recordId);
|
||||||
|
if (code === 200) {
|
||||||
|
} else {
|
||||||
|
message.error(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveRelationConfig = async (domainId: number, graphData: any) => {
|
||||||
|
const { code, msg } = await createOrUpdateViewInfo({
|
||||||
|
id: relationConfig?.id,
|
||||||
|
// modelId: domainManger.selectModelId,
|
||||||
|
domainId: domainId,
|
||||||
|
type: 'modelEdgeRelation',
|
||||||
|
config: JSON.stringify(graphData),
|
||||||
|
});
|
||||||
|
if (code === 200) {
|
||||||
|
queryModelRelaList(selectDomainId);
|
||||||
|
} else {
|
||||||
|
message.error(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryModelRelaList = async (domainId: number) => {
|
||||||
|
const { code, data, msg } = await getModelRelaList(domainId);
|
||||||
|
if (code === 200) {
|
||||||
|
// setRelationData(data);
|
||||||
|
relationDataListRef.current = data;
|
||||||
|
} else {
|
||||||
|
message.error(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteEdge = () => {
|
||||||
|
graphRef.current.removeItem(currentEdgeItem);
|
||||||
|
setCurrentEdgeItem(undefined);
|
||||||
|
saveModelRelationEdges();
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
graphLegendDataSourceIds.current = undefined;
|
graphLegendDataSourceIds.current = undefined;
|
||||||
graphRef.current = null;
|
graphRef.current = null;
|
||||||
queryDataSourceList({ modelId });
|
queryDataSourceList({ domainId: selectDomainId });
|
||||||
}, [modelId]);
|
queryModelRelaList(selectDomainId);
|
||||||
|
// deleteRelationConfig(16);
|
||||||
|
}, [selectDomainId]);
|
||||||
|
|
||||||
// const getLegendDataFilterFunctions = () => {
|
// const getLegendDataFilterFunctions = () => {
|
||||||
// legendDataRef.current.map((item: any) => {
|
// legendDataRef.current.map((item: any) => {
|
||||||
@@ -310,6 +382,13 @@ const DomainManger: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const modelRelationDataInit = (fromModelId: number, toModelId: number) => {
|
||||||
|
const targetData = relationDataListRef.current.find((item) => {
|
||||||
|
return item.fromModelId === fromModelId && item.toModelId === toModelId;
|
||||||
|
});
|
||||||
|
setCurrentRelationDataItem(targetData);
|
||||||
|
};
|
||||||
|
|
||||||
const handleNodeTypeClick = (nodeData: any) => {
|
const handleNodeTypeClick = (nodeData: any) => {
|
||||||
setCurrentNodeData(nodeData);
|
setCurrentNodeData(nodeData);
|
||||||
setInfoDrawerVisible(true);
|
setInfoDrawerVisible(true);
|
||||||
@@ -399,7 +478,8 @@ const DomainManger: React.FC<Props> = ({
|
|||||||
|
|
||||||
if (!graph && graphData) {
|
if (!graph && graphData) {
|
||||||
const graphNodeList = flatGraphDataNode(graphData.children);
|
const graphNodeList = flatGraphDataNode(graphData.children);
|
||||||
const graphConfigKey = graphNodeList.length > 20 ? 'dendrogram' : 'mindmap';
|
// const graphConfigKey = graphNodeList.length > 20 ? 'dendrogram' : 'mindmap';
|
||||||
|
const graphConfigKey = 'dendrogram';
|
||||||
|
|
||||||
// getLegendDataFilterFunctions();
|
// getLegendDataFilterFunctions();
|
||||||
const toolbar = initToolBar({ onSearch: handleSeachNode, onClick: handleToolBarClick });
|
const toolbar = initToolBar({ onSearch: handleSeachNode, onClick: handleToolBarClick });
|
||||||
@@ -407,9 +487,158 @@ const DomainManger: React.FC<Props> = ({
|
|||||||
const contextMenu = initContextMenu({
|
const contextMenu = initContextMenu({
|
||||||
onMenuClick: handleContextMenuClick,
|
onMenuClick: handleContextMenuClick,
|
||||||
});
|
});
|
||||||
// const legend = initLegend({
|
|
||||||
// nodeData: legendDataRef.current,
|
G6.registerNode(
|
||||||
// filterFunctions: { ...legendDataFilterFunctions.current },
|
'rect-node',
|
||||||
|
{
|
||||||
|
width: 220,
|
||||||
|
height: 80,
|
||||||
|
afterDraw(cfg, group) {
|
||||||
|
group.addShape('circle', {
|
||||||
|
attrs: {
|
||||||
|
r: 8,
|
||||||
|
x: 80 / 2,
|
||||||
|
y: 0,
|
||||||
|
fill: '#fff',
|
||||||
|
stroke: '#5F95FF',
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: `anchor-point`, // the name, for searching by group.find(ele => ele.get('name') === 'anchor-point')
|
||||||
|
anchorPointIdx: 1, // flag the idx of the anchor-point circle
|
||||||
|
links: 0, // cache the number of edges connected to this shape
|
||||||
|
visible: false, // invisible by default, shows up when links > 1 or the node is in showAnchors state
|
||||||
|
draggable: true, // allow to catch the drag events on this shape
|
||||||
|
});
|
||||||
|
|
||||||
|
group.addShape('circle', {
|
||||||
|
attrs: {
|
||||||
|
r: 8,
|
||||||
|
x: -80 / 2,
|
||||||
|
y: 0,
|
||||||
|
fill: '#fff',
|
||||||
|
stroke: '#5F95FF',
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: `anchor-point`, // the name, for searching by group.find(ele => ele.get('name') === 'anchor-point')
|
||||||
|
anchorPointIdx: 2, // flag the idx of the anchor-point circle
|
||||||
|
links: 0, // cache the number of edges connected to this shape
|
||||||
|
visible: false, // invisible by default, shows up when links > 1 or the node is in showAnchors state
|
||||||
|
draggable: true, // allow to catch the drag events on this shape
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setState(name, value, item) {
|
||||||
|
if (name === 'showAnchors') {
|
||||||
|
const anchorPoints = item
|
||||||
|
.getContainer()
|
||||||
|
.findAll((ele) => ele.get('name') === 'anchor-point');
|
||||||
|
anchorPoints.forEach((point) => {
|
||||||
|
if (value || point.get('links') > 0) point.show();
|
||||||
|
else point.hide();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'rect',
|
||||||
|
);
|
||||||
|
|
||||||
|
let sourceAnchorIdx: any;
|
||||||
|
let targetAnchorIdx: any;
|
||||||
|
|
||||||
|
// graphRef.current = new G6.TreeGraph({
|
||||||
|
// container: 'semanticGraph',
|
||||||
|
// width,
|
||||||
|
// height,
|
||||||
|
// modes: {
|
||||||
|
// default: [
|
||||||
|
// // {
|
||||||
|
// // type: 'collapse-expand',
|
||||||
|
// // onChange: function onChange(item, collapsed) {
|
||||||
|
// // const data = item!.get('model');
|
||||||
|
// // data.collapsed = collapsed;
|
||||||
|
// // return true;
|
||||||
|
// // },
|
||||||
|
// // },
|
||||||
|
// // 'drag-node',
|
||||||
|
// {
|
||||||
|
// type: 'drag-node',
|
||||||
|
// shouldBegin: (e) => {
|
||||||
|
// if (e.target.get('name') === 'anchor-point') return false;
|
||||||
|
// return true;
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// 'drag-canvas',
|
||||||
|
// {
|
||||||
|
// type: 'create-edge',
|
||||||
|
// trigger: 'drag', // set the trigger to be drag to make the create-edge triggered by drag
|
||||||
|
// shouldBegin: (e) => {
|
||||||
|
// // avoid beginning at other shapes on the node
|
||||||
|
// if (e.target && e.target.get('name') !== 'anchor-point') return false;
|
||||||
|
// sourceAnchorIdx = e.target.get('anchorPointIdx');
|
||||||
|
// e.target.set('links', e.target.get('links') + 1); // cache the number of edge connected to this anchor-point circle
|
||||||
|
// return true;
|
||||||
|
// },
|
||||||
|
// shouldEnd: (e) => {
|
||||||
|
// // avoid ending at other shapes on the node
|
||||||
|
// if (e.target && e.target.get('name') !== 'anchor-point') return false;
|
||||||
|
// if (e.target) {
|
||||||
|
// targetAnchorIdx = e.target.get('anchorPointIdx');
|
||||||
|
// e.target.set('links', e.target.get('links') + 1); // cache the number of edge connected to this anchor-point circle
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// targetAnchorIdx = undefined;
|
||||||
|
// return true;
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// // 'activate-relations',
|
||||||
|
// {
|
||||||
|
// type: 'zoom-canvas',
|
||||||
|
// sensitivity: 0.3, // 设置缩放灵敏度,值越小,缩放越不敏感,默认值为 1
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// type: 'activate-relations',
|
||||||
|
// trigger: 'mouseenter', // 触发方式,可以是 'mouseenter' 或 'click'
|
||||||
|
// resetSelected: true, // 点击空白处时,是否取消高亮
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// // defaultNode: {
|
||||||
|
// // size: 26,
|
||||||
|
// // anchorPoints: [
|
||||||
|
// // [0, 0.5],
|
||||||
|
// // [1, 0.5],
|
||||||
|
// // ],
|
||||||
|
// // labelCfg: {
|
||||||
|
// // position: 'right',
|
||||||
|
// // offset: 5,
|
||||||
|
// // style: {
|
||||||
|
// // stroke: '#fff',
|
||||||
|
// // lineWidth: 4,
|
||||||
|
// // },
|
||||||
|
// // },
|
||||||
|
// // },
|
||||||
|
|
||||||
|
// // defaultEdge: {
|
||||||
|
// // type: graphConfigMap[graphConfigKey].defaultEdge.type,
|
||||||
|
// // },
|
||||||
|
// defaultNode: {
|
||||||
|
// type: 'rect-node',
|
||||||
|
// style: {
|
||||||
|
// fill: '#eee',
|
||||||
|
// stroke: '#ccc',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// defaultEdge: {
|
||||||
|
// type: 'quadratic',
|
||||||
|
// style: {
|
||||||
|
// stroke: '#F6BD16',
|
||||||
|
// lineWidth: 2,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// layout: {
|
||||||
|
// ...graphConfigMap[graphConfigKey].layout,
|
||||||
|
// },
|
||||||
|
// plugins: [tooltip, toolbar, contextMenu],
|
||||||
|
// // plugins: [legend, tooltip, toolbar, contextMenu],
|
||||||
// });
|
// });
|
||||||
|
|
||||||
graphRef.current = new G6.TreeGraph({
|
graphRef.current = new G6.TreeGraph({
|
||||||
@@ -418,17 +647,38 @@ const DomainManger: React.FC<Props> = ({
|
|||||||
height,
|
height,
|
||||||
modes: {
|
modes: {
|
||||||
default: [
|
default: [
|
||||||
// {
|
// config the shouldBegin for drag-node to avoid node moving while dragging on the anchor-point circles
|
||||||
// type: 'collapse-expand',
|
{
|
||||||
// onChange: function onChange(item, collapsed) {
|
type: 'drag-node',
|
||||||
// const data = item!.get('model');
|
shouldBegin: (e) => {
|
||||||
// data.collapsed = collapsed;
|
if (e.target.get('name') === 'anchor-point') return false;
|
||||||
// return true;
|
return true;
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
'drag-node',
|
|
||||||
'drag-canvas',
|
'drag-canvas',
|
||||||
// 'activate-relations',
|
// config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles
|
||||||
|
{
|
||||||
|
type: 'create-edge',
|
||||||
|
trigger: 'drag', // set the trigger to be drag to make the create-edge triggered by drag
|
||||||
|
shouldBegin: (e) => {
|
||||||
|
// avoid beginning at other shapes on the node
|
||||||
|
if (e.target && e.target.get('name') !== 'anchor-point') return false;
|
||||||
|
sourceAnchorIdx = e.target.get('anchorPointIdx');
|
||||||
|
e.target.set('links', e.target.get('links') + 1); // cache the number of edge connected to this anchor-point circle
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
shouldEnd: (e) => {
|
||||||
|
// avoid ending at other shapes on the node
|
||||||
|
if (e.target && e.target.get('name') !== 'anchor-point') return false;
|
||||||
|
if (e.target) {
|
||||||
|
targetAnchorIdx = e.target.get('anchorPointIdx');
|
||||||
|
e.target.set('links', e.target.get('links') + 1); // cache the number of edge connected to this anchor-point circle
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
targetAnchorIdx = undefined;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'zoom-canvas',
|
type: 'zoom-canvas',
|
||||||
sensitivity: 0.3, // 设置缩放灵敏度,值越小,缩放越不敏感,默认值为 1
|
sensitivity: 0.3, // 设置缩放灵敏度,值越小,缩放越不敏感,默认值为 1
|
||||||
@@ -440,30 +690,23 @@ const DomainManger: React.FC<Props> = ({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
defaultNode: {
|
layout: {
|
||||||
size: 26,
|
...graphConfigMap[graphConfigKey].layout,
|
||||||
anchorPoints: [
|
|
||||||
[0, 0.5],
|
|
||||||
[1, 0.5],
|
|
||||||
],
|
|
||||||
labelCfg: {
|
|
||||||
position: 'right',
|
|
||||||
offset: 5,
|
|
||||||
style: {
|
|
||||||
stroke: '#fff',
|
|
||||||
lineWidth: 4,
|
|
||||||
},
|
},
|
||||||
|
plugins: [tooltip, toolbar, contextMenu],
|
||||||
|
|
||||||
|
defaultNode: {
|
||||||
|
type: 'rect-node',
|
||||||
|
style: {
|
||||||
|
fill: '#eee',
|
||||||
|
stroke: '#ccc',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultEdge: {
|
defaultEdge: {
|
||||||
type: graphConfigMap[graphConfigKey].defaultEdge.type,
|
type: graphConfigMap[graphConfigKey].defaultEdge.type,
|
||||||
},
|
},
|
||||||
layout: {
|
|
||||||
...graphConfigMap[graphConfigKey].layout,
|
|
||||||
},
|
|
||||||
plugins: [tooltip, toolbar, contextMenu],
|
|
||||||
// plugins: [legend, tooltip, toolbar, contextMenu],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
graphRef.current.set('initGraphData', graphData);
|
graphRef.current.set('initGraphData', graphData);
|
||||||
graphRef.current.set('initDataSource', dataSourceRef.current);
|
graphRef.current.set('initDataSource', dataSourceRef.current);
|
||||||
|
|
||||||
@@ -496,6 +739,122 @@ const DomainManger: React.FC<Props> = ({
|
|||||||
label: node.name,
|
label: node.name,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
graphRef.current.on('aftercreateedge', (e) => {
|
||||||
|
// update the sourceAnchor and targetAnchor for the newly added edge
|
||||||
|
const model = e?.item?.getModel?.() || {};
|
||||||
|
const { targetAnchor, sourceAnchor } = model;
|
||||||
|
if (!targetAnchor && !sourceAnchor) {
|
||||||
|
graphRef.current.updateItem(e.edge, {
|
||||||
|
sourceAnchor: sourceAnchorIdx,
|
||||||
|
targetAnchor: targetAnchorIdx,
|
||||||
|
label: '模型关系编辑',
|
||||||
|
style: {
|
||||||
|
stroke: '#296df3',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the curveOffset for parallel edges
|
||||||
|
// const edges = graphRef.current.save().edges;
|
||||||
|
|
||||||
|
// const savedGraph = graphRef.current.save();
|
||||||
|
// const edges = [];
|
||||||
|
|
||||||
|
// // savedGraph.children.forEach((root) => {
|
||||||
|
// traverse(savedGraph, null);
|
||||||
|
// // });
|
||||||
|
|
||||||
|
// function traverse(node, parent) {
|
||||||
|
// if (Array.isArray(node.children)) {
|
||||||
|
// node.children.forEach((child) => {
|
||||||
|
// if (child.type === 'edge') {
|
||||||
|
// // 假设边的节点类型为 'edge'
|
||||||
|
// edges.push({
|
||||||
|
// source: parent ? parent.id : null,
|
||||||
|
// target: child.id,
|
||||||
|
// // 其他边的属性
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// traverse(child, node);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // processParallelEdgesOnAnchorPoint(edges);
|
||||||
|
// graphRef.current.getEdges().forEach((edge, i) => {
|
||||||
|
// graphRef.current.updateItem(edge, {
|
||||||
|
// curveOffset: edges[i].curveOffset,
|
||||||
|
// curvePosition: edges[i].curvePosition,
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
|
||||||
|
graphRef.current.on('afteradditem', (e) => {
|
||||||
|
const model = e!.item!.getModel();
|
||||||
|
const { sourceAnchor, targetAnchor } = model;
|
||||||
|
if (e.item && e.item.getType() === 'edge') {
|
||||||
|
if (!sourceAnchor) {
|
||||||
|
graphRef.current.updateItem(e.item, {
|
||||||
|
sourceAnchor: sourceAnchorIdx,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (sourceAnchor && targetAnchor) {
|
||||||
|
graphRef.current.updateItem(e.item, {
|
||||||
|
label: '模型关系编辑',
|
||||||
|
style: {
|
||||||
|
stroke: '#296df3',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
graphRef.current.on('afterremoveitem', (e) => {
|
||||||
|
if (e.item && e.item.source && e.item.target) {
|
||||||
|
const sourceNode = graphRef.current.findById(e.item.source);
|
||||||
|
const targetNode = graphRef.current.findById(e.item.target);
|
||||||
|
const { sourceAnchor, targetAnchor } = e.item;
|
||||||
|
if (sourceNode && !isNaN(sourceAnchor)) {
|
||||||
|
const sourceAnchorShape = sourceNode
|
||||||
|
.getContainer()
|
||||||
|
.find(
|
||||||
|
(ele) =>
|
||||||
|
ele.get('name') === 'anchor-point' && ele.get('anchorPointIdx') === sourceAnchor,
|
||||||
|
);
|
||||||
|
sourceAnchorShape.set('links', sourceAnchorShape.get('links') - 1);
|
||||||
|
}
|
||||||
|
if (targetNode && !isNaN(targetAnchor)) {
|
||||||
|
const targetAnchorShape = targetNode
|
||||||
|
.getContainer()
|
||||||
|
.find(
|
||||||
|
(ele) =>
|
||||||
|
ele.get('name') === 'anchor-point' && ele.get('anchorPointIdx') === targetAnchor,
|
||||||
|
);
|
||||||
|
targetAnchorShape.set('links', targetAnchorShape.get('links') - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
graphRef.current.on('node:mouseenter', (e) => {
|
||||||
|
graphRef.current.setItemState(e.item, 'showAnchors', true);
|
||||||
|
});
|
||||||
|
graphRef.current.on('node:mouseleave', (e) => {
|
||||||
|
graphRef.current.setItemState(e.item, 'showAnchors', false);
|
||||||
|
});
|
||||||
|
graphRef.current.on('node:dragenter', (e) => {
|
||||||
|
graphRef.current.setItemState(e.item, 'showAnchors', true);
|
||||||
|
});
|
||||||
|
graphRef.current.on('node:dragleave', (e) => {
|
||||||
|
graphRef.current.setItemState(e.item, 'showAnchors', false);
|
||||||
|
});
|
||||||
|
graphRef.current.on('node:dragstart', (e) => {
|
||||||
|
graphRef.current.setItemState(e.item, 'showAnchors', true);
|
||||||
|
});
|
||||||
|
graphRef.current.on('node:dragout', (e) => {
|
||||||
|
graphRef.current.setItemState(e.item, 'showAnchors', false);
|
||||||
|
});
|
||||||
|
|
||||||
graphRef.current.data(graphData);
|
graphRef.current.data(graphData);
|
||||||
graphRef.current.render();
|
graphRef.current.render();
|
||||||
|
|
||||||
@@ -524,12 +883,34 @@ const DomainManger: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
graphRef.current.on('edge:click', (e: any) => {
|
||||||
|
const model = e!.item!.getModel();
|
||||||
|
const eleType = e!.item!.getType();
|
||||||
|
const sourceNode = e.item.get('sourceNode');
|
||||||
|
const targetNode = e.item.get('targetNode');
|
||||||
|
if (eleType === 'node' || (eleType === 'edge' && model.sourceAnchor)) {
|
||||||
|
if (sourceNode && targetNode) {
|
||||||
|
const sourceData = sourceNode.getModel();
|
||||||
|
const targetData = targetNode.getModel();
|
||||||
|
setNodeModel({
|
||||||
|
sourceData,
|
||||||
|
targetData,
|
||||||
|
});
|
||||||
|
modelRelationDataInit(sourceData.uid, targetData.uid);
|
||||||
|
}
|
||||||
|
setCurrentEdgeItem(e.item);
|
||||||
|
setModelRelationDrawerOpen(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
graphRef.current.on('canvas:click', () => {
|
graphRef.current.on('canvas:click', () => {
|
||||||
setInfoDrawerVisible(false);
|
setInfoDrawerVisible(false);
|
||||||
|
setModelRelationDrawerOpen(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
const rootNode = graphRef.current.findById('root');
|
const rootNode = graphRef.current.findById('root');
|
||||||
graphRef.current.hideItem(rootNode);
|
graphRef.current.hideItem(rootNode);
|
||||||
|
getRelationConfig(selectDomainId);
|
||||||
if (typeof window !== 'undefined')
|
if (typeof window !== 'undefined')
|
||||||
window.onresize = () => {
|
window.onresize = () => {
|
||||||
if (!graphRef.current || graphRef.current.get('destroyed')) return;
|
if (!graphRef.current || graphRef.current.get('destroyed')) return;
|
||||||
@@ -541,7 +922,7 @@ const DomainManger: React.FC<Props> = ({
|
|||||||
|
|
||||||
const updateGraphData = async (params?: { graphShowType?: SemanticNodeType }) => {
|
const updateGraphData = async (params?: { graphShowType?: SemanticNodeType }) => {
|
||||||
const graphRootData = await queryDataSourceList({
|
const graphRootData = await queryDataSourceList({
|
||||||
modelId,
|
domainId: selectDomainId,
|
||||||
graphShowType: params?.graphShowType,
|
graphShowType: params?.graphShowType,
|
||||||
});
|
});
|
||||||
if (graphRootData) {
|
if (graphRootData) {
|
||||||
@@ -556,6 +937,15 @@ const DomainManger: React.FC<Props> = ({
|
|||||||
graphRef.current.fitView();
|
graphRef.current.fitView();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const saveModelRelationEdges = () => {
|
||||||
|
const edges = graphRef.current.getEdges();
|
||||||
|
const edgesModel = edges.map((edge) => edge.getModel());
|
||||||
|
const modelRelationEdges = edgesModel.filter(
|
||||||
|
(edgeModel) => edgeModel.sourceAnchor && edgeModel.targetAnchor,
|
||||||
|
);
|
||||||
|
saveRelationConfig(selectDomainId, modelRelationEdges);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<GraphLegend
|
<GraphLegend
|
||||||
@@ -719,6 +1109,26 @@ const DomainManger: React.FC<Props> = ({
|
|||||||
nodeData={currentNodeData}
|
nodeData={currentNodeData}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
<ModelRelationFormDrawer
|
||||||
|
domainId={domainManger.selectDomainId}
|
||||||
|
nodeModel={nodeModel}
|
||||||
|
relationData={currentRelationDataItem}
|
||||||
|
onClose={() => {
|
||||||
|
setCurrentRelationDataItem({});
|
||||||
|
setModelRelationDrawerOpen(false);
|
||||||
|
}}
|
||||||
|
onSave={() => {
|
||||||
|
saveModelRelationEdges();
|
||||||
|
setCurrentRelationDataItem({});
|
||||||
|
setModelRelationDrawerOpen(false);
|
||||||
|
}}
|
||||||
|
onDelete={() => {
|
||||||
|
handleDeleteEdge();
|
||||||
|
setCurrentRelationDataItem({});
|
||||||
|
setModelRelationDrawerOpen(false);
|
||||||
|
}}
|
||||||
|
open={modelRelationDrawerOpen}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,817 @@
|
|||||||
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
|
import { connect } from 'umi';
|
||||||
|
import type { StateType } from '../model';
|
||||||
|
// import { IGroup } from '@antv/g-base';
|
||||||
|
import type { Dispatch } from 'umi';
|
||||||
|
import {
|
||||||
|
typeConfigs,
|
||||||
|
formatterRelationData,
|
||||||
|
loopNodeFindDataSource,
|
||||||
|
getNodeConfigByType,
|
||||||
|
flatGraphDataNode,
|
||||||
|
} from './utils';
|
||||||
|
import { message } from 'antd';
|
||||||
|
import { getDomainSchemaRela } from '../service';
|
||||||
|
import { Item, TreeGraphData, NodeConfig, IItemBaseConfig } from '@antv/g6-core';
|
||||||
|
import initToolBar from './components/ToolBar';
|
||||||
|
import initTooltips from './components/ToolTips';
|
||||||
|
import initContextMenu from './components/ContextMenu';
|
||||||
|
// import initLegend from './components/Legend';
|
||||||
|
import { SemanticNodeType } from '../enum';
|
||||||
|
import G6 from '@antv/g6';
|
||||||
|
import { ISemantic, IDataSource } from '../data';
|
||||||
|
import NodeInfoDrawer from './components/NodeInfoDrawer';
|
||||||
|
import DimensionInfoModal from '../components/DimensionInfoModal';
|
||||||
|
import MetricInfoCreateForm from '../components/MetricInfoCreateForm';
|
||||||
|
import DeleteConfirmModal from './components/DeleteConfirmModal';
|
||||||
|
import ClassDataSourceTypeModal from '../components/ClassDataSourceTypeModal';
|
||||||
|
import GraphToolBar from './components/GraphToolBar';
|
||||||
|
import GraphLegend from './components/GraphLegend';
|
||||||
|
import GraphLegendVisibleModeItem from './components/GraphLegendVisibleModeItem';
|
||||||
|
import { flowRectNodeRegister, cardNodeRegister } from './CustomNodeRegister';
|
||||||
|
|
||||||
|
// import { cloneDeep } from 'lodash';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
// graphShowType?: SemanticNodeType;
|
||||||
|
domainManger: StateType;
|
||||||
|
dispatch: Dispatch;
|
||||||
|
};
|
||||||
|
|
||||||
|
flowRectNodeRegister();
|
||||||
|
|
||||||
|
const DomainManger: React.FC<Props> = ({
|
||||||
|
domainManger,
|
||||||
|
// graphShowType = SemanticNodeType.DIMENSION,
|
||||||
|
// graphShowType,
|
||||||
|
dispatch,
|
||||||
|
}) => {
|
||||||
|
const ref = useRef(null);
|
||||||
|
const dataSourceRef = useRef<ISemantic.IDomainSchemaRelaList>([]);
|
||||||
|
const [graphData, setGraphData] = useState<TreeGraphData>();
|
||||||
|
const [createDimensionModalVisible, setCreateDimensionModalVisible] = useState<boolean>(false);
|
||||||
|
const [createMetricModalVisible, setCreateMetricModalVisible] = useState<boolean>(false);
|
||||||
|
const [infoDrawerVisible, setInfoDrawerVisible] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [currentNodeData, setCurrentNodeData] = useState<any>();
|
||||||
|
|
||||||
|
const legendDataRef = useRef<any[]>([]);
|
||||||
|
const graphRef = useRef<any>(null);
|
||||||
|
// const legendDataFilterFunctions = useRef<any>({});
|
||||||
|
const [dimensionItem, setDimensionItem] = useState<ISemantic.IDimensionItem>();
|
||||||
|
|
||||||
|
const [metricItem, setMetricItem] = useState<ISemantic.IMetricItem>();
|
||||||
|
|
||||||
|
const [nodeDataSource, setNodeDataSource] = useState<any>();
|
||||||
|
|
||||||
|
const [dataSourceInfoList, setDataSourceInfoList] = useState<IDataSource.IDataSourceItem[]>([]);
|
||||||
|
|
||||||
|
const { dimensionList, metricList, selectModelId: modelId, selectDomainId } = domainManger;
|
||||||
|
|
||||||
|
const dimensionListRef = useRef<ISemantic.IDimensionItem[]>([]);
|
||||||
|
const metricListRef = useRef<ISemantic.IMetricItem[]>([]);
|
||||||
|
|
||||||
|
const [confirmModalOpenState, setConfirmModalOpenState] = useState<boolean>(false);
|
||||||
|
const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false);
|
||||||
|
|
||||||
|
const visibleModeOpenRef = useRef<boolean>(false);
|
||||||
|
const [visibleModeOpen, setVisibleModeOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const graphShowTypeRef = useRef<SemanticNodeType>();
|
||||||
|
const [graphShowTypeState, setGraphShowTypeState] = useState<SemanticNodeType>();
|
||||||
|
|
||||||
|
const graphLegendDataSourceIds = useRef<string[]>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dimensionListRef.current = dimensionList;
|
||||||
|
metricListRef.current = metricList;
|
||||||
|
}, [dimensionList, metricList]);
|
||||||
|
|
||||||
|
const handleSeachNode = (text: string) => {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
data.push({
|
||||||
|
...item,
|
||||||
|
dimensions: dimensionsList,
|
||||||
|
metrics: metricsList,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
const rootGraphData = changeGraphData(filterData);
|
||||||
|
refreshGraphData(rootGraphData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeGraphData = (dataSourceList: ISemantic.IDomainSchemaRelaList): TreeGraphData => {
|
||||||
|
const relationData = formatterRelationData({
|
||||||
|
dataSourceList,
|
||||||
|
type: graphShowTypeRef.current,
|
||||||
|
limit: 20,
|
||||||
|
showDataSourceId: graphLegendDataSourceIds.current,
|
||||||
|
});
|
||||||
|
|
||||||
|
const graphRootData = {
|
||||||
|
id: 'root',
|
||||||
|
name: domainManger.selectDomainName,
|
||||||
|
children: relationData,
|
||||||
|
};
|
||||||
|
return graphRootData;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initLegendData = (graphRootData: TreeGraphData) => {
|
||||||
|
const legendList = graphRootData?.children?.map((item: any) => {
|
||||||
|
const { id, name } = item;
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
label: name,
|
||||||
|
order: 4,
|
||||||
|
...typeConfigs.datasource,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
legendDataRef.current = legendList as any;
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryDataSourceList = async (params: {
|
||||||
|
domainId: number;
|
||||||
|
graphShowType?: SemanticNodeType;
|
||||||
|
}) => {
|
||||||
|
const { code, data } = await getDomainSchemaRela(params.domainId);
|
||||||
|
if (code === 200) {
|
||||||
|
if (data) {
|
||||||
|
setDataSourceInfoList(
|
||||||
|
data.map((item: ISemantic.IDomainSchemaRelaItem) => {
|
||||||
|
return item.model;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const graphRootData = changeGraphData(data);
|
||||||
|
dataSourceRef.current = data;
|
||||||
|
initLegendData(graphRootData);
|
||||||
|
setGraphData(graphRootData);
|
||||||
|
return graphRootData;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
graphLegendDataSourceIds.current = undefined;
|
||||||
|
graphRef.current = null;
|
||||||
|
queryDataSourceList({ domainId: selectDomainId });
|
||||||
|
}, [selectDomainId]);
|
||||||
|
|
||||||
|
// const getLegendDataFilterFunctions = () => {
|
||||||
|
// legendDataRef.current.map((item: any) => {
|
||||||
|
// const { id } = item;
|
||||||
|
// legendDataFilterFunctions.current = {
|
||||||
|
// ...legendDataFilterFunctions.current,
|
||||||
|
// [id]: (d: any) => {
|
||||||
|
// if (d.legendType === id) {
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// return false;
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const setAllActiveLegend = (legend: any) => {
|
||||||
|
// const legendCanvas = legend._cfgs.legendCanvas;
|
||||||
|
// if (!legendCanvas) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// // 从图例中找出node-group节点;
|
||||||
|
// const group = legendCanvas.find((e: any) => e.get('name') === 'node-group');
|
||||||
|
// // 数据源的图例节点在node-group中的children中;
|
||||||
|
// const groups = group.get('children');
|
||||||
|
// groups.forEach((itemGroup: any) => {
|
||||||
|
// const labelText = itemGroup.find((e: any) => e.get('name') === 'circle-node-text');
|
||||||
|
// // legend中activateLegend事件触发在图例节点的Text上,方法中存在向上溯源的逻辑:const shapeGroup = shape.get('parent');
|
||||||
|
// // 因此复用实例方法时,在这里不能直接将图例节点传入,需要在节点的children中找任意一个元素作为入参;
|
||||||
|
// legend.activateLegend(labelText);
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|
||||||
|
const handleContextMenuClickEdit = (item: IItemBaseConfig) => {
|
||||||
|
const targetData = item.model;
|
||||||
|
if (!targetData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const datasource = loopNodeFindDataSource(item);
|
||||||
|
if (datasource) {
|
||||||
|
setNodeDataSource({
|
||||||
|
...datasource,
|
||||||
|
id: datasource.uid,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (targetData.nodeType === SemanticNodeType.DATASOURCE) {
|
||||||
|
setCreateDataSourceModalOpen(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (targetData.nodeType === SemanticNodeType.DIMENSION) {
|
||||||
|
const targetItem = dimensionListRef.current.find((item) => item.id === targetData.uid);
|
||||||
|
if (targetItem) {
|
||||||
|
setDimensionItem({ ...targetItem });
|
||||||
|
setCreateDimensionModalVisible(true);
|
||||||
|
} else {
|
||||||
|
message.error('获取维度初始化数据失败');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (targetData.nodeType === SemanticNodeType.METRIC) {
|
||||||
|
const targetItem = metricListRef.current.find((item) => item.id === targetData.uid);
|
||||||
|
if (targetItem) {
|
||||||
|
setMetricItem({ ...targetItem });
|
||||||
|
setCreateMetricModalVisible(true);
|
||||||
|
} else {
|
||||||
|
message.error('获取指标初始化数据失败');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleContextMenuClickCreate = (item: IItemBaseConfig, key: string) => {
|
||||||
|
const datasource = item.model;
|
||||||
|
if (!datasource) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setNodeDataSource({
|
||||||
|
...datasource,
|
||||||
|
id: datasource.uid,
|
||||||
|
});
|
||||||
|
if (key === 'createDimension') {
|
||||||
|
setCreateDimensionModalVisible(true);
|
||||||
|
}
|
||||||
|
if (key === 'createMetric') {
|
||||||
|
setCreateMetricModalVisible(true);
|
||||||
|
}
|
||||||
|
setDimensionItem(undefined);
|
||||||
|
setMetricItem(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleContextMenuClickDelete = (item: IItemBaseConfig) => {
|
||||||
|
const targetData = item.model;
|
||||||
|
if (!targetData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (targetData.nodeType === SemanticNodeType.DATASOURCE) {
|
||||||
|
setCurrentNodeData({
|
||||||
|
...targetData,
|
||||||
|
id: targetData.uid,
|
||||||
|
});
|
||||||
|
setConfirmModalOpenState(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (targetData.nodeType === SemanticNodeType.DIMENSION) {
|
||||||
|
const targetItem = dimensionListRef.current.find((item) => item.id === targetData.uid);
|
||||||
|
if (targetItem) {
|
||||||
|
setCurrentNodeData({ ...targetData, ...targetItem });
|
||||||
|
setConfirmModalOpenState(true);
|
||||||
|
} else {
|
||||||
|
message.error('获取维度初始化数据失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (targetData.nodeType === SemanticNodeType.METRIC) {
|
||||||
|
const targetItem = metricListRef.current.find((item) => item.id === targetData.uid);
|
||||||
|
if (targetItem) {
|
||||||
|
setCurrentNodeData({ ...targetData, ...targetItem });
|
||||||
|
setConfirmModalOpenState(true);
|
||||||
|
} else {
|
||||||
|
message.error('获取指标初始化数据失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleContextMenuClick = (key: string, item: Item) => {
|
||||||
|
if (!item?._cfg) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (key) {
|
||||||
|
case 'edit':
|
||||||
|
case 'editDatasource':
|
||||||
|
handleContextMenuClickEdit(item._cfg);
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
case 'deleteDatasource':
|
||||||
|
handleContextMenuClickDelete(item._cfg);
|
||||||
|
break;
|
||||||
|
case 'createDimension':
|
||||||
|
case 'createMetric':
|
||||||
|
handleContextMenuClickCreate(item._cfg, key);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNodeTypeClick = (nodeData: any) => {
|
||||||
|
setCurrentNodeData(nodeData);
|
||||||
|
setInfoDrawerVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const graphConfigMap = {
|
||||||
|
dendrogram: {
|
||||||
|
defaultEdge: {
|
||||||
|
type: 'cubic-horizontal',
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
type: 'dendrogram',
|
||||||
|
direction: 'LR',
|
||||||
|
animate: false,
|
||||||
|
nodeSep: 200,
|
||||||
|
rankSep: 300,
|
||||||
|
radial: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mindmap: {
|
||||||
|
defaultEdge: {
|
||||||
|
type: 'polyline',
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
type: 'mindmap',
|
||||||
|
animate: false,
|
||||||
|
direction: 'H',
|
||||||
|
getHeight: () => {
|
||||||
|
return 50;
|
||||||
|
},
|
||||||
|
getWidth: () => {
|
||||||
|
return 50;
|
||||||
|
},
|
||||||
|
getVGap: () => {
|
||||||
|
return 10;
|
||||||
|
},
|
||||||
|
getHGap: () => {
|
||||||
|
return 50;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleToolBarClick(code: string) {
|
||||||
|
if (code === 'visibleMode') {
|
||||||
|
visibleModeOpenRef.current = !visibleModeOpenRef.current;
|
||||||
|
setVisibleModeOpen(visibleModeOpenRef.current);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visibleModeOpenRef.current = false;
|
||||||
|
setVisibleModeOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const lessNodeZoomRealAndMoveCenter = () => {
|
||||||
|
const bbox = graphRef.current.get('group').getBBox();
|
||||||
|
|
||||||
|
// 计算图形的中心点
|
||||||
|
const centerX = (bbox.minX + bbox.maxX) / 2;
|
||||||
|
const centerY = (bbox.minY + bbox.maxY) / 2;
|
||||||
|
|
||||||
|
// 获取画布的中心点
|
||||||
|
const canvasWidth = graphRef.current.get('width');
|
||||||
|
const canvasHeight = graphRef.current.get('height');
|
||||||
|
const canvasCenterX = canvasWidth / 2;
|
||||||
|
const canvasCenterY = canvasHeight / 2;
|
||||||
|
|
||||||
|
// 计算画布需要移动的距离
|
||||||
|
const dx = canvasCenterX - centerX;
|
||||||
|
const dy = canvasCenterY - centerY;
|
||||||
|
|
||||||
|
// 将画布移动到中心点
|
||||||
|
graphRef.current.translate(dx, dy);
|
||||||
|
|
||||||
|
// 将缩放比例设置为 1,以画布中心点为中心进行缩放
|
||||||
|
graphRef.current.zoomTo(1, { x: canvasCenterX, y: canvasCenterY });
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!Array.isArray(graphData?.children)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const container = document.getElementById('semanticGraph');
|
||||||
|
const width = container!.scrollWidth;
|
||||||
|
const height = container!.scrollHeight || 500;
|
||||||
|
|
||||||
|
const graph = graphRef.current;
|
||||||
|
|
||||||
|
if (!graph && graphData) {
|
||||||
|
const graphNodeList = flatGraphDataNode(graphData.children);
|
||||||
|
const graphConfigKey = graphNodeList.length > 20 ? 'dendrogram' : 'mindmap';
|
||||||
|
|
||||||
|
// getLegendDataFilterFunctions();
|
||||||
|
const toolbar = initToolBar({ onSearch: handleSeachNode, onClick: handleToolBarClick });
|
||||||
|
const tooltip = initTooltips();
|
||||||
|
const contextMenu = initContextMenu({
|
||||||
|
onMenuClick: handleContextMenuClick,
|
||||||
|
});
|
||||||
|
// const legend = initLegend({
|
||||||
|
// nodeData: legendDataRef.current,
|
||||||
|
// filterFunctions: { ...legendDataFilterFunctions.current },
|
||||||
|
// });
|
||||||
|
|
||||||
|
graphRef.current = new G6.Graph({
|
||||||
|
container: 'semanticGraph',
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
// translate the graph to align the canvas's center, support by v3.5.1
|
||||||
|
fitCenter: true,
|
||||||
|
modes: {
|
||||||
|
default: [
|
||||||
|
// {
|
||||||
|
// type: 'collapse-expand',
|
||||||
|
// onChange: function onChange(item, collapsed) {
|
||||||
|
// const data = item!.get('model');
|
||||||
|
// data.collapsed = collapsed;
|
||||||
|
// return true;
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
'drag-node',
|
||||||
|
'drag-canvas',
|
||||||
|
// 'activate-relations',
|
||||||
|
{
|
||||||
|
type: 'zoom-canvas',
|
||||||
|
sensitivity: 0.3, // 设置缩放灵敏度,值越小,缩放越不敏感,默认值为 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'activate-relations',
|
||||||
|
trigger: 'mouseenter', // 触发方式,可以是 'mouseenter' 或 'click'
|
||||||
|
resetSelected: true, // 点击空白处时,是否取消高亮
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
defaultNode: {
|
||||||
|
type: 'card-node',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// graphRef.current = new G6.TreeGraph({
|
||||||
|
// container: 'semanticGraph',
|
||||||
|
// width,
|
||||||
|
// height,
|
||||||
|
// modes: {
|
||||||
|
// default: [
|
||||||
|
// // {
|
||||||
|
// // type: 'collapse-expand',
|
||||||
|
// // onChange: function onChange(item, collapsed) {
|
||||||
|
// // const data = item!.get('model');
|
||||||
|
// // data.collapsed = collapsed;
|
||||||
|
// // return true;
|
||||||
|
// // },
|
||||||
|
// // },
|
||||||
|
// 'drag-node',
|
||||||
|
// 'drag-canvas',
|
||||||
|
// // 'activate-relations',
|
||||||
|
// {
|
||||||
|
// type: 'zoom-canvas',
|
||||||
|
// sensitivity: 0.3, // 设置缩放灵敏度,值越小,缩放越不敏感,默认值为 1
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// type: 'activate-relations',
|
||||||
|
// trigger: 'mouseenter', // 触发方式,可以是 'mouseenter' 或 'click'
|
||||||
|
// resetSelected: true, // 点击空白处时,是否取消高亮
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// defaultNode: {
|
||||||
|
// type: 'card-node',
|
||||||
|
// },
|
||||||
|
// defaultEdge: {
|
||||||
|
// type: 'cubic-horizontal',
|
||||||
|
// style: {
|
||||||
|
// stroke: '#CED4D9',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// // defaultNode: {
|
||||||
|
// // size: 26,
|
||||||
|
// // anchorPoints: [
|
||||||
|
// // [0, 0.5],
|
||||||
|
// // [1, 0.5],
|
||||||
|
// // ],
|
||||||
|
// // labelCfg: {
|
||||||
|
// // position: 'right',
|
||||||
|
// // offset: 5,
|
||||||
|
// // style: {
|
||||||
|
// // stroke: '#fff',
|
||||||
|
// // lineWidth: 4,
|
||||||
|
// // },
|
||||||
|
// // },
|
||||||
|
// // },
|
||||||
|
// // defaultEdge: {
|
||||||
|
// // type: graphConfigMap[graphConfigKey].defaultEdge.type,
|
||||||
|
// // },
|
||||||
|
// layout: {
|
||||||
|
// ...graphConfigMap[graphConfigKey].layout,
|
||||||
|
// },
|
||||||
|
// plugins: [tooltip, toolbar, contextMenu],
|
||||||
|
// // plugins: [legend, tooltip, toolbar, contextMenu],
|
||||||
|
// });
|
||||||
|
|
||||||
|
cardNodeRegister(graphRef.current);
|
||||||
|
|
||||||
|
graphRef.current.set('initGraphData', graphData);
|
||||||
|
graphRef.current.set('initDataSource', dataSourceRef.current);
|
||||||
|
|
||||||
|
// const legendCanvas = legend._cfgs.legendCanvas;
|
||||||
|
|
||||||
|
// legend模式事件方法bindEvents会有点击图例空白清空选中的逻辑,在注册click事件前,先将click事件队列清空;
|
||||||
|
// legend._cfgs.legendCanvas._events.click = [];
|
||||||
|
// legendCanvas.on('click', () => {
|
||||||
|
// // @ts-ignore findLegendItemsByState为Legend的 private方法,忽略ts校验
|
||||||
|
// const activedNodeList = legend.findLegendItemsByState('active');
|
||||||
|
// // 获取当前所有激活节点后进行数据遍历筛选;
|
||||||
|
// const activedNodeIds = activedNodeList.map((item: IGroup) => {
|
||||||
|
// return item.cfg.id;
|
||||||
|
// });
|
||||||
|
// const graphDataClone = cloneDeep(graphData);
|
||||||
|
// const filterGraphDataChildren = Array.isArray(graphDataClone?.children)
|
||||||
|
// ? graphDataClone.children.reduce((children: TreeGraphData[], item: TreeGraphData) => {
|
||||||
|
// if (activedNodeIds.includes(item.id)) {
|
||||||
|
// children.push(item);
|
||||||
|
// }
|
||||||
|
// return children;
|
||||||
|
// }, [])
|
||||||
|
// : [];
|
||||||
|
// graphDataClone.children = filterGraphDataChildren;
|
||||||
|
// refreshGraphData(graphDataClone);
|
||||||
|
// });
|
||||||
|
|
||||||
|
graphRef.current.node(function (node: NodeConfig) {
|
||||||
|
return getNodeConfigByType(node, {
|
||||||
|
label: node.name,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
name: 'cardNodeApp',
|
||||||
|
ip: '127.0.0.1',
|
||||||
|
nodeError: true,
|
||||||
|
dataType: 'root',
|
||||||
|
keyInfo: 'this is a card node info',
|
||||||
|
x: 100,
|
||||||
|
y: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cardNodeApp',
|
||||||
|
ip: '127.0.0.1',
|
||||||
|
nodeError: false,
|
||||||
|
dataType: 'subRoot',
|
||||||
|
keyInfo: 'this is sub root',
|
||||||
|
x: 100,
|
||||||
|
y: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cardNodeApp',
|
||||||
|
ip: '127.0.0.1',
|
||||||
|
nodeError: false,
|
||||||
|
dataType: 'subRoot',
|
||||||
|
keyInfo: 'this is sub root',
|
||||||
|
x: 100,
|
||||||
|
y: 250,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'sub',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
edges: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
graphRef.current.data(data);
|
||||||
|
// graphRef.current.data(graphData);
|
||||||
|
graphRef.current.render();
|
||||||
|
|
||||||
|
const nodeCount = graphRef.current.getNodes().length;
|
||||||
|
if (nodeCount < 10) {
|
||||||
|
lessNodeZoomRealAndMoveCenter();
|
||||||
|
} else {
|
||||||
|
graphRef.current.fitView([80, 80]);
|
||||||
|
}
|
||||||
|
|
||||||
|
graphRef.current.on('node:click', (evt: any) => {
|
||||||
|
const item = evt.item; // 被操作的节点 item
|
||||||
|
const itemData = item?._cfg?.model;
|
||||||
|
if (itemData) {
|
||||||
|
const { nodeType } = itemData;
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
SemanticNodeType.DIMENSION,
|
||||||
|
SemanticNodeType.METRIC,
|
||||||
|
SemanticNodeType.DATASOURCE,
|
||||||
|
].includes(nodeType)
|
||||||
|
) {
|
||||||
|
handleNodeTypeClick(itemData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
graphRef.current.on('canvas:click', () => {
|
||||||
|
setInfoDrawerVisible(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
const rootNode = graphRef.current.findById('root');
|
||||||
|
graphRef.current.hideItem(rootNode);
|
||||||
|
if (typeof window !== 'undefined')
|
||||||
|
window.onresize = () => {
|
||||||
|
if (!graphRef.current || graphRef.current.get('destroyed')) return;
|
||||||
|
if (!container || !container.scrollWidth || !container.scrollHeight) return;
|
||||||
|
graphRef.current.changeSize(container.scrollWidth, container.scrollHeight);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [graphData]);
|
||||||
|
|
||||||
|
const updateGraphData = async (params?: { graphShowType?: SemanticNodeType }) => {
|
||||||
|
const graphRootData = await queryDataSourceList({
|
||||||
|
modelId,
|
||||||
|
graphShowType: params?.graphShowType,
|
||||||
|
});
|
||||||
|
if (graphRootData) {
|
||||||
|
refreshGraphData(graphRootData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshGraphData = (graphRootData: TreeGraphData) => {
|
||||||
|
graphRef.current.changeData(graphRootData);
|
||||||
|
const rootNode = graphRef.current.findById('root');
|
||||||
|
graphRef.current.hideItem(rootNode);
|
||||||
|
graphRef.current.fitView();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<GraphLegend
|
||||||
|
legendOptions={legendDataRef.current}
|
||||||
|
defaultCheckAll={true}
|
||||||
|
onChange={(nodeIds: string[]) => {
|
||||||
|
graphLegendDataSourceIds.current = nodeIds;
|
||||||
|
const rootGraphData = changeGraphData(dataSourceRef.current);
|
||||||
|
refreshGraphData(rootGraphData);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{visibleModeOpen && (
|
||||||
|
<GraphLegendVisibleModeItem
|
||||||
|
value={graphShowTypeState}
|
||||||
|
onChange={(showType) => {
|
||||||
|
graphShowTypeRef.current = showType;
|
||||||
|
setGraphShowTypeState(showType);
|
||||||
|
const rootGraphData = changeGraphData(dataSourceRef.current);
|
||||||
|
refreshGraphData(rootGraphData);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<GraphToolBar
|
||||||
|
onClick={({ eventName }: { eventName: string }) => {
|
||||||
|
setNodeDataSource(undefined);
|
||||||
|
if (eventName === 'createDatabase') {
|
||||||
|
setCreateDataSourceModalOpen(true);
|
||||||
|
}
|
||||||
|
if (eventName === 'createDimension') {
|
||||||
|
setCreateDimensionModalVisible(true);
|
||||||
|
setDimensionItem(undefined);
|
||||||
|
}
|
||||||
|
if (eventName === 'createMetric') {
|
||||||
|
setCreateMetricModalVisible(true);
|
||||||
|
setMetricItem(undefined);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
key={`${modelId}`}
|
||||||
|
id="semanticGraph"
|
||||||
|
style={{ width: '100%', height: 'calc(100vh - 175px)', position: 'relative' }}
|
||||||
|
/>
|
||||||
|
<NodeInfoDrawer
|
||||||
|
nodeData={currentNodeData}
|
||||||
|
placement="right"
|
||||||
|
onClose={() => {
|
||||||
|
setInfoDrawerVisible(false);
|
||||||
|
}}
|
||||||
|
open={infoDrawerVisible}
|
||||||
|
mask={false}
|
||||||
|
getContainer={false}
|
||||||
|
onEditBtnClick={(nodeData: any) => {
|
||||||
|
handleContextMenuClickEdit({ model: nodeData });
|
||||||
|
setInfoDrawerVisible(false);
|
||||||
|
}}
|
||||||
|
onNodeChange={({ eventName }: { eventName: string }) => {
|
||||||
|
updateGraphData();
|
||||||
|
setInfoDrawerVisible(false);
|
||||||
|
if (eventName === SemanticNodeType.METRIC) {
|
||||||
|
dispatch({
|
||||||
|
type: 'domainManger/queryMetricList',
|
||||||
|
payload: {
|
||||||
|
modelId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (eventName === SemanticNodeType.DIMENSION) {
|
||||||
|
dispatch({
|
||||||
|
type: 'domainManger/queryDimensionList',
|
||||||
|
payload: {
|
||||||
|
modelId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{createDimensionModalVisible && (
|
||||||
|
<DimensionInfoModal
|
||||||
|
modelId={modelId}
|
||||||
|
bindModalVisible={createDimensionModalVisible}
|
||||||
|
dimensionItem={dimensionItem}
|
||||||
|
dataSourceList={nodeDataSource ? [nodeDataSource] : dataSourceInfoList}
|
||||||
|
onSubmit={() => {
|
||||||
|
setCreateDimensionModalVisible(false);
|
||||||
|
updateGraphData();
|
||||||
|
dispatch({
|
||||||
|
type: 'domainManger/queryDimensionList',
|
||||||
|
payload: {
|
||||||
|
modelId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
setCreateDimensionModalVisible(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{createMetricModalVisible && (
|
||||||
|
<MetricInfoCreateForm
|
||||||
|
domainId={selectDomainId}
|
||||||
|
modelId={modelId}
|
||||||
|
key={metricItem?.id}
|
||||||
|
datasourceId={nodeDataSource?.id}
|
||||||
|
createModalVisible={createMetricModalVisible}
|
||||||
|
metricItem={metricItem}
|
||||||
|
onSubmit={() => {
|
||||||
|
setCreateMetricModalVisible(false);
|
||||||
|
updateGraphData();
|
||||||
|
dispatch({
|
||||||
|
type: 'domainManger/queryMetricList',
|
||||||
|
payload: {
|
||||||
|
modelId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
setCreateMetricModalVisible(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{
|
||||||
|
<ClassDataSourceTypeModal
|
||||||
|
open={createDataSourceModalOpen}
|
||||||
|
onCancel={() => {
|
||||||
|
setNodeDataSource(undefined);
|
||||||
|
setCreateDataSourceModalOpen(false);
|
||||||
|
}}
|
||||||
|
dataSourceItem={nodeDataSource}
|
||||||
|
onSubmit={() => {
|
||||||
|
updateGraphData();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
<DeleteConfirmModal
|
||||||
|
open={confirmModalOpenState}
|
||||||
|
onOkClick={() => {
|
||||||
|
setConfirmModalOpenState(false);
|
||||||
|
updateGraphData();
|
||||||
|
graphShowTypeState === SemanticNodeType.DIMENSION
|
||||||
|
? dispatch({
|
||||||
|
type: 'domainManger/queryDimensionList',
|
||||||
|
payload: {
|
||||||
|
modelId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: dispatch({
|
||||||
|
type: 'domainManger/queryMetricList',
|
||||||
|
payload: {
|
||||||
|
modelId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onCancelClick={() => {
|
||||||
|
setConfirmModalOpenState(false);
|
||||||
|
}}
|
||||||
|
nodeData={currentNodeData}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default connect(({ domainManger }: { domainManger: StateType }) => ({
|
||||||
|
domainManger,
|
||||||
|
}))(DomainManger);
|
||||||
@@ -0,0 +1,490 @@
|
|||||||
|
import G6 from '@antv/g6';
|
||||||
|
|
||||||
|
let graph;
|
||||||
|
|
||||||
|
const ERROR_COLOR = '#F5222D';
|
||||||
|
const getNodeConfig = (node) => {
|
||||||
|
if (node.nodeError) {
|
||||||
|
return {
|
||||||
|
basicColor: ERROR_COLOR,
|
||||||
|
fontColor: '#FFF',
|
||||||
|
borderColor: ERROR_COLOR,
|
||||||
|
bgColor: '#E66A6C',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let config = {
|
||||||
|
basicColor: '#5B8FF9',
|
||||||
|
fontColor: '#5B8FF9',
|
||||||
|
borderColor: '#5B8FF9',
|
||||||
|
bgColor: '#C6E5FF',
|
||||||
|
};
|
||||||
|
switch (node.type) {
|
||||||
|
case 'root': {
|
||||||
|
config = {
|
||||||
|
basicColor: '#E3E6E8',
|
||||||
|
fontColor: 'rgba(0,0,0,0.85)',
|
||||||
|
borderColor: '#E3E6E8',
|
||||||
|
bgColor: '#5b8ff9',
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
const COLLAPSE_ICON = function COLLAPSE_ICON(x, y, r) {
|
||||||
|
return [
|
||||||
|
['M', x - r, y],
|
||||||
|
['a', r, r, 0, 1, 0, r * 2, 0],
|
||||||
|
['a', r, r, 0, 1, 0, -r * 2, 0],
|
||||||
|
['M', x - r + 4, y],
|
||||||
|
['L', x - r + 2 * r - 4, y],
|
||||||
|
];
|
||||||
|
};
|
||||||
|
const EXPAND_ICON = function EXPAND_ICON(x, y, r) {
|
||||||
|
return [
|
||||||
|
['M', x - r, y],
|
||||||
|
['a', r, r, 0, 1, 0, r * 2, 0],
|
||||||
|
['a', r, r, 0, 1, 0, -r * 2, 0],
|
||||||
|
['M', x - r + 4, y],
|
||||||
|
['L', x - r + 2 * r - 4, y],
|
||||||
|
['M', x - r + r, y - r + 4],
|
||||||
|
['L', x, y + r - 4],
|
||||||
|
];
|
||||||
|
};
|
||||||
|
const nodeBasicMethod = {
|
||||||
|
createNodeBox: (group, config, w, h, isRoot) => {
|
||||||
|
/* 最外面的大矩形 */
|
||||||
|
const container = group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: w,
|
||||||
|
height: h,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'big-rect-shape',
|
||||||
|
});
|
||||||
|
if (!isRoot) {
|
||||||
|
/* 左边的小圆点 */
|
||||||
|
group.addShape('circle', {
|
||||||
|
attrs: {
|
||||||
|
x: 3,
|
||||||
|
y: h / 2,
|
||||||
|
r: 6,
|
||||||
|
fill: config.basicColor,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'left-dot-shape',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/* 矩形 */
|
||||||
|
group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: 3,
|
||||||
|
y: 0,
|
||||||
|
width: w - 19,
|
||||||
|
height: h,
|
||||||
|
fill: config.bgColor,
|
||||||
|
stroke: config.borderColor,
|
||||||
|
radius: 2,
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'rect-shape',
|
||||||
|
});
|
||||||
|
|
||||||
|
/* 左边的粗线 */
|
||||||
|
group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: 3,
|
||||||
|
y: 0,
|
||||||
|
width: 3,
|
||||||
|
height: h,
|
||||||
|
fill: config.basicColor,
|
||||||
|
radius: 1.5,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'left-border-shape',
|
||||||
|
});
|
||||||
|
return container;
|
||||||
|
},
|
||||||
|
/* 生成树上的 marker */
|
||||||
|
createNodeMarker: (group, collapsed, x, y) => {
|
||||||
|
group.addShape('circle', {
|
||||||
|
attrs: {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
r: 13,
|
||||||
|
fill: 'rgba(47, 84, 235, 0.05)',
|
||||||
|
opacity: 0,
|
||||||
|
zIndex: -2,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'collapse-icon-bg',
|
||||||
|
});
|
||||||
|
group.addShape('marker', {
|
||||||
|
attrs: {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
r: 7,
|
||||||
|
symbol: collapsed ? EXPAND_ICON : COLLAPSE_ICON,
|
||||||
|
stroke: 'rgba(0,0,0,0.25)',
|
||||||
|
fill: 'rgba(0,0,0,0)',
|
||||||
|
lineWidth: 1,
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'collapse-icon',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
afterDraw: (cfg, group) => {
|
||||||
|
/* 操作 marker 的背景色显示隐藏 */
|
||||||
|
const icon = group.find((element) => element.get('name') === 'collapse-icon');
|
||||||
|
if (icon) {
|
||||||
|
const bg = group.find((element) => element.get('name') === 'collapse-icon-bg');
|
||||||
|
icon.on('mouseenter', () => {
|
||||||
|
bg.attr('opacity', 1);
|
||||||
|
graph.get('canvas').draw();
|
||||||
|
});
|
||||||
|
icon.on('mouseleave', () => {
|
||||||
|
bg.attr('opacity', 0);
|
||||||
|
graph.get('canvas').draw();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/* ip 显示 */
|
||||||
|
const ipBox = group.find((element) => element.get('name') === 'ip-box');
|
||||||
|
if (ipBox) {
|
||||||
|
/* ip 复制的几个元素 */
|
||||||
|
const ipLine = group.find((element) => element.get('name') === 'ip-cp-line');
|
||||||
|
const ipBG = group.find((element) => element.get('name') === 'ip-cp-bg');
|
||||||
|
const ipIcon = group.find((element) => element.get('name') === 'ip-cp-icon');
|
||||||
|
const ipCPBox = group.find((element) => element.get('name') === 'ip-cp-box');
|
||||||
|
|
||||||
|
const onMouseEnter = () => {
|
||||||
|
ipLine.attr('opacity', 1);
|
||||||
|
ipBG.attr('opacity', 1);
|
||||||
|
ipIcon.attr('opacity', 1);
|
||||||
|
graph.get('canvas').draw();
|
||||||
|
};
|
||||||
|
const onMouseLeave = () => {
|
||||||
|
ipLine.attr('opacity', 0);
|
||||||
|
ipBG.attr('opacity', 0);
|
||||||
|
ipIcon.attr('opacity', 0);
|
||||||
|
graph.get('canvas').draw();
|
||||||
|
};
|
||||||
|
ipBox.on('mouseenter', () => {
|
||||||
|
onMouseEnter();
|
||||||
|
});
|
||||||
|
ipBox.on('mouseleave', () => {
|
||||||
|
onMouseLeave();
|
||||||
|
});
|
||||||
|
ipCPBox.on('mouseenter', () => {
|
||||||
|
onMouseEnter();
|
||||||
|
});
|
||||||
|
ipCPBox.on('mouseleave', () => {
|
||||||
|
onMouseLeave();
|
||||||
|
});
|
||||||
|
ipCPBox.on('click', () => {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setState: (name, value, item) => {
|
||||||
|
const hasOpacityClass = [
|
||||||
|
'ip-cp-line',
|
||||||
|
'ip-cp-bg',
|
||||||
|
'ip-cp-icon',
|
||||||
|
'ip-cp-box',
|
||||||
|
'ip-box',
|
||||||
|
'collapse-icon-bg',
|
||||||
|
];
|
||||||
|
const group = item.getContainer();
|
||||||
|
const childrens = group.get('children');
|
||||||
|
graph.setAutoPaint(false);
|
||||||
|
if (name === 'emptiness') {
|
||||||
|
if (value) {
|
||||||
|
childrens.forEach((shape) => {
|
||||||
|
if (hasOpacityClass.indexOf(shape.get('name')) > -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
shape.attr('opacity', 0.4);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
childrens.forEach((shape) => {
|
||||||
|
if (hasOpacityClass.indexOf(shape.get('name')) > -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
shape.attr('opacity', 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
graph.setAutoPaint(true);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
G6.registerNode('card-node', {
|
||||||
|
draw: (cfg, group) => {
|
||||||
|
const config = getNodeConfig(cfg);
|
||||||
|
const isRoot = cfg.dataType === 'root';
|
||||||
|
const nodeError = cfg.nodeError;
|
||||||
|
/* the biggest rect */
|
||||||
|
const container = nodeBasicMethod.createNodeBox(group, config, 243, 64, isRoot);
|
||||||
|
|
||||||
|
if (cfg.dataType !== 'root') {
|
||||||
|
/* the type text */
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
text: cfg.dataType,
|
||||||
|
x: 3,
|
||||||
|
y: -10,
|
||||||
|
fontSize: 12,
|
||||||
|
textAlign: 'left',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
fill: 'rgba(0,0,0,0.65)',
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'type-text-shape',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cfg.ip) {
|
||||||
|
/* ip start */
|
||||||
|
/* ipBox */
|
||||||
|
const ipRect = group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
fill: nodeError ? null : '#FFF',
|
||||||
|
stroke: nodeError ? 'rgba(255,255,255,0.65)' : null,
|
||||||
|
radius: 2,
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'ip-container-shape',
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ip */
|
||||||
|
const ipText = group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
text: cfg.ip,
|
||||||
|
x: 0,
|
||||||
|
y: 19,
|
||||||
|
fontSize: 12,
|
||||||
|
textAlign: 'left',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
fill: nodeError ? 'rgba(255,255,255,0.85)' : 'rgba(0,0,0,0.65)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'ip-text-shape',
|
||||||
|
});
|
||||||
|
|
||||||
|
const ipBBox = ipText.getBBox();
|
||||||
|
/* the distance from the IP to the right is 12px */
|
||||||
|
ipText.attr({
|
||||||
|
x: 224 - 12 - ipBBox.width,
|
||||||
|
});
|
||||||
|
/* ipBox */
|
||||||
|
ipRect.attr({
|
||||||
|
x: 224 - 12 - ipBBox.width - 4,
|
||||||
|
y: ipBBox.minY - 5,
|
||||||
|
width: ipBBox.width + 8,
|
||||||
|
height: ipBBox.height + 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
/* a transparent shape on the IP for click listener */
|
||||||
|
group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
stroke: '',
|
||||||
|
cursor: 'pointer',
|
||||||
|
x: 224 - 12 - ipBBox.width - 4,
|
||||||
|
y: ipBBox.minY - 5,
|
||||||
|
width: ipBBox.width + 8,
|
||||||
|
height: ipBBox.height + 10,
|
||||||
|
fill: '#fff',
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'ip-box',
|
||||||
|
});
|
||||||
|
|
||||||
|
/* copyIpLine */
|
||||||
|
group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: 194,
|
||||||
|
y: 7,
|
||||||
|
width: 1,
|
||||||
|
height: 24,
|
||||||
|
fill: '#E3E6E8',
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'ip-cp-line',
|
||||||
|
});
|
||||||
|
/* copyIpBG */
|
||||||
|
group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: 195,
|
||||||
|
y: 8,
|
||||||
|
width: 22,
|
||||||
|
height: 22,
|
||||||
|
fill: '#FFF',
|
||||||
|
cursor: 'pointer',
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'ip-cp-bg',
|
||||||
|
});
|
||||||
|
/* copyIpIcon */
|
||||||
|
group.addShape('image', {
|
||||||
|
attrs: {
|
||||||
|
x: 200,
|
||||||
|
y: 13,
|
||||||
|
height: 12,
|
||||||
|
width: 10,
|
||||||
|
img: 'https://os.alipayobjects.com/rmsportal/DFhnQEhHyPjSGYW.png',
|
||||||
|
cursor: 'pointer',
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'ip-cp-icon',
|
||||||
|
});
|
||||||
|
/* a transparent rect on the icon area for click listener */
|
||||||
|
group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: 195,
|
||||||
|
y: 8,
|
||||||
|
width: 22,
|
||||||
|
height: 22,
|
||||||
|
fill: '#FFF',
|
||||||
|
cursor: 'pointer',
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'ip-cp-box',
|
||||||
|
tooltip: 'Copy the IP',
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ip end */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* name */
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
text: cfg.name,
|
||||||
|
x: 19,
|
||||||
|
y: 19,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 700,
|
||||||
|
textAlign: 'left',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
fill: config.fontColor,
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'name-text-shape',
|
||||||
|
});
|
||||||
|
|
||||||
|
/* the description text */
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
text: cfg.keyInfo,
|
||||||
|
x: 19,
|
||||||
|
y: 45,
|
||||||
|
fontSize: 14,
|
||||||
|
textAlign: 'left',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
fill: config.fontColor,
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'bottom-text-shape',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (nodeError) {
|
||||||
|
group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: 191,
|
||||||
|
y: 62,
|
||||||
|
text: '⚠️',
|
||||||
|
fill: '#000',
|
||||||
|
fontSize: 18,
|
||||||
|
},
|
||||||
|
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||||
|
name: 'error-text-shape',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasChildren = cfg.children && cfg.children.length > 0;
|
||||||
|
if (hasChildren) {
|
||||||
|
nodeBasicMethod.createNodeMarker(group, cfg.collapsed, 236, 32);
|
||||||
|
}
|
||||||
|
return container;
|
||||||
|
},
|
||||||
|
afterDraw: nodeBasicMethod.afterDraw,
|
||||||
|
setState: nodeBasicMethod.setState,
|
||||||
|
});
|
||||||
|
|
||||||
|
const container = document.getElementById('container');
|
||||||
|
const width = container.scrollWidth;
|
||||||
|
const height = container.scrollHeight || 500;
|
||||||
|
graph = new G6.Graph({
|
||||||
|
container: 'container',
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
// translate the graph to align the canvas's center, support by v3.5.1
|
||||||
|
fitCenter: true,
|
||||||
|
modes: {
|
||||||
|
default: ['drag-node'],
|
||||||
|
},
|
||||||
|
defaultNode: {
|
||||||
|
type: 'card-node',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
name: 'cardNodeApp',
|
||||||
|
ip: '127.0.0.1',
|
||||||
|
nodeError: true,
|
||||||
|
dataType: 'root',
|
||||||
|
keyInfo: 'this is a card node info',
|
||||||
|
x: 100,
|
||||||
|
y: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cardNodeApp',
|
||||||
|
ip: '127.0.0.1',
|
||||||
|
nodeError: false,
|
||||||
|
dataType: 'subRoot',
|
||||||
|
keyInfo: 'this is sub root',
|
||||||
|
x: 100,
|
||||||
|
y: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cardNodeApp',
|
||||||
|
ip: '127.0.0.1',
|
||||||
|
nodeError: false,
|
||||||
|
dataType: 'subRoot',
|
||||||
|
keyInfo: 'this is sub root',
|
||||||
|
x: 100,
|
||||||
|
y: 250,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'sub',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
edges: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
graph.data(data);
|
||||||
|
graph.render();
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined')
|
||||||
|
window.onresize = () => {
|
||||||
|
if (!graph || graph.get('destroyed')) return;
|
||||||
|
if (!container || !container.scrollWidth || !container.scrollHeight) return;
|
||||||
|
graph.changeSize(container.scrollWidth, container.scrollHeight);
|
||||||
|
};
|
||||||
@@ -72,8 +72,8 @@ export const formatterRelationData = (params: {
|
|||||||
const { type, dataSourceList, limit, showDataSourceId } = params;
|
const { type, dataSourceList, limit, showDataSourceId } = params;
|
||||||
const relationData = dataSourceList.reduce(
|
const relationData = dataSourceList.reduce(
|
||||||
(relationList: TreeGraphData[], item: ISemantic.IDomainSchemaRelaItem) => {
|
(relationList: TreeGraphData[], item: ISemantic.IDomainSchemaRelaItem) => {
|
||||||
const { datasource, dimensions, metrics } = item;
|
const { model, dimensions, metrics } = item;
|
||||||
const { id } = datasource;
|
const { id } = model;
|
||||||
const dataSourceNodeId = `${SemanticNodeType.DATASOURCE}-${id}`;
|
const dataSourceNodeId = `${SemanticNodeType.DATASOURCE}-${id}`;
|
||||||
let childrenList = [];
|
let childrenList = [];
|
||||||
if (type === SemanticNodeType.METRIC) {
|
if (type === SemanticNodeType.METRIC) {
|
||||||
@@ -89,7 +89,7 @@ export const formatterRelationData = (params: {
|
|||||||
}
|
}
|
||||||
if (!showDataSourceId || showDataSourceId.includes(dataSourceNodeId)) {
|
if (!showDataSourceId || showDataSourceId.includes(dataSourceNodeId)) {
|
||||||
relationList.push({
|
relationList.push({
|
||||||
...datasource,
|
...model,
|
||||||
legendType: dataSourceNodeId,
|
legendType: dataSourceNodeId,
|
||||||
id: dataSourceNodeId,
|
id: dataSourceNodeId,
|
||||||
uid: id,
|
uid: id,
|
||||||
@@ -130,6 +130,24 @@ export const getNodeConfigByType = (nodeData: any, defaultConfig = {}) => {
|
|||||||
return {
|
return {
|
||||||
...defaultConfig,
|
...defaultConfig,
|
||||||
labelCfg: { position: 'bottom', ...labelCfg },
|
labelCfg: { position: 'bottom', ...labelCfg },
|
||||||
|
// type: 'rect',
|
||||||
|
size: [80, 40],
|
||||||
|
// linkPoints: {
|
||||||
|
// // top: true,
|
||||||
|
// right: true,
|
||||||
|
// // bottom: true,
|
||||||
|
// left: true,
|
||||||
|
// /* linkPoints' size, 8 by default */
|
||||||
|
// // size: 5,
|
||||||
|
// /* linkPoints' style */
|
||||||
|
// // fill: '#ccc',
|
||||||
|
// // stroke: '#333',
|
||||||
|
// // lineWidth: 2,
|
||||||
|
// },
|
||||||
|
// style: {
|
||||||
|
// fill: '#eee',
|
||||||
|
// stroke: '#ccc',
|
||||||
|
// },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case SemanticNodeType.DIMENSION:
|
case SemanticNodeType.DIMENSION:
|
||||||
|
|||||||
@@ -68,10 +68,6 @@ const BindMeasuresTable: React.FC<CreateFormProps> = ({
|
|||||||
dataIndex: 'agg',
|
dataIndex: 'agg',
|
||||||
title: '算子类型',
|
title: '算子类型',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
dataIndex: 'datasourceName',
|
|
||||||
title: '所属数据源',
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
const renderFooter = () => {
|
const renderFooter = () => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -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);
|
|
||||||
@@ -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 { ConsoleSqlOutlined, CoffeeOutlined } from '@ant-design/icons';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import type { Dispatch } from 'umi';
|
import type { Dispatch } from 'umi';
|
||||||
@@ -47,7 +47,7 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({
|
|||||||
setCreateDataSourceModalOpen(open);
|
setCreateDataSourceModalOpen(open);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (dataSourceItem?.datasourceDetail?.queryType === 'table_query') {
|
if (dataSourceItem?.modelDetail?.queryType === 'table_query') {
|
||||||
setDataSourceModalVisible(true);
|
setDataSourceModalVisible(true);
|
||||||
} else {
|
} else {
|
||||||
setCreateModalVisible(true);
|
setCreateModalVisible(true);
|
||||||
@@ -80,16 +80,16 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const queryTableColumnListByScript = async (dataSource: IDataSource.IDataSourceItem) => {
|
const queryTableColumnListByScript = async (dataSource: IDataSource.IDataSourceItem) => {
|
||||||
if (!dataSource) {
|
if (!dataSource?.modelDetail?.sqlQuery) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { code, data, msg } = await excuteSql({
|
const { code, data } = await excuteSql({
|
||||||
sql: dataSource.datasourceDetail?.sqlQuery,
|
sql: dataSource.modelDetail?.sqlQuery,
|
||||||
id: dataSource.databaseId,
|
id: dataSource.databaseId,
|
||||||
});
|
});
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
fetchTaskResult(data);
|
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>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
@@ -143,7 +143,7 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Meta title="SQL脚本" description="自定义SQL脚本创建数据源" />
|
<Meta title="SQL脚本" description="自定义SQL脚本创建模型" />
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -207,7 +207,6 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({
|
|||||||
setSql(sql);
|
setSql(sql);
|
||||||
setScriptColumns(columns);
|
setScriptColumns(columns);
|
||||||
setCurrentDatabaseId(databaseId);
|
setCurrentDatabaseId(databaseId);
|
||||||
onSubmit?.();
|
|
||||||
setDataSourceEditOpen(false);
|
setDataSourceEditOpen(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ import type { StateType } from '../model';
|
|||||||
import { StatusEnum } from '../enum';
|
import { StatusEnum } from '../enum';
|
||||||
import { SENSITIVE_LEVEL_ENUM } from '../constant';
|
import { SENSITIVE_LEVEL_ENUM } from '../constant';
|
||||||
import {
|
import {
|
||||||
getDatasourceList,
|
getModelList,
|
||||||
getDimensionList,
|
getDimensionList,
|
||||||
deleteDimension,
|
deleteDimension,
|
||||||
batchUpdateDimensionStatus,
|
batchUpdateDimensionStatus,
|
||||||
} from '../service';
|
} from '../service';
|
||||||
import DimensionInfoModal from './DimensionInfoModal';
|
import DimensionInfoModal from './DimensionInfoModal';
|
||||||
import DimensionValueSettingModal from './DimensionValueSettingModal';
|
import DimensionValueSettingModal from './DimensionValueSettingModal';
|
||||||
import { updateDimension } from '../service';
|
// import { updateDimension } from '../service';
|
||||||
import { ISemantic, IDataSource } from '../data';
|
import { ISemantic, IDataSource } from '../data';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import BatchCtrlDropDownButton from '@/components/BatchCtrlDropDownButton';
|
import BatchCtrlDropDownButton from '@/components/BatchCtrlDropDownButton';
|
||||||
@@ -27,7 +27,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||||
const { selectModelId: modelId } = domainManger;
|
const { selectModelId: modelId, selectDomainId: domainId } = domainManger;
|
||||||
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
|
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
|
||||||
const [dimensionItem, setDimensionItem] = useState<ISemantic.IDimensionItem>();
|
const [dimensionItem, setDimensionItem] = useState<ISemantic.IDimensionItem>();
|
||||||
const [dataSourceList, setDataSourceList] = useState<IDataSource.IDataSourceItem[]>([]);
|
const [dataSourceList, setDataSourceList] = useState<IDataSource.IDataSourceItem[]>([]);
|
||||||
@@ -80,7 +80,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const queryDataSourceList = async () => {
|
const queryDataSourceList = async () => {
|
||||||
const { code, data, msg } = await getDatasourceList({ modelId });
|
const { code, data, msg } = await getModelList(domainId);
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
setDataSourceList(data);
|
setDataSourceList(data);
|
||||||
} else {
|
} else {
|
||||||
@@ -92,20 +92,20 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
queryDataSourceList();
|
queryDataSourceList();
|
||||||
}, [modelId]);
|
}, [modelId]);
|
||||||
|
|
||||||
const updateDimensionStatus = async (dimensionData: ISemantic.IDimensionItem) => {
|
// const updateDimensionStatus = async (dimensionData: ISemantic.IDimensionItem) => {
|
||||||
const { code, msg } = await updateDimension(dimensionData);
|
// const { code, msg } = await updateDimension(dimensionData);
|
||||||
if (code === 200) {
|
// if (code === 200) {
|
||||||
actionRef?.current?.reload();
|
// actionRef?.current?.reload();
|
||||||
dispatch({
|
// dispatch({
|
||||||
type: 'domainManger/queryDimensionList',
|
// type: 'domainManger/queryDimensionList',
|
||||||
payload: {
|
// payload: {
|
||||||
modelId,
|
// modelId,
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
message.error(msg);
|
// message.error(msg);
|
||||||
};
|
// };
|
||||||
|
|
||||||
const queryBatchUpdateStatus = async (ids: React.Key[], status: StatusEnum) => {
|
const queryBatchUpdateStatus = async (ids: React.Key[], status: StatusEnum) => {
|
||||||
if (Array.isArray(ids) && ids.length === 0) {
|
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',
|
dataIndex: 'createdBy',
|
||||||
title: '创建人',
|
title: '创建人',
|
||||||
@@ -299,6 +294,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
title="确认删除?"
|
title="确认删除?"
|
||||||
okText="是"
|
okText="是"
|
||||||
cancelText="否"
|
cancelText="否"
|
||||||
|
placement="left"
|
||||||
onConfirm={async () => {
|
onConfirm={async () => {
|
||||||
const { code, msg } = await deleteDimension(record.id);
|
const { code, msg } = await deleteDimension(record.id);
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
@@ -331,29 +327,29 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const dropdownButtonItems = [
|
// const dropdownButtonItems = [
|
||||||
{
|
// {
|
||||||
key: 'batchStart',
|
// key: 'batchStart',
|
||||||
label: '批量启用',
|
// label: '批量启用',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
key: 'batchStop',
|
// key: 'batchStop',
|
||||||
label: '批量停用',
|
// label: '批量停用',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
key: 'batchDelete',
|
// key: 'batchDelete',
|
||||||
label: (
|
// label: (
|
||||||
<Popconfirm
|
// <Popconfirm
|
||||||
title="确定批量删除吗?"
|
// title="确定批量删除吗?"
|
||||||
onConfirm={() => {
|
// onConfirm={() => {
|
||||||
queryBatchUpdateStatus(selectedRowKeys, StatusEnum.DELETED);
|
// queryBatchUpdateStatus(selectedRowKeys, StatusEnum.DELETED);
|
||||||
}}
|
// }}
|
||||||
>
|
// >
|
||||||
<a>批量删除</a>
|
// <a>批量删除</a>
|
||||||
</Popconfirm>
|
// </Popconfirm>
|
||||||
),
|
// ),
|
||||||
},
|
// },
|
||||||
];
|
// ];
|
||||||
|
|
||||||
const onMenuClick = (key: string) => {
|
const onMenuClick = (key: string) => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
|
|||||||
@@ -155,20 +155,6 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
|
|||||||
>
|
>
|
||||||
<Input placeholder="名称不可重复" disabled={isEdit} />
|
<Input placeholder="名称不可重复" disabled={isEdit} />
|
||||||
</FormItem>
|
</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="别名">
|
<FormItem label="别名">
|
||||||
<Row>
|
<Row>
|
||||||
<Col flex="1 1 200px">
|
<Col flex="1 1 200px">
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import type { StateType } from '../model';
|
|||||||
import TransTypeTag from './TransTypeTag';
|
import TransTypeTag from './TransTypeTag';
|
||||||
import TableTitleTooltips from '../components/TableTitleTooltips';
|
import TableTitleTooltips from '../components/TableTitleTooltips';
|
||||||
import { ISemantic } from '../data';
|
import { ISemantic } from '../data';
|
||||||
import { getDimensionList } from '../service';
|
import { getDimensionList, getDimensionInModelCluster } from '../service';
|
||||||
import { SemanticNodeType, TransType } from '../enum';
|
import { SemanticNodeType, TransType } from '../enum';
|
||||||
|
|
||||||
interface RecordType {
|
interface RecordType {
|
||||||
@@ -45,9 +45,9 @@ const DimensionMetricRelationTableTransfer: React.FC<Props> = ({
|
|||||||
}, [metricItem, relationsInitialValue]);
|
}, [metricItem, relationsInitialValue]);
|
||||||
|
|
||||||
const queryDimensionList = async () => {
|
const queryDimensionList = async () => {
|
||||||
const { code, data, msg } = await getDimensionList({ modelId: metricItem?.modelId || modelId });
|
const { code, data, msg } = await getDimensionInModelCluster(metricItem?.modelId || modelId);
|
||||||
if (code === 200 && Array.isArray(data?.list)) {
|
if (code === 200 && Array.isArray(data)) {
|
||||||
setDimensionList(data.list);
|
setDimensionList(data);
|
||||||
} else {
|
} else {
|
||||||
message.error(msg);
|
message.error(msg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ import { Tabs, Breadcrumb, Space } from 'antd';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect, history } from 'umi';
|
import { connect, history } from 'umi';
|
||||||
|
|
||||||
import ClassDataSourceTable from './ClassDataSourceTable';
|
|
||||||
import ClassDimensionTable from './ClassDimensionTable';
|
import ClassDimensionTable from './ClassDimensionTable';
|
||||||
import ClassMetricTable from './ClassMetricTable';
|
import ClassMetricTable from './ClassMetricTable';
|
||||||
import PermissionSection from './Permission/PermissionSection';
|
import PermissionSection from './Permission/PermissionSection';
|
||||||
// import EntitySettingSection from './Entity/EntitySettingSection';
|
|
||||||
import ChatSettingSection from '../ChatSetting/ChatSettingSection';
|
import ChatSettingSection from '../ChatSetting/ChatSettingSection';
|
||||||
import OverView from './OverView';
|
import OverView from './OverView';
|
||||||
import styles from './style.less';
|
import styles from './style.less';
|
||||||
@@ -38,7 +36,7 @@ const DomainManagerTab: React.FC<Props> = ({
|
|||||||
onBackDomainBtnClick,
|
onBackDomainBtnClick,
|
||||||
onMenuChange,
|
onMenuChange,
|
||||||
}) => {
|
}) => {
|
||||||
const defaultTabKey = 'xflow';
|
const defaultTabKey = 'dimenstion';
|
||||||
const { selectDomainId, domainList, selectModelId, selectModelName, selectDomainName } =
|
const { selectDomainId, domainList, selectModelId, selectModelName, selectDomainName } =
|
||||||
domainManger;
|
domainManger;
|
||||||
|
|
||||||
@@ -55,6 +53,15 @@ const DomainManagerTab: React.FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '画布',
|
||||||
|
key: 'xflow',
|
||||||
|
children: (
|
||||||
|
<div style={{ width: '100%' }}>
|
||||||
|
<SemanticGraphCanvas />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '权限管理',
|
label: '权限管理',
|
||||||
key: 'permissonSetting',
|
key: 'permissonSetting',
|
||||||
@@ -74,21 +81,6 @@ const DomainManagerTab: React.FC<Props> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const isModelItem = [
|
const isModelItem = [
|
||||||
{
|
|
||||||
label: '画布',
|
|
||||||
key: 'xflow',
|
|
||||||
children: (
|
|
||||||
<div style={{ width: '100%' }}>
|
|
||||||
<SemanticGraphCanvas />
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
label: '数据源',
|
|
||||||
key: 'dataSource',
|
|
||||||
children: <ClassDataSourceTable />,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: '维度',
|
label: '维度',
|
||||||
key: 'dimenstion',
|
key: 'dimenstion',
|
||||||
@@ -99,11 +91,6 @@ const DomainManagerTab: React.FC<Props> = ({
|
|||||||
key: 'metric',
|
key: 'metric',
|
||||||
children: <ClassMetricTable />,
|
children: <ClassMetricTable />,
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// label: '实体',
|
|
||||||
// key: 'entity',
|
|
||||||
// children: <EntitySettingSection />,
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
label: '权限管理',
|
label: '权限管理',
|
||||||
key: 'permissonSetting',
|
key: 'permissonSetting',
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { SENSITIVE_LEVEL_OPTIONS } from '../constant';
|
|||||||
import { formLayout } from '@/components/FormHelper/utils';
|
import { formLayout } from '@/components/FormHelper/utils';
|
||||||
import FormItemTitle from '@/components/FormHelper/FormItemTitle';
|
import FormItemTitle from '@/components/FormHelper/FormItemTitle';
|
||||||
import styles from './style.less';
|
import styles from './style.less';
|
||||||
import { getMeasureListByModelId } from '../service';
|
import { getMeasureListByModelId, getModelDetail } from '../service';
|
||||||
import DimensionAndMetricRelationModal from './DimensionAndMetricRelationModal';
|
import DimensionAndMetricRelationModal from './DimensionAndMetricRelationModal';
|
||||||
import TableTitleTooltips from '../components/TableTitleTooltips';
|
import TableTitleTooltips from '../components/TableTitleTooltips';
|
||||||
import { creatExprMetric, updateExprMetric, mockMetricAlias, getMetricTags } from '../service';
|
import { creatExprMetric, updateExprMetric, mockMetricAlias, getMetricTags } from '../service';
|
||||||
@@ -88,8 +88,10 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
|||||||
const backward = () => setCurrentStep(currentStep - 1);
|
const backward = () => setCurrentStep(currentStep - 1);
|
||||||
|
|
||||||
const queryClassMeasureList = async () => {
|
const queryClassMeasureList = async () => {
|
||||||
const { code, data } = await getMeasureListByModelId(modelId);
|
// const { code, data } = await getMeasureListByModelId(modelId);
|
||||||
|
const { code, data } = await getModelDetail({ modelId });
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
|
if (Array.isArray(data?.modelDetail?.measures)) {
|
||||||
setClassMeasureList(data);
|
setClassMeasureList(data);
|
||||||
if (datasourceId) {
|
if (datasourceId) {
|
||||||
const hasMeasures = data.some(
|
const hasMeasures = data.some(
|
||||||
@@ -99,6 +101,7 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
setClassMeasureList([]);
|
setClassMeasureList([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ const ModelCreateFormModal: React.FC<ModelCreateFormModalProps> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
width={640}
|
width={640}
|
||||||
styles={{ padding: '32px 40px 48px' }}
|
// styles={{ padding: '32px 40px 48px' }}
|
||||||
destroyOnClose
|
destroyOnClose
|
||||||
title={'模型信息'}
|
title={'模型信息'}
|
||||||
open={true}
|
open={true}
|
||||||
@@ -125,7 +125,18 @@ const ModelCreateFormModal: React.FC<ModelCreateFormModalProps> = (props) => {
|
|||||||
>
|
>
|
||||||
<Input placeholder="请输入模型英文名称" />
|
<Input placeholder="请输入模型英文名称" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem name="alias" label="别名">
|
<FormItem
|
||||||
|
name="alias"
|
||||||
|
label="别名"
|
||||||
|
getValueFromEvent={(value) => {
|
||||||
|
return value.join(',');
|
||||||
|
}}
|
||||||
|
getValueProps={(value) => {
|
||||||
|
return {
|
||||||
|
value: value.split(','),
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Select
|
<Select
|
||||||
mode="tags"
|
mode="tags"
|
||||||
placeholder="输入别名后回车确认,多别名输入、复制粘贴支持英文逗号自动分隔"
|
placeholder="输入别名后回车确认,多别名输入、复制粘贴支持英文逗号自动分隔"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type { Dispatch } from 'umi';
|
|||||||
import { connect } from 'umi';
|
import { connect } from 'umi';
|
||||||
import type { StateType } from '../model';
|
import type { StateType } from '../model';
|
||||||
import { deleteModel, updateModel } from '../service';
|
import { deleteModel, updateModel } from '../service';
|
||||||
|
import ClassDataSourceTypeModal from './ClassDataSourceTypeModal';
|
||||||
|
|
||||||
import ModelCreateFormModal from './ModelCreateFormModal';
|
import ModelCreateFormModal from './ModelCreateFormModal';
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ const ModelTable: React.FC<Props> = ({
|
|||||||
const [modelCreateFormModalVisible, setModelCreateFormModalVisible] = useState<boolean>(false);
|
const [modelCreateFormModalVisible, setModelCreateFormModalVisible] = useState<boolean>(false);
|
||||||
const [modelItem, setModelItem] = useState<ISemantic.IModelItem>();
|
const [modelItem, setModelItem] = useState<ISemantic.IModelItem>();
|
||||||
const [saveLoading, setSaveLoading] = useState<boolean>(false);
|
const [saveLoading, setSaveLoading] = useState<boolean>(false);
|
||||||
|
const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false);
|
||||||
const actionRef = useRef<ActionType>();
|
const actionRef = useRef<ActionType>();
|
||||||
|
|
||||||
const updateModelStatus = async (modelData: ISemantic.IModelItem) => {
|
const updateModelStatus = async (modelData: ISemantic.IModelItem) => {
|
||||||
@@ -141,7 +143,8 @@ const ModelTable: React.FC<Props> = ({
|
|||||||
key="metricEditBtn"
|
key="metricEditBtn"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setModelItem(record);
|
setModelItem(record);
|
||||||
setModelCreateFormModalVisible(true);
|
// setModelCreateFormModalVisible(true);
|
||||||
|
setCreateDataSourceModalOpen(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
编辑
|
编辑
|
||||||
@@ -217,7 +220,9 @@ const ModelTable: React.FC<Props> = ({
|
|||||||
type="primary"
|
type="primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setModelItem(undefined);
|
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 && (
|
{modelCreateFormModalVisible && (
|
||||||
<ModelCreateFormModal
|
<ModelCreateFormModal
|
||||||
domainId={selectDomainId}
|
domainId={selectDomainId}
|
||||||
|
|||||||
@@ -235,6 +235,7 @@
|
|||||||
|
|
||||||
|
|
||||||
.classTable {
|
.classTable {
|
||||||
|
// padding: 0 20px;
|
||||||
:global {
|
:global {
|
||||||
.ant-pro-table-search-query-filter {
|
.ant-pro-table-search-query-filter {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
@@ -253,6 +254,9 @@
|
|||||||
.ant-table-selection-column {
|
.ant-table-selection-column {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
.ant-pro-card-body {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export declare namespace IDataSource {
|
|||||||
sensitiveLevel: SensitiveLevel;
|
sensitiveLevel: SensitiveLevel;
|
||||||
domainId: number;
|
domainId: number;
|
||||||
databaseId: number;
|
databaseId: number;
|
||||||
datasourceDetail: IDataSourceDetail;
|
modelDetail: IDataSourceDetail;
|
||||||
}
|
}
|
||||||
type IDataSourceList = IDataSourceItem[];
|
type IDataSourceList = IDataSourceItem[];
|
||||||
}
|
}
|
||||||
@@ -237,7 +237,7 @@ export declare namespace ISemantic {
|
|||||||
domainId: number;
|
domainId: number;
|
||||||
dimensions: IDimensionList;
|
dimensions: IDimensionList;
|
||||||
metrics: IMetricList;
|
metrics: IMetricList;
|
||||||
datasource: IDataSourceItem;
|
model: IDataSourceItem;
|
||||||
}
|
}
|
||||||
type IDomainSchemaRelaList = IDomainSchemaRelaItem[];
|
type IDomainSchemaRelaList = IDomainSchemaRelaItem[];
|
||||||
|
|
||||||
|
|||||||
@@ -12,10 +12,6 @@ export function getDomainList(): Promise<any> {
|
|||||||
return request.get(`${process.env.API_BASE_URL}domain/getDomainList`);
|
return request.get(`${process.env.API_BASE_URL}domain/getDomainList`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDatasourceList(data: any): Promise<any> {
|
|
||||||
return request.get(`${process.env.API_BASE_URL}datasource/getDatasourceList/${data.modelId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getDomainDetail(data: any): Promise<any> {
|
export function getDomainDetail(data: any): Promise<any> {
|
||||||
return request.get(`${process.env.API_BASE_URL}domain/getDomain/${data.modelId}`);
|
return request.get(`${process.env.API_BASE_URL}domain/getDomain/${data.modelId}`);
|
||||||
}
|
}
|
||||||
@@ -61,6 +57,10 @@ export function getDimensionList(data: any): Promise<any> {
|
|||||||
return request.post(`${process.env.API_BASE_URL}dimension/queryDimension`, queryParams);
|
return request.post(`${process.env.API_BASE_URL}dimension/queryDimension`, queryParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getDimensionInModelCluster(modelId: number): Promise<any> {
|
||||||
|
return request.get(`${process.env.API_BASE_URL}dimension/getDimensionInModelCluster/${modelId}`);
|
||||||
|
}
|
||||||
|
|
||||||
export function createDimension(data: any): Promise<any> {
|
export function createDimension(data: any): Promise<any> {
|
||||||
return request.post(`${process.env.API_BASE_URL}dimension/createDimension`, {
|
return request.post(`${process.env.API_BASE_URL}dimension/createDimension`, {
|
||||||
data,
|
data,
|
||||||
@@ -252,6 +252,29 @@ export function createOrUpdateDatasourceRela(data: any): Promise<any> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createOrUpdateModelRela(data: any): Promise<any> {
|
||||||
|
return request(`${process.env.API_BASE_URL}modelRela`, {
|
||||||
|
method: data?.id ? 'PUT' : 'POST',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteModelRela(id: any): Promise<any> {
|
||||||
|
if (!id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return request(`${process.env.API_BASE_URL}modelRela/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getModelRelaList(domainId: number): Promise<any> {
|
||||||
|
return request(`${process.env.API_BASE_URL}modelRela/list`, {
|
||||||
|
method: 'GET',
|
||||||
|
params: { domainId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function createOrUpdateViewInfo(data: any): Promise<any> {
|
export function createOrUpdateViewInfo(data: any): Promise<any> {
|
||||||
return request(`${process.env.API_BASE_URL}viewInfo/createOrUpdateViewInfo`, {
|
return request(`${process.env.API_BASE_URL}viewInfo/createOrUpdateViewInfo`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -265,6 +288,12 @@ export function getViewInfoList(domainId: number): Promise<any> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deleteViewInfo(recordId: any): Promise<any> {
|
||||||
|
return request(`${process.env.API_BASE_URL}viewInfo/deleteViewInfo/${recordId}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function deleteDatasourceRela(domainId: any): Promise<any> {
|
export function deleteDatasourceRela(domainId: any): Promise<any> {
|
||||||
return request(`${process.env.API_BASE_URL}viewInfo/deleteDatasourceRela/${domainId}`, {
|
return request(`${process.env.API_BASE_URL}viewInfo/deleteDatasourceRela/${domainId}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
@@ -418,7 +447,7 @@ export function queryDimValue(data: any): Promise<any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function queryStruct({
|
export async function queryStruct({
|
||||||
modelId,
|
modelIds,
|
||||||
bizName,
|
bizName,
|
||||||
dateField = 'sys_imp_date',
|
dateField = 'sys_imp_date',
|
||||||
startDate,
|
startDate,
|
||||||
@@ -427,7 +456,7 @@ export async function queryStruct({
|
|||||||
groups = [],
|
groups = [],
|
||||||
dimensionFilters = [],
|
dimensionFilters = [],
|
||||||
}: {
|
}: {
|
||||||
modelId: number;
|
modelIds: number[];
|
||||||
bizName: string;
|
bizName: string;
|
||||||
dateField: string;
|
dateField: string;
|
||||||
startDate: string;
|
startDate: string;
|
||||||
@@ -442,7 +471,7 @@ export async function queryStruct({
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
...(download ? { responseType: 'blob', getResponse: true } : {}),
|
...(download ? { responseType: 'blob', getResponse: true } : {}),
|
||||||
data: {
|
data: {
|
||||||
modelId,
|
modelIds,
|
||||||
groups: [dateField, ...groups],
|
groups: [dateField, ...groups],
|
||||||
dimensionFilters,
|
dimensionFilters,
|
||||||
aggregators: [
|
aggregators: [
|
||||||
|
|||||||
Reference in New Issue
Block a user