[improvement](webapp) optimize drill down dimensions (#84)

This commit is contained in:
williamhliu
2023-09-13 15:05:23 +08:00
committed by GitHub
parent c8b5c0f3a3
commit c38507d50c
18 changed files with 322 additions and 380 deletions

View File

@@ -42,7 +42,7 @@ export const THEME_COLOR_LIST = [
export const PARSE_ERROR_TIP = '智能助理不太懂您说什么呐,回去一定补充知识'; export const PARSE_ERROR_TIP = '智能助理不太懂您说什么呐,回去一定补充知识';
export const SEARCH_EXCEPTION_TIP = '查询出错啦,智能助理还不够聪明,请您换个表达再试试'; export const SEARCH_EXCEPTION_TIP = '查询出错啦,数据库可能出现异常或者负载繁忙,请联系管理员或者稍后重试';
export const MSG_VALID_TIP = { export const MSG_VALID_TIP = {
[MsgValidTypeEnum.SEARCH_EXCEPTION]: '数据查询异常', [MsgValidTypeEnum.SEARCH_EXCEPTION]: '数据查询异常',

View File

@@ -173,7 +173,7 @@ export enum SemanticTypeEnum {
}; };
export const SEMANTIC_TYPE_MAP = { export const SEMANTIC_TYPE_MAP = {
[SemanticTypeEnum.DOMAIN]: '主题域', [SemanticTypeEnum.DOMAIN]: '数据模型',
[SemanticTypeEnum.DIMENSION]: '维度', [SemanticTypeEnum.DIMENSION]: '维度',
[SemanticTypeEnum.METRIC]: '指标', [SemanticTypeEnum.METRIC]: '指标',
[SemanticTypeEnum.VALUE]: '维度值', [SemanticTypeEnum.VALUE]: '维度值',

View File

@@ -165,7 +165,7 @@ const ParseTip: React.FC<Props> = ({
</div> </div>
) : ( ) : (
<div className={`${prefixCls}-tip-item`}> <div className={`${prefixCls}-tip-item`}>
<div className={`${prefixCls}-tip-item-name`}></div> <div className={`${prefixCls}-tip-item-name`}></div>
<div className={itemValueClass}>{modelName}</div> <div className={itemValueClass}>{modelName}</div>
</div> </div>
)} )}

View File

@@ -1,37 +1,24 @@
import { CHART_BLUE_COLOR, CHART_SECONDARY_COLOR, PREFIX_CLS } from '../../../common/constants'; import { CHART_BLUE_COLOR, CHART_SECONDARY_COLOR, PREFIX_CLS } from '../../../common/constants';
import { DrillDownDimensionType, MsgDataType } from '../../../common/type'; import { MsgDataType } from '../../../common/type';
import { getChartLightenColor, getFormattedValue } from '../../../utils/utils'; import { getChartLightenColor, getFormattedValue } from '../../../utils/utils';
import type { ECharts } from 'echarts'; import type { ECharts } from 'echarts';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import NoPermissionChart from '../NoPermissionChart'; import NoPermissionChart from '../NoPermissionChart';
import DrillDownDimensions from '../../DrillDownDimensions';
import { Spin } from 'antd'; import { Spin } from 'antd';
import FilterSection from '../FilterSection';
type Props = { type Props = {
data: MsgDataType; data: MsgDataType;
triggerResize?: boolean; triggerResize?: boolean;
drillDownDimension?: DrillDownDimensionType;
loading: boolean; loading: boolean;
onSelectDimension: (dimension?: DrillDownDimensionType) => void;
onApplyAuth?: (model: string) => void; onApplyAuth?: (model: string) => void;
}; };
const BarChart: React.FC<Props> = ({ const BarChart: React.FC<Props> = ({ data, triggerResize, loading, onApplyAuth }) => {
data,
triggerResize,
drillDownDimension,
loading,
onSelectDimension,
onApplyAuth,
}) => {
const chartRef = useRef<any>(); const chartRef = useRef<any>();
const [instance, setInstance] = useState<ECharts>(); const [instance, setInstance] = useState<ECharts>();
const { queryColumns, queryResults, entityInfo, chatContext, queryMode } = data; const { queryColumns, queryResults, entityInfo } = data;
const { dateInfo, dimensionFilters } = chatContext || {};
const categoryColumnName = const categoryColumnName =
queryColumns?.find(column => column.showType === 'CATEGORY')?.nameEn || ''; queryColumns?.find(column => column.showType === 'CATEGORY')?.nameEn || '';
@@ -159,48 +146,16 @@ const BarChart: React.FC<Props> = ({
); );
} }
// const hasFilterSection = dimensionFilters?.length > 0;
const prefixCls = `${PREFIX_CLS}-bar`; const prefixCls = `${PREFIX_CLS}-bar`;
return ( return (
<div> <div>
<div className={`${prefixCls}-top-bar`}> <div className={`${prefixCls}-top-bar`}>
<div className={`${prefixCls}-indicator-name`}>{metricColumn?.name}</div> <div className={`${prefixCls}-indicator-name`}>{metricColumn?.name}</div>
{drillDownDimension && (
<div className={`${prefixCls}-filter-section-wrapper`}>
(
<div className={`${prefixCls}-filter-section`}>
{/* <FilterSection chatContext={chatContext} entityInfo={entityInfo} /> */}
{drillDownDimension && (
<div className={`${prefixCls}-filter-item`}>
<div className={`${prefixCls}-filter-item-label`}></div>
<div className={`${prefixCls}-filter-item-value`}>{drillDownDimension.name}</div>
</div>
)}
</div>
)
</div>
)}
</div> </div>
{/* {dateInfo && (
<div className={`${prefixCls}-date-range`}>
{dateInfo.startDate === dateInfo.endDate
? dateInfo.startDate
: `${dateInfo.startDate} ~ ${dateInfo.endDate}`}
</div>
)} */}
<Spin spinning={loading}> <Spin spinning={loading}>
<div className={`${prefixCls}-chart`} ref={chartRef} /> <div className={`${prefixCls}-chart`} ref={chartRef} />
</Spin> </Spin>
{queryMode.includes('METRIC') && (
<DrillDownDimensions
modelId={chatContext.modelId}
drillDownDimension={drillDownDimension}
dimensionFilters={chatContext.dimensionFilters}
onSelectDimension={onSelectDimension}
/>
)}
</div> </div>
); );
}; };

View File

@@ -0,0 +1,46 @@
import classNames from 'classnames';
import { CLS_PREFIX, DATE_TYPES } from '../../../common/constants';
import { isMobile } from '../../../utils/utils';
import { ChatContextType } from '../../../common/type';
type Props = {
chatContext: ChatContextType;
currentDateOption?: number;
onSelectDateOption: (value: number) => void;
};
const DateOptions: React.FC<Props> = ({ chatContext, currentDateOption, onSelectDateOption }) => {
const prefixCls = `${CLS_PREFIX}-date-options`;
const dateOptions = DATE_TYPES[chatContext?.dateInfo?.period] || DATE_TYPES.DAY;
return (
<div className={prefixCls}>
{dateOptions.map((dateOption: { label: string; value: number }, index: number) => {
const dateOptionClass = classNames(`${prefixCls}-item`, {
[`${prefixCls}-date-active`]: dateOption.value === currentDateOption,
[`${prefixCls}-date-mobile`]: isMobile,
});
return (
<>
<div
key={dateOption.value}
className={dateOptionClass}
onClick={() => {
onSelectDateOption(dateOption.value);
}}
>
{dateOption.label}
{dateOption.value === currentDateOption && (
<div className={`${prefixCls}-active-identifier`} />
)}
</div>
{index !== dateOptions.length - 1 && <div className={`${prefixCls}-item-divider`} />}
</>
);
})}
</div>
);
};
export default DateOptions;

View File

@@ -0,0 +1,43 @@
@import '../../../styles/index.less';
@date-options-prefix-cls: ~'@{supersonic-chat-prefix}-date-options';
.@{date-options-prefix-cls} {
display: flex;
align-items: center;
column-gap: 20px;
font-size: 14px;
&-item {
position: relative;
color: var(--text-color-secondary);
cursor: pointer;
&:hover {
color: var(--chat-blue);
}
}
&-date-active {
color: var(--chat-blue);
}
&-date-mobile {
font-size: 12px;
}
&-active-identifier {
position: absolute;
bottom: -6px;
width: 100%;
height: 4px;
background-color: var(--chat-blue);
border-radius: 4px 4px 0 0;
}
&-item-divider {
width: 1px;
height: 16px;
background-color: var(--text-color-fifth);
}
}

View File

@@ -68,7 +68,7 @@ const Message: React.FC<Props> = ({
<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`}>
<div className={`${prefixCls}-info-name`}></div> <div className={`${prefixCls}-info-name`}></div>
<div className={`${prefixCls}-info-value`}>{modelName}</div> <div className={`${prefixCls}-info-value`}>{modelName}</div>
</div> </div>
<div className={`${prefixCls}-info-item`}> <div className={`${prefixCls}-info-item`}>

View File

@@ -1,9 +1,8 @@
import { PREFIX_CLS } from '../../../common/constants'; import { PREFIX_CLS } from '../../../common/constants';
import { formatByDecimalPlaces, formatMetric, formatNumberWithCN } from '../../../utils/utils'; import { formatByDecimalPlaces, formatMetric, formatNumberWithCN } from '../../../utils/utils';
import ApplyAuth from '../ApplyAuth'; import ApplyAuth from '../ApplyAuth';
import { DrillDownDimensionType, MsgDataType } from '../../../common/type'; import { MsgDataType } from '../../../common/type';
import PeriodCompareItem from './PeriodCompareItem'; import PeriodCompareItem from './PeriodCompareItem';
import DrillDownDimensions from '../../DrillDownDimensions';
import { Spin } from 'antd'; import { Spin } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { SwapOutlined } from '@ant-design/icons'; import { SwapOutlined } from '@ant-design/icons';
@@ -11,20 +10,12 @@ import { useState } from 'react';
type Props = { type Props = {
data: MsgDataType; data: MsgDataType;
drillDownDimension?: DrillDownDimensionType;
loading: boolean; loading: boolean;
onSelectDimension: (dimension?: DrillDownDimensionType) => void;
onApplyAuth?: (model: string) => void; onApplyAuth?: (model: string) => void;
}; };
const MetricCard: React.FC<Props> = ({ const MetricCard: React.FC<Props> = ({ data, loading, onApplyAuth }) => {
data, const { queryMode, queryColumns, queryResults, entityInfo, aggregateInfo } = data;
drillDownDimension,
loading,
onSelectDimension,
onApplyAuth,
}) => {
const { queryMode, queryColumns, queryResults, entityInfo, aggregateInfo, chatContext } = data;
const { metricInfos } = aggregateInfo || {}; const { metricInfos } = aggregateInfo || {};
@@ -57,20 +48,6 @@ const MetricCard: React.FC<Props> = ({
) : ( ) : (
<div style={{ height: 10 }} /> <div style={{ height: 10 }} />
)} )}
{drillDownDimension && (
<div className={`${prefixCls}-filter-section-wrapper`}>
(
<div className={`${prefixCls}-filter-section`}>
{drillDownDimension && (
<div className={`${prefixCls}-filter-item`}>
<div className={`${prefixCls}-filter-item-label`}></div>
<div className={`${prefixCls}-filter-item-value`}>{drillDownDimension.name}</div>
</div>
)}
</div>
)
</div>
)}
</div> </div>
<Spin spinning={loading}> <Spin spinning={loading}>
<div className={indicatorClass}> <div className={indicatorClass}>
@@ -104,16 +81,6 @@ const MetricCard: React.FC<Props> = ({
)} )}
</div> </div>
</Spin> </Spin>
{queryMode.includes('METRIC') && (
<div className={`${prefixCls}-drill-down-dimensions`}>
<DrillDownDimensions
modelId={chatContext?.modelId}
dimensionFilters={chatContext?.dimensionFilters}
drillDownDimension={drillDownDimension}
onSelectDimension={onSelectDimension}
/>
</div>
)}
</div> </div>
); );
}; };

View File

@@ -1,150 +1,48 @@
import { useEffect, useState } from 'react'; import { CLS_PREFIX } from '../../../common/constants';
import { CLS_PREFIX, DATE_TYPES } from '../../../common/constants'; import { FieldType, MsgDataType } from '../../../common/type';
import { ColumnType, DrillDownDimensionType, FieldType, MsgDataType } from '../../../common/type';
import { isMobile } from '../../../utils/utils'; import { isMobile } from '../../../utils/utils';
import { queryData } from '../../../service';
import MetricTrendChart from './MetricTrendChart'; import MetricTrendChart from './MetricTrendChart';
import classNames from 'classnames';
import { Spin } from 'antd'; import { Spin } from 'antd';
import Table from '../Table'; import Table from '../Table';
import DrillDownDimensions from '../../DrillDownDimensions';
import MetricInfo from './MetricInfo'; import MetricInfo from './MetricInfo';
import MetricOptions from '../../MetricOptions'; import DateOptions from '../DateOptions';
type Props = { type Props = {
data: MsgDataType; data: MsgDataType;
chartIndex: number; chartIndex: number;
triggerResize?: boolean; triggerResize?: boolean;
loading: boolean;
activeMetricField?: FieldType;
currentDateOption?: number;
onApplyAuth?: (model: string) => void; onApplyAuth?: (model: string) => void;
onSelectDateOption: (value: number) => void;
}; };
const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApplyAuth }) => { const MetricTrend: React.FC<Props> = ({
const { entityInfo, chatContext, queryMode } = data; data,
const { dateInfo, dimensionFilters, elementMatches } = chatContext || {}; chartIndex,
const { dateMode, unit } = dateInfo || {}; triggerResize,
const dateOptions = DATE_TYPES[chatContext?.dateInfo?.period] || DATE_TYPES.DAY; loading,
activeMetricField,
currentDateOption,
onApplyAuth,
onSelectDateOption,
}) => {
const { queryColumns, queryResults, aggregateInfo, entityInfo, chatContext } = data;
const [columns, setColumns] = useState<ColumnType[]>([]); const dateField: any = queryColumns?.find(
const [defaultMetricField, setDefaultMetricField] = useState<FieldType>();
const [activeMetricField, setActiveMetricField] = useState<FieldType>();
const [dataSource, setDataSource] = useState<any[]>([]);
const [currentDateOption, setCurrentDateOption] = useState<number>();
const [dimensions, setDimensions] = useState<FieldType[]>();
const [drillDownDimension, setDrillDownDimension] = useState<DrillDownDimensionType>();
const [aggregateInfoValue, setAggregateInfoValue] = useState<any>();
const [dateModeValue, setDateModeValue] = useState<any>();
const [loading, setLoading] = useState(false);
const dateField: any = columns.find(
(column: any) => column.showType === 'DATE' || column.type === 'DATE' (column: any) => column.showType === 'DATE' || column.type === 'DATE'
); );
const dateColumnName = dateField?.nameEn || ''; const dateColumnName = dateField?.nameEn || '';
const categoryColumnName = const categoryColumnName =
columns.find((column: any) => column.showType === 'CATEGORY')?.nameEn || ''; queryColumns?.find((column: any) => column.showType === 'CATEGORY')?.nameEn || '';
const entityId = dimensionFilters?.length > 0 ? dimensionFilters[0].value : undefined; const currentMetricField = queryColumns?.find((column: any) => column.showType === 'NUMBER');
const entityName = elementMatches?.find((item: any) => item.element?.type === 'ID')?.element
?.name;
const isEntityMode =
(queryMode === 'ENTITY_LIST_FILTER' || queryMode === 'METRIC_ENTITY') &&
typeof entityId === 'string' &&
entityName !== undefined;
useEffect(() => {
const { queryColumns, queryResults, chatContext, aggregateInfo } = data;
const initialDateOption = dateOptions.find((option: any) => {
return dateMode === 'RECENT' && option.value === unit;
})?.value;
setColumns(queryColumns || []);
const metricField = chatContext?.metrics?.[0];
setDefaultMetricField(metricField);
setActiveMetricField(metricField);
setDataSource(queryResults);
setCurrentDateOption(initialDateOption);
setDimensions(chatContext?.dimensions);
setDrillDownDimension(undefined);
setAggregateInfoValue(aggregateInfo);
setDateModeValue(chatContext?.dateInfo?.dateMode);
}, [data]);
useEffect(() => {
if (queryMode === 'METRIC_GROUPBY') {
const dimensionValue = chatContext?.dimensions?.find(
dimension => dimension.type === 'DIMENSION'
);
setDrillDownDimension(dimensionValue);
setDimensions(
chatContext?.dimensions?.filter(dimension => dimension.id !== dimensionValue?.id)
);
}
}, []);
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 || []);
setAggregateInfoValue(data.data?.aggregateInfo);
}
};
const selectDateOption = (dateOption: number) => {
setCurrentDateOption(dateOption);
setDateModeValue('RECENT');
onLoadData({
metrics: [activeMetricField],
dimensions: drillDownDimension ? [...(dimensions || []), drillDownDimension] : undefined,
dateInfo: {
...chatContext?.dateInfo,
dateMode: 'RECENT',
unit: dateOption,
},
});
};
const onSwitchMetric = (metricField?: FieldType) => {
setActiveMetricField(metricField);
onLoadData({
dateInfo: {
...chatContext.dateInfo,
dateMode: dateModeValue,
unit: currentDateOption || chatContext.dateInfo.unit,
},
dimensions: drillDownDimension ? [...(dimensions || []), drillDownDimension] : undefined,
metrics: [metricField || defaultMetricField],
});
};
const onSelectDimension = (dimension?: DrillDownDimensionType) => {
setDrillDownDimension(dimension);
onLoadData({
dateInfo: {
...chatContext.dateInfo,
dateMode: dateModeValue,
unit: currentDateOption || chatContext.dateInfo.unit,
},
metrics: [activeMetricField],
dimensions: dimension === undefined ? undefined : [...(dimensions || []), dimension],
});
};
const currentMetricField = columns.find((column: any) => column.showType === 'NUMBER');
if (!currentMetricField) { if (!currentMetricField) {
return null; return null;
} }
const isMultipleMetric = chatContext?.metrics?.length > 1;
const existDrillDownDimension = queryMode.includes('METRIC') && !isEntityMode;
const prefixCls = `${CLS_PREFIX}-metric-trend`; const prefixCls = `${CLS_PREFIX}-metric-trend`;
return ( return (
@@ -157,92 +55,31 @@ const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApply
> >
{activeMetricField?.name} {activeMetricField?.name}
</div> </div>
{drillDownDimension && (
<div className={`${prefixCls}-filter-section-wrapper`}>
(
<div className={`${prefixCls}-filter-section`}>
{drillDownDimension && (
<div className={`${prefixCls}-filter-item`}>
<div className={`${prefixCls}-filter-item-label`}></div>
<div className={`${prefixCls}-filter-item-value`}>
{drillDownDimension.name}
</div>
</div>
)}
</div>
)
</div>
)}
</div> </div>
<Spin spinning={loading}> <Spin spinning={loading}>
<div className={`${prefixCls}-content`}> <div className={`${prefixCls}-content`}>
{!isMobile && aggregateInfoValue?.metricInfos?.length > 0 && ( {!isMobile && aggregateInfo?.metricInfos?.length > 0 && (
<MetricInfo <MetricInfo aggregateInfo={aggregateInfo} currentMetricField={currentMetricField} />
aggregateInfo={aggregateInfoValue}
currentMetricField={currentMetricField}
/>
)} )}
<div className={`${prefixCls}-date-options`}> <DateOptions
{dateOptions.map((dateOption: { label: string; value: number }, index: number) => { chatContext={chatContext}
const dateOptionClass = classNames(`${prefixCls}-date-option`, { currentDateOption={currentDateOption}
[`${prefixCls}-date-active`]: dateOption.value === currentDateOption, onSelectDateOption={onSelectDateOption}
[`${prefixCls}-date-mobile`]: isMobile, />
}); {queryResults?.length === 1 || chartIndex % 2 === 1 ? (
return ( <Table data={{ ...data, queryResults }} onApplyAuth={onApplyAuth} />
<>
<div
key={dateOption.value}
className={dateOptionClass}
onClick={() => {
selectDateOption(dateOption.value);
}}
>
{dateOption.label}
{dateOption.value === currentDateOption && (
<div className={`${prefixCls}-active-identifier`} />
)}
</div>
{index !== dateOptions.length - 1 && (
<div className={`${prefixCls}-date-option-divider`} />
)}
</>
);
})}
</div>
{dataSource?.length === 1 || chartIndex % 2 === 1 ? (
<Table data={{ ...data, queryResults: dataSource }} onApplyAuth={onApplyAuth} />
) : ( ) : (
<MetricTrendChart <MetricTrendChart
model={entityInfo?.modelInfo.name} model={entityInfo?.modelInfo.name}
dateColumnName={dateColumnName} dateColumnName={dateColumnName}
categoryColumnName={categoryColumnName} categoryColumnName={categoryColumnName}
metricField={currentMetricField} metricField={currentMetricField}
resultList={dataSource} resultList={queryResults}
triggerResize={triggerResize} triggerResize={triggerResize}
onApplyAuth={onApplyAuth} onApplyAuth={onApplyAuth}
/> />
)} )}
</div> </div>
{(isMultipleMetric || existDrillDownDimension) && (
<div className={`${prefixCls}-bottom-tools`}>
{isMultipleMetric && (
<MetricOptions
metrics={chatContext.metrics}
defaultMetric={defaultMetricField}
currentMetric={activeMetricField}
onSelectMetric={onSwitchMetric}
/>
)}
{existDrillDownDimension && (
<DrillDownDimensions
modelId={chatContext.modelId}
drillDownDimension={drillDownDimension}
dimensionFilters={chatContext.dimensionFilters}
onSelectDimension={onSelectDimension}
/>
)}
</div>
)}
</Spin> </Spin>
</div> </div>
</div> </div>

View File

@@ -139,53 +139,6 @@
color: var(--text-color); color: var(--text-color);
} }
} }
&-date-options {
display: flex;
align-items: center;
column-gap: 20px;
font-size: 14px;
}
&-date-option {
position: relative;
color: var(--text-color-secondary);
cursor: pointer;
&:hover {
color: var(--chat-blue);
}
}
&-date-option-active {
color: var(--chat-blue);
}
&-date-option-mobile {
font-size: 12px;
}
&-bottom-tools {
display: flex;
align-items: center;
column-gap: 20px;
font-size: 14px;
}
&-active-identifier {
position: absolute;
bottom: -6px;
width: 100%;
height: 4px;
background-color: var(--chat-blue);
border-radius: 4px 4px 0 0;
}
&-date-option-divider {
width: 1px;
height: 16px;
background-color: var(--text-color-fifth);
}
} }
.@{metric-info-prefix-cls} { .@{metric-info-prefix-cls} {

View File

@@ -11,6 +11,39 @@
justify-content: center; justify-content: center;
} }
&-filter-section-wrapper {
display: flex;
align-items: center;
color: var(--text-color-third);
margin-bottom: 12px;
}
&-filter-section {
display: flex;
align-items: center;
font-size: 13px;
column-gap: 12px;
color: var(--text-color-third);
}
&-filter-item {
display: flex;
align-items: center;
}
&-filter-item-label {
color: var(--text-color-third);
}
&-filter-item-value {
color: var(--text-color);
font-weight: 500;
}
&-drill-down-dimensions {
margin-top: 12px;
}
table { table {
width: 100%; width: 100%;
} }

View File

@@ -2,12 +2,14 @@ import Bar from './Bar';
import MetricCard from './MetricCard'; import MetricCard from './MetricCard';
import MetricTrend from './MetricTrend'; import MetricTrend from './MetricTrend';
import Table from './Table'; import Table from './Table';
import { ColumnType, DrillDownDimensionType, MsgDataType } from '../../common/type'; import { ColumnType, DrillDownDimensionType, FieldType, MsgDataType } from '../../common/type';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { queryData } from '../../service'; import { queryData } from '../../service';
import classNames from 'classnames'; import classNames from 'classnames';
import { PREFIX_CLS } from '../../common/constants'; import { PREFIX_CLS } from '../../common/constants';
import Text from './Text'; import Text from './Text';
import DrillDownDimensions from '../DrillDownDimensions';
import MetricOptions from '../MetricOptions';
type Props = { type Props = {
data: MsgDataType; data: MsgDataType;
@@ -16,14 +18,18 @@ type Props = {
}; };
const ChatMsg: React.FC<Props> = ({ data, chartIndex, triggerResize }) => { const ChatMsg: React.FC<Props> = ({ data, chartIndex, triggerResize }) => {
const { queryColumns, queryResults, chatContext, queryMode } = data; const { queryColumns, queryResults, chatContext, queryMode } = data || {};
const { dimensionFilters, elementMatches } = chatContext || {};
const [columns, setColumns] = useState<ColumnType[]>(); const [columns, setColumns] = useState<ColumnType[]>();
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 [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [defaultMetricField, setDefaultMetricField] = useState<FieldType>();
const [activeMetricField, setActiveMetricField] = useState<FieldType>();
const [dateModeValue, setDateModeValue] = useState<any>();
const [currentDateOption, setCurrentDateOption] = useState<number>();
const prefixCls = `${PREFIX_CLS}-chat-msg`; const prefixCls = `${PREFIX_CLS}-chat-msg`;
@@ -36,7 +42,11 @@ const ChatMsg: React.FC<Props> = ({ data, chartIndex, triggerResize }) => {
useEffect(() => { useEffect(() => {
updateColummns(queryColumns); updateColummns(queryColumns);
setDataSource(queryResults); setDataSource(queryResults);
}, [queryColumns, queryResults]); setDefaultMetricField(chatContext?.metrics?.[0]);
setActiveMetricField(chatContext?.metrics?.[0]);
setDateModeValue(chatContext?.dateInfo?.dateMode);
setCurrentDateOption(chatContext?.dateInfo?.unit);
}, [data]);
if (!queryColumns || !queryResults || !columns) { if (!queryColumns || !queryResults || !columns) {
return null; return null;
@@ -69,6 +79,52 @@ const ChatMsg: React.FC<Props> = ({ data, chartIndex, triggerResize }) => {
queryMode === 'ENTITY_DIMENSION' || queryMode === 'ENTITY_DIMENSION' ||
(categoryField.length === 1 && metricFields.length === 0)); (categoryField.length === 1 && metricFields.length === 0));
const getMsgContent = () => {
if (isText) {
return <Text columns={columns} referenceColumn={referenceColumn} dataSource={dataSource} />;
}
if (isMetricCard) {
return (
<MetricCard
data={{ ...data, queryColumns: columns, queryResults: dataSource }}
loading={loading}
/>
);
}
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 (categoryField?.length > 0 && metricFields?.length > 0) {
return (
<Bar
data={{ ...data, queryColumns: columns, queryResults: dataSource }}
triggerResize={triggerResize}
loading={loading}
/>
);
}
return <Table data={{ ...data, queryColumns: columns, queryResults: dataSource }} />;
};
const onLoadData = async (value: any) => { const onLoadData = async (value: any) => {
setLoading(true); setLoading(true);
const { data } = await queryData({ const { data } = await queryData({
@@ -85,58 +141,94 @@ const ChatMsg: React.FC<Props> = ({ data, chartIndex, triggerResize }) => {
const onSelectDimension = (dimension?: DrillDownDimensionType) => { const onSelectDimension = (dimension?: DrillDownDimensionType) => {
setDrillDownDimension(dimension); setDrillDownDimension(dimension);
onLoadData({ onLoadData({
dimensions: dateInfo: {
dimension === undefined ? undefined : [...(chatContext.dimensions || []), dimension], ...chatContext.dateInfo,
dateMode: dateModeValue,
unit: currentDateOption || chatContext.dateInfo.unit,
},
dimensions: dimension
? [...(chatContext.dimensions || []), dimension]
: chatContext.dimensions,
metrics: [activeMetricField || defaultMetricField],
}); });
}; };
const getMsgContent = () => { const onSwitchMetric = (metricField?: FieldType) => {
if (isText) { setActiveMetricField(metricField);
return <Text columns={columns} referenceColumn={referenceColumn} dataSource={dataSource} />; onLoadData({
} dateInfo: {
if (isMetricCard) { ...chatContext.dateInfo,
return ( dateMode: dateModeValue,
<MetricCard unit: currentDateOption || chatContext.dateInfo.unit,
data={{ ...data, queryColumns: columns, queryResults: dataSource }} },
loading={loading} dimensions: drillDownDimension
drillDownDimension={drillDownDimension} ? [...(chatContext.dimensions || []), drillDownDimension]
onSelectDimension={onSelectDimension} : chatContext.dimensions,
/> metrics: [metricField || defaultMetricField],
); });
} };
if (isTable) {
return <Table data={{ ...data, queryColumns: columns, queryResults: dataSource }} />; const selectDateOption = (dateOption: number) => {
} setCurrentDateOption(dateOption);
if (dateField && metricFields.length > 0) { setDateModeValue('RECENT');
if (!dataSource.every(item => item[dateField.nameEn] === dataSource[0][dateField.nameEn])) { onLoadData({
return ( metrics: [activeMetricField || defaultMetricField],
<MetricTrend dimensions: drillDownDimension
data={{ ...data, queryColumns: columns, queryResults: dataSource }} ? [...(chatContext.dimensions || []), drillDownDimension]
chartIndex={chartIndex} : chatContext.dimensions,
triggerResize={triggerResize} dateInfo: {
/> ...chatContext?.dateInfo,
); dateMode: 'RECENT',
} unit: dateOption,
} },
if (categoryField?.length > 0 && metricFields?.length > 0) { });
return (
<Bar
data={{ ...data, queryColumns: columns, queryResults: dataSource }}
triggerResize={triggerResize}
loading={loading}
drillDownDimension={drillDownDimension}
onSelectDimension={onSelectDimension}
/>
);
}
return <Table data={{ ...data, queryColumns: columns, queryResults: dataSource }} />;
}; };
const chartMsgClass = classNames({ [prefixCls]: !isTable }); const chartMsgClass = classNames({ [prefixCls]: !isTable });
const entityId = dimensionFilters?.length > 0 ? dimensionFilters[0].value : undefined;
const entityName = elementMatches?.find((item: any) => item.element?.type === 'ID')?.element
?.name;
const isEntityMode =
(queryMode === 'ENTITY_LIST_FILTER' || queryMode === 'METRIC_ENTITY') &&
typeof entityId === 'string' &&
entityName !== undefined;
const existDrillDownDimension = queryMode.includes('METRIC') && !isText && !isEntityMode;
const isMultipleMetric = existDrillDownDimension && chatContext?.metrics?.length > 1;
return ( return (
<div className={chartMsgClass}> <div className={chartMsgClass}>
{dataSource?.length === 0 ? <div></div> : getMsgContent()} {dataSource?.length === 0 ? (
<div></div>
) : (
<div>
{getMsgContent()}
{(isMultipleMetric || existDrillDownDimension) && (
<div className={`${prefixCls}-bottom-tools`}>
{isMultipleMetric && (
<MetricOptions
metrics={chatContext.metrics}
defaultMetric={defaultMetricField}
currentMetric={activeMetricField}
onSelectMetric={onSwitchMetric}
/>
)}
{existDrillDownDimension && (
<DrillDownDimensions
modelId={chatContext.modelId}
drillDownDimension={drillDownDimension}
originDimensions={chatContext.dimensions}
dimensionFilters={chatContext.dimensionFilters}
onSelectDimension={onSelectDimension}
/>
)}
</div>
)}
</div>
)}
</div> </div>
); );
}; };

View File

@@ -7,4 +7,12 @@
border: 1px solid var(--border-color-base); border: 1px solid var(--border-color-base);
border-radius: 4px; border-radius: 4px;
background: #f5f8fb; background: #f5f8fb;
&-bottom-tools {
display: flex;
align-items: center;
column-gap: 20px;
font-size: 14px;
margin-top: 6px;
}
} }

View File

@@ -10,6 +10,7 @@ type Props = {
modelId: number; modelId: number;
drillDownDimension?: DrillDownDimensionType; drillDownDimension?: DrillDownDimensionType;
isMetricCard?: boolean; isMetricCard?: boolean;
originDimensions?: DrillDownDimensionType[];
dimensionFilters?: FilterItemType[]; dimensionFilters?: FilterItemType[];
onSelectDimension: (dimension?: DrillDownDimensionType) => void; onSelectDimension: (dimension?: DrillDownDimensionType) => void;
}; };
@@ -20,6 +21,7 @@ const DrillDownDimensions: React.FC<Props> = ({
modelId, modelId,
drillDownDimension, drillDownDimension,
isMetricCard, isMetricCard,
originDimensions,
dimensionFilters, dimensionFilters,
onSelectDimension, onSelectDimension,
}) => { }) => {
@@ -33,7 +35,11 @@ const DrillDownDimensions: React.FC<Props> = ({
const res = await queryDrillDownDimensions(modelId); const res = await queryDrillDownDimensions(modelId);
setDimensions( setDimensions(
res.data.data.dimensions res.data.data.dimensions
.filter(dimension => !dimensionFilters?.some(filter => filter.name === dimension.name)) .filter(
dimension =>
!dimensionFilters?.some(filter => filter.name === dimension.name) &&
(!originDimensions || !originDimensions.some(item => item.id === dimension.id))
)
.slice(0, MAX_DIMENSION_COUNT) .slice(0, MAX_DIMENSION_COUNT)
); );
}; };

View File

@@ -1,7 +1,7 @@
import { createFromIconfontCN } from '@ant-design/icons'; import { createFromIconfontCN } from '@ant-design/icons';
const IconFont = createFromIconfontCN({ const IconFont = createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/c/font_4120566_sz2crkuyuj.js', scriptUrl: '//at.alicdn.com/t/c/font_4120566_x5c4www9bqm.js',
}); });
export default IconFont; export default IconFont;

View File

@@ -1,7 +1,7 @@
import axios from './axiosInstance'; import axios from './axiosInstance';
import { ChatContextType, DrillDownDimensionType, HistoryType, MsgDataType, ParseDataType, SearchRecommendItem } from '../common/type'; import { ChatContextType, DrillDownDimensionType, HistoryType, MsgDataType, ParseDataType, SearchRecommendItem } from '../common/type';
const DEFAULT_CHAT_ID = 0; const DEFAULT_CHAT_ID = 12009993;
const prefix = '/api'; const prefix = '/api';

View File

@@ -22,6 +22,8 @@
@import "../components/ChatMsg/FilterSection/style.less"; @import "../components/ChatMsg/FilterSection/style.less";
@import "../components/ChatMsg/DateOptions/style.less";
@import "../components/ChatMsg/Text/style.less"; @import "../components/ChatMsg/Text/style.less";
@import '../components/ChatItem/style.less'; @import '../components/ChatItem/style.less';

View File

@@ -46,7 +46,7 @@
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
margin: 16px 0 0; margin: 16px 0 1px;
row-gap: 8px; row-gap: 8px;
} }