From 59d8a8edc33df1152be047a9d5763cffd340f99d Mon Sep 17 00:00:00 2001 From: lizhensheng Date: Wed, 20 May 2026 14:18:35 +0800 Subject: [PATCH 1/6] feat: Remove redundant code --- packages/sqle/src/page/OperationRecord/List/index.test.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/sqle/src/page/OperationRecord/List/index.test.tsx b/packages/sqle/src/page/OperationRecord/List/index.test.tsx index 564efafde..9612cbdf9 100644 --- a/packages/sqle/src/page/OperationRecord/List/index.test.tsx +++ b/packages/sqle/src/page/OperationRecord/List/index.test.tsx @@ -29,8 +29,6 @@ describe('sqle/OperationRecord/List', () => { it('render operation record table when request return data', async () => { const operationRecordListSpy = operationRecord.getOperationRecordList(); - // const actionSpy = operationRecord.getOperationActionList(); - // const typeNameSpy = operationRecord.getOperationTypeNameList(); const { baseElement } = superRender(); await act(async () => jest.advanceTimersByTime(3000)); expect(baseElement).toMatchSnapshot(); From ae48181ff2014d203937bb6692774d6eb2c3b86d Mon Sep 17 00:00:00 2001 From: lizhensheng Date: Wed, 20 May 2026 14:19:41 +0800 Subject: [PATCH 2/6] chore: build api --- .../shared/lib/api/base/service/common.d.ts | 2 + .../sqle/service/GlobalDashboard/index.d.ts | 15 ++- .../shared/lib/api/sqle/service/common.d.ts | 94 +------------------ .../api/sqle/service/sql_analysis/index.d.ts | 10 +- .../api/sqle/service/sql_analysis/index.ts | 16 +--- 5 files changed, 23 insertions(+), 114 deletions(-) diff --git a/packages/shared/lib/api/base/service/common.d.ts b/packages/shared/lib/api/base/service/common.d.ts index a9312bbad..5d9d120e8 100644 --- a/packages/shared/lib/api/base/service/common.d.ts +++ b/packages/shared/lib/api/base/service/common.d.ts @@ -1308,6 +1308,8 @@ export interface IGlobalDataExportWorkflow { status?: GlobalDataExportWorkflowStatusEnum; + updated_at?: string; + workflow_name?: string; workflow_uid?: string; diff --git a/packages/shared/lib/api/sqle/service/GlobalDashboard/index.d.ts b/packages/shared/lib/api/sqle/service/GlobalDashboard/index.d.ts index 4b5bf9c62..ba573225b 100644 --- a/packages/shared/lib/api/sqle/service/GlobalDashboard/index.d.ts +++ b/packages/shared/lib/api/sqle/service/GlobalDashboard/index.d.ts @@ -2,7 +2,8 @@ import { GetGlobalAccountListV2FilterCardEnum, GetGlobalSqlManageTaskListV2FilterCardEnum, GetGlobalWorkflowListV2FilterCardEnum, - GetGlobalWorkflowListV2WorkflowTypeEnum + GetGlobalWorkflowListV2WorkflowTypeEnum, + GetGlobalWorkflowListV2FilterStatusEnum } from './index.enum'; import { @@ -82,6 +83,18 @@ export interface IGetGlobalWorkflowListV2Params { filter_card?: GetGlobalWorkflowListV2FilterCardEnum; workflow_type?: GetGlobalWorkflowListV2WorkflowTypeEnum; + + filter_status?: GetGlobalWorkflowListV2FilterStatusEnum; + + filter_update_time_from?: string; + + filter_update_time_to?: string; + + filter_create_user_id?: string; + + filter_create_time_from?: string; + + filter_create_time_to?: string; } export interface IGetGlobalWorkflowListV2Return diff --git a/packages/shared/lib/api/sqle/service/common.d.ts b/packages/shared/lib/api/sqle/service/common.d.ts index 096adde8d..57804bfc7 100644 --- a/packages/shared/lib/api/sqle/service/common.d.ts +++ b/packages/shared/lib/api/sqle/service/common.d.ts @@ -241,10 +241,12 @@ export interface IGlobalWorkflowListData { } export interface IGlobalWorkflowListItem { - current_step_assignee_user_name_list?: string[]; + create_user_name?: string; created_at?: string; + current_step_assignee_user_name_list?: string[]; + instance_id?: string; instance_name?: string; @@ -257,6 +259,8 @@ export interface IGlobalWorkflowListItem { status?: GlobalWorkflowListItemStatusEnum; + updated_at?: string; + workflow_desc?: string; workflow_id?: string; @@ -3404,94 +3408,6 @@ export interface ISQLExplain { sql?: string; } -export interface ISQLLineageAnalyzeReqV1 { - default_schema?: string; - - instance_type?: string; - - result_columns?: string[]; - - sql?: string; -} - -export interface ISQLLineageAnalyzeResDataV1 { - result?: ISQLLineageAnalyzeResultV1; -} - -export interface ISQLLineageAnalyzeResV1 { - code?: number; - - data?: ISQLLineageAnalyzeResDataV1; - - message?: string; -} - -export interface ISQLLineageAnalyzeResultV1 { - edges?: ISQLLineageEdgeV1[]; - - nodes?: ISQLLineageNodeV1[]; - - original_sql?: string; - - result_columns?: ISQLLineageResultColumnV1[]; - - source_columns?: ISQLLineageColumnRefV1[]; - - tables?: ISQLLineageTableRefV1[]; - - title?: string; - - warnings?: string[]; -} - -export interface ISQLLineageColumnRefV1 { - column?: string; - - schema?: string; - - table?: string; -} - -export interface ISQLLineageEdgeV1 { - from_id?: string; - - to_id?: string; - - type?: string; -} - -export interface ISQLLineageNodeV1 { - column?: string; - - expr?: string; - - id?: string; - - name?: string; - - schema?: string; - - table?: string; - - type?: string; -} - -export interface ISQLLineageResultColumnV1 { - expression?: string; - - name?: string; - - sources?: ISQLLineageColumnRefV1[]; -} - -export interface ISQLLineageTableRefV1 { - alias?: string; - - schema?: string; - - table?: string; -} - export interface ISQLQueryConfigResV1 { allow_query_when_less_than_audit_level?: SQLQueryConfigResV1AllowQueryWhenLessThanAuditLevelEnum; diff --git a/packages/shared/lib/api/sqle/service/sql_analysis/index.d.ts b/packages/shared/lib/api/sqle/service/sql_analysis/index.d.ts index c91c8c515..38cead858 100644 --- a/packages/shared/lib/api/sqle/service/sql_analysis/index.d.ts +++ b/packages/shared/lib/api/sqle/service/sql_analysis/index.d.ts @@ -1,8 +1,4 @@ -import { - IDirectGetSQLAnalysisResV1, - ISQLLineageAnalyzeReqV1, - ISQLLineageAnalyzeResV1 -} from '../common.d'; +import { IDirectGetSQLAnalysisResV1 } from '../common.d'; export interface IDirectGetSQLAnalysisV1Params { project_name: string; @@ -16,7 +12,3 @@ export interface IDirectGetSQLAnalysisV1Params { export interface IDirectGetSQLAnalysisV1Return extends IDirectGetSQLAnalysisResV1 {} - -export interface ISqlLineageAnalyzeV1Params extends ISQLLineageAnalyzeReqV1 {} - -export interface ISqlLineageAnalyzeV1Return extends ISQLLineageAnalyzeResV1 {} diff --git a/packages/shared/lib/api/sqle/service/sql_analysis/index.ts b/packages/shared/lib/api/sqle/service/sql_analysis/index.ts index e262a2c07..c5d99f6a1 100644 --- a/packages/shared/lib/api/sqle/service/sql_analysis/index.ts +++ b/packages/shared/lib/api/sqle/service/sql_analysis/index.ts @@ -8,9 +8,7 @@ import { AxiosRequestConfig } from 'axios'; import { IDirectGetSQLAnalysisV1Params, - IDirectGetSQLAnalysisV1Return, - ISqlLineageAnalyzeV1Params, - ISqlLineageAnalyzeV1Return + IDirectGetSQLAnalysisV1Return } from './index.d'; class SqlAnalysisService extends ServiceBase { @@ -25,18 +23,6 @@ class SqlAnalysisService extends ServiceBase { options ); } - - public sqlLineageAnalyzeV1( - params: ISqlLineageAnalyzeV1Params, - options?: AxiosRequestConfig - ) { - const paramsData = this.cloneDeep(params); - return this.post( - '/v1/sql_lineage_analysis', - paramsData, - options - ); - } } export default new SqlAnalysisService(); From 6eeaef36700e0f915cfaf60fc39914c9d602afd3 Mon Sep 17 00:00:00 2001 From: lizhensheng Date: Wed, 20 May 2026 14:21:18 +0800 Subject: [PATCH 3/6] feat(home): added workflow statistics info --- packages/base/src/locale/en-US/dmsHome.ts | 17 +- packages/base/src/locale/zh-CN/dmsHome.ts | 17 +- .../__snapshots__/index.test.tsx.snap | 260 ++++++++++++++++++ .../__tests__/index.test.tsx | 155 +++++++++++ .../src/page/Home/WorkflowStatCards/index.tsx | 125 +++++++++ .../src/page/Home/WorkflowStatCards/style.ts | 133 +++++++++ .../__snapshots__/index.ce.test.tsx.snap | 32 ++- .../__snapshots__/index.test.tsx.snap | 32 ++- .../src/page/Home/__tests__/index.ce.test.tsx | 18 +- .../src/page/Home/__tests__/index.test.tsx | 18 +- packages/base/src/page/Home/index.tsx | 2 + 11 files changed, 801 insertions(+), 8 deletions(-) create mode 100644 packages/base/src/page/Home/WorkflowStatCards/__tests__/__snapshots__/index.test.tsx.snap create mode 100644 packages/base/src/page/Home/WorkflowStatCards/__tests__/index.test.tsx create mode 100644 packages/base/src/page/Home/WorkflowStatCards/index.tsx create mode 100644 packages/base/src/page/Home/WorkflowStatCards/style.ts diff --git a/packages/base/src/locale/en-US/dmsHome.ts b/packages/base/src/locale/en-US/dmsHome.ts index 11cae0b80..440370519 100644 --- a/packages/base/src/locale/en-US/dmsHome.ts +++ b/packages/base/src/locale/en-US/dmsHome.ts @@ -1,6 +1,21 @@ // eslint-disable-next-line import/no-anonymous-default-export export default { - pageTitle: 'User guide', + pageTitle: 'Home', + workflowCards: { + sectionTitle: 'Workflow Overview', + sectionDescription: 'Track workflow progress related to you', + viewAll: 'View workflow management', + workflow: { + pendingMine: 'Pending for me', + pendingMineSubtitle: 'Action required', + initiated: 'Initiated by me', + initiatedSubtitle: 'Track progress', + archived: 'Archived', + archivedSubtitle: 'Completed tasks', + viewAll: 'View all', + viewAllSubtitle: 'Currently visible' + } + }, aiBanner: { insightTitle: 'AI Governance Efficiency Insights', insightDescription: diff --git a/packages/base/src/locale/zh-CN/dmsHome.ts b/packages/base/src/locale/zh-CN/dmsHome.ts index ca0877742..c7c9bf99a 100644 --- a/packages/base/src/locale/zh-CN/dmsHome.ts +++ b/packages/base/src/locale/zh-CN/dmsHome.ts @@ -1,6 +1,21 @@ // eslint-disable-next-line import/no-anonymous-default-export export default { - pageTitle: '用户引导', + pageTitle: '首页', + workflowCards: { + sectionTitle: '工单概览', + sectionDescription: '跟踪与您相关的工单进度', + viewAll: '查看工单管理', + workflow: { + pendingMine: '待我处理', + pendingMineSubtitle: '需立即行动', + initiated: '我发起的', + initiatedSubtitle: '关注进度', + archived: '已归档', + archivedSubtitle: '已完结任务', + viewAll: '查看全部', + viewAllSubtitle: '当前可见范围' + } + }, aiBanner: { insightTitle: 'AI治理效能洞察', insightDescription: '基于大模型实时监控规范与性能, AI驱动全链路质量闭环。', diff --git a/packages/base/src/page/Home/WorkflowStatCards/__tests__/__snapshots__/index.test.tsx.snap b/packages/base/src/page/Home/WorkflowStatCards/__tests__/__snapshots__/index.test.tsx.snap new file mode 100644 index 000000000..5bad738d8 --- /dev/null +++ b/packages/base/src/page/Home/WorkflowStatCards/__tests__/__snapshots__/index.test.tsx.snap @@ -0,0 +1,260 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`WorkflowStatCards should render loading state on initial render 1`] = ` + +
+
+
+
+
+
+
    +
  • +
  • +
  • +
  • +
+
+
+
+
+
+
+ +`; + +exports[`WorkflowStatCards should render workflow statistics after data loaded 1`] = ` + +
+
+
+
+
+
+
+ 工单概览 +
+
+ 跟踪与您相关的工单进度 +
+ + 查看工单管理 + + + + + +
+
+
+
+ + + + + + + 待我处理 + +
+
+ 8 +
+
+ 需立即行动 +
+
+
+
+ + + + + + + 我发起的 + +
+
+ 5 +
+
+ 关注进度 +
+
+
+
+ + + + + + + 已归档 + +
+
+ 12 +
+
+ 已完结任务 +
+
+
+
+ + + + + + + 查看全部 + +
+
+ 25 +
+
+ 当前可见范围 +
+
+
+
+
+
+
+
+ +`; diff --git a/packages/base/src/page/Home/WorkflowStatCards/__tests__/index.test.tsx b/packages/base/src/page/Home/WorkflowStatCards/__tests__/index.test.tsx new file mode 100644 index 000000000..b2aebda4a --- /dev/null +++ b/packages/base/src/page/Home/WorkflowStatCards/__tests__/index.test.tsx @@ -0,0 +1,155 @@ +import { act, cleanup, fireEvent, screen } from '@testing-library/react'; +import WorkflowStatCards from '..'; +import { baseSuperRender } from '../../../../testUtils/superRender'; +import { sqleMockApi } from '@actiontech/shared/lib/testUtil'; +import { useTypedNavigate } from '@actiontech/shared'; +import { ROUTE_PATHS } from '@actiontech/shared/lib/data/routePaths'; +import { GetGlobalWorkflowListV2FilterCardEnum } from '@actiontech/shared/lib/api/sqle/service/GlobalDashboard/index.enum'; + +jest.mock('@actiontech/shared', () => ({ + ...jest.requireActual('@actiontech/shared'), + useTypedNavigate: jest.fn() +})); + +describe('WorkflowStatCards', () => { + const navigateSpy = jest.fn(); + let getGlobalWorkflowStatisticsSpy: jest.SpyInstance; + + beforeEach(() => { + jest.useFakeTimers(); + getGlobalWorkflowStatisticsSpy = + sqleMockApi.globalDashboard.getGlobalWorkflowStatistics(); + (useTypedNavigate as jest.Mock).mockImplementation(() => navigateSpy); + }); + + afterEach(() => { + jest.useRealTimers(); + jest.clearAllMocks(); + cleanup(); + }); + + it('should render loading state on initial render', () => { + const { baseElement } = baseSuperRender(); + expect(baseElement).toMatchSnapshot(); + expect(getGlobalWorkflowStatisticsSpy).toHaveBeenCalledTimes(1); + }); + + it('should render workflow statistics after data loaded', async () => { + const { baseElement } = baseSuperRender(); + + await act(async () => jest.advanceTimersByTime(3000)); + + expect(baseElement).toMatchSnapshot(); + + expect(screen.getByText('工单概览')).toBeInTheDocument(); + expect(screen.getByText('跟踪与您相关的工单进度')).toBeInTheDocument(); + expect( + screen.getByText('查看工单管理', { exact: false }) + ).toBeInTheDocument(); + + expect(screen.getByText('待我处理')).toBeInTheDocument(); + expect(screen.getByText('需立即行动')).toBeInTheDocument(); + expect(screen.getByText('我发起的')).toBeInTheDocument(); + expect(screen.getByText('关注进度')).toBeInTheDocument(); + expect(screen.getByText('已归档')).toBeInTheDocument(); + expect(screen.getByText('已完结任务')).toBeInTheDocument(); + expect(screen.getByText('查看全部')).toBeInTheDocument(); + expect(screen.getByText('当前可见范围')).toBeInTheDocument(); + + // 验证统计数字(mock 数据:8 / 5 / 12 / 25) + expect(screen.getByText('8')).toBeInTheDocument(); + expect(screen.getByText('5')).toBeInTheDocument(); + expect(screen.getByText('12')).toBeInTheDocument(); + expect(screen.getByText('25')).toBeInTheDocument(); + }); + + it('should navigate to workflow management when clicking section link', async () => { + baseSuperRender(); + + await act(async () => jest.advanceTimersByTime(3000)); + + fireEvent.click(screen.getByText('查看工单管理', { exact: false })); + + expect(navigateSpy).toHaveBeenCalledTimes(1); + expect(navigateSpy).toHaveBeenCalledWith( + ROUTE_PATHS.SQLE.GLOBAL_DASHBOARD.index, + { queries: { tab: 'workflow' } } + ); + }); + + it('should navigate with pending_for_me filter when clicking "待我处理" card', async () => { + baseSuperRender(); + + await act(async () => jest.advanceTimersByTime(3000)); + + fireEvent.click(screen.getByText('待我处理')); + + expect(navigateSpy).toHaveBeenCalledTimes(1); + expect(navigateSpy).toHaveBeenCalledWith( + ROUTE_PATHS.SQLE.GLOBAL_DASHBOARD.index, + { + queries: { + tab: 'workflow', + card: GetGlobalWorkflowListV2FilterCardEnum.pending_for_me + } + } + ); + }); + + it('should navigate with initiated_by_me filter when clicking "我发起的" card', async () => { + baseSuperRender(); + + await act(async () => jest.advanceTimersByTime(3000)); + + fireEvent.click(screen.getByText('我发起的')); + + expect(navigateSpy).toHaveBeenCalledTimes(1); + expect(navigateSpy).toHaveBeenCalledWith( + ROUTE_PATHS.SQLE.GLOBAL_DASHBOARD.index, + { + queries: { + tab: 'workflow', + card: GetGlobalWorkflowListV2FilterCardEnum.initiated_by_me + } + } + ); + }); + + it('should navigate with archived filter when clicking "已归档" card', async () => { + baseSuperRender(); + + await act(async () => jest.advanceTimersByTime(3000)); + + fireEvent.click(screen.getByText('已归档')); + + expect(navigateSpy).toHaveBeenCalledTimes(1); + expect(navigateSpy).toHaveBeenCalledWith( + ROUTE_PATHS.SQLE.GLOBAL_DASHBOARD.index, + { + queries: { + tab: 'workflow', + card: GetGlobalWorkflowListV2FilterCardEnum.archived + } + } + ); + }); + + it('should navigate with view_all filter when clicking "查看全部" card', async () => { + baseSuperRender(); + + await act(async () => jest.advanceTimersByTime(3000)); + + fireEvent.click(screen.getByText('查看全部')); + + expect(navigateSpy).toHaveBeenCalledTimes(1); + expect(navigateSpy).toHaveBeenCalledWith( + ROUTE_PATHS.SQLE.GLOBAL_DASHBOARD.index, + { + queries: { + tab: 'workflow', + card: GetGlobalWorkflowListV2FilterCardEnum.view_all + } + } + ); + }); +}); diff --git a/packages/base/src/page/Home/WorkflowStatCards/index.tsx b/packages/base/src/page/Home/WorkflowStatCards/index.tsx new file mode 100644 index 000000000..ad2a2f207 --- /dev/null +++ b/packages/base/src/page/Home/WorkflowStatCards/index.tsx @@ -0,0 +1,125 @@ +import { useRequest } from 'ahooks'; +import { useTranslation } from 'react-i18next'; +import { Card, Typography } from 'antd'; +import { + CheckboxMultipleBlankFilled, + ClockCircleOutlined, + EditFilled, + ProfileSquareFilled +} from '@actiontech/icons'; +import { GlobalDashboardService } from '@actiontech/shared/lib/api/sqle'; +import { GetGlobalWorkflowListV2FilterCardEnum } from '@actiontech/shared/lib/api/sqle/service/GlobalDashboard/index.enum'; +import { useTypedNavigate } from '@actiontech/shared'; +import { ROUTE_PATHS } from '@actiontech/shared/lib/data/routePaths'; +import { RightOutlined } from '@ant-design/icons'; +import { WorkflowStatCardsWrapper, WorkflowCardItemWrapper } from './style'; + +const WORKFLOW_ACCENT: Record = { + [GetGlobalWorkflowListV2FilterCardEnum.pending_for_me]: '#fa8c16', + [GetGlobalWorkflowListV2FilterCardEnum.initiated_by_me]: '#1677ff', + [GetGlobalWorkflowListV2FilterCardEnum.archived]: '#52c41a', + [GetGlobalWorkflowListV2FilterCardEnum.view_all]: '#722ed1' +}; + +const WorkflowStatCards: React.FC = () => { + const { t } = useTranslation(); + const navigate = useTypedNavigate(); + + const { data: workflowStats, loading } = useRequest(() => + GlobalDashboardService.GetGlobalWorkflowStatisticsV2({}) + ); + + const cards = [ + { + key: GetGlobalWorkflowListV2FilterCardEnum.pending_for_me, + title: t('dmsHome.workflowCards.workflow.pendingMine'), + subtitle: t('dmsHome.workflowCards.workflow.pendingMineSubtitle'), + count: workflowStats?.data?.data?.pending_for_me_count ?? 0, + icon: , + accentColor: + WORKFLOW_ACCENT[GetGlobalWorkflowListV2FilterCardEnum.pending_for_me] + }, + { + key: GetGlobalWorkflowListV2FilterCardEnum.initiated_by_me, + title: t('dmsHome.workflowCards.workflow.initiated'), + subtitle: t('dmsHome.workflowCards.workflow.initiatedSubtitle'), + count: workflowStats?.data?.data?.initiated_by_me_count ?? 0, + icon: , + accentColor: + WORKFLOW_ACCENT[GetGlobalWorkflowListV2FilterCardEnum.initiated_by_me] + }, + { + key: GetGlobalWorkflowListV2FilterCardEnum.archived, + title: t('dmsHome.workflowCards.workflow.archived'), + subtitle: t('dmsHome.workflowCards.workflow.archivedSubtitle'), + count: workflowStats?.data?.data?.archived_count ?? 0, + icon: , + accentColor: + WORKFLOW_ACCENT[GetGlobalWorkflowListV2FilterCardEnum.archived] + }, + { + key: GetGlobalWorkflowListV2FilterCardEnum.view_all, + title: t('dmsHome.workflowCards.workflow.viewAll'), + subtitle: t('dmsHome.workflowCards.workflow.viewAllSubtitle'), + count: workflowStats?.data?.data?.view_all_count ?? 0, + icon: ( + + ), + accentColor: + WORKFLOW_ACCENT[GetGlobalWorkflowListV2FilterCardEnum.view_all] + } + ]; + + return ( + + +
+
+
+ {t('dmsHome.workflowCards.sectionTitle')} +
+
+ {t('dmsHome.workflowCards.sectionDescription')} +
+ + navigate(ROUTE_PATHS.SQLE.GLOBAL_DASHBOARD.index, { + queries: { tab: 'workflow' } + }) + } + > + {t('dmsHome.workflowCards.viewAll')} + +
+
+ {cards.map((card) => ( + + navigate(ROUTE_PATHS.SQLE.GLOBAL_DASHBOARD.index, { + queries: { tab: 'workflow', card: card.key } + }) + } + > +
+ {card.icon} + {card.title} +
+
{card.count}
+
{card.subtitle}
+
+ ))} +
+
+
+
+ ); +}; + +export default WorkflowStatCards; diff --git a/packages/base/src/page/Home/WorkflowStatCards/style.ts b/packages/base/src/page/Home/WorkflowStatCards/style.ts new file mode 100644 index 000000000..37691cdb6 --- /dev/null +++ b/packages/base/src/page/Home/WorkflowStatCards/style.ts @@ -0,0 +1,133 @@ +import { styled } from '@mui/material/styles'; + +export const WorkflowStatCardsWrapper = styled('div')` + margin: 20px ${({ theme }) => theme.baseTheme.guidance.padding}px; + margin-bottom: ${({ theme }) => theme.baseTheme.guidance.gap}px; + + .workflow-cards-card { + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + background: ${({ theme }) => theme.sharedTheme.uiToken.colorBgBase}; + + .ant-card-body { + padding: 24px; + } + } + + .cards-content { + display: flex; + align-items: center; + gap: 32px; + } + + .cards-left-section { + min-width: 160px; + padding-right: 32px; + border-right: 1px solid + ${({ theme }) => theme.sharedTheme.uiToken.colorBorderSecondary}; + + .section-title { + font-size: 16px; + font-weight: 600; + color: ${({ theme }) => theme.sharedTheme.uiToken.colorText}; + margin-bottom: 6px; + } + + .section-description { + font-size: 13px; + line-height: 20px; + color: ${({ theme }) => theme.sharedTheme.uiToken.colorTextSecondary}; + margin-bottom: 14px; + } + + .section-link { + font-size: 13px; + color: #1677ff; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 4px; + transition: color 0.3s; + + &:hover { + color: #4096ff; + } + } + } + + .cards-row { + flex: 1; + display: flex; + align-items: stretch; + } +`; + +export const WorkflowCardItemWrapper = styled('div')<{ + $accentColor: string; +}>` + flex: 1; + cursor: pointer; + padding: 0 24px; + border-left: 1px solid + ${({ theme }) => theme.sharedTheme.uiToken.colorBorderSecondary}; + transition: background 0.2s; + border-radius: 6px; + + &:first-of-type { + border-left: none; + padding-left: 0; + } + + &:last-of-type { + padding-right: 0; + } + + &:hover { + background: ${({ theme }) => theme.sharedTheme.uiToken.colorFillQuaternary}; + + .card-icon { + color: ${({ $accentColor }) => $accentColor}; + } + + .card-count { + color: ${({ $accentColor }) => $accentColor}; + } + } + + .card-header { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 10px; + + .card-icon { + display: inline-flex; + align-items: center; + justify-content: center; + color: ${({ theme }) => theme.sharedTheme.uiToken.colorTextTertiary}; + font-size: 16px; + transition: color 0.2s; + } + + .card-title { + font-size: 14px; + color: ${({ theme }) => theme.sharedTheme.uiToken.colorTextSecondary}; + white-space: nowrap; + } + } + + .card-count { + font-size: 32px; + font-weight: 600; + color: ${({ theme }) => theme.sharedTheme.uiToken.colorText}; + line-height: 1; + margin-bottom: 8px; + transition: color 0.2s; + } + + .card-subtitle { + font-size: 12px; + color: ${({ theme }) => theme.sharedTheme.uiToken.colorTextTertiary}; + white-space: nowrap; + } +`; diff --git a/packages/base/src/page/Home/__tests__/__snapshots__/index.ce.test.tsx.snap b/packages/base/src/page/Home/__tests__/__snapshots__/index.ce.test.tsx.snap index 2d6a3b230..164ca5579 100644 --- a/packages/base/src/page/Home/__tests__/__snapshots__/index.ce.test.tsx.snap +++ b/packages/base/src/page/Home/__tests__/__snapshots__/index.ce.test.tsx.snap @@ -9,12 +9,42 @@ exports[`test base/page/Home should match snapshot 1`] = `
- 用户引导 + 首页
+
+
+
+
+
+
    +
  • +
  • +
  • +
  • +
+
+
+
+
+
diff --git a/packages/base/src/page/Home/__tests__/__snapshots__/index.test.tsx.snap b/packages/base/src/page/Home/__tests__/__snapshots__/index.test.tsx.snap index e2d3030fd..f4d1785ea 100644 --- a/packages/base/src/page/Home/__tests__/__snapshots__/index.test.tsx.snap +++ b/packages/base/src/page/Home/__tests__/__snapshots__/index.test.tsx.snap @@ -9,12 +9,42 @@ exports[`test base/page/Home should match snapshot 1`] = `
- 用户引导 + 首页
+
+
+
+
+
+
    +
  • +
  • +
  • +
  • +
+
+
+
+
+
diff --git a/packages/base/src/page/Home/__tests__/index.ce.test.tsx b/packages/base/src/page/Home/__tests__/index.ce.test.tsx index 256f28f2a..ff94beb12 100644 --- a/packages/base/src/page/Home/__tests__/index.ce.test.tsx +++ b/packages/base/src/page/Home/__tests__/index.ce.test.tsx @@ -2,14 +2,28 @@ * @test_version ce */ -import { mockUseCurrentUser } from '@actiontech/shared/lib/testUtil/mockHook/mockUseCurrentUser'; +import { cleanup, screen } from '@testing-library/react'; +import { + mockUseCurrentUser, + mockUsePermission, + sqleMockApi +} from '@actiontech/shared/lib/testUtil'; import Home from '..'; import { baseSuperRender } from '../../../testUtils/superRender'; -import { screen } from '@testing-library/react'; describe('test base/page/Home', () => { beforeEach(() => { mockUseCurrentUser(); + mockUsePermission( + { checkActionPermission: jest.fn().mockReturnValue(true) }, + { useSpyOnMockHooks: true } + ); + sqleMockApi.globalDashboard.getGlobalWorkflowStatistics(); + }); + + afterEach(() => { + jest.clearAllMocks(); + cleanup(); }); test('should match snapshot', () => { diff --git a/packages/base/src/page/Home/__tests__/index.test.tsx b/packages/base/src/page/Home/__tests__/index.test.tsx index 194b4c3de..faa61fd68 100644 --- a/packages/base/src/page/Home/__tests__/index.test.tsx +++ b/packages/base/src/page/Home/__tests__/index.test.tsx @@ -1,11 +1,25 @@ -import { mockUseCurrentUser } from '@actiontech/shared/lib/testUtil/mockHook/mockUseCurrentUser'; +import { cleanup, screen } from '@testing-library/react'; +import { + mockUseCurrentUser, + mockUsePermission, + sqleMockApi +} from '@actiontech/shared/lib/testUtil'; import Home from '..'; import { baseSuperRender } from '../../../testUtils/superRender'; -import { screen } from '@testing-library/react'; describe('test base/page/Home', () => { beforeEach(() => { mockUseCurrentUser(); + mockUsePermission( + { checkActionPermission: jest.fn().mockReturnValue(true) }, + { useSpyOnMockHooks: true } + ); + sqleMockApi.globalDashboard.getGlobalWorkflowStatistics(); + }); + + afterEach(() => { + jest.clearAllMocks(); + cleanup(); }); test('should match snapshot', () => { diff --git a/packages/base/src/page/Home/index.tsx b/packages/base/src/page/Home/index.tsx index 9e69b0eb9..b989c5626 100644 --- a/packages/base/src/page/Home/index.tsx +++ b/packages/base/src/page/Home/index.tsx @@ -3,6 +3,7 @@ import { PageHeader } from '@actiontech/dms-kit'; import { useTranslation } from 'react-i18next'; import CEDefaultScene from './DefaultScene/index.ce'; import AIBanner from './AIBanner'; +import WorkflowStatCards from './WorkflowStatCards'; const Home: React.FC = () => { const { t } = useTranslation(); @@ -10,6 +11,7 @@ const Home: React.FC = () => { return ( <> + {/* #if [ee] */} From e7da5ca9dfc2df953da422db8b23f77eea9b0f00 Mon Sep 17 00:00:00 2001 From: lizhensheng Date: Wed, 20 May 2026 14:23:05 +0800 Subject: [PATCH 4/6] feat(globalDashboard): add relevant fields and filter options --- .../__tests__/index.test.tsx | 2 +- .../src/page/Home/WorkflowStatCards/index.tsx | 2 +- packages/dms-kit/src/data/routePaths.ts | 2 +- .../sqle/src/locale/en-US/globalDashboard.ts | 5 +- .../sqle/src/locale/zh-CN/globalDashboard.ts | 5 +- .../__snapshots__/index.sqle.test.tsx.snap | 245 +++++++++++++++++- .../__tests__/index.sqle.test.tsx | 31 ++- .../components/TableFilter/index.tsx | 4 +- .../__snapshots__/index.test.tsx.snap | 235 +++++++++++++++++ .../WorkflowPanel/__tests__/column.test.tsx | 103 ++++++++ .../WorkflowPanel/__tests__/index.test.tsx | 134 ++++++++-- .../components/WorkflowPanel/action.ts | 3 +- .../components/WorkflowPanel/column.tsx | 28 ++ .../components/WorkflowPanel/index.tsx | 82 +++++- .../components/WorkflowPanel/index.type.ts | 14 +- .../sqle/src/page/GlobalDashboard/index.tsx | 17 +- .../sqle/src/page/GlobalDashboard/style.ts | 4 +- 17 files changed, 870 insertions(+), 46 deletions(-) diff --git a/packages/base/src/page/Home/WorkflowStatCards/__tests__/index.test.tsx b/packages/base/src/page/Home/WorkflowStatCards/__tests__/index.test.tsx index b2aebda4a..fb02dd09a 100644 --- a/packages/base/src/page/Home/WorkflowStatCards/__tests__/index.test.tsx +++ b/packages/base/src/page/Home/WorkflowStatCards/__tests__/index.test.tsx @@ -3,7 +3,7 @@ import WorkflowStatCards from '..'; import { baseSuperRender } from '../../../../testUtils/superRender'; import { sqleMockApi } from '@actiontech/shared/lib/testUtil'; import { useTypedNavigate } from '@actiontech/shared'; -import { ROUTE_PATHS } from '@actiontech/shared/lib/data/routePaths'; +import { ROUTE_PATHS } from '@actiontech/dms-kit'; import { GetGlobalWorkflowListV2FilterCardEnum } from '@actiontech/shared/lib/api/sqle/service/GlobalDashboard/index.enum'; jest.mock('@actiontech/shared', () => ({ diff --git a/packages/base/src/page/Home/WorkflowStatCards/index.tsx b/packages/base/src/page/Home/WorkflowStatCards/index.tsx index ad2a2f207..4d9400e57 100644 --- a/packages/base/src/page/Home/WorkflowStatCards/index.tsx +++ b/packages/base/src/page/Home/WorkflowStatCards/index.tsx @@ -10,7 +10,7 @@ import { import { GlobalDashboardService } from '@actiontech/shared/lib/api/sqle'; import { GetGlobalWorkflowListV2FilterCardEnum } from '@actiontech/shared/lib/api/sqle/service/GlobalDashboard/index.enum'; import { useTypedNavigate } from '@actiontech/shared'; -import { ROUTE_PATHS } from '@actiontech/shared/lib/data/routePaths'; +import { ROUTE_PATHS } from '@actiontech/dms-kit'; import { RightOutlined } from '@ant-design/icons'; import { WorkflowStatCardsWrapper, WorkflowCardItemWrapper } from './style'; diff --git a/packages/dms-kit/src/data/routePaths.ts b/packages/dms-kit/src/data/routePaths.ts index c42f76c50..8abaa3268 100644 --- a/packages/dms-kit/src/data/routePaths.ts +++ b/packages/dms-kit/src/data/routePaths.ts @@ -128,7 +128,7 @@ export const ROUTE_PATHS = { GLOBAL_DASHBOARD: { index: { path: '/sqle/global-dashboard', - query: 'tab' + query: 'tab&card' } }, REPORT_STATISTICS: { diff --git a/packages/sqle/src/locale/en-US/globalDashboard.ts b/packages/sqle/src/locale/en-US/globalDashboard.ts index fb1a5eb2b..114f72ba5 100644 --- a/packages/sqle/src/locale/en-US/globalDashboard.ts +++ b/packages/sqle/src/locale/en-US/globalDashboard.ts @@ -35,7 +35,10 @@ export default { instance: 'Instance', assignee: 'Assignee', priority: 'Priority', - status: 'Status' + status: 'Status', + createUser: 'Initiator', + createdAt: 'Created At', + updatedAt: 'Last Updated' }, status: 'Workflow Status', name: 'Workflow Name', diff --git a/packages/sqle/src/locale/zh-CN/globalDashboard.ts b/packages/sqle/src/locale/zh-CN/globalDashboard.ts index b14a15730..8af8c1c38 100644 --- a/packages/sqle/src/locale/zh-CN/globalDashboard.ts +++ b/packages/sqle/src/locale/zh-CN/globalDashboard.ts @@ -30,7 +30,10 @@ export default { instance: '数据源', assignee: '当前处理人', priority: '优先级', - status: '状态' + status: '状态', + createUser: '发起人', + createdAt: '创建时间', + updatedAt: '最后操作时间' }, status: '工单状态', name: '工单名称', diff --git a/packages/sqle/src/page/GlobalDashboard/__tests__/__snapshots__/index.sqle.test.tsx.snap b/packages/sqle/src/page/GlobalDashboard/__tests__/__snapshots__/index.sqle.test.tsx.snap index ddd94e5ac..0a22a59e8 100644 --- a/packages/sqle/src/page/GlobalDashboard/__tests__/__snapshots__/index.sqle.test.tsx.snap +++ b/packages/sqle/src/page/GlobalDashboard/__tests__/__snapshots__/index.sqle.test.tsx.snap @@ -93,7 +93,7 @@ exports[`GlobalDashboard should render workflow tab by default 1`] = `
>>>>>> 5153cce06 (feat(globalDashboard): add relevant fields and filter options) >
+
+ +
+ + + @@ -621,6 +665,24 @@ exports[`GlobalDashboard should render workflow tab by default 1`] = ` > 状态 + + 发起人 + + + 创建时间 + + + 最后操作时间 + + +
+   +
+ + +
+   +
+ + +
+   +
+ + + - + + + 2026-04-13 10:00:00 + + + - + + + - + + + 2026-04-13 12:00:00 + + + - + + + - + + + 2026-04-13 13:00:00 + + + - + + + - + + + 2026-04-13 14:00:00 + + + - + + + - + + + 2026-04-13 15:00:00 + + + - + + + - + + + 2026-04-13 16:00:00 + + + - + + + - + + + 2026-04-13 17:00:00 + + + - + + + - + + + 2026-04-13 18:00:00 + + + - + + + - + + + 2026-04-13 19:00:00 + + + - + + + - + + + 2026-04-13 20:00:00 + + + - + ({ @@ -40,6 +43,7 @@ describe('GlobalDashboard', () => { getGlobalSqlManageTaskListSpy = sqleMockApi.globalDashboard.getGlobalSqlManageTaskList(); getInstanceTipListSpy = sqleMockApi.instance.getInstanceTipList(); + baseMockApi.userCenter.getUserList(); (useTypedNavigate as jest.Mock).mockImplementation(() => navigateSpy); (useTypedQuery as jest.Mock).mockImplementation(() => extractQuerySpy); @@ -133,4 +137,29 @@ describe('GlobalDashboard', () => { project_name: 'test' }); }); + + it('should initialize workflow card from card URL query param', async () => { + extractQuerySpy.mockReturnValue({ + tab: 'workflow', + card: 'initiated_by_me' + }); + + superRender(); + await act(async () => jest.advanceTimersByTime(3000)); + + expect(getGlobalWorkflowListSpy).toHaveBeenCalledWith( + expect.objectContaining({ filter_card: 'initiated_by_me' }) + ); + }); + + it('should default to pending_for_me when no card query param', async () => { + extractQuerySpy.mockReturnValue(undefined); + + superRender(); + await act(async () => jest.advanceTimersByTime(3000)); + + expect(getGlobalWorkflowListSpy).toHaveBeenCalledWith( + expect.objectContaining({ filter_card: 'pending_for_me' }) + ); + }); }); diff --git a/packages/sqle/src/page/GlobalDashboard/components/TableFilter/index.tsx b/packages/sqle/src/page/GlobalDashboard/components/TableFilter/index.tsx index d715298b1..b56d05eb5 100644 --- a/packages/sqle/src/page/GlobalDashboard/components/TableFilter/index.tsx +++ b/packages/sqle/src/page/GlobalDashboard/components/TableFilter/index.tsx @@ -12,6 +12,7 @@ const GlobalDashboardTableFilter: React.FC = ({ onProjectChange }) => { const { t } = useTranslation(); + const selectedProjectId = Form.useWatch('projectId', form); return ( @@ -22,7 +23,6 @@ const GlobalDashboardTableFilter: React.FC = ({ prefix={t('globalDashboard.filter.project')} placeholder={t('common.all')} suffixIcon={null} - bordered={false} options={projectOptions} onChange={(value) => { onProjectChange?.(value as string); @@ -34,9 +34,9 @@ const GlobalDashboardTableFilter: React.FC = ({ prefix={t('globalDashboard.filter.instance')} placeholder={t('common.all')} suffixIcon={null} - bordered={false} options={instanceIDOptions} loading={getInstanceListLoading} + disabled={!selectedProjectId} /> diff --git a/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/__tests__/__snapshots__/index.test.tsx.snap b/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/__tests__/__snapshots__/index.test.tsx.snap index b0f54c899..666aad5a8 100644 --- a/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/__tests__/__snapshots__/index.test.tsx.snap +++ b/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/__tests__/__snapshots__/index.test.tsx.snap @@ -238,6 +238,7 @@ exports[`GlobalDashboard/WorkflowPanel should render workflow panel and request
+
+ +
+ + + @@ -401,6 +441,24 @@ exports[`GlobalDashboard/WorkflowPanel should render workflow panel and request > 状态 + + 发起人 + + + 创建时间 + + + 最后操作时间 + + +
+   +
+ + +
+   +
+ + +
+   +
+ + + - + + + 2026-04-13 10:00:00 + + + - + + + - + + + 2026-04-13 12:00:00 + + + - + + + - + + + 2026-04-13 13:00:00 + + + - + + + - + + + 2026-04-13 14:00:00 + + + - + + + - + + + 2026-04-13 15:00:00 + + + - + + + - + + + 2026-04-13 16:00:00 + + + - + + + - + + + 2026-04-13 17:00:00 + + + - + + + - + + + 2026-04-13 18:00:00 + + + - + + + - + + + 2026-04-13 19:00:00 + + + - + + + - + + + 2026-04-13 20:00:00 + + + - + { + beforeEach(() => { + MockDate.set('2026-04-13 12:00:00'); + }); + + afterEach(() => { + MockDate.reset(); + }); + it('should render fallback priority tag for unknown priority', () => { const columns = workflowPanelColumns(); const priorityRender = columns[5].render as (value?: string) => any; @@ -57,4 +66,98 @@ describe('GlobalDashboard/WorkflowPanel/column', () => { expect(screen.getByText('-')).toBeInTheDocument(); }); + + describe('create_user_name column (index 7)', () => { + it('should render CustomAvatar when create_user_name is provided', () => { + const columns = workflowPanelColumns(); + const render = columns[7].render as (name: string | undefined) => any; + + superRender(<>{render('admin')}); + + // CustomAvatar renders the uppercased first letter of the name + expect(screen.getByText('A')).toBeInTheDocument(); + }); + + it('should render dash when create_user_name is empty', () => { + const columns = workflowPanelColumns(); + const render = columns[7].render as (name: string | undefined) => any; + + superRender(<>{render(undefined)}); + + expect(screen.getByText('-')).toBeInTheDocument(); + }); + + it('should have filterCustomType select and correct filterKey', () => { + const columns = workflowPanelColumns(); + const col = columns[7]; + + expect(col.filterCustomType).toBe('select'); + expect(col.filterKey).toBe('filter_create_user_id'); + expect(col.filterLabel).toBe('发起人'); + }); + }); + + describe('created_at column (index 8)', () => { + it('should render formatted time when created_at is provided', () => { + const columns = workflowPanelColumns(); + const render = columns[8].render as (time: string | undefined) => any; + + superRender(<>{render('2026-04-13 10:00:00')}); + + expect(screen.getByText('2026-04-13 10:00:00')).toBeInTheDocument(); + }); + + it('should render dash when created_at is empty', () => { + const columns = workflowPanelColumns(); + const render = columns[8].render as (time: string | undefined) => any; + + superRender(<>{render(undefined)}); + + expect(screen.getByText('-')).toBeInTheDocument(); + }); + + it('should have filterCustomType date-range and correct filterKey array', () => { + const columns = workflowPanelColumns(); + const col = columns[8]; + + expect(col.filterCustomType).toBe('date-range'); + expect(col.filterKey).toEqual([ + 'filter_create_time_from', + 'filter_create_time_to' + ]); + expect(col.filterLabel).toBe('创建时间'); + }); + }); + + describe('updated_at column (index 9)', () => { + it('should render formatted time when updated_at is provided', () => { + const columns = workflowPanelColumns(); + const render = columns[9].render as (time: string | undefined) => any; + + superRender(<>{render('2026-04-13 16:00:00')}); + + expect(screen.getByText('2026-04-13 16:00:00')).toBeInTheDocument(); + }); + + it('should render dash when updated_at is empty', () => { + const columns = workflowPanelColumns(); + const render = columns[9].render as (time: string | undefined) => any; + + superRender(<>{render(undefined)}); + + expect(screen.getByText('-')).toBeInTheDocument(); + }); + + it('should have filterCustomType date-range and correct filterKey array', () => { + const columns = workflowPanelColumns(); + const col = columns[9]; + + expect(col.filterCustomType).toBe('date-range'); + expect(col.filterKey).toEqual([ + 'filter_update_time_from', + 'filter_update_time_to' + ]); + expect(col.filterLabel).toBe('最后操作时间'); + }); + }); }); diff --git a/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/__tests__/index.test.tsx b/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/__tests__/index.test.tsx index bac2cc644..294b03c98 100644 --- a/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/__tests__/index.test.tsx +++ b/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/__tests__/index.test.tsx @@ -3,18 +3,23 @@ import WorkflowPanel from '..'; import { superRender } from '@actiontech/shared/lib/testUtil/superRender'; import { mockUseCurrentProject } from '@actiontech/shared/lib/testUtil/mockHook/mockUseCurrentProject'; import { mockUseCurrentUser } from '@actiontech/shared/lib/testUtil/mockHook/mockUseCurrentUser'; -import { sqleMockApi } from '@actiontech/shared/lib/testUtil/mockApi'; +import { + sqleMockApi, + baseMockApi +} from '@actiontech/shared/lib/testUtil/mockApi'; import { createSpyErrorResponse, createSpySuccessResponse } from '@actiontech/shared/lib/testUtil/mockApi/common'; import { GlobalWorkflowListItemWorkflowTypeEnum } from '@actiontech/shared/lib/api/sqle/service/common.enum'; import { mockGlobalWorkflowStatisticsData } from '@actiontech/shared/lib/testUtil/mockApi/sqle/globalDashboard/data'; +import { GetGlobalWorkflowListV2FilterCardEnum } from '@actiontech/shared/lib/api/sqle/service/GlobalDashboard/index.enum'; describe('GlobalDashboard/WorkflowPanel', () => { const openSpy = jest.spyOn(window, 'open').mockImplementation(jest.fn()); let getGlobalWorkflowStatisticsSpy: jest.SpyInstance; let getGlobalWorkflowListSpy: jest.SpyInstance; + let getUserListSpy: jest.SpyInstance; beforeEach(() => { jest.useFakeTimers(); @@ -24,6 +29,7 @@ describe('GlobalDashboard/WorkflowPanel', () => { sqleMockApi.globalDashboard.getGlobalWorkflowStatistics(); getGlobalWorkflowListSpy = sqleMockApi.globalDashboard.getGlobalWorkflowList(); + getUserListSpy = baseMockApi.userCenter.getUserList(); }); afterEach(() => { @@ -145,31 +151,129 @@ describe('GlobalDashboard/WorkflowPanel', () => { expect(getGlobalWorkflowListSpy).toHaveBeenCalledTimes(1); }); - it('should update workflow type filter and then clear to all', async () => { + it('should call User.ListUsers on mount with page_size 9999', async () => { + superRender(); + + await act(async () => jest.advanceTimersByTime(3000)); + + expect(getUserListSpy).toHaveBeenCalledWith({ page_size: 9999 }); + }); + + it('should render filter button in toolbar', async () => { + superRender(); + await act(async () => jest.advanceTimersByTime(3000)); + + expect( + document.querySelector('.actiontech-filter-button-namespace') + ).toBeInTheDocument(); + }); + + it('should show status and createdAt and updatedAt filters in TableFilterContainer after clicking filter button', async () => { superRender(); await act(async () => jest.advanceTimersByTime(3000)); fireEvent.click( - document.querySelector( - '.custom-segmented-filter-wrapper .ant-segmented-item-label[title="SQL上线工单"]' - ) as Element + document.querySelector('.actiontech-filter-button-namespace') as Element ); await act(async () => jest.advanceTimersByTime(0)); - expect(getGlobalWorkflowListSpy).toHaveBeenLastCalledWith( - expect.objectContaining({ workflow_type: 'sql_release' }) - ); + // After clicking filter button all filters are toggled on. + // "状态" / "创建时间" / "最后操作时间" appear in both table headers and filter container, + // so we verify at least one instance is in the filter container namespace. + expect( + document.querySelector('.actiontech-table-filter-container-namespace') + ).toBeInTheDocument(); + + const filterContainer = document.querySelector( + '.actiontech-table-filter-container-namespace' + ) as Element; + expect(filterContainer.textContent).toContain('状态'); + expect(filterContainer.textContent).toContain('创建时间'); + expect(filterContainer.textContent).toContain('最后操作时间'); + }); + + it('should call workflow list with filter_status when status filter is applied', async () => { + superRender(); + await act(async () => jest.advanceTimersByTime(3000)); + + // Enable all filters fireEvent.click( - document.querySelector( - '.custom-segmented-filter-wrapper .ant-segmented-item-label[title="全部"]' - ) as Element + document.querySelector('.actiontech-filter-button-namespace') as Element ); await act(async () => jest.advanceTimersByTime(0)); - const lastCallArgs = (getGlobalWorkflowListSpy as jest.Mock).mock.calls[ - (getGlobalWorkflowListSpy as jest.Mock).mock.calls.length - 1 - ][0]; - expect(lastCallArgs).not.toHaveProperty('workflow_type'); + // Find the status select inside the filter container (first select = status filter) + const filterContainer = document.querySelector( + '.actiontech-table-filter-container-namespace' + ) as Element; + const statusSelect = filterContainer.querySelector( + '.ant-select-selector' + ) as Element; + fireEvent.mouseDown(statusSelect); + await act(async () => jest.advanceTimersByTime(0)); + + // Select '待处理' (pending_action) from dropdown + const dropdownOptions = document.querySelectorAll( + '.ant-select-item-option-content' + ); + const pendingActionOption = Array.from(dropdownOptions).find( + (el) => el.textContent === '待处理' + ); + fireEvent.click(pendingActionOption as Element); + await act(async () => jest.advanceTimersByTime(3000)); + + expect(getGlobalWorkflowListSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ filter_status: 'pending_action' }) + ); + }); + + it('should render create_user_name and created_at and updated_at columns in table', async () => { + getGlobalWorkflowListSpy.mockImplementation(() => + createSpySuccessResponse({ + data: { + total_nums: 1, + has_more: false, + next_cursor: '', + workflows: [ + { + workflow_id: 'workflow-col-test', + workflow_name: 'Column Test Workflow', + workflow_type: GlobalWorkflowListItemWorkflowTypeEnum.sql_release, + create_user_name: 'admin', + created_at: '2026-04-13 10:00:00', + updated_at: '2026-04-13 18:00:00', + project_uid: '1' + } + ] + } + }) + ); + + superRender(); + await act(async () => jest.advanceTimersByTime(3000)); + + expect(screen.getByText('发起人')).toBeInTheDocument(); + expect(screen.getByText('创建时间')).toBeInTheDocument(); + expect(screen.getByText('最后操作时间')).toBeInTheDocument(); + // CustomAvatar renders the uppercased first letter of user name + expect(screen.getByText('A')).toBeInTheDocument(); + expect(screen.getByText('2026-04-13 10:00:00')).toBeInTheDocument(); + expect(screen.getByText('2026-04-13 18:00:00')).toBeInTheDocument(); + }); + + it('should render initialCard prop and request with the correct filter_card', async () => { + superRender( + + ); + await act(async () => jest.advanceTimersByTime(3000)); + + expect(getGlobalWorkflowListSpy).toHaveBeenCalledWith( + expect.objectContaining({ + filter_card: 'initiated_by_me' + }) + ); }); it('should keep cursor when requesting page greater than one', async () => { diff --git a/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/action.ts b/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/action.ts index 9c70accf2..79c275296 100644 --- a/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/action.ts +++ b/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/action.ts @@ -38,7 +38,8 @@ export const workflowPanelTableActions = ( permissions: () => filterCard === GetGlobalWorkflowListV2FilterCardEnum.initiated_by_me || - filterCard === GetGlobalWorkflowListV2FilterCardEnum.archived + filterCard === GetGlobalWorkflowListV2FilterCardEnum.archived || + filterCard === GetGlobalWorkflowListV2FilterCardEnum.view_all } ] }; diff --git a/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/column.tsx b/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/column.tsx index 59a5546c9..7ba789953 100644 --- a/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/column.tsx +++ b/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/column.tsx @@ -2,6 +2,7 @@ import { BasicTag, BasicTagProps, CustomAvatar, + formatTime, ROUTE_PATHS } from '@actiontech/dms-kit'; import { TypedLink } from '@actiontech/shared'; @@ -226,6 +227,9 @@ export const workflowPanelColumns = (): ActiontechTableColumn< { dataIndex: 'status', title: t('globalDashboard.workflow.column.status'), + filterCustomType: 'select', + filterKey: 'filter_status', + filterLabel: t('globalDashboard.workflow.filter.status'), render: (status) => status ? ( @@ -234,6 +238,30 @@ export const workflowPanelColumns = (): ActiontechTableColumn< ) : ( '-' ) + }, + { + dataIndex: 'create_user_name', + title: t('globalDashboard.workflow.column.createUser'), + filterCustomType: 'select', + filterKey: 'filter_create_user_id', + filterLabel: t('globalDashboard.workflow.column.createUser'), + render: (name) => (name ? : '-') + }, + { + dataIndex: 'created_at', + title: t('globalDashboard.workflow.column.createdAt'), + filterCustomType: 'date-range', + filterKey: ['filter_create_time_from', 'filter_create_time_to'], + filterLabel: t('globalDashboard.workflow.column.createdAt'), + render: (time) => formatTime(time, '-') + }, + { + dataIndex: 'updated_at', + title: t('globalDashboard.workflow.column.updatedAt'), + filterCustomType: 'date-range', + filterKey: ['filter_update_time_from', 'filter_update_time_to'], + filterLabel: t('globalDashboard.workflow.column.updatedAt'), + render: (time) => formatTime(time, '-') } ]; }; diff --git a/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/index.tsx b/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/index.tsx index fa32022f2..6f4dca596 100644 --- a/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/index.tsx +++ b/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/index.tsx @@ -1,11 +1,15 @@ import { ActiontechTable, ActiontechTableWrapper, + CustomAvatar, CustomSegmentedFilter, getErrorMessage, ROUTE_PATHS, TableToolbar, - useTableRequestParams + useTableRequestParams, + useTableFilterContainer, + TableFilterContainer, + FilterCustomProps } from '@actiontech/dms-kit'; import { CheckboxMultipleBlankFilled, @@ -14,7 +18,7 @@ import { ProfileSquareFilled } from '@actiontech/icons'; import { useRequest } from 'ahooks'; -import { message } from 'antd'; +import { message, Space, Typography } from 'antd'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { StatCardsStyleWrapper, StatCardItemStyleWrapper } from '../../style'; @@ -30,25 +34,33 @@ import useThemeStyleData from '../../../../hooks/useThemeStyleData'; import { GlobalDashboardService } from '@actiontech/shared/lib/api/sqle'; import { GlobalWorkflowListItemWorkflowTypeEnum } from '@actiontech/shared/lib/api/sqle/service/common.enum'; import { parse2ReactRouterPath } from '@actiontech/shared'; -import { workflowTypeLabelDictionary } from './data'; +import { + workflowFilterStatusOptions, + workflowTypeLabelDictionary +} from './data'; +import { UserService } from '@actiontech/shared/lib/api/base'; type WorkflowPanelProps = { projectId?: string; instanceId?: string; refreshSignal?: number; + initialCard?: GetGlobalWorkflowListV2FilterCardEnum; }; const WorkflowPanel: React.FC = ({ projectId, instanceId, - refreshSignal + refreshSignal, + initialCard }) => { const { t } = useTranslation(); const { sqleTheme } = useThemeStyleData(); const [messageApi, messageContextHolder] = message.useMessage(); + const [workflowType, setWorkflowType] = + useState(null); const [workflowCard, setWorkflowCard] = useState( - GetGlobalWorkflowListV2FilterCardEnum.pending_for_me + initialCard ?? GetGlobalWorkflowListV2FilterCardEnum.pending_for_me ); const cursor = useRef(undefined); @@ -100,6 +112,7 @@ const WorkflowPanel: React.FC = ({ filter_instance_id: instanceId, cursor: cursor.current ?? undefined, ...tableFilterInfo, + workflow_type: workflowType ?? undefined, keyword: searchKeyword?.trim() || undefined }); }, @@ -118,13 +131,52 @@ const WorkflowPanel: React.FC = ({ instanceId, workflowCard, refreshSignal, - tableFilterInfo + tableFilterInfo, + workflowType ] } ); + const { data: userListData } = useRequest(() => + UserService.ListUsers({ page_size: 9999 }) + ); + + const userOptions = useMemo(() => { + return (userListData?.data?.data ?? []).map((u) => ({ + value: u.uid, + label: ( + + + {u.name} + + ), + text: u.name + })); + }, [userListData]); + const columns = useMemo(() => workflowPanelColumns(), []); + const { filterButtonMeta, filterContainerMeta, updateAllSelectedFilterItem } = + useTableFilterContainer< + IGlobalWorkflowListItem, + GlobalDashboardWorkflowTableFilterParam + >(columns, updateTableFilterInfo); + + const filterCustomProps = useMemo( + () => + new Map([ + ['status', { options: workflowFilterStatusOptions() }], + ['updated_at', { showTime: false }], + ['create_user_name', { options: userOptions }], + ['created_at', { showTime: false }] + ]), + [userOptions] + ); + const openWorkflow = useCallback( (record: IGlobalWorkflowListItem) => { const getGlobalWorkflowDetailPath = (): string | null => { @@ -247,6 +299,10 @@ const WorkflowPanel: React.FC = ({ refresh: workflowList.refresh, disabled: tableLoading }} + filterButton={{ + filterButtonMeta, + updateAllSelectedFilterItem + }} searchInput={{ placeholder: t( 'globalDashboard.workflow.toolbar.searchPlaceholder' @@ -258,13 +314,9 @@ const WorkflowPanel: React.FC = ({ }} > - value={tableFilterInfo.workflow_type ?? null} + value={workflowType} onChange={(val) => { - updateTableFilterInfo(() => - val == null - ? ({} as GlobalDashboardWorkflowTableFilterParam) - : { workflow_type: val } - ); + setWorkflowType(val); }} labelDictionary={workflowTypeLabelDictionary()} options={[ @@ -274,6 +326,12 @@ const WorkflowPanel: React.FC = ({ withAll /> + ; diff --git a/packages/sqle/src/page/GlobalDashboard/index.tsx b/packages/sqle/src/page/GlobalDashboard/index.tsx index 90cd83660..60cf3c7c6 100644 --- a/packages/sqle/src/page/GlobalDashboard/index.tsx +++ b/packages/sqle/src/page/GlobalDashboard/index.tsx @@ -12,6 +12,7 @@ import { DashboardTabKey } from './constants'; import WorkflowPanel from './components/WorkflowPanel'; import SqlGovernancePanel from './components/SqlGovernancePanel'; import AccountPanel from './components/AccountPanel'; +import { GetGlobalWorkflowListV2FilterCardEnum } from '@actiontech/shared/lib/api/sqle/service/GlobalDashboard/index.enum'; import { useTypedNavigate, useTypedQuery } from '@actiontech/shared'; const GlobalDashBoard = () => { @@ -22,6 +23,9 @@ const GlobalDashBoard = () => { const [activeTab, setActiveTab] = useState( DashboardTabKey.Workflow ); + const [initialWorkflowCard, setInitialWorkflowCard] = useState< + GetGlobalWorkflowListV2FilterCardEnum | undefined + >(undefined); const [refreshSignals, setRefreshSignals] = useState({ [DashboardTabKey.Workflow]: 0, [DashboardTabKey.SqlGovernance]: 0, @@ -63,6 +67,7 @@ const GlobalDashBoard = () => { projectId={projectId} instanceId={instanceId} refreshSignal={refreshSignals[DashboardTabKey.Workflow]} + initialCard={initialWorkflowCard} /> ) }, @@ -96,7 +101,7 @@ const GlobalDashBoard = () => { ]; return items; - }, [t, projectId, instanceId, refreshSignals, isAdmin]); + }, [t, projectId, instanceId, refreshSignals, isAdmin, initialWorkflowCard]); useEffect(() => { const searchParams = extractQuery(ROUTE_PATHS.SQLE.GLOBAL_DASHBOARD.index); @@ -109,6 +114,16 @@ const GlobalDashBoard = () => { ) { setActiveTab(searchParams.tab as DashboardTabKey); } + if ( + searchParams?.card && + Object.values(GetGlobalWorkflowListV2FilterCardEnum).includes( + searchParams.card as GetGlobalWorkflowListV2FilterCardEnum + ) + ) { + setInitialWorkflowCard( + searchParams.card as GetGlobalWorkflowListV2FilterCardEnum + ); + } }, [extractQuery]); return ( diff --git a/packages/sqle/src/page/GlobalDashboard/style.ts b/packages/sqle/src/page/GlobalDashboard/style.ts index 0e1cb468c..b135c38e0 100644 --- a/packages/sqle/src/page/GlobalDashboard/style.ts +++ b/packages/sqle/src/page/GlobalDashboard/style.ts @@ -3,12 +3,12 @@ import { styled } from '@mui/material/styles'; export const GlobalDashboardFilterStyleWrapper = styled('div')` & .ant-form.ant-form-horizontal { .ant-select-selector, - .custom-search-input { + .basic-search-input { font-size: 13px !important; } } - & .custom-select-namespace { + & .basic-select-wrapper { width: 280px; } `; From c579cb621bfa1f966be76003290481c8a3219508 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 7 May 2026 07:55:46 +0000 Subject: [PATCH 5/6] =?UTF-8?q?feat(global-dashboard):=20=E5=B7=A5?= =?UTF-8?q?=E5=8D=95=E9=9D=A2=E6=9D=BF=E6=96=B0=E5=A2=9E"=E7=8A=B6?= =?UTF-8?q?=E6=80=81"=E4=B8=8B=E6=8B=89=E7=AD=9B=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WorkflowPanel toolbar 在工单类型 segmented 旁新增一个 CustomSelect 单选 + "全部", 状态枚举与后端 GetGlobalWorkflowListV2 的 filter_status 对齐 (pending_approval/pending_action/in_progress/exporting/rejected/cancelled/ failed/completed/unknown 共 9 项)。选择后通过 tableFilterInfo.filter_status 随分页 / 卡片切换一起送到后端, 由后端按工单类型映射到上线工单与导出工单 各自的原生状态。 - packages/shared: API client 同步新增 GetGlobalWorkflowListV2FilterStatus Enum 与 IGetGlobalWorkflowListV2Params.filter_status, 与生成器风格一致。 - WorkflowPanel: toolbar 加 CustomSelect; 同步把 segmented 的清空逻辑改成 "omit key", 与 select allowClear 时 omit 行为对齐, 保证传给 API 的对象 不带空值字段。 - i18n: zh-CN/en-US 各加一条 globalDashboard.workflow.filter.status。 - 同步 3 个 GlobalDashboard 相关的 jest snapshot (toolbar DOM 多一个 select 节点)。 --- .../service/GlobalDashboard/index.enum.ts | 20 ++++++ .../sqle/src/locale/en-US/globalDashboard.ts | 3 +- .../sqle/src/locale/zh-CN/globalDashboard.ts | 3 +- .../__snapshots__/index.sqle.test.tsx.snap | 61 +++++++++++++++++++ .../components/WorkflowPanel/data.ts | 44 ++++++++++++- .../components/WorkflowPanel/index.tsx | 23 +++++++ 6 files changed, 151 insertions(+), 3 deletions(-) diff --git a/packages/shared/lib/api/sqle/service/GlobalDashboard/index.enum.ts b/packages/shared/lib/api/sqle/service/GlobalDashboard/index.enum.ts index be63331ae..b9278e9c2 100644 --- a/packages/shared/lib/api/sqle/service/GlobalDashboard/index.enum.ts +++ b/packages/shared/lib/api/sqle/service/GlobalDashboard/index.enum.ts @@ -27,3 +27,23 @@ export enum GetGlobalWorkflowListV2WorkflowTypeEnum { 'data_export' = 'data_export' } + +export enum GetGlobalWorkflowListV2FilterStatusEnum { + 'pending_approval' = 'pending_approval', + + 'pending_action' = 'pending_action', + + 'in_progress' = 'in_progress', + + 'exporting' = 'exporting', + + 'rejected' = 'rejected', + + 'cancelled' = 'cancelled', + + 'failed' = 'failed', + + 'completed' = 'completed', + + 'unknown' = 'unknown' +} diff --git a/packages/sqle/src/locale/en-US/globalDashboard.ts b/packages/sqle/src/locale/en-US/globalDashboard.ts index 114f72ba5..6c4ee78a1 100644 --- a/packages/sqle/src/locale/en-US/globalDashboard.ts +++ b/packages/sqle/src/locale/en-US/globalDashboard.ts @@ -60,7 +60,8 @@ export default { data_export: 'Data Export Workflow' }, filter: { - workflowType: 'Workflow type' + workflowType: 'Workflow type', + status: 'Status' }, toolbar: { searchPlaceholder: 'Search by workflow title or ID' diff --git a/packages/sqle/src/locale/zh-CN/globalDashboard.ts b/packages/sqle/src/locale/zh-CN/globalDashboard.ts index 8af8c1c38..a2719faf5 100644 --- a/packages/sqle/src/locale/zh-CN/globalDashboard.ts +++ b/packages/sqle/src/locale/zh-CN/globalDashboard.ts @@ -55,7 +55,8 @@ export default { data_export: '数据导出工单' }, filter: { - workflowType: '工单类型' + workflowType: '工单类型', + status: '状态' }, toolbar: { searchPlaceholder: '搜索标题或工单ID' diff --git a/packages/sqle/src/page/GlobalDashboard/__tests__/__snapshots__/index.sqle.test.tsx.snap b/packages/sqle/src/page/GlobalDashboard/__tests__/__snapshots__/index.sqle.test.tsx.snap index 0a22a59e8..3c6053eed 100644 --- a/packages/sqle/src/page/GlobalDashboard/__tests__/__snapshots__/index.sqle.test.tsx.snap +++ b/packages/sqle/src/page/GlobalDashboard/__tests__/__snapshots__/index.sqle.test.tsx.snap @@ -460,9 +460,70 @@ exports[`GlobalDashboard should render workflow tab by default 1`] = `
>>>>>> 5153cce06 (feat(globalDashboard): add relevant fields and filter options) +======= + style="margin-right: 12px;" + > +
+
+ + + + + + + 状态 + + + 全部 + + + +
+
+
+
>>>>>> d251c3d23 (feat(global-dashboard): 工单面板新增"状态"下拉筛选) > ({ @@ -9,3 +12,42 @@ export const workflowTypeLabelDictionary = () => ({ 'globalDashboard.workflow.workflowTypeLabel.data_export' ) }); + +export const workflowFilterStatusOptions = () => [ + { + label: t('globalDashboard.workflow.statusLabel.pending_approval'), + value: GetGlobalWorkflowListV2FilterStatusEnum.pending_approval + }, + { + label: t('globalDashboard.workflow.statusLabel.pending_action'), + value: GetGlobalWorkflowListV2FilterStatusEnum.pending_action + }, + { + label: t('globalDashboard.workflow.statusLabel.in_progress'), + value: GetGlobalWorkflowListV2FilterStatusEnum.in_progress + }, + { + label: t('globalDashboard.workflow.statusLabel.exporting'), + value: GetGlobalWorkflowListV2FilterStatusEnum.exporting + }, + { + label: t('globalDashboard.workflow.statusLabel.rejected'), + value: GetGlobalWorkflowListV2FilterStatusEnum.rejected + }, + { + label: t('globalDashboard.workflow.statusLabel.cancelled'), + value: GetGlobalWorkflowListV2FilterStatusEnum.cancelled + }, + { + label: t('globalDashboard.workflow.statusLabel.failed'), + value: GetGlobalWorkflowListV2FilterStatusEnum.failed + }, + { + label: t('globalDashboard.workflow.statusLabel.completed'), + value: GetGlobalWorkflowListV2FilterStatusEnum.completed + }, + { + label: t('globalDashboard.workflow.statusLabel.unknown'), + value: GetGlobalWorkflowListV2FilterStatusEnum.unknown + } +]; diff --git a/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/index.tsx b/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/index.tsx index 6f4dca596..4c13e1384 100644 --- a/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/index.tsx +++ b/packages/sqle/src/page/GlobalDashboard/components/WorkflowPanel/index.tsx @@ -3,6 +3,7 @@ import { ActiontechTableWrapper, CustomAvatar, CustomSegmentedFilter, + CustomSelect, getErrorMessage, ROUTE_PATHS, TableToolbar, @@ -25,6 +26,7 @@ import { StatCardsStyleWrapper, StatCardItemStyleWrapper } from '../../style'; import { workflowPanelColumns } from './column'; import { GetGlobalWorkflowListV2FilterCardEnum, + GetGlobalWorkflowListV2FilterStatusEnum, GetGlobalWorkflowListV2WorkflowTypeEnum } from '@actiontech/shared/lib/api/sqle/service/GlobalDashboard/index.enum'; import { IGlobalWorkflowListItem } from '@actiontech/shared/lib/api/sqle/service/common'; @@ -325,6 +327,27 @@ const WorkflowPanel: React.FC = ({ ]} withAll /> + { + updateTableFilterInfo((prev) => { + const { filter_status: _omit, ...rest } = prev; + return val + ? { + ...rest, + filter_status: + val as GetGlobalWorkflowListV2FilterStatusEnum + } + : rest; + }); + }} + /> Date: Wed, 20 May 2026 16:01:13 +0800 Subject: [PATCH 6/6] test: update snapshot --- .../__snapshots__/index.sqle.test.tsx.snap | 70 +------------------ .../__snapshots__/index.test.tsx.snap | 2 +- .../components/WorkflowPanel/index.tsx | 23 ------ 3 files changed, 4 insertions(+), 91 deletions(-) diff --git a/packages/sqle/src/page/GlobalDashboard/__tests__/__snapshots__/index.sqle.test.tsx.snap b/packages/sqle/src/page/GlobalDashboard/__tests__/__snapshots__/index.sqle.test.tsx.snap index 3c6053eed..1eb678491 100644 --- a/packages/sqle/src/page/GlobalDashboard/__tests__/__snapshots__/index.sqle.test.tsx.snap +++ b/packages/sqle/src/page/GlobalDashboard/__tests__/__snapshots__/index.sqle.test.tsx.snap @@ -106,7 +106,7 @@ exports[`GlobalDashboard should render workflow tab by default 1`] = ` style="margin-right: 12px;" >
>>>>>> 5153cce06 (feat(globalDashboard): add relevant fields and filter options) -======= - style="margin-right: 12px;" - > -
-
- - - - - - - 状态 - - - 全部 - - - -
-
-
-
>>>>>> d251c3d23 (feat(global-dashboard): 工单面板新增"状态"下拉筛选) >