add chat plugin and split query to parse and execute (#25)

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

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

* [feature](webapp) gitignore add supersonic-webapp

* [feature](webapp) gitignore add supersonic-webapp

* [feature](webapp) add chat plugin and split query to parse and execute

* [feature](webapp) add chat plugin and split query to parse and execute

* [feature](webapp) add chat plugin and split query to parse and execute

---------

Co-authored-by: williamhliu <williamhliu@tencent.com>
This commit is contained in:
williamhliu
2023-08-05 22:17:42 +08:00
committed by GitHub
parent c9baed6c4e
commit 6951eada9d
86 changed files with 3193 additions and 1595 deletions

View File

@@ -31,7 +31,7 @@ const BarChart: React.FC<Props> = ({
const { queryColumns, queryResults, entityInfo, chatContext, queryMode } = data;
const { dateInfo } = chatContext || {};
const { dateInfo, dimensionFilters } = chatContext || {};
const categoryColumnName =
queryColumns?.find(column => column.showType === 'CATEGORY')?.nameEn || '';
@@ -51,13 +51,6 @@ const BarChart: React.FC<Props> = ({
);
const xData = data.map(item => item[categoryColumnName]);
instanceObj.setOption({
// legend: {
// left: 0,
// top: 0,
// icon: 'rect',
// itemWidth: 15,
// itemHeight: 5,
// },
xAxis: {
type: 'category',
axisTick: {
@@ -166,21 +159,43 @@ const BarChart: React.FC<Props> = ({
);
}
const hasFilterSection = dimensionFilters?.length > 0;
const prefixCls = `${PREFIX_CLS}-bar`;
return (
<div>
<div className={`${PREFIX_CLS}-bar-metric-name`}>{metricColumn?.name}</div>
<FilterSection chatContext={chatContext} />
<div className={`${prefixCls}-top-bar`}>
<div className={`${prefixCls}-indicator-name`}>{metricColumn?.name}</div>
{(hasFilterSection || 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={`${PREFIX_CLS}-bar-date-range`}>
<div className={`${prefixCls}-date-range`}>
{dateInfo.startDate === dateInfo.endDate
? dateInfo.startDate
: `${dateInfo.startDate} ~ ${dateInfo.endDate}`}
</div>
)}
<Spin spinning={loading}>
<div className={`${PREFIX_CLS}-bar-chart`} ref={chartRef} />
<div className={`${prefixCls}-chart`} ref={chartRef} />
</Spin>
{(queryMode === 'METRIC_DOMAIN' || queryMode === 'METRIC_FILTER') && (
{(queryMode === 'METRIC_DOMAIN' ||
queryMode === 'METRIC_FILTER' ||
queryMode === 'METRIC_GROUPBY') && (
<DrillDownDimensions
domainId={chatContext.domainId}
drillDownDimension={drillDownDimension}

View File

@@ -9,11 +9,49 @@
margin-top: 16px;
}
&-metric-name {
font-size: 15px;
&-top-bar {
display: flex;
align-items: baseline;
flex-wrap: wrap;
column-gap: 8px;
row-gap: 12px;
}
&-filter-section-wrapper {
display: flex;
align-items: center;
color: var(--text-color-third);
}
&-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;
}
&-indicator-name {
font-size: 14px;
color: var(--text-color);
font-weight: 500;
margin-top: 2px;
}
&-date-range {
margin-top: 12px;
font-size: 13px;

View File

@@ -1,13 +1,17 @@
import { PREFIX_CLS } from '../../../common/constants';
import { ChatContextType } from '../../../common/type';
import { ChatContextType, EntityInfoType } from '../../../common/type';
type Props = {
chatContext?: ChatContextType;
entityInfo?: EntityInfoType;
};
const FilterSection: React.FC<Props> = ({ chatContext }) => {
const FilterSection: React.FC<Props> = ({ chatContext, entityInfo }) => {
const prefixCls = `${PREFIX_CLS}-filter-section`;
const entityInfoList =
entityInfo?.dimensions?.filter(dimension => !dimension.bizName.includes('photo')) || [];
const { dimensionFilters } = chatContext || {};
const hasFilterSection = dimensionFilters && dimensionFilters.length > 0;
@@ -16,7 +20,7 @@ const FilterSection: React.FC<Props> = ({ chatContext }) => {
<div className={prefixCls}>
<div className={`${prefixCls}-field-label`}></div>
<div className={`${prefixCls}-filter-values`}>
{dimensionFilters.map(filterItem => {
{(entityInfoList.length > 0 ? entityInfoList : dimensionFilters).map(filterItem => {
const filterValue =
typeof filterItem.value === 'string' ? [filterItem.value] : filterItem.value || [];
return (

View File

@@ -5,21 +5,25 @@
.@{filter-section-prefix-cls} {
display: flex;
align-items: center;
flex-wrap: wrap;
row-gap: 12px;
color: var(--text-color-secondary);
font-weight: normal;
font-size: 13px;
&-field-label {
color: var(--text-color-fourth);
}
&-filter-values {
display: flex;
align-items: center;
column-gap: 6px;
flex-wrap: wrap;
column-gap: 12px;
}
&-filter-item {
padding: 2px 12px;
color: var(--text-color-third);
background-color: #edf2f2;
border-radius: 13px;
max-width: 200px;
text-overflow: ellipsis;
white-space: nowrap;

View File

@@ -12,67 +12,28 @@ type Props = {
entityInfo?: EntityInfoType;
children?: React.ReactNode;
isMobileMode?: boolean;
queryMode?: string;
};
const Message: React.FC<Props> = ({
position,
width,
height,
title,
followQuestions,
children,
bubbleClassName,
chatContext,
entityInfo,
isMobileMode,
queryMode,
chatContext,
}) => {
const { dimensionFilters, domainName } = chatContext || {};
const prefixCls = `${PREFIX_CLS}-message`;
const { domainName, dateInfo, dimensionFilters } = chatContext || {};
const { startDate, endDate } = dateInfo || {};
const entityInfoList =
entityInfo?.dimensions?.filter(dimension => !dimension.bizName.includes('photo')) || [];
const hasFilterSection =
dimensionFilters && dimensionFilters.length > 0 && entityInfoList.length === 0;
const filterSection = hasFilterSection && (
<div className={`${prefixCls}-filter-section`}>
<div className={`${prefixCls}-field-name`}></div>
<div className={`${prefixCls}-filter-values`}>
{dimensionFilters.map(filterItem => {
const filterValue =
typeof filterItem.value === 'string' ? [filterItem.value] : filterItem.value || [];
return (
<div
className={`${prefixCls}-filter-item`}
key={filterItem.name}
title={filterValue.join('、')}
>
{filterItem.name}{filterValue.join('、')}
</div>
);
})}
</div>
</div>
);
const leftTitle = title
? followQuestions && followQuestions.length > 0
? `多轮对话:${[title, ...followQuestions].join(' ← ')}`
: `单轮对话:${title}`
: '';
return (
<div className={prefixCls}>
<div className={`${prefixCls}-title-bar`}>
{domainName && <div className={`${prefixCls}-domain-name`}>{domainName}</div>}
{position === 'left' && leftTitle && (
<div className={`${prefixCls}-top-bar`} title={leftTitle}>
({leftTitle})
</div>
)}
</div>
<div className={`${prefixCls}-content`}>
<div className={`${prefixCls}-body`}>
<div
@@ -82,10 +43,9 @@ const Message: React.FC<Props> = ({
e.stopPropagation();
}}
>
{entityInfoList.length > 0 && (
<div className={`${prefixCls}-info-bar`}>
{/* {filterSection} */}
{entityInfoList.length > 0 && (
{(queryMode === 'METRIC_ENTITY' || queryMode === 'ENTITY_DETAIL') &&
entityInfoList.length > 0 && (
<div className={`${prefixCls}-info-bar`}>
<div className={`${prefixCls}-main-entity-info`}>
{entityInfoList.slice(0, 4).map(dimension => {
return (
@@ -100,7 +60,36 @@ const Message: React.FC<Props> = ({
);
})}
</div>
)}
</div>
)}
{queryMode === 'ENTITY_LIST_FILTER' && (
<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-value`}>{domainName}</div>
</div>
<div className={`${prefixCls}-info-item`}>
<div className={`${prefixCls}-info-name`}></div>
<div className={`${prefixCls}-info-value`}>
{startDate === endDate ? startDate : `${startDate} ~ ${endDate}`}
</div>
</div>
{dimensionFilters && dimensionFilters?.length > 0 && (
<div className={`${prefixCls}-info-item`}>
<div className={`${prefixCls}-info-name`}></div>
{dimensionFilters.map((filter, index) => (
<div className={`${prefixCls}-info-value`}>
<span>{filter.name}</span>
<span>
{Array.isArray(filter.value) ? filter.value.join('、') : filter.value}
</span>
{index !== dimensionFilters.length - 1 && <span></span>}
</div>
))}
</div>
)}
</div>
</div>
)}
<div className={`${prefixCls}-children`}>{children}</div>

View File

@@ -83,7 +83,8 @@
align-items: center;
row-gap: 12px;
flex-wrap: wrap;
margin-top: 4px;
margin-top: 2px;
margin-bottom: 12px;
column-gap: 20px;
color: var(--text-color-secondary);
background: rgba(133, 156, 241, 0.1);
@@ -96,7 +97,6 @@
display: flex;
flex-wrap: wrap;
align-items: center;
font-size: 13px;
column-gap: 20px;
row-gap: 10px;
}
@@ -107,11 +107,12 @@
}
&-info-name {
color: var(--text-color-third);
color: var(--text-color-secondary);
}
&-info-value {
color: var(--text-color);
font-weight: 500;
}
}

View File

@@ -1,11 +1,12 @@
import { PREFIX_CLS } from '../../../common/constants';
import { formatByThousandSeperator } from '../../../utils/utils';
import { formatMetric } from '../../../utils/utils';
import ApplyAuth from '../ApplyAuth';
import { DrillDownDimensionType, MsgDataType } from '../../../common/type';
import PeriodCompareItem from './PeriodCompareItem';
import DrillDownDimensions from '../../DrillDownDimensions';
import { Spin } from 'antd';
import classNames from 'classnames';
import FilterSection from '../FilterSection';
type Props = {
data: MsgDataType;
@@ -25,8 +26,8 @@ const MetricCard: React.FC<Props> = ({
const { queryMode, queryColumns, queryResults, entityInfo, aggregateInfo, chatContext } = data;
const { metricInfos } = aggregateInfo || {};
const { dateInfo } = chatContext || {};
const { startDate, endDate } = dateInfo || {};
const { dateInfo, dimensionFilters } = chatContext || {};
const { startDate } = dateInfo || {};
const indicatorColumn = queryColumns?.find(column => column.showType === 'NUMBER');
const indicatorColumnName = indicatorColumn?.nameEn || '';
@@ -37,19 +38,36 @@ const MetricCard: React.FC<Props> = ({
[`${prefixCls}-indicator-period-compare`]: metricInfos?.length > 0,
});
const hasFilterSection = dimensionFilters?.length > 0;
return (
<div className={prefixCls}>
<div className={`${prefixCls}-indicator-name`}>{indicatorColumn?.name}</div>
<div className={`${prefixCls}-top-bar`}>
<div className={`${prefixCls}-indicator-name`}>{indicatorColumn?.name}</div>
{(hasFilterSection || 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>
<Spin spinning={loading}>
<div className={indicatorClass}>
<div className={`${prefixCls}-date-range`}>
{startDate === endDate ? startDate : `${startDate} ~ ${endDate}`}
</div>
<div className={`${prefixCls}-date-range`}>{startDate}</div>
{indicatorColumn && !indicatorColumn?.authorized ? (
<ApplyAuth domain={entityInfo?.domainInfo.name || ''} onApplyAuth={onApplyAuth} />
) : (
<div className={`${prefixCls}-indicator-value`}>
{formatByThousandSeperator(queryResults?.[0]?.[indicatorColumnName])}
{formatMetric(queryResults?.[0]?.[indicatorColumnName]) || '-'}
</div>
)}
{metricInfos?.length > 0 && (

View File

@@ -7,6 +7,41 @@
height: 130px;
row-gap: 4px;
&-top-bar {
display: flex;
align-items: baseline;
flex-wrap: wrap;
column-gap: 8px;
}
&-filter-section-wrapper {
display: flex;
align-items: center;
color: var(--text-color-third);
}
&-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;
}
&-indicator-name {
font-size: 14px;
color: var(--text-color);
@@ -74,7 +109,7 @@
&-drill-down-dimensions {
position: absolute;
bottom: -38px;
left: 0;
bottom: -44px;
left: -16;
}
}

View File

@@ -1,5 +1,5 @@
import { PREFIX_CLS } from '../../../common/constants';
import { formatByThousandSeperator } from '../../../utils/utils';
import { formatMetric } from '../../../utils/utils';
import { AggregateInfoType } from '../../../common/type';
import PeriodCompareItem from '../MetricCard/PeriodCompareItem';
@@ -18,7 +18,7 @@ const MetricInfo: React.FC<Props> = ({ aggregateInfo }) => {
<div className={prefixCls}>
<div className={`${prefixCls}-indicator`}>
<div className={`${prefixCls}-date`}>{date}</div>
<div className={`${prefixCls}-indicator-value`}>{formatByThousandSeperator(value)}</div>
<div className={`${prefixCls}-indicator-value`}>{formatMetric(value)}</div>
{metricInfos?.length > 0 && (
<div className={`${prefixCls}-period-compare`}>
{Object.keys(statistics).map((key: any) => (

View File

@@ -46,8 +46,17 @@ const MetricTrendChart: React.FC<Props> = ({
}
const valueColumnName = metricField.nameEn;
const groupDataValue = groupByColumn(resultList, categoryColumnName);
const [startDate, endDate] = getMinMaxDate(resultList, dateColumnName);
const dataSource = resultList.map((item: any) => {
return {
...item,
[dateColumnName]: Array.isArray(item[dateColumnName])
? moment(item[dateColumnName].join('')).format('MM-DD')
: item[dateColumnName],
};
});
const groupDataValue = groupByColumn(dataSource, categoryColumnName);
const [startDate, endDate] = getMinMaxDate(dataSource, dateColumnName);
const groupData = Object.keys(groupDataValue).reduce((result: any, key) => {
result[key] =
startDate &&
@@ -61,7 +70,7 @@ const MetricTrendChart: React.FC<Props> = ({
endDate,
dateColumnName.includes('month') ? 'months' : 'days'
)
: groupDataValue[key].reverse();
: groupDataValue[key];
return result;
}, {});
@@ -167,7 +176,7 @@ const MetricTrendChart: React.FC<Props> = ({
data: data.map((item: any) => {
const value = item[valueColumnName];
return metricField.dataFormatType === 'percent' &&
metricField.dataFormat?.needmultiply100
metricField.dataFormat?.needMultiply100
? value * 100
: value;
}),

View File

@@ -10,29 +10,33 @@ import Table from '../Table';
import DrillDownDimensions from '../../DrillDownDimensions';
import MetricInfo from './MetricInfo';
import FilterSection from '../FilterSection';
import moment from 'moment';
type Props = {
data: MsgDataType;
chartIndex: number;
triggerResize?: boolean;
onApplyAuth?: (domain: string) => void;
};
const MetricTrend: React.FC<Props> = ({ data, triggerResize, onApplyAuth }) => {
const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApplyAuth }) => {
const { queryColumns, queryResults, entityInfo, chatContext, queryMode, aggregateInfo } = data;
const dateOptions = DATE_TYPES[chatContext?.dateInfo?.period] || DATE_TYPES.DAY;
const initialDateOption = dateOptions.find(
(option: any) => option.value === chatContext?.dateInfo?.unit
)?.value;
const { dateMode, unit } = chatContext?.dateInfo || {};
const [columns, setColumns] = useState<ColumnType[]>(queryColumns);
const dateOptions = DATE_TYPES[chatContext?.dateInfo?.period] || DATE_TYPES.DAY;
const initialDateOption = dateOptions.find((option: any) => {
return dateMode === 'RECENT' && option.value === unit;
})?.value;
const [columns, setColumns] = useState<ColumnType[]>(queryColumns || []);
const currentMetricField = columns.find((column: any) => column.showType === 'NUMBER');
const [activeMetricField, setActiveMetricField] = useState<FieldType>(chatContext.metrics?.[0]);
const [dataSource, setDataSource] = useState<any[]>(queryResults);
const [currentDateOption, setCurrentDateOption] = useState<number>(initialDateOption);
const [dimensions, setDimensions] = useState<FieldType[]>(chatContext?.dimensions);
const [drillDownDimension, setDrillDownDimension] = useState<DrillDownDimensionType>();
const [dateModeValue, setDateModeValue] = useState(dateMode);
const [loading, setLoading] = useState(false);
const dateField: any = columns.find(
@@ -46,6 +50,18 @@ const MetricTrend: React.FC<Props> = ({ data, triggerResize, onApplyAuth }) => {
setDataSource(queryResults);
}, [queryResults]);
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({
@@ -61,19 +77,13 @@ const MetricTrend: React.FC<Props> = ({ data, triggerResize, onApplyAuth }) => {
const selectDateOption = (dateOption: number) => {
setCurrentDateOption(dateOption);
const endDate = moment().subtract(1, 'days').format('YYYY-MM-DD');
const startDate = moment(endDate)
.subtract(dateOption - 1, 'days')
.format('YYYY-MM-DD');
setDateModeValue('RECENT');
onLoadData({
metrics: [activeMetricField],
dimensions: drillDownDimension
? [...(chatContext.dimensions || []), drillDownDimension]
: undefined,
dimensions: drillDownDimension ? [...(dimensions || []), drillDownDimension] : undefined,
dateInfo: {
...chatContext?.dateInfo,
startDate,
endDate,
dateMode: 'RECENT',
unit: dateOption,
},
});
@@ -82,10 +92,12 @@ const MetricTrend: React.FC<Props> = ({ data, triggerResize, onApplyAuth }) => {
const onSwitchMetric = (metricField: FieldType) => {
setActiveMetricField(metricField);
onLoadData({
dateInfo: { ...chatContext.dateInfo, unit: currentDateOption || chatContext.dateInfo.unit },
dimensions: drillDownDimension
? [...(chatContext.dimensions || []), drillDownDimension]
: undefined,
dateInfo: {
...chatContext.dateInfo,
dateMode: dateModeValue,
unit: currentDateOption || chatContext.dateInfo.unit,
},
dimensions: drillDownDimension ? [...(dimensions || []), drillDownDimension] : undefined,
metrics: [metricField],
});
};
@@ -93,10 +105,13 @@ const MetricTrend: React.FC<Props> = ({ data, triggerResize, onApplyAuth }) => {
const onSelectDimension = (dimension?: DrillDownDimensionType) => {
setDrillDownDimension(dimension);
onLoadData({
dateInfo: { ...chatContext.dateInfo, unit: currentDateOption || chatContext.dateInfo.unit },
dateInfo: {
...chatContext.dateInfo,
dateMode: dateModeValue,
unit: currentDateOption || chatContext.dateInfo.unit,
},
metrics: [activeMetricField],
dimensions:
dimension === undefined ? undefined : [...(chatContext.dimensions || []), dimension],
dimensions: dimension === undefined ? undefined : [...(dimensions || []), dimension],
});
};
@@ -106,36 +121,58 @@ const MetricTrend: React.FC<Props> = ({ data, triggerResize, onApplyAuth }) => {
const prefixCls = `${CLS_PREFIX}-metric-trend`;
const { dimensionFilters } = chatContext || {};
const hasFilterSection = dimensionFilters?.length > 0;
return (
<div className={prefixCls}>
<div className={`${prefixCls}-charts`}>
{chatContext.metrics.length > 0 && (
<div className={`${prefixCls}-metric-fields`}>
{chatContext.metrics.map((metricField: FieldType) => {
const metricFieldClass = classNames(`${prefixCls}-metric-field`, {
[`${prefixCls}-metric-field-active`]:
activeMetricField?.bizName === metricField.bizName &&
chatContext.metrics.length > 1,
[`${prefixCls}-metric-field-single`]: chatContext.metrics.length === 1,
});
return (
<div
className={metricFieldClass}
key={metricField.bizName}
onClick={() => {
if (chatContext.metrics.length > 1) {
onSwitchMetric(metricField);
}
}}
>
{metricField.name}
</div>
);
})}
</div>
)}
<div className={`${prefixCls}-top-bar`}>
{chatContext.metrics.length > 0 && (
<div className={`${prefixCls}-metric-fields`}>
{chatContext.metrics.slice(0, 5).map((metricField: FieldType) => {
const metricFieldClass = classNames(`${prefixCls}-metric-field`, {
[`${prefixCls}-metric-field-active`]:
activeMetricField?.bizName === metricField.bizName &&
chatContext.metrics.length > 1,
[`${prefixCls}-metric-field-single`]: chatContext.metrics.length === 1,
});
return (
<div
className={metricFieldClass}
key={metricField.bizName}
onClick={() => {
if (chatContext.metrics.length > 1) {
onSwitchMetric(metricField);
}
}}
>
{metricField.name}
</div>
);
})}
</div>
)}
{(hasFilterSection || drillDownDimension) && (
<div className={`${prefixCls}-filter-section-wrapper`}>
(
<div className={`${prefixCls}-filter-section`}>
<FilterSection chatContext={chatContext} />
{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>
{aggregateInfo?.metricInfos?.length > 0 && <MetricInfo aggregateInfo={aggregateInfo} />}
<FilterSection chatContext={chatContext} />
<div className={`${prefixCls}-date-options`}>
{dateOptions.map((dateOption: { label: string; value: number }, index: number) => {
const dateOptionClass = classNames(`${prefixCls}-date-option`, {
@@ -164,7 +201,7 @@ const MetricTrend: React.FC<Props> = ({ data, triggerResize, onApplyAuth }) => {
})}
</div>
<Spin spinning={loading}>
{dataSource?.length === 1 ? (
{dataSource?.length === 1 || chartIndex % 2 === 1 ? (
<Table data={{ ...data, queryResults: dataSource }} onApplyAuth={onApplyAuth} />
) : (
<MetricTrendChart
@@ -178,7 +215,9 @@ const MetricTrend: React.FC<Props> = ({ data, triggerResize, onApplyAuth }) => {
/>
)}
</Spin>
{(queryMode === 'METRIC_DOMAIN' || queryMode === 'METRIC_FILTER') && (
{(queryMode === 'METRIC_DOMAIN' ||
queryMode === 'METRIC_FILTER' ||
queryMode === 'METRIC_GROUPBY') && (
<DrillDownDimensions
domainId={chatContext.domainId}
drillDownDimension={drillDownDimension}

View File

@@ -13,6 +13,41 @@
width: 100%;
row-gap: 4px;
&-top-bar {
display: flex;
align-items: baseline;
flex-wrap: wrap;
row-gap: 12px;
}
&-filter-section-wrapper {
display: flex;
align-items: center;
color: var(--text-color-third);
}
&-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;
}
&-indicator {
display: flex;
flex-direction: column;
@@ -171,4 +206,3 @@
overflow-x: auto;
}
}

View File

@@ -9,15 +9,11 @@
&-holder {
width: 100%;
height: 300px;
// background-image: url(~./images/line_chart_holder.png);
// background-repeat: no-repeat;
// background-size: 100% 300px;
height: 280px;
}
&-bar-chart-holder {
margin-top: 20px;
// background-image: url(~./images/bar_chart_holder.png);
}
&-no-permission {

View File

@@ -1,25 +0,0 @@
import { Tag } from 'antd';
import React from 'react';
import { SemanticTypeEnum, SEMANTIC_TYPE_MAP } from '../../../common/type';
type Props = {
infoType?: SemanticTypeEnum;
};
const SemanticTypeTag: React.FC<Props> = ({ infoType = SemanticTypeEnum.METRIC }) => {
return (
<Tag
color={
infoType === SemanticTypeEnum.DIMENSION || infoType === SemanticTypeEnum.DOMAIN
? 'blue'
: infoType === SemanticTypeEnum.VALUE
? 'geekblue'
: 'orange'
}
>
{SEMANTIC_TYPE_MAP[infoType]}
</Tag>
);
};
export default SemanticTypeTag;

View File

@@ -1,104 +0,0 @@
import { Popover, message, Row, Col, Button, Spin } from 'antd';
import React, { useEffect, useState } from 'react';
import { SemanticTypeEnum } from '../../../common/type';
import { queryMetricInfo } from '../../../service';
import SemanticTypeTag from './SemanticTypeTag';
import { isMobile } from '../../../utils/utils';
import { CLS_PREFIX } from '../../../common/constants';
type Props = {
children: React.ReactNode;
classId?: number;
infoType?: SemanticTypeEnum;
uniqueId: string | number;
onDetailBtnClick?: (data: any) => void;
};
const SemanticInfoPopover: React.FC<Props> = ({
classId,
infoType,
uniqueId,
children,
onDetailBtnClick,
}) => {
const [semanticInfo, setSemanticInfo] = useState<any>(undefined);
const [popoverVisible, setPopoverVisible] = useState<boolean>(false);
const [loading, setLoading] = useState<boolean>(false);
const prefixCls = `${CLS_PREFIX}-semantic-info-popover`;
const text = (
<Row>
<Col flex="1">
<SemanticTypeTag infoType={infoType} />
</Col>
{onDetailBtnClick && (
<Col flex="0 1 40px">
{semanticInfo && (
<Button
type="link"
size="small"
onClick={() => {
onDetailBtnClick(semanticInfo);
}}
>
</Button>
)}
</Col>
)}
</Row>
);
const content = loading ? (
<div className={`${prefixCls}-spin-box`}>
<Spin />
</div>
) : (
<div>
<span>{semanticInfo?.description || '暂无数据'}</span>
</div>
);
const getMetricInfo = async () => {
setLoading(true);
const { data: resData } = await queryMetricInfo({
classId,
uniqueId,
});
const { code, data, msg } = resData;
setLoading(false);
if (code === '0') {
setSemanticInfo({
...data,
semanticInfoType: SemanticTypeEnum.METRIC,
});
} else {
message.error(msg);
}
};
useEffect(() => {
if (popoverVisible && !semanticInfo) {
getMetricInfo();
}
}, [popoverVisible]);
return (
<Popover
placement="top"
title={text}
content={content}
trigger="hover"
open={classId && !isMobile ? undefined : false}
onOpenChange={visible => {
setPopoverVisible(visible);
}}
overlayClassName={prefixCls}
>
{children}
</Popover>
);
};
export default SemanticInfoPopover;

View File

@@ -1,18 +0,0 @@
@import '../../../styles/index.less';
@semantic-info-popover-cls: ~'@{supersonic-chat-prefix}-semantic-info-popover';
.semantic-info-popover-cls {
max-width: 300px;
&-spin-box {
text-align: center;
padding-top: 10px;
}
.ant-popover-title{
padding: 5px 8px 4px;
}
.ant-popover-inner-content {
min-height: 60px;
min-width: 185px;
}
}

View File

@@ -3,13 +3,15 @@ import { Table as AntTable } from 'antd';
import { MsgDataType } from '../../../common/type';
import { CLS_PREFIX } from '../../../common/constants';
import ApplyAuth from '../ApplyAuth';
import { SizeType } from 'antd/es/config-provider/SizeContext';
type Props = {
data: MsgDataType;
size?: SizeType;
onApplyAuth?: (domain: string) => void;
};
const Table: React.FC<Props> = ({ data, onApplyAuth }) => {
const Table: React.FC<Props> = ({ data, size, onApplyAuth }) => {
const { entityInfo, queryColumns, queryResults } = data;
const prefixCls = `${CLS_PREFIX}-table`;
@@ -19,7 +21,7 @@ const Table: React.FC<Props> = ({ data, onApplyAuth }) => {
return {
dataIndex: nameEn,
key: nameEn,
title: name,
title: name || nameEn,
render: (value: string | number) => {
if (!authorized) {
return (
@@ -30,7 +32,7 @@ const Table: React.FC<Props> = ({ data, onApplyAuth }) => {
return (
<div className={`${prefixCls}-formatted-value`}>
{`${formatByDecimalPlaces(
dataFormat?.needmultiply100 ? +value * 100 : value,
dataFormat?.needMultiply100 ? +value * 100 : value,
dataFormat?.decimalPlaces || 2
)}%`}
</div>
@@ -71,6 +73,7 @@ const Table: React.FC<Props> = ({ data, onApplyAuth }) => {
style={{ width: '100%' }}
scroll={{ x: 'max-content' }}
rowClassName={getRowClassName}
size={size}
/>
</div>
);

View File

@@ -10,19 +10,13 @@ import { queryData } from '../../service';
type Props = {
question: string;
followQuestions?: string[];
data: MsgDataType;
chartIndex: number;
isMobileMode?: boolean;
triggerResize?: boolean;
};
const ChatMsg: React.FC<Props> = ({
question,
followQuestions,
data,
isMobileMode,
triggerResize,
}) => {
const ChatMsg: React.FC<Props> = ({ question, data, chartIndex, isMobileMode, triggerResize }) => {
const { queryColumns, queryResults, chatContext, entityInfo, queryMode } = data;
const [columns, setColumns] = useState<ColumnType[]>(queryColumns);
@@ -41,7 +35,10 @@ const ChatMsg: React.FC<Props> = ({
const metricFields = columns.filter(item => item.showType === 'NUMBER');
const isMetricCard =
(queryMode === 'METRIC_DOMAIN' || queryMode === 'METRIC_FILTER') && singleData;
(queryMode === 'METRIC_DOMAIN' || queryMode === 'METRIC_FILTER') &&
(singleData || chatContext?.dateInfo?.startDate === chatContext?.dateInfo?.endDate);
const isText = columns.length === 1 && columns[0].showType === 'CATEGORY' && singleData;
const onLoadData = async (value: any) => {
setLoading(true);
@@ -65,6 +62,13 @@ const ChatMsg: React.FC<Props> = ({
};
const getMsgContent = () => {
if (isText) {
return (
<div style={{ lineHeight: '24px', width: 'fit-content' }}>
{dataSource[0][columns[0].nameEn]}
</div>
);
}
if (isMetricCard) {
return (
<MetricCard
@@ -88,6 +92,7 @@ const ChatMsg: React.FC<Props> = ({
return (
<MetricTrend
data={{ ...data, queryColumns: columns, queryResults: dataSource }}
chartIndex={chartIndex}
triggerResize={triggerResize}
/>
);
@@ -105,7 +110,9 @@ const ChatMsg: React.FC<Props> = ({
};
let width = '100%';
if (isMetricCard) {
if (isText) {
width = 'fit-content';
} else if (isMetricCard) {
width = '370px';
} else if (categoryField.length > 1 && !isMobile && !isMobileMode) {
if (columns.length === 1) {
@@ -121,9 +128,9 @@ const ChatMsg: React.FC<Props> = ({
chatContext={chatContext}
entityInfo={entityInfo}
title={question}
followQuestions={followQuestions}
isMobileMode={isMobileMode}
width={width}
queryMode={queryMode}
>
{getMsgContent()}
</Message>