From cd6c4932d21f86f298660f67ef8a80d3cb4e79d2 Mon Sep 17 00:00:00 2001 From: wanghao Date: Wed, 14 May 2025 15:03:45 +0800 Subject: [PATCH] [feat] add sql insights page --- .apiforgerc | 2 +- .gitignore | 1 + packages/base/src/locale/zh-CN/dmsMenu.ts | 3 +- .../Nav/SideMenu/MenuList/menus/index.type.ts | 3 +- .../Nav/SideMenu/MenuList/menus/menu.data.tsx | 2 +- .../page/Nav/SideMenu/MenuList/menus/sqle.tsx | 12 +- packages/base/vite.config.mts | 6 +- packages/shared/lib/api/sqle/index.ts | 8 +- .../lib/api/sqle/service/SqlManage/index.d.ts | 56 +++- .../api/sqle/service/SqlManage/index.enum.ts | 16 + .../lib/api/sqle/service/SqlManage/index.ts | 51 ++++ .../shared/lib/api/sqle/service/common.d.ts | 161 ++++++++++ .../lib/api/sqle/service/common.enum.ts | 66 +++++ .../lib/api/sqle/service/statistic/index.d.ts | 8 + .../lib/api/sqle/service/statistic/index.ts | 14 + packages/shared/lib/data/EmitterKey.ts | 4 +- packages/shared/lib/data/routePaths.ts | 6 + packages/shared/lib/styleWrapper/element.ts | 2 + packages/shared/lib/utils/Common.ts | 2 +- packages/sqle/src/data/ModalName.ts | 5 +- packages/sqle/src/locale/en-US/index.ts | 4 +- packages/sqle/src/locale/en-US/sqlInsights.ts | 57 ++++ packages/sqle/src/locale/zh-CN/index.ts | 5 +- packages/sqle/src/locale/zh-CN/sqlInsights.ts | 90 ++++++ .../components/ActiveSessionTrend/index.tsx | 46 +++ .../components/ActiveSessionTrend/style.ts | 17 ++ .../DataSourcePerformanceTrend/index.tsx | 46 +++ .../DataSourcePerformanceTrend/style.ts | 19 ++ .../SqlRelatedTransactionDrawer/data.tsx | 40 +++ .../SqlRelatedTransactionDrawer/index.tsx | 278 ++++++++++++++++++ .../components/DrawerManager/index.tsx | 11 + .../SqlExecutionCostTrendChart.tsx | 167 +++++++++++ .../RelatedSqlList/SqlFingerprintColumn.tsx | 48 +++ .../components/RelatedSqlList/actions.tsx | 28 ++ .../components/RelatedSqlList/column.tsx | 76 +++++ .../components/RelatedSqlList/index.tsx | 203 +++++++++++++ .../components/RelatedSqlList/style.ts | 6 + .../RelatedSqlList/useRelatedSqlRedux.tsx | 22 ++ .../components/SlowSqlTrend/index.tsx | 45 +++ .../components/SlowSqlTrend/style.ts | 18 ++ .../components/SqlInsightsLineChart/index.tsx | 261 ++++++++++++++++ .../components/SqlInsightsLineChart/style.ts | 14 + .../components/TopSqlTrend/index.tsx | 42 +++ .../components/TopSqlTrend/style.ts | 18 ++ .../SqlInsights/hooks/useSqlInsightsMetric.ts | 63 ++++ .../sqle/src/page/SqlInsights/index.data.ts | 25 ++ packages/sqle/src/page/SqlInsights/index.tsx | 136 +++++++++ packages/sqle/src/router/config.tsx | 6 + packages/sqle/src/store/index.ts | 4 +- packages/sqle/src/store/sqlInsights/index.ts | 51 ++++ 50 files changed, 2254 insertions(+), 20 deletions(-) create mode 100644 packages/sqle/src/locale/en-US/sqlInsights.ts create mode 100644 packages/sqle/src/locale/zh-CN/sqlInsights.ts create mode 100644 packages/sqle/src/page/SqlInsights/components/ActiveSessionTrend/index.tsx create mode 100644 packages/sqle/src/page/SqlInsights/components/ActiveSessionTrend/style.ts create mode 100644 packages/sqle/src/page/SqlInsights/components/DataSourcePerformanceTrend/index.tsx create mode 100644 packages/sqle/src/page/SqlInsights/components/DataSourcePerformanceTrend/style.ts create mode 100644 packages/sqle/src/page/SqlInsights/components/DrawerManager/SqlRelatedTransactionDrawer/data.tsx create mode 100644 packages/sqle/src/page/SqlInsights/components/DrawerManager/SqlRelatedTransactionDrawer/index.tsx create mode 100644 packages/sqle/src/page/SqlInsights/components/DrawerManager/index.tsx create mode 100644 packages/sqle/src/page/SqlInsights/components/RelatedSqlList/SqlExecutionCostTrendChart.tsx create mode 100644 packages/sqle/src/page/SqlInsights/components/RelatedSqlList/SqlFingerprintColumn.tsx create mode 100644 packages/sqle/src/page/SqlInsights/components/RelatedSqlList/actions.tsx create mode 100644 packages/sqle/src/page/SqlInsights/components/RelatedSqlList/column.tsx create mode 100644 packages/sqle/src/page/SqlInsights/components/RelatedSqlList/index.tsx create mode 100644 packages/sqle/src/page/SqlInsights/components/RelatedSqlList/style.ts create mode 100644 packages/sqle/src/page/SqlInsights/components/RelatedSqlList/useRelatedSqlRedux.tsx create mode 100644 packages/sqle/src/page/SqlInsights/components/SlowSqlTrend/index.tsx create mode 100644 packages/sqle/src/page/SqlInsights/components/SlowSqlTrend/style.ts create mode 100644 packages/sqle/src/page/SqlInsights/components/SqlInsightsLineChart/index.tsx create mode 100644 packages/sqle/src/page/SqlInsights/components/SqlInsightsLineChart/style.ts create mode 100644 packages/sqle/src/page/SqlInsights/components/TopSqlTrend/index.tsx create mode 100644 packages/sqle/src/page/SqlInsights/components/TopSqlTrend/style.ts create mode 100644 packages/sqle/src/page/SqlInsights/hooks/useSqlInsightsMetric.ts create mode 100644 packages/sqle/src/page/SqlInsights/index.data.ts create mode 100644 packages/sqle/src/page/SqlInsights/index.tsx create mode 100644 packages/sqle/src/store/sqlInsights/index.ts diff --git a/.apiforgerc b/.apiforgerc index 877e1ac8c..1842cb186 100644 --- a/.apiforgerc +++ b/.apiforgerc @@ -33,4 +33,4 @@ "serviceAlias": "@actiontech/shared/lib/api/base/service" } } -} \ No newline at end of file +} diff --git a/.gitignore b/.gitignore index b3e42a14f..69308120e 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ yarn-error.log* docs-dist docs-dist.tar.gz es +.cursor \ No newline at end of file diff --git a/packages/base/src/locale/zh-CN/dmsMenu.ts b/packages/base/src/locale/zh-CN/dmsMenu.ts index 8c1781ac7..7c0b16528 100644 --- a/packages/base/src/locale/zh-CN/dmsMenu.ts +++ b/packages/base/src/locale/zh-CN/dmsMenu.ts @@ -74,5 +74,6 @@ export default { copyRight: '© {{year}} 上海爱可生信息技术股份有限公司 版权所有', quickActions: { globalDashboard: '全局Dashboard' - } + }, + sqlPerformanceInsights: 'SQL洞察' }; diff --git a/packages/base/src/page/Nav/SideMenu/MenuList/menus/index.type.ts b/packages/base/src/page/Nav/SideMenu/MenuList/menus/index.type.ts index 14bcc178c..795b45dc8 100644 --- a/packages/base/src/page/Nav/SideMenu/MenuList/menus/index.type.ts +++ b/packages/base/src/page/Nav/SideMenu/MenuList/menus/index.type.ts @@ -73,4 +73,5 @@ type SqleMenuStructTreeKey = | 'push-rule-configuration' | 'pipeline-configuration' | 'version-management' - | 'data-source-comparison'; + | 'data-source-comparison' + | 'sql-insights'; diff --git a/packages/base/src/page/Nav/SideMenu/MenuList/menus/menu.data.tsx b/packages/base/src/page/Nav/SideMenu/MenuList/menus/menu.data.tsx index c396d0ade..c0946ede6 100644 --- a/packages/base/src/page/Nav/SideMenu/MenuList/menus/menu.data.tsx +++ b/packages/base/src/page/Nav/SideMenu/MenuList/menus/menu.data.tsx @@ -32,7 +32,7 @@ export const SQLE_MENU_STRUCT: MenuTreeI18n[] = [ { type: 'group', groupLabelKey: 'dmsMenu.groupLabel.SQLManagement', - groups: ['sql-management', 'sql-management-conf'] + groups: ['sql-management', 'sql-insights', 'sql-management-conf'] }, { type: 'divider' }, { diff --git a/packages/base/src/page/Nav/SideMenu/MenuList/menus/sqle.tsx b/packages/base/src/page/Nav/SideMenu/MenuList/menus/sqle.tsx index 8b5182a42..0894ac57a 100644 --- a/packages/base/src/page/Nav/SideMenu/MenuList/menus/sqle.tsx +++ b/packages/base/src/page/Nav/SideMenu/MenuList/menus/sqle.tsx @@ -192,6 +192,15 @@ const dataSourceComparison: GenerateMenuItemI18nConfig = (projectID) => ({ key: `sqle/project/${SIDE_MENU_DATA_PLACEHOLDER_KEY}/data-source-comparison`, structKey: 'data-source-comparison' }); +const sqlPerformanceInsights: GenerateMenuItemI18nConfig = (projectID) => ({ + label: 'dmsMenu.sqlPerformanceInsights', + to: parse2ReactRouterPath(ROUTE_PATHS.SQLE.SQL_INSIGHTS.index, { + params: { projectID: projectID } + }), + icon: , + key: `sqle/project/${SIDE_MENU_DATA_PLACEHOLDER_KEY}/sql-insights`, + structKey: 'sql-insights' +}); const sqleMenusCollection = [ projectOverviewMenuItem, @@ -210,7 +219,8 @@ const sqleMenusCollection = [ sqlManagementException, pipelineConfiguration, versionManagement, - dataSourceComparison + dataSourceComparison, + sqlPerformanceInsights ]; export default sqleMenusCollection; diff --git a/packages/base/vite.config.mts b/packages/base/vite.config.mts index 1d64a64ea..1fed4fb72 100644 --- a/packages/base/vite.config.mts +++ b/packages/base/vite.config.mts @@ -114,13 +114,13 @@ export default defineConfig((config) => { open: true, proxy: { '^(/v|/sqle/v)': { - target: 'http://10.186.62.13:11000/' + target: 'http://10.186.64.24:10000/' }, '^/provision/v': { - target: 'http://10.186.62.13:11000/' + target: 'http://10.186.64.24:10000/' }, '^/logo': { - target: 'http://10.186.62.13:11000/' + target: 'http://10.186.64.24:10000/' } }, cors: true diff --git a/packages/shared/lib/api/sqle/index.ts b/packages/shared/lib/api/sqle/index.ts index 1a9c51084..565cb1541 100644 --- a/packages/shared/lib/api/sqle/index.ts +++ b/packages/shared/lib/api/sqle/index.ts @@ -1,3 +1,7 @@ +export { default as OperationRecordService } from './service/OperationRecord'; +export { default as ReportPushConfigService } from './service/ReportPushConfig'; +export { default as SqlDEVRecordService } from './service/SqlDEVRecord'; +export { default as SqlManageService } from './service/SqlManage'; export { default as AuditPlanService } from './service/audit_plan'; export { default as AuditWhitelistService } from './service/audit_whitelist'; export { default as BlacklistService } from './service/blacklist'; @@ -9,12 +13,8 @@ export { default as InstanceService } from './service/instance'; export { default as InstanceAuditPlanService } from './service/instance_audit_plan'; export { default as KnowledgeBaseService } from './service/knowledge_base'; export { default as OperationService } from './service/operation'; -export { default as OperationRecordService } from './service/OperationRecord'; export { default as PipelineService } from './service/pipeline'; -export { default as ReportPushConfigService } from './service/ReportPushConfig'; export { default as RuleTemplateService } from './service/rule_template'; -export { default as SqlDEVRecordService } from './service/SqlDEVRecord'; -export { default as SqlManageService } from './service/SqlManage'; export { default as SqlAnalysisService } from './service/sql_analysis'; export { default as SqlAuditService } from './service/sql_audit'; export { default as SqlAuditRecordService } from './service/sql_audit_record'; diff --git a/packages/shared/lib/api/sqle/service/SqlManage/index.d.ts b/packages/shared/lib/api/sqle/service/SqlManage/index.d.ts index 0c8ebd31d..eee7ac48b 100644 --- a/packages/shared/lib/api/sqle/service/SqlManage/index.d.ts +++ b/packages/shared/lib/api/sqle/service/SqlManage/index.d.ts @@ -12,6 +12,8 @@ import { exportSqlManageV1FilterStatusEnum, exportSqlManageV1SortFieldEnum, exportSqlManageV1SortOrderEnum, + GetSqlManageSqlPerformanceInsightsMetricNameEnum, + GetSqlManageSqlPerformanceInsightsRelatedSQLFilterSourceEnum, GetSqlManageListV2FilterSourceEnum, GetSqlManageListV2FilterAuditLevelEnum, GetSqlManageListV2FilterStatusEnum, @@ -43,7 +45,10 @@ import { ISqlManageCodingReq, IPostSqlManageCodingResp, IGetSqlManageSqlAnalysisResp, - ISqlManageAnalysisChartResp + ISqlManageAnalysisChartResp, + IGetSqlManageSqlPerformanceInsightsResp, + IGetSqlManageSqlPerformanceInsightsRelatedSQLResp, + IGetSqlManageSqlPerformanceInsightsRelatedTransactionResp } from '../common.d'; export interface IGetGlobalSqlManageListParams { @@ -198,6 +203,55 @@ export interface IGetSqlManageSqlAnalysisChartV1Params { export interface IGetSqlManageSqlAnalysisChartV1Return extends ISqlManageAnalysisChartResp {} +export interface IGetSqlManageSqlPerformanceInsightsParams { + project_name: string; + + metric_name: GetSqlManageSqlPerformanceInsightsMetricNameEnum; + + start_time: string; + + end_time: string; + + instance_name: string; +} + +export interface IGetSqlManageSqlPerformanceInsightsReturn + extends IGetSqlManageSqlPerformanceInsightsResp {} + +export interface IGetSqlManageSqlPerformanceInsightsRelatedSQLParams { + project_name: string; + + instance_name: string; + + start_time: string; + + end_time: string; + + filter_source?: GetSqlManageSqlPerformanceInsightsRelatedSQLFilterSourceEnum; + + order_by?: string; + + is_asc?: boolean; + + page_index: number; + + page_size: number; +} + +export interface IGetSqlManageSqlPerformanceInsightsRelatedSQLReturn + extends IGetSqlManageSqlPerformanceInsightsRelatedSQLResp {} + +export interface IGetSqlManageSqlPerformanceInsightsRelatedTransactionParams { + project_name: string; + + instance_name: string; + + sql_id: string; +} + +export interface IGetSqlManageSqlPerformanceInsightsRelatedTransactionReturn + extends IGetSqlManageSqlPerformanceInsightsRelatedTransactionResp {} + export interface IGetSqlManageListV2Params { project_name: string; diff --git a/packages/shared/lib/api/sqle/service/SqlManage/index.enum.ts b/packages/shared/lib/api/sqle/service/SqlManage/index.enum.ts index fc2401c3c..1b6820cfe 100644 --- a/packages/shared/lib/api/sqle/service/SqlManage/index.enum.ts +++ b/packages/shared/lib/api/sqle/service/SqlManage/index.enum.ts @@ -102,6 +102,22 @@ export enum exportSqlManageV1SortOrderEnum { 'desc' = 'desc' } +export enum GetSqlManageSqlPerformanceInsightsMetricNameEnum { + 'comprehensive_trend' = 'comprehensive_trend', + + 'slow_sql_trend' = 'slow_sql_trend', + + 'top_sql_trend' = 'top_sql_trend', + + 'active_session_trend' = 'active_session_trend' +} + +export enum GetSqlManageSqlPerformanceInsightsRelatedSQLFilterSourceEnum { + 'order' = 'order', + + 'sql_manage' = 'sql_manage' +} + export enum GetSqlManageListV2FilterSourceEnum { 'audit_plan' = 'audit_plan', diff --git a/packages/shared/lib/api/sqle/service/SqlManage/index.ts b/packages/shared/lib/api/sqle/service/SqlManage/index.ts index 535cf514a..9e934930b 100644 --- a/packages/shared/lib/api/sqle/service/SqlManage/index.ts +++ b/packages/shared/lib/api/sqle/service/SqlManage/index.ts @@ -26,6 +26,12 @@ import { IGetSqlManageSqlAnalysisV1Return, IGetSqlManageSqlAnalysisChartV1Params, IGetSqlManageSqlAnalysisChartV1Return, + IGetSqlManageSqlPerformanceInsightsParams, + IGetSqlManageSqlPerformanceInsightsReturn, + IGetSqlManageSqlPerformanceInsightsRelatedSQLParams, + IGetSqlManageSqlPerformanceInsightsRelatedSQLReturn, + IGetSqlManageSqlPerformanceInsightsRelatedTransactionParams, + IGetSqlManageSqlPerformanceInsightsRelatedTransactionReturn, IGetSqlManageListV2Params, IGetSqlManageListV2Return, IExportSqlManageV2Params, @@ -184,6 +190,51 @@ class SqlManageService extends ServiceBase { ); } + public GetSqlManageSqlPerformanceInsights( + params: IGetSqlManageSqlPerformanceInsightsParams, + options?: AxiosRequestConfig + ) { + const paramsData = this.cloneDeep(params); + const project_name = paramsData.project_name; + delete paramsData.project_name; + + return this.get( + `/v1/projects/${project_name}/sql_performance_insights`, + paramsData, + options + ); + } + + public GetSqlManageSqlPerformanceInsightsRelatedSQL( + params: IGetSqlManageSqlPerformanceInsightsRelatedSQLParams, + options?: AxiosRequestConfig + ) { + const paramsData = this.cloneDeep(params); + const project_name = paramsData.project_name; + delete paramsData.project_name; + + return this.get( + `/v1/projects/${project_name}/sql_performance_insights/related_sql`, + paramsData, + options + ); + } + + public GetSqlManageSqlPerformanceInsightsRelatedTransaction( + params: IGetSqlManageSqlPerformanceInsightsRelatedTransactionParams, + options?: AxiosRequestConfig + ) { + const paramsData = this.cloneDeep(params); + const project_name = paramsData.project_name; + delete paramsData.project_name; + + return this.get( + `/v1/projects/${project_name}/sql_performance_insights/related_sql/related_transaction`, + paramsData, + options + ); + } + public GetSqlManageListV2( params: IGetSqlManageListV2Params, options?: AxiosRequestConfig diff --git a/packages/shared/lib/api/sqle/service/common.d.ts b/packages/shared/lib/api/sqle/service/common.d.ts index c08aa0c3b..c1117ef4a 100644 --- a/packages/shared/lib/api/sqle/service/common.d.ts +++ b/packages/shared/lib/api/sqle/service/common.d.ts @@ -41,6 +41,7 @@ import { ObjectDiffResultComparisonResultEnum, OperationRecordListStatusEnum, RecordSourceNameEnum, + RelatedSQLInfoSourceEnum, ReportPushConfigListPushUserTypeEnum, ReportPushConfigListTriggerTypeEnum, RewriteSuggestionAuditLevelEnum, @@ -57,6 +58,10 @@ import { SqlVersionDetailResV1StatusEnum, SqlVersionResV1StatusEnum, TestFeishuConfigurationReqV1AccountTypeEnum, + TransactionInfoLockTypeEnum, + TransactionInfoTransactionStateEnum, + TransactionLockInfoLockTypeEnum, + TransactionSQLLockTypeEnum, UpdateAuditPlanNotifyConfigReqV1NotifyLevelEnum, UpdateAuditPlanStatusReqV1ActiveEnum, UpdateAuditWhitelistReqV1MatchTypeEnum, @@ -1482,6 +1487,14 @@ export interface IGetInstanceHealthResV1 { message?: string; } +export interface IGetInstanceOverviewStatisticsRes { + code?: number; + + data?: IInstanceOverviewStatistics[]; + + message?: string; +} + export interface IGetInstanceSchemaResV1 { code?: number; @@ -1884,6 +1897,32 @@ export interface IGetSqlManageSqlAnalysisResp { message?: string; } +export interface IGetSqlManageSqlPerformanceInsightsRelatedSQLResp { + code?: number; + + data?: IRelatedSQLInfo[]; + + message?: string; + + total_nums?: number; +} + +export interface IGetSqlManageSqlPerformanceInsightsRelatedTransactionResp { + code?: number; + + data?: IRelatedTransactionInfo; + + message?: string; +} + +export interface IGetSqlManageSqlPerformanceInsightsResp { + code?: number; + + data?: ISqlManageSqlPerformanceInsights; + + message?: string; +} + export interface IGetSqlVersionDetailResV1 { code?: number; @@ -2270,6 +2309,16 @@ export interface IInstanceInfo { instance_name?: string; } +export interface IInstanceOverviewStatistics { + avg_score?: number; + + high_priority_sql_count?: number; + + instance_id?: string; + + pending_workflow_count?: number; +} + export interface IInstanceSchemaResV1 { schema_name_list?: string[]; } @@ -2360,6 +2409,12 @@ export interface ILicenseUsageV1 { users_usage?: ILicenseUsageItem; } +export interface ILine { + line_name?: string; + + points?: IChartPoint[]; +} + export interface IListTableBySchemaResV1 { code?: number; @@ -2626,6 +2681,32 @@ export interface IRejectWorkflowReqV1 { reason?: string; } +export interface IRelatedSQLInfo { + execute_end_time?: string; + + execute_start_time?: string; + + execute_time?: number; + + execution_cost_trend?: ISqlAnalysisScatterChart; + + lock_wait_time?: number; + + source?: RelatedSQLInfoSourceEnum; + + sql_fingerprint?: string; +} + +export interface IRelatedTransactionInfo { + related_sql_info?: ITransactionSQL[]; + + transaction_info?: ITransactionInfo; + + transaction_lock_info?: ITransactionLockInfo[]; + + transaction_timeline?: ITransactionTimeline; +} + export interface IReleaseWorkflows { target_release_instances?: ITargetReleaseInstance[]; @@ -2810,6 +2891,8 @@ export interface IRuleResV1 { rule_name?: string; type?: string; + + version?: number; } export interface IRuleRespV1 { @@ -2954,6 +3037,22 @@ export interface ISSHPublicKeyInfoV1Rsp { message?: string; } +export interface IScatterPoint { + cost?: number; + + id?: number; + + info?: Array<{ + [key: string]: string; + }>; + + is_in_transaction?: boolean; + + sql?: string; + + time?: string; +} + export interface IScheduleTaskDefaultOption { default_selector?: ScheduleTaskDefaultOptionDefaultSelectorEnum; } @@ -3010,6 +3109,16 @@ export interface ISqlAnalysisResDataV1 { table_metas?: ITableMeta[]; } +export interface ISqlAnalysisScatterChart { + message?: string; + + points?: IScatterPoint[]; + + x_info?: string; + + y_info?: string; +} + export interface ISqlAverageExecutionTime { average_execution_seconds?: number; @@ -3112,6 +3221,16 @@ export interface ISqlManageCodingReq { type?: SqlManageCodingReqTypeEnum; } +export interface ISqlManageSqlPerformanceInsights { + lines?: ILine[]; + + message?: string; + + x_info?: string; + + y_info?: string; +} + export interface ISqlVersionDetailResV1 { desc?: string; @@ -3366,6 +3485,48 @@ export interface ITimeResV1 { minute?: number; } +export interface ITransactionInfo { + lock_type?: TransactionInfoLockTypeEnum; + + transaction_duration?: number; + + transaction_end_time?: string; + + transaction_id?: string; + + transaction_start_time?: string; + + transaction_state?: TransactionInfoTransactionStateEnum; +} + +export interface ITransactionLockInfo { + create_lock_sql?: string; + + lock_type?: TransactionLockInfoLockTypeEnum; + + table_name?: string; +} + +export interface ITransactionSQL { + execute_duration?: number; + + lock_type?: TransactionSQLLockTypeEnum; + + sql?: string; +} + +export interface ITransactionTimeline { + current_step_index?: number; + + timeline?: ITransactionTimelineItem[]; +} + +export interface ITransactionTimelineItem { + description?: string; + + start_time?: string; +} + export interface ITriggerAuditPlanResV1 { code?: number; diff --git a/packages/shared/lib/api/sqle/service/common.enum.ts b/packages/shared/lib/api/sqle/service/common.enum.ts index 228b4d2bc..da33ad58d 100644 --- a/packages/shared/lib/api/sqle/service/common.enum.ts +++ b/packages/shared/lib/api/sqle/service/common.enum.ts @@ -390,6 +390,12 @@ export enum RecordSourceNameEnum { 'ide_plugin' = 'ide_plugin' } +export enum RelatedSQLInfoSourceEnum { + 'order' = 'order', + + 'sql_manage' = 'sql_manage' +} + export enum ReportPushConfigListPushUserTypeEnum { 'fixed' = 'fixed', @@ -520,6 +526,66 @@ export enum TestFeishuConfigurationReqV1AccountTypeEnum { 'phone' = 'phone' } +export enum TransactionInfoLockTypeEnum { + 'SHARED' = 'SHARED', + + 'EXCLUSIVE' = 'EXCLUSIVE', + + 'INTENTION_SHARED' = 'INTENTION_SHARED', + + 'INTENTION_EXCLUSIVE' = 'INTENTION_EXCLUSIVE', + + 'SHARED_INTENTION_EXCLUSIVE' = 'SHARED_INTENTION_EXCLUSIVE', + + 'ROW_LOCK' = 'ROW_LOCK', + + 'TABLE_LOCK' = 'TABLE_LOCK', + + 'METADATA_LOCK' = 'METADATA_LOCK' +} + +export enum TransactionInfoTransactionStateEnum { + 'RUNNING' = 'RUNNING', + + 'COMPLETED' = 'COMPLETED' +} + +export enum TransactionLockInfoLockTypeEnum { + 'SHARED' = 'SHARED', + + 'EXCLUSIVE' = 'EXCLUSIVE', + + 'INTENTION_SHARED' = 'INTENTION_SHARED', + + 'INTENTION_EXCLUSIVE' = 'INTENTION_EXCLUSIVE', + + 'SHARED_INTENTION_EXCLUSIVE' = 'SHARED_INTENTION_EXCLUSIVE', + + 'ROW_LOCK' = 'ROW_LOCK', + + 'TABLE_LOCK' = 'TABLE_LOCK', + + 'METADATA_LOCK' = 'METADATA_LOCK' +} + +export enum TransactionSQLLockTypeEnum { + 'SHARED' = 'SHARED', + + 'EXCLUSIVE' = 'EXCLUSIVE', + + 'INTENTION_SHARED' = 'INTENTION_SHARED', + + 'INTENTION_EXCLUSIVE' = 'INTENTION_EXCLUSIVE', + + 'SHARED_INTENTION_EXCLUSIVE' = 'SHARED_INTENTION_EXCLUSIVE', + + 'ROW_LOCK' = 'ROW_LOCK', + + 'TABLE_LOCK' = 'TABLE_LOCK', + + 'METADATA_LOCK' = 'METADATA_LOCK' +} + export enum UpdateAuditPlanNotifyConfigReqV1NotifyLevelEnum { 'normal' = 'normal', diff --git a/packages/shared/lib/api/sqle/service/statistic/index.d.ts b/packages/shared/lib/api/sqle/service/statistic/index.d.ts index 920b08054..ada4b6e8e 100644 --- a/packages/shared/lib/api/sqle/service/statistic/index.d.ts +++ b/packages/shared/lib/api/sqle/service/statistic/index.d.ts @@ -8,6 +8,7 @@ import { IGetRoleUserCountResV1, IGetWorkflowStatusCountResV1, IGetProjectStatisticsResV1, + IGetInstanceOverviewStatisticsRes, IGetSqlAverageExecutionTimeResV1, IGetSqlExecutionFailPercentResV1, IGetInstancesTypePercentResV1, @@ -81,6 +82,13 @@ export interface IGetProjectStatisticsV1Params { export interface IGetProjectStatisticsV1Return extends IGetProjectStatisticsResV1 {} +export interface IGetInstanceOverviewStatisticsV1Params { + filter_by_db_service_ids?: string[]; +} + +export interface IGetInstanceOverviewStatisticsV1Return + extends IGetInstanceOverviewStatisticsRes {} + export interface IGetSqlAverageExecutionTimeV1Params { limit: number; } diff --git a/packages/shared/lib/api/sqle/service/statistic/index.ts b/packages/shared/lib/api/sqle/service/statistic/index.ts index 34df051ff..0d4a6cb3e 100644 --- a/packages/shared/lib/api/sqle/service/statistic/index.ts +++ b/packages/shared/lib/api/sqle/service/statistic/index.ts @@ -25,6 +25,8 @@ import { IStatisticWorkflowStatusV1Return, IGetProjectStatisticsV1Params, IGetProjectStatisticsV1Return, + IGetInstanceOverviewStatisticsV1Params, + IGetInstanceOverviewStatisticsV1Return, IGetSqlAverageExecutionTimeV1Params, IGetSqlAverageExecutionTimeV1Return, IGetSqlExecutionFailPercentV1Params, @@ -182,6 +184,18 @@ class StatisticService extends ServiceBase { ); } + public getInstanceOverviewStatisticsV1( + params: IGetInstanceOverviewStatisticsV1Params, + options?: AxiosRequestConfig + ) { + const paramsData = this.cloneDeep(params); + return this.get( + '/v1/statistic/instances/resource_overview_statistics', + paramsData, + options + ); + } + public getSqlAverageExecutionTimeV1( params: IGetSqlAverageExecutionTimeV1Params, options?: AxiosRequestConfig diff --git a/packages/shared/lib/data/EmitterKey.ts b/packages/shared/lib/data/EmitterKey.ts index 35134ccd2..9cbe584d0 100644 --- a/packages/shared/lib/data/EmitterKey.ts +++ b/packages/shared/lib/data/EmitterKey.ts @@ -1,7 +1,9 @@ enum EmitterKey { UPDATE_LOCAL_COLUMNS = 'UPDATE_LOCAL_COLUMNS', - OPEN_GLOBAL_NOTIFICATION = 'OPEN_GLOBAL_NOTIFICATION' + OPEN_GLOBAL_NOTIFICATION = 'OPEN_GLOBAL_NOTIFICATION', + + SQL_INSIGHTS_LINE_CHART_MASK_INTERACTION = 'SQL_INSIGHTS_LINE_CHART_MASK_INTERACTION' } export default EmitterKey; diff --git a/packages/shared/lib/data/routePaths.ts b/packages/shared/lib/data/routePaths.ts index 9892833ab..58da472ba 100644 --- a/packages/shared/lib/data/routePaths.ts +++ b/packages/shared/lib/data/routePaths.ts @@ -255,6 +255,12 @@ export const ROUTE_PATHS = { path: ':sqlManageId/analyze' } }, + SQL_INSIGHTS: { + index: { + prefix: '/sqle/project', + path: ':projectID/sql-insights' + } + }, PROJECT_DASHBOARD: { index: { prefix: '/sqle/project', diff --git a/packages/shared/lib/styleWrapper/element.ts b/packages/shared/lib/styleWrapper/element.ts index d58a1c2ee..aa3815886 100644 --- a/packages/shared/lib/styleWrapper/element.ts +++ b/packages/shared/lib/styleWrapper/element.ts @@ -90,6 +90,8 @@ export const SegmentedRowStyleWrapper = styled(Row)` ${({ theme }) => theme.sharedTheme.uiToken.colorBorderSecondary}; `; +export const EmptyRowStyleWrapper = SegmentedRowStyleWrapper; + export const RuleStatusWrapperStyleWrapper = styled('div')` padding: 10px 40px; border-bottom: 1px solid diff --git a/packages/shared/lib/utils/Common.ts b/packages/shared/lib/utils/Common.ts index 715a9e69d..7e6685f2c 100644 --- a/packages/shared/lib/utils/Common.ts +++ b/packages/shared/lib/utils/Common.ts @@ -41,7 +41,7 @@ export const integerValidate = (value: string): boolean => { }; export const formatTime = ( - timeVal?: string | Date, + timeVal?: string | Date | Dayjs, defaultVal = '' ): string => { if (!timeVal) { diff --git a/packages/sqle/src/data/ModalName.ts b/packages/sqle/src/data/ModalName.ts index a00c84754..fa924d373 100644 --- a/packages/sqle/src/data/ModalName.ts +++ b/packages/sqle/src/data/ModalName.ts @@ -34,5 +34,8 @@ export enum ModalName { Version_Management_Associate_Workflow_Modal = 'Version_Management_Associate_Workflow_Modal', - Version_Management_Offline_Execute_Modal = 'Version_Management_Offline_Execute_Modal' + Version_Management_Offline_Execute_Modal = 'Version_Management_Offline_Execute_Modal', + + // sql insights + Sql_Insights_Related_SQL_Item_Relate_Transaction_Drawer = 'SQL_INSIGHTS_RELATED_SQL_ITEM_RELATE_TRANSACTION_DRAWER' } diff --git a/packages/sqle/src/locale/en-US/index.ts b/packages/sqle/src/locale/en-US/index.ts index e5ddbed1f..91ecef89a 100644 --- a/packages/sqle/src/locale/en-US/index.ts +++ b/packages/sqle/src/locale/en-US/index.ts @@ -26,6 +26,7 @@ import pushRule from './pushRule'; import sqlManagementException from './sqlManagementException'; import pipelineConfiguration from './pipelineConfiguration'; import versionManagement from './versionManagement'; +import sqlInsights from './sqlInsights'; // eslint-disable-next-line import/no-anonymous-default-export export default { @@ -57,6 +58,7 @@ export default { pushRule, sqlManagementException, pipelineConfiguration, - versionManagement + versionManagement, + sqlInsights } }; diff --git a/packages/sqle/src/locale/en-US/sqlInsights.ts b/packages/sqle/src/locale/en-US/sqlInsights.ts new file mode 100644 index 000000000..9e4917eac --- /dev/null +++ b/packages/sqle/src/locale/en-US/sqlInsights.ts @@ -0,0 +1,57 @@ +// eslint-disable-next-line import/no-anonymous-default-export +export default { + title: 'SQL Insights', + dataSourceSelect: 'Data Source', + dateRange: 'Date Range', + chart: { + xAxisTitle: 'Date', + yAxisTitle: 'Value', + noData: 'No Data', + noValidData: 'No valid data in the selected area' + }, + performanceTrend: { + title: 'Data Source Performance Trend' + }, + slowSqlTrend: { + title: 'Slow SQL Trend' + }, + topSqlTrend: { + title: 'Top SQL Execution Trend' + }, + activeSessionTrend: { + title: 'Active Session Trend' + }, + relatedSqlList: { + title: 'SQL Related to Selected Period', + dateRangePlaceholder: 'Please select date range', + column: { + sqlFingerprint: 'SQL Fingerprint', + source: 'Source', + executeStartTime: 'Execution Start Time', + executeEndTime: 'Execution End Time', + executeTime: 'Execution Time', + lockWaitTime: 'Lock Wait Time' + }, + source: { + order: 'Order', + sqlManage: 'SQL Control' + }, + actions: { + analyzeSql: 'SQL Analysis', + viewRelatedTransactions: 'View Related Transactions' + }, + sqlFingerprintDetail: { + title: 'SQL Fingerprint Detail', + chart: { + xAxisTitle: 'Date', + yAxisTitle: 'Execution Cost', + analyzeButtonText: + 'Click node to view detailed SQL analysis in SQL Analyze' + } + }, + sqlRelatedTransaction: { + title: 'SQL Related Transactions' + }, + noExecutionCostTrend: 'No execution cost trend data' + } +}; diff --git a/packages/sqle/src/locale/zh-CN/index.ts b/packages/sqle/src/locale/zh-CN/index.ts index ad1f377f4..44caf2d4c 100644 --- a/packages/sqle/src/locale/zh-CN/index.ts +++ b/packages/sqle/src/locale/zh-CN/index.ts @@ -1,4 +1,4 @@ -// @warn/cli/create-dms-page +import sqlInsights from './sqlInsights'; // @warn/cli/create-dms-page import audit from './audit'; import dashboard from './dashboard'; @@ -67,6 +67,7 @@ export default { globalDashboard, dataSourceComparison, sqlRewrite, - knowledgeBase + knowledgeBase, + sqlInsights } }; diff --git a/packages/sqle/src/locale/zh-CN/sqlInsights.ts b/packages/sqle/src/locale/zh-CN/sqlInsights.ts new file mode 100644 index 000000000..259b38975 --- /dev/null +++ b/packages/sqle/src/locale/zh-CN/sqlInsights.ts @@ -0,0 +1,90 @@ +// eslint-disable-next-line import/no-anonymous-default-export +export default { + title: 'SQL洞察', + dataSourceSelect: '数据源', + dateRange: '日期范围', + chart: { + xAxisTitle: '日期', + yAxisTitle: '数值', + noData: '暂无数据', + noValidData: '选区中没有任何有效数据' + }, + performanceTrend: { + title: '数据源综合性能趋势' + }, + slowSqlTrend: { + title: '慢SQL趋势' + }, + topSqlTrend: { + title: 'TopSQL执行趋势' + }, + activeSessionTrend: { + title: '活跃会话数趋势' + }, + relatedSqlList: { + title: '选定区间关联的SQL', + dateRangePlaceholder: '请选择日期范围', + column: { + sqlFingerprint: 'SQL指纹', + source: '来源', + executeStartTime: '执行开始时间', + executeEndTime: '执行结束时间', + executeTime: '执行时间', + lockWaitTime: '锁等待时间' + }, + source: { + order: '工单', + sqlManage: 'SQL管控' + }, + actions: { + analyzeSql: 'SQL分析', + viewRelatedTransactions: '查看关联事务' + }, + sqlFingerprintDetail: { + title: 'SQL指纹详情', + chart: { + xAxisTitle: '日期', + yAxisTitle: '执行成本', + analyzeButtonText: '点击节点到 SQL分析 查看当前sql的详细分析' + } + }, + sqlRelatedTransaction: { + title: 'SQL关联事务', + actions: { + showSQAnalysis: '查看SQL分析' + }, + noData: '暂无事务数据', + originalSql: '原始SQL', + transactionInfo: { + id: '事务ID', + lockType: '锁类型', + duration: '事务总耗时', + startTime: '开始时间', + state: '状态', + commitTime: '提交时间', + lockTypeDict: { + exclusive: '排他锁', + shared: '共享锁', + intentionShared: '意向共享锁', + intentionExclusive: '意向排他锁', + sharedIntentionExclusive: '共享意向排他锁', + rowLock: '行锁', + tableLock: '表锁', + metadataLock: '元数据锁' + } + }, + timeline: '事务时间线', + relatedSqlList: '关联SQL列表', + sqlInfo: { + executeDuration: '执行耗时', + lockType: '锁类型', + sqlAnalysis: 'SQL分析' + }, + lockAnalysis: { + title: '锁等待分析', + desc: '{{table_name}}表: {{lock_type}} - 由{{create_lock_sql}}语句获取' + } + }, + noExecutionCostTrend: '没有执行成本趋势数据' + } +}; diff --git a/packages/sqle/src/page/SqlInsights/components/ActiveSessionTrend/index.tsx b/packages/sqle/src/page/SqlInsights/components/ActiveSessionTrend/index.tsx new file mode 100644 index 000000000..596abb7f5 --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/components/ActiveSessionTrend/index.tsx @@ -0,0 +1,46 @@ +import { useTranslation } from 'react-i18next'; +import { Dayjs } from 'dayjs'; +import { ActiveSessionTrendStyleWrapper } from './style'; +import { GetSqlManageSqlPerformanceInsightsMetricNameEnum } from '@actiontech/shared/lib/api/sqle/service/SqlManage/index.enum'; +import SqlInsightsLineChart from '../SqlInsightsLineChart'; +import useSqlInsightsMetric from '../../hooks/useSqlInsightsMetric'; +import EmitterKey from '@actiontech/shared/lib/data/EmitterKey'; +import useRelatedSqlRedux from '../RelatedSqlList/useRelatedSqlRedux'; + +export interface ActiveSessionTrendProps { + instanceId?: string; + dateRange?: [Dayjs, Dayjs]; +} + +const ActiveSessionTrend: React.FC = ({ + instanceId, + dateRange +}) => { + const { t } = useTranslation(); + const { updateRelateSqlListDateRange } = useRelatedSqlRedux(); + const { loading, chartData } = useSqlInsightsMetric({ + instanceId, + dateRange, + metricName: + GetSqlManageSqlPerformanceInsightsMetricNameEnum.active_session_trend + }); + + return ( + + { + updateRelateSqlListDateRange(selectedDateRange); + }} + /> + + ); +}; + +export default ActiveSessionTrend; diff --git a/packages/sqle/src/page/SqlInsights/components/ActiveSessionTrend/style.ts b/packages/sqle/src/page/SqlInsights/components/ActiveSessionTrend/style.ts new file mode 100644 index 000000000..6d7178a2e --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/components/ActiveSessionTrend/style.ts @@ -0,0 +1,17 @@ +import { styled } from '@mui/material/styles'; +import { SQL_INSIGHTS_CHART_HEIGHT } from '../../index.data'; + +export const ActiveSessionTrendStyleWrapper = styled('div')` + margin-top: 16px; + padding: 20px; + + .chart-title { + font-size: 16px; + font-weight: 500; + margin-bottom: 20px; + } + + .active-session-trend-chart { + height: ${SQL_INSIGHTS_CHART_HEIGHT}px; + } +`; diff --git a/packages/sqle/src/page/SqlInsights/components/DataSourcePerformanceTrend/index.tsx b/packages/sqle/src/page/SqlInsights/components/DataSourcePerformanceTrend/index.tsx new file mode 100644 index 000000000..9c952f534 --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/components/DataSourcePerformanceTrend/index.tsx @@ -0,0 +1,46 @@ +import { useTranslation } from 'react-i18next'; +import { Dayjs } from 'dayjs'; +import { DataSourcePerformanceTrendStyleWrapper } from './style'; +import { GetSqlManageSqlPerformanceInsightsMetricNameEnum } from '@actiontech/shared/lib/api/sqle/service/SqlManage/index.enum'; +import SqlInsightsLineChart from '../SqlInsightsLineChart'; +import useSqlInsightsMetric from '../../hooks/useSqlInsightsMetric'; +import EmitterKey from '@actiontech/shared/lib/data/EmitterKey'; +import useRelatedSqlRedux from '../RelatedSqlList/useRelatedSqlRedux'; + +export interface DataSourcePerformanceTrendProps { + instanceId?: string; + dateRange?: [Dayjs, Dayjs]; +} + +const DataSourcePerformanceTrend: React.FC = ({ + instanceId, + dateRange +}) => { + const { t } = useTranslation(); + const { updateRelateSqlListDateRange } = useRelatedSqlRedux(); + const { loading, chartData } = useSqlInsightsMetric({ + instanceId, + dateRange, + metricName: + GetSqlManageSqlPerformanceInsightsMetricNameEnum.comprehensive_trend + }); + + return ( + + { + updateRelateSqlListDateRange(selectedDateRange); + }} + /> + + ); +}; + +export default DataSourcePerformanceTrend; diff --git a/packages/sqle/src/page/SqlInsights/components/DataSourcePerformanceTrend/style.ts b/packages/sqle/src/page/SqlInsights/components/DataSourcePerformanceTrend/style.ts new file mode 100644 index 000000000..d8fea2931 --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/components/DataSourcePerformanceTrend/style.ts @@ -0,0 +1,19 @@ +import { styled } from '@mui/material/styles'; +import { SQL_INSIGHTS_CHART_HEIGHT } from '../../index.data'; + +export const DataSourcePerformanceTrendStyleWrapper = styled('div')` + padding: 20px; + border-radius: 8px; + + .chart-title { + font-size: 16px; + font-weight: 500; + color: ${({ theme }) => theme.sharedTheme.uiToken.colorText}; + margin-bottom: 16px; + } + + .performance-trend-chart { + width: 100%; + height: ${SQL_INSIGHTS_CHART_HEIGHT}px; + } +`; diff --git a/packages/sqle/src/page/SqlInsights/components/DrawerManager/SqlRelatedTransactionDrawer/data.tsx b/packages/sqle/src/page/SqlInsights/components/DrawerManager/SqlRelatedTransactionDrawer/data.tsx new file mode 100644 index 000000000..5aaeef6d7 --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/components/DrawerManager/SqlRelatedTransactionDrawer/data.tsx @@ -0,0 +1,40 @@ +import { TransactionInfoLockTypeEnum } from '@actiontech/shared/lib/api/sqle/service/common.enum'; +import { t } from '../../../../../locale'; + +export const TransactionInfoLockTypeEnumDict = { + [TransactionInfoLockTypeEnum.EXCLUSIVE]: t( + 'sqlInsights.relatedSqlList.sqlRelatedTransaction.transactionInfo.lockTypeDict.exclusive' + ), + [TransactionInfoLockTypeEnum.SHARED]: t( + 'sqlInsights.relatedSqlList.sqlRelatedTransaction.transactionInfo.lockTypeDict.shared' + ), + [TransactionInfoLockTypeEnum.INTENTION_SHARED]: t( + 'sqlInsights.relatedSqlList.sqlRelatedTransaction.transactionInfo.lockTypeDict.intentionShared' + ), + [TransactionInfoLockTypeEnum.INTENTION_EXCLUSIVE]: t( + 'sqlInsights.relatedSqlList.sqlRelatedTransaction.transactionInfo.lockTypeDict.intentionExclusive' + ), + [TransactionInfoLockTypeEnum.SHARED_INTENTION_EXCLUSIVE]: t( + 'sqlInsights.relatedSqlList.sqlRelatedTransaction.transactionInfo.lockTypeDict.sharedIntentionExclusive' + ), + [TransactionInfoLockTypeEnum.ROW_LOCK]: t( + 'sqlInsights.relatedSqlList.sqlRelatedTransaction.transactionInfo.lockTypeDict.rowLock' + ), + [TransactionInfoLockTypeEnum.TABLE_LOCK]: t( + 'sqlInsights.relatedSqlList.sqlRelatedTransaction.transactionInfo.lockTypeDict.tableLock' + ), + [TransactionInfoLockTypeEnum.METADATA_LOCK]: t( + 'sqlInsights.relatedSqlList.sqlRelatedTransaction.transactionInfo.lockTypeDict.metadataLock' + ) +}; + +export const TransactionInfoLockTypeEnumFlagDict = { + [TransactionInfoLockTypeEnum.SHARED]: 'S', + [TransactionInfoLockTypeEnum.INTENTION_SHARED]: 'IS', + [TransactionInfoLockTypeEnum.INTENTION_EXCLUSIVE]: 'IX', + [TransactionInfoLockTypeEnum.SHARED_INTENTION_EXCLUSIVE]: 'SIX', + [TransactionInfoLockTypeEnum.ROW_LOCK]: 'RL', + [TransactionInfoLockTypeEnum.TABLE_LOCK]: 'TL', + [TransactionInfoLockTypeEnum.METADATA_LOCK]: 'ML', + [TransactionInfoLockTypeEnum.EXCLUSIVE]: 'X' +}; diff --git a/packages/sqle/src/page/SqlInsights/components/DrawerManager/SqlRelatedTransactionDrawer/index.tsx b/packages/sqle/src/page/SqlInsights/components/DrawerManager/SqlRelatedTransactionDrawer/index.tsx new file mode 100644 index 000000000..65f551926 --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/components/DrawerManager/SqlRelatedTransactionDrawer/index.tsx @@ -0,0 +1,278 @@ +import { useDispatch, useSelector } from 'react-redux'; +import { IReduxState } from '../../../../../store'; +import { ModalName } from '../../../../../data/ModalName'; +import { useCallback, useEffect, useState } from 'react'; +import { + initSqlInsightsModalStatus, + updateSqlInsightsModalStatus +} from '../../../../../store/sqlInsights'; +import { useTranslation } from 'react-i18next'; +import { BasicDrawer } from '@actiontech/shared/lib/components/BasicDrawer'; +import { BasicButton, HighlightCode, EmptyBox } from '@actiontech/shared'; +import { Descriptions, Divider, Steps, Typography, Spin } from 'antd'; +import { + IRelatedSQLInfo, + IRelatedTransactionInfo +} from '@actiontech/shared/lib/api/sqle/service/common'; +import SqlManage from '@actiontech/shared/lib/api/sqle/service/SqlManage'; +import { useCurrentProject } from '@actiontech/shared/lib/features'; +import { + TransactionInfoLockTypeEnumDict, + TransactionInfoLockTypeEnumFlagDict +} from './data'; + +const SqlRelatedTransactionDrawer = () => { + const { t } = useTranslation(); + const { projectName } = useCurrentProject(); + const [loading, setLoading] = useState(false); + const [transactionData, setTransactionData] = + useState(); + + const dispatch = useDispatch(); + + const visible = useSelector( + (state) => + !!state.sqlInsights.modalStatus[ + ModalName.Sql_Insights_Related_SQL_Item_Relate_Transaction_Drawer + ] + ); + + const selectedRecord = useSelector( + (state) => state.sqlInsights.relateSqlList.selectedRecord + ); + + const closeModal = useCallback(() => { + dispatch( + updateSqlInsightsModalStatus({ + modalName: + ModalName.Sql_Insights_Related_SQL_Item_Relate_Transaction_Drawer, + status: false + }) + ); + }, [dispatch]); + + useEffect(() => { + dispatch( + initSqlInsightsModalStatus({ + modalStatus: { + [ModalName.Sql_Insights_Related_SQL_Item_Relate_Transaction_Drawer]: + false + } + }) + ); + }, [dispatch]); + + const onShowSQAnalysis = useCallback(() => { + console.log('onShowSQAnalysis'); + }, []); + + const getTransactionData = useCallback(async () => { + if (!projectName || !selectedRecord || !visible) return; + + setLoading(true); + try { + const res = + // fixme: 这个接口的参数还为确定。需要跟具体实现的后端讨论。 + await SqlManage.GetSqlManageSqlPerformanceInsightsRelatedTransaction({ + project_name: projectName, + instance_name: '123', + sql_id: '123' + }); + setTransactionData(res.data.data); + } finally { + setLoading(false); + } + }, [projectName, selectedRecord, visible]); + + useEffect(() => { + getTransactionData(); + }, [getTransactionData]); + + return ( + + {t( + 'sqlInsights.relatedSqlList.sqlRelatedTransaction.actions.showSQAnalysis' + )} + + } + width={520} + > + + + {t('sqlInsights.relatedSqlList.sqlRelatedTransaction.noData')} + + } + > + {/* 抽屉内容 */} + + {t('sqlInsights.relatedSqlList.sqlRelatedTransaction.originalSql')} + +
+ + + + {transactionData?.transaction_info?.transaction_id || '-'} + + + {transactionData?.transaction_info?.lock_type + ? TransactionInfoLockTypeEnumDict[ + transactionData?.transaction_info?.lock_type + ] + : '-'} + + + {transactionData?.transaction_info?.transaction_duration || '-'} + + + {transactionData?.transaction_info?.transaction_start_time || '-'} + + + {transactionData?.transaction_info?.transaction_state || '-'} + + + {transactionData?.transaction_info?.transaction_start_time || '-'} + + + + + {t('sqlInsights.relatedSqlList.sqlRelatedTransaction.timeline')} + + ({ + title: item.start_time, + description: item.description + })) || [] + } + /> + + + {t( + 'sqlInsights.relatedSqlList.sqlRelatedTransaction.relatedSqlList' + )} + +
+ {transactionData?.related_sql_info?.map((sql, index) => ( +
+
+
+ + + {sql.execute_duration || '-'} + + + {sql.lock_type + ? TransactionInfoLockTypeEnumDict[sql.lock_type] + : '-'} + + + + {t( + 'sqlInsights.relatedSqlList.sqlRelatedTransaction.sqlInfo.sqlAnalysis' + )} + + + +
+
+ ))} +
+ + + {t( + 'sqlInsights.relatedSqlList.sqlRelatedTransaction.lockAnalysis.title' + )} + + + 0)} + defaultNode={t( + 'sqlInsights.relatedSqlList.sqlRelatedTransaction.lockAnalysis.noData' + )} + > +
    + {transactionData?.transaction_lock_info?.map((lock, index) => ( +
  • + {t( + 'sqlInsights.relatedSqlList.sqlRelatedTransaction.lockAnalysis.desc', + { + table_name: lock.table_name, + lock_type: lock.lock_type + ? `${ + TransactionInfoLockTypeEnumDict[lock.lock_type] + } (${ + TransactionInfoLockTypeEnumFlagDict[ + lock.lock_type + ] + })` + : '-', + create_lock_sql: lock.create_lock_sql + } + )} +
  • + ))} +
+
+
+ + + + ); +}; + +export default SqlRelatedTransactionDrawer; diff --git a/packages/sqle/src/page/SqlInsights/components/DrawerManager/index.tsx b/packages/sqle/src/page/SqlInsights/components/DrawerManager/index.tsx new file mode 100644 index 000000000..3a838ea0e --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/components/DrawerManager/index.tsx @@ -0,0 +1,11 @@ +import SqlRelatedTransactionDrawer from './SqlRelatedTransactionDrawer'; + +const DrawerManager: React.FC = () => { + return ( + <> + + + ); +}; + +export default DrawerManager; diff --git a/packages/sqle/src/page/SqlInsights/components/RelatedSqlList/SqlExecutionCostTrendChart.tsx b/packages/sqle/src/page/SqlInsights/components/RelatedSqlList/SqlExecutionCostTrendChart.tsx new file mode 100644 index 000000000..d8cd78c6d --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/components/RelatedSqlList/SqlExecutionCostTrendChart.tsx @@ -0,0 +1,167 @@ +import { Spin, Typography } from 'antd'; +import { Scatter, ScatterConfig, Tooltip } from '@ant-design/plots'; +import { useTranslation } from 'react-i18next'; +import { useMemo } from 'react'; +import { IRelatedSQLInfo } from '@actiontech/shared/lib/api/sqle/service/common'; +import { useChangeTheme } from '@actiontech/shared/lib/features'; +import useThemeStyleData from '../../../../hooks/useThemeStyleData'; +import ChartWrapper from '../../../../components/ChartCom/ChartWrapper'; +import { styled } from '@mui/material/styles'; +import dayjs from 'dayjs'; +import ChartTooltip from '../../../../components/ChartCom/ChartTooltip'; +import { SharedTheme } from '@actiontech/shared/lib/types/theme.type'; +import { useMemoizedFn } from 'ahooks'; + +const SqlExecutionCostTrendChartStyleWrapper = styled('section')` + .chart-wrapper { + width: 800px; + height: 400px; + } +`; + +const TooltipAnalyzeButtonStyleWrapper = styled('span')` + cursor: pointer; + + &:hover { + text-decoration: underline; + } +`; + +export interface SqlExecutionCostTrendChartProps { + record: IRelatedSQLInfo; + loading?: boolean; +} + +const renderTooltipFormatter: Tooltip['formatter'] = (item) => { + return { + name: item?.x, + value: item?.y + }; +}; + +type ScatterOnEventType = Required['onEvent']; + +const renderTooltipCustomContent = ( + dataSource: any[], + sharedTheme: SharedTheme, + t: (key: string) => string +) => { + const data = dataSource[0]?.data; + if (!data || data.time === undefined) return null; + return ( + + {data.sql} + + ) + }, + { + label: t( + 'sqlInsights.relatedSqlList.sqlFingerprintDetail.chart.yAxisTitle' + ), + value: data.cost + }, + { + label: ( + + {t( + 'sqlInsights.relatedSqlList.sqlFingerprintDetail.chart.analyzeButtonText' + )} + + ) + } + ]} + sharedTheme={sharedTheme} + /> + ); +}; + +const SqlExecutionCostTrendChart: React.FC = ({ + record, + loading = false +}) => { + const { t } = useTranslation(); + const { currentTheme } = useChangeTheme(); + const { sharedTheme } = useThemeStyleData(); + + const { data } = useMemo(() => { + return { + data: record.execution_cost_trend?.points || [] + }; + }, [record.execution_cost_trend]); + + const config: ScatterConfig = useMemo(() => { + return { + data, + xField: 'time', + yField: 'cost', + width: 800, + height: 400, + colorField: 'sql', + xAxis: { + nice: true, + label: { + formatter: (value) => { + return dayjs(value).format('MM-DD HH:mm:ss'); + } + }, + title: { + text: record.execution_cost_trend?.x_info + } + }, + yAxis: { + title: { + text: record.execution_cost_trend?.y_info + } + }, + appendPadding: 20, + tooltip: { + fields: ['time', 'cost'], + formatter: renderTooltipFormatter, + customContent: (title: string, dataSource: any[]) => + renderTooltipCustomContent(dataSource, sharedTheme, t) + }, + interactions: [ + { + type: 'element-highlight-by-color' + } + ] + }; + }, [data, sharedTheme, t, record.execution_cost_trend]); + + const onEvent = useMemoizedFn((_, event) => { + if (event.type === 'element:click') { + console.log(event.data); + // fixme: 添加跳转到SQL分析页面。但是参数是什么需要跟后端讨论。 + } + }); + + return ( + +
+ + + + + +
+
+ ); +}; + +export default SqlExecutionCostTrendChart; diff --git a/packages/sqle/src/page/SqlInsights/components/RelatedSqlList/SqlFingerprintColumn.tsx b/packages/sqle/src/page/SqlInsights/components/RelatedSqlList/SqlFingerprintColumn.tsx new file mode 100644 index 000000000..c9cf50483 --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/components/RelatedSqlList/SqlFingerprintColumn.tsx @@ -0,0 +1,48 @@ +import { IRelatedSQLInfo } from '@actiontech/shared/lib/api/sqle/service/common'; +import { Popover } from 'antd'; +import { useTranslation } from 'react-i18next'; +import SqlExecutionCostTrendChart from './SqlExecutionCostTrendChart'; +import HighlightCode from '@actiontech/shared/lib/utils/HighlightCode'; + +interface SqlFingerprintColumnProps { + sqlFingerprint: string; + record: IRelatedSQLInfo; +} + +const SqlFingerprintColumn: React.FC = ({ + sqlFingerprint, + record +}) => { + const { t } = useTranslation(); + + const hasExecutionCostTrend = !!record.execution_cost_trend?.points?.length; + + return ( + + ) : ( + t('sqlInsights.relatedSqlList.noExecutionCostTrend') + ) + } + title={t('sqlInsights.relatedSqlList.sqlFingerprintDetail.title')} + placement="topLeft" + > +
+
+ ); +}; + +export default SqlFingerprintColumn; diff --git a/packages/sqle/src/page/SqlInsights/components/RelatedSqlList/actions.tsx b/packages/sqle/src/page/SqlInsights/components/RelatedSqlList/actions.tsx new file mode 100644 index 000000000..b708e9de4 --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/components/RelatedSqlList/actions.tsx @@ -0,0 +1,28 @@ +import { ActiontechTableActionMeta } from '@actiontech/shared/lib/components/ActiontechTable'; +import { IRelatedSQLInfo } from '@actiontech/shared/lib/api/sqle/service/common'; +import { t } from '../../../../locale'; + +export const relatedSqlTableActions: ( + onAnalyzeSql: (record: IRelatedSQLInfo) => void, + onViewRelatedTransactions: (record: IRelatedSQLInfo) => void +) => ActiontechTableActionMeta[] = ( + onAnalyzeSql, + onViewRelatedTransactions +) => { + return [ + { + key: 'analyze-sql', + text: t('sqlInsights.relatedSqlList.actions.analyzeSql'), + buttonProps: (record) => ({ + onClick: () => onAnalyzeSql(record ?? {}) + }) + }, + { + key: 'view-related-transactions', + text: t('sqlInsights.relatedSqlList.actions.viewRelatedTransactions'), + buttonProps: (record) => ({ + onClick: () => onViewRelatedTransactions(record ?? {}) + }) + } + ]; +}; diff --git a/packages/sqle/src/page/SqlInsights/components/RelatedSqlList/column.tsx b/packages/sqle/src/page/SqlInsights/components/RelatedSqlList/column.tsx new file mode 100644 index 000000000..7218156f5 --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/components/RelatedSqlList/column.tsx @@ -0,0 +1,76 @@ +import { ActiontechTableColumn } from '@actiontech/shared/lib/components/ActiontechTable'; +import { IRelatedSQLInfo } from '@actiontech/shared/lib/api/sqle/service/common'; +import { IGetSqlManageSqlPerformanceInsightsRelatedSQLParams } from '@actiontech/shared/lib/api/sqle/service/SqlManage/index.d'; +import { formatTime } from '@actiontech/shared/lib/utils/Common'; +import { RelatedSQLInfoSourceEnum } from '@actiontech/shared/lib/api/sqle/service/common.enum'; +import { t } from '../../../../locale'; +import { Popover, Tooltip } from 'antd'; +import SqlFingerprintColumn from './SqlFingerprintColumn'; + +export type RelatedSqlListFilterParams = Pick< + IGetSqlManageSqlPerformanceInsightsRelatedSQLParams, + 'filter_source' +>; + +export const RelatedSqlListColumn = (): ActiontechTableColumn< + IRelatedSQLInfo, + RelatedSqlListFilterParams +> => { + return [ + { + dataIndex: 'sql_fingerprint', + title: () => t('sqlInsights.relatedSqlList.column.sqlFingerprint'), + render: (value, record) => { + return value ? ( + + ) : ( + '-' + ); + } + }, + { + dataIndex: 'source', + title: () => t('sqlInsights.relatedSqlList.column.source'), + render: (value?: RelatedSQLInfoSourceEnum) => { + if (value === RelatedSQLInfoSourceEnum.order) { + return t('sqlInsights.relatedSqlList.source.order'); + } else if (value === RelatedSQLInfoSourceEnum.sql_manage) { + return t('sqlInsights.relatedSqlList.source.sqlManage'); + } else { + return '-'; + } + }, + filterKey: 'filter_source', + filterCustomType: 'select' + }, + { + dataIndex: 'execute_start_time', + title: () => t('sqlInsights.relatedSqlList.column.executeStartTime'), + render: (value) => { + return formatTime(value, '-'); + }, + sorter: true + }, + { + dataIndex: 'execute_end_time', + title: () => t('sqlInsights.relatedSqlList.column.executeEndTime'), + render: (value) => { + return formatTime(value, '-'); + } + }, + { + dataIndex: 'execute_time', + title: () => t('sqlInsights.relatedSqlList.column.executeTime'), + render: (value) => { + return value ? `${value} ms` : '-'; + } + }, + { + dataIndex: 'lock_wait_time', + title: () => t('sqlInsights.relatedSqlList.column.lockWaitTime'), + render: (value) => { + return value ? `${value} ms` : '-'; + } + } + ]; +}; diff --git a/packages/sqle/src/page/SqlInsights/components/RelatedSqlList/index.tsx b/packages/sqle/src/page/SqlInsights/components/RelatedSqlList/index.tsx new file mode 100644 index 000000000..7019dc058 --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/components/RelatedSqlList/index.tsx @@ -0,0 +1,203 @@ +import { useTranslation } from 'react-i18next'; +import { RelatedSqlListStyleWrapper } from './style'; +import { + ActiontechTable, + useTableRequestError, + useTableRequestParams, + TableToolbar, + TableFilterContainer, + useTableFilterContainer, + FilterCustomProps +} from '@actiontech/shared/lib/components/ActiontechTable'; +import { useMemoizedFn, useRequest } from 'ahooks'; +import { useCurrentProject } from '@actiontech/shared/lib/features'; +import sqlManage from '@actiontech/shared/lib/api/sqle/service/SqlManage'; +import { RelatedSqlListColumn, RelatedSqlListFilterParams } from './column'; +import { IRelatedSQLInfo } from '@actiontech/shared/lib/api/sqle/service/common'; +import { EmptyRowStyleWrapper } from '@actiontech/shared/lib/styleWrapper/element'; +import { formatTime } from '@actiontech/shared'; +import { Space, message } from 'antd'; +import { useMemo } from 'react'; +import { GetSqlManageSqlPerformanceInsightsRelatedSQLFilterSourceEnum } from '@actiontech/shared/lib/api/sqle/service/SqlManage/index.enum'; +import { IGetSqlManageSqlPerformanceInsightsRelatedSQLParams } from '@actiontech/shared/lib/api/sqle/service/SqlManage/index.d'; +import { useSelector, useDispatch } from 'react-redux'; +import { IReduxState } from '../../../../store'; +import { relatedSqlTableActions } from './actions'; +import { ModalName } from '../../../../data/ModalName'; +import { + updateSqlInsightsModalStatus, + updateRelateSqlSelectedRecord +} from '../../../../store/sqlInsights'; + +export interface RelatedSqlListProps { + instanceId?: string; +} + +const RelatedSqlList: React.FC = ({ instanceId }) => { + const { t } = useTranslation(); + const { projectName } = useCurrentProject(); + const dispatch = useDispatch(); + const dateRange = useSelector( + (state: IReduxState) => state.sqlInsights.relateSqlList.selectedDateRange + ); + const { + tableChange, + pagination, + tableFilterInfo, + updateTableFilterInfo, + sortInfo, + createSortParams + } = useTableRequestParams(); + const { requestErrorMessage, handleTableRequestError } = + useTableRequestError(); + + const onAnalyzeSql = useMemoizedFn((record: IRelatedSQLInfo) => { + message.info('SQL分析功能将在此实现'); + // TODO: 跳转至SQL分析页面,但是现有的设计有两个问题。需要跟具体实现的后端/产品讨论。 + // 1、现有的SQL分析页面需要一个 sqlManageId 参数。但是当前table没有这个感念。 + // 2、现有的SQL分析针对的是具体的SQL,而当前的表针对的是sql指纹。 + }); + + const onViewRelatedTransactions = useMemoizedFn((record: IRelatedSQLInfo) => { + dispatch( + updateSqlInsightsModalStatus({ + modalName: + ModalName.Sql_Insights_Related_SQL_Item_Relate_Transaction_Drawer, + status: true + }) + ); + dispatch( + updateRelateSqlSelectedRecord({ + record + }) + ); + }); + + const columns = useMemo(() => { + return RelatedSqlListColumn(); + }, []); + + const tableActions = useMemo(() => { + return relatedSqlTableActions(onAnalyzeSql, onViewRelatedTransactions); + }, [onAnalyzeSql, onViewRelatedTransactions]); + + const { filterButtonMeta, filterContainerMeta, updateAllSelectedFilterItem } = + useTableFilterContainer( + columns, + updateTableFilterInfo + ); + + const filterCustomProps = useMemo(() => { + return new Map([ + [ + 'source', + { + options: [ + { + label: t('sqlInsights.relatedSqlList.source.order'), + value: + GetSqlManageSqlPerformanceInsightsRelatedSQLFilterSourceEnum.order + }, + { + label: t('sqlInsights.relatedSqlList.source.sqlManage'), + value: + GetSqlManageSqlPerformanceInsightsRelatedSQLFilterSourceEnum.sql_manage + } + ] + } + ] + ]); + }, [t]); + + const { + data: listData, + loading, + refresh: refreshData + } = useRequest( + () => { + if (!dateRange) { + return Promise.resolve<{ + list?: IRelatedSQLInfo[]; + total: number; + }>({ + list: [], + total: 0 + }); + } + const params: IGetSqlManageSqlPerformanceInsightsRelatedSQLParams = { + project_name: projectName, + instance_name: instanceId ?? '', + start_time: dateRange?.[0].format('YYYY-MM-DD HH:mm:ss') ?? '', + end_time: dateRange?.[1].format('YYYY-MM-DD HH:mm:ss') ?? '', + page_index: pagination.page_index, + page_size: pagination.page_size, + ...tableFilterInfo + }; + + createSortParams(params); + + return handleTableRequestError( + sqlManage.GetSqlManageSqlPerformanceInsightsRelatedSQL(params) + ); + }, + { + refreshDeps: [ + instanceId, + dateRange, + pagination, + tableFilterInfo, + sortInfo + ], + ready: !!instanceId + } + ); + + return ( + + + + {t('sqlInsights.relatedSqlList.title')} + {dateRange && dateRange[0] && dateRange[1] ? ( + + ({formatTime(dateRange[0], '-')} ~ {formatTime(dateRange[1], '-')} + ) + + ) : ( + + ({t('sqlInsights.relatedSqlList.dateRangePlaceholder')}) + + )} + + + + + + + ); +}; + +export default RelatedSqlList; diff --git a/packages/sqle/src/page/SqlInsights/components/RelatedSqlList/style.ts b/packages/sqle/src/page/SqlInsights/components/RelatedSqlList/style.ts new file mode 100644 index 000000000..c23b5bf50 --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/components/RelatedSqlList/style.ts @@ -0,0 +1,6 @@ +import { styled } from '@mui/material/styles'; + +export const RelatedSqlListStyleWrapper = styled('div')` + margin-top: 20px; + width: 100%; +`; diff --git a/packages/sqle/src/page/SqlInsights/components/RelatedSqlList/useRelatedSqlRedux.tsx b/packages/sqle/src/page/SqlInsights/components/RelatedSqlList/useRelatedSqlRedux.tsx new file mode 100644 index 000000000..dca5ef28b --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/components/RelatedSqlList/useRelatedSqlRedux.tsx @@ -0,0 +1,22 @@ +import { useDispatch, useSelector } from 'react-redux'; +import { IReduxState } from '../../../../store'; +import { Dayjs } from 'dayjs'; +import { updateRelateSqlListDateRange } from '../../../../store/sqlInsights'; + +const useRelatedSqlRedux = () => { + const dispatch = useDispatch(); + const relateSqlListDateRange = useSelector( + (state: IReduxState) => state.sqlInsights.relateSqlList.selectedDateRange + ); + + const update = (dateRange: [Dayjs, Dayjs] | null) => { + dispatch(updateRelateSqlListDateRange({ dateRange })); + }; + + return { + relateSqlListDateRange, + updateRelateSqlListDateRange: update + }; +}; + +export default useRelatedSqlRedux; diff --git a/packages/sqle/src/page/SqlInsights/components/SlowSqlTrend/index.tsx b/packages/sqle/src/page/SqlInsights/components/SlowSqlTrend/index.tsx new file mode 100644 index 000000000..2e42540e7 --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/components/SlowSqlTrend/index.tsx @@ -0,0 +1,45 @@ +import { useTranslation } from 'react-i18next'; +import { Dayjs } from 'dayjs'; +import { SlowSqlTrendStyleWrapper } from './style'; +import { GetSqlManageSqlPerformanceInsightsMetricNameEnum } from '@actiontech/shared/lib/api/sqle/service/SqlManage/index.enum'; +import SqlInsightsLineChart from '../SqlInsightsLineChart'; +import useSqlInsightsMetric from '../../hooks/useSqlInsightsMetric'; +import EmitterKey from '@actiontech/shared/lib/data/EmitterKey'; +import useRelatedSqlRedux from '../RelatedSqlList/useRelatedSqlRedux'; + +export interface SlowSqlTrendProps { + instanceId?: string; + dateRange?: [Dayjs, Dayjs]; +} + +const SlowSqlTrend: React.FC = ({ + instanceId, + dateRange +}) => { + const { t } = useTranslation(); + const { updateRelateSqlListDateRange } = useRelatedSqlRedux(); + const { loading, chartData } = useSqlInsightsMetric({ + instanceId, + dateRange, + metricName: GetSqlManageSqlPerformanceInsightsMetricNameEnum.slow_sql_trend + }); + + return ( + + { + updateRelateSqlListDateRange(selectedDateRange); + }} + /> + + ); +}; + +export default SlowSqlTrend; diff --git a/packages/sqle/src/page/SqlInsights/components/SlowSqlTrend/style.ts b/packages/sqle/src/page/SqlInsights/components/SlowSqlTrend/style.ts new file mode 100644 index 000000000..9eed41e50 --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/components/SlowSqlTrend/style.ts @@ -0,0 +1,18 @@ +import { styled } from '@mui/material/styles'; +import { SQL_INSIGHTS_CHART_HEIGHT } from '../../index.data'; + +export const SlowSqlTrendStyleWrapper = styled('div')` + margin-top: 24px; + border-radius: 8px; + padding: 24px; + + .chart-title { + font-size: 16px; + font-weight: 500; + margin: 0 0 16px 0; + } + + .slow-sql-trend-chart { + height: ${SQL_INSIGHTS_CHART_HEIGHT}px; + } +`; diff --git a/packages/sqle/src/page/SqlInsights/components/SqlInsightsLineChart/index.tsx b/packages/sqle/src/page/SqlInsights/components/SqlInsightsLineChart/index.tsx new file mode 100644 index 000000000..985865934 --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/components/SqlInsightsLineChart/index.tsx @@ -0,0 +1,261 @@ +import { useMemo, useRef, useEffect, useState, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import dayjs from 'dayjs'; +import ChartWrapper from '../../../../components/ChartCom/ChartWrapper'; +import { LineConfig, Line } from '@ant-design/plots'; +import { ILine } from '@actiontech/shared/lib/api/sqle/service/common'; +import { SqlInsightsLineChartWrapper } from './style'; +import classNames from 'classnames'; +import { eventEmitter } from '@actiontech/shared/lib/utils/EventEmitter'; +import EmitterKey from '@actiontech/shared/lib/data/EmitterKey'; +import { message } from 'antd'; +import { useMemoizedFn } from 'ahooks'; + +export interface SqlInsightsLineChartProps { + loading: boolean; + chartData: ILine[]; + title: string; + className?: string; + /** + * @title 联动 + * @description 图表联动配置 + */ + maskInteractionEventName?: EmitterKey; + onSelectDate?: (dateRange: [dayjs.Dayjs, dayjs.Dayjs] | null) => void; +} + +type LineChart = Parameters['onReady']>[0]; +type LineOnReady = Required['onReady']; +type LineOnEvent = Required['onEvent']; + +const SqlInsightsLineChart: React.FC = ({ + loading, + chartData, + title, + className, + maskInteractionEventName, + onSelectDate +}) => { + const { t } = useTranslation(); + + const [messageApi, messageContextHolder] = message.useMessage(); + + // 将API返回的数据转换为图表需要的格式 + const transformedData = useMemo(() => { + const result: Array<{ + date: string; + value: number; + type: string; + }> = []; + + chartData.forEach((line) => { + line.points?.forEach((point) => { + if (point.x && point.y !== undefined) { + result.push({ + date: point.x, + value: point.y, + type: line.line_name || '' + }); + } + }); + }); + + return result; + }, [chartData]); + + const chartRef = useRef(null); + const isMaskCreatedChartRef = useRef(false); + + const config: LineConfig = useMemo(() => { + return { + data: transformedData, + xField: 'date', + yField: 'value', + seriesField: 'type', + xAxis: { + type: 'time', + label: { + formatter: (value: string) => { + return dayjs(value).format('MM-DD HH:mm:ss'); + } + }, + title: { + text: t('sqlInsights.chart.xAxisTitle') + } + }, + yAxis: { + title: { + text: t('sqlInsights.chart.yAxisTitle') + } + }, + legend: { + position: 'top' + }, + smooth: true, + animation: { + appear: { + animation: 'path-in', + duration: 1000 + } + }, + interactions: [ + { + type: 'brush', + enable: false + }, + { + // 为了能保证 mask 长时间停留在图表上,需要重新定义 brush:end 的 actions。但是给 brush:end 添加 actions 后,会导致图表发生 call maximum 的错误。怀疑为 antd-design/plots 的问题。 + // 尝试:即使移除所有功能,只传递 cfg: { end: [{trigger: 'plot:mouseup',action: ['rect-mask:end', 'brush:end']}]} 进去也会触发这个问题。 + type: 'brush-x', + enable: false + } + ] + }; + }, [transformedData, t]); + + const [maskXPosition, setMaskXPosition] = useState(0); + const [maskYPosition, setMaskYPosition] = useState(0); + const [maskWidth, setMaskWidth] = useState(0); + const [maskHeight, setMaskHeight] = useState(0); + + const handleChartReady = useCallback((chart) => { + chartRef.current = chart; + const { y, height } = chart.chart.coordinateBBox; + setMaskYPosition(y); + setMaskHeight(height); + }, []); + + const maskStartXPosition = useRef(0); + const maskEndXPosition = useRef(0); + + useEffect(() => { + // 当数据或加载状态变化时,清除 mask + setMaskWidth(0); + maskStartXPosition.current = 0; + maskEndXPosition.current = 0; + isMaskCreatedChartRef.current = false; + }, [transformedData, loading]); + + // fixme: 当前 antd-design/plots 的版本为1.x,底层g2的版本为4.x,这个版本的g2无法通过 emit 的方式提交 brush-x 事件。只能绕过处理。等升级后,需要使用原生 brushXHighlight 等事件重写这个功能。 + const handleChartEvent = useMemoizedFn((chart, event) => { + if (!maskInteractionEventName) { + return; + } + const type = event.type; + if (type === 'plot:mousedown' && !isMaskCreatedChartRef.current) { + isMaskCreatedChartRef.current = true; + maskStartXPosition.current = event.x; + maskEndXPosition.current = event.x; + eventEmitter.emit(maskInteractionEventName, { + maskStartXPosition: event.x, + maskEndXPosition: event.x + }); + } + if (type === 'plot:mouseup') { + isMaskCreatedChartRef.current = false; + let minDate = dayjs().add(10, 'day'); + let maxDate = dayjs().add(-10000, 'day'); + let validPoints = 0; + for (let i = 0; i < transformedData.length; i++) { + const current = transformedData[i]; + const point = chart.chart.getXY(current); + const { x } = point; + let left = maskStartXPosition.current; + let right = maskEndXPosition.current; + if (left > right) { + [left, right] = [right, left]; + } + if (x >= left && x <= right) { + validPoints++; + const currentDate = dayjs(current.date); + if (currentDate.isBefore(minDate)) { + minDate = currentDate; + } + if (currentDate.isAfter(maxDate)) { + maxDate = currentDate; + } + } + } + if (validPoints === 0) { + onSelectDate?.(null); + messageApi.info(t('sqlInsights.chart.noValidData')); + } else { + onSelectDate?.([minDate, maxDate]); + } + maskStartXPosition.current = 0; + maskEndXPosition.current = 0; + } + if (type === 'plot:mousemove' && isMaskCreatedChartRef.current) { + const { x } = event; + maskEndXPosition.current = x; + eventEmitter.emit(maskInteractionEventName, { + maskStartXPosition: maskStartXPosition.current, + maskEndXPosition: x + }); + } + }); + + useEffect(() => { + if (maskInteractionEventName) { + const { unsubscribe } = eventEmitter.subscribe( + maskInteractionEventName, + (data: { maskStartXPosition: number; maskEndXPosition: number }) => { + let left = data.maskStartXPosition; + let right = data.maskEndXPosition; + if (left > right) { + [left, right] = [right, left]; + } + setMaskXPosition(left); + setMaskWidth(right - left); + } + ); + return () => { + unsubscribe(); + }; + } + }, [maskInteractionEventName, maskXPosition]); + + return ( + <> + {messageContextHolder} + +

{title}

+
+ + + +
0 ? 'block' : 'none', + position: 'absolute', + top: maskYPosition, + left: maskXPosition, + width: maskWidth, + height: maskHeight, + backgroundColor: 'rgba(197, 212, 235, 0.6)', + pointerEvents: 'none' + }} + onMouseUp={() => { + isMaskCreatedChartRef.current = false; + }} + >
+
+
+ + ); +}; + +export default SqlInsightsLineChart; diff --git a/packages/sqle/src/page/SqlInsights/components/SqlInsightsLineChart/style.ts b/packages/sqle/src/page/SqlInsights/components/SqlInsightsLineChart/style.ts new file mode 100644 index 000000000..0f5517492 --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/components/SqlInsightsLineChart/style.ts @@ -0,0 +1,14 @@ +import { styled } from '@mui/material/styles'; + +export const SqlInsightsLineChartWrapper = styled('div')` + .chart-title { + font-size: 16px; + font-weight: 500; + margin-bottom: 16px; + } + + .chart-container { + height: 100%; + cursor: crosshair !important; + } +`; diff --git a/packages/sqle/src/page/SqlInsights/components/TopSqlTrend/index.tsx b/packages/sqle/src/page/SqlInsights/components/TopSqlTrend/index.tsx new file mode 100644 index 000000000..88837c49f --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/components/TopSqlTrend/index.tsx @@ -0,0 +1,42 @@ +import { useTranslation } from 'react-i18next'; +import { Dayjs } from 'dayjs'; +import { TopSqlTrendStyleWrapper } from './style'; +import { GetSqlManageSqlPerformanceInsightsMetricNameEnum } from '@actiontech/shared/lib/api/sqle/service/SqlManage/index.enum'; +import SqlInsightsLineChart from '../SqlInsightsLineChart'; +import useSqlInsightsMetric from '../../hooks/useSqlInsightsMetric'; +import EmitterKey from '@actiontech/shared/lib/data/EmitterKey'; +import useRelatedSqlRedux from '../RelatedSqlList/useRelatedSqlRedux'; + +export interface TopSqlTrendProps { + instanceId?: string; + dateRange?: [Dayjs, Dayjs]; +} + +const TopSqlTrend: React.FC = ({ instanceId, dateRange }) => { + const { t } = useTranslation(); + const { updateRelateSqlListDateRange } = useRelatedSqlRedux(); + const { loading, chartData } = useSqlInsightsMetric({ + instanceId, + dateRange, + metricName: GetSqlManageSqlPerformanceInsightsMetricNameEnum.top_sql_trend + }); + + return ( + + { + updateRelateSqlListDateRange(selectedDateRange); + }} + /> + + ); +}; + +export default TopSqlTrend; diff --git a/packages/sqle/src/page/SqlInsights/components/TopSqlTrend/style.ts b/packages/sqle/src/page/SqlInsights/components/TopSqlTrend/style.ts new file mode 100644 index 000000000..c7f9f9f3b --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/components/TopSqlTrend/style.ts @@ -0,0 +1,18 @@ +import { styled } from '@mui/material/styles'; +import { SQL_INSIGHTS_CHART_HEIGHT } from '../../index.data'; + +export const TopSqlTrendStyleWrapper = styled('div')` + margin-top: 24px; + border-radius: 8px; + padding: 24px; + + .chart-title { + font-size: 16px; + font-weight: 500; + margin: 0 0 16px 0; + } + + .top-sql-trend-chart { + height: ${SQL_INSIGHTS_CHART_HEIGHT}px; + } +`; diff --git a/packages/sqle/src/page/SqlInsights/hooks/useSqlInsightsMetric.ts b/packages/sqle/src/page/SqlInsights/hooks/useSqlInsightsMetric.ts new file mode 100644 index 000000000..a84f54792 --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/hooks/useSqlInsightsMetric.ts @@ -0,0 +1,63 @@ +import { useState, useEffect } from 'react'; +import { Dayjs } from 'dayjs'; +import { useCurrentProject } from '@actiontech/shared/lib/features'; +import SqlManage from '@actiontech/shared/lib/api/sqle/service/SqlManage'; +import { GetSqlManageSqlPerformanceInsightsMetricNameEnum } from '@actiontech/shared/lib/api/sqle/service/SqlManage/index.enum'; +import { ILine } from '@actiontech/shared/lib/api/sqle/service/common'; + +interface UseSqlInsightsMetricProps { + instanceId?: string; + dateRange?: [Dayjs, Dayjs]; + metricName: GetSqlManageSqlPerformanceInsightsMetricNameEnum; +} + +interface UseSqlInsightsMetricReturn { + loading: boolean; + chartData: ILine[]; +} + +export const useSqlInsightsMetric = ({ + instanceId, + dateRange, + metricName +}: UseSqlInsightsMetricProps): UseSqlInsightsMetricReturn => { + const { projectName } = useCurrentProject(); + const [loading, setLoading] = useState(false); + const [chartData, setChartData] = useState([]); + + useEffect(() => { + if ( + !projectName || + !instanceId || + !dateRange || + !dateRange[0] || + !dateRange[1] + ) { + return; + } + + const fetchData = async () => { + setLoading(true); + try { + const res = await SqlManage.GetSqlManageSqlPerformanceInsights({ + project_name: projectName, + instance_name: instanceId, + metric_name: metricName, + start_time: dateRange[0].format('YYYY-MM-DD HH:mm:ss'), + end_time: dateRange[1].format('YYYY-MM-DD HH:mm:ss') + }); + if (res.data?.data?.lines) { + setChartData(res.data.data.lines); + } + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [projectName, instanceId, dateRange, metricName]); + + return { loading, chartData }; +}; + +export default useSqlInsightsMetric; diff --git a/packages/sqle/src/page/SqlInsights/index.data.ts b/packages/sqle/src/page/SqlInsights/index.data.ts new file mode 100644 index 000000000..73203c344 --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/index.data.ts @@ -0,0 +1,25 @@ +import { t } from '../../locale'; + +export enum DateRangeEnum { + '24H' = '24H', + '7D' = '7D', + '30D' = '30D' +} + +export const DateRangeOptions = [ + { + label: t('sqlQuery.executePlan.twentyFourHours'), + value: DateRangeEnum['24H'] + }, + { + label: t('sqlQuery.executePlan.sevenDays'), + value: DateRangeEnum['7D'] + }, + { + label: t('sqlQuery.executePlan.thirtyDays'), + value: DateRangeEnum['30D'] + } +]; + +// 统一的图表高度配置 +export const SQL_INSIGHTS_CHART_HEIGHT = 250; diff --git a/packages/sqle/src/page/SqlInsights/index.tsx b/packages/sqle/src/page/SqlInsights/index.tsx new file mode 100644 index 000000000..b3e9492c1 --- /dev/null +++ b/packages/sqle/src/page/SqlInsights/index.tsx @@ -0,0 +1,136 @@ +import { BasicRangePickerProps, PageHeader } from '@actiontech/shared'; +import { useTranslation } from 'react-i18next'; +import { Space } from 'antd'; +import { TableRefreshButton } from '@actiontech/shared/lib/components/ActiontechTable'; +import { useCallback, useEffect, useState } from 'react'; +import { useCurrentProject } from '@actiontech/shared/lib/features'; +import useInstance from '../../hooks/useInstance'; +import { + BasicSelect, + BasicRangePicker, + BasicSegmented +} from '@actiontech/shared'; +import { EmptyRowStyleWrapper } from '@actiontech/shared/lib/styleWrapper/element'; +import dayjs, { Dayjs } from 'dayjs'; +import DataSourcePerformanceTrend from './components/DataSourcePerformanceTrend'; +import { DateRangeEnum, DateRangeOptions } from './index.data'; +import { SegmentedValue } from 'antd/es/segmented'; +import SlowSqlTrend from './components/SlowSqlTrend'; +import TopSqlTrend from './components/TopSqlTrend'; +import ActiveSessionTrend from './components/ActiveSessionTrend'; +import RelatedSqlList from './components/RelatedSqlList'; +import DrawerManager from './components/DrawerManager'; +const SqlInsights: React.FC = () => { + const { t } = useTranslation(); + const { projectName } = useCurrentProject(); + const [selectedInstance, setSelectedInstance] = useState(); + const [dateRange, setDateRange] = useState<[Dayjs, Dayjs]>([ + dayjs().subtract(7, 'day'), + dayjs() + ]); + const [timePeriod, setTimePeriod] = useState( + DateRangeEnum['7D'] + ); + + const onRefresh = () => { + console.log('onRefresh'); + }; + + const { + updateInstanceList, + loading: getInstanceListLoading, + instanceIDOptions + } = useInstance(); + + useEffect(() => { + if (projectName) { + updateInstanceList({ project_name: projectName }); + } + }, [projectName, updateInstanceList]); + + const handleInstanceChange = useCallback((value: string) => { + setSelectedInstance(value); + }, []); + + const handleDateRangeChange = useCallback< + Required['onChange'] + >((dates) => { + if (dates && dates[0] && dates[1]) { + setDateRange([dates[0], dates[1]]); + } + }, []); + + const onSegmentedChange = useCallback((value: SegmentedValue) => { + setTimePeriod(value as DateRangeEnum); + let startTime = dayjs(); + const endTime = dayjs(); + + if (value === DateRangeEnum['24H']) { + startTime = endTime.subtract(24, 'hour'); + } else if (value === DateRangeEnum['7D']) { + startTime = endTime.subtract(7, 'day'); + } else if (value === DateRangeEnum['30D']) { + startTime = endTime.subtract(30, 'day'); + } + + setDateRange([startTime, endTime]); + }, []); + + return ( + <> + + {t('sqlInsights.title')} + + + } + extra={ + + + + } + /> + + + {t('sqlInsights.dateRange')}: + + current && current > dayjs().endOf('day') + } + /> + + + + + + + + + + + + ); +}; + +export default SqlInsights; diff --git a/packages/sqle/src/router/config.tsx b/packages/sqle/src/router/config.tsx index 035ce20e0..37c282d45 100644 --- a/packages/sqle/src/router/config.tsx +++ b/packages/sqle/src/router/config.tsx @@ -225,6 +225,7 @@ const PipelineConfigurationUpdate = React.lazy( const VersionManagement = React.lazy(() => import('../page/VersionManagement')); const GlobalDashboard = React.lazy(() => import('../page/GlobalDashboard')); +const SqlInsights = React.lazy(() => import('../page/SqlInsights')); export const projectDetailRouterConfig: RouterConfigItem[] = [ { @@ -315,6 +316,11 @@ export const projectDetailRouterConfig: RouterConfigItem[] = [ } ] }, + { + path: ROUTE_PATHS.SQLE.SQL_INSIGHTS.index.path, + key: 'sqlInsights', + element: + }, { path: ROUTE_PATHS.SQLE.SQL_MANAGEMENT.index.path, key: 'sqlManagement', diff --git a/packages/sqle/src/store/index.ts b/packages/sqle/src/store/index.ts index 99e704b27..80463207c 100644 --- a/packages/sqle/src/store/index.ts +++ b/packages/sqle/src/store/index.ts @@ -10,6 +10,7 @@ import sqlExecWorkflow from './sqlExecWorkflow'; import sqlManagementException from './sqlManagementException'; import pipeline from './pipeline'; import versionManagement from './versionManagement'; +import sqlInsights from './sqlInsights'; export const SQLEStoreData = { whitelist, @@ -22,7 +23,8 @@ export const SQLEStoreData = { sqlExecWorkflow, sqlManagementException, pipeline, - versionManagement + versionManagement, + sqlInsights }; const store = configureStore({ diff --git a/packages/sqle/src/store/sqlInsights/index.ts b/packages/sqle/src/store/sqlInsights/index.ts new file mode 100644 index 000000000..a5f17eb0f --- /dev/null +++ b/packages/sqle/src/store/sqlInsights/index.ts @@ -0,0 +1,51 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { commonModalReducer } from '../common'; +import { ModalStatus } from '@actiontech/shared/lib/types/common.type'; +import { Dayjs } from 'dayjs'; +import { IRelatedSQLInfo } from '@actiontech/shared/lib/api/sqle/service/common'; +type SqlInsightsReduxState = { + modalStatus: ModalStatus; + relateSqlList: { + selectedDateRange: [Dayjs, Dayjs] | null; + selectedRecord: IRelatedSQLInfo | null; + }; +}; + +const initialState: SqlInsightsReduxState = { + modalStatus: {}, + relateSqlList: { + selectedDateRange: null, + selectedRecord: null + } +}; + +const sqlInsights = createSlice({ + name: 'sqlInsights', + initialState, + reducers: { + updateRelateSqlListDateRange( + state, + { + payload: { dateRange } + }: PayloadAction<{ dateRange: [Dayjs, Dayjs] | null }> + ) { + state.relateSqlList.selectedDateRange = dateRange; + }, + updateRelateSqlSelectedRecord( + state, + { payload: { record } }: PayloadAction<{ record: IRelatedSQLInfo | null }> + ) { + state.relateSqlList.selectedRecord = record; + }, + ...commonModalReducer() + } +}); + +export const { + updateRelateSqlListDateRange, + updateRelateSqlSelectedRecord, + initModalStatus: initSqlInsightsModalStatus, + updateModalStatus: updateSqlInsightsModalStatus +} = sqlInsights.actions; + +export default sqlInsights.reducer;