add drill down dimensions and metric period compare and modify layout (#22)

* [feature](webapp) add drill down dimensions and metric period compare and modify layout

* [feature](webapp) add drill down dimensions and metric period compare and modify layout

---------

Co-authored-by: williamhliu <williamhliu@tencent.com>
This commit is contained in:
williamhliu
2023-07-31 12:00:39 +08:00
committed by GitHub
parent 0ac652c5d9
commit 7c99829052
68 changed files with 1429 additions and 1239 deletions

View File

@@ -1,22 +1,38 @@
import { CHART_BLUE_COLOR, CHART_SECONDARY_COLOR, PREFIX_CLS } from '../../../common/constants';
import { MsgDataType } from '../../../common/type';
import { DrillDownDimensionType, MsgDataType } from '../../../common/type';
import { getChartLightenColor, getFormattedValue } from '../../../utils/utils';
import type { ECharts } from 'echarts';
import * as echarts from 'echarts';
import React, { useEffect, useRef, useState } from 'react';
import NoPermissionChart from '../NoPermissionChart';
import DrillDownDimensions from '../../DrillDownDimensions';
import { Spin } from 'antd';
import FilterSection from '../FilterSection';
type Props = {
data: MsgDataType;
triggerResize?: boolean;
drillDownDimension?: DrillDownDimensionType;
loading: boolean;
onSelectDimension: (dimension?: DrillDownDimensionType) => void;
onApplyAuth?: (domain: string) => void;
};
const BarChart: React.FC<Props> = ({ data, triggerResize, onApplyAuth }) => {
const BarChart: React.FC<Props> = ({
data,
triggerResize,
drillDownDimension,
loading,
onSelectDimension,
onApplyAuth,
}) => {
const chartRef = useRef<any>();
const [instance, setInstance] = useState<ECharts>();
const { queryColumns, queryResults, entityInfo } = data;
const { queryColumns, queryResults, entityInfo, chatContext, queryMode } = data;
const { dateInfo } = chatContext || {};
const categoryColumnName =
queryColumns?.find(column => column.showType === 'CATEGORY')?.nameEn || '';
const metricColumn = queryColumns?.find(column => column.showType === 'NUMBER');
@@ -35,13 +51,13 @@ const BarChart: React.FC<Props> = ({ data, triggerResize, onApplyAuth }) => {
);
const xData = data.map(item => item[categoryColumnName]);
instanceObj.setOption({
legend: {
left: 0,
top: 0,
icon: 'rect',
itemWidth: 15,
itemHeight: 5,
},
// legend: {
// left: 0,
// top: 0,
// icon: 'rect',
// itemWidth: 15,
// itemHeight: 5,
// },
xAxis: {
type: 'category',
axisTick: {
@@ -99,7 +115,7 @@ const BarChart: React.FC<Props> = ({ data, triggerResize, onApplyAuth }) => {
left: '2%',
right: '1%',
bottom: '3%',
top: 50,
top: 20,
containLabel: true,
},
series: {
@@ -150,7 +166,30 @@ const BarChart: React.FC<Props> = ({ data, triggerResize, onApplyAuth }) => {
);
}
return <div className={`${PREFIX_CLS}-bar`} ref={chartRef} />;
return (
<div>
<div className={`${PREFIX_CLS}-bar-metric-name`}>{metricColumn?.name}</div>
<FilterSection chatContext={chatContext} />
{dateInfo && (
<div className={`${PREFIX_CLS}-bar-date-range`}>
{dateInfo.startDate === dateInfo.endDate
? dateInfo.startDate
: `${dateInfo.startDate} ~ ${dateInfo.endDate}`}
</div>
)}
<Spin spinning={loading}>
<div className={`${PREFIX_CLS}-bar-chart`} ref={chartRef} />
</Spin>
{(queryMode === 'METRIC_DOMAIN' || queryMode === 'METRIC_FILTER') && (
<DrillDownDimensions
domainId={chatContext.domainId}
drillDownDimension={drillDownDimension}
dimensionFilters={chatContext.dimensionFilters}
onSelectDimension={onSelectDimension}
/>
)}
</div>
);
};
export default BarChart;

View File

@@ -3,6 +3,20 @@
@bar-cls: ~'@{supersonic-chat-prefix}-bar';
.@{bar-cls} {
height: 270px;
margin-top: 16px;
&-chart {
height: 260px;
margin-top: 16px;
}
&-metric-name {
font-size: 15px;
font-weight: 500;
}
&-date-range {
margin-top: 12px;
font-size: 13px;
color: var(--text-color-third);
}
}

View File

@@ -0,0 +1,38 @@
import { PREFIX_CLS } from '../../../common/constants';
import { ChatContextType } from '../../../common/type';
type Props = {
chatContext?: ChatContextType;
};
const FilterSection: React.FC<Props> = ({ chatContext }) => {
const prefixCls = `${PREFIX_CLS}-filter-section`;
const { dimensionFilters } = chatContext || {};
const hasFilterSection = dimensionFilters && dimensionFilters.length > 0;
return hasFilterSection ? (
<div className={prefixCls}>
<div className={`${prefixCls}-field-label`}></div>
<div className={`${prefixCls}-filter-values`}>
{dimensionFilters.map(filterItem => {
const filterValue =
typeof filterItem.value === 'string' ? [filterItem.value] : filterItem.value || [];
return (
<div
className={`${prefixCls}-filter-item`}
key={filterItem.name}
title={filterValue.join('、')}
>
<span className={`${prefixCls}-field-name`}>{filterItem.name}</span>
<span className={`${prefixCls}-filter-value`}>{filterValue.join('、')}</span>
</div>
);
})}
</div>
</div>
) : null;
};
export default FilterSection;

View File

@@ -0,0 +1,33 @@
@import '../../../styles/index.less';
@filter-section-prefix-cls: ~'@{supersonic-chat-prefix}-filter-section';
.@{filter-section-prefix-cls} {
display: flex;
align-items: center;
color: var(--text-color-secondary);
font-weight: normal;
font-size: 13px;
&-filter-values {
display: flex;
align-items: center;
column-gap: 6px;
}
&-filter-item {
padding: 2px 12px;
color: var(--text-color-third);
background-color: #edf2f2;
border-radius: 13px;
max-width: 200px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
&-filter-value {
color: var(--text-color);
font-weight: 500;
}
}

View File

@@ -65,7 +65,14 @@ const Message: React.FC<Props> = ({
return (
<div className={prefixCls}>
{domainName && <div className={`${prefixCls}-domain-name`}>{domainName}</div>}
<div className={`${prefixCls}-title-bar`}>
{domainName && <div className={`${prefixCls}-domain-name`}>{domainName}</div>}
{position === 'left' && leftTitle && (
<div className={`${prefixCls}-top-bar`} title={leftTitle}>
({leftTitle})
</div>
)}
</div>
<div className={`${prefixCls}-content`}>
<div className={`${prefixCls}-body`}>
<div
@@ -75,14 +82,9 @@ const Message: React.FC<Props> = ({
e.stopPropagation();
}}
>
{position === 'left' && title && (
<div className={`${prefixCls}-top-bar`} title={leftTitle}>
{leftTitle}
</div>
)}
{(entityInfoList.length > 0 || hasFilterSection) && (
{entityInfoList.length > 0 && (
<div className={`${prefixCls}-info-bar`}>
{filterSection}
{/* {filterSection} */}
{entityInfoList.length > 0 && (
<div className={`${prefixCls}-main-entity-info`}>
{entityInfoList.slice(0, 4).map(dimension => {

View File

@@ -3,11 +3,28 @@
@msg-prefix-cls: ~'@{supersonic-chat-prefix}-message';
.@{msg-prefix-cls} {
&-title-bar {
display: flex;
align-items: baseline;
column-gap: 10px;
margin-bottom: 6px;
}
&-domain-name {
color: var(--text-color);
margin-bottom: 2px;
margin-left: 4px;
font-weight: 500;
font-size: 15px;
}
&-top-bar {
position: relative;
max-width: 80%;
color: var(--text-color-third);
font-size: 13px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
&-content {
@@ -20,6 +37,7 @@
}
&-bubble {
position: relative;
box-sizing: border-box;
min-width: 1px;
max-width: 100%;
@@ -30,18 +48,6 @@
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.14), 0 0 2px rgba(0, 0, 0, 0.12);
}
&-top-bar {
position: relative;
max-width: 100%;
padding: 4px 0 8px;
color: var(--text-color-third);
font-size: 13px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
border-bottom: 1px solid rgba(0, 0, 0, 0.03);
}
&-filter-section {
display: flex;
align-items: center;
@@ -77,7 +83,7 @@
align-items: center;
row-gap: 12px;
flex-wrap: wrap;
margin-top: 20px;
margin-top: 4px;
column-gap: 20px;
color: var(--text-color-secondary);
background: rgba(133, 156, 241, 0.1);

View File

@@ -0,0 +1,29 @@
import classNames from 'classnames';
import { PREFIX_CLS } from '../../../common/constants';
import IconFont from '../../IconFont';
type Props = {
title: string;
value: string;
};
const PeriodCompareItem: React.FC<Props> = ({ title, value }) => {
const prefixCls = `${PREFIX_CLS}-metric-card`;
const itemValueClass = classNames(`${prefixCls}-period-compare-item-value`, {
[`${prefixCls}-period-compare-item-value-up`]: !value.includes('-'),
[`${prefixCls}-period-compare-item-value-down`]: value.includes('-'),
});
return (
<div className={`${prefixCls}-period-compare-item`}>
<div className={`${prefixCls}-period-compare-item-title`}>{title}</div>
<div className={itemValueClass}>
<IconFont type={!value.includes('-') ? 'icon-shangsheng' : 'icon-xiajiang'} />
<div>{value}</div>
</div>
</div>
);
};
export default PeriodCompareItem;

View File

@@ -1,36 +1,77 @@
import { PREFIX_CLS } from '../../../common/constants';
import { getFormattedValue } from '../../../utils/utils';
import { formatByThousandSeperator } from '../../../utils/utils';
import ApplyAuth from '../ApplyAuth';
import { MsgDataType } from '../../../common/type';
import { DrillDownDimensionType, MsgDataType } from '../../../common/type';
import PeriodCompareItem from './PeriodCompareItem';
import DrillDownDimensions from '../../DrillDownDimensions';
import { Spin } from 'antd';
import classNames from 'classnames';
type Props = {
data: MsgDataType;
drillDownDimension?: DrillDownDimensionType;
loading: boolean;
onSelectDimension: (dimension?: DrillDownDimensionType) => void;
onApplyAuth?: (domain: string) => void;
};
const MetricCard: React.FC<Props> = ({ data, onApplyAuth }) => {
const { queryColumns, queryResults, entityInfo } = data;
const MetricCard: React.FC<Props> = ({
data,
drillDownDimension,
loading,
onSelectDimension,
onApplyAuth,
}) => {
const { queryMode, queryColumns, queryResults, entityInfo, aggregateInfo, chatContext } = data;
const { metricInfos } = aggregateInfo || {};
const { dateInfo } = chatContext || {};
const { startDate, endDate } = dateInfo || {};
const indicatorColumn = queryColumns?.find(column => column.showType === 'NUMBER');
const indicatorColumnName = indicatorColumn?.nameEn || '';
const prefixCls = `${PREFIX_CLS}-metric-card`;
const indicatorClass = classNames(`${prefixCls}-indicator`, {
[`${prefixCls}-indicator-period-compare`]: metricInfos?.length > 0,
});
return (
<div className={prefixCls}>
<div className={`${prefixCls}-indicator`}>
{/* <div className={`${prefixCls}-date-range`}>
{startTime === endTime ? startTime : `${startTime} ~ ${endTime}`}
</div> */}
{indicatorColumn && !indicatorColumn?.authorized ? (
<ApplyAuth domain={entityInfo?.domainInfo.name || ''} onApplyAuth={onApplyAuth} />
) : (
<div className={`${prefixCls}-indicator-value`}>
{getFormattedValue(queryResults?.[0]?.[indicatorColumnName])}
<div className={`${prefixCls}-indicator-name`}>{indicatorColumn?.name}</div>
<Spin spinning={loading}>
<div className={indicatorClass}>
<div className={`${prefixCls}-date-range`}>
{startDate === endDate ? startDate : `${startDate} ~ ${endDate}`}
</div>
)}
{/* <div className={`${prefixCls}-indicator-name`}>{query}</div> */}
</div>
{indicatorColumn && !indicatorColumn?.authorized ? (
<ApplyAuth domain={entityInfo?.domainInfo.name || ''} onApplyAuth={onApplyAuth} />
) : (
<div className={`${prefixCls}-indicator-value`}>
{formatByThousandSeperator(queryResults?.[0]?.[indicatorColumnName])}
</div>
)}
{metricInfos?.length > 0 && (
<div className={`${prefixCls}-period-compare`}>
{Object.keys(metricInfos[0].statistics).map((key: any) => (
<PeriodCompareItem title={key} value={metricInfos[0].statistics[key]} />
))}
</div>
)}
</div>
</Spin>
{(queryMode === 'METRIC_DOMAIN' || queryMode === 'METRIC_FILTER') && (
<div className={`${prefixCls}-drill-down-dimensions`}>
<DrillDownDimensions
domainId={chatContext.domainId}
dimensionFilters={chatContext.dimensionFilters}
drillDownDimension={drillDownDimension}
onSelectDimension={onSelectDimension}
isMetricCard
/>
</div>
)}
</div>
);
};

View File

@@ -3,34 +3,78 @@
@metric-card-prefix-cls: ~'@{supersonic-chat-prefix}-metric-card';
.@{metric-card-prefix-cls} {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 150px;
height: 130px;
row-gap: 4px;
&-indicator-name {
font-size: 14px;
color: var(--text-color);
font-weight: 500;
margin-top: 2px;
}
&-indicator {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
}
&-indicator-period-compare {
align-items: flex-start;
justify-content: center;
}
&-date-range {
color: var(--text-color-fourth);
font-size: 14px;
font-size: 12px;
margin-top: 8px;
}
&-indicator-value {
color: var(--text-color);
font-weight: 600;
font-size: 30px;
font-weight: 700;
font-size: 40px;
color: var(--chat-blue);
}
&-indicator-name {
&-period-compare {
width: 100%;
display: flex;
align-items: center;
column-gap: 40px;
font-size: 13px;
overflow-x: auto;
}
&-period-compare-item {
display: flex;
align-items: center;
column-gap: 10px;
}
&-period-compare-item-title {
color: var(--text-color-fourth);
font-size: 14px;
}
&-period-compare-item-value {
display: flex;
align-items: center;
column-gap: 4px;
font-weight: 500;
}
&-period-compare-item-value-up {
color: rgb(252, 103, 114);
}
&-period-compare-item-value-down {
color: rgb(45, 202, 147);
}
&-drill-down-dimensions {
position: absolute;
bottom: -38px;
left: 0;
}
}

View File

@@ -0,0 +1,34 @@
import { PREFIX_CLS } from '../../../common/constants';
import { formatByThousandSeperator } from '../../../utils/utils';
import { AggregateInfoType } from '../../../common/type';
import PeriodCompareItem from '../MetricCard/PeriodCompareItem';
type Props = {
aggregateInfo: AggregateInfoType;
};
const MetricInfo: React.FC<Props> = ({ aggregateInfo }) => {
const { metricInfos } = aggregateInfo || {};
const metricInfo = metricInfos?.[0] || {};
const { date, value, statistics } = metricInfo || {};
const prefixCls = `${PREFIX_CLS}-metric-info`;
return (
<div className={prefixCls}>
<div className={`${prefixCls}-indicator`}>
<div className={`${prefixCls}-date`}>{date}</div>
<div className={`${prefixCls}-indicator-value`}>{formatByThousandSeperator(value)}</div>
{metricInfos?.length > 0 && (
<div className={`${prefixCls}-period-compare`}>
{Object.keys(statistics).map((key: any) => (
<PeriodCompareItem title={key} value={metricInfos[0].statistics[key]} />
))}
</div>
)}
</div>
</div>
);
};
export default MetricInfo;

View File

@@ -1,24 +1,27 @@
import { useEffect, useState } from 'react';
import { CLS_PREFIX, DATE_TYPES } from '../../../common/constants';
import { ColumnType, FieldType, MsgDataType } from '../../../common/type';
import { ColumnType, DrillDownDimensionType, FieldType, MsgDataType } from '../../../common/type';
import { isMobile } from '../../../utils/utils';
import { queryData } from '../../../service';
import MetricTrendChart from './MetricTrendChart';
import classNames from 'classnames';
import { Spin } from 'antd';
import Table from '../Table';
import DrillDownDimensions from '../../DrillDownDimensions';
import MetricInfo from './MetricInfo';
import FilterSection from '../FilterSection';
import moment from 'moment';
type Props = {
data: MsgDataType;
triggerResize?: boolean;
onApplyAuth?: (domain: string) => void;
onCheckMetricInfo?: (data: any) => void;
};
const MetricTrend: React.FC<Props> = ({ data, triggerResize, onApplyAuth, onCheckMetricInfo }) => {
const { queryColumns, queryResults, entityInfo, chatContext } = data;
const MetricTrend: React.FC<Props> = ({ data, triggerResize, onApplyAuth }) => {
const { queryColumns, queryResults, entityInfo, chatContext, queryMode, aggregateInfo } = data;
const dateOptions = DATE_TYPES[chatContext?.dateInfo?.period] || DATE_TYPES[0];
const dateOptions = DATE_TYPES[chatContext?.dateInfo?.period] || DATE_TYPES.DAY;
const initialDateOption = dateOptions.find(
(option: any) => option.value === chatContext?.dateInfo?.unit
)?.value;
@@ -29,6 +32,7 @@ const MetricTrend: React.FC<Props> = ({ data, triggerResize, onApplyAuth, onChec
const [activeMetricField, setActiveMetricField] = useState<FieldType>(chatContext.metrics?.[0]);
const [dataSource, setDataSource] = useState<any[]>(queryResults);
const [currentDateOption, setCurrentDateOption] = useState<number>(initialDateOption);
const [drillDownDimension, setDrillDownDimension] = useState<DrillDownDimensionType>();
const [loading, setLoading] = useState(false);
const dateField: any = columns.find(
@@ -57,9 +61,21 @@ const MetricTrend: React.FC<Props> = ({ data, triggerResize, onApplyAuth, onChec
const selectDateOption = (dateOption: number) => {
setCurrentDateOption(dateOption);
const endDate = moment().subtract(1, 'days').format('YYYY-MM-DD');
const startDate = moment(endDate)
.subtract(dateOption - 1, 'days')
.format('YYYY-MM-DD');
onLoadData({
metrics: [activeMetricField],
dateInfo: { ...chatContext?.dateInfo, unit: dateOption },
dimensions: drillDownDimension
? [...(chatContext.dimensions || []), drillDownDimension]
: undefined,
dateInfo: {
...chatContext?.dateInfo,
startDate,
endDate,
unit: dateOption,
},
});
};
@@ -67,10 +83,23 @@ const MetricTrend: React.FC<Props> = ({ data, triggerResize, onApplyAuth, onChec
setActiveMetricField(metricField);
onLoadData({
dateInfo: { ...chatContext.dateInfo, unit: currentDateOption || chatContext.dateInfo.unit },
dimensions: drillDownDimension
? [...(chatContext.dimensions || []), drillDownDimension]
: undefined,
metrics: [metricField],
});
};
const onSelectDimension = (dimension?: DrillDownDimensionType) => {
setDrillDownDimension(dimension);
onLoadData({
dateInfo: { ...chatContext.dateInfo, unit: currentDateOption || chatContext.dateInfo.unit },
metrics: [activeMetricField],
dimensions:
dimension === undefined ? undefined : [...(chatContext.dimensions || []), dimension],
});
};
if (!currentMetricField) {
return null;
}
@@ -80,6 +109,33 @@ const MetricTrend: React.FC<Props> = ({ data, triggerResize, onApplyAuth, onChec
return (
<div className={prefixCls}>
<div className={`${prefixCls}-charts`}>
{chatContext.metrics.length > 0 && (
<div className={`${prefixCls}-metric-fields`}>
{chatContext.metrics.map((metricField: FieldType) => {
const metricFieldClass = classNames(`${prefixCls}-metric-field`, {
[`${prefixCls}-metric-field-active`]:
activeMetricField?.bizName === metricField.bizName &&
chatContext.metrics.length > 1,
[`${prefixCls}-metric-field-single`]: chatContext.metrics.length === 1,
});
return (
<div
className={metricFieldClass}
key={metricField.bizName}
onClick={() => {
if (chatContext.metrics.length > 1) {
onSwitchMetric(metricField);
}
}}
>
{metricField.name}
</div>
);
})}
</div>
)}
{aggregateInfo?.metricInfos?.length > 0 && <MetricInfo aggregateInfo={aggregateInfo} />}
<FilterSection chatContext={chatContext} />
<div className={`${prefixCls}-date-options`}>
{dateOptions.map((dateOption: { label: string; value: number }, index: number) => {
const dateOptionClass = classNames(`${prefixCls}-date-option`, {
@@ -107,41 +163,10 @@ const MetricTrend: React.FC<Props> = ({ data, triggerResize, onApplyAuth, onChec
);
})}
</div>
{chatContext.metrics.length > 0 && (
<div className={`${prefixCls}-metric-fields`}>
{chatContext.metrics.map((metricField: FieldType) => {
const metricFieldClass = classNames(`${prefixCls}-metric-field`, {
[`${prefixCls}-metric-field-active`]:
activeMetricField?.bizName === metricField.bizName &&
chatContext.metrics.length > 1,
[`${prefixCls}-metric-field-single`]: chatContext.metrics.length === 1,
});
return (
<div
className={metricFieldClass}
key={metricField.bizName}
onClick={() => {
if (chatContext.metrics.length > 1) {
onSwitchMetric(metricField);
}
}}
>
{/* <SemanticInfoPopover
classId={chatContext.domainId}
uniqueId={metricField.bizName}
onDetailBtnClick={onCheckMetricInfo}
> */}
{metricField.name}
{/* </SemanticInfoPopover> */}
</div>
);
})}
</div>
)}
{dataSource?.length === 1 ? (
<Table data={data} onApplyAuth={onApplyAuth} />
) : (
<Spin spinning={loading}>
<Spin spinning={loading}>
{dataSource?.length === 1 ? (
<Table data={{ ...data, queryResults: dataSource }} onApplyAuth={onApplyAuth} />
) : (
<MetricTrendChart
domain={entityInfo?.domainInfo.name}
dateColumnName={dateColumnName}
@@ -151,7 +176,15 @@ const MetricTrend: React.FC<Props> = ({ data, triggerResize, onApplyAuth, onChec
triggerResize={triggerResize}
onApplyAuth={onApplyAuth}
/>
</Spin>
)}
</Spin>
{(queryMode === 'METRIC_DOMAIN' || queryMode === 'METRIC_FILTER') && (
<DrillDownDimensions
domainId={chatContext.domainId}
drillDownDimension={drillDownDimension}
dimensionFilters={chatContext.dimensionFilters}
onSelectDimension={onSelectDimension}
/>
)}
</div>
</div>

View File

@@ -2,12 +2,14 @@
@metric-trend-prefix-cls: ~'@{supersonic-chat-prefix}-metric-trend';
@metric-info-prefix-cls: ~'@{supersonic-chat-prefix}-metric-info';
.@{metric-trend-prefix-cls} {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-top: 16px;
margin-top: 4px;
width: 100%;
row-gap: 4px;
@@ -35,14 +37,15 @@
}
&-flow-trend-chart {
height: 270px;
margin-top: 4px;
height: 230px;
}
&-charts {
display: flex;
flex-direction: column;
width: 100%;
row-gap: 16px;
row-gap: 12px;
}
&-metric-fields {
@@ -50,6 +53,8 @@
flex-wrap: wrap;
align-items: center;
row-gap: 12px;
color: var(--text-color);
font-size: 15px;
}
&-metric-field {
@@ -85,10 +90,11 @@
padding-left: 0;
font-weight: 500;
cursor: default;
color: var(--text-color-secondary);
font-size: 15px;
color: var(--text-color);
&:hover {
color: var(--text-color-secondary);
color: var(--text-color);
}
}
@@ -133,3 +139,36 @@
}
}
.@{metric-info-prefix-cls} {
&-indicator {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
}
&-date {
color: var(--text-color-fourth);
font-size: 12px;
}
&-indicator-value {
color: var(--text-color);
font-weight: 500;
font-size: 36px;
line-height: 40px;
margin-top: 2px;
color: var(--text-color-secondary);
}
&-period-compare {
width: 100%;
display: flex;
align-items: center;
column-gap: 20px;
margin-top: 2px;
font-size: 13px;
overflow-x: auto;
}
}

View File

@@ -4,7 +4,9 @@ import Message from './Message';
import MetricCard from './MetricCard';
import MetricTrend from './MetricTrend';
import Table from './Table';
import { MsgDataType } from '../../common/type';
import { ColumnType, DrillDownDimensionType, MsgDataType } from '../../common/type';
import { useState } from 'react';
import { queryData } from '../../service';
type Props = {
question: string;
@@ -12,7 +14,6 @@ type Props = {
data: MsgDataType;
isMobileMode?: boolean;
triggerResize?: boolean;
onCheckMetricInfo?: (data: any) => void;
};
const ChatMsg: React.FC<Props> = ({
@@ -21,48 +22,95 @@ const ChatMsg: React.FC<Props> = ({
data,
isMobileMode,
triggerResize,
onCheckMetricInfo,
}) => {
const { queryColumns, queryResults, chatContext, entityInfo, queryMode } = data;
const [columns, setColumns] = useState<ColumnType[]>(queryColumns);
const [dataSource, setDataSource] = useState<any[]>(queryResults);
const [drillDownDimension, setDrillDownDimension] = useState<DrillDownDimensionType>();
const [loading, setLoading] = useState(false);
if (!queryColumns || !queryResults) {
return null;
}
const singleData = queryResults.length === 1;
const dateField = queryColumns.find(item => item.showType === 'DATE' || item.type === 'DATE');
const categoryField = queryColumns.filter(item => item.showType === 'CATEGORY');
const metricFields = queryColumns.filter(item => item.showType === 'NUMBER');
const singleData = dataSource.length === 1;
const dateField = columns.find(item => item.showType === 'DATE' || item.type === 'DATE');
const categoryField = columns.filter(item => item.showType === 'CATEGORY');
const metricFields = columns.filter(item => item.showType === 'NUMBER');
const isMetricCard =
(queryMode === 'METRIC_DOMAIN' || queryMode === 'METRIC_FILTER') && singleData;
const onLoadData = async (value: any) => {
setLoading(true);
const { data } = await queryData({
...chatContext,
...value,
});
setLoading(false);
if (data.code === 200) {
setColumns(data.data?.queryColumns || []);
setDataSource(data.data?.queryResults || []);
}
};
const onSelectDimension = (dimension?: DrillDownDimensionType) => {
setDrillDownDimension(dimension);
onLoadData({
dimensions:
dimension === undefined ? undefined : [...(chatContext.dimensions || []), dimension],
});
};
const getMsgContent = () => {
if (isMetricCard) {
return (
<MetricCard
data={{ ...data, queryColumns: columns, queryResults: dataSource }}
loading={loading}
drillDownDimension={drillDownDimension}
onSelectDimension={onSelectDimension}
/>
);
}
if (
categoryField.length > 1 ||
queryMode === 'ENTITY_DETAIL' ||
queryMode === 'ENTITY_DIMENSION' ||
(categoryField.length === 1 && metricFields.length === 0)
) {
return <Table data={data} />;
return <Table data={{ ...data, queryColumns: columns, queryResults: dataSource }} />;
}
if (dateField && metricFields.length > 0) {
return (
<MetricTrend
data={data}
triggerResize={triggerResize}
onCheckMetricInfo={onCheckMetricInfo}
/>
);
if (!dataSource.every(item => item[dateField.nameEn] === dataSource[0][dateField.nameEn])) {
return (
<MetricTrend
data={{ ...data, queryColumns: columns, queryResults: dataSource }}
triggerResize={triggerResize}
/>
);
}
}
if (singleData) {
return <MetricCard data={data} />;
}
return <Bar data={data} triggerResize={triggerResize} />;
return (
<Bar
data={{ ...data, queryColumns: columns, queryResults: dataSource }}
triggerResize={triggerResize}
loading={loading}
drillDownDimension={drillDownDimension}
onSelectDimension={onSelectDimension}
/>
);
};
let width = '100%';
if (categoryField.length > 1 && !isMobile && !isMobileMode) {
if (queryColumns.length === 1) {
if (isMetricCard) {
width = '370px';
} else if (categoryField.length > 1 && !isMobile && !isMobileMode) {
if (columns.length === 1) {
width = '600px';
} else if (queryColumns.length === 2) {
} else if (columns.length === 2) {
width = '1000px';
}
}