mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-14 22:25:19 +00:00
(feature)(webapp) add show case and support multiple selection and deletion of filter conditions (#251)
This commit is contained in:
@@ -28,11 +28,7 @@ const MetricCard: React.FC<Props> = ({ data, loading, onApplyAuth }) => {
|
||||
const prefixCls = `${PREFIX_CLS}-metric-card`;
|
||||
|
||||
const matricCardClass = classNames(prefixCls, {
|
||||
[`${PREFIX_CLS}-metric-card-dsl`]: queryMode === 'DSL',
|
||||
});
|
||||
|
||||
const indicatorClass = classNames(`${prefixCls}-indicator`, {
|
||||
[`${prefixCls}-indicator-period-compare`]: metricInfos?.length > 0,
|
||||
[`${PREFIX_CLS}-metric-card-dsl`]: queryMode === 'LLM_S2QL',
|
||||
});
|
||||
|
||||
const [isNumber, setIsNumber] = useState(false);
|
||||
@@ -50,7 +46,7 @@ const MetricCard: React.FC<Props> = ({ data, loading, onApplyAuth }) => {
|
||||
)}
|
||||
</div>
|
||||
<Spin spinning={loading}>
|
||||
<div className={indicatorClass}>
|
||||
<div className={`${prefixCls}-indicator`}>
|
||||
{indicatorColumn && !indicatorColumn?.authorized ? (
|
||||
<ApplyAuth model={entityInfo?.modelInfo.name || ''} onApplyAuth={onApplyAuth} />
|
||||
) : (
|
||||
|
||||
@@ -55,12 +55,8 @@
|
||||
&-indicator {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
&-indicator-period-compare {
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
&-date-range {
|
||||
@@ -90,6 +86,7 @@
|
||||
column-gap: 40px;
|
||||
font-size: 13px;
|
||||
overflow-x: auto;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
&-period-compare-item {
|
||||
|
||||
@@ -13,6 +13,7 @@ import moment from 'moment';
|
||||
import { ColumnType } from '../../../common/type';
|
||||
import NoPermissionChart from '../NoPermissionChart';
|
||||
import classNames from 'classnames';
|
||||
import { isArray } from 'lodash';
|
||||
|
||||
type Props = {
|
||||
model?: string;
|
||||
@@ -83,7 +84,9 @@ const MetricTrendChart: React.FC<Props> = ({
|
||||
});
|
||||
|
||||
const xData = groupData[sortedGroupKeys[0]]?.map((item: any) => {
|
||||
const date = `${item[dateColumnName]}`;
|
||||
const date = isArray(item[dateColumnName])
|
||||
? item[dateColumnName].join('-')
|
||||
: `${item[dateColumnName]}`;
|
||||
return date.length === 10 ? moment(date).format('MM-DD') : date;
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import { CHART_SECONDARY_COLOR, CLS_PREFIX, THEME_COLOR_LIST } from '../../../common/constants';
|
||||
import { getFormattedValue } from '../../../utils/utils';
|
||||
import type { ECharts } from 'echarts';
|
||||
import * as echarts from 'echarts';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import moment from 'moment';
|
||||
import { ColumnType } from '../../../common/type';
|
||||
import { isArray } from 'lodash';
|
||||
|
||||
type Props = {
|
||||
dateColumnName: string;
|
||||
metricFields: ColumnType[];
|
||||
resultList: any[];
|
||||
triggerResize?: boolean;
|
||||
};
|
||||
|
||||
const MultiMetricsTrendChart: React.FC<Props> = ({
|
||||
dateColumnName,
|
||||
metricFields,
|
||||
resultList,
|
||||
triggerResize,
|
||||
}) => {
|
||||
const chartRef = useRef<any>();
|
||||
const [instance, setInstance] = useState<ECharts>();
|
||||
|
||||
const renderChart = () => {
|
||||
let instanceObj: any;
|
||||
if (!instance) {
|
||||
instanceObj = echarts.init(chartRef.current);
|
||||
setInstance(instanceObj);
|
||||
} else {
|
||||
instanceObj = instance;
|
||||
instanceObj.clear();
|
||||
}
|
||||
|
||||
const xData = resultList?.map((item: any) => {
|
||||
const date = isArray(item[dateColumnName])
|
||||
? item[dateColumnName].join('-')
|
||||
: `${item[dateColumnName]}`;
|
||||
return date.length === 10 ? moment(date).format('MM-DD') : date;
|
||||
});
|
||||
|
||||
instanceObj.setOption({
|
||||
legend: {
|
||||
left: 0,
|
||||
top: 0,
|
||||
icon: 'rect',
|
||||
itemWidth: 15,
|
||||
itemHeight: 5,
|
||||
type: 'scroll',
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
axisTick: {
|
||||
alignWithLabel: true,
|
||||
lineStyle: {
|
||||
color: CHART_SECONDARY_COLOR,
|
||||
},
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: CHART_SECONDARY_COLOR,
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
showMaxLabel: true,
|
||||
color: '#999',
|
||||
},
|
||||
data: xData,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
opacity: 0.3,
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
formatter: function (value: any) {
|
||||
return value === 0 ? 0 : getFormattedValue(value);
|
||||
},
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: function (params: any[]) {
|
||||
const param = params[0];
|
||||
const valueLabels = params
|
||||
.sort((a, b) => b.value - a.value)
|
||||
.map(
|
||||
(item: any) =>
|
||||
`<div style="margin-top: 3px;">${
|
||||
item.marker
|
||||
} <span style="display: inline-block; width: 70px; margin-right: 12px;">${
|
||||
item.seriesName
|
||||
}</span><span style="display: inline-block; width: 90px; text-align: right; font-weight: 500;">${
|
||||
item.value === '' ? '-' : getFormattedValue(item.value)
|
||||
}</span></div>`
|
||||
)
|
||||
.join('');
|
||||
return `${param.name}<br />${valueLabels}`;
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
left: '1%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
top: 45,
|
||||
containLabel: true,
|
||||
},
|
||||
series: metricFields.map((metricField, index) => {
|
||||
return {
|
||||
type: 'line',
|
||||
name: metricField.name,
|
||||
symbol: 'circle',
|
||||
showSymbol: resultList.length === 1,
|
||||
smooth: true,
|
||||
data: resultList.map((item: any) => {
|
||||
const value = item[metricField.nameEn];
|
||||
return (metricField.dataFormatType === 'percent' ||
|
||||
metricField.dataFormatType === 'decimal') &&
|
||||
metricField.dataFormat?.needMultiply100
|
||||
? value * 100
|
||||
: value;
|
||||
}),
|
||||
color: THEME_COLOR_LIST[index],
|
||||
};
|
||||
}),
|
||||
});
|
||||
instanceObj.resize();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
renderChart();
|
||||
}, [resultList]);
|
||||
|
||||
useEffect(() => {
|
||||
if (triggerResize && instance) {
|
||||
instance.resize();
|
||||
}
|
||||
}, [triggerResize]);
|
||||
|
||||
const prefixCls = `${CLS_PREFIX}-metric-trend`;
|
||||
|
||||
return <div className={`${prefixCls}-flow-trend-chart`} ref={chartRef} />;
|
||||
};
|
||||
|
||||
export default MultiMetricsTrendChart;
|
||||
@@ -1,11 +1,12 @@
|
||||
import { CLS_PREFIX } from '../../../common/constants';
|
||||
import { FieldType, MsgDataType } from '../../../common/type';
|
||||
import { DrillDownDimensionType, FieldType, MsgDataType } from '../../../common/type';
|
||||
import { isMobile } from '../../../utils/utils';
|
||||
import MetricTrendChart from './MetricTrendChart';
|
||||
import { Spin } from 'antd';
|
||||
import Table from '../Table';
|
||||
import MetricInfo from './MetricInfo';
|
||||
import DateOptions from '../DateOptions';
|
||||
import MultiMetricsTrendChart from './MultiMetricsTrendChart';
|
||||
|
||||
type Props = {
|
||||
data: MsgDataType;
|
||||
@@ -13,6 +14,7 @@ type Props = {
|
||||
triggerResize?: boolean;
|
||||
loading: boolean;
|
||||
activeMetricField?: FieldType;
|
||||
drillDownDimension?: DrillDownDimensionType;
|
||||
currentDateOption?: number;
|
||||
onApplyAuth?: (model: string) => void;
|
||||
onSelectDateOption: (value: number) => void;
|
||||
@@ -24,6 +26,7 @@ const MetricTrend: React.FC<Props> = ({
|
||||
triggerResize,
|
||||
loading,
|
||||
activeMetricField,
|
||||
drillDownDimension,
|
||||
currentDateOption,
|
||||
onApplyAuth,
|
||||
onSelectDateOption,
|
||||
@@ -36,6 +39,7 @@ const MetricTrend: React.FC<Props> = ({
|
||||
const dateColumnName = dateField?.nameEn || '';
|
||||
const categoryColumnName =
|
||||
queryColumns?.find((column: any) => column.showType === 'CATEGORY')?.nameEn || '';
|
||||
const metricFields = queryColumns?.filter((column: any) => column.showType === 'NUMBER');
|
||||
|
||||
const currentMetricField = queryColumns?.find((column: any) => column.showType === 'NUMBER');
|
||||
|
||||
@@ -48,19 +52,23 @@ const MetricTrend: React.FC<Props> = ({
|
||||
return (
|
||||
<div className={prefixCls}>
|
||||
<div className={`${prefixCls}-charts`}>
|
||||
<div className={`${prefixCls}-top-bar`}>
|
||||
<div
|
||||
className={`${prefixCls}-metric-fields ${prefixCls}-metric-field-single`}
|
||||
key={activeMetricField?.bizName}
|
||||
>
|
||||
{activeMetricField?.name}
|
||||
{metricFields?.length === 1 && (
|
||||
<div className={`${prefixCls}-top-bar`}>
|
||||
<div
|
||||
className={`${prefixCls}-metric-fields ${prefixCls}-metric-field-single`}
|
||||
key={activeMetricField?.bizName}
|
||||
>
|
||||
{activeMetricField?.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Spin spinning={loading}>
|
||||
<div className={`${prefixCls}-content`}>
|
||||
{!isMobile && aggregateInfo?.metricInfos?.length > 0 && (
|
||||
<MetricInfo aggregateInfo={aggregateInfo} currentMetricField={currentMetricField} />
|
||||
)}
|
||||
{!isMobile &&
|
||||
aggregateInfo?.metricInfos?.length > 0 &&
|
||||
drillDownDimension === undefined && (
|
||||
<MetricInfo aggregateInfo={aggregateInfo} currentMetricField={currentMetricField} />
|
||||
)}
|
||||
<DateOptions
|
||||
chatContext={chatContext}
|
||||
currentDateOption={currentDateOption}
|
||||
@@ -68,6 +76,13 @@ const MetricTrend: React.FC<Props> = ({
|
||||
/>
|
||||
{queryResults?.length === 1 || chartIndex % 2 === 1 ? (
|
||||
<Table data={{ ...data, queryResults }} onApplyAuth={onApplyAuth} />
|
||||
) : metricFields.length > 1 ? (
|
||||
<MultiMetricsTrendChart
|
||||
dateColumnName={dateColumnName}
|
||||
metricFields={metricFields}
|
||||
resultList={queryResults}
|
||||
triggerResize={triggerResize}
|
||||
/>
|
||||
) : (
|
||||
<MetricTrendChart
|
||||
model={entityInfo?.modelInfo.name}
|
||||
|
||||
@@ -59,11 +59,9 @@ const ChatMsg: React.FC<Props> = ({ queryId, data, chartIndex, triggerResize })
|
||||
const metricFields = columns.filter(item => item.showType === 'NUMBER');
|
||||
|
||||
const isDslMetricCard =
|
||||
queryMode === 'DSL' && singleData && metricFields.length === 1 && columns.length === 1;
|
||||
queryMode === 'LLM_S2QL' && singleData && metricFields.length === 1 && columns.length === 1;
|
||||
|
||||
const isMetricCard =
|
||||
(queryMode.includes('METRIC') || isDslMetricCard) &&
|
||||
(singleData || chatContext?.dateInfo?.startDate === chatContext?.dateInfo?.endDate);
|
||||
const isMetricCard = (queryMode.includes('METRIC') || isDslMetricCard) && singleData;
|
||||
|
||||
const isText =
|
||||
columns.length === 1 &&
|
||||
@@ -95,24 +93,27 @@ const ChatMsg: React.FC<Props> = ({ queryId, data, chartIndex, triggerResize })
|
||||
if (isTable) {
|
||||
return <Table data={{ ...data, queryColumns: columns, queryResults: dataSource }} />;
|
||||
}
|
||||
if (dateField && metricFields.length > 0) {
|
||||
if (!dataSource.every(item => item[dateField.nameEn] === dataSource[0][dateField.nameEn])) {
|
||||
return (
|
||||
<MetricTrend
|
||||
data={{
|
||||
...data,
|
||||
queryColumns: columns,
|
||||
queryResults: dataSource,
|
||||
}}
|
||||
loading={loading}
|
||||
chartIndex={chartIndex}
|
||||
triggerResize={triggerResize}
|
||||
activeMetricField={activeMetricField}
|
||||
currentDateOption={currentDateOption}
|
||||
onSelectDateOption={selectDateOption}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (
|
||||
dateField &&
|
||||
metricFields.length > 0 &&
|
||||
!dataSource.every(item => item[dateField.nameEn] === dataSource[0][dateField.nameEn])
|
||||
) {
|
||||
return (
|
||||
<MetricTrend
|
||||
data={{
|
||||
...data,
|
||||
queryColumns: columns,
|
||||
queryResults: dataSource,
|
||||
}}
|
||||
loading={loading}
|
||||
chartIndex={chartIndex}
|
||||
triggerResize={triggerResize}
|
||||
activeMetricField={activeMetricField}
|
||||
drillDownDimension={drillDownDimension}
|
||||
currentDateOption={currentDateOption}
|
||||
onSelectDateOption={selectDateOption}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (categoryField?.length > 0 && metricFields?.length > 0) {
|
||||
return (
|
||||
@@ -209,7 +210,11 @@ const ChatMsg: React.FC<Props> = ({ queryId, data, chartIndex, triggerResize })
|
||||
<div>
|
||||
{getMsgContent()}
|
||||
{(isMultipleMetric || existDrillDownDimension) && (
|
||||
<div className={`${prefixCls}-bottom-tools`}>
|
||||
<div
|
||||
className={`${prefixCls}-bottom-tools ${
|
||||
isMetricCard ? `${prefixCls}-metric-card-tools` : ''
|
||||
}`}
|
||||
>
|
||||
{isMultipleMetric && (
|
||||
<MetricOptions
|
||||
metrics={chatContext.metrics}
|
||||
@@ -221,6 +226,7 @@ const ChatMsg: React.FC<Props> = ({ queryId, data, chartIndex, triggerResize })
|
||||
{existDrillDownDimension && (
|
||||
<DrillDownDimensions
|
||||
modelId={chatContext.modelId}
|
||||
metricId={activeMetricField?.id || defaultMetricField?.id}
|
||||
drillDownDimension={drillDownDimension}
|
||||
originDimensions={chatContext.dimensions}
|
||||
dimensionFilters={chatContext.dimensionFilters}
|
||||
|
||||
@@ -14,5 +14,10 @@
|
||||
column-gap: 20px;
|
||||
font-size: 14px;
|
||||
margin-top: 6px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
&-metric-card-tools {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user