semantic-fe visual modeling pr (#21)

* [improvement][semantic-fe] Added an editing component to set filtering rules for Q&A. Now, the SQL editor will be accompanied by a list for display and control, to resolve ambiguity when using comma-separated values.
[improvement][semantic-fe] Improved validation logic and prompt copywriting for data source/dimension/metric editing and creation.
[improvement][semantic-fe] Improved user experience for visual modeling. Now, when using the legend to control the display/hide of data sources and their associated metric dimensions, the canvas will be re-layout based on the activated data source in the legend.

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

---------

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

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View 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;