mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-10 11:07:06 +00:00
(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:
@@ -90,8 +90,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-select-selection-placeholder {
|
.ant-select-selection-placeholder {
|
||||||
padding-left: 2px;
|
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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} {
|
||||||
|
|||||||
@@ -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`}>
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user