mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-11 03:58:14 +00:00
semantic-fe visual modeling pr (#21)
* [improvement][semantic-fe] Added an editing component to set filtering rules for Q&A. Now, the SQL editor will be accompanied by a list for display and control, to resolve ambiguity when using comma-separated values. [improvement][semantic-fe] Improved validation logic and prompt copywriting for data source/dimension/metric editing and creation. [improvement][semantic-fe] Improved user experience for visual modeling. Now, when using the legend to control the display/hide of data sources and their associated metric dimensions, the canvas will be re-layout based on the activated data source in the legend. * [improvement][semantic-fe] Submitted a new version of the visual modeling tool. [improvement][semantic-fe] Fixed an issue with the initialization of YoY and MoM metrics in Q&A settings. [improvement][semantic-fe] Added a version field to the database settings. [improvement][semantic-fe] 1. Added the ability to set YoY and MoM metrics in Q&A settings.2. Moved dimension value editing from the dimension editing window to the dimension list. --------- Co-authored-by: tristanliu <tristanliu@tencent.com>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
@import '~antd/es/style/themes/default.less';
|
||||
|
||||
.standardFormRow {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-bottom: 12px;
|
||||
// padding-bottom: 16px;
|
||||
// border-bottom: 1px dashed @border-color-split;
|
||||
:global {
|
||||
.ant-form-item,
|
||||
.ant-legacy-form-item {
|
||||
margin-right: 24px;
|
||||
}
|
||||
.ant-form-item-label,
|
||||
.ant-legacy-form-item-label {
|
||||
label {
|
||||
margin-right: 0;
|
||||
color: @text-color;
|
||||
}
|
||||
}
|
||||
.ant-form-item-label,
|
||||
.ant-legacy-form-item-label,
|
||||
.ant-form-item-control,
|
||||
.ant-legacy-form-item-control {
|
||||
padding: 0;
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
.label {
|
||||
flex: 0 0 auto;
|
||||
margin-right: 24px;
|
||||
color: @heading-color;
|
||||
font-size: @font-size-base;
|
||||
text-align: left;
|
||||
& > span {
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
width: 80px;
|
||||
height: 32px;
|
||||
// height: 20px;
|
||||
color: #999;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
font-family: PingFangSC-Medium, PingFang SC;
|
||||
line-height: 32px;
|
||||
// line-height: 20px;
|
||||
// &::after {
|
||||
// content: ':';
|
||||
// }
|
||||
}
|
||||
}
|
||||
.content {
|
||||
flex: 1 1 0;
|
||||
:global {
|
||||
.ant-form-item,
|
||||
.ant-legacy-form-item {
|
||||
&:last-child {
|
||||
display: block;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.standardFormRowLast {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.standardFormRowBlock {
|
||||
:global {
|
||||
.ant-form-item,
|
||||
.ant-legacy-form-item,
|
||||
div.ant-form-item-control-wrapper,
|
||||
div.ant-legacy-form-item-control-wrapper {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.standardFormRowGrid {
|
||||
:global {
|
||||
.ant-form-item,
|
||||
.ant-legacy-form-item,
|
||||
div.ant-form-item-control-wrapper,
|
||||
div.ant-legacy-form-item-control-wrapper {
|
||||
display: block;
|
||||
}
|
||||
.ant-form-item-label,
|
||||
.ant-legacy-form-item-label {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import styles from './index.less';
|
||||
|
||||
type StandardFormRowProps = {
|
||||
title?: string;
|
||||
last?: boolean;
|
||||
block?: boolean;
|
||||
grid?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
titleClassName?: string;
|
||||
};
|
||||
|
||||
const StandardFormRow: React.FC<StandardFormRowProps> = ({
|
||||
title,
|
||||
children,
|
||||
last,
|
||||
block,
|
||||
grid,
|
||||
titleClassName,
|
||||
...rest
|
||||
}) => {
|
||||
const cls = classNames(styles.standardFormRow, {
|
||||
[styles.standardFormRowBlock]: block,
|
||||
[styles.standardFormRowLast]: last,
|
||||
[styles.standardFormRowGrid]: grid,
|
||||
});
|
||||
|
||||
const labelCls = classNames(styles.label, titleClassName);
|
||||
|
||||
return (
|
||||
<div className={cls} {...rest}>
|
||||
{title && (
|
||||
<div className={labelCls}>
|
||||
<span>{title}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.content}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StandardFormRow;
|
||||
@@ -0,0 +1,33 @@
|
||||
@import '~antd/es/style/themes/default.less';
|
||||
|
||||
.tagSelect {
|
||||
position: relative;
|
||||
max-height: 32px;
|
||||
margin-left: -8px;
|
||||
overflow: hidden;
|
||||
line-height: 32px;
|
||||
transition: all 0.3s;
|
||||
user-select: none;
|
||||
:global {
|
||||
.ant-tag {
|
||||
margin-right: 24px;
|
||||
padding: 0 8px;
|
||||
font-size: @font-size-base;
|
||||
}
|
||||
}
|
||||
&.expanded {
|
||||
max-height: 200px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.trigger {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
span.anticon {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
&.hasExpandTag {
|
||||
padding-right: 50px;
|
||||
}
|
||||
}
|
||||
191
webapp/packages/supersonic-fe/src/components/TagSelect/index.tsx
Normal file
191
webapp/packages/supersonic-fe/src/components/TagSelect/index.tsx
Normal file
@@ -0,0 +1,191 @@
|
||||
import { DownOutlined, UpOutlined } from '@ant-design/icons';
|
||||
import { useBoolean, useControllableValue } from 'ahooks';
|
||||
import { Tag } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { FC, useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import styles from './index.less';
|
||||
|
||||
const { CheckableTag } = Tag;
|
||||
|
||||
export interface TagSelectOptionProps {
|
||||
value: string | number | undefined;
|
||||
style?: React.CSSProperties;
|
||||
checked?: boolean;
|
||||
onChange?: (value: string | number, state: boolean) => void;
|
||||
}
|
||||
|
||||
const TagSelectOption: React.FC<TagSelectOptionProps> & {
|
||||
isTagSelectOption: boolean;
|
||||
} = ({ children, checked, onChange, value }) => (
|
||||
<CheckableTag
|
||||
checked={!!checked}
|
||||
key={value}
|
||||
onChange={(state) => onChange && onChange(value, state)}
|
||||
>
|
||||
{children}
|
||||
</CheckableTag>
|
||||
);
|
||||
|
||||
TagSelectOption.isTagSelectOption = true;
|
||||
|
||||
type TagSelectOptionElement = React.ReactElement<TagSelectOptionProps, typeof TagSelectOption>;
|
||||
export interface TagSelectProps {
|
||||
onChange?: (value: (string | number)[]) => void;
|
||||
expandable?: boolean;
|
||||
value?: (string | number)[];
|
||||
defaultValue?: (string | number)[];
|
||||
style?: React.CSSProperties;
|
||||
hideCheckAll?: boolean;
|
||||
actionsText?: {
|
||||
expandText?: React.ReactNode;
|
||||
collapseText?: React.ReactNode;
|
||||
selectAllText?: React.ReactNode;
|
||||
};
|
||||
className?: string;
|
||||
Option?: TagSelectOptionProps;
|
||||
children?: TagSelectOptionElement | TagSelectOptionElement[];
|
||||
single?: boolean;
|
||||
disableUnCheck?: boolean;
|
||||
empty?: boolean;
|
||||
isSelectAll?: boolean;
|
||||
reverseCheckAll?: boolean;
|
||||
}
|
||||
|
||||
const TagSelect: FC<TagSelectProps> & { Option: typeof TagSelectOption } = (props) => {
|
||||
const {
|
||||
children,
|
||||
hideCheckAll = false,
|
||||
className,
|
||||
style,
|
||||
expandable,
|
||||
actionsText = {},
|
||||
single = false,
|
||||
disableUnCheck = false,
|
||||
empty = false,
|
||||
isSelectAll = false,
|
||||
reverseCheckAll = false,
|
||||
} = props;
|
||||
|
||||
const [expand, { toggle }] = useBoolean();
|
||||
|
||||
const [value, setValue] = useControllableValue<(string | number)[] | undefined>(props);
|
||||
|
||||
useEffect(() => {
|
||||
if (empty) {
|
||||
setValue([]);
|
||||
}
|
||||
}, [empty]);
|
||||
|
||||
const isTagSelectOption = (node: TagSelectOptionElement) =>
|
||||
node &&
|
||||
node.type &&
|
||||
(node.type.isTagSelectOption || node.type.displayName === 'TagSelectOption');
|
||||
|
||||
const getAllTags = () => {
|
||||
const childrenArray = React.Children.toArray(children) as TagSelectOptionElement[];
|
||||
const checkedTags = childrenArray
|
||||
.filter((child) => isTagSelectOption(child))
|
||||
.map((child) => child.props.value);
|
||||
return checkedTags || [];
|
||||
};
|
||||
|
||||
const onSelectAll = (checked: boolean) => {
|
||||
let checkedTags: (string | number)[] = [];
|
||||
if (reverseCheckAll) {
|
||||
setValue(undefined);
|
||||
return;
|
||||
}
|
||||
if (checked) {
|
||||
checkedTags = getAllTags();
|
||||
}
|
||||
setValue(checkedTags);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isSelectAll) {
|
||||
onSelectAll(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleTagChange = (tag: string | number, checked: boolean) => {
|
||||
let checkedTags: (string | number)[] = [...(value || [])];
|
||||
if (single && checkedTags.length > 0) {
|
||||
checkedTags = [checkedTags.join('')];
|
||||
}
|
||||
const index = checkedTags.indexOf(tag);
|
||||
if (checked && index === -1) {
|
||||
if (single) {
|
||||
checkedTags = [tag];
|
||||
} else {
|
||||
checkedTags.push(tag);
|
||||
}
|
||||
} else if (!checked && index > -1 && !disableUnCheck) {
|
||||
checkedTags.splice(index, 1);
|
||||
}
|
||||
setValue(checkedTags.length === 0 ? undefined : checkedTags);
|
||||
};
|
||||
|
||||
const checkedAll = getAllTags().length === value?.length;
|
||||
const hasChecked = value === undefined ? false : value?.length > 0;
|
||||
|
||||
const {
|
||||
expandText = '展开',
|
||||
collapseText = '收起',
|
||||
selectAllText = reverseCheckAll ? '不限' : '全部',
|
||||
} = actionsText;
|
||||
|
||||
const cls = classNames(styles.tagSelect, className, {
|
||||
[styles.hasExpandTag]: expandable,
|
||||
[styles.expanded]: expand,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={cls} style={style}>
|
||||
{hideCheckAll ? null : (
|
||||
<CheckableTag
|
||||
checked={reverseCheckAll ? !hasChecked : checkedAll}
|
||||
key="tag-select-__all__"
|
||||
onChange={onSelectAll}
|
||||
>
|
||||
{selectAllText}
|
||||
</CheckableTag>
|
||||
)}
|
||||
{children &&
|
||||
React.Children.map(children, (child: TagSelectOptionElement) => {
|
||||
if (isTagSelectOption(child)) {
|
||||
return React.cloneElement(child, {
|
||||
key: `tag-select-${child.props.value}`,
|
||||
value: child.props.value,
|
||||
checked: value && value.indexOf(child.props.value) > -1,
|
||||
onChange: handleTagChange,
|
||||
});
|
||||
}
|
||||
return child;
|
||||
})}
|
||||
{expandable && (
|
||||
<a
|
||||
className={styles.trigger}
|
||||
onClick={() => {
|
||||
toggle();
|
||||
}}
|
||||
>
|
||||
{expand ? (
|
||||
<>
|
||||
{collapseText} <UpOutlined />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{expandText}
|
||||
<DownOutlined />
|
||||
</>
|
||||
)}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
TagSelect.Option = TagSelectOption;
|
||||
|
||||
export default TagSelect;
|
||||
@@ -228,7 +228,7 @@ ol {
|
||||
li {
|
||||
cursor: pointer;
|
||||
list-style-type:none;
|
||||
// list-style: none;
|
||||
line-height: 25px;
|
||||
margin-left: 0;
|
||||
&:hover {
|
||||
color: #4E86F5;
|
||||
@@ -242,4 +242,15 @@ ol {
|
||||
.ant-tag {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.semantic-graph-toolbar {
|
||||
position: absolute;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.g6-component-tooltip {
|
||||
p {
|
||||
line-height: 25px;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import styles from './components/style.less';
|
||||
import type { StateType } from './model';
|
||||
import { DownOutlined } from '@ant-design/icons';
|
||||
import EntitySection from './components/Entity/EntitySection';
|
||||
import RecommendedQuestionsSection from './components/Entity/RecommendedQuestionsSection';
|
||||
import { ISemantic } from './data';
|
||||
import { getDomainList } from './service';
|
||||
import OverView from './components/OverView';
|
||||
@@ -130,15 +131,20 @@ const ChatSetting: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
|
||||
const isModelItem = [
|
||||
{
|
||||
label: '指标场景',
|
||||
label: '指标模式',
|
||||
key: 'metric',
|
||||
children: <EntitySection chatConfigType={ChatConfigType.AGG} />,
|
||||
},
|
||||
{
|
||||
label: '明细场景',
|
||||
label: '实体模式',
|
||||
key: 'dimenstion',
|
||||
children: <EntitySection chatConfigType={ChatConfigType.DETAIL} />,
|
||||
},
|
||||
{
|
||||
label: '推荐问题',
|
||||
key: 'recommendedQuestions',
|
||||
children: <RecommendedQuestionsSection />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import { Form, Select, Input } from 'antd';
|
||||
import StandardFormRow from '@/components/StandardFormRow';
|
||||
import TagSelect from '@/components/TagSelect';
|
||||
import React, { useEffect } from 'react';
|
||||
import { SENSITIVE_LEVEL_OPTIONS } from '../../constant';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
const { Option } = Select;
|
||||
|
||||
type Props = {
|
||||
filterValues?: any;
|
||||
onFiltersChange: (_: any, values: any) => void;
|
||||
};
|
||||
|
||||
const MetricFilter: React.FC<Props> = ({ filterValues = {}, onFiltersChange }) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({
|
||||
...filterValues,
|
||||
});
|
||||
}, [form, filterValues]);
|
||||
|
||||
const handleValuesChange = (value: any, values: any) => {
|
||||
onFiltersChange(value, values);
|
||||
};
|
||||
|
||||
const onSearch = (value) => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
onFiltersChange(value, form.getFieldsValue());
|
||||
};
|
||||
|
||||
const filterList = [
|
||||
{
|
||||
title: '指标类型',
|
||||
key: 'type',
|
||||
options: [
|
||||
{
|
||||
value: 'ATOMIC',
|
||||
label: '原子指标',
|
||||
},
|
||||
{ value: 'DERIVED', label: '衍生指标' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '敏感度',
|
||||
key: 'sensitiveLevel',
|
||||
options: SENSITIVE_LEVEL_OPTIONS,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Form
|
||||
layout="inline"
|
||||
form={form}
|
||||
colon={false}
|
||||
onValuesChange={(value, values) => {
|
||||
if (value.keywords || value.keywordsType) {
|
||||
return;
|
||||
}
|
||||
handleValuesChange(value, values);
|
||||
}}
|
||||
initialValues={{
|
||||
keywordsType: 'name',
|
||||
}}
|
||||
>
|
||||
<StandardFormRow key="search" block>
|
||||
<Input.Group compact>
|
||||
<FormItem name={'keywordsType'} noStyle>
|
||||
<Select>
|
||||
<Option value="name">中文名</Option>
|
||||
<Option value="bizName">英文名</Option>
|
||||
<Option value="id">ID</Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem name={'keywords'} noStyle>
|
||||
<Input.Search
|
||||
placeholder="请输入需要查询的指标信息"
|
||||
allowClear
|
||||
onSearch={onSearch}
|
||||
style={{ width: 300 }}
|
||||
enterButton
|
||||
/>
|
||||
</FormItem>
|
||||
</Input.Group>
|
||||
</StandardFormRow>
|
||||
{filterList.map((item) => {
|
||||
const { title, key, options } = item;
|
||||
return (
|
||||
<StandardFormRow key={key} title={title} block>
|
||||
<FormItem name={key}>
|
||||
<TagSelect reverseCheckAll single>
|
||||
{options.map((item: any) => (
|
||||
<TagSelect.Option key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</TagSelect.Option>
|
||||
))}
|
||||
</TagSelect>
|
||||
</FormItem>
|
||||
</StandardFormRow>
|
||||
);
|
||||
})}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default MetricFilter;
|
||||
0
webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/data.d.ts
vendored
Normal file
0
webapp/packages/supersonic-fe/src/pages/SemanticModel/Metric/data.d.ts
vendored
Normal file
@@ -0,0 +1,196 @@
|
||||
import type { ActionType, ProColumns } from '@ant-design/pro-table';
|
||||
import ProTable from '@ant-design/pro-table';
|
||||
import { message, Space } from 'antd';
|
||||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import type { Dispatch } from 'umi';
|
||||
import { connect } from 'umi';
|
||||
import type { StateType } from '../model';
|
||||
import { SENSITIVE_LEVEL_ENUM } from '../constant';
|
||||
import { queryMetric } from '../service';
|
||||
import MetricFilter from './components/MetricFilter';
|
||||
|
||||
import moment from 'moment';
|
||||
import styles from './style.less';
|
||||
|
||||
type Props = {
|
||||
dispatch: Dispatch;
|
||||
domainManger: StateType;
|
||||
};
|
||||
|
||||
type QueryMetricListParams = {
|
||||
id?: string;
|
||||
name?: string;
|
||||
bizName?: string;
|
||||
sensitiveLevel?: string;
|
||||
type?: string;
|
||||
};
|
||||
|
||||
const ClassMetricTable: React.FC<Props> = () => {
|
||||
const [pagination, setPagination] = useState({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const [dataSource, setDataSource] = useState<any[]>([]);
|
||||
const actionRef = useRef<ActionType>();
|
||||
|
||||
useEffect(() => {
|
||||
queryMetricList();
|
||||
}, []);
|
||||
|
||||
const queryMetricList = async (params: QueryMetricListParams = {}) => {
|
||||
const { code, data, msg } = await queryMetric({
|
||||
...params,
|
||||
...pagination,
|
||||
});
|
||||
const { list, pageSize, current, total } = data;
|
||||
let resData: any = {};
|
||||
if (code === 200) {
|
||||
setPagination({
|
||||
pageSize: Math.min(pageSize, 100),
|
||||
current,
|
||||
total,
|
||||
});
|
||||
setDataSource(list);
|
||||
resData = {
|
||||
data: list || [],
|
||||
success: true,
|
||||
};
|
||||
} else {
|
||||
message.error(msg);
|
||||
setDataSource([]);
|
||||
resData = {
|
||||
data: [],
|
||||
total: 0,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
return resData;
|
||||
};
|
||||
|
||||
const columns: ProColumns[] = [
|
||||
{
|
||||
dataIndex: 'id',
|
||||
title: 'ID',
|
||||
},
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: '指标名称',
|
||||
},
|
||||
{
|
||||
dataIndex: 'alias',
|
||||
title: '别名',
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
dataIndex: 'bizName',
|
||||
title: '字段名称',
|
||||
},
|
||||
{
|
||||
dataIndex: 'sensitiveLevel',
|
||||
title: '敏感度',
|
||||
valueEnum: SENSITIVE_LEVEL_ENUM,
|
||||
},
|
||||
{
|
||||
dataIndex: 'createdBy',
|
||||
title: '创建人',
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
dataIndex: 'description',
|
||||
title: '描述',
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
dataIndex: 'type',
|
||||
title: '指标类型',
|
||||
// search: false,
|
||||
valueEnum: {
|
||||
ATOMIC: '原子指标',
|
||||
DERIVED: '衍生指标',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
dataIndex: 'updatedAt',
|
||||
title: '更新时间',
|
||||
search: false,
|
||||
render: (value: any) => {
|
||||
return value && value !== '-' ? moment(value).format('YYYY-MM-DD HH:mm:ss') : '-';
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const handleFilterChange = async (filterParams: {
|
||||
keywordsType: string;
|
||||
keywords: string;
|
||||
sensitiveLevel: string;
|
||||
type: string;
|
||||
}) => {
|
||||
const params: QueryMetricListParams = {};
|
||||
const { keywordsType, keywords, sensitiveLevel, type } = filterParams;
|
||||
if (keywordsType && keywords) {
|
||||
params[keywordsType] = keywords;
|
||||
}
|
||||
const sensitiveLevelValue = sensitiveLevel?.[0];
|
||||
const typeValue = type?.[0];
|
||||
if (sensitiveLevelValue) {
|
||||
params.sensitiveLevel = sensitiveLevelValue;
|
||||
}
|
||||
if (type) {
|
||||
params.type = typeValue;
|
||||
}
|
||||
|
||||
await queryMetricList(params);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.metricFilterWrapper}>
|
||||
<MetricFilter
|
||||
onFiltersChange={(_, values) => {
|
||||
handleFilterChange(values);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<ProTable
|
||||
className={`${styles.metricTable}`}
|
||||
actionRef={actionRef}
|
||||
// headerTitle="指标列表"
|
||||
rowKey="id"
|
||||
search={false}
|
||||
dataSource={dataSource}
|
||||
columns={columns}
|
||||
pagination={pagination}
|
||||
tableAlertRender={() => {
|
||||
return false;
|
||||
}}
|
||||
onChange={(data: any) => {
|
||||
const { current, pageSize, total } = data;
|
||||
setPagination({
|
||||
current,
|
||||
pageSize,
|
||||
total,
|
||||
});
|
||||
}}
|
||||
size="small"
|
||||
options={{ reload: false, density: false, fullScreen: false }}
|
||||
// toolBarRender={() => [
|
||||
// <Button
|
||||
// key="create"
|
||||
// type="primary"
|
||||
// onClick={() => {
|
||||
// setMetricItem(undefined);
|
||||
// }}
|
||||
// >
|
||||
// 创建指标
|
||||
// </Button>,
|
||||
// ]}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default connect(({ domainManger }: { domainManger: StateType }) => ({
|
||||
domainManger,
|
||||
}))(ClassMetricTable);
|
||||
@@ -0,0 +1,10 @@
|
||||
.metricFilterWrapper {
|
||||
margin: 20px;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.metricTable {
|
||||
margin: 20px;
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import ClassDimensionTable from './components/ClassDimensionTable';
|
||||
import ClassMetricTable from './components/ClassMetricTable';
|
||||
import PermissionSection from './components/Permission/PermissionSection';
|
||||
import DatabaseSection from './components/Database/DatabaseSection';
|
||||
import EntitySettingSection from './components/Entity/EntitySettingSection';
|
||||
import OverView from './components/OverView';
|
||||
import styles from './components/style.less';
|
||||
import type { StateType } from './model';
|
||||
@@ -149,11 +150,10 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
|
||||
const isModelItem = [
|
||||
{
|
||||
label: '可视化建模',
|
||||
label: '画布',
|
||||
key: 'xflow',
|
||||
children: (
|
||||
<div style={{ width: '100%', height: 'calc(100vh - 200px)' }}>
|
||||
{/* <SemanticFlow /> */}
|
||||
<div style={{ width: '100%', marginTop: -20 }}>
|
||||
<SemanticGraphCanvas />
|
||||
</div>
|
||||
),
|
||||
@@ -178,6 +178,12 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
key: 'metric',
|
||||
children: <ClassMetricTable />,
|
||||
},
|
||||
{
|
||||
label: '实体',
|
||||
key: 'entity',
|
||||
children: <EntitySettingSection />,
|
||||
},
|
||||
|
||||
{
|
||||
label: '权限管理',
|
||||
key: 'permissonSetting',
|
||||
|
||||
@@ -7,7 +7,7 @@ import { connect } from 'umi';
|
||||
import { DATASOURCE_NODE_RENDER_ID } from '../constant';
|
||||
import DataSourceRelationFormDrawer from './DataSourceRelationFormDrawer';
|
||||
import DataSourceCreateForm from '../../Datasource/components/DataSourceCreateForm';
|
||||
import ClassDataSourceTypeModal from '../../components/ClassDataSourceTypeModal';
|
||||
import ClassDataSourceTypeModal from '../../components/ClassDataSourceTypeModal1';
|
||||
import { GraphApi } from '../service';
|
||||
import { SemanticNodeType } from '../../enum';
|
||||
import type { StateType } from '../../model';
|
||||
|
||||
@@ -6,34 +6,36 @@ import { SemanticNodeType } from '../../enum';
|
||||
import { SEMANTIC_NODE_TYPE_CONFIG } from '../../constant';
|
||||
|
||||
type InitContextMenuProps = {
|
||||
graphShowType: string;
|
||||
graphShowType?: string;
|
||||
onMenuClick?: (key: string, item: Item) => void;
|
||||
};
|
||||
|
||||
export const getMenuConfig = (props?: InitContextMenuProps) => {
|
||||
const { graphShowType, onMenuClick } = props || {};
|
||||
const { onMenuClick } = props || {};
|
||||
return {
|
||||
getContent(evt) {
|
||||
const nodeData = evt?.item?._cfg?.model;
|
||||
const { name, nodeType } = nodeData as any;
|
||||
if (nodeData) {
|
||||
const nodeTypeConfig = SEMANTIC_NODE_TYPE_CONFIG[nodeType] || {};
|
||||
let ulNode = `<ul>
|
||||
let ulNode = `
|
||||
<li title='编辑' key='edit' >编辑</li>
|
||||
<li title='删除' key='delete' >删除</li>
|
||||
</ul>`;
|
||||
`;
|
||||
if (nodeType === SemanticNodeType.DATASOURCE) {
|
||||
if (graphShowType) {
|
||||
const typeString = graphShowType === SemanticNodeType.DIMENSION ? '维度' : '指标';
|
||||
ulNode = `<ul>
|
||||
<li title='新增${typeString}' key='create' >新增${typeString}</li>
|
||||
</ul>`;
|
||||
}
|
||||
ulNode = `
|
||||
<li title='新增维度' key='createDimension' >新增维度</li>
|
||||
<li title='新增指标' key='createMetric' >新增指标</li>
|
||||
<li title='编辑' key='editDatasource' >编辑</li>
|
||||
<li title='删除' key='deleteDatasource' >删除</li>
|
||||
`;
|
||||
}
|
||||
const header = `${name}`;
|
||||
return `<div class="g6ContextMenuContainer">
|
||||
<h3>${presetsTagDomString(nodeTypeConfig.label, nodeTypeConfig.color)}${header}</h3>
|
||||
<ul>
|
||||
${ulNode}
|
||||
</ul>
|
||||
</div>`;
|
||||
}
|
||||
return `<div>当前节点信息获取失败</div>`;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { Button, Modal, message } from 'antd';
|
||||
import { Modal, message } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { SemanticNodeType } from '../../enum';
|
||||
import { deleteDimension, deleteMetric } from '../../service';
|
||||
import { deleteDimension, deleteMetric, deleteDatasource } from '../../service';
|
||||
|
||||
type Props = {
|
||||
nodeData: any;
|
||||
nodeType: SemanticNodeType;
|
||||
onOkClick: () => void;
|
||||
onCancelClick: () => void;
|
||||
open: boolean;
|
||||
@@ -13,7 +12,6 @@ type Props = {
|
||||
|
||||
const DeleteConfirmModal: React.FC<Props> = ({
|
||||
nodeData,
|
||||
nodeType,
|
||||
onOkClick,
|
||||
onCancelClick,
|
||||
open = false,
|
||||
@@ -21,11 +19,21 @@ const DeleteConfirmModal: React.FC<Props> = ({
|
||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||
const deleteNode = async () => {
|
||||
setConfirmLoading(true);
|
||||
const { id } = nodeData;
|
||||
let deleteQuery = deleteDimension;
|
||||
const { id, nodeType } = nodeData;
|
||||
let deleteQuery;
|
||||
if (nodeType === SemanticNodeType.DIMENSION) {
|
||||
deleteQuery = deleteDimension;
|
||||
}
|
||||
if (nodeType === SemanticNodeType.METRIC) {
|
||||
deleteQuery = deleteMetric;
|
||||
}
|
||||
if (nodeType === SemanticNodeType.DATASOURCE) {
|
||||
deleteQuery = deleteDatasource;
|
||||
}
|
||||
if (!deleteQuery) {
|
||||
message.error('当前节点类型不是维度,指标,数据源中的一种,请确认节点数据');
|
||||
return;
|
||||
}
|
||||
const { code, msg } = await deleteQuery(id);
|
||||
setConfirmLoading(false);
|
||||
if (code === 200) {
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Button, Space } from 'antd';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import React from 'react';
|
||||
|
||||
import { connect } from 'umi';
|
||||
import type { StateType } from '../../model';
|
||||
|
||||
type Props = {
|
||||
domainManger: StateType;
|
||||
onClick: (params?: { eventName?: string }) => void;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
const GraphToolBar: React.FC<Props> = ({ onClick }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: 0,
|
||||
backgroundColor: '#fff',
|
||||
display: 'flex',
|
||||
justifyContent: 'end',
|
||||
position: 'absolute',
|
||||
top: 20,
|
||||
right: 20,
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
<Space>
|
||||
<Button
|
||||
key="createDatabaseBtn"
|
||||
icon={<PlusOutlined />}
|
||||
size="small"
|
||||
onClick={() => {
|
||||
onClick?.({ eventName: 'createDatabase' });
|
||||
}}
|
||||
>
|
||||
新建数据源
|
||||
</Button>
|
||||
<Button
|
||||
key="createDimensionBtn"
|
||||
icon={<PlusOutlined />}
|
||||
size="small"
|
||||
onClick={() => {
|
||||
onClick?.({ eventName: 'createDimension' });
|
||||
}}
|
||||
>
|
||||
新建维度
|
||||
</Button>
|
||||
<Button
|
||||
key="createMetricBtn"
|
||||
icon={<PlusOutlined />}
|
||||
size="small"
|
||||
onClick={() => {
|
||||
onClick?.({ eventName: 'createMetric' });
|
||||
}}
|
||||
>
|
||||
新建指标
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ domainManger }: { domainManger: StateType }) => ({
|
||||
domainManger,
|
||||
}))(GraphToolBar);
|
||||
@@ -0,0 +1,292 @@
|
||||
import {
|
||||
Button,
|
||||
Drawer,
|
||||
message,
|
||||
Row,
|
||||
Col,
|
||||
Divider,
|
||||
Tag,
|
||||
Space,
|
||||
Typography,
|
||||
Popconfirm,
|
||||
} from 'antd';
|
||||
import React, { useState, useEffect, ReactNode } from 'react';
|
||||
import { SemanticNodeType } from '../../enum';
|
||||
import { deleteDimension, deleteMetric, deleteDatasource } from '../../service';
|
||||
import { connect } from 'umi';
|
||||
import type { StateType } from '../../model';
|
||||
import moment from 'moment';
|
||||
import styles from '../style.less';
|
||||
import TransTypeTag from '../../components/TransTypeTag';
|
||||
import { MetricTypeWording } from '../../enum';
|
||||
import { SENSITIVE_LEVEL_ENUM } from '../../constant';
|
||||
|
||||
const { Paragraph } = Typography;
|
||||
|
||||
type Props = {
|
||||
nodeData: any;
|
||||
domainManger: StateType;
|
||||
onNodeChange: (params?: { eventName?: string }) => void;
|
||||
onEditBtnClick?: (nodeData: any) => void;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
interface DescriptionItemProps {
|
||||
title: string;
|
||||
content: React.ReactNode;
|
||||
}
|
||||
|
||||
type InfoListItemChildrenItem = {
|
||||
label: string;
|
||||
value: string;
|
||||
content?: ReactNode;
|
||||
hideItem?: boolean;
|
||||
};
|
||||
|
||||
type InfoListItem = {
|
||||
title: string;
|
||||
hideItem?: boolean;
|
||||
children: InfoListItemChildrenItem[];
|
||||
};
|
||||
|
||||
const DescriptionItem = ({ title, content }: DescriptionItemProps) => (
|
||||
<div style={{ marginBottom: 7, fontSize: 14 }}>
|
||||
<Space>
|
||||
<span style={{ width: 'max-content' }}>{title}:</span>
|
||||
{content}
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
|
||||
const NodeInfoDrawer: React.FC<Props> = ({
|
||||
nodeData,
|
||||
domainManger,
|
||||
onNodeChange,
|
||||
onEditBtnClick,
|
||||
...restProps
|
||||
}) => {
|
||||
const [infoList, setInfoList] = useState<InfoListItem[]>([]);
|
||||
const { selectDomainName } = domainManger;
|
||||
useEffect(() => {
|
||||
if (!nodeData) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
alias,
|
||||
fullPath,
|
||||
bizName,
|
||||
createdBy,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
description,
|
||||
domainName,
|
||||
sensitiveLevel,
|
||||
type,
|
||||
nodeType,
|
||||
} = nodeData;
|
||||
|
||||
const list = [
|
||||
{
|
||||
title: '基本信息',
|
||||
children: [
|
||||
{
|
||||
label: '字段名称',
|
||||
value: bizName,
|
||||
},
|
||||
{
|
||||
label: '别名',
|
||||
value: alias || '-',
|
||||
},
|
||||
{
|
||||
label: '所属主题域',
|
||||
value: domainName,
|
||||
content: <Tag>{domainName || selectDomainName}</Tag>,
|
||||
},
|
||||
|
||||
{
|
||||
label: '描述',
|
||||
value: description,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '应用信息',
|
||||
children: [
|
||||
{
|
||||
label: '全路径',
|
||||
value: fullPath,
|
||||
content: (
|
||||
<Paragraph style={{ width: 275, margin: 0 }} ellipsis={{ tooltip: fullPath }}>
|
||||
{fullPath}
|
||||
</Paragraph>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: '敏感度',
|
||||
value: SENSITIVE_LEVEL_ENUM[sensitiveLevel],
|
||||
},
|
||||
{
|
||||
label: '指标类型',
|
||||
value: MetricTypeWording[type],
|
||||
hideItem: nodeType !== SemanticNodeType.METRIC,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '创建信息',
|
||||
children: [
|
||||
{
|
||||
label: '创建人',
|
||||
value: createdBy,
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
value: createdAt ? moment(createdAt).format('YYYY-MM-DD HH:mm:ss') : '',
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
value: updatedAt ? moment(updatedAt).format('YYYY-MM-DD HH:mm:ss') : '',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const datasourceList = [
|
||||
{
|
||||
title: '基本信息',
|
||||
children: [
|
||||
{
|
||||
label: '英文名称',
|
||||
value: bizName,
|
||||
},
|
||||
{
|
||||
label: '所属主题域',
|
||||
value: domainName,
|
||||
content: <Tag>{domainName || selectDomainName}</Tag>,
|
||||
},
|
||||
{
|
||||
label: '描述',
|
||||
value: description,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '创建信息',
|
||||
children: [
|
||||
{
|
||||
label: '创建人',
|
||||
value: createdBy,
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
value: createdAt ? moment(createdAt).format('YYYY-MM-DD HH:mm:ss') : '',
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
value: updatedAt ? moment(updatedAt).format('YYYY-MM-DD HH:mm:ss') : '',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
setInfoList(nodeType === SemanticNodeType.DATASOURCE ? datasourceList : list);
|
||||
}, [nodeData]);
|
||||
|
||||
const handleDeleteConfirm = async () => {
|
||||
let deleteQuery;
|
||||
if (nodeData?.nodeType === SemanticNodeType.METRIC) {
|
||||
deleteQuery = deleteMetric;
|
||||
}
|
||||
if (nodeData?.nodeType === SemanticNodeType.DIMENSION) {
|
||||
deleteQuery = deleteDimension;
|
||||
}
|
||||
if (nodeData?.nodeType === SemanticNodeType.DATASOURCE) {
|
||||
deleteQuery = deleteDatasource;
|
||||
}
|
||||
if (!deleteQuery) {
|
||||
return;
|
||||
}
|
||||
const { code, msg } = await deleteQuery(nodeData?.uid);
|
||||
if (code === 200) {
|
||||
onNodeChange?.({ eventName: nodeData?.nodeType });
|
||||
} else {
|
||||
message.error(msg);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Drawer
|
||||
title={
|
||||
<Space>
|
||||
{nodeData?.name}
|
||||
<TransTypeTag type={nodeData?.nodeType} />
|
||||
</Space>
|
||||
}
|
||||
placement="right"
|
||||
mask={false}
|
||||
getContainer={false}
|
||||
footer={
|
||||
<div className="ant-drawer-extra">
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
key="editBtn"
|
||||
onClick={() => {
|
||||
onEditBtnClick?.(nodeData);
|
||||
}}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
|
||||
<Popconfirm
|
||||
title="确认删除?"
|
||||
okText="是"
|
||||
cancelText="否"
|
||||
onConfirm={() => {
|
||||
handleDeleteConfirm();
|
||||
}}
|
||||
>
|
||||
<Button danger key="deleteBtn">
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
</div>
|
||||
}
|
||||
{...restProps}
|
||||
>
|
||||
<div key={nodeData?.id} className={styles.nodeInfoDrawerContent}>
|
||||
{infoList.map((item) => {
|
||||
const { children, title } = item;
|
||||
return (
|
||||
<div key={title} style={{ display: item.hideItem ? 'none' : 'block' }}>
|
||||
<p className={styles.title}>{title}</p>
|
||||
{children.map((childrenItem) => {
|
||||
return (
|
||||
<Row
|
||||
key={`${childrenItem.label}-${childrenItem.value}`}
|
||||
style={{ marginBottom: 10, display: childrenItem.hideItem ? 'none' : 'flex' }}
|
||||
>
|
||||
<Col span={24}>
|
||||
<DescriptionItem
|
||||
title={childrenItem.label}
|
||||
content={childrenItem.content || childrenItem.value}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
})}
|
||||
<Divider />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ domainManger }: { domainManger: StateType }) => ({
|
||||
domainManger,
|
||||
}))(NodeInfoDrawer);
|
||||
@@ -1,6 +1,6 @@
|
||||
import G6, { Graph } from '@antv/g6';
|
||||
import { createDom } from '@antv/dom-util';
|
||||
import { RefreshGraphData } from '../../data';
|
||||
import { ToolBarSearchCallBack } from '../../data';
|
||||
const searchIconSvgPath = `<path d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z" />`;
|
||||
|
||||
// const searchNode = (graph) => {
|
||||
@@ -27,64 +27,25 @@ const searchIconSvgPath = `<path d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479
|
||||
// }
|
||||
// };
|
||||
|
||||
interface Node {
|
||||
label: string;
|
||||
children?: Node[];
|
||||
}
|
||||
|
||||
function findNodesByLabel(query: string, nodes: Node[]): Node[] {
|
||||
const result: Node[] = [];
|
||||
|
||||
for (const node of nodes) {
|
||||
let match = false;
|
||||
let children: Node[] = [];
|
||||
|
||||
// 如果节点的label包含查询字符串,我们将其标记为匹配
|
||||
if (node.label.includes(query)) {
|
||||
match = true;
|
||||
}
|
||||
|
||||
// 我们还需要在子节点中进行搜索
|
||||
if (node.children) {
|
||||
children = findNodesByLabel(query, node.children);
|
||||
if (children.length > 0) {
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果节点匹配或者其子节点匹配,我们将其添加到结果中
|
||||
if (match) {
|
||||
result.push({ ...node, children });
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const searchNode = (graph: Graph, refreshGraphData?: RefreshGraphData) => {
|
||||
const searchNode = (graph: Graph, onSearch?: ToolBarSearchCallBack) => {
|
||||
const toolBarSearchInput = document.getElementById('toolBarSearchInput') as HTMLInputElement;
|
||||
const searchText = toolBarSearchInput.value.trim();
|
||||
const graphData = graph.get('initGraphData');
|
||||
const filterChildrenData = findNodesByLabel(searchText, graphData.children);
|
||||
refreshGraphData?.({
|
||||
...graphData,
|
||||
children: filterChildrenData,
|
||||
});
|
||||
onSearch?.(searchText);
|
||||
};
|
||||
|
||||
const generatorSearchInputDom = (graph: Graph, refreshGraphData: RefreshGraphData) => {
|
||||
const generatorSearchInputDom = (graph: Graph, onSearch: ToolBarSearchCallBack) => {
|
||||
const domString =
|
||||
'<input placeholder="请输入指标/维度名称" class="ant-input" id="toolBarSearchInput" type="text" value="" />';
|
||||
const searchInputDom = createDom(domString);
|
||||
searchInputDom.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
searchNode(graph, refreshGraphData);
|
||||
searchNode(graph, onSearch);
|
||||
}
|
||||
});
|
||||
return searchInputDom;
|
||||
};
|
||||
|
||||
const generatorSearchBtnDom = (graph: Graph) => {
|
||||
const generatorSearchBtnDom = (graph: Graph, onSearch: ToolBarSearchCallBack) => {
|
||||
const domString = `<button
|
||||
id="toolBarSearchBtn"
|
||||
type="button"
|
||||
@@ -106,14 +67,14 @@ const generatorSearchBtnDom = (graph: Graph) => {
|
||||
</button>`;
|
||||
const searchBtnDom = createDom(domString);
|
||||
searchBtnDom.addEventListener('click', () => {
|
||||
searchNode(graph);
|
||||
searchNode(graph, onSearch);
|
||||
});
|
||||
return searchBtnDom;
|
||||
};
|
||||
|
||||
const searchInputDOM = (graph: Graph, refreshGraphData: RefreshGraphData) => {
|
||||
const searchInputDom = generatorSearchInputDom(graph, refreshGraphData);
|
||||
const searchBtnDom = generatorSearchBtnDom(graph);
|
||||
const searchInputDOM = (graph: Graph, onSearch: ToolBarSearchCallBack) => {
|
||||
const searchInputDom = generatorSearchInputDom(graph, onSearch);
|
||||
const searchBtnDom = generatorSearchBtnDom(graph, onSearch);
|
||||
const searchInput = `
|
||||
<div id="searchInputContent" class="g6-component-toolbar-search-input" style="position: absolute;top: 38px;width: 190px;left: 0;">
|
||||
<span class="ant-input-group-wrapper ant-input-search" >
|
||||
@@ -129,7 +90,7 @@ const searchInputDOM = (graph: Graph, refreshGraphData: RefreshGraphData) => {
|
||||
return searchDom;
|
||||
};
|
||||
|
||||
const initToolBar = ({ refreshGraphData }: { refreshGraphData: RefreshGraphData }) => {
|
||||
const initToolBar = ({ onSearch }: { onSearch: ToolBarSearchCallBack }) => {
|
||||
const toolBarInstance = new G6.ToolBar();
|
||||
const config = toolBarInstance._cfgs;
|
||||
const defaultContentDomString = config.getContent();
|
||||
@@ -155,10 +116,10 @@ const initToolBar = ({ refreshGraphData }: { refreshGraphData: RefreshGraphData
|
||||
defaultContentDom.insertAdjacentHTML('afterbegin', searchBtnDom);
|
||||
let searchInputContentVisible = true;
|
||||
const toolbar = new G6.ToolBar({
|
||||
position: { x: 10, y: 10 },
|
||||
position: { x: 20, y: 20 },
|
||||
className: 'semantic-graph-toolbar',
|
||||
getContent: (graph) => {
|
||||
const searchInput = searchInputDOM(graph as Graph, refreshGraphData);
|
||||
const searchInput = searchInputDOM(graph as Graph, onSearch);
|
||||
const content = `<div class="g6-component-toolbar-content">${defaultContentDom.outerHTML}</div>`;
|
||||
const contentDom = createDom(content);
|
||||
contentDom.appendChild(searchInput);
|
||||
|
||||
@@ -20,15 +20,17 @@ 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 { cloneDeep } from 'lodash';
|
||||
|
||||
type Props = {
|
||||
domainId: number;
|
||||
graphShowType: SemanticNodeType;
|
||||
graphShowType?: SemanticNodeType;
|
||||
domainManger: StateType;
|
||||
dispatch: Dispatch;
|
||||
};
|
||||
@@ -36,13 +38,18 @@ type Props = {
|
||||
const DomainManger: React.FC<Props> = ({
|
||||
domainManger,
|
||||
domainId,
|
||||
graphShowType = SemanticNodeType.DIMENSION,
|
||||
// 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);
|
||||
@@ -53,12 +60,15 @@ const DomainManger: React.FC<Props> = ({
|
||||
|
||||
const [nodeDataSource, setNodeDataSource] = useState<any>();
|
||||
|
||||
const [dataSourceInfoList, setDataSourceInfoList] = useState<IDataSource.IDataSourceItem[]>([]);
|
||||
|
||||
const { dimensionList, metricList } = domainManger;
|
||||
|
||||
const dimensionListRef = useRef<ISemantic.IDimensionItem[]>([]);
|
||||
const metricListRef = useRef<ISemantic.IMetricItem[]>([]);
|
||||
|
||||
const [confirmModalOpenState, setConfirmModalOpenState] = useState<boolean>(false);
|
||||
const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false);
|
||||
|
||||
// const toggleNodeVisibility = (graph: Graph, node: Item, visible: boolean) => {
|
||||
// if (visible) {
|
||||
@@ -84,8 +94,34 @@ const DomainManger: React.FC<Props> = ({
|
||||
// }
|
||||
// };
|
||||
|
||||
const changeGraphData = (data: IDataSource.IDataSourceItem[], type: SemanticNodeType) => {
|
||||
const relationData = formatterRelationData(data, type);
|
||||
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,
|
||||
type?: SemanticNodeType,
|
||||
): TreeGraphData => {
|
||||
const relationData = formatterRelationData({ dataSourceList, type, limit: 20 });
|
||||
const legendList = relationData.map((item: any) => {
|
||||
const { id, name } = item;
|
||||
return {
|
||||
@@ -101,7 +137,6 @@ const DomainManger: React.FC<Props> = ({
|
||||
name: domainManger.selectDomainName,
|
||||
children: relationData,
|
||||
};
|
||||
//
|
||||
return graphRootData;
|
||||
};
|
||||
|
||||
@@ -112,8 +147,14 @@ const DomainManger: React.FC<Props> = ({
|
||||
const { code, data } = await getDomainSchemaRela(params.domainId);
|
||||
if (code === 200) {
|
||||
if (data) {
|
||||
const graphRootData = changeGraphData(data, params.graphShowType || graphShowType);
|
||||
setDataSourceInfoList(
|
||||
data.map((item: ISemantic.IDomainSchemaRelaItem) => {
|
||||
return item.datasource;
|
||||
}),
|
||||
);
|
||||
const graphRootData = changeGraphData(data);
|
||||
setGraphData(graphRootData);
|
||||
dataSourceRef.current = data;
|
||||
return graphRootData;
|
||||
}
|
||||
return false;
|
||||
@@ -164,14 +205,17 @@ const DomainManger: React.FC<Props> = ({
|
||||
if (!targetData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const datasource = loopNodeFindDataSource(item);
|
||||
if (datasource) {
|
||||
setNodeDataSource({
|
||||
...datasource,
|
||||
id: datasource.uid,
|
||||
name: datasource.name,
|
||||
});
|
||||
}
|
||||
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) {
|
||||
@@ -180,6 +224,7 @@ const DomainManger: React.FC<Props> = ({
|
||||
} else {
|
||||
message.error('获取维度初始化数据失败');
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (targetData.nodeType === SemanticNodeType.METRIC) {
|
||||
const targetItem = metricListRef.current.find((item) => item.id === targetData.uid);
|
||||
@@ -189,22 +234,23 @@ const DomainManger: React.FC<Props> = ({
|
||||
} else {
|
||||
message.error('获取指标初始化数据失败');
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const handleContextMenuClickCreate = (item: IItemBaseConfig) => {
|
||||
const handleContextMenuClickCreate = (item: IItemBaseConfig, key: string) => {
|
||||
const datasource = item.model;
|
||||
if (!datasource) {
|
||||
return;
|
||||
}
|
||||
setNodeDataSource({
|
||||
...datasource,
|
||||
id: datasource.uid,
|
||||
name: datasource.name,
|
||||
});
|
||||
if (graphShowType === SemanticNodeType.DIMENSION) {
|
||||
if (key === 'createDimension') {
|
||||
setCreateDimensionModalVisible(true);
|
||||
}
|
||||
if (graphShowType === SemanticNodeType.METRIC) {
|
||||
if (key === 'createMetric') {
|
||||
setCreateMetricModalVisible(true);
|
||||
}
|
||||
setDimensionItem(undefined);
|
||||
@@ -216,10 +262,19 @@ const DomainManger: React.FC<Props> = ({
|
||||
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) {
|
||||
setDimensionItem({ ...targetItem });
|
||||
// setDimensionItem({ ...targetItem });
|
||||
setCurrentNodeData(targetItem);
|
||||
setConfirmModalOpenState(true);
|
||||
} else {
|
||||
message.error('获取维度初始化数据失败');
|
||||
@@ -228,7 +283,8 @@ const DomainManger: React.FC<Props> = ({
|
||||
if (targetData.nodeType === SemanticNodeType.METRIC) {
|
||||
const targetItem = metricListRef.current.find((item) => item.id === targetData.uid);
|
||||
if (targetItem) {
|
||||
setMetricItem({ ...targetItem });
|
||||
// setMetricItem({ ...targetItem });
|
||||
setCurrentNodeData(targetItem);
|
||||
setConfirmModalOpenState(true);
|
||||
} else {
|
||||
message.error('获取指标初始化数据失败');
|
||||
@@ -242,19 +298,27 @@ const DomainManger: React.FC<Props> = ({
|
||||
}
|
||||
switch (key) {
|
||||
case 'edit':
|
||||
case 'editDatasource':
|
||||
handleContextMenuClickEdit(item._cfg);
|
||||
break;
|
||||
case 'delete':
|
||||
case 'deleteDatasource':
|
||||
handleContextMenuClickDelete(item._cfg);
|
||||
break;
|
||||
case 'create':
|
||||
handleContextMenuClickCreate(item._cfg);
|
||||
case 'createDimension':
|
||||
case 'createMetric':
|
||||
handleContextMenuClickCreate(item._cfg, key);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const handleNodeTypeClick = (nodeData: any) => {
|
||||
setCurrentNodeData(nodeData);
|
||||
setInfoDrawerVisible(true);
|
||||
};
|
||||
|
||||
const graphConfigMap = {
|
||||
dendrogram: {
|
||||
defaultEdge: {
|
||||
@@ -308,7 +372,7 @@ const DomainManger: React.FC<Props> = ({
|
||||
const graphConfigKey = graphNodeList.length > 20 ? 'dendrogram' : 'mindmap';
|
||||
|
||||
getLegendDataFilterFunctions();
|
||||
const toolbar = initToolBar({ refreshGraphData });
|
||||
const toolbar = initToolBar({ onSearch: handleSeachNode });
|
||||
const tooltip = initTooltips();
|
||||
const contextMenu = initContextMenu({
|
||||
graphShowType,
|
||||
@@ -325,14 +389,14 @@ const DomainManger: React.FC<Props> = ({
|
||||
height,
|
||||
modes: {
|
||||
default: [
|
||||
{
|
||||
type: 'collapse-expand',
|
||||
onChange: function onChange(item, collapsed) {
|
||||
const data = item!.get('model');
|
||||
data.collapsed = collapsed;
|
||||
return true;
|
||||
},
|
||||
},
|
||||
// {
|
||||
// type: 'collapse-expand',
|
||||
// onChange: function onChange(item, collapsed) {
|
||||
// const data = item!.get('model');
|
||||
// data.collapsed = collapsed;
|
||||
// return true;
|
||||
// },
|
||||
// },
|
||||
'drag-node',
|
||||
'drag-canvas',
|
||||
// 'activate-relations',
|
||||
@@ -368,6 +432,8 @@ const DomainManger: React.FC<Props> = ({
|
||||
plugins: [legend, tooltip, toolbar, contextMenu],
|
||||
});
|
||||
graphRef.current.set('initGraphData', graphData);
|
||||
graphRef.current.set('initDataSource', dataSourceRef.current);
|
||||
|
||||
const legendCanvas = legend._cfgs.legendCanvas;
|
||||
|
||||
// legend模式事件方法bindEvents会有点击图例空白清空选中的逻辑,在注册click事件前,先将click事件队列清空;
|
||||
@@ -408,13 +474,34 @@ const DomainManger: React.FC<Props> = ({
|
||||
label: node.name,
|
||||
});
|
||||
});
|
||||
|
||||
graphRef.current.data(graphData);
|
||||
graphRef.current.render();
|
||||
graphRef.current.fitView([80, 80]);
|
||||
|
||||
setAllActiveLegend(legend);
|
||||
|
||||
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')
|
||||
@@ -442,18 +529,69 @@ const DomainManger: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<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={`${domainId}-${graphShowType}`}
|
||||
id="semanticGraph"
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
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: {
|
||||
domainId,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (eventName === SemanticNodeType.DIMENSION) {
|
||||
dispatch({
|
||||
type: 'domainManger/queryDimensionList',
|
||||
payload: {
|
||||
domainId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{createDimensionModalVisible && (
|
||||
<DimensionInfoModal
|
||||
domainId={domainId}
|
||||
bindModalVisible={createDimensionModalVisible}
|
||||
dimensionItem={dimensionItem}
|
||||
dataSourceList={nodeDataSource ? [nodeDataSource] : []}
|
||||
dataSourceList={nodeDataSource ? [nodeDataSource] : dataSourceInfoList}
|
||||
onSubmit={() => {
|
||||
setCreateDimensionModalVisible(false);
|
||||
updateGraphData();
|
||||
@@ -473,7 +611,7 @@ const DomainManger: React.FC<Props> = ({
|
||||
<MetricInfoCreateForm
|
||||
domainId={domainId}
|
||||
key={metricItem?.id}
|
||||
datasourceId={nodeDataSource.id}
|
||||
datasourceId={nodeDataSource?.id}
|
||||
createModalVisible={createMetricModalVisible}
|
||||
metricItem={metricItem}
|
||||
onSubmit={() => {
|
||||
@@ -491,6 +629,19 @@ const DomainManger: React.FC<Props> = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{
|
||||
<ClassDataSourceTypeModal
|
||||
open={createDataSourceModalOpen}
|
||||
onCancel={() => {
|
||||
setNodeDataSource(undefined);
|
||||
setCreateDataSourceModalOpen(false);
|
||||
}}
|
||||
dataSourceItem={nodeDataSource}
|
||||
onSubmit={() => {
|
||||
updateGraphData();
|
||||
}}
|
||||
/>
|
||||
}
|
||||
{
|
||||
<DeleteConfirmModal
|
||||
open={confirmModalOpenState}
|
||||
@@ -514,8 +665,7 @@ const DomainManger: React.FC<Props> = ({
|
||||
onCancelClick={() => {
|
||||
setConfirmModalOpenState(false);
|
||||
}}
|
||||
nodeType={graphShowType}
|
||||
nodeData={graphShowType === SemanticNodeType.DIMENSION ? dimensionItem : metricItem}
|
||||
nodeData={currentNodeData}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
|
||||
@@ -1,760 +1,22 @@
|
||||
@borderColor: #eee;
|
||||
@activeColor: #a0c5e8;
|
||||
@hoverColor: #dee4e9;
|
||||
|
||||
.pageContainer {
|
||||
position: absolute;
|
||||
top: 55px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
// margin: -24px;
|
||||
background: #fff;
|
||||
|
||||
&.externalPageContainer {
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.searchBar {
|
||||
:global {
|
||||
.ant-form-item-label {
|
||||
width: 70px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
:global {
|
||||
.ant-tabs {
|
||||
height: 100% !important;
|
||||
.ant-tabs-content {
|
||||
height: 100% !important;
|
||||
.ant-tabs-tabpane {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rightSide {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
min-width: 250px;
|
||||
height: 100%;
|
||||
margin-left: 4px;
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
:global {
|
||||
.ant-form-item {
|
||||
margin-bottom: 6px;
|
||||
|
||||
.ant-form-item-label {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.ant-form-item-control {
|
||||
min-width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rightListSide {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
// padding: 10px 10px 0;
|
||||
background-color: #fff;
|
||||
// 去掉标签间距
|
||||
:global {
|
||||
.ant-tabs-card.ant-tabs-top > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab,
|
||||
.ant-tabs-card.ant-tabs-bottom > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab,
|
||||
.ant-tabs-card.ant-tabs-top > div > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab,
|
||||
.ant-tabs-card.ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab {
|
||||
margin-left: 0;
|
||||
}
|
||||
.ant-tabs > .ant-tabs-nav .ant-tabs-nav-add,
|
||||
.ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-add {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.leftListSide {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
// padding: 10px 10px 0;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.tableTotal {
|
||||
margin: 0 2px;
|
||||
color: #296df3;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tableDetaildrawer {
|
||||
:global {
|
||||
.ant-drawer-header {
|
||||
padding: 10px 45px 10px 10px;
|
||||
}
|
||||
|
||||
.ant-drawer-close {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.ant-drawer-body {
|
||||
padding: 0 10px 10px;
|
||||
}
|
||||
|
||||
.ant-tabs-top > .ant-tabs-nav {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tableDetailTable {
|
||||
:global {
|
||||
.ant-table-cell,
|
||||
.resultTableRow > td {
|
||||
padding: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sqlEditor {
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
border: solid 1px @borderColor;
|
||||
|
||||
:global {
|
||||
.ace_editor {
|
||||
font-family: 'Menlo', 'Monaco', 'Ubuntu Mono', 'Consolas', 'source-code-pro' !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sqlOprBar {
|
||||
margin-top: -10px;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
.sqlOprBarLeftBox {
|
||||
flex: 1 1 200px;
|
||||
}
|
||||
.sqlOprBarRightBox {
|
||||
flex: 0 1 210px;
|
||||
}
|
||||
:global {
|
||||
.ant-btn-round.ant-btn-sm {
|
||||
font-size: 12px;
|
||||
}
|
||||
.ant-btn-primary {
|
||||
color: #fff;
|
||||
background: #02a7f0;
|
||||
border-color: #02a7f0;
|
||||
}
|
||||
.ant-segmented-item-selected {
|
||||
color: #fff;
|
||||
background: #02a7f0;
|
||||
border-color: #02a7f0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sqlOprIcon {
|
||||
margin-right: 30px;
|
||||
color: #02a7f0;
|
||||
font-size: 22px;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.sqlOprBtn {
|
||||
margin-right: 30px;
|
||||
vertical-align: super !important;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.sqlOprSwitch {
|
||||
// vertical-align: super !important;
|
||||
float: right;
|
||||
margin-right: 10px !important;
|
||||
}
|
||||
|
||||
:global {
|
||||
.is-sql-full-select {
|
||||
background-color: #02a7f0;
|
||||
}
|
||||
.cjjWdp:hover {
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
.sqlMain {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
|
||||
.sqlEditorWrapper {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sqlParams {
|
||||
width: 20%;
|
||||
height: 100% !important;
|
||||
overflow: auto;
|
||||
}
|
||||
.hideSqlParams {
|
||||
width: 0;
|
||||
height: 100% !important;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.sqlParamsBody {
|
||||
.header {
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
.nodeInfoDrawerContent {
|
||||
.title {
|
||||
margin-bottom: 12px;
|
||||
font-size: 16px;
|
||||
color: #0e73ff;
|
||||
font-weight: bold;
|
||||
|
||||
.title {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 10px !important;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
&::before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
content: "";
|
||||
left: -10px;
|
||||
top: 7px;
|
||||
height: 14px;
|
||||
width: 3px;
|
||||
font-size: 0;
|
||||
background: #0e73ff;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #0e73ff;
|
||||
}
|
||||
}
|
||||
|
||||
.paramsList {
|
||||
.paramsItem {
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
:global {
|
||||
.ant-list-item-action {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
flex: 1;
|
||||
width: 80%;
|
||||
overflow: hidden;
|
||||
font-size: 12px;
|
||||
text-overflow: ellipsis;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
// display: none;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// .paramsItem:hover {
|
||||
// .icon {
|
||||
// display: inline-block;
|
||||
// margin-left: 8px;
|
||||
// cursor: pointer;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
.disableIcon {
|
||||
vertical-align: super !important;
|
||||
// color: rgba(0, 10, 36, 0.25);
|
||||
background: #7d7f80 !important;
|
||||
border-color: #7d7f80 !important;
|
||||
:global {
|
||||
.anticon .anticon-play-circle {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: not-allowed;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.sqlTaskListWrap {
|
||||
position: relative;
|
||||
width: 262px;
|
||||
border-top: 0 !important;
|
||||
border-radius: 0;
|
||||
|
||||
:global {
|
||||
.ant-card-head {
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
.ant-card-head-title {
|
||||
padding: 8px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sqlTaskList {
|
||||
position: absolute !important;
|
||||
top: 42px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.sqlBottmWrap {
|
||||
// position: absolute;
|
||||
// top: 484px;
|
||||
// right: 0;
|
||||
// bottom: 0;
|
||||
// left: 0;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
// padding: 0 10px;
|
||||
|
||||
&:global(.small) {
|
||||
top: 334px;
|
||||
}
|
||||
|
||||
&:global(.middle) {
|
||||
top: 384px;
|
||||
}
|
||||
}
|
||||
|
||||
.sqlResultWrap {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
border: solid 1px @borderColor;
|
||||
border-top: 0;
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.sqlToolBar {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
align-items: center;
|
||||
height: 41px;
|
||||
padding: 5px 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.sqlResultPane {
|
||||
flex: 1;
|
||||
border-top: solid 1px @borderColor;
|
||||
}
|
||||
|
||||
.sqlToolBtn {
|
||||
margin-right: 15px;
|
||||
}
|
||||
.runScriptBtn {
|
||||
margin-right: 15px;
|
||||
background-color: #e87954;
|
||||
border-color: #e87954;
|
||||
&:hover{
|
||||
border-color: #f89878;
|
||||
background: #f89878;
|
||||
}
|
||||
&:focus {
|
||||
border-color: #f89878;
|
||||
background: #f89878;
|
||||
}
|
||||
}
|
||||
|
||||
.taskFailed {
|
||||
padding: 20px 20px 0 20px;
|
||||
}
|
||||
|
||||
.sqlResultContent {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
color: rgba(0, 0, 0, 0.25);
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sqlResultLog {
|
||||
padding: 20px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.tableList {
|
||||
position: absolute !important;
|
||||
top: 160px;
|
||||
right: 0;
|
||||
bottom: 26px;
|
||||
left: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
border-bottom: solid 1px @borderColor;
|
||||
}
|
||||
|
||||
.tablePage {
|
||||
position: absolute !important;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
min-width: 250px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tableListItem {
|
||||
width: 88%;
|
||||
overflow: hidden;
|
||||
font-size: 12px;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.tableItem {
|
||||
&:global(.ant-list-item) {
|
||||
padding: 6px 0 6px 6px;
|
||||
}
|
||||
|
||||
:global(.ant-list-item-action) {
|
||||
margin-left: 12px !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: @hoverColor;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
&:global(.active) {
|
||||
background: @activeColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.taskIcon {
|
||||
margin-right: 10px;
|
||||
color: #1890ff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.taskSuccessIcon {
|
||||
.taskIcon();
|
||||
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.taskFailIcon {
|
||||
.taskIcon();
|
||||
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.resultFailIcon {
|
||||
margin-right: 8px;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.taskItem {
|
||||
padding: 10px 8px !important;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
|
||||
&:global(.ant-list-item) {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: @hoverColor;
|
||||
}
|
||||
}
|
||||
|
||||
.activeTask {
|
||||
background: @activeColor;
|
||||
}
|
||||
|
||||
.resultTable {
|
||||
width: 100%;
|
||||
|
||||
:global {
|
||||
.ant-table-body {
|
||||
width: 100%;
|
||||
// max-height: none !important;
|
||||
overflow: auto !important;
|
||||
}
|
||||
|
||||
.ant-table-cell,
|
||||
.resultTableRow > td {
|
||||
padding: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.taskLogWrap {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.siteTagPlus {
|
||||
background: #fff;
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.editTag {
|
||||
margin-bottom: 5px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tagInput {
|
||||
width: 78px;
|
||||
margin-right: 8px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.outside {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
.collapseRightBtn {
|
||||
position: absolute;
|
||||
top: calc(50% + 50px);
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 70px;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
background-color: rgba(40, 46, 54, 0.2);
|
||||
border-radius: 24px 0 0 24px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.collapseLeftBtn {
|
||||
position: absolute;
|
||||
top: calc(50% + 45px);
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 70px;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
background-color: rgba(40, 46, 54, 0.2);
|
||||
border-radius: 0 24px 24px 0;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.detail {
|
||||
.titleCollapse {
|
||||
float: right;
|
||||
padding-right: 18px;
|
||||
color: #1890ff;
|
||||
line-height: 35px;
|
||||
text-align: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tableTitle {
|
||||
display: inline-block;
|
||||
width: 85%;
|
||||
margin-left: 15px;
|
||||
overflow: hidden;
|
||||
line-height: 35px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:global {
|
||||
.ant-divider-horizontal {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.middleArea {
|
||||
:global {
|
||||
.ant-tabs-nav .ant-tabs-tab {
|
||||
border: none;
|
||||
// background: #d9d9d96e;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
.ant-tabs-nav-add {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.ant-tabs-tab {
|
||||
.ant-tabs-tab-remove {
|
||||
.closeTab {
|
||||
opacity: 0;
|
||||
}
|
||||
.dot {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ant-tabs-tab:hover {
|
||||
.ant-tabs-tab-remove {
|
||||
.closeTab {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
.dot {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
height: 100%;
|
||||
padding: 5px;
|
||||
overflow: hidden;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
:global {
|
||||
.ant-form {
|
||||
margin: -2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menuList {
|
||||
position: absolute !important;
|
||||
top: 95px;
|
||||
right: 0;
|
||||
bottom: 26px;
|
||||
left: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
border-bottom: solid 1px @borderColor;
|
||||
.menuItem {
|
||||
&:global(.ant-list-item) {
|
||||
padding: 6px 0 6px 14px;
|
||||
}
|
||||
|
||||
:global(.ant-list-item-action) {
|
||||
margin-left: 12px !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: @hoverColor;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
.icon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&:global(.active) {
|
||||
background: @activeColor;
|
||||
}
|
||||
.menuListItem {
|
||||
width: 90%;
|
||||
overflow: hidden;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
display: none;
|
||||
margin-right: 15px !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
.menuIcon {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scriptFile {
|
||||
width: 100%;
|
||||
margin: 10px;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
.icon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.sqlScriptName {
|
||||
width: 93% !important;
|
||||
margin: 14px 0 0 14px !important;
|
||||
}
|
||||
|
||||
.fileIcon {
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
padding-top: 2px !important;
|
||||
padding-right: 5px !important;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.itemName {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.paneName {
|
||||
width: 100px;
|
||||
overflow: hidden;
|
||||
font-size: 12px !important;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.titleIcon {
|
||||
width: 16px !important;
|
||||
height: 16px !important;
|
||||
margin: 0 3px 4px;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ISemantic, IDataSource } from '../data';
|
||||
import { ISemantic } from '../data';
|
||||
import { SemanticNodeType } from '../enum';
|
||||
import { TreeGraphData } from '@antv/g6-core';
|
||||
|
||||
export const typeConfigs = {
|
||||
datasource: {
|
||||
@@ -11,6 +12,7 @@ export const typeConfigs = {
|
||||
export const getDimensionChildren = (
|
||||
dimensions: ISemantic.IDimensionItem[],
|
||||
dataSourceNodeId: string,
|
||||
limit: number = 999,
|
||||
) => {
|
||||
const dimensionChildrenList = dimensions.reduce(
|
||||
(dimensionChildren: any[], dimension: ISemantic.IDimensionItem) => {
|
||||
@@ -19,7 +21,7 @@ export const getDimensionChildren = (
|
||||
...dimension,
|
||||
nodeType: SemanticNodeType.DIMENSION,
|
||||
legendType: dataSourceNodeId,
|
||||
id: `${SemanticNodeType.DIMENSION}-${id}`,
|
||||
id: `${dataSourceNodeId}-${SemanticNodeType.DIMENSION}-${id}`,
|
||||
uid: id,
|
||||
style: {
|
||||
lineWidth: 2,
|
||||
@@ -31,10 +33,14 @@ export const getDimensionChildren = (
|
||||
},
|
||||
[],
|
||||
);
|
||||
return dimensionChildrenList;
|
||||
return dimensionChildrenList.slice(0, limit);
|
||||
};
|
||||
|
||||
export const getMetricChildren = (metrics: ISemantic.IMetricItem[], dataSourceNodeId: string) => {
|
||||
export const getMetricChildren = (
|
||||
metrics: ISemantic.IMetricItem[],
|
||||
dataSourceNodeId: string,
|
||||
limit: number = 999,
|
||||
) => {
|
||||
const metricsChildrenList = metrics.reduce(
|
||||
(metricsChildren: any[], metric: ISemantic.IMetricItem) => {
|
||||
const { id } = metric;
|
||||
@@ -42,7 +48,7 @@ export const getMetricChildren = (metrics: ISemantic.IMetricItem[], dataSourceNo
|
||||
...metric,
|
||||
nodeType: SemanticNodeType.METRIC,
|
||||
legendType: dataSourceNodeId,
|
||||
id: `${SemanticNodeType.METRIC}-${id}`,
|
||||
id: `${dataSourceNodeId}-${SemanticNodeType.METRIC}-${id}`,
|
||||
uid: id,
|
||||
style: {
|
||||
lineWidth: 2,
|
||||
@@ -54,40 +60,50 @@ export const getMetricChildren = (metrics: ISemantic.IMetricItem[], dataSourceNo
|
||||
},
|
||||
[],
|
||||
);
|
||||
return metricsChildrenList;
|
||||
return metricsChildrenList.slice(0, limit);
|
||||
};
|
||||
|
||||
export const formatterRelationData = (
|
||||
dataSourceList: IDataSource.IDataSourceItem[],
|
||||
type: SemanticNodeType = SemanticNodeType.DIMENSION,
|
||||
) => {
|
||||
const relationData = dataSourceList.reduce((relationList: any[], item: any) => {
|
||||
const { datasource, dimensions, metrics } = item;
|
||||
const { id } = datasource;
|
||||
const dataSourceNodeId = `${SemanticNodeType.DATASOURCE}-${id}`;
|
||||
let childrenList = [];
|
||||
if (type === SemanticNodeType.METRIC) {
|
||||
childrenList = getMetricChildren(metrics, dataSourceNodeId);
|
||||
}
|
||||
if (type === SemanticNodeType.DIMENSION) {
|
||||
childrenList = getDimensionChildren(dimensions, dataSourceNodeId);
|
||||
}
|
||||
relationList.push({
|
||||
...datasource,
|
||||
legendType: dataSourceNodeId,
|
||||
id: dataSourceNodeId,
|
||||
uid: id,
|
||||
nodeType: SemanticNodeType.DATASOURCE,
|
||||
size: 40,
|
||||
children: [...childrenList],
|
||||
style: {
|
||||
lineWidth: 2,
|
||||
fill: '#BDEFDB',
|
||||
stroke: '#5AD8A6',
|
||||
},
|
||||
});
|
||||
return relationList;
|
||||
}, []);
|
||||
export const formatterRelationData = (params: {
|
||||
dataSourceList: ISemantic.IDomainSchemaRelaList;
|
||||
limit?: number;
|
||||
type?: SemanticNodeType;
|
||||
}): TreeGraphData[] => {
|
||||
const { type, dataSourceList, limit } = params;
|
||||
const relationData = dataSourceList.reduce(
|
||||
(relationList: TreeGraphData[], item: ISemantic.IDomainSchemaRelaItem) => {
|
||||
const { datasource, dimensions, metrics } = item;
|
||||
const { id } = datasource;
|
||||
const dataSourceNodeId = `${SemanticNodeType.DATASOURCE}-${id}`;
|
||||
let childrenList = [];
|
||||
if (type === SemanticNodeType.METRIC) {
|
||||
childrenList = getMetricChildren(metrics, dataSourceNodeId, limit);
|
||||
}
|
||||
if (type === SemanticNodeType.DIMENSION) {
|
||||
childrenList = getDimensionChildren(dimensions, dataSourceNodeId, limit);
|
||||
}
|
||||
if (!type) {
|
||||
const dimensionList = getDimensionChildren(dimensions, dataSourceNodeId, limit);
|
||||
const metricList = getMetricChildren(metrics, dataSourceNodeId, limit);
|
||||
childrenList = [...dimensionList, ...metricList];
|
||||
}
|
||||
relationList.push({
|
||||
...datasource,
|
||||
legendType: dataSourceNodeId,
|
||||
id: dataSourceNodeId,
|
||||
uid: id,
|
||||
nodeType: SemanticNodeType.DATASOURCE,
|
||||
size: 40,
|
||||
children: [...childrenList],
|
||||
style: {
|
||||
lineWidth: 2,
|
||||
fill: '#BDEFDB',
|
||||
stroke: '#5AD8A6',
|
||||
},
|
||||
});
|
||||
return relationList;
|
||||
},
|
||||
[],
|
||||
);
|
||||
return relationData;
|
||||
};
|
||||
|
||||
@@ -121,6 +137,11 @@ export const getNodeConfigByType = (nodeData: any, defaultConfig = {}) => {
|
||||
case SemanticNodeType.METRIC:
|
||||
return {
|
||||
...defaultConfig,
|
||||
style: {
|
||||
lineWidth: 2,
|
||||
fill: '#fffbe6',
|
||||
stroke: '#ffe58f',
|
||||
},
|
||||
labelCfg: { position: 'right', ...labelCfg },
|
||||
};
|
||||
default:
|
||||
@@ -137,3 +158,37 @@ export const flatGraphDataNode = (graphData: any[]) => {
|
||||
return nodeList;
|
||||
}, []);
|
||||
};
|
||||
|
||||
interface Node {
|
||||
label: string;
|
||||
children?: Node[];
|
||||
}
|
||||
|
||||
export const findNodesByLabel = (query: string, nodes: Node[]): Node[] => {
|
||||
const result: Node[] = [];
|
||||
|
||||
for (const node of nodes) {
|
||||
let match = false;
|
||||
let children: Node[] = [];
|
||||
|
||||
// 如果节点的label包含查询字符串,我们将其标记为匹配
|
||||
if (node.label.includes(query)) {
|
||||
match = true;
|
||||
}
|
||||
|
||||
// 我们还需要在子节点中进行搜索
|
||||
if (node.children) {
|
||||
children = findNodesByLabel(query, node.children);
|
||||
if (children.length > 0) {
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果节点匹配或者其子节点匹配,我们将其添加到结果中
|
||||
if (match) {
|
||||
result.push({ ...node, children });
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -12,11 +12,11 @@ type Props = {
|
||||
};
|
||||
|
||||
const SemanticGraphCanvas: React.FC<Props> = ({ domainManger }) => {
|
||||
const [graphShowType, setGraphShowType] = useState<SemanticNodeType>(SemanticNodeType.DATASOURCE);
|
||||
const [graphShowType, setGraphShowType] = useState<SemanticNodeType>(SemanticNodeType.DIMENSION);
|
||||
const { selectDomainId } = domainManger;
|
||||
return (
|
||||
<div className={styles.semanticGraphCanvas}>
|
||||
<div className={styles.toolbar}>
|
||||
{/* <div className={styles.toolbar}>
|
||||
<Radio.Group
|
||||
buttonStyle="solid"
|
||||
value={graphShowType}
|
||||
@@ -29,7 +29,7 @@ const SemanticGraphCanvas: React.FC<Props> = ({ domainManger }) => {
|
||||
<Radio.Button value={SemanticNodeType.DIMENSION}>维度</Radio.Button>
|
||||
<Radio.Button value={SemanticNodeType.METRIC}>指标</Radio.Button>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className={styles.canvasContainer}>
|
||||
{graphShowType === SemanticNodeType.DATASOURCE ? (
|
||||
@@ -37,8 +37,8 @@ const SemanticGraphCanvas: React.FC<Props> = ({ domainManger }) => {
|
||||
<SemanticFlow />
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ width: '100%', height: 'calc(100vh - 220px)' }}>
|
||||
<SemanticGraph domainId={selectDomainId} graphShowType={graphShowType} />
|
||||
<div style={{ width: '100%' }}>
|
||||
<SemanticGraph domainId={selectDomainId} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,31 +1,23 @@
|
||||
import type { ActionType, ProColumns } from '@ant-design/pro-table';
|
||||
import ProTable from '@ant-design/pro-table';
|
||||
import { message, Button, Drawer, Space, Popconfirm, Modal, Card, Row, Col } from 'antd';
|
||||
import { ConsoleSqlOutlined, CoffeeOutlined } from '@ant-design/icons';
|
||||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import { message, Button, Space, Popconfirm } from 'antd';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import type { Dispatch } from 'umi';
|
||||
import { connect } from 'umi';
|
||||
import DataSourceCreateForm from '../Datasource/components/DataSourceCreateForm';
|
||||
import ClassDataSourceTypeModal from './ClassDataSourceTypeModal';
|
||||
import type { StateType } from '../model';
|
||||
import { getDatasourceList, deleteDatasource } from '../service';
|
||||
import DataSource from '../Datasource';
|
||||
import moment from 'moment';
|
||||
|
||||
const { Meta } = Card;
|
||||
type Props = {
|
||||
dispatch: Dispatch;
|
||||
domainManger: StateType;
|
||||
};
|
||||
|
||||
const ClassDataSourceTable: React.FC<Props> = ({ dispatch, domainManger }) => {
|
||||
const { selectDomainId, dataBaseResultColsMap, dataBaseConfig } = domainManger;
|
||||
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
|
||||
const { selectDomainId } = domainManger;
|
||||
const [dataSourceItem, setDataSourceItem] = useState<any>();
|
||||
const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false);
|
||||
const [dataSourceModalVisible, setDataSourceModalVisible] = useState(false);
|
||||
const [fastModeSql, setFastModeSql] = useState<string>('');
|
||||
const [fastModeTableName, setFastModeTableName] = useState<string>('');
|
||||
|
||||
const actionRef = useRef<ActionType>();
|
||||
|
||||
@@ -70,11 +62,7 @@ const ClassDataSourceTable: React.FC<Props> = ({ dispatch, domainManger }) => {
|
||||
key="classEditBtn"
|
||||
onClick={() => {
|
||||
setDataSourceItem(record);
|
||||
if (record.datasourceDetail.queryType === 'table_query') {
|
||||
setDataSourceModalVisible(true);
|
||||
return;
|
||||
}
|
||||
setCreateModalVisible(true);
|
||||
setCreateDataSourceModalOpen(true);
|
||||
}}
|
||||
>
|
||||
编辑
|
||||
@@ -133,25 +121,10 @@ const ClassDataSourceTable: React.FC<Props> = ({ dispatch, domainManger }) => {
|
||||
return resData;
|
||||
};
|
||||
|
||||
const queryDataBaseExcuteSql = (tableName: string) => {
|
||||
const sql = `select * from ${tableName}`;
|
||||
setFastModeSql(sql);
|
||||
setFastModeTableName(tableName);
|
||||
dispatch({
|
||||
type: 'domainManger/queryDataBaseExcuteSql',
|
||||
payload: {
|
||||
sql,
|
||||
domainId: selectDomainId,
|
||||
tableName,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ProTable
|
||||
actionRef={actionRef}
|
||||
headerTitle="数据源列表"
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
params={{ domainId: selectDomainId }}
|
||||
@@ -179,59 +152,12 @@ const ClassDataSourceTable: React.FC<Props> = ({ dispatch, domainManger }) => {
|
||||
onCancel={() => {
|
||||
setCreateDataSourceModalOpen(false);
|
||||
}}
|
||||
onTypeChange={(type) => {
|
||||
if (type === 'fast') {
|
||||
setDataSourceModalVisible(true);
|
||||
} else {
|
||||
setCreateModalVisible(true);
|
||||
}
|
||||
setCreateDataSourceModalOpen(false);
|
||||
dataSourceItem={dataSourceItem}
|
||||
onSubmit={() => {
|
||||
actionRef.current?.reload();
|
||||
}}
|
||||
/>
|
||||
}
|
||||
{dataSourceModalVisible && (
|
||||
<DataSourceCreateForm
|
||||
sql={fastModeSql}
|
||||
basicInfoFormMode="fast"
|
||||
domainId={Number(selectDomainId)}
|
||||
dataSourceItem={dataSourceItem}
|
||||
onCancel={() => {
|
||||
setDataSourceModalVisible(false);
|
||||
}}
|
||||
onDataBaseTableChange={(tableName: string) => {
|
||||
queryDataBaseExcuteSql(tableName);
|
||||
}}
|
||||
onSubmit={() => {
|
||||
setDataSourceModalVisible(false);
|
||||
setDataSourceItem(undefined);
|
||||
actionRef.current?.reload();
|
||||
}}
|
||||
createModalVisible={dataSourceModalVisible}
|
||||
/>
|
||||
)}
|
||||
{createModalVisible && (
|
||||
<Drawer
|
||||
width={'100%'}
|
||||
destroyOnClose
|
||||
title="数据源编辑"
|
||||
open={true}
|
||||
onClose={() => {
|
||||
setCreateModalVisible(false);
|
||||
setDataSourceItem(undefined);
|
||||
}}
|
||||
footer={null}
|
||||
>
|
||||
<DataSource
|
||||
initialValues={dataSourceItem}
|
||||
domainId={Number(selectDomainId)}
|
||||
onSubmitSuccess={() => {
|
||||
setCreateModalVisible(false);
|
||||
setDataSourceItem(undefined);
|
||||
actionRef.current?.reload();
|
||||
}}
|
||||
/>
|
||||
</Drawer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,28 +1,69 @@
|
||||
import { Modal, Card, Row, Col, Result, Button } from 'antd';
|
||||
import { Button, Drawer, Result, Modal, Card, Row, Col } from 'antd';
|
||||
import { ConsoleSqlOutlined, CoffeeOutlined } from '@ant-design/icons';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { history, connect } from 'umi';
|
||||
import type { Dispatch } from 'umi';
|
||||
import { history, connect } from 'umi';
|
||||
import DataSourceCreateForm from '../Datasource/components/DataSourceCreateForm';
|
||||
import type { StateType } from '../model';
|
||||
import DataSource from '../Datasource';
|
||||
import { IDataSource } from '../data';
|
||||
|
||||
const { Meta } = Card;
|
||||
type Props = {
|
||||
open: boolean;
|
||||
domainManger: StateType;
|
||||
onTypeChange: (type: 'fast' | 'normal') => void;
|
||||
dataSourceItem: IDataSource.IDataSourceItem;
|
||||
onTypeChange?: (type: 'fast' | 'normal') => void;
|
||||
onSubmit?: () => void;
|
||||
onCancel?: () => void;
|
||||
dispatch: Dispatch;
|
||||
domainManger: StateType;
|
||||
};
|
||||
|
||||
const ClassDataSourceTypeModal: React.FC<Props> = ({
|
||||
open,
|
||||
onTypeChange,
|
||||
onSubmit,
|
||||
dataSourceItem,
|
||||
domainManger,
|
||||
onCancel,
|
||||
dispatch,
|
||||
}) => {
|
||||
const { selectDomainId, dataBaseConfig } = domainManger;
|
||||
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
|
||||
|
||||
const [dataSourceModalVisible, setDataSourceModalVisible] = useState(false);
|
||||
const [fastModeSql, setFastModeSql] = useState<string>('');
|
||||
|
||||
const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setCreateDataSourceModalOpen(open);
|
||||
}, [open]);
|
||||
if (!dataSourceItem || !open) {
|
||||
setCreateDataSourceModalOpen(open);
|
||||
return;
|
||||
}
|
||||
if (dataSourceItem?.datasourceDetail?.queryType === 'table_query') {
|
||||
setDataSourceModalVisible(true);
|
||||
} else {
|
||||
setCreateModalVisible(true);
|
||||
}
|
||||
}, [dataSourceItem, open]);
|
||||
|
||||
const queryDataBaseExcuteSql = (tableName: string) => {
|
||||
const sql = `select * from ${tableName}`;
|
||||
setFastModeSql(sql);
|
||||
|
||||
dispatch({
|
||||
type: 'domainManger/queryDataBaseExcuteSql',
|
||||
payload: {
|
||||
sql,
|
||||
domainId: selectDomainId,
|
||||
tableName,
|
||||
},
|
||||
});
|
||||
};
|
||||
const handleCancel = () => {
|
||||
onCancel?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -30,7 +71,7 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({
|
||||
open={createDataSourceModalOpen}
|
||||
onCancel={() => {
|
||||
setCreateDataSourceModalOpen(false);
|
||||
onCancel?.();
|
||||
handleCancel();
|
||||
}}
|
||||
footer={null}
|
||||
centered
|
||||
@@ -43,8 +84,10 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({
|
||||
hoverable
|
||||
style={{ height: 220 }}
|
||||
onClick={() => {
|
||||
onTypeChange('fast');
|
||||
onTypeChange?.('fast');
|
||||
setCreateDataSourceModalOpen(false);
|
||||
|
||||
setDataSourceModalVisible(true);
|
||||
}}
|
||||
cover={
|
||||
<CoffeeOutlined
|
||||
@@ -59,8 +102,10 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({
|
||||
<Col span={12}>
|
||||
<Card
|
||||
onClick={() => {
|
||||
onTypeChange('normal');
|
||||
onTypeChange?.('normal');
|
||||
setCreateDataSourceModalOpen(false);
|
||||
|
||||
setCreateModalVisible(true);
|
||||
}}
|
||||
hoverable
|
||||
style={{ height: 220 }}
|
||||
@@ -93,10 +138,52 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
{dataSourceModalVisible && (
|
||||
<DataSourceCreateForm
|
||||
sql={fastModeSql}
|
||||
basicInfoFormMode="fast"
|
||||
domainId={Number(selectDomainId)}
|
||||
dataSourceItem={dataSourceItem}
|
||||
onCancel={() => {
|
||||
setDataSourceModalVisible(false);
|
||||
handleCancel();
|
||||
}}
|
||||
onDataBaseTableChange={(tableName: string) => {
|
||||
queryDataBaseExcuteSql(tableName);
|
||||
}}
|
||||
onSubmit={() => {
|
||||
setDataSourceModalVisible(false);
|
||||
onSubmit?.();
|
||||
}}
|
||||
createModalVisible={dataSourceModalVisible}
|
||||
/>
|
||||
)}
|
||||
{createModalVisible && (
|
||||
<Drawer
|
||||
width={'100%'}
|
||||
destroyOnClose
|
||||
title="数据源编辑"
|
||||
open={true}
|
||||
onClose={() => {
|
||||
setCreateModalVisible(false);
|
||||
handleCancel();
|
||||
}}
|
||||
footer={null}
|
||||
>
|
||||
<DataSource
|
||||
initialValues={dataSourceItem}
|
||||
domainId={Number(selectDomainId)}
|
||||
onSubmitSuccess={() => {
|
||||
setCreateModalVisible(false);
|
||||
onSubmit?.();
|
||||
}}
|
||||
/>
|
||||
</Drawer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ domainManger }: { domainManger: StateType }) => ({
|
||||
domainManger,
|
||||
}))(ClassDataSourceTypeModal);
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { Modal, Card, Row, Col, Result, Button } from 'antd';
|
||||
import { ConsoleSqlOutlined, CoffeeOutlined } from '@ant-design/icons';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { history, connect } from 'umi';
|
||||
import type { StateType } from '../model';
|
||||
const { Meta } = Card;
|
||||
type Props = {
|
||||
open: boolean;
|
||||
domainManger: StateType;
|
||||
onTypeChange: (type: 'fast' | 'normal') => void;
|
||||
onCancel?: () => void;
|
||||
};
|
||||
|
||||
const ClassDataSourceTypeModal: React.FC<Props> = ({
|
||||
open,
|
||||
onTypeChange,
|
||||
domainManger,
|
||||
onCancel,
|
||||
}) => {
|
||||
const { selectDomainId, dataBaseConfig } = domainManger;
|
||||
const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false);
|
||||
useEffect(() => {
|
||||
setCreateDataSourceModalOpen(open);
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
open={createDataSourceModalOpen}
|
||||
onCancel={() => {
|
||||
setCreateDataSourceModalOpen(false);
|
||||
onCancel?.();
|
||||
}}
|
||||
footer={null}
|
||||
centered
|
||||
closable={false}
|
||||
>
|
||||
{dataBaseConfig && dataBaseConfig.id ? (
|
||||
<Row gutter={16} style={{ marginTop: '0px' }}>
|
||||
<Col span={12}>
|
||||
<Card
|
||||
hoverable
|
||||
style={{ height: 220 }}
|
||||
onClick={() => {
|
||||
onTypeChange('fast');
|
||||
setCreateDataSourceModalOpen(false);
|
||||
}}
|
||||
cover={
|
||||
<CoffeeOutlined
|
||||
width={240}
|
||||
style={{ paddingTop: '45px', height: 120, fontSize: '48px', color: '#1890ff' }}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Meta title="快速创建" description="自动进行数据源可视化创建" />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Card
|
||||
onClick={() => {
|
||||
onTypeChange('normal');
|
||||
setCreateDataSourceModalOpen(false);
|
||||
}}
|
||||
hoverable
|
||||
style={{ height: 220 }}
|
||||
cover={
|
||||
<ConsoleSqlOutlined
|
||||
style={{ paddingTop: '45px', height: 120, fontSize: '48px', color: '#1890ff' }}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Meta title="SQL脚本" description="自定义SQL脚本创建数据源" />
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
) : (
|
||||
<Result
|
||||
status="warning"
|
||||
subTitle="创建数据源需要先完成数据库设置"
|
||||
extra={
|
||||
<Button
|
||||
type="primary"
|
||||
key="console"
|
||||
onClick={() => {
|
||||
history.replace(`/semanticModel/${selectDomainId}/dataBase`);
|
||||
onCancel?.();
|
||||
}}
|
||||
>
|
||||
去设置
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ domainManger }: { domainManger: StateType }) => ({
|
||||
domainManger,
|
||||
}))(ClassDataSourceTypeModal);
|
||||
@@ -8,6 +8,7 @@ import type { StateType } from '../model';
|
||||
import { SENSITIVE_LEVEL_ENUM } from '../constant';
|
||||
import { getDatasourceList, getDimensionList, deleteDimension } from '../service';
|
||||
import DimensionInfoModal from './DimensionInfoModal';
|
||||
import DimensionValueSettingModal from './DimensionValueSettingModal';
|
||||
import { ISemantic } from '../data';
|
||||
import moment from 'moment';
|
||||
import styles from './style.less';
|
||||
@@ -22,6 +23,11 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
|
||||
const [dimensionItem, setDimensionItem] = useState<ISemantic.IDimensionItem>();
|
||||
const [dataSourceList, setDataSourceList] = useState<any[]>([]);
|
||||
const [dimensionValueSettingList, setDimensionValueSettingList] = useState<
|
||||
ISemantic.IDimensionValueSettingItem[]
|
||||
>([]);
|
||||
const [dimensionValueSettingModalVisible, setDimensionValueSettingModalVisible] =
|
||||
useState<boolean>(false);
|
||||
const [pagination, setPagination] = useState({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
@@ -141,6 +147,20 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
>
|
||||
编辑
|
||||
</a>
|
||||
<a
|
||||
key="classEditBtn"
|
||||
onClick={() => {
|
||||
setDimensionItem(record);
|
||||
setDimensionValueSettingModalVisible(true);
|
||||
if (Array.isArray(record.dimValueMaps)) {
|
||||
setDimensionValueSettingList(record.dimValueMaps);
|
||||
} else {
|
||||
setDimensionValueSettingList([]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
维度值设置
|
||||
</a>
|
||||
<Popconfirm
|
||||
title="确认删除?"
|
||||
okText="是"
|
||||
@@ -175,7 +195,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
<ProTable
|
||||
className={`${styles.classTable} ${styles.classTableSelectColumnAlignLeft}`}
|
||||
actionRef={actionRef}
|
||||
headerTitle="维度列表"
|
||||
// headerTitle="维度列表"
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
request={queryDimensionList}
|
||||
@@ -236,6 +256,26 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{dimensionValueSettingModalVisible && (
|
||||
<DimensionValueSettingModal
|
||||
dimensionValueSettingList={dimensionValueSettingList}
|
||||
open={dimensionValueSettingModalVisible}
|
||||
dimensionItem={dimensionItem}
|
||||
onCancel={() => {
|
||||
setDimensionValueSettingModalVisible(false);
|
||||
}}
|
||||
onSubmit={() => {
|
||||
actionRef?.current?.reload();
|
||||
dispatch({
|
||||
type: 'domainManger/queryDimensionList',
|
||||
payload: {
|
||||
domainId: selectDomainId,
|
||||
},
|
||||
});
|
||||
setDimensionValueSettingModalVisible(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -166,43 +166,12 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
},
|
||||
];
|
||||
|
||||
// const saveMetric = async (fieldsValue: any, reloadState: boolean = true) => {
|
||||
// const queryParams = {
|
||||
// domainId: selectDomainId,
|
||||
// ...fieldsValue,
|
||||
// };
|
||||
// if (queryParams.typeParams && !queryParams.typeParams.expr) {
|
||||
// message.error('度量表达式不能为空');
|
||||
// return;
|
||||
// }
|
||||
// let saveMetricQuery = creatExprMetric;
|
||||
// if (queryParams.id) {
|
||||
// saveMetricQuery = updateExprMetric;
|
||||
// }
|
||||
// const { code, msg } = await saveMetricQuery(queryParams);
|
||||
// if (code === 200) {
|
||||
// message.success('编辑指标成功');
|
||||
// setCreateModalVisible(false);
|
||||
// if (reloadState) {
|
||||
// actionRef?.current?.reload();
|
||||
// }
|
||||
// dispatch({
|
||||
// type: 'domainManger/queryMetricList',
|
||||
// payload: {
|
||||
// domainId: selectDomainId,
|
||||
// },
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
// message.error(msg);
|
||||
// };
|
||||
|
||||
return (
|
||||
<>
|
||||
<ProTable
|
||||
className={`${styles.classTable} ${styles.classTableSelectColumnAlignLeft}`}
|
||||
actionRef={actionRef}
|
||||
headerTitle="指标列表"
|
||||
// headerTitle="指标列表"
|
||||
rowKey="id"
|
||||
search={{
|
||||
span: 4,
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { List, Collapse, Button, Input } from 'antd';
|
||||
import { uuid } from '@/utils/utils';
|
||||
|
||||
import styles from './style.less';
|
||||
|
||||
const { Panel } = Collapse;
|
||||
const { TextArea } = Input;
|
||||
|
||||
type Props = {
|
||||
title?: string;
|
||||
defaultCollapse?: boolean;
|
||||
value?: string[];
|
||||
onChange?: (list: string[]) => void;
|
||||
};
|
||||
|
||||
type ListItem = {
|
||||
id: string;
|
||||
sql: string;
|
||||
};
|
||||
|
||||
type List = ListItem[];
|
||||
|
||||
const CommonEditList: React.FC<Props> = ({ title, defaultCollapse = false, value, onChange }) => {
|
||||
const [listDataSource, setListDataSource] = useState<List>([]);
|
||||
const [currentSql, setCurrentSql] = useState<string>('');
|
||||
const [activeKey, setActiveKey] = useState<string>();
|
||||
const [currentRecord, setCurrentRecord] = useState<ListItem>();
|
||||
|
||||
useEffect(() => {
|
||||
if (Array.isArray(value)) {
|
||||
const list = value.map((sql: string) => {
|
||||
return {
|
||||
id: uuid(),
|
||||
sql,
|
||||
};
|
||||
});
|
||||
setListDataSource(list);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
const handleListChange = (listDataSource: List) => {
|
||||
const sqlList = listDataSource.map((item) => {
|
||||
return item.sql;
|
||||
});
|
||||
onChange?.(sqlList);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.commonEditList}>
|
||||
<Collapse
|
||||
activeKey={activeKey}
|
||||
defaultActiveKey={defaultCollapse ? ['editor'] : undefined}
|
||||
onChange={() => {}}
|
||||
ghost
|
||||
>
|
||||
<Panel
|
||||
header={title}
|
||||
key="editor"
|
||||
extra={
|
||||
activeKey ? (
|
||||
<Button
|
||||
key="saveBtn"
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
if (!currentRecord && !currentSql) {
|
||||
setActiveKey(undefined);
|
||||
return;
|
||||
}
|
||||
if (currentRecord) {
|
||||
const list = [...listDataSource].map((item) => {
|
||||
if (item.id === currentRecord.id) {
|
||||
return {
|
||||
...item,
|
||||
sql: currentSql,
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
setListDataSource(list);
|
||||
handleListChange(list);
|
||||
} else {
|
||||
const list = [
|
||||
...listDataSource,
|
||||
{
|
||||
id: uuid(),
|
||||
sql: currentSql,
|
||||
},
|
||||
];
|
||||
setListDataSource(list);
|
||||
handleListChange(list);
|
||||
}
|
||||
|
||||
setActiveKey(undefined);
|
||||
}}
|
||||
>
|
||||
确认
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="primary"
|
||||
key="createBtn"
|
||||
onClick={() => {
|
||||
setCurrentRecord(undefined);
|
||||
setCurrentSql('');
|
||||
setActiveKey('editor');
|
||||
}}
|
||||
>
|
||||
新增
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
showArrow={false}
|
||||
>
|
||||
<div>
|
||||
<TextArea
|
||||
placeholder="请输入推荐问题"
|
||||
value={currentSql}
|
||||
style={{ height: 150 }}
|
||||
minLength={5}
|
||||
onChange={(e) => {
|
||||
setCurrentSql(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Panel>
|
||||
</Collapse>
|
||||
<List
|
||||
itemLayout="horizontal"
|
||||
dataSource={listDataSource || []}
|
||||
renderItem={(item) => (
|
||||
<List.Item
|
||||
actions={[
|
||||
<a
|
||||
key="list-loadmore-edit"
|
||||
onClick={() => {
|
||||
setCurrentSql(item.sql);
|
||||
setCurrentRecord(item);
|
||||
setActiveKey('editor');
|
||||
}}
|
||||
>
|
||||
编辑
|
||||
</a>,
|
||||
<a
|
||||
key="list-loadmore-more"
|
||||
onClick={() => {
|
||||
const list = [...listDataSource].filter(({ id }) => {
|
||||
return item.id !== id;
|
||||
});
|
||||
handleListChange(list);
|
||||
setListDataSource(list);
|
||||
}}
|
||||
>
|
||||
删除
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
<List.Item.Meta title={item.sql} />
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommonEditList;
|
||||
@@ -9,7 +9,7 @@ type Props = {
|
||||
title?: string;
|
||||
tableDataSource: any[];
|
||||
columnList: any[];
|
||||
rowKey: string;
|
||||
rowKey?: string;
|
||||
editableProTableProps?: any;
|
||||
onDataSourceChange?: (dataSource: any) => void;
|
||||
extenderCtrlColumn?: (text, record, _, action) => ReactNode[];
|
||||
@@ -35,6 +35,7 @@ const CommonEditTable: React.FC<Props> = forwardRef(
|
||||
}: Props,
|
||||
ref: Ref<any>,
|
||||
) => {
|
||||
const defaultRowKey = rowKey || 'editRowId';
|
||||
const [dataSource, setDataSource] = useState<any[]>(tableDataSource);
|
||||
const actionRef = useRef<ActionType>();
|
||||
|
||||
@@ -50,7 +51,7 @@ const CommonEditTable: React.FC<Props> = forwardRef(
|
||||
tableDataSource.map((item: any) => {
|
||||
return {
|
||||
...item,
|
||||
editRowId: item[rowKey] || (Math.random() * 1000000).toFixed(0),
|
||||
editRowId: item[defaultRowKey] || (Math.random() * 1000000).toFixed(0),
|
||||
};
|
||||
}),
|
||||
);
|
||||
@@ -82,7 +83,9 @@ const CommonEditTable: React.FC<Props> = forwardRef(
|
||||
<a
|
||||
key="deleteBtn"
|
||||
onClick={() => {
|
||||
const data = [...dataSource].filter((item) => item[rowKey] !== record[rowKey]);
|
||||
const data = [...dataSource].filter(
|
||||
(item) => item[defaultRowKey] !== record[defaultRowKey],
|
||||
);
|
||||
setDataSource(data);
|
||||
handleDataSourceChange(data);
|
||||
}}
|
||||
@@ -111,7 +114,7 @@ const CommonEditTable: React.FC<Props> = forwardRef(
|
||||
key={title}
|
||||
actionRef={actionRef}
|
||||
headerTitle={title}
|
||||
rowKey={'editRowId'}
|
||||
rowKey={defaultRowKey}
|
||||
columns={columns}
|
||||
value={dataSource}
|
||||
tableAlertRender={() => {
|
||||
@@ -133,9 +136,9 @@ const CommonEditTable: React.FC<Props> = forwardRef(
|
||||
}}
|
||||
editable={{
|
||||
onSave: (_, row) => {
|
||||
const rowKeyValue = row[rowKey];
|
||||
const rowKeyValue = row[defaultRowKey];
|
||||
const isSame = dataSource.filter((item: any, index: number) => {
|
||||
return index !== row.index && item[rowKey] === rowKeyValue;
|
||||
return index !== row.index && item[defaultRowKey] === rowKeyValue;
|
||||
});
|
||||
if (isSame[0]) {
|
||||
message.error('存在重复值');
|
||||
|
||||
@@ -20,15 +20,8 @@ const DatabaseCreateForm: ForwardRefRenderFunction<any, Props> = (
|
||||
) => {
|
||||
const [form] = Form.useForm();
|
||||
const [selectedDbType, setSelectedDbType] = useState<string>('h2');
|
||||
// const queryDatabaseConfig = async () => {
|
||||
// const { code, data } = await getDatabaseByDomainId(domainId);
|
||||
// if (code === 200) {
|
||||
// form.setFieldsValue({ ...data });
|
||||
// setSelectedDbType(data?.type);
|
||||
// return;
|
||||
// }
|
||||
// message.error('数据库配置获取错误');
|
||||
// };
|
||||
|
||||
const [testLoading, setTestLoading] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
form.resetFields();
|
||||
@@ -36,11 +29,6 @@ const DatabaseCreateForm: ForwardRefRenderFunction<any, Props> = (
|
||||
setSelectedDbType(dataBaseConfig?.type);
|
||||
}, [dataBaseConfig]);
|
||||
|
||||
// useEffect(() => {
|
||||
// form.resetFields();
|
||||
// // queryDatabaseConfig();
|
||||
// }, [domainId]);
|
||||
|
||||
const getFormValidateFields = async () => {
|
||||
return await form.validateFields();
|
||||
};
|
||||
@@ -65,10 +53,12 @@ const DatabaseCreateForm: ForwardRefRenderFunction<any, Props> = (
|
||||
};
|
||||
const testDatabaseConnection = async () => {
|
||||
const values = await form.validateFields();
|
||||
setTestLoading(true);
|
||||
const { code, data } = await testDatabaseConnect({
|
||||
...values,
|
||||
domainId,
|
||||
});
|
||||
setTestLoading(false);
|
||||
if (code === 200 && data) {
|
||||
message.success('连接测试通过');
|
||||
return;
|
||||
@@ -138,7 +128,9 @@ const DatabaseCreateForm: ForwardRefRenderFunction<any, Props> = (
|
||||
<FormItem name="database" label="数据库名称">
|
||||
<Input placeholder="请输入数据库名称" />
|
||||
</FormItem>
|
||||
|
||||
<FormItem name="version" label="数据库版本">
|
||||
<Input placeholder="请输入数据库版本" />
|
||||
</FormItem>
|
||||
<FormItem name="description" label="描述">
|
||||
<TextArea placeholder="请输入数据库描述" style={{ height: 100 }} />
|
||||
</FormItem>
|
||||
@@ -146,6 +138,7 @@ const DatabaseCreateForm: ForwardRefRenderFunction<any, Props> = (
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
loading={testLoading}
|
||||
onClick={() => {
|
||||
testDatabaseConnection();
|
||||
}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Form, Input, Modal, Select } from 'antd';
|
||||
import { SENSITIVE_LEVEL_OPTIONS } from '../constant';
|
||||
import { formLayout } from '@/components/FormHelper/utils';
|
||||
@@ -6,6 +6,7 @@ import SqlEditor from '@/components/SqlEditor';
|
||||
import InfoTagList from './InfoTagList';
|
||||
import { ISemantic } from '../data';
|
||||
import { createDimension, updateDimension } from '../service';
|
||||
// import DimensionValueSettingModal from './DimensionValueSettingModal';
|
||||
import { message } from 'antd';
|
||||
|
||||
export type CreateFormProps = {
|
||||
@@ -31,16 +32,28 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
|
||||
onSubmit: handleUpdate,
|
||||
}) => {
|
||||
const isEdit = !!dimensionItem?.id;
|
||||
|
||||
const [dimensionValueSettingList, setDimensionValueSettingList] = useState<
|
||||
ISemantic.IDimensionValueSettingItem[]
|
||||
>([]);
|
||||
const [form] = Form.useForm();
|
||||
const { setFieldsValue, resetFields } = form;
|
||||
|
||||
const handleSubmit = async () => {
|
||||
// const [dimensionValueSettingModalVisible, setDimensionValueSettingModalVisible] =
|
||||
// useState<boolean>(false);
|
||||
const handleSubmit = async (
|
||||
isSilenceSubmit = false,
|
||||
dimValueMaps?: ISemantic.IDimensionValueSettingItem[],
|
||||
) => {
|
||||
const fieldsValue = await form.validateFields();
|
||||
await saveDimension(fieldsValue);
|
||||
await saveDimension(
|
||||
{
|
||||
...fieldsValue,
|
||||
dimValueMaps: dimValueMaps || dimensionValueSettingList,
|
||||
},
|
||||
isSilenceSubmit,
|
||||
);
|
||||
};
|
||||
|
||||
const saveDimension = async (fieldsValue: any) => {
|
||||
const saveDimension = async (fieldsValue: any, isSilenceSubmit = false) => {
|
||||
const queryParams = {
|
||||
domainId,
|
||||
type: 'categorical',
|
||||
@@ -52,8 +65,10 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
|
||||
}
|
||||
const { code, msg } = await saveDimensionQuery(queryParams);
|
||||
if (code === 200) {
|
||||
message.success('编辑维度成功');
|
||||
handleUpdate(fieldsValue);
|
||||
if (!isSilenceSubmit) {
|
||||
message.success('编辑维度成功');
|
||||
handleUpdate(fieldsValue);
|
||||
}
|
||||
return;
|
||||
}
|
||||
message.error(msg);
|
||||
@@ -66,6 +81,11 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
|
||||
useEffect(() => {
|
||||
if (dimensionItem) {
|
||||
setFormVal();
|
||||
if (Array.isArray(dimensionItem.dimValueMaps)) {
|
||||
setDimensionValueSettingList(dimensionItem.dimValueMaps);
|
||||
} else {
|
||||
setDimensionValueSettingList([]);
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
@@ -78,7 +98,12 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
|
||||
return (
|
||||
<>
|
||||
<Button onClick={onCancel}>取消</Button>
|
||||
<Button type="primary" onClick={handleSubmit}>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
handleSubmit();
|
||||
}}
|
||||
>
|
||||
完成
|
||||
</Button>
|
||||
</>
|
||||
@@ -93,20 +118,22 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
|
||||
</FormItem>
|
||||
<FormItem
|
||||
name="name"
|
||||
label="维度中文名"
|
||||
rules={[{ required: true, message: '请输入维度中文名' }]}
|
||||
label="维度名称"
|
||||
rules={[{ required: true, message: '请输入维度名称' }]}
|
||||
>
|
||||
<Input placeholder="名称不可重复" />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
hidden={isEdit}
|
||||
name="bizName"
|
||||
label="维度英文名"
|
||||
rules={[{ required: true, message: '请输入维度英文名' }]}
|
||||
label="字段名称"
|
||||
rules={[{ required: true, message: '请输入字段名称' }]}
|
||||
>
|
||||
<Input placeholder="名称不可重复" disabled={isEdit} />
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
hidden={isEdit}
|
||||
name="datasourceId"
|
||||
label="所属数据源"
|
||||
rules={[{ required: true, message: '请选择所属数据源' }]}
|
||||
@@ -158,6 +185,16 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
|
||||
>
|
||||
<TextArea placeholder="请输入维度描述" />
|
||||
</FormItem>
|
||||
{/* <FormItem name="dimValueMaps" label="维度值设置">
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
setDimensionValueSettingModalVisible(true);
|
||||
}}
|
||||
>
|
||||
设置
|
||||
</Button>
|
||||
</FormItem> */}
|
||||
<FormItem
|
||||
name="expr"
|
||||
label="表达式"
|
||||
@@ -171,28 +208,38 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
width={800}
|
||||
destroyOnClose
|
||||
title="维度信息"
|
||||
style={{ top: 48 }}
|
||||
maskClosable={false}
|
||||
open={bindModalVisible}
|
||||
footer={renderFooter()}
|
||||
onCancel={onCancel}
|
||||
>
|
||||
<Form
|
||||
{...formLayout}
|
||||
form={form}
|
||||
initialValues={
|
||||
{
|
||||
// ...formVals,
|
||||
}
|
||||
}
|
||||
<>
|
||||
<Modal
|
||||
width={800}
|
||||
destroyOnClose
|
||||
title="维度信息"
|
||||
style={{ top: 48 }}
|
||||
maskClosable={false}
|
||||
open={bindModalVisible}
|
||||
footer={renderFooter()}
|
||||
onCancel={onCancel}
|
||||
>
|
||||
{renderContent()}
|
||||
</Form>
|
||||
</Modal>
|
||||
<Form {...formLayout} form={form}>
|
||||
{renderContent()}
|
||||
</Form>
|
||||
</Modal>
|
||||
{/* {dimensionValueSettingModalVisible && (
|
||||
<DimensionValueSettingModal
|
||||
dimensionValueSettingList={dimensionValueSettingList}
|
||||
open={dimensionValueSettingModalVisible}
|
||||
onCancel={() => {
|
||||
setDimensionValueSettingModalVisible(false);
|
||||
}}
|
||||
onSubmit={(dimValueMaps) => {
|
||||
if (isEdit) {
|
||||
handleSubmit(true, dimValueMaps);
|
||||
}
|
||||
setDimensionValueSettingList(dimValueMaps);
|
||||
setDimensionValueSettingModalVisible(false);
|
||||
}}
|
||||
/>
|
||||
)} */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Modal, message } from 'antd';
|
||||
import { ISemantic } from '../data';
|
||||
import CommonEditTable from './CommonEditTable';
|
||||
import { createDimension, updateDimension } from '../service';
|
||||
import { connect } from 'umi';
|
||||
import type { StateType } from '../model';
|
||||
|
||||
export type CreateFormProps = {
|
||||
dimensionValueSettingList: ISemantic.IDimensionValueSettingItem[];
|
||||
onCancel: () => void;
|
||||
dimensionItem?: ISemantic.IDimensionItem;
|
||||
open: boolean;
|
||||
onSubmit: (values?: any) => void;
|
||||
domainManger: StateType;
|
||||
};
|
||||
|
||||
type TableDataSource = { techName: string; bizName: string; alias: string };
|
||||
|
||||
const DimensionInfoModal: React.FC<CreateFormProps> = ({
|
||||
onCancel,
|
||||
open,
|
||||
dimensionItem,
|
||||
dimensionValueSettingList,
|
||||
domainManger,
|
||||
onSubmit,
|
||||
}) => {
|
||||
const [tableDataSource, setTableDataSource] = useState<TableDataSource[]>([]);
|
||||
const { selectDomainId } = domainManger;
|
||||
const [dimValueMaps, setDimValueMaps] = useState<ISemantic.IDimensionValueSettingItem[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const dataSource = dimensionValueSettingList.map((item) => {
|
||||
const { alias } = item;
|
||||
return {
|
||||
...item,
|
||||
alias: Array.isArray(alias) ? alias.join(',') : '',
|
||||
};
|
||||
});
|
||||
setTableDataSource(dataSource);
|
||||
setDimValueMaps(dimensionValueSettingList);
|
||||
}, [dimensionValueSettingList]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await saveDimension({ dimValueMaps });
|
||||
onSubmit?.(dimValueMaps);
|
||||
};
|
||||
|
||||
const saveDimension = async (fieldsValue: any) => {
|
||||
if (!dimensionItem?.id) {
|
||||
return;
|
||||
}
|
||||
const queryParams = {
|
||||
domainId: selectDomainId,
|
||||
id: dimensionItem.id,
|
||||
...fieldsValue,
|
||||
};
|
||||
const { code, msg } = await updateDimension(queryParams);
|
||||
if (code === 200) {
|
||||
return;
|
||||
}
|
||||
message.error(msg);
|
||||
};
|
||||
|
||||
const renderFooter = () => {
|
||||
return (
|
||||
<>
|
||||
<Button onClick={onCancel}>取消</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
handleSubmit();
|
||||
}}
|
||||
>
|
||||
完成
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '技术名称',
|
||||
dataIndex: 'techName',
|
||||
width: 200,
|
||||
formItemProps: {
|
||||
fieldProps: {
|
||||
placeholder: '请填写技术名称',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
whitespace: true,
|
||||
message: '此项是必填项',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '业务名称',
|
||||
dataIndex: 'bizName',
|
||||
width: 200,
|
||||
fieldProps: {
|
||||
placeholder: '请填写业务名称',
|
||||
},
|
||||
formItemProps: {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
whitespace: true,
|
||||
message: '此项是必填项',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '别名',
|
||||
dataIndex: 'alias',
|
||||
fieldProps: {
|
||||
placeholder: '多个别名用英文逗号隔开',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Modal
|
||||
width={1000}
|
||||
destroyOnClose
|
||||
title="维度值设置"
|
||||
style={{ top: 48 }}
|
||||
maskClosable={false}
|
||||
open={open}
|
||||
footer={renderFooter()}
|
||||
onCancel={onCancel}
|
||||
>
|
||||
<CommonEditTable
|
||||
tableDataSource={tableDataSource}
|
||||
columnList={columns}
|
||||
onDataSourceChange={(tableData) => {
|
||||
const dimValueMaps = tableData.map((item: TableDataSource) => {
|
||||
return {
|
||||
...item,
|
||||
alias: item.alias ? `${item.alias}`.split(',') : [],
|
||||
};
|
||||
});
|
||||
|
||||
setDimValueMaps(dimValueMaps);
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ domainManger }: { domainManger: StateType }) => ({
|
||||
domainManger,
|
||||
}))(DimensionInfoModal);
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from './utils';
|
||||
import styles from '../style.less';
|
||||
import { ISemantic } from '../../data';
|
||||
import { ChatConfigType, TransType } from '../../enum';
|
||||
import { ChatConfigType, TransType, SemanticNodeType } from '../../enum';
|
||||
import TransTypeTag from '../TransTypeTag';
|
||||
|
||||
type Props = {
|
||||
@@ -99,7 +99,7 @@ const DefaultSettingForm: ForwardRefRenderFunction<any, Props> = (
|
||||
name,
|
||||
label: (
|
||||
<>
|
||||
<TransTypeTag type={TransType.DIMENSION} />
|
||||
<TransTypeTag type={SemanticNodeType.DIMENSION} />
|
||||
{name}
|
||||
</>
|
||||
),
|
||||
@@ -115,7 +115,7 @@ const DefaultSettingForm: ForwardRefRenderFunction<any, Props> = (
|
||||
name,
|
||||
label: (
|
||||
<>
|
||||
<TransTypeTag type={TransType.METRIC} />
|
||||
<TransTypeTag type={SemanticNodeType.METRIC} />
|
||||
{name}
|
||||
</>
|
||||
),
|
||||
@@ -198,30 +198,56 @@ const DefaultSettingForm: ForwardRefRenderFunction<any, Props> = (
|
||||
</FormItem>
|
||||
)}
|
||||
{chatConfigType === ChatConfigType.AGG && (
|
||||
<FormItem
|
||||
name="metricIds"
|
||||
label={
|
||||
<FormItemTitle
|
||||
title={'指标'}
|
||||
subTitle={'问答搜索结果选择中,如果没有指定指标,将会采用默认指标进行展示'}
|
||||
<>
|
||||
{/* <FormItem
|
||||
name="metricIds"
|
||||
label={
|
||||
<FormItemTitle
|
||||
title={'指标'}
|
||||
subTitle={'问答搜索结果选择中,如果没有指定指标,将会采用默认指标进行展示'}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
allowClear
|
||||
style={{ width: '100%' }}
|
||||
filterOption={(inputValue: string, item: any) => {
|
||||
const { label } = item;
|
||||
if (label.includes(inputValue)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}}
|
||||
placeholder="请选择展示指标信息"
|
||||
options={metricListOptions}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
allowClear
|
||||
style={{ width: '100%' }}
|
||||
filterOption={(inputValue: string, item: any) => {
|
||||
const { label } = item;
|
||||
if (label.includes(inputValue)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}}
|
||||
placeholder="请选择展示指标信息"
|
||||
options={metricListOptions}
|
||||
/>
|
||||
</FormItem>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
name="ratioMetricIds"
|
||||
label={
|
||||
<FormItemTitle
|
||||
title={'同环比指标'}
|
||||
subTitle={'问答搜索含有指定的指标,将会同时计算该指标最后一天的同环比'}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
allowClear
|
||||
style={{ width: '100%' }}
|
||||
filterOption={(inputValue: string, item: any) => {
|
||||
const { label } = item;
|
||||
if (label.includes(inputValue)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}}
|
||||
placeholder="请选择同环比指标"
|
||||
options={metricListOptions}
|
||||
/>
|
||||
</FormItem> */}
|
||||
</>
|
||||
)}
|
||||
|
||||
<FormItem
|
||||
|
||||
@@ -15,7 +15,7 @@ type Props = {
|
||||
settingSourceList: any[];
|
||||
onCancel: () => void;
|
||||
visible: boolean;
|
||||
onSubmit: (params?: any) => void;
|
||||
onSubmit: (params?: { isSilenceSubmit?: boolean }) => void;
|
||||
};
|
||||
|
||||
const dimensionConfig = {
|
||||
@@ -72,7 +72,8 @@ const DimensionAndMetricVisibleModal: React.FC<Props> = ({
|
||||
}
|
||||
}, [entityData, settingSourceList]);
|
||||
|
||||
const saveEntity = async () => {
|
||||
const saveEntity = async (submitData: any, isSilenceSubmit = false) => {
|
||||
const { selectedKeyList, knowledgeInfosMap } = submitData;
|
||||
const globalKnowledgeConfigFormFields = await formRef?.current?.getFormValidateFields?.();
|
||||
let globalKnowledgeConfig = entityData.globalKnowledgeConfig;
|
||||
if (globalKnowledgeConfigFormFields) {
|
||||
@@ -127,8 +128,10 @@ const DimensionAndMetricVisibleModal: React.FC<Props> = ({
|
||||
id,
|
||||
});
|
||||
if (code === 200) {
|
||||
onSubmit?.();
|
||||
message.success('保存成功');
|
||||
if (!isSilenceSubmit) {
|
||||
message.success('保存成功');
|
||||
}
|
||||
onSubmit?.({ isSilenceSubmit });
|
||||
return;
|
||||
}
|
||||
message.error(msg);
|
||||
@@ -145,7 +148,7 @@ const DimensionAndMetricVisibleModal: React.FC<Props> = ({
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
saveEntity();
|
||||
saveEntity({ selectedKeyList, knowledgeInfosMap });
|
||||
}}
|
||||
>
|
||||
完成
|
||||
@@ -160,8 +163,9 @@ const DimensionAndMetricVisibleModal: React.FC<Props> = ({
|
||||
key: 'visibleSetting',
|
||||
children: (
|
||||
<DimensionMetricVisibleTransfer
|
||||
onKnowledgeInfosMapChange={(knowledgeInfosMap) => {
|
||||
setKnowledgeInfosMap(knowledgeInfosMap);
|
||||
onKnowledgeInfosMapChange={(knowledgeInfos) => {
|
||||
setKnowledgeInfosMap(knowledgeInfos);
|
||||
saveEntity({ selectedKeyList, knowledgeInfosMap: knowledgeInfos }, true);
|
||||
}}
|
||||
knowledgeInfosMap={knowledgeInfosMap}
|
||||
titles={settingTypeConfig.titles}
|
||||
@@ -169,6 +173,7 @@ const DimensionAndMetricVisibleModal: React.FC<Props> = ({
|
||||
targetList={selectedKeyList}
|
||||
onChange={(newTargetKeys) => {
|
||||
handleTransferChange(newTargetKeys);
|
||||
saveEntity({ selectedKeyList: newTargetKeys, knowledgeInfosMap }, true);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
@@ -177,10 +182,12 @@ const DimensionAndMetricVisibleModal: React.FC<Props> = ({
|
||||
label: '全局维度值过滤',
|
||||
key: 'dimensionValueFilter',
|
||||
children: (
|
||||
<DimensionValueSettingForm
|
||||
initialValues={globalKnowledgeConfigInitialValues}
|
||||
ref={formRef}
|
||||
/>
|
||||
<div style={{ margin: '0 auto', width: '975px' }}>
|
||||
<DimensionValueSettingForm
|
||||
initialValues={globalKnowledgeConfigInitialValues}
|
||||
ref={formRef}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -74,9 +74,11 @@ const DimensionMetricVisibleForm: ForwardRefRenderFunction<any, Props> = ({
|
||||
onCancel={() => {
|
||||
setDimensionModalVisible(false);
|
||||
}}
|
||||
onSubmit={() => {
|
||||
onSubmit={(params) => {
|
||||
onSubmit?.();
|
||||
setDimensionModalVisible(false);
|
||||
if (!params?.isSilenceSubmit) {
|
||||
setDimensionModalVisible(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -5,8 +5,7 @@ import { Form, Input } from 'antd';
|
||||
import { formLayout } from '@/components/FormHelper/utils';
|
||||
import { isString } from 'lodash';
|
||||
import styles from '../style.less';
|
||||
import CommonEditList from '../../components/CommonEditList/index';
|
||||
import SqlEditor from '@/components/SqlEditor';
|
||||
import CommonEditList from '../../components/CommonEditList';
|
||||
type Props = {
|
||||
initialValues: any;
|
||||
onSubmit?: () => void;
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { useEffect, useState, forwardRef, useImperativeHandle } from 'react';
|
||||
import type { ForwardRefRenderFunction } from 'react';
|
||||
import { message, Form, Input, Select, Button } from 'antd';
|
||||
import { addDomainExtend, editDomainExtend } from '../../service';
|
||||
import type { ISemantic, IChatConfig } from '../../data';
|
||||
import { updateDomain } from '../../service';
|
||||
import type { ISemantic } from '../../data';
|
||||
import { formLayout } from '@/components/FormHelper/utils';
|
||||
import { formatRichEntityDataListToIds } from './utils';
|
||||
import styles from '../style.less';
|
||||
|
||||
type Props = {
|
||||
entityData: IChatConfig.IChatRichConfig;
|
||||
entityData?: { id: number; names: string[] };
|
||||
dimensionList: ISemantic.IDimensionList;
|
||||
domainId: number;
|
||||
onSubmit: () => void;
|
||||
@@ -21,22 +20,20 @@ const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
|
||||
ref,
|
||||
) => {
|
||||
const [form] = Form.useForm();
|
||||
const formatEntityData = formatRichEntityDataListToIds(entityData);
|
||||
const [dimensionListOptions, setDimensionListOptions] = useState<any>([]);
|
||||
|
||||
const getFormValidateFields = async () => {
|
||||
return await form.validateFields();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
form.resetFields();
|
||||
if (!entityData?.entity) {
|
||||
if (!entityData) {
|
||||
return;
|
||||
}
|
||||
|
||||
form.setFieldsValue({
|
||||
...formatEntityData.entity,
|
||||
id: formatEntityData.id,
|
||||
...entityData,
|
||||
name: entityData.names.join(','),
|
||||
});
|
||||
}, [entityData]);
|
||||
|
||||
@@ -56,20 +53,14 @@ const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
|
||||
|
||||
const saveEntity = async () => {
|
||||
const values = await form.validateFields();
|
||||
const { id, name } = values;
|
||||
let saveDomainExtendQuery = addDomainExtend;
|
||||
if (id) {
|
||||
saveDomainExtendQuery = editDomainExtend;
|
||||
}
|
||||
const { code, msg, data } = await saveDomainExtendQuery({
|
||||
chatDetailConfig: {
|
||||
...formatEntityData,
|
||||
entity: {
|
||||
...values,
|
||||
names: name.split(','),
|
||||
},
|
||||
const { name } = values;
|
||||
|
||||
const { code, msg, data } = await updateDomain({
|
||||
entity: {
|
||||
...values,
|
||||
names: name.split(','),
|
||||
},
|
||||
id,
|
||||
id: domainId,
|
||||
domainId,
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { connect } from 'umi';
|
||||
import type { StateType } from '../../model';
|
||||
import { getDomainExtendDetailConfig } from '../../service';
|
||||
import ProCard from '@ant-design/pro-card';
|
||||
import EntityCreateForm from './EntityCreateForm';
|
||||
|
||||
import DefaultSettingForm from './DefaultSettingForm';
|
||||
import type { IChatConfig } from '../../data';
|
||||
import DimensionMetricVisibleForm from './DimensionMetricVisibleForm';
|
||||
@@ -26,8 +26,6 @@ const EntitySection: React.FC<Props> = ({
|
||||
|
||||
const [entityData, setentityData] = useState<IChatConfig.IChatRichConfig>();
|
||||
|
||||
const entityCreateRef = useRef<any>({});
|
||||
|
||||
const queryThemeListData: any = async () => {
|
||||
const { code, data } = await getDomainExtendDetailConfig({
|
||||
domainId: selectDomainId,
|
||||
@@ -58,25 +56,6 @@ const EntitySection: React.FC<Props> = ({
|
||||
return (
|
||||
<div style={{ width: 800, margin: '0 auto' }}>
|
||||
<Space direction="vertical" style={{ width: '100%' }} size={20}>
|
||||
{chatConfigType === 'detail' && entityData && (
|
||||
<ProCard title="实体" bordered>
|
||||
<EntityCreateForm
|
||||
ref={entityCreateRef}
|
||||
domainId={Number(selectDomainId)}
|
||||
entityData={entityData}
|
||||
dimensionList={dimensionList.filter((item) => {
|
||||
const blackDimensionList = entityData?.visibility?.blackDimIdList;
|
||||
if (Array.isArray(blackDimensionList)) {
|
||||
return !blackDimensionList.includes(item.id);
|
||||
}
|
||||
return false;
|
||||
})}
|
||||
onSubmit={() => {
|
||||
queryThemeListData();
|
||||
}}
|
||||
/>
|
||||
</ProCard>
|
||||
)}
|
||||
<ProCard bordered title="问答可见">
|
||||
<DimensionMetricVisibleForm
|
||||
chatConfigKey={
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { message, Space } from 'antd';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import type { Dispatch } from 'umi';
|
||||
import { connect } from 'umi';
|
||||
import type { StateType } from '../../model';
|
||||
import { getDomainDetail } from '../../service';
|
||||
import ProCard from '@ant-design/pro-card';
|
||||
import EntityCreateForm from './EntityCreateForm';
|
||||
import type { IChatConfig } from '../../data';
|
||||
|
||||
type Props = {
|
||||
dispatch: Dispatch;
|
||||
domainManger: StateType;
|
||||
};
|
||||
|
||||
const EntitySettingSection: React.FC<Props> = ({ domainManger }) => {
|
||||
const { selectDomainId, dimensionList, metricList } = domainManger;
|
||||
|
||||
const [entityData, setEntityData] = useState<IChatConfig.IChatRichConfig>();
|
||||
|
||||
const entityCreateRef = useRef<any>({});
|
||||
|
||||
const queryDomainData: any = async () => {
|
||||
const { code, data } = await getDomainDetail({
|
||||
domainId: selectDomainId,
|
||||
});
|
||||
|
||||
if (code === 200) {
|
||||
const { entity } = data;
|
||||
|
||||
setEntityData(entity);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
message.error('获取问答设置信息失败');
|
||||
};
|
||||
|
||||
const initPage = async () => {
|
||||
queryDomainData();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
initPage();
|
||||
}, [selectDomainId]);
|
||||
|
||||
return (
|
||||
<div style={{ width: 800, margin: '0 auto' }}>
|
||||
<Space direction="vertical" style={{ width: '100%' }} size={20}>
|
||||
{
|
||||
<ProCard title="实体" bordered>
|
||||
<EntityCreateForm
|
||||
ref={entityCreateRef}
|
||||
domainId={Number(selectDomainId)}
|
||||
entityData={entityData}
|
||||
dimensionList={dimensionList}
|
||||
onSubmit={() => {
|
||||
queryDomainData();
|
||||
}}
|
||||
/>
|
||||
</ProCard>
|
||||
}
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default connect(({ domainManger }: { domainManger: StateType }) => ({
|
||||
domainManger,
|
||||
}))(EntitySettingSection);
|
||||
@@ -0,0 +1,87 @@
|
||||
import { message } from 'antd';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { connect } from 'umi';
|
||||
import type { StateType } from '../../model';
|
||||
import { getDomainExtendConfig, addDomainExtend, editDomainExtend } from '../../service';
|
||||
import ProCard from '@ant-design/pro-card';
|
||||
|
||||
import TextAreaCommonEditList from '../../components/CommonEditList/TextArea';
|
||||
|
||||
type Props = {
|
||||
domainManger: StateType;
|
||||
};
|
||||
|
||||
const RecommendedQuestionsSection: React.FC<Props> = ({ domainManger }) => {
|
||||
const { selectDomainId } = domainManger;
|
||||
|
||||
const [questionData, setQuestionData] = useState<string[]>([]);
|
||||
const [currentRecordId, setCurrentRecordId] = useState<number>(0);
|
||||
|
||||
const queryThemeListData: any = async () => {
|
||||
const { code, data } = await getDomainExtendConfig({
|
||||
domainId: selectDomainId,
|
||||
});
|
||||
|
||||
if (code === 200) {
|
||||
const target = data?.[0] || {};
|
||||
if (Array.isArray(target.recommendedQuestions)) {
|
||||
setQuestionData(
|
||||
target.recommendedQuestions.map((item: { question: string }) => {
|
||||
return item.question;
|
||||
}),
|
||||
);
|
||||
setCurrentRecordId(target.id || 0);
|
||||
} else {
|
||||
setQuestionData([]);
|
||||
setCurrentRecordId(0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
message.error('获取问答设置信息失败');
|
||||
};
|
||||
|
||||
const saveEntity = async (list: string[]) => {
|
||||
let saveDomainExtendQuery = addDomainExtend;
|
||||
if (currentRecordId) {
|
||||
saveDomainExtendQuery = editDomainExtend;
|
||||
}
|
||||
const { code, msg } = await saveDomainExtendQuery({
|
||||
recommendedQuestions: list.map((question: string) => {
|
||||
return { question };
|
||||
}),
|
||||
id: currentRecordId,
|
||||
domainId: selectDomainId,
|
||||
});
|
||||
|
||||
if (code === 200) {
|
||||
return;
|
||||
}
|
||||
message.error(msg);
|
||||
};
|
||||
|
||||
const initPage = async () => {
|
||||
queryThemeListData();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
initPage();
|
||||
}, [selectDomainId]);
|
||||
|
||||
return (
|
||||
<div style={{ width: 800, margin: '0 auto' }}>
|
||||
<ProCard bordered title="问题推荐列表">
|
||||
<TextAreaCommonEditList
|
||||
value={questionData}
|
||||
onChange={(list) => {
|
||||
saveEntity(list);
|
||||
setQuestionData(list);
|
||||
}}
|
||||
/>
|
||||
</ProCard>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default connect(({ domainManger }: { domainManger: StateType }) => ({
|
||||
domainManger,
|
||||
}))(RecommendedQuestionsSection);
|
||||
@@ -18,8 +18,9 @@ export const formatRichEntityDataListToIds = (
|
||||
const detailData: {
|
||||
dimensionIds: number[];
|
||||
metricIds: number[];
|
||||
} = { dimensionIds: [], metricIds: [] };
|
||||
const { dimensions, metrics } = chatDefaultConfig || {};
|
||||
ratioMetricIds: number[];
|
||||
} = { dimensionIds: [], metricIds: [], ratioMetricIds: [] };
|
||||
const { dimensions, metrics, ratioMetrics } = chatDefaultConfig || {};
|
||||
if (Array.isArray(dimensions)) {
|
||||
detailData.dimensionIds = dimensions.map((item: ISemantic.IDimensionItem) => {
|
||||
return item.id;
|
||||
@@ -30,6 +31,12 @@ export const formatRichEntityDataListToIds = (
|
||||
return item.id;
|
||||
});
|
||||
}
|
||||
if (Array.isArray(ratioMetrics)) {
|
||||
detailData.ratioMetricIds = ratioMetrics.map((item: ISemantic.IMetricItem) => {
|
||||
return item.id;
|
||||
});
|
||||
}
|
||||
|
||||
let entitySetting = {};
|
||||
if (entity) {
|
||||
const entityItem = entity.dimItem;
|
||||
|
||||
@@ -199,15 +199,15 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
||||
</FormItem>
|
||||
<FormItem
|
||||
name="name"
|
||||
label="指标中文名"
|
||||
rules={[{ required: true, message: '请输入指标中文名' }]}
|
||||
label="指标名称"
|
||||
rules={[{ required: true, message: '请输入指标名称' }]}
|
||||
>
|
||||
<Input placeholder="名称不可重复" />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
name="bizName"
|
||||
label="指标英文名"
|
||||
rules={[{ required: true, message: '请输入指标英文名' }]}
|
||||
label="字段名称"
|
||||
rules={[{ required: true, message: '请输入字段名称' }]}
|
||||
>
|
||||
<Input placeholder="名称不可重复" disabled={isEdit} />
|
||||
</FormItem>
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import { Tag } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
import { TransType } from '../enum';
|
||||
import { SemanticNodeType } from '../enum';
|
||||
|
||||
type Props = {
|
||||
type: TransType;
|
||||
type: SemanticNodeType;
|
||||
};
|
||||
|
||||
const TransTypeTag: React.FC<Props> = ({ type }) => {
|
||||
return (
|
||||
<>
|
||||
{type === TransType.DIMENSION ? (
|
||||
{type === SemanticNodeType.DIMENSION ? (
|
||||
<Tag color="blue">{'维度'}</Tag>
|
||||
) : type === 'metric' ? (
|
||||
) : type === SemanticNodeType.METRIC ? (
|
||||
<Tag color="orange">{'指标'}</Tag>
|
||||
) : type === SemanticNodeType.DATASOURCE ? (
|
||||
<Tag color="green">{'数据源'}</Tag>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
.projectManger {
|
||||
width: 100%;
|
||||
min-height: calc(100vh - 48px);
|
||||
background: #f8f9fb;
|
||||
// background: #f8f9fb;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
|
||||
|
||||
@@ -37,8 +38,17 @@
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 0 20px;
|
||||
line-height: 28px;
|
||||
:global {
|
||||
.ant-tabs-tab-btn {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
.ant-tabs-nav-wrap {
|
||||
padding: 0 20px;
|
||||
}
|
||||
.ant-tabs-nav {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mainTip {
|
||||
@@ -50,8 +60,9 @@
|
||||
padding: 0 !important;
|
||||
}
|
||||
.ant-tabs-content-holder {
|
||||
overflow: scroll;
|
||||
height: calc(100vh - 192px);
|
||||
margin-top: 20px;
|
||||
// overflow: scroll;
|
||||
// height: calc(100vh - 175px);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@ export type UserName = string;
|
||||
|
||||
export type SensitiveLevel = 0 | 1 | 2 | null;
|
||||
|
||||
export type RefreshGraphData = (graphRootData: TreeGraphData) => void;
|
||||
// export type RefreshGraphData = (graphRootData: TreeGraphData) => void;
|
||||
|
||||
export type ToolBarSearchCallBack = (text: string) => void;
|
||||
|
||||
export declare namespace IDataSource {
|
||||
interface IIdentifiersItem {
|
||||
@@ -113,8 +115,14 @@ export declare namespace ISemantic {
|
||||
semanticType: string;
|
||||
alias: string;
|
||||
useCnt: number;
|
||||
dimValueMaps: IDimensionValueSettingItem[];
|
||||
}
|
||||
|
||||
interface IDimensionValueSettingItem {
|
||||
techName: string;
|
||||
bizName: string;
|
||||
alias?: string[];
|
||||
}
|
||||
interface IMeasure {
|
||||
name: string;
|
||||
agg?: string;
|
||||
@@ -156,6 +164,14 @@ export declare namespace ISemantic {
|
||||
|
||||
type IDimensionList = IDimensionItem[];
|
||||
type IMetricList = IMetricItem[];
|
||||
|
||||
interface IDomainSchemaRelaItem {
|
||||
domainId: number;
|
||||
dimensions: IDimensionList;
|
||||
metrics: IMetricList;
|
||||
datasource: IDataSourceItem;
|
||||
}
|
||||
type IDomainSchemaRelaList = IDomainSchemaRelaItem[];
|
||||
}
|
||||
|
||||
export declare namespace IChatConfig {
|
||||
@@ -218,6 +234,7 @@ export declare namespace IChatConfig {
|
||||
chatDefaultConfig: {
|
||||
dimensions: ISemantic.IDimensionList;
|
||||
metrics: ISemantic.IMetricList;
|
||||
ratioMetrics: ISemantic.IMetricList;
|
||||
unit: number;
|
||||
period: string;
|
||||
};
|
||||
|
||||
@@ -13,3 +13,8 @@ export enum SemanticNodeType {
|
||||
DIMENSION = 'dimension',
|
||||
METRIC = 'metric',
|
||||
}
|
||||
|
||||
export enum MetricTypeWording {
|
||||
ATOMIC = '原子指标',
|
||||
DERIVED = '衍生指标',
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ export function deleteDomain(id: any): Promise<any> {
|
||||
});
|
||||
}
|
||||
|
||||
export function getGroupAuthInfo(id: string): Promise<any> {
|
||||
export function getGroupAuthInfo(id: number): Promise<any> {
|
||||
return request(`${process.env.AUTH_API_BASE_URL}queryGroup`, {
|
||||
method: 'GET',
|
||||
params: {
|
||||
|
||||
Reference in New Issue
Block a user