[improvement][semantic-fe] add metric trend download functionality (#246)

* [improvement][semantic-fe] Add model alias setting & Add view permission restrictions to the model permission management tab.
[improvement][semantic-fe] Add permission control to the action buttons for the main domain; apply high sensitivity filtering to the authorization of metrics/dimensions.
[improvement][semantic-fe] Optimize the editing mode in the dimension/metric/datasource components to use the modelId stored in the database for data, instead of relying on the data from the state manager.

* [improvement][semantic-fe] Add time granularity setting in the data source configuration.

* [improvement][semantic-fe] Dictionary import for dimension values supported in Q&A visibility

* [improvement][semantic-fe] Modification of data source creation prompt wording"

* [improvement][semantic-fe] metric market experience optimization

* [improvement][semantic-fe] enhance the analysis of metric trends

* [improvement][semantic-fe] optimize the presentation of metric trend permissions

* [improvement][semantic-fe] add metric trend download functionality
This commit is contained in:
tristanliu
2023-10-17 05:35:24 -05:00
committed by GitHub
parent 968d50e071
commit 883cdbefbe
8 changed files with 142 additions and 104 deletions

View File

@@ -100,7 +100,7 @@
"sql-formatter": "^2.3.3", "sql-formatter": "^2.3.3",
"supersonic-chat-sdk": "0.0.0", "supersonic-chat-sdk": "0.0.0",
"umi": "3.5", "umi": "3.5",
"umi-request": "^1.0.8" "umi-request": "^1.4.0"
}, },
"devDependencies": { "devDependencies": {
"@ant-design/pro-cli": "^2.0.2", "@ant-design/pro-cli": "^2.0.2",

View File

@@ -75,14 +75,6 @@ const MDatePicker: React.FC<Props> = ({
initialValues?.staticParams?.dateRangeType || initialValues?.staticParams?.dateRangeType ||
DateRangeType.DAY, DateRangeType.DAY,
); );
// const [pickerType, setPickerType] = useState<PickerType>(() => {
// // if (staticFormData.dateRangeType) {
// // return DateRangeTypeToPickerMap[staticFormData.dateRangeType];
// // }
// return DateRangePicker.DATE;
// });
// const [dateRangeValue, setDateRangeValue] = useState<any>([]);
const staticDefaultConfig = { const staticDefaultConfig = {
dateSettingType: DateSettingType.STATIC, dateSettingType: DateSettingType.STATIC,
@@ -92,8 +84,7 @@ const MDatePicker: React.FC<Props> = ({
dateMultiple: [], dateMultiple: [],
dateRangeStringDesc: '', dateRangeStringDesc: '',
}; };
// const { getMaxPartitionData } = useModel('useMaxPartitionData');
// const { globalViewId } = useModel('useViewsData');
const [latestDateMap, setLatestDateMap] = useState<LatestDateMap>({ const [latestDateMap, setLatestDateMap] = useState<LatestDateMap>({
maxPartition: moment().format('YYYY-MM-DD'), maxPartition: moment().format('YYYY-MM-DD'),
}); });

View File

@@ -83,12 +83,17 @@ const MetricFilter: React.FC<Props> = ({ initFilterValues = {}, onFiltersChange
</Row> </Row>
</div> </div>
</StandardFormRow> </StandardFormRow>
<Space size={80}> <Space size={40}>
<StandardFormRow key="showType" title="切换为卡片" block> <StandardFormRow key="showType" title="切换为卡片" block>
<FormItem name="showType" valuePropName="checked"> <FormItem name="showType" valuePropName="checked">
<Switch size="small" /> <Switch size="small" />
</FormItem> </FormItem>
</StandardFormRow> </StandardFormRow>
<StandardFormRow key="onlyShowMe" title="仅显示我的" block>
<FormItem name="onlyShowMe" valuePropName="checked">
<Switch size="small" />
</FormItem>
</StandardFormRow>
<StandardFormRow key="domainIds" title="所属主题域" block> <StandardFormRow key="domainIds" title="所属主题域" block>
<FormItem name="domainIds"> <FormItem name="domainIds">
<DomainTreeSelect /> <DomainTreeSelect />

View File

@@ -1,10 +1,11 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { SemanticNodeType } from '../../enum'; import { SemanticNodeType } from '../../enum';
import moment from 'moment'; import moment from 'moment';
import { message } from 'antd'; import { message, Row, Col, Button } from 'antd';
import { queryStruct } from '@/pages/SemanticModel/service'; import { queryStruct, downloadCosFile } from '@/pages/SemanticModel/service';
import TrendChart from '@/pages/SemanticModel/Metric/components/MetricTrend'; import TrendChart from '@/pages/SemanticModel/Metric/components/MetricTrend';
import MDatePicker from '@/components/MDatePicker'; import MDatePicker from '@/components/MDatePicker';
import { useModel } from 'umi';
import { DateRangeType, DateSettingType } from '@/components/MDatePicker/type'; import { DateRangeType, DateSettingType } from '@/components/MDatePicker/type';
import { ISemantic } from '../../data'; import { ISemantic } from '../../data';
@@ -24,6 +25,7 @@ const MetricTrendSection: React.FC<Props> = ({ nodeData }) => {
const [metricTrendLoading, setMetricTrendLoading] = useState<boolean>(false); const [metricTrendLoading, setMetricTrendLoading] = useState<boolean>(false);
const [metricColumnConfig, setMetricColumnConfig] = useState<ISemantic.IMetricTrendColumn>(); const [metricColumnConfig, setMetricColumnConfig] = useState<ISemantic.IMetricTrendColumn>();
const [authMessage, setAuthMessage] = useState<string>(''); const [authMessage, setAuthMessage] = useState<string>('');
const [downloadLoding, setDownloadLoding] = useState<boolean>(false);
const [periodDate, setPeriodDate] = useState<{ const [periodDate, setPeriodDate] = useState<{
startDate: string; startDate: string;
endDate: string; endDate: string;
@@ -34,17 +36,28 @@ const MetricTrendSection: React.FC<Props> = ({ nodeData }) => {
dateField: dateFieldMap[DateRangeType.DAY], dateField: dateFieldMap[DateRangeType.DAY],
}); });
const getMetricTrendData = async () => { const getMetricTrendData = async (download = false) => {
setMetricTrendLoading(true); if (download) {
setDownloadLoding(true);
} else {
setMetricTrendLoading(true);
}
const { modelId, bizName, name } = nodeData; const { modelId, bizName, name } = nodeData;
indicatorFields.current = [{ name, column: bizName }]; indicatorFields.current = [{ name, column: bizName }];
const { code, data, msg } = await queryStruct({ const res = await queryStruct({
modelId, modelId,
bizName, bizName,
dateField: periodDate.dateField, dateField: periodDate.dateField,
startDate: periodDate.startDate, startDate: periodDate.startDate,
endDate: periodDate.endDate, endDate: periodDate.endDate,
download,
}); });
if (download) {
setDownloadLoding(false);
return;
}
const { code, data, msg } = res;
setMetricTrendLoading(false); setMetricTrendLoading(false);
if (code === 200) { if (code === 200) {
const { resultList, columns, queryAuthorization } = data; const { resultList, columns, queryAuthorization } = data;
@@ -78,42 +91,52 @@ const MetricTrendSection: React.FC<Props> = ({ nodeData }) => {
return ( return (
<> <>
<div style={{ marginBottom: 5 }}> <div style={{ marginBottom: 5 }}>
<MDatePicker <Row>
initialValues={{ <Col flex="1 1 200px">
dateSettingType: 'DYNAMIC', <MDatePicker
dynamicParams: { initialValues={{
number: 7, dateSettingType: 'DYNAMIC',
periodType: 'DAYS', dynamicParams: {
includesCurrentPeriod: true, number: 7,
shortCutId: 'last7Days', periodType: 'DAYS',
dateRangeType: 'DAY', includesCurrentPeriod: true,
dynamicAdvancedConfigType: 'last', shortCutId: 'last7Days',
dateRangeStringDesc: '最近7天', dateRangeType: 'DAY',
dateSettingType: DateSettingType.DYNAMIC, dynamicAdvancedConfigType: 'last',
}, dateRangeStringDesc: '最近7天',
staticParams: {}, dateSettingType: DateSettingType.DYNAMIC,
}} },
onDateRangeChange={(value, config) => { staticParams: {},
const [startDate, endDate] = value; }}
const { dateSettingType, dynamicParams, staticParams } = config; onDateRangeChange={(value, config) => {
let dateField = dateFieldMap[DateRangeType.DAY]; const [startDate, endDate] = value;
if (DateSettingType.DYNAMIC === dateSettingType) { const { dateSettingType, dynamicParams, staticParams } = config;
dateField = dateFieldMap[dynamicParams.dateRangeType]; let dateField = dateFieldMap[DateRangeType.DAY];
} if (DateSettingType.DYNAMIC === dateSettingType) {
if (DateSettingType.STATIC === dateSettingType) { dateField = dateFieldMap[dynamicParams.dateRangeType];
dateField = dateFieldMap[staticParams.dateRangeType]; }
} if (DateSettingType.STATIC === dateSettingType) {
setPeriodDate({ startDate, endDate, dateField }); dateField = dateFieldMap[staticParams.dateRangeType];
}} }
disabledAdvanceSetting={true} setPeriodDate({ startDate, endDate, dateField });
/> }}
disabledAdvanceSetting={true}
/>
</Col>
<Col flex="0 1">
<Button
type="primary"
loading={downloadLoding}
onClick={() => {
getMetricTrendData(true);
}}
>
</Button>
</Col>
</Row>
</div> </div>
{authMessage && ( {authMessage && <div style={{ color: '#d46b08', marginBottom: 15 }}>{authMessage}</div>}
<div style={{ color: '#d46b08', marginBottom: 15 }}>
: {authMessage}
</div>
)}
<div style={{ color: '#d46b08', marginBottom: 15 }}>: {authMessage}</div>
<TrendChart <TrendChart
data={metricTrendData} data={metricTrendData}
isPer={ isPer={

View File

@@ -3,7 +3,7 @@ import ProTable from '@ant-design/pro-table';
import { message, Space, Popconfirm, Tag, Spin } from 'antd'; import { message, Space, Popconfirm, Tag, Spin } from 'antd';
import React, { useRef, useState, useEffect } from 'react'; import React, { useRef, useState, useEffect } from 'react';
import type { Dispatch } from 'umi'; import type { Dispatch } from 'umi';
import { connect, history } from 'umi'; import { connect, history, useModel } from 'umi';
import type { StateType } from '../model'; import type { StateType } from '../model';
import { SENSITIVE_LEVEL_ENUM } from '../constant'; import { SENSITIVE_LEVEL_ENUM } from '../constant';
import { queryMetric, deleteMetric } from '../service'; import { queryMetric, deleteMetric } from '../service';
@@ -31,6 +31,10 @@ type QueryMetricListParams = {
}; };
const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => { const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
const { initialState = {} } = useModel('@@initialState');
const { currentUser = {} } = initialState as any;
const { selectDomainId, selectModelId: modelId } = domainManger; const { selectDomainId, selectModelId: modelId } = domainManger;
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false); const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
const defaultPagination = { const defaultPagination = {
@@ -59,6 +63,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
const { code, data, msg } = await queryMetric({ const { code, data, msg } = await queryMetric({
...pagination, ...pagination,
...params, ...params,
createdBy: params.onlyShowMe ? currentUser.name : null,
pageSize: params.showType ? 100 : defaultPagination.pageSize, pageSize: params.showType ? 100 : defaultPagination.pageSize,
}); });
setLoading(false); setLoading(false);
@@ -184,15 +189,6 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
title: '描述', title: '描述',
search: false, search: false,
}, },
// {
// dataIndex: 'type',
// title: '指标类型',
// valueEnum: {
// ATOMIC: '原子指标',
// DERIVED: '衍生指标',
// },
// },
{ {
dataIndex: 'updatedAt', dataIndex: 'updatedAt',
title: '更新时间', title: '更新时间',

View File

@@ -62,13 +62,11 @@ const NodeInfoDrawer: React.FC<Props> = ({
} }
const { const {
alias, alias,
fullPath,
bizName, bizName,
createdBy, createdBy,
createdAt, createdAt,
updatedAt, updatedAt,
description, description,
// domainName,
sensitiveLevel, sensitiveLevel,
modelName, modelName,
nodeType, nodeType,
@@ -111,13 +109,11 @@ const NodeInfoDrawer: React.FC<Props> = ({
{ {
title: '指标趋势', title: '指标趋势',
render: () => ( render: () => (
<div key="指标趋势" style={{ display: 'block' }}> <Row key={`metricTrendSection`} style={{ marginBottom: 10, display: 'flex' }}>
<Row key={`metricTrendSection`} style={{ marginBottom: 10, display: 'flex' }}> <Col span={24}>
<Col span={24}> <MetricTrendSection nodeData={nodeData} />
<MetricTrendSection nodeData={nodeData} /> </Col>
</Col> </Row>
</Row>
</div>
), ),
}, },
{ {

View File

@@ -1,4 +1,7 @@
import request from 'umi-request'; import request from 'umi-request';
import axios from 'axios';
import { AUTH_TOKEN_KEY } from '@/common/constants';
import moment from 'moment';
const getRunningEnv = () => { const getRunningEnv = () => {
return window.location.pathname.includes('/chatSetting/') ? 'chat' : 'semantic'; return window.location.pathname.includes('/chatSetting/') ? 'chat' : 'semantic';
@@ -366,47 +369,69 @@ export function searchDictLatestTaskList(data: any): Promise<any> {
}); });
} }
export function queryStruct({ const downloadStruct = (blob: Blob) => {
const fieldName = `supersonic_${moment().format('YYYYMMDDhhmmss')}.xlsx`;
const link = document.createElement('a');
link.href = URL.createObjectURL(new Blob([blob]));
link.download = fieldName;
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(link.href);
document.body.removeChild(link);
};
export async function queryStruct({
modelId, modelId,
bizName, bizName,
dateField = 'sys_imp_date', dateField = 'sys_imp_date',
startDate, startDate,
endDate, endDate,
download = false,
}: { }: {
modelId: number; modelId: number;
bizName: string; bizName: string;
dateField: string; dateField: string;
startDate: string; startDate: string;
endDate: string; endDate: string;
download?: boolean;
}): Promise<any> { }): Promise<any> {
return request(`${process.env.API_BASE_URL}query/struct`, { const response = await request(
method: 'POST', `${process.env.API_BASE_URL}query/${download ? 'download/' : ''}struct`,
data: { {
modelId, method: 'POST',
groups: [dateField], ...(download ? { responseType: 'blob', getResponse: true } : {}),
aggregators: [ data: {
{ modelId,
column: bizName, groups: [dateField],
// func: 'SUM', aggregators: [
nameCh: 'null', {
args: null, column: bizName,
// func: 'SUM',
nameCh: 'null',
args: null,
},
],
orders: [],
dimensionFilters: [],
metricFilters: [],
params: [],
dateInfo: {
dateMode: 'BETWEEN',
startDate,
endDate,
dateList: [],
unit: 7,
period: 'DAY',
text: 'null',
}, },
], limit: 365,
orders: [], nativeQuery: false,
dimensionFilters: [],
metricFilters: [],
params: [],
dateInfo: {
dateMode: 'BETWEEN',
startDate,
endDate,
dateList: [],
unit: 7,
period: 'DAY',
text: 'null',
}, },
limit: 365,
nativeQuery: false,
}, },
}); );
if (download) {
downloadStruct(response.data);
} else {
return response;
}
} }

View File

@@ -39,11 +39,13 @@ const responseInterceptor = async (response: Response) => {
const contextpath = response.headers.get('contextpath'); const contextpath = response.headers.get('contextpath');
win.location.href = contextpath; win.location.href = contextpath;
} else { } else {
const data: Result<any> = await response?.clone()?.json?.(); try {
if (Number(data.code) === 403) { const data: Result<any> = await response?.clone()?.json?.();
history.push('/login'); if (Number(data.code) === 403) {
return response; history.push('/login');
} return response;
}
} catch (e) {}
} }
return response; return response;