This is an automated email from the ASF dual-hosted git repository.

imbajin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hugegraph-toolchain.git

commit 3414047e64c155655f8efbd07cec5be425b6b755
Author: dark <[email protected]>
AuthorDate: Fri Aug 14 12:39:02 2026 +0800

    fix(task): improve import feedback and layout
---
 .../src/pages/Task/components/ViewLayer.test.js    | 107 ++++++++++++++++++++-
 .../src/pages/Task/components/index.module.scss    |   9 ++
 hugegraph-hubble/hubble-fe/src/pages/Task/index.js |  56 +++++++++--
 3 files changed, 164 insertions(+), 8 deletions(-)

diff --git 
a/hugegraph-hubble/hubble-fe/src/pages/Task/components/ViewLayer.test.js 
b/hugegraph-hubble/hubble-fe/src/pages/Task/components/ViewLayer.test.js
index 37ac59e81..392d48e61 100644
--- a/hugegraph-hubble/hubble-fe/src/pages/Task/components/ViewLayer.test.js
+++ b/hugegraph-hubble/hubble-fe/src/pages/Task/components/ViewLayer.test.js
@@ -20,6 +20,8 @@ import {render, screen, waitFor} from 
'@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import ViewLayer from './ViewLayer';
 import * as api from '../../../api';
+import enPages from '../../../i18n/resources/en-US/modules/pages.json';
+import zhPages from '../../../i18n/resources/zh-CN/modules/pages.json';
 
 jest.mock('react-i18next', () => ({
     useTranslation: () => ({t: key => key}),
@@ -29,7 +31,9 @@ jest.mock('../../../api', () => ({
     manage: {getTaskDetail: jest.fn()},
 }));
 
-jest.mock('react-json-view', () => ({src}) => <div>{src.name}</div>);
+jest.mock('react-json-view', () => ({src}) => (
+    <pre data-testid='raw-task-json'>{JSON.stringify(src)}</pre>
+));
 
 const deferred = () => {
     let resolve;
@@ -72,3 +76,104 @@ test('clears stale task data and offers retry for the 
current task', async () =>
     await userEvent.click(screen.getByRole('button', {name: 
'task.view.retry'}));
     await waitFor(() => expect(screen.getByText('Task 
B')).toBeInTheDocument());
 });
+
+test('shows a user summary while keeping raw backend fields collapsed', async 
() => {
+    api.manage.getTaskDetail.mockResolvedValue({
+        status: 200,
+        data: {
+            id: 7,
+            conn_id: 3,
+            job_name: 'movie_import',
+            graphspace: 'DEFAULT',
+            graph: 'hugegraph',
+            job_status: 'SUCCESS',
+            job_size: '12 MB',
+            job_duration: '5 s',
+            create_time: '2026-08-10 09:00:00',
+            update_time: '2026-08-10 09:00:05',
+        },
+    });
+
+    render(<ViewLayer visible task_id='7' onCancel={jest.fn()} />);
+
+    expect(await screen.findByText('movie_import')).toBeVisible();
+    expect(screen.getByText('DEFAULT')).toBeVisible();
+    expect(screen.getByText('hugegraph')).toBeVisible();
+    expect(screen.getByText('task.status.success')).toBeVisible();
+    expect(screen.getByText('12 MB')).toBeVisible();
+    expect(screen.getByText('5 s')).toBeVisible();
+    expect(screen.getByText('2026-08-10 09:00:00')).toBeVisible();
+    expect(screen.getByText('2026-08-10 09:00:05')).toBeVisible();
+
+    const technicalDetails = screen.getByRole('button', {
+        name: /task\.view\.technical_details/,
+    });
+    expect(technicalDetails).toHaveAttribute('aria-expanded', 'false');
+    expect(screen.queryByTestId('raw-task-json')).not.toBeInTheDocument();
+    expect(screen.queryByText(/SUCCESS/)).not.toBeInTheDocument();
+
+    await userEvent.click(technicalDetails);
+
+    expect(technicalDetails).toHaveAttribute('aria-expanded', 'true');
+    expect(screen.getByTestId('raw-task-json')).toBeVisible();
+    expect(screen.getByText(/SUCCESS/)).toBeVisible();
+    expect(screen.getByText(/conn_id/)).toBeVisible();
+});
+
+test('labels missing summary values instead of leaving blank fields', async () 
=> {
+    api.manage.getTaskDetail.mockResolvedValue({
+        status: 200,
+        data: {
+            job_name: null,
+            graphspace: '',
+            graph: undefined,
+            job_status: null,
+            job_size: null,
+            job_duration: '',
+            create_time: null,
+            update_time: undefined,
+        },
+    });
+
+    render(<ViewLayer visible task_id='8' onCancel={jest.fn()} />);
+
+    expect(await 
screen.findAllByText('task.view.unavailable_value')).toHaveLength(7);
+    expect(screen.getByText('task.status.unknown')).toBeVisible();
+});
+
+test('shows a valid intermediate JobStatus as running instead of unknown', 
async () => {
+    api.manage.getTaskDetail.mockResolvedValue({
+        status: 200,
+        data: {
+            job_name: 'active_import',
+            graphspace: 'DEFAULT',
+            graph: 'hugegraph',
+            job_status: 'LOADING',
+        },
+    });
+
+    render(<ViewLayer visible task_id='9' onCancel={jest.fn()} />);
+
+    expect(await screen.findByText('task.status.running')).toBeVisible();
+    expect(screen.queryByText('task.status.unknown')).not.toBeInTheDocument();
+    expect(screen.queryByText('LOADING')).not.toBeInTheDocument();
+});
+
+test('ships accurate Task Information copy in both languages', () => {
+    expect(zhPages.task.action.config).toBe('查看任务信息');
+    expect(enPages.task.action.config).toBe('View task information');
+    expect(zhPages.task.view).toMatchObject({
+        title: '任务信息',
+        status: '状态',
+        data_size: '数据量',
+        duration: '持续时间',
+        technical_details: '技术详情',
+    });
+    expect(enPages.task.view).toMatchObject({
+        title: 'Task Information',
+        status: 'Status',
+        data_size: 'Data Size',
+        duration: 'Duration',
+        technical_details: 'Technical Details',
+    });
+});
diff --git 
a/hugegraph-hubble/hubble-fe/src/pages/Task/components/index.module.scss 
b/hugegraph-hubble/hubble-fe/src/pages/Task/components/index.module.scss
index b65f2a368..cc87bf76a 100644
--- a/hugegraph-hubble/hubble-fe/src/pages/Task/components/index.module.scss
+++ b/hugegraph-hubble/hubble-fe/src/pages/Task/components/index.module.scss
@@ -59,3 +59,12 @@
     }
     
 }
+
+.technical_details {
+    margin-top: 12px;
+}
+
+.raw_json {
+    max-height: 280px;
+    overflow: auto;
+}
diff --git a/hugegraph-hubble/hubble-fe/src/pages/Task/index.js 
b/hugegraph-hubble/hubble-fe/src/pages/Task/index.js
index db43435cc..a6a4b16d1 100644
--- a/hugegraph-hubble/hubble-fe/src/pages/Task/index.js
+++ b/hugegraph-hubble/hubble-fe/src/pages/Task/index.js
@@ -48,14 +48,26 @@ import ViewLayer from './components/ViewLayer';
 import TopStatistic from './components/TopStatistic';
 import {useNavigate, Link} from 'react-router-dom';
 import * as api from '../../api';
-import {StatusField} from '../../components/Status';
 import {sourceType, syncType} from './config';
 import TableHeader from '../../components/TableHeader';
 import DataPreparationNav from '../../components/DataPreparationNav';
 import {readWorkbenchGraphContext} from '../../utils/workbenchGraphContext';
+import {TaskStatus} from './status';
 
 const {Text} = Typography;
 
+const TASK_COLUMN_WIDTHS = {
+    name: 150,
+    source: 80,
+    graphspace: 100,
+    graph: 100,
+    created: 145,
+    creator: 70,
+    status: 80,
+    sync: 90,
+    actions: 190,
+};
+
 const DetailTip = ({row}) => {
     const {t} = useTranslation();
     const {job_summary} = row;
@@ -347,6 +359,7 @@ const Task = () => {
     }, [t]);
     const loadHlmDemo = useCallback(() => loadDemo('hlm'), [loadDemo]);
     const loadLoaderDemo = useCallback(() => loadDemo('loader'), [loadDemo]);
+    const loadRankDemo = useCallback(() => loadDemo('rank'), [loadDemo]);
     const chooseDemoGraph = useCallback(() => navigate('/graphspace'), 
[navigate]);
 
     const handleHideEditLayer = useCallback(() => setEditLayer(false), []);
@@ -354,12 +367,14 @@ const Task = () => {
     const handleHideViewLayer = useCallback(() => setViewLayer(false), []);
 
     const rowKey = useCallback(record => record.task_id, []);
+    const hasCreator = data.some(row => Boolean(row?.creator));
 
     const columns = [
         {
             title: t('task.col.name'),
             dataIndex: 'task_name',
-            width: 220,
+            className: style.no_wrap,
+            width: TASK_COLUMN_WIDTHS.name,
             ellipsis: true,
             render: task_name => (
                 <Text
@@ -373,6 +388,9 @@ const Task = () => {
         {
             title: t('task.col.source_type'),
             dataIndex: 'ingestion_mapping',
+            className: style.no_wrap,
+            width: TASK_COLUMN_WIDTHS.source,
+            ellipsis: true,
             render: val => {
                 const {structs} = val ?? {};
                 if (!structs?.length) {
@@ -386,31 +404,47 @@ const Task = () => {
         {
             title: t('task.col.target_space'),
             dataIndex: 'ingestion_option',
+            className: style.no_wrap,
+            width: TASK_COLUMN_WIDTHS.graphspace,
+            ellipsis: true,
             render: val => val?.graphspace ?? '-',
         },
         {
             title: t('task.col.target_graph'),
             dataIndex: 'ingestion_option',
+            className: style.no_wrap,
+            width: TASK_COLUMN_WIDTHS.graph,
+            ellipsis: true,
             render: val => val?.graph ?? '-',
         },
         {
             title: t('task.col.create_time'),
             dataIndex: 'create_time',
+            className: style.no_wrap,
+            width: TASK_COLUMN_WIDTHS.created,
+            ellipsis: true,
         },
-        {
+        ...(hasCreator ? [{
             title: t('task.col.creator'),
             dataIndex: 'creator',
-        },
+            className: style.no_wrap,
+            width: TASK_COLUMN_WIDTHS.creator,
+            ellipsis: true,
+        }] : []),
         {
             title: t('task.col.status'),
             dataIndex: 'last_metrics',
+            className: style.no_wrap,
             align: 'center',
-            width: 120,
-            render: val => <StatusField status={val} />,
+            width: TASK_COLUMN_WIDTHS.status,
+            render: val => <TaskStatus status={val} />,
         },
         {
             title: t('task.col.sync_type'),
             dataIndex: 'task_schedule_type',
+            className: style.no_wrap,
+            width: TASK_COLUMN_WIDTHS.sync,
+            ellipsis: true,
             render: val => {
                 return syncTypes.find(item => item.value === val)?.label ?? 
val;
             },
@@ -418,7 +452,7 @@ const Task = () => {
         {
             title: t('graphspace.col.operation'),
             align: 'center',
-            width: 160,
+            width: TASK_COLUMN_WIDTHS.actions,
             render: row => {
                 return (
                     <TaskActions
@@ -581,6 +615,13 @@ const Task = () => {
                             >
                                 {t('graph.menu.load_loader_sample')}
                             </Button>
+                            <Button
+                                loading={demoLoading === 'rank'}
+                                disabled={Boolean(demoLoading)}
+                                onClick={loadRankDemo}
+                            >
+                                {t('graph.menu.load_rank_sample')}
+                            </Button>
                         </Space>
                     ) : (
                         <Button onClick={chooseDemoGraph}>
@@ -619,6 +660,7 @@ const Task = () => {
                         </Button>
                     </TableHeader>
                     <Table
+                        className={style.task_table}
                         columns={columns}
                         rowKey={rowKey}
                         scroll={{x: 'max-content'}}

Reply via email to