mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-10 19:51:00 +00:00
[improvement](webapp) optimize drill down dimensions (#84)
This commit is contained in:
@@ -42,7 +42,7 @@ export const THEME_COLOR_LIST = [
|
||||
|
||||
export const PARSE_ERROR_TIP = '智能助理不太懂您说什么呐,回去一定补充知识';
|
||||
|
||||
export const SEARCH_EXCEPTION_TIP = '查询出错啦,智能助理还不够聪明,请您换个表达再试试';
|
||||
export const SEARCH_EXCEPTION_TIP = '查询出错啦,数据库可能出现异常或者负载繁忙,请联系管理员或者稍后重试';
|
||||
|
||||
export const MSG_VALID_TIP = {
|
||||
[MsgValidTypeEnum.SEARCH_EXCEPTION]: '数据查询异常',
|
||||
|
||||
@@ -173,7 +173,7 @@ export enum SemanticTypeEnum {
|
||||
};
|
||||
|
||||
export const SEMANTIC_TYPE_MAP = {
|
||||
[SemanticTypeEnum.DOMAIN]: '主题域',
|
||||
[SemanticTypeEnum.DOMAIN]: '数据模型',
|
||||
[SemanticTypeEnum.DIMENSION]: '维度',
|
||||
[SemanticTypeEnum.METRIC]: '指标',
|
||||
[SemanticTypeEnum.VALUE]: '维度值',
|
||||
|
||||
@@ -165,7 +165,7 @@ const ParseTip: React.FC<Props> = ({
|
||||
</div>
|
||||
) : (
|
||||
<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>
|
||||
)}
|
||||
|
||||
@@ -1,37 +1,24 @@
|
||||
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 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?: (model: string) => void;
|
||||
};
|
||||
|
||||
const BarChart: React.FC<Props> = ({
|
||||
data,
|
||||
triggerResize,
|
||||
drillDownDimension,
|
||||
loading,
|
||||
onSelectDimension,
|
||||
onApplyAuth,
|
||||
}) => {
|
||||
const BarChart: React.FC<Props> = ({ data, triggerResize, loading, onApplyAuth }) => {
|
||||
const chartRef = useRef<any>();
|
||||
const [instance, setInstance] = useState<ECharts>();
|
||||
|
||||
const { queryColumns, queryResults, entityInfo, chatContext, queryMode } = data;
|
||||
|
||||
const { dateInfo, dimensionFilters } = chatContext || {};
|
||||
const { queryColumns, queryResults, entityInfo } = data;
|
||||
|
||||
const categoryColumnName =
|
||||
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`;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={`${prefixCls}-top-bar`}>
|
||||
<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>
|
||||
{/* {dateInfo && (
|
||||
<div className={`${prefixCls}-date-range`}>
|
||||
{dateInfo.startDate === dateInfo.endDate
|
||||
? dateInfo.startDate
|
||||
: `${dateInfo.startDate} ~ ${dateInfo.endDate}`}
|
||||
</div>
|
||||
)} */}
|
||||
<Spin spinning={loading}>
|
||||
<div className={`${prefixCls}-chart`} ref={chartRef} />
|
||||
</Spin>
|
||||
{queryMode.includes('METRIC') && (
|
||||
<DrillDownDimensions
|
||||
modelId={chatContext.modelId}
|
||||
drillDownDimension={drillDownDimension}
|
||||
dimensionFilters={chatContext.dimensionFilters}
|
||||
onSelectDimension={onSelectDimension}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ const Message: React.FC<Props> = ({
|
||||
<div className={`${prefixCls}-info-bar`}>
|
||||
<div className={`${prefixCls}-main-entity-info`}>
|
||||
<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>
|
||||
<div className={`${prefixCls}-info-item`}>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { PREFIX_CLS } from '../../../common/constants';
|
||||
import { formatByDecimalPlaces, formatMetric, formatNumberWithCN } from '../../../utils/utils';
|
||||
import ApplyAuth from '../ApplyAuth';
|
||||
import { DrillDownDimensionType, MsgDataType } from '../../../common/type';
|
||||
import { MsgDataType } from '../../../common/type';
|
||||
import PeriodCompareItem from './PeriodCompareItem';
|
||||
import DrillDownDimensions from '../../DrillDownDimensions';
|
||||
import { Spin } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { SwapOutlined } from '@ant-design/icons';
|
||||
@@ -11,20 +10,12 @@ import { useState } from 'react';
|
||||
|
||||
type Props = {
|
||||
data: MsgDataType;
|
||||
drillDownDimension?: DrillDownDimensionType;
|
||||
loading: boolean;
|
||||
onSelectDimension: (dimension?: DrillDownDimensionType) => void;
|
||||
onApplyAuth?: (model: string) => void;
|
||||
};
|
||||
|
||||
const MetricCard: React.FC<Props> = ({
|
||||
data,
|
||||
drillDownDimension,
|
||||
loading,
|
||||
onSelectDimension,
|
||||
onApplyAuth,
|
||||
}) => {
|
||||
const { queryMode, queryColumns, queryResults, entityInfo, aggregateInfo, chatContext } = data;
|
||||
const MetricCard: React.FC<Props> = ({ data, loading, onApplyAuth }) => {
|
||||
const { queryMode, queryColumns, queryResults, entityInfo, aggregateInfo } = data;
|
||||
|
||||
const { metricInfos } = aggregateInfo || {};
|
||||
|
||||
@@ -57,20 +48,6 @@ const MetricCard: React.FC<Props> = ({
|
||||
) : (
|
||||
<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>
|
||||
<Spin spinning={loading}>
|
||||
<div className={indicatorClass}>
|
||||
@@ -104,16 +81,6 @@ const MetricCard: React.FC<Props> = ({
|
||||
)}
|
||||
</div>
|
||||
</Spin>
|
||||
{queryMode.includes('METRIC') && (
|
||||
<div className={`${prefixCls}-drill-down-dimensions`}>
|
||||
<DrillDownDimensions
|
||||
modelId={chatContext?.modelId}
|
||||
dimensionFilters={chatContext?.dimensionFilters}
|
||||
drillDownDimension={drillDownDimension}
|
||||
onSelectDimension={onSelectDimension}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,150 +1,48 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { CLS_PREFIX, DATE_TYPES } from '../../../common/constants';
|
||||
import { ColumnType, DrillDownDimensionType, FieldType, MsgDataType } from '../../../common/type';
|
||||
import { CLS_PREFIX } from '../../../common/constants';
|
||||
import { 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 MetricOptions from '../../MetricOptions';
|
||||
import DateOptions from '../DateOptions';
|
||||
|
||||
type Props = {
|
||||
data: MsgDataType;
|
||||
chartIndex: number;
|
||||
triggerResize?: boolean;
|
||||
loading: boolean;
|
||||
activeMetricField?: FieldType;
|
||||
currentDateOption?: number;
|
||||
onApplyAuth?: (model: string) => void;
|
||||
onSelectDateOption: (value: number) => void;
|
||||
};
|
||||
|
||||
const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApplyAuth }) => {
|
||||
const { entityInfo, chatContext, queryMode } = data;
|
||||
const { dateInfo, dimensionFilters, elementMatches } = chatContext || {};
|
||||
const { dateMode, unit } = dateInfo || {};
|
||||
const dateOptions = DATE_TYPES[chatContext?.dateInfo?.period] || DATE_TYPES.DAY;
|
||||
const MetricTrend: React.FC<Props> = ({
|
||||
data,
|
||||
chartIndex,
|
||||
triggerResize,
|
||||
loading,
|
||||
activeMetricField,
|
||||
currentDateOption,
|
||||
onApplyAuth,
|
||||
onSelectDateOption,
|
||||
}) => {
|
||||
const { queryColumns, queryResults, aggregateInfo, entityInfo, chatContext } = data;
|
||||
|
||||
const [columns, setColumns] = useState<ColumnType[]>([]);
|
||||
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(
|
||||
const dateField: any = queryColumns?.find(
|
||||
(column: any) => column.showType === 'DATE' || column.type === 'DATE'
|
||||
);
|
||||
const dateColumnName = dateField?.nameEn || '';
|
||||
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 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');
|
||||
const currentMetricField = queryColumns?.find((column: any) => column.showType === 'NUMBER');
|
||||
|
||||
if (!currentMetricField) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isMultipleMetric = chatContext?.metrics?.length > 1;
|
||||
const existDrillDownDimension = queryMode.includes('METRIC') && !isEntityMode;
|
||||
|
||||
const prefixCls = `${CLS_PREFIX}-metric-trend`;
|
||||
|
||||
return (
|
||||
@@ -157,92 +55,31 @@ const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApply
|
||||
>
|
||||
{activeMetricField?.name}
|
||||
</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>
|
||||
<Spin spinning={loading}>
|
||||
<div className={`${prefixCls}-content`}>
|
||||
{!isMobile && aggregateInfoValue?.metricInfos?.length > 0 && (
|
||||
<MetricInfo
|
||||
aggregateInfo={aggregateInfoValue}
|
||||
currentMetricField={currentMetricField}
|
||||
{!isMobile && aggregateInfo?.metricInfos?.length > 0 && (
|
||||
<MetricInfo aggregateInfo={aggregateInfo} currentMetricField={currentMetricField} />
|
||||
)}
|
||||
<DateOptions
|
||||
chatContext={chatContext}
|
||||
currentDateOption={currentDateOption}
|
||||
onSelectDateOption={onSelectDateOption}
|
||||
/>
|
||||
)}
|
||||
<div className={`${prefixCls}-date-options`}>
|
||||
{dateOptions.map((dateOption: { label: string; value: number }, index: number) => {
|
||||
const dateOptionClass = classNames(`${prefixCls}-date-option`, {
|
||||
[`${prefixCls}-date-active`]: dateOption.value === currentDateOption,
|
||||
[`${prefixCls}-date-mobile`]: isMobile,
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<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} />
|
||||
{queryResults?.length === 1 || chartIndex % 2 === 1 ? (
|
||||
<Table data={{ ...data, queryResults }} onApplyAuth={onApplyAuth} />
|
||||
) : (
|
||||
<MetricTrendChart
|
||||
model={entityInfo?.modelInfo.name}
|
||||
dateColumnName={dateColumnName}
|
||||
categoryColumnName={categoryColumnName}
|
||||
metricField={currentMetricField}
|
||||
resultList={dataSource}
|
||||
resultList={queryResults}
|
||||
triggerResize={triggerResize}
|
||||
onApplyAuth={onApplyAuth}
|
||||
/>
|
||||
)}
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -139,53 +139,6 @@
|
||||
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} {
|
||||
|
||||
@@ -11,6 +11,39 @@
|
||||
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 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@ import Bar from './Bar';
|
||||
import MetricCard from './MetricCard';
|
||||
import MetricTrend from './MetricTrend';
|
||||
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 { queryData } from '../../service';
|
||||
import classNames from 'classnames';
|
||||
import { PREFIX_CLS } from '../../common/constants';
|
||||
import Text from './Text';
|
||||
import DrillDownDimensions from '../DrillDownDimensions';
|
||||
import MetricOptions from '../MetricOptions';
|
||||
|
||||
type Props = {
|
||||
data: MsgDataType;
|
||||
@@ -16,14 +18,18 @@ type Props = {
|
||||
};
|
||||
|
||||
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 [referenceColumn, setReferenceColumn] = useState<ColumnType>();
|
||||
const [dataSource, setDataSource] = useState<any[]>(queryResults);
|
||||
|
||||
const [drillDownDimension, setDrillDownDimension] = useState<DrillDownDimensionType>();
|
||||
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`;
|
||||
|
||||
@@ -36,7 +42,11 @@ const ChatMsg: React.FC<Props> = ({ data, chartIndex, triggerResize }) => {
|
||||
useEffect(() => {
|
||||
updateColummns(queryColumns);
|
||||
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) {
|
||||
return null;
|
||||
@@ -69,6 +79,52 @@ const ChatMsg: React.FC<Props> = ({ data, chartIndex, triggerResize }) => {
|
||||
queryMode === 'ENTITY_DIMENSION' ||
|
||||
(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) => {
|
||||
setLoading(true);
|
||||
const { data } = await queryData({
|
||||
@@ -85,58 +141,94 @@ const ChatMsg: React.FC<Props> = ({ data, chartIndex, triggerResize }) => {
|
||||
const onSelectDimension = (dimension?: DrillDownDimensionType) => {
|
||||
setDrillDownDimension(dimension);
|
||||
onLoadData({
|
||||
dimensions:
|
||||
dimension === undefined ? undefined : [...(chatContext.dimensions || []), dimension],
|
||||
dateInfo: {
|
||||
...chatContext.dateInfo,
|
||||
dateMode: dateModeValue,
|
||||
unit: currentDateOption || chatContext.dateInfo.unit,
|
||||
},
|
||||
dimensions: dimension
|
||||
? [...(chatContext.dimensions || []), dimension]
|
||||
: chatContext.dimensions,
|
||||
metrics: [activeMetricField || defaultMetricField],
|
||||
});
|
||||
};
|
||||
|
||||
const getMsgContent = () => {
|
||||
if (isText) {
|
||||
return <Text columns={columns} referenceColumn={referenceColumn} dataSource={dataSource} />;
|
||||
}
|
||||
if (isMetricCard) {
|
||||
return (
|
||||
<MetricCard
|
||||
data={{ ...data, queryColumns: columns, queryResults: dataSource }}
|
||||
loading={loading}
|
||||
drillDownDimension={drillDownDimension}
|
||||
onSelectDimension={onSelectDimension}
|
||||
/>
|
||||
);
|
||||
}
|
||||
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 }}
|
||||
chartIndex={chartIndex}
|
||||
triggerResize={triggerResize}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
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 onSwitchMetric = (metricField?: FieldType) => {
|
||||
setActiveMetricField(metricField);
|
||||
onLoadData({
|
||||
dateInfo: {
|
||||
...chatContext.dateInfo,
|
||||
dateMode: dateModeValue,
|
||||
unit: currentDateOption || chatContext.dateInfo.unit,
|
||||
},
|
||||
dimensions: drillDownDimension
|
||||
? [...(chatContext.dimensions || []), drillDownDimension]
|
||||
: chatContext.dimensions,
|
||||
metrics: [metricField || defaultMetricField],
|
||||
});
|
||||
};
|
||||
|
||||
const selectDateOption = (dateOption: number) => {
|
||||
setCurrentDateOption(dateOption);
|
||||
setDateModeValue('RECENT');
|
||||
onLoadData({
|
||||
metrics: [activeMetricField || defaultMetricField],
|
||||
dimensions: drillDownDimension
|
||||
? [...(chatContext.dimensions || []), drillDownDimension]
|
||||
: chatContext.dimensions,
|
||||
dateInfo: {
|
||||
...chatContext?.dateInfo,
|
||||
dateMode: 'RECENT',
|
||||
unit: dateOption,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
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 (
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,4 +7,12 @@
|
||||
border: 1px solid var(--border-color-base);
|
||||
border-radius: 4px;
|
||||
background: #f5f8fb;
|
||||
|
||||
&-bottom-tools {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 20px;
|
||||
font-size: 14px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ type Props = {
|
||||
modelId: number;
|
||||
drillDownDimension?: DrillDownDimensionType;
|
||||
isMetricCard?: boolean;
|
||||
originDimensions?: DrillDownDimensionType[];
|
||||
dimensionFilters?: FilterItemType[];
|
||||
onSelectDimension: (dimension?: DrillDownDimensionType) => void;
|
||||
};
|
||||
@@ -20,6 +21,7 @@ const DrillDownDimensions: React.FC<Props> = ({
|
||||
modelId,
|
||||
drillDownDimension,
|
||||
isMetricCard,
|
||||
originDimensions,
|
||||
dimensionFilters,
|
||||
onSelectDimension,
|
||||
}) => {
|
||||
@@ -33,7 +35,11 @@ const DrillDownDimensions: React.FC<Props> = ({
|
||||
const res = await queryDrillDownDimensions(modelId);
|
||||
setDimensions(
|
||||
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)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createFromIconfontCN } from '@ant-design/icons';
|
||||
|
||||
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;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import axios from './axiosInstance';
|
||||
import { ChatContextType, DrillDownDimensionType, HistoryType, MsgDataType, ParseDataType, SearchRecommendItem } from '../common/type';
|
||||
|
||||
const DEFAULT_CHAT_ID = 0;
|
||||
const DEFAULT_CHAT_ID = 12009993;
|
||||
|
||||
const prefix = '/api';
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
@import "../components/ChatMsg/FilterSection/style.less";
|
||||
|
||||
@import "../components/ChatMsg/DateOptions/style.less";
|
||||
|
||||
@import "../components/ChatMsg/Text/style.less";
|
||||
|
||||
@import '../components/ChatItem/style.less';
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
margin: 16px 0 0;
|
||||
margin: 16px 0 1px;
|
||||
row-gap: 8px;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user