From 444ae4e48bdb82fb71d3c64d1a299c5b739597c5 Mon Sep 17 00:00:00 2001 From: zzyangh <799463087@qq.com> Date: Thu, 16 Jan 2025 16:33:21 +0800 Subject: [PATCH] [feature](SqlAnalyze/ManagementConf): Add SQL execution plan cost chart --- .../__snapshots__/index.test.tsx.snap | 6617 ++++++++++++++++- .../SqlAnalyze/ManagementConf/index.test.tsx | 35 +- .../page/SqlAnalyze/ManagementConf/index.tsx | 15 + .../src/page/SqlAnalyze/SqlManage/index.tsx | 38 +- .../SqlAnalyze/hooks/useSqlExecPlanCost.ts | 51 + 5 files changed, 6721 insertions(+), 35 deletions(-) create mode 100644 packages/sqle/src/page/SqlAnalyze/hooks/useSqlExecPlanCost.ts diff --git a/packages/sqle/src/page/SqlAnalyze/ManagementConf/__snapshots__/index.test.tsx.snap b/packages/sqle/src/page/SqlAnalyze/ManagementConf/__snapshots__/index.test.tsx.snap index a043266ca..c1a031f9f 100644 --- a/packages/sqle/src/page/SqlAnalyze/ManagementConf/__snapshots__/index.test.tsx.snap +++ b/packages/sqle/src/page/SqlAnalyze/ManagementConf/__snapshots__/index.test.tsx.snap @@ -1,5 +1,6378 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`SqlAnalyze/ManagementConfAnalyze filter sql execution plan cost 1`] = ` +
+
+
+
+ SQL分析 +
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+

+ SQL语句 +

+
+
+
+
+
+ + + SELECT + + + + * + + + + from + + tasks; + +
+
+ + + +
+
+
+
+
+
+
+
+

+ SQL执行计划 Cost趋势 +

+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+ +
+
+ + + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 当前执行计划 +
+
+ + 2025-01-09 12:00:00 + +
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ id + + select_type + + table + + partitions + + type + + possible_keys + + key + + key_len + + ref + + rows + + filtered + + Extra +
+ + 1 + + + + SIMPLE + + + + tasks + + + + - + + + + ALL + + + + - + + + + - + + + + - + + + + - + + + + 1 + + + + 100.00 + + + + - + +
+
+
+
+
+
+
+
+
+

+ 性能统计 +

+
+
+
+
+
+ + + + + + + +
+

+ 影响行数 +

+

+ 区别于执行计划的rows列,显示SQL的实际影响行数 +

+
+
+
+ -- +
+
+
+
+
+ + +
+
+
+
+
+
+
+`; + exports[`SqlAnalyze/ManagementConfAnalyze should get analyze data from origin 1`] = `
+ > +
+
+
+

+ SQL执行计划 Cost趋势 +

+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+ +
+
+ + + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + +
+
+
+
+
+
+
+ + + + + + + + + + + + +
+
+ + 暂无数据,选择其他时间段查看 + +
+ +
+
+
+
+
+
+
+
{ return { @@ -31,6 +33,8 @@ describe('SqlAnalyze/ManagementConfAnalyze', () => { const useParamsMock: jest.Mock = useParams as jest.Mock; + let getSqlManageSqlAnalysisChartSpy: jest.SpyInstance; + let currentTime = dayjs('2025-01-09 12:00:00'); beforeEach(() => { MockDate.set(dayjs('2025-01-09 12:00:00').valueOf()); jest.useFakeTimers({ legacyFakeTimers: true }); @@ -40,6 +44,7 @@ describe('SqlAnalyze/ManagementConfAnalyze', () => { id: '2', projectName }); + getSqlManageSqlAnalysisChartSpy = sqlManage.getSqlManageSqlAnalysisChart(); }); afterEach(() => { @@ -79,6 +84,34 @@ describe('SqlAnalyze/ManagementConfAnalyze', () => { expect(container).toMatchSnapshot(); }); + it('filter sql execution plan cost', async () => { + mockGetAnalyzeData(); + const { container } = renderWithReduxAndTheme(); + await act(async () => jest.advanceTimersByTime(3000)); + expect(container).toMatchSnapshot(); + + expect(screen.getByText('SQL执行计划 Cost趋势')).toBeInTheDocument(); + expect(getSqlManageSqlAnalysisChartSpy).toHaveBeenCalledTimes(1); + expect(getSqlManageSqlAnalysisChartSpy).toHaveBeenNthCalledWith(1, { + sql_manage_id: '2', + project_name: projectName, + metric_name: 'explain_cost', + start_time: translateTimeForRequest(currentTime.subtract(24, 'hour')), + end_time: translateTimeForRequest(currentTime) + }); + + fireEvent.click(screen.getByText('7天')); + await act(async () => jest.advanceTimersByTime(0)); + expect(getSqlManageSqlAnalysisChartSpy).toHaveBeenCalledTimes(2); + expect(getSqlManageSqlAnalysisChartSpy).toHaveBeenNthCalledWith(2, { + sql_manage_id: '2', + project_name: projectName, + metric_name: 'explain_cost', + start_time: translateTimeForRequest(currentTime.subtract(7, 'day')), + end_time: translateTimeForRequest(currentTime) + }); + }); + test('should render error result of type "info" when response code is 8001', async () => { const spy = mockGetAnalyzeData(); spy.mockImplementation(() => diff --git a/packages/sqle/src/page/SqlAnalyze/ManagementConf/index.tsx b/packages/sqle/src/page/SqlAnalyze/ManagementConf/index.tsx index ea560bd17..5d95d45fb 100644 --- a/packages/sqle/src/page/SqlAnalyze/ManagementConf/index.tsx +++ b/packages/sqle/src/page/SqlAnalyze/ManagementConf/index.tsx @@ -12,6 +12,7 @@ import { import instance_audit_plan from '@actiontech/shared/lib/api/sqle/service/instance_audit_plan'; import { useTypedParams } from '@actiontech/shared'; import { ROUTE_PATHS } from '@actiontech/shared/lib/data/routePaths'; +import useSqlExecPlanCost from '../hooks/useSqlExecPlanCost'; const ManagementConfAnalyze = () => { const urlParams = @@ -62,6 +63,13 @@ const ManagementConfAnalyze = () => { getSqlAnalyzeFinish ]); + const { + data, + getSqlExecPlanCostDataSourceLoading, + getSqlExecPlanCostDataSource, + getSqlExecPlanCostDataSourceError + } = useSqlExecPlanCost(urlParams.id ?? ''); + useEffect(() => { getSqlAnalyze(); }, [getSqlAnalyze]); @@ -74,6 +82,13 @@ const ManagementConfAnalyze = () => { errorMessage={errorMessage} performanceStatistics={performanceStatistics} loading={loading} + sqlExecPlanCostDataSource={data} + getSqlExecPlanCostDataSourceLoading={getSqlExecPlanCostDataSourceLoading} + getSqlExecPlanCostDataSource={getSqlExecPlanCostDataSource} + getSqlExecPlanCostDataSourceError={ + getSqlExecPlanCostDataSourceError?.message + } + showExecPlanCostChart /> ); }; diff --git a/packages/sqle/src/page/SqlAnalyze/SqlManage/index.tsx b/packages/sqle/src/page/SqlAnalyze/SqlManage/index.tsx index dfbc5a8c6..0be709a89 100644 --- a/packages/sqle/src/page/SqlAnalyze/SqlManage/index.tsx +++ b/packages/sqle/src/page/SqlAnalyze/SqlManage/index.tsx @@ -12,9 +12,7 @@ import { useCurrentProject } from '@actiontech/shared/lib/global'; import SqlAnalyze from '../SqlAnalyze'; import { useTypedParams } from '@actiontech/shared'; import { ROUTE_PATHS } from '@actiontech/shared/lib/data/routePaths'; -import { useRequest } from 'ahooks'; -import dayjs, { Dayjs } from 'dayjs'; -import { translateTimeForRequest } from '@actiontech/shared/lib/utils/Common'; +import useSqlExecPlanCost from '../hooks/useSqlExecPlanCost'; const SQLManageAnalyze = () => { const urlParams = @@ -65,36 +63,10 @@ const SQLManageAnalyze = () => { const { data, - loading: getSqlExecPlanCostDataSourceLoading, - run: getSqlExecPlanCostDataSource, - error: getSqlExecPlanCostDataSourceError - } = useRequest((startTime?: Dayjs, endTime?: Dayjs) => { - const startTimeParam = startTime ?? dayjs().subtract(24, 'hour'); - const endTimeParam = endTime ?? dayjs(); - return SqlManage.GetSqlManageSqlAnalysisChartV1({ - sql_manage_id: urlParams.sqlManageId ?? '', - project_name: projectName, - metric_name: 'explain_cost', - start_time: translateTimeForRequest(startTimeParam), - end_time: translateTimeForRequest(endTimeParam) - }).then((res) => { - if (res.data.code === ResponseCode.SUCCESS) { - // 根据start_time和end_time填充数据 - const { points } = res.data.data ?? {}; - const firstPoint = points?.[0]; - const lastPoint = points?.[points.length - 1]; - if (startTimeParam?.isBefore(dayjs(firstPoint?.x))) { - points?.unshift({ - x: translateTimeForRequest(startTimeParam) - }); - } - if (endTimeParam?.isAfter(dayjs(lastPoint?.x))) { - points?.push({ x: translateTimeForRequest(endTimeParam) }); - } - return points; - } - }); - }); + getSqlExecPlanCostDataSourceLoading, + getSqlExecPlanCostDataSource, + getSqlExecPlanCostDataSourceError + } = useSqlExecPlanCost(urlParams.sqlManageId ?? ''); useEffect(() => { getSqlAnalyze(); diff --git a/packages/sqle/src/page/SqlAnalyze/hooks/useSqlExecPlanCost.ts b/packages/sqle/src/page/SqlAnalyze/hooks/useSqlExecPlanCost.ts new file mode 100644 index 000000000..2fd3b5b59 --- /dev/null +++ b/packages/sqle/src/page/SqlAnalyze/hooks/useSqlExecPlanCost.ts @@ -0,0 +1,51 @@ +import { useRequest } from 'ahooks'; +import dayjs, { Dayjs } from 'dayjs'; +import { translateTimeForRequest } from '@actiontech/shared/lib/utils/Common'; +import SqlManage from '@actiontech/shared/lib/api/sqle/service/SqlManage'; +import { useCurrentProject } from '@actiontech/shared/lib/global'; +import { ResponseCode } from '@actiontech/shared/lib/enum'; + +const useSqlExecPlanCost = (id: string) => { + const { projectName } = useCurrentProject(); + const { + data, + loading: getSqlExecPlanCostDataSourceLoading, + run: getSqlExecPlanCostDataSource, + error: getSqlExecPlanCostDataSourceError + } = useRequest((startTime?: Dayjs, endTime?: Dayjs) => { + const startTimeParam = startTime ?? dayjs().subtract(24, 'hour'); + const endTimeParam = endTime ?? dayjs(); + return SqlManage.GetSqlManageSqlAnalysisChartV1({ + sql_manage_id: id ?? '', + project_name: projectName, + metric_name: 'explain_cost', + start_time: translateTimeForRequest(startTimeParam), + end_time: translateTimeForRequest(endTimeParam) + }).then((res) => { + if (res.data.code === ResponseCode.SUCCESS) { + // 根据start_time和end_time填充数据 + const { points } = res.data.data ?? {}; + const firstPoint = points?.[0]; + const lastPoint = points?.[points.length - 1]; + if (startTimeParam?.isBefore(dayjs(firstPoint?.x))) { + points?.unshift({ + x: translateTimeForRequest(startTimeParam) + }); + } + if (endTimeParam?.isAfter(dayjs(lastPoint?.x))) { + points?.push({ x: translateTimeForRequest(endTimeParam) }); + } + return points; + } + }); + }); + + return { + data, + getSqlExecPlanCostDataSourceLoading, + getSqlExecPlanCostDataSource, + getSqlExecPlanCostDataSourceError + }; +}; + +export default useSqlExecPlanCost;