(feature)(chat-sdk) trend chart supports switch between line and bar,add second drill-down dimensions,chang queryMode from ENTITY to TAG (#422)

This commit is contained in:
williamhliu
2023-11-25 11:13:10 +08:00
committed by GitHub
parent fe2a424718
commit 0534053ff9
17 changed files with 251 additions and 127 deletions

View File

@@ -90,8 +90,8 @@
} }
.ant-select-selection-placeholder { .ant-select-selection-placeholder {
padding-left: 2px;
line-height: 40px; line-height: 40px;
margin-bottom: 30px;
} }
} }

View File

@@ -127,7 +127,6 @@ const FilterItem: React.FC<Props> = ({
}; };
const onDateChange = (_: any, date: string) => { const onDateChange = (_: any, date: string) => {
console.log('onDateChange', date);
const newFilters = filters.map((item, indexValue) => { const newFilters = filters.map((item, indexValue) => {
if (item.bizName === filter.bizName && index === indexValue) { if (item.bizName === filter.bizName && index === indexValue) {
item.value = date; item.value = date;

View File

@@ -126,7 +126,7 @@ const ParseTip: React.FC<Props> = ({
const { type: agentType, name: agentName } = properties || {}; const { type: agentType, name: agentName } = properties || {};
const fields = const fields =
queryMode === 'ENTITY_DETAIL' ? dimensionItems?.concat(metrics || []) : dimensionItems; queryMode === 'TAG_DETAIL' ? dimensionItems?.concat(metrics || []) : dimensionItems;
return ( return (
<div className={`${prefixCls}-tip-content`}> <div className={`${prefixCls}-tip-content`}>
@@ -162,9 +162,7 @@ const ParseTip: React.FC<Props> = ({
</div> </div>
</div> </div>
)} )}
{['METRIC_GROUPBY', 'METRIC_ORDERBY', 'ENTITY_DETAIL', 'LLM_S2SQL'].includes( {['METRIC_GROUPBY', 'METRIC_ORDERBY', 'TAG_DETAIL', 'LLM_S2SQL'].includes(queryMode!) &&
queryMode!
) &&
fields && fields &&
fields.length > 0 && ( fields.length > 0 && (
<div className={`${prefixCls}-tip-item`}> <div className={`${prefixCls}-tip-item`}>
@@ -173,7 +171,7 @@ const ParseTip: React.FC<Props> = ({
? nativeQuery ? nativeQuery
? '查询字段' ? '查询字段'
: '下钻维度' : '下钻维度'
: queryMode === 'ENTITY_DETAIL' : queryMode === 'TAG_DETAIL'
? '查询字段' ? '查询字段'
: '下钻维度'} : '下钻维度'}
@@ -187,7 +185,7 @@ const ParseTip: React.FC<Props> = ({
</div> </div>
</div> </div>
)} )}
{queryMode !== 'ENTITY_ID' && {queryMode !== 'TAG_ID' &&
!dimensions?.some(item => item.bizName?.includes('_id')) && !dimensions?.some(item => item.bizName?.includes('_id')) &&
entityDimensions entityDimensions
?.filter(dimension => dimension.value != null) ?.filter(dimension => dimension.value != null)

View File

@@ -57,6 +57,10 @@
align-items: center; align-items: center;
column-gap: 13px; column-gap: 13px;
margin-left: -10px; margin-left: -10px;
&.mobile {
flex-wrap: wrap;
}
} }
&-content-option { &-content-option {
@@ -358,6 +362,20 @@
overflow-wrap: break-word; overflow-wrap: break-word;
user-select: text; user-select: text;
} }
&-toggle-expand-btn {
margin-left: 4px;
color: var(--text-color-fourth);
font-size: 12px;
cursor: pointer;
}
&-step-item {
position: relative;
margin: 2px 0 2px 7px;
padding: 2px 0 8px 18px;
border-left: 1px solid var(--green);
overflow: auto;
}
} }
.@{filter-item-prefix-cls} { .@{filter-item-prefix-cls} {

View File

@@ -45,7 +45,7 @@ const Message: React.FC<Props> = ({
e.stopPropagation(); e.stopPropagation();
}} }}
> >
{(queryMode === 'METRIC_ENTITY' || queryMode === 'ENTITY_DETAIL') && {(queryMode === 'METRIC_TAG' || queryMode === 'TAG_DETAIL') &&
entityInfoList.length > 0 && ( entityInfoList.length > 0 && (
<div className={`${prefixCls}-info-bar`}> <div className={`${prefixCls}-info-bar`}>
<div className={`${prefixCls}-main-entity-info`}> <div className={`${prefixCls}-main-entity-info`}>
@@ -64,7 +64,7 @@ const Message: React.FC<Props> = ({
</div> </div>
</div> </div>
)} )}
{queryMode === 'ENTITY_LIST_FILTER' && ( {queryMode === 'TAG_LIST_FILTER' && (
<div className={`${prefixCls}-info-bar`}> <div className={`${prefixCls}-info-bar`}>
<div className={`${prefixCls}-main-entity-info`}> <div className={`${prefixCls}-main-entity-info`}>
<div className={`${prefixCls}-info-item`}> <div className={`${prefixCls}-info-item`}>

View File

@@ -32,6 +32,7 @@
} }
&-filter-item { &-filter-item {
margin-right: 4px;
display: flex; display: flex;
align-items: center; align-items: center;
} }
@@ -52,11 +53,16 @@
margin-top: 2px; margin-top: 2px;
} }
&-query-tootip {
margin-left: 5px;
}
&-indicator { &-indicator {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
justify-content: flex-start; justify-content: flex-start;
margin: 12px 0;
} }
&-date-range { &-date-range {

View File

@@ -23,6 +23,7 @@ type Props = {
resultList: any[]; resultList: any[];
triggerResize?: boolean; triggerResize?: boolean;
onApplyAuth?: (model: string) => void; onApplyAuth?: (model: string) => void;
chartType?: string;
}; };
const MetricTrendChart: React.FC<Props> = ({ const MetricTrendChart: React.FC<Props> = ({
@@ -33,6 +34,7 @@ const MetricTrendChart: React.FC<Props> = ({
resultList, resultList,
triggerResize, triggerResize,
onApplyAuth, onApplyAuth,
chartType,
}) => { }) => {
const chartRef = useRef<any>(); const chartRef = useRef<any>();
const [instance, setInstance] = useState<ECharts>(); const [instance, setInstance] = useState<ECharts>();
@@ -173,7 +175,7 @@ const MetricTrendChart: React.FC<Props> = ({
series: sortedGroupKeys.slice(0, 20).map((category, index) => { series: sortedGroupKeys.slice(0, 20).map((category, index) => {
const data = groupData[category]; const data = groupData[category];
return { return {
type: 'line', type: chartType,
name: categoryColumnName ? category : metricField.name, name: categoryColumnName ? category : metricField.name,
symbol: 'circle', symbol: 'circle',
showSymbol: data.length === 1, showSymbol: data.length === 1,
@@ -197,7 +199,7 @@ const MetricTrendChart: React.FC<Props> = ({
if (metricField.authorized) { if (metricField.authorized) {
renderChart(); renderChart();
} }
}, [resultList, metricField]); }, [resultList, metricField, chartType]);
useEffect(() => { useEffect(() => {
if (triggerResize && instance) { if (triggerResize && instance) {

View File

@@ -12,6 +12,7 @@ type Props = {
metricFields: ColumnType[]; metricFields: ColumnType[];
resultList: any[]; resultList: any[];
triggerResize?: boolean; triggerResize?: boolean;
chartType?: string;
}; };
const MultiMetricsTrendChart: React.FC<Props> = ({ const MultiMetricsTrendChart: React.FC<Props> = ({
@@ -19,6 +20,7 @@ const MultiMetricsTrendChart: React.FC<Props> = ({
metricFields, metricFields,
resultList, resultList,
triggerResize, triggerResize,
chartType,
}) => { }) => {
const chartRef = useRef<any>(); const chartRef = useRef<any>();
const [instance, setInstance] = useState<ECharts>(); const [instance, setInstance] = useState<ECharts>();
@@ -110,7 +112,7 @@ const MultiMetricsTrendChart: React.FC<Props> = ({
}, },
series: metricFields.map((metricField, index) => { series: metricFields.map((metricField, index) => {
return { return {
type: 'line', type: chartType,
name: metricField.name, name: metricField.name,
symbol: 'circle', symbol: 'circle',
showSymbol: resultList.length === 1, showSymbol: resultList.length === 1,
@@ -132,7 +134,7 @@ const MultiMetricsTrendChart: React.FC<Props> = ({
useEffect(() => { useEffect(() => {
renderChart(); renderChart();
}, [resultList]); }, [resultList, chartType]);
useEffect(() => { useEffect(() => {
if (triggerResize && instance) { if (triggerResize && instance) {

View File

@@ -2,11 +2,23 @@ import { CLS_PREFIX } from '../../../common/constants';
import { DrillDownDimensionType, FieldType, MsgDataType } from '../../../common/type'; import { DrillDownDimensionType, FieldType, MsgDataType } from '../../../common/type';
import { isMobile } from '../../../utils/utils'; import { isMobile } from '../../../utils/utils';
import MetricTrendChart from './MetricTrendChart'; import MetricTrendChart from './MetricTrendChart';
import { Spin } from 'antd'; import { Spin, Select } from 'antd';
import Table from '../Table'; import Table from '../Table';
import MetricInfo from './MetricInfo'; import MetricInfo from './MetricInfo';
import DateOptions from '../DateOptions'; import DateOptions from '../DateOptions';
import MultiMetricsTrendChart from './MultiMetricsTrendChart'; import MultiMetricsTrendChart from './MultiMetricsTrendChart';
import { useState } from 'react';
const metricChartSelectOptions = [
{
value: 'line',
label: '折线图',
},
{
value: 'bar',
label: '柱状图',
},
];
type Props = { type Props = {
data: MsgDataType; data: MsgDataType;
@@ -32,6 +44,7 @@ const MetricTrend: React.FC<Props> = ({
onSelectDateOption, onSelectDateOption,
}) => { }) => {
const { queryColumns, queryResults, aggregateInfo, entityInfo, chatContext } = data; const { queryColumns, queryResults, aggregateInfo, entityInfo, chatContext } = data;
const [chartType, setChartType] = useState('line');
const dateField: any = queryColumns?.find( const dateField: any = queryColumns?.find(
(column: any) => column.showType === 'DATE' || column.type === 'DATE' (column: any) => column.showType === 'DATE' || column.type === 'DATE'
@@ -69,11 +82,21 @@ const MetricTrend: React.FC<Props> = ({
drillDownDimension === undefined && ( drillDownDimension === undefined && (
<MetricInfo aggregateInfo={aggregateInfo} currentMetricField={currentMetricField} /> <MetricInfo aggregateInfo={aggregateInfo} currentMetricField={currentMetricField} />
)} )}
<div className={`${prefixCls}-select-options`}>
<DateOptions <DateOptions
chatContext={chatContext} chatContext={chatContext}
currentDateOption={currentDateOption} currentDateOption={currentDateOption}
onSelectDateOption={onSelectDateOption} onSelectDateOption={onSelectDateOption}
/> />
<div>
<Select
defaultValue="line"
bordered={false}
options={metricChartSelectOptions}
onChange={(value: string) => setChartType(value)}
/>
</div>
</div>
{queryResults?.length === 1 || chartIndex % 2 === 1 ? ( {queryResults?.length === 1 || chartIndex % 2 === 1 ? (
<Table data={{ ...data, queryResults }} onApplyAuth={onApplyAuth} /> <Table data={{ ...data, queryResults }} onApplyAuth={onApplyAuth} />
) : metricFields.length > 1 ? ( ) : metricFields.length > 1 ? (
@@ -82,6 +105,7 @@ const MetricTrend: React.FC<Props> = ({
metricFields={metricFields} metricFields={metricFields}
resultList={queryResults} resultList={queryResults}
triggerResize={triggerResize} triggerResize={triggerResize}
chartType={chartType}
/> />
) : ( ) : (
<MetricTrendChart <MetricTrendChart
@@ -92,6 +116,7 @@ const MetricTrend: React.FC<Props> = ({
resultList={queryResults} resultList={queryResults}
triggerResize={triggerResize} triggerResize={triggerResize}
onApplyAuth={onApplyAuth} onApplyAuth={onApplyAuth}
chartType={chartType}
/> />
)} )}
</div> </div>

View File

@@ -56,6 +56,11 @@
row-gap: 12px; row-gap: 12px;
} }
&-select-options {
display: flex;
justify-content: space-between;
}
&-indicator { &-indicator {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@@ -1,4 +1,4 @@
import { formatByDecimalPlaces, getFormattedValue, isMobile } from '../../../utils/utils'; import { formatByDecimalPlaces, getFormattedValue } from '../../../utils/utils';
import { Table as AntTable } from 'antd'; import { Table as AntTable } from 'antd';
import { MsgDataType } from '../../../common/type'; import { MsgDataType } from '../../../common/type';
import { CLS_PREFIX } from '../../../common/constants'; import { CLS_PREFIX } from '../../../common/constants';
@@ -9,10 +9,11 @@ import moment from 'moment';
type Props = { type Props = {
data: MsgDataType; data: MsgDataType;
size?: SizeType; size?: SizeType;
loading?: boolean;
onApplyAuth?: (model: string) => void; onApplyAuth?: (model: string) => void;
}; };
const Table: React.FC<Props> = ({ data, size, onApplyAuth }) => { const Table: React.FC<Props> = ({ data, size, loading, onApplyAuth }) => {
const { entityInfo, queryColumns, queryResults } = data; const { entityInfo, queryColumns, queryResults } = data;
const prefixCls = `${CLS_PREFIX}-table`; const prefixCls = `${CLS_PREFIX}-table`;
@@ -70,19 +71,14 @@ const Table: React.FC<Props> = ({ data, size, onApplyAuth }) => {
<div className={prefixCls}> <div className={prefixCls}>
<AntTable <AntTable
pagination={ pagination={
queryResults.length <= 10 queryResults.length <= 10 ? false : { defaultPageSize: 10, position: ['bottomCenter'] }
? false
: {
defaultPageSize: 10,
position: ['bottomCenter'],
size: isMobile ? 'small' : 'default',
}
} }
columns={tableColumns} columns={tableColumns}
dataSource={dataSource} dataSource={dataSource}
style={{ width: '100%', overflowX: 'auto', overflowY: 'hidden' }} style={{ width: '100%', overflowX: 'auto', overflowY: 'hidden' }}
rowClassName={getRowClassName} rowClassName={getRowClassName}
size={size} size={size}
loading={loading}
/> />
</div> </div>
); );

View File

@@ -27,6 +27,8 @@ const ChatMsg: React.FC<Props> = ({ queryId, data, chartIndex, triggerResize })
const [referenceColumn, setReferenceColumn] = useState<ColumnType>(); const [referenceColumn, setReferenceColumn] = useState<ColumnType>();
const [dataSource, setDataSource] = useState<any[]>(queryResults); const [dataSource, setDataSource] = useState<any[]>(queryResults);
const [drillDownDimension, setDrillDownDimension] = useState<DrillDownDimensionType>(); const [drillDownDimension, setDrillDownDimension] = useState<DrillDownDimensionType>();
const [secondDrillDownDimension, setSecondDrillDownDimension] =
useState<DrillDownDimensionType>();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [defaultMetricField, setDefaultMetricField] = useState<FieldType>(); const [defaultMetricField, setDefaultMetricField] = useState<FieldType>();
const [activeMetricField, setActiveMetricField] = useState<FieldType>(); const [activeMetricField, setActiveMetricField] = useState<FieldType>();
@@ -48,6 +50,8 @@ const ChatMsg: React.FC<Props> = ({ queryId, data, chartIndex, triggerResize })
setActiveMetricField(chatContext?.metrics?.[0]); setActiveMetricField(chatContext?.metrics?.[0]);
setDateModeValue(chatContext?.dateInfo?.dateMode); setDateModeValue(chatContext?.dateInfo?.dateMode);
setCurrentDateOption(chatContext?.dateInfo?.unit); setCurrentDateOption(chatContext?.dateInfo?.unit);
setDrillDownDimension(undefined);
setSecondDrillDownDimension(undefined);
}, [data]); }, [data]);
if (!queryColumns || !queryResults || !columns) { if (!queryColumns || !queryResults || !columns) {
@@ -75,7 +79,7 @@ const ChatMsg: React.FC<Props> = ({ queryId, data, chartIndex, triggerResize })
!isText && !isText &&
!isMetricCard && !isMetricCard &&
(categoryField.length > 1 || (categoryField.length > 1 ||
queryMode === 'ENTITY_DETAIL' || queryMode === 'TAG_DETAIL' ||
queryMode === 'ENTITY_DIMENSION' || queryMode === 'ENTITY_DIMENSION' ||
(categoryField.length === 1 && metricFields.length === 0)); (categoryField.length === 1 && metricFields.length === 0));
@@ -92,7 +96,12 @@ const ChatMsg: React.FC<Props> = ({ queryId, data, chartIndex, triggerResize })
); );
} }
if (isTable) { if (isTable) {
return <Table data={{ ...data, queryColumns: columns, queryResults: dataSource }} />; return (
<Table
data={{ ...data, queryColumns: columns, queryResults: dataSource }}
loading={loading}
/>
);
} }
if ( if (
dateField && dateField &&
@@ -129,15 +138,21 @@ const ChatMsg: React.FC<Props> = ({ queryId, data, chartIndex, triggerResize })
/> />
); );
} }
return <Table data={{ ...data, queryColumns: columns, queryResults: dataSource }} />; return (
<Table
data={{ ...data, queryColumns: columns, queryResults: dataSource }}
loading={loading}
/>
);
}; };
const onLoadData = async (value: any) => { const onLoadData = async (value: any, extraFilter?: any) => {
setLoading(true); setLoading(true);
const res: any = await queryData({ const res: any = await queryData({
...chatContext,
...value,
queryId, queryId,
parseId: chatContext.id, parseId: chatContext.id,
...value,
}); });
setLoading(false); setLoading(false);
if (res.code === 200) { if (res.code === 200) {
@@ -146,7 +161,8 @@ const ChatMsg: React.FC<Props> = ({ queryId, data, chartIndex, triggerResize })
} }
}; };
const onSelectDimension = (dimension?: DrillDownDimensionType) => { const onSelectDimension = async (dimension?: DrillDownDimensionType) => {
setLoading(true);
setDrillDownDimension(dimension); setDrillDownDimension(dimension);
onLoadData({ onLoadData({
dateInfo: { dateInfo: {
@@ -161,6 +177,23 @@ const ChatMsg: React.FC<Props> = ({ queryId, data, chartIndex, triggerResize })
}); });
}; };
const onSelectSecondDimension = (dimension?: DrillDownDimensionType) => {
setSecondDrillDownDimension(dimension);
onLoadData({
dateInfo: {
...chatContext.dateInfo,
dateMode: dateModeValue,
unit: currentDateOption || chatContext.dateInfo.unit,
},
dimensions: [
...(chatContext.dimensions || []),
...(drillDownDimension ? [drillDownDimension] : []),
...(dimension ? [dimension] : []),
],
metrics: [activeMetricField || defaultMetricField],
});
};
const onSwitchMetric = (metricField?: FieldType) => { const onSwitchMetric = (metricField?: FieldType) => {
setActiveMetricField(metricField); setActiveMetricField(metricField);
onLoadData({ onLoadData({
@@ -199,7 +232,7 @@ const ChatMsg: React.FC<Props> = ({ queryId, data, chartIndex, triggerResize })
?.name; ?.name;
const isEntityMode = const isEntityMode =
(queryMode === 'ENTITY_LIST_FILTER' || queryMode === 'METRIC_ENTITY') && (queryMode === 'TAG_LIST_FILTER' || queryMode === 'METRIC_TAG') &&
typeof entityId === 'string' && typeof entityId === 'string' &&
entityName !== undefined; entityName !== undefined;
@@ -225,12 +258,11 @@ const ChatMsg: React.FC<Props> = ({ queryId, data, chartIndex, triggerResize })
<div <div
className={`${prefixCls}-bottom-tools ${ className={`${prefixCls}-bottom-tools ${
isMetricCard ? `${prefixCls}-metric-card-tools` : '' isMetricCard ? `${prefixCls}-metric-card-tools` : ''
}`} } ${isMobile ? 'mobile' : ''}`}
> >
{isMultipleMetric && ( {isMultipleMetric && (
<MetricOptions <MetricOptions
// metrics={chatContext.metrics} metrics={chatContext.metrics}
metrics={recommendMetrics}
defaultMetric={defaultMetricField} defaultMetric={defaultMetricField}
currentMetric={activeMetricField} currentMetric={activeMetricField}
onSelectMetric={onSwitchMetric} onSelectMetric={onSwitchMetric}
@@ -241,9 +273,11 @@ const ChatMsg: React.FC<Props> = ({ queryId, data, chartIndex, triggerResize })
modelId={chatContext.modelId} modelId={chatContext.modelId}
metricId={activeMetricField?.id || defaultMetricField?.id} metricId={activeMetricField?.id || defaultMetricField?.id}
drillDownDimension={drillDownDimension} drillDownDimension={drillDownDimension}
secondDrillDownDimension={secondDrillDownDimension}
originDimensions={chatContext.dimensions} originDimensions={chatContext.dimensions}
dimensionFilters={chatContext.dimensionFilters} dimensionFilters={chatContext.dimensionFilters}
onSelectDimension={onSelectDimension} onSelectDimension={onSelectDimension}
onSelectSecondDimension={onSelectSecondDimension}
/> />
)} )}
</div> </div>

View File

@@ -15,9 +15,13 @@
font-size: 14px; font-size: 14px;
margin-top: 12px; margin-top: 12px;
margin-bottom: 2px; margin-bottom: 2px;
&.mobile {
flex-wrap: wrap;
}
} }
&-metric-card-tools { &-metric-card-tools {
margin-top: 2px; margin-top: 0;
} }
} }

View File

@@ -0,0 +1,95 @@
import classNames from 'classnames';
import { CLS_PREFIX } from '../../common/constants';
import { DrillDownDimensionType } from '../../common/type';
import { Dropdown, Menu } from 'antd';
import { DownOutlined } from '@ant-design/icons';
type Props = {
drillDownDimension?: DrillDownDimensionType;
dimensions: DrillDownDimensionType[];
isSecondDrillDown?: boolean;
onSelectDimension: (dimension?: DrillDownDimensionType) => void;
onCancelDrillDown: () => void;
};
const DEFAULT_DIMENSION_COUNT = 5;
const DimensionSection: React.FC<Props> = ({
drillDownDimension,
dimensions,
isSecondDrillDown,
onSelectDimension,
onCancelDrillDown,
}) => {
const prefixCls = `${CLS_PREFIX}-drill-down-dimensions`;
const defaultDimensions = dimensions.slice(0, DEFAULT_DIMENSION_COUNT);
return (
<div className={`${prefixCls}-section`}>
<div className={`${prefixCls}-title`}>{isSecondDrillDown ? '二级' : '推荐'}</div>
<div className={`${prefixCls}-content`}>
{defaultDimensions.map((dimension, index) => {
const itemNameClass = classNames(`${prefixCls}-content-item-name`, {
[`${prefixCls}-content-item-active`]: drillDownDimension?.id === dimension.id,
});
return (
<div>
<span
className={itemNameClass}
onClick={() => {
onSelectDimension(
drillDownDimension?.id === dimension.id ? undefined : dimension
);
}}
>
{dimension.name}
</span>
{index !== defaultDimensions.length - 1 && <span></span>}
</div>
);
})}
{dimensions.length > DEFAULT_DIMENSION_COUNT && (
<div>
<span></span>
<Dropdown
overlay={
<Menu>
{dimensions.slice(DEFAULT_DIMENSION_COUNT).map(dimension => {
const itemNameClass = classNames({
[`${prefixCls}-menu-item-active`]: drillDownDimension?.id === dimension.id,
});
return (
<Menu.Item key={dimension.id}>
<span
className={itemNameClass}
onClick={() => {
onSelectDimension(dimension);
}}
>
{dimension.name}
</span>
</Menu.Item>
);
})}
</Menu>
}
>
<span>
<span className={`${prefixCls}-content-item-name`}></span>
<DownOutlined className={`${prefixCls}-down-arrow`} />
</span>
</Dropdown>
</div>
)}
{drillDownDimension && (
<div className={`${prefixCls}-cancel-drill-down`} onClick={onCancelDrillDown}>
{isSecondDrillDown ? '二级' : ''}
</div>
)}
</div>
</div>
);
};
export default DimensionSection;

View File

@@ -2,32 +2,30 @@ import { useEffect, useState } from 'react';
import { CLS_PREFIX } from '../../common/constants'; import { CLS_PREFIX } from '../../common/constants';
import { DrillDownDimensionType, FilterItemType } from '../../common/type'; import { DrillDownDimensionType, FilterItemType } from '../../common/type';
import { queryDrillDownDimensions } from '../../service'; import { queryDrillDownDimensions } from '../../service';
import { Dropdown, Menu } from 'antd'; import DimensionSection from './DimensionSection';
import { DownOutlined } from '@ant-design/icons';
import classNames from 'classnames';
type Props = { type Props = {
modelId: number; modelId: number;
metricId?: number; metricId?: number;
drillDownDimension?: DrillDownDimensionType; drillDownDimension?: DrillDownDimensionType;
isMetricCard?: boolean; secondDrillDownDimension?: DrillDownDimensionType;
originDimensions?: DrillDownDimensionType[]; originDimensions?: DrillDownDimensionType[];
dimensionFilters?: FilterItemType[]; dimensionFilters?: FilterItemType[];
onSelectDimension: (dimension?: DrillDownDimensionType) => void; onSelectDimension: (dimension?: DrillDownDimensionType) => void;
onSelectSecondDimension: (dimension?: DrillDownDimensionType) => void;
}; };
const MAX_DIMENSION_COUNT = 20; const MAX_DIMENSION_COUNT = 20;
const DEFAULT_DIMENSION_COUNT = 5;
const DrillDownDimensions: React.FC<Props> = ({ const DrillDownDimensions: React.FC<Props> = ({
modelId, modelId,
metricId, metricId,
drillDownDimension, drillDownDimension,
isMetricCard, secondDrillDownDimension,
originDimensions, originDimensions,
dimensionFilters, dimensionFilters,
onSelectDimension, onSelectDimension,
onSelectSecondDimension,
}) => { }) => {
const [dimensions, setDimensions] = useState<DrillDownDimensionType[]>([]); const [dimensions, setDimensions] = useState<DrillDownDimensionType[]>([]);
@@ -54,77 +52,27 @@ const DrillDownDimensions: React.FC<Props> = ({
onSelectDimension(undefined); onSelectDimension(undefined);
}; };
const defaultDimensions = dimensions.slice(0, DEFAULT_DIMENSION_COUNT); const cancelSecondDrillDown = () => {
onSelectSecondDimension(undefined);
const drillDownDimensionsSectionClass = classNames(`${prefixCls}-section`, { };
[`${prefixCls}-metric-card`]: isMetricCard,
});
return ( return (
<div className={prefixCls}> <div className={prefixCls}>
<div className={drillDownDimensionsSectionClass}> <DimensionSection
<div className={`${prefixCls}-title`}></div> drillDownDimension={drillDownDimension}
<div className={`${prefixCls}-content`}> dimensions={dimensions}
{defaultDimensions.map((dimension, index) => { onSelectDimension={onSelectDimension}
const itemNameClass = classNames(`${prefixCls}-content-item-name`, { onCancelDrillDown={cancelDrillDown}
[`${prefixCls}-content-item-active`]: drillDownDimension?.id === dimension.id, />
}); {drillDownDimension && dimensions.length > 1 && (
return ( <DimensionSection
<div> drillDownDimension={secondDrillDownDimension}
<span dimensions={dimensions.filter(dimension => dimension.id !== drillDownDimension?.id)}
className={itemNameClass} isSecondDrillDown
onClick={() => { onSelectDimension={onSelectSecondDimension}
onSelectDimension( onCancelDrillDown={cancelSecondDrillDown}
drillDownDimension?.id === dimension.id ? undefined : dimension />
);
}}
>
{dimension.name}
</span>
{index !== defaultDimensions.length - 1 && <span></span>}
</div>
);
})}
{dimensions.length > DEFAULT_DIMENSION_COUNT && (
<div>
<span></span>
<Dropdown
overlay={
<Menu>
{dimensions.slice(DEFAULT_DIMENSION_COUNT).map(dimension => {
const itemNameClass = classNames({
[`${prefixCls}-menu-item-active`]: drillDownDimension?.id === dimension.id,
});
return (
<Menu.Item key={dimension.id}>
<span
className={itemNameClass}
onClick={() => {
onSelectDimension(dimension);
}}
>
{dimension.name}
</span>
</Menu.Item>
);
})}
</Menu>
}
>
<span>
<span className={`${prefixCls}-content-item-name`}></span>
<DownOutlined className={`${prefixCls}-down-arrow`} />
</span>
</Dropdown>
</div>
)} )}
{drillDownDimension && (
<div className={`${prefixCls}-cancel-drill-down`} onClick={cancelDrillDown}>
</div>
)}
</div>
</div>
</div> </div>
); );
}; };

View File

@@ -4,25 +4,17 @@
.@{drill-down-dimensions-prefix-cls} { .@{drill-down-dimensions-prefix-cls} {
display: flex; display: flex;
flex-direction: column; align-items: center;
column-gap: 12px;
&-section { &-section {
width: 100%; // width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
column-gap: 6px; column-gap: 6px;
} }
&-metric-card {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.14), 0 0 2px rgba(0, 0, 0, 0.12);
border-radius: 8px;
background-color: #fff;
width: fit-content;
padding: 2px 4px;
font-size: 12px;
}
&-title { &-title {
color: var(--text-color-third); color: var(--text-color-third);
} }

View File

@@ -89,7 +89,7 @@ export const formatNumberWithCN = (num: number) => {
if (num >= 10000) { if (num >= 10000) {
return (num / 10000).toFixed(1) + "万"; return (num / 10000).toFixed(1) + "万";
} else { } else {
return num; return formatByDecimalPlaces(num, 2);
} }
} }