first commit

This commit is contained in:
jerryjzhang
2023-06-12 18:44:01 +08:00
commit dc4fc69b57
879 changed files with 573090 additions and 0 deletions

View File

@@ -0,0 +1,197 @@
import { CHART_SECONDARY_COLOR, CLS_PREFIX, THEME_COLOR_LIST } from '../../../common/constants';
import {
formatByDecimalPlaces,
getFormattedValue,
getMinMaxDate,
groupByColumn,
normalizeTrendData,
} from '../../../utils/utils';
import type { ECharts } from 'echarts';
import * as echarts from 'echarts';
import React, { useEffect, useRef, useState } from 'react';
import moment from 'moment';
import { ColumnType } from '../../../common/type';
import NoPermissionChart from '../NoPermissionChart';
type Props = {
domain?: string;
dateColumnName: string;
categoryColumnName: string;
metricField: ColumnType;
resultList: any[];
onApplyAuth?: (domain: string) => void;
};
const MetricTrendChart: React.FC<Props> = ({
domain,
dateColumnName,
categoryColumnName,
metricField,
resultList,
onApplyAuth,
}) => {
const chartRef = useRef<any>();
const [instance, setInstance] = useState<ECharts>();
const renderChart = () => {
let instanceObj: any;
if (!instance) {
instanceObj = echarts.init(chartRef.current);
setInstance(instanceObj);
} else {
instanceObj = instance;
}
const valueColumnName = metricField.nameEn;
const groupDataValue = groupByColumn(resultList, categoryColumnName);
const [startDate, endDate] = getMinMaxDate(resultList, dateColumnName);
const groupData = Object.keys(groupDataValue).reduce((result: any, key) => {
result[key] =
startDate &&
endDate &&
(dateColumnName.includes('date') || dateColumnName.includes('month'))
? normalizeTrendData(
groupDataValue[key],
dateColumnName,
valueColumnName,
startDate,
endDate,
dateColumnName.includes('month') ? 'months' : 'days'
)
: groupDataValue[key].reverse();
return result;
}, {});
const sortedGroupKeys = Object.keys(groupData).sort((a, b) => {
return (
groupData[b][groupData[b].length - 1][valueColumnName] -
groupData[a][groupData[a].length - 1][valueColumnName]
);
});
const xData = groupData[sortedGroupKeys[0]]?.map((item: any) => {
const date = `${item[dateColumnName]}`;
return date.length === 10 ? moment(date).format('MM-DD') : date;
});
instanceObj.setOption({
legend: categoryColumnName && {
left: 0,
top: 0,
icon: 'rect',
itemWidth: 15,
itemHeight: 5,
type: 'scroll',
},
xAxis: {
type: 'category',
axisTick: {
alignWithLabel: true,
lineStyle: {
color: CHART_SECONDARY_COLOR,
},
},
axisLine: {
lineStyle: {
color: CHART_SECONDARY_COLOR,
},
},
axisLabel: {
showMaxLabel: true,
color: '#999',
},
data: xData,
},
yAxis: {
type: 'value',
splitLine: {
lineStyle: {
opacity: 0.3,
},
},
axisLabel: {
formatter: function (value: any) {
return value === 0
? 0
: metricField.dataFormatType === 'percent'
? `${formatByDecimalPlaces(value, metricField.dataFormat?.decimalPlaces || 2)}%`
: getFormattedValue(value);
},
},
},
tooltip: {
trigger: 'axis',
formatter: function (params: any[]) {
const param = params[0];
const valueLabels = params
.sort((a, b) => b.value - a.value)
.map(
(item: any) =>
`<div style="margin-top: 3px;">${
item.marker
} <span style="display: inline-block; width: 70px; margin-right: 12px;">${
item.seriesName
}</span><span style="display: inline-block; width: 90px; text-align: right; font-weight: 500;">${
item.value === ''
? '-'
: metricField.dataFormatType === 'percent'
? `${formatByDecimalPlaces(
item.value,
metricField.dataFormat?.decimalPlaces || 2
)}%`
: getFormattedValue(item.value)
}</span></div>`
)
.join('');
return `${param.name}<br />${valueLabels}`;
},
},
grid: {
left: '1%',
right: '4%',
bottom: '3%',
top: categoryColumnName ? 45 : 20,
containLabel: true,
},
series: sortedGroupKeys.slice(0, 20).map((category, index) => {
const data = groupData[category];
return {
type: 'line',
name: categoryColumnName ? category : metricField.name,
symbol: 'circle',
showSymbol: data.length === 1,
smooth: true,
data: data.map((item: any) => {
const value = item[valueColumnName];
return metricField.dataFormatType === 'percent' &&
metricField.dataFormat?.needmultiply100
? value * 100
: value;
}),
color: THEME_COLOR_LIST[index],
};
}),
});
instanceObj.resize();
};
useEffect(() => {
if (metricField.authorized) {
renderChart();
}
}, [resultList, metricField]);
const prefixCls = `${CLS_PREFIX}-metric-trend`;
return (
<div>
{!metricField.authorized ? (
<NoPermissionChart domain={domain || ''} onApplyAuth={onApplyAuth} />
) : (
<div className={`${prefixCls}-flow-trend-chart`} ref={chartRef} />
)}
</div>
);
};
export default MetricTrendChart;

View File

@@ -0,0 +1,205 @@
import { useEffect, useState } from 'react';
import { CLS_PREFIX, DATE_TYPES } from '../../../common/constants';
import { ColumnType, MsgDataType } from '../../../common/type';
import { groupByColumn, 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 SemanticInfoPopover from '../SemanticInfoPopover';
type Props = {
data: MsgDataType;
onApplyAuth?: (domain: string) => void;
onCheckMetricInfo?: (data: any) => void;
};
const MetricTrend: React.FC<Props> = ({ data, onApplyAuth, onCheckMetricInfo }) => {
const { queryColumns, queryResults, entityInfo, chatContext } = data;
const [columns, setColumns] = useState<ColumnType[]>(queryColumns);
const metricFields = columns.filter((column: any) => column.showType === 'NUMBER') || [];
const [currentMetricField, setCurrentMetricField] = useState<ColumnType>(metricFields[0]);
const [onlyOneDate, setOnlyOneDate] = useState(false);
const [trendData, setTrendData] = useState(data);
const [dataSource, setDataSource] = useState<any[]>(queryResults);
const [mergeMetric, setMergeMetric] = useState(false);
const [currentDateOption, setCurrentDateOption] = useState<number>();
const [loading, setLoading] = useState(false);
const dateField: any = columns.find(
(column: any) => column.showType === 'DATE' || column.type === 'DATE'
);
const dateColumnName = dateField?.nameEn || '';
const categoryColumnName =
columns.find((column: any) => column.showType === 'CATEGORY')?.nameEn || '';
const getColumns = () => {
const categoryFieldData = groupByColumn(dataSource, categoryColumnName);
const result = [dateField];
const columnsValue = Object.keys(categoryFieldData).map(item => ({
authorized: currentMetricField.authorized,
name: item !== 'undefined' ? item : currentMetricField.name,
nameEn: `${item}${currentMetricField.name}`,
showType: 'NUMBER',
type: 'NUMBER',
}));
return result.concat(columnsValue);
};
const getResultList = () => {
return [
{
[dateField.nameEn]: dataSource[0][dateField.nameEn],
...dataSource.reduce((result, item) => {
result[`${item[categoryColumnName]}${currentMetricField.name}`] =
item[currentMetricField.nameEn];
return result;
}, {}),
},
];
};
useEffect(() => {
setDataSource(queryResults);
}, [queryResults]);
useEffect(() => {
let onlyOneDateValue = false;
let dataValue = trendData;
if (dateColumnName && dataSource.length > 0) {
const dateFieldData = groupByColumn(dataSource, dateColumnName);
onlyOneDateValue =
Object.keys(dateFieldData).length === 1 && Object.keys(dateFieldData)[0] !== undefined;
if (onlyOneDateValue) {
if (categoryColumnName !== '') {
dataValue = {
...trendData,
queryColumns: getColumns(),
queryResults: getResultList(),
};
} else {
setMergeMetric(true);
}
}
}
setOnlyOneDate(onlyOneDateValue);
setTrendData(dataValue);
}, [currentMetricField]);
const dateOptions = DATE_TYPES[chatContext.dateInfo?.period] || DATE_TYPES[0];
const onLoadData = async (value: number) => {
setLoading(true);
const { data } = await queryData({
...chatContext,
dateInfo: { ...chatContext.dateInfo, unit: value },
});
setLoading(false);
if (data.code === 200) {
setColumns(data.data?.queryColumns || []);
setDataSource(data.data?.queryResults || []);
}
};
const selectDateOption = (dateOption: number) => {
setCurrentDateOption(dateOption);
// const { domainName, dimensions, metrics, aggType, filters } = chatContext || {};
// const dimensionSection = dimensions?.join('、') || '';
// const metricSection = metrics?.join('、') || '';
// const aggregatorSection = aggType || '';
// const filterSection = filters
// .reduce((result, dimensionName) => {
// result = result.concat(dimensionName);
// return result;
// }, [])
// .join('、');
onLoadData(dateOption);
};
if (metricFields.length === 0) {
return null;
}
const prefixCls = `${CLS_PREFIX}-metric-trend`;
return (
<div className={prefixCls}>
<div className={`${prefixCls}-charts`}>
{!onlyOneDate && (
<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>
)}
{metricFields.length > 1 && !mergeMetric && (
<div className={`${prefixCls}-metric-fields`}>
{metricFields.map((metricField: ColumnType) => {
const metricFieldClass = classNames(`${prefixCls}-metric-field`, {
[`${prefixCls}-metric-field-active`]:
currentMetricField?.nameEn === metricField.nameEn,
});
return (
<div
className={metricFieldClass}
key={metricField.nameEn}
onClick={() => {
setCurrentMetricField(metricField);
}}
>
<SemanticInfoPopover
classId={chatContext.domainId}
uniqueId={metricField.nameEn}
onDetailBtnClick={onCheckMetricInfo}
>
{metricField.name}
</SemanticInfoPopover>
</div>
);
})}
</div>
)}
{onlyOneDate ? (
<Table data={trendData} onApplyAuth={onApplyAuth} />
) : (
<Spin spinning={loading}>
<MetricTrendChart
domain={entityInfo?.domainInfo.name}
dateColumnName={dateColumnName}
categoryColumnName={categoryColumnName}
metricField={currentMetricField}
resultList={dataSource}
onApplyAuth={onApplyAuth}
/>
</Spin>
)}
</div>
</div>
);
};
export default MetricTrend;

View File

@@ -0,0 +1,124 @@
@import '../../../styles/index.less';
@metric-trend-prefix-cls: ~'@{supersonic-chat-prefix}-metric-trend';
.@{metric-trend-prefix-cls} {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-top: 20px;
width: 100%;
row-gap: 4px;
&-indicator {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
}
&-date-range {
color: var(--text-color-fourth);
font-size: 14px;
}
&-indicator-value {
color: var(--text-color);
font-weight: 600;
font-size: 30px;
}
&-indicator-name {
color: var(--text-color-fourth);
font-size: 14px;
}
&-flow-trend-chart {
height: 300px;
}
&-charts {
display: flex;
flex-direction: column;
width: 100%;
row-gap: 20px;
}
&-metric-fields {
display: flex;
flex-wrap: wrap;
align-items: center;
row-gap: 12px;
}
&-metric-field {
display: inline-block;
box-sizing: border-box;
height: auto;
margin: 0;
margin-right: 8px;
padding: 1px 8px;
color: var(--text-color-third);
font-variant: tabular-nums;
line-height: 20px;
white-space: nowrap;
list-style: none;
border-color: transparent;
border-radius: 2px;
cursor: pointer;
opacity: 1;
transition: all 0.3s;
font-feature-settings: 'tnum', 'tnum';
&:hover {
color: var(--chat-blue);
}
}
&-metric-field-active {
color: #fff !important;
background-color: var(--chat-blue);
}
&-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;
}
&-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);
}
}