Skip to content

Commit 6513450

Browse files
authored
support edit&retry task (#394)
* ✨ support retry task * ✨ support edit task * ✨ support edit task
1 parent 1408632 commit 6513450

File tree

9 files changed

+432
-61
lines changed

9 files changed

+432
-61
lines changed

frontend/src/i18n/locales/en/common.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,27 @@
8282
"actions": {
8383
"start": "Start",
8484
"stop": "Stop",
85+
"retry": "Retry",
86+
"edit": "Edit",
8587
"executionRecords": "Execution Records",
8688
"delete": "Delete"
8789
},
8890
"messages": {
8991
"startSuccess": "Task start request sent",
9092
"stopSuccess": "Task stop request sent",
93+
"retrySuccess": "Task retry request sent",
94+
"updateSuccess": "Task updated successfully",
95+
"updateFailed": "Failed to update task",
9196
"deleteSuccess": "Task deleted",
9297
"deleteConfirm": "Are you sure you want to delete this task? This action cannot be undone.",
9398
"deleteConfirmMessage": "Are you sure you want to delete task \"{{taskName}}\"? This action cannot be undone.",
9499
"confirmDelete": "Delete",
95100
"cancel": "Cancel"
101+
},
102+
"edit": {
103+
"title": "Edit Task",
104+
"taskInfo": "Task Information",
105+
"resetHint": "After saving, the task status will be reset to PENDING and can be re-executed"
96106
}
97107
},
98108
"templateManagement": {
@@ -165,9 +175,11 @@
165175
},
166176
"createTask": {
167177
"title": "Create Collection Task",
178+
"editTitle": "Edit Collection Task",
168179
"back": "Back",
169180
"cancel": "Cancel",
170181
"submit": "Create Task",
182+
"updateButton": "Update Task",
171183
"actions": {
172184
"formatJson": "Format JSON"
173185
},

frontend/src/i18n/locales/zh/common.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,27 @@
8282
"actions": {
8383
"start": "启动",
8484
"stop": "停止",
85+
"retry": "重试",
86+
"edit": "编辑",
8587
"executionRecords": "执行记录",
8688
"delete": "删除"
8789
},
8890
"messages": {
8991
"startSuccess": "任务启动请求已发送",
9092
"stopSuccess": "任务停止请求已发送",
93+
"retrySuccess": "任务重试请求已发送",
94+
"updateSuccess": "任务更新成功",
95+
"updateFailed": "任务更新失败",
9196
"deleteSuccess": "任务已删除",
9297
"deleteConfirm": "确定要删除该任务吗?此操作不可撤销。",
9398
"deleteConfirmMessage": "确定要删除任务「{{taskName}}」吗?删除后将无法恢复。",
9499
"confirmDelete": "删除",
95100
"cancel": "取消"
101+
},
102+
"edit": {
103+
"title": "编辑任务",
104+
"taskInfo": "任务信息",
105+
"resetHint": "保存后任务状态将重置为待执行,可以重新执行"
96106
}
97107
},
98108
"templateManagement": {
@@ -165,9 +175,11 @@
165175
},
166176
"createTask": {
167177
"title": "创建归集任务",
178+
"editTitle": "编辑归集任务",
168179
"back": "返回",
169180
"cancel": "取消",
170181
"submit": "创建任务",
182+
"updateButton": "更新任务",
171183
"actions": {
172184
"formatJson": "格式化JSON"
173185
},

frontend/src/pages/DataCollection/Create/CreateTask.tsx

Lines changed: 168 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { useEffect, useState } from "react";
22
import { Input, Button, Radio, Form, App, Select, InputNumber } from "antd";
3-
import { Link, useNavigate } from "react-router";
3+
import { Link, useNavigate, useSearchParams } from "react-router";
44
import { ArrowLeft } from "lucide-react";
5-
import { createTaskUsingPost, queryDataXTemplatesUsingGet } from "../collection.apis";
5+
import { createTaskUsingPost, updateTaskUsingPut, queryDataXTemplatesUsingGet, queryTasksUsingGet } from "../collection.apis";
66
import SimpleCronScheduler from "@/pages/DataCollection/Create/SimpleCronScheduler";
77
import { getSyncModeMap } from "../collection.const";
88
import { SyncMode } from "../collection.model";
@@ -39,11 +39,17 @@ type TemplateFieldDef = {
3939

4040
export default function CollectionTaskCreate() {
4141
const navigate = useNavigate();
42+
const [searchParams] = useSearchParams();
4243
const [form] = Form.useForm();
4344
const { message } = App.useApp();
4445
const { t } = useTranslation();
4546
const syncModeOptions = Object.values(getSyncModeMap(t));
4647

48+
// 编辑模式
49+
const taskId = searchParams.get("taskId");
50+
const isEditMode = !!taskId;
51+
const [editLoading, setEditLoading] = useState(false);
52+
4753
const [templates, setTemplates] = useState<CollectionTemplate[]>([]);
4854
const [templatesLoading, setTemplatesLoading] = useState(false);
4955
const [selectedTemplateId, setSelectedTemplateId] = useState<string | undefined>(undefined);
@@ -65,8 +71,52 @@ export default function CollectionTaskCreate() {
6571
cronExpression: "0 0 * * *",
6672
});
6773

74+
// 解析 cron 表达式
75+
const parseCronExpression = (cronExpr: string) => {
76+
const parts = cronExpr.trim().split(/\s+/);
77+
if (parts.length !== 5) {
78+
// 无效的 cron 表达式,返回默认值
79+
return {
80+
type: "daily" as const,
81+
time: "00:00",
82+
cronExpression: cronExpr,
83+
};
84+
}
85+
86+
const [minute, hour, day, month, weekday] = parts;
87+
const formattedHour = hour.padStart(2, "0");
88+
const formattedMinute = minute.padStart(2, "0");
89+
const time = `${formattedHour}:${formattedMinute}`;
90+
91+
// 判断类型:monthly (指定日期), weekly (指定星期), daily (都是 *)
92+
if (day !== "*" && month === "*") {
93+
// monthly: 例如 "0 9 1 * *" 表示每月1号9点
94+
return {
95+
type: "monthly" as const,
96+
time,
97+
monthDay: parseInt(day, 10),
98+
cronExpression: cronExpr,
99+
};
100+
} else if (weekday !== "*" && day === "*") {
101+
// weekly: 例如 "0 9 * * 1" 表示每周一9点
102+
return {
103+
type: "weekly" as const,
104+
time,
105+
weekDay: parseInt(weekday, 10),
106+
cronExpression: cronExpr,
107+
};
108+
} else {
109+
// daily: 例如 "0 9 * * *" 表示每天9点
110+
return {
111+
type: "daily" as const,
112+
time,
113+
cronExpression: cronExpr,
114+
};
115+
}
116+
};
117+
68118
useEffect(() => {
69-
const run = async () => {
119+
const loadTemplates = async () => {
70120
setTemplatesLoading(true);
71121
try {
72122
const resp: any = await queryDataXTemplatesUsingGet({ page: 1, size: 1000 });
@@ -78,8 +128,56 @@ export default function CollectionTaskCreate() {
78128
setTemplatesLoading(false);
79129
}
80130
};
81-
run()
82-
}, []);
131+
132+
const loadTask = async () => {
133+
if (!taskId) return;
134+
setEditLoading(true);
135+
try {
136+
const resp: any = await queryTasksUsingGet({ page: 1, size: 1 });
137+
const task = resp?.data?.content?.find((t: any) => t.id === taskId);
138+
if (task) {
139+
// 设置表单值
140+
setSelectedTemplateId(task.templateId);
141+
form.setFieldsValue({
142+
name: task.name,
143+
description: task.description,
144+
syncMode: task.syncMode,
145+
scheduleExpression: task.scheduleExpression || "",
146+
timeoutSeconds: task.timeoutSeconds || 3600,
147+
templateId: task.templateId,
148+
config: task.config || { parameter: {}, reader: {}, writer: {} },
149+
});
150+
setNewTask({
151+
name: task.name,
152+
description: task.description,
153+
syncMode: task.syncMode,
154+
scheduleExpression: task.scheduleExpression || "",
155+
timeoutSeconds: task.timeoutSeconds || 3600,
156+
templateId: task.templateId,
157+
config: task.config || { parameter: {}, reader: {}, writer: {} },
158+
});
159+
// 解析 cron 表达式
160+
if (task.scheduleExpression) {
161+
const parsedSchedule = parseCronExpression(task.scheduleExpression);
162+
setScheduleExpression(parsedSchedule);
163+
}
164+
} else {
165+
message.error(t("dataCollection.taskManagement.messages.updateFailed"));
166+
navigate("/data/collection");
167+
}
168+
} catch (e) {
169+
message.error(t("dataCollection.taskManagement.messages.updateFailed"));
170+
navigate("/data/collection");
171+
} finally {
172+
setEditLoading(false);
173+
}
174+
};
175+
176+
loadTemplates();
177+
if (isEditMode) {
178+
loadTask();
179+
}
180+
}, [taskId]);
83181

84182
const parseJsonObjectInput = (value: any) => {
85183
if (value === undefined || value === null) return value;
@@ -195,10 +293,30 @@ export default function CollectionTaskCreate() {
195293
),
196294
};
197295
}
198-
await createTaskUsingPost(payload);
199-
message.success(t("dataCollection.createTask.messages.createSuccess"));
296+
297+
if (isEditMode) {
298+
// 编辑模式:只更新允许的字段
299+
const updateData: any = {
300+
description: payload.description,
301+
timeoutSeconds: payload.timeoutSeconds,
302+
config: payload.config,
303+
};
304+
if (payload.syncMode === SyncMode.SCHEDULED && payload.scheduleExpression) {
305+
updateData.scheduleExpression = payload.scheduleExpression;
306+
}
307+
await updateTaskUsingPut(taskId!, updateData);
308+
message.success(t("dataCollection.taskManagement.messages.updateSuccess"));
309+
} else {
310+
// 创建模式
311+
await createTaskUsingPost(payload);
312+
message.success(t("dataCollection.createTask.messages.createSuccess"));
313+
}
200314
navigate("/data/collection");
201315
} catch (error) {
316+
if (error.errorFields) {
317+
// 表单验证错误,不显示消息
318+
return;
319+
}
202320
message.error(
203321
t("dataCollection.createTask.messages.errorWithDetail", {
204322
message: error?.data?.message ?? "",
@@ -412,22 +530,30 @@ export default function CollectionTaskCreate() {
412530

413531
return (
414532
<div className="h-full flex flex-col">
415-
<div className="flex items-center justify-between mb-2">
416-
<div className="flex items-center">
417-
<Link to="/data/collection">
418-
<Button type="text">
419-
<ArrowLeft className="w-4 h-4 mr-1" />
420-
</Button>
421-
</Link>
422-
<h1 className="text-xl font-bold bg-clip-text">
423-
{t("dataCollection.createTask.title")}
424-
</h1>
533+
{editLoading ? (
534+
<div className="flex-1 flex items-center justify-center">
535+
<div className="text-gray-500">{t("common.loading")}</div>
425536
</div>
426-
</div>
537+
) : (
538+
<>
539+
<div className="flex items-center justify-between mb-2">
540+
<div className="flex items-center">
541+
<Link to="/data/collection">
542+
<Button type="text">
543+
<ArrowLeft className="w-4 h-4 mr-1" />
544+
</Button>
545+
</Link>
546+
<h1 className="text-xl font-bold bg-clip-text">
547+
{isEditMode
548+
? t("dataCollection.createTask.editTitle")
549+
: t("dataCollection.createTask.title")}
550+
</h1>
551+
</div>
552+
</div>
427553

428-
<div className="flex-overflow-auto border-card">
429-
<div className="flex-1 overflow-auto p-4">
430-
<Form
554+
<div className="flex-overflow-auto border-card">
555+
<div className="flex-1 overflow-auto p-4">
556+
<Form
431557
form={form}
432558
layout="vertical"
433559
className="[&_.ant-form-item]:mb-3 [&_.ant-form-item-label]:pb-1"
@@ -447,7 +573,10 @@ export default function CollectionTaskCreate() {
447573
name="name"
448574
rules={[{ required: true, message: t("dataCollection.createTask.basicInfo.nameRequired") }]}
449575
>
450-
<Input placeholder={t("dataCollection.createTask.basicInfo.namePlaceholder")} />
576+
<Input
577+
placeholder={t("dataCollection.createTask.basicInfo.namePlaceholder")}
578+
disabled={isEditMode}
579+
/>
451580
</Form.Item>
452581

453582
<Form.Item
@@ -487,6 +616,7 @@ export default function CollectionTaskCreate() {
487616
<Radio.Group
488617
value={newTask.syncMode}
489618
options={syncModeOptions}
619+
disabled={isEditMode}
490620
onChange={(e) => {
491621
const value = e.target.value;
492622
setNewTask({
@@ -532,6 +662,7 @@ export default function CollectionTaskCreate() {
532662
<Select
533663
placeholder={t("dataCollection.createTask.templateConfig.selectTemplatePlaceholder")}
534664
loading={templatesLoading}
665+
disabled={isEditMode}
535666
onChange={(templateId) => {
536667
setSelectedTemplateId(templateId);
537668
form.setFieldsValue({
@@ -607,17 +738,21 @@ export default function CollectionTaskCreate() {
607738
) : null}
608739
</>
609740
) : null}
610-
</Form>
611-
</div>
612-
<div className="flex gap-2 justify-end border-top p-4">
613-
<Button onClick={() => navigate("/data/collection")}>
614-
{t("dataCollection.createTask.cancel")}
615-
</Button>
616-
<Button type="primary" onClick={handleSubmit}>
617-
{t("dataCollection.createTask.submit")}
618-
</Button>
619-
</div>
620-
</div>
741+
</Form>
742+
</div>
743+
<div className="flex gap-2 justify-end border-top p-4">
744+
<Button onClick={() => navigate("/data/collection")}>
745+
{t("dataCollection.createTask.cancel")}
746+
</Button>
747+
<Button type="primary" onClick={handleSubmit}>
748+
{isEditMode
749+
? t("dataCollection.createTask.updateButton")
750+
: t("dataCollection.createTask.submit")}
751+
</Button>
752+
</div>
753+
</div>
754+
</>
755+
)}
621756
</div>
622757
);
623758
}

0 commit comments

Comments
 (0)