[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 SEARCH_EXCEPTION_TIP = '查询出错啦,智能助理还不够聪明,请您换个表达再试试';
export const SEARCH_EXCEPTION_TIP = '查询出错啦,数据库可能出现异常或者负载繁忙,请联系管理员或者稍后重试';
export const MSG_VALID_TIP = {
[MsgValidTypeEnum.SEARCH_EXCEPTION]: '数据查询异常',

View File

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

View File

@@ -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>
)}

View File

@@ -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>
);
};

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}-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`}>

View File

@@ -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>
);
};

View File

@@ -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>

View File

@@ -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} {

View File

@@ -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%;
}

View File

@@ -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>
);
};

View File

@@ -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;
}
}

View File

@@ -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)
);
};

View File

@@ -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;

View File

@@ -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';

View File

@@ -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';

View File

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