Corrected the place where the task name is generated, it's now part of the capability editor rather than bespoke code.
This commit is contained in:
parent
f30120d448
commit
6f49add5f7
@ -1,19 +1,18 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import templateVersionsService, {
|
import { TaskMetadata } from "../services/WorkflowTemplateService";
|
||||||
TaskMetadata,
|
|
||||||
} from "../services/WorkflowTemplateService";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Namespaces } from "../../../../i18n/i18n";
|
import { Namespaces } from "../../../../i18n/i18n";
|
||||||
|
|
||||||
interface AddTaskButtonProps {
|
interface AddTaskButtonProps {
|
||||||
taskType: string;
|
allowedTasks: TaskMetadata[];
|
||||||
onAdd: (selectedType: TaskMetadata) => void;
|
onAdd: (selectedType: TaskMetadata) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AddTaskButton: React.FC<AddTaskButtonProps> = ({ taskType, onAdd }) => {
|
const AddTaskButton: React.FC<AddTaskButtonProps> = ({
|
||||||
|
allowedTasks,
|
||||||
|
onAdd,
|
||||||
|
}) => {
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
const [items, setItems] = React.useState<TaskMetadata[]>([]);
|
|
||||||
const [loading, setLoading] = React.useState(false);
|
|
||||||
|
|
||||||
const { t: tTaskType } = useTranslation(Namespaces.TaskTypes);
|
const { t: tTaskType } = useTranslation(Namespaces.TaskTypes);
|
||||||
const { t } = useTranslation(Namespaces.Common);
|
const { t } = useTranslation(Namespaces.Common);
|
||||||
@ -21,17 +20,6 @@ const AddTaskButton: React.FC<AddTaskButtonProps> = ({ taskType, onAdd }) => {
|
|||||||
const toggle = async () => {
|
const toggle = async () => {
|
||||||
const next = !open;
|
const next = !open;
|
||||||
setOpen(next);
|
setOpen(next);
|
||||||
|
|
||||||
// Fetch only when opening AND only once
|
|
||||||
if (next && items.length === 0) {
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const meta = await templateVersionsService.getTaskMetadata(taskType);
|
|
||||||
setItems(meta);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -42,10 +30,7 @@ const AddTaskButton: React.FC<AddTaskButtonProps> = ({ taskType, onAdd }) => {
|
|||||||
|
|
||||||
{open && (
|
{open && (
|
||||||
<div className="dropdown-menu show">
|
<div className="dropdown-menu show">
|
||||||
{loading && <div className="dropdown-item">{t("Loading")}</div>}
|
{allowedTasks.map((item) => (
|
||||||
|
|
||||||
{!loading &&
|
|
||||||
items.map((item) => (
|
|
||||||
<button
|
<button
|
||||||
key={item.taskType}
|
key={item.taskType}
|
||||||
className="dropdown-item"
|
className="dropdown-item"
|
||||||
|
|||||||
@ -1,12 +1,18 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { InputType } from "../../../../../components/common/Input";
|
import { InputType } from "../../../../../components/common/Input";
|
||||||
import { TaskDefinition } from "../../services/WorkflowTemplateService";
|
import {
|
||||||
|
TaskDefinition,
|
||||||
|
TaskMetadata,
|
||||||
|
} from "../../services/WorkflowTemplateService";
|
||||||
import { renderTaskField } from "../taskEditorHelpers";
|
import { renderTaskField } from "../taskEditorHelpers";
|
||||||
import { TaskValidationResult } from "../TasksEditor";
|
import { TaskValidationResult } from "../TasksEditor";
|
||||||
|
import { Namespaces } from "../../../../../i18n/i18n";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
interface TaskCoreEditorProps {
|
interface TaskCoreEditorProps {
|
||||||
task: TaskDefinition;
|
task: TaskDefinition;
|
||||||
allTasks: TaskDefinition[];
|
allTasks: TaskDefinition[];
|
||||||
|
allowedTasks: TaskMetadata[];
|
||||||
onChange: (updated: TaskDefinition) => void;
|
onChange: (updated: TaskDefinition) => void;
|
||||||
onValidate: (result: TaskValidationResult) => void;
|
onValidate: (result: TaskValidationResult) => void;
|
||||||
}
|
}
|
||||||
@ -14,22 +20,62 @@ interface TaskCoreEditorProps {
|
|||||||
export const TaskCoreEditor: React.FC<TaskCoreEditorProps> = ({
|
export const TaskCoreEditor: React.FC<TaskCoreEditorProps> = ({
|
||||||
task,
|
task,
|
||||||
allTasks,
|
allTasks,
|
||||||
|
allowedTasks,
|
||||||
onChange,
|
onChange,
|
||||||
onValidate,
|
onValidate,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t: tTaskType } = useTranslation(Namespaces.TaskTypes);
|
||||||
const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});
|
const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});
|
||||||
const prevErrorsRef = useRef<Record<string, string>>({});
|
const prevErrorsRef = useRef<Record<string, string>>({});
|
||||||
|
|
||||||
|
const formatNewTaskName = (
|
||||||
|
tasks: TaskDefinition<Record<string, unknown>>[],
|
||||||
|
) => {
|
||||||
|
const displayName = allowedTasks.find(
|
||||||
|
(t) => t.taskType === task.type,
|
||||||
|
)?.displayName;
|
||||||
|
|
||||||
|
return `${tTaskType(displayName!)} ${tasks.length + 1}`;
|
||||||
|
};
|
||||||
|
|
||||||
const runValidation = useCallback(() => {
|
const runValidation = useCallback(() => {
|
||||||
const errors: Record<string, string> = {};
|
const errors: Record<string, string> = {};
|
||||||
|
|
||||||
if (!task.config.description) {
|
//If the task doesn't have a name (can happen when adding a new task), generate a default one.
|
||||||
errors["description"] = "Description cannot be empty";
|
if (task.config.name === undefined) {
|
||||||
|
task.config.name = formatNewTaskName(allTasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!task.config.name || (task.config.name as string).trim() === "") {
|
||||||
|
errors["name"] = "Name cannot be empty";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task.config.name) {
|
||||||
|
// Name must be unique across all tasks
|
||||||
|
const duplicate = allTasks.find(
|
||||||
|
(t) =>
|
||||||
|
(t.config.guid as string) !== (task.config.guid as string) && // exclude self
|
||||||
|
(t.config.name as string).trim().toLowerCase() ===
|
||||||
|
(task.config.name as string).trim().toLowerCase(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (duplicate) {
|
||||||
|
errors["name"] = "Name must be unique.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const descriptionMaxLength = 5000;
|
||||||
|
if (
|
||||||
|
task.config.description &&
|
||||||
|
(task.config.description as string).length >= descriptionMaxLength
|
||||||
|
) {
|
||||||
|
errors["description"] =
|
||||||
|
`Description can be up to ${descriptionMaxLength} characters long.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isValid = Object.keys(errors).length === 0;
|
const isValid = Object.keys(errors).length === 0;
|
||||||
return { errors, isValid };
|
return { errors, isValid };
|
||||||
}, [task.config.description]);
|
}, [allTasks, task.config.description, task.config.guid, task.config.name]);
|
||||||
|
|
||||||
//Validate when task changes.
|
//Validate when task changes.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
interface TaskListProps {
|
interface TaskListProps {
|
||||||
tasks: TaskDefinition[];
|
tasks: TaskDefinition[];
|
||||||
validTasksList: Record<string, boolean>;
|
validTasksList: Record<string, boolean>;
|
||||||
taskType: string;
|
allowedTasks: TaskMetadata[];
|
||||||
onChange: (tasks: TaskDefinition[]) => void;
|
onChange: (tasks: TaskDefinition[]) => void;
|
||||||
selectedTask: TaskDefinition | null;
|
selectedTask: TaskDefinition | null;
|
||||||
onSelectTask: (task: TaskDefinition | null) => void;
|
onSelectTask: (task: TaskDefinition | null) => void;
|
||||||
@ -22,28 +22,16 @@ interface TaskListProps {
|
|||||||
const TaskList: React.FC<TaskListProps> = ({
|
const TaskList: React.FC<TaskListProps> = ({
|
||||||
tasks,
|
tasks,
|
||||||
validTasksList,
|
validTasksList,
|
||||||
taskType,
|
allowedTasks,
|
||||||
onChange,
|
onChange,
|
||||||
selectedTask,
|
selectedTask,
|
||||||
onSelectTask,
|
onSelectTask,
|
||||||
}) => {
|
}) => {
|
||||||
const { t: tTaskType } = useTranslation(Namespaces.TaskTypes);
|
|
||||||
|
|
||||||
const formatNewTaskName = (
|
|
||||||
displayName: string,
|
|
||||||
tasks: TaskDefinition<Record<string, unknown>>[],
|
|
||||||
) => {
|
|
||||||
return `${tTaskType(displayName)} ${tasks.length + 1}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddTask = (selectedType: TaskMetadata) => {
|
const handleAddTask = (selectedType: TaskMetadata) => {
|
||||||
const formattedName = formatNewTaskName(selectedType.displayName, tasks);
|
|
||||||
|
|
||||||
const newTask: TaskDefinition = {
|
const newTask: TaskDefinition = {
|
||||||
type: selectedType.taskType,
|
type: selectedType.taskType,
|
||||||
|
|
||||||
config: {
|
config: {
|
||||||
name: formattedName,
|
|
||||||
guid: crypto.randomUUID(),
|
guid: crypto.randomUUID(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -56,7 +44,7 @@ const TaskList: React.FC<TaskListProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<AddTaskButton taskType={taskType} onAdd={handleAddTask} />
|
<AddTaskButton allowedTasks={allowedTasks} onAdd={handleAddTask} />
|
||||||
|
|
||||||
<SelectableList
|
<SelectableList
|
||||||
items={tasks}
|
items={tasks}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export interface TaskValidationResult {
|
|||||||
interface TaskEditorProps {
|
interface TaskEditorProps {
|
||||||
task: TaskDefinition;
|
task: TaskDefinition;
|
||||||
allTasks: TaskDefinition[];
|
allTasks: TaskDefinition[];
|
||||||
|
allowedTasks: TaskMetadata[];
|
||||||
onChange: (updatedTask: TaskDefinition) => void;
|
onChange: (updatedTask: TaskDefinition) => void;
|
||||||
onValidate: (taskId: string, isValid: boolean) => void;
|
onValidate: (taskId: string, isValid: boolean) => void;
|
||||||
}
|
}
|
||||||
@ -17,6 +18,7 @@ interface TaskEditorProps {
|
|||||||
export const TaskEditor: React.FC<TaskEditorProps> = ({
|
export const TaskEditor: React.FC<TaskEditorProps> = ({
|
||||||
task,
|
task,
|
||||||
allTasks,
|
allTasks,
|
||||||
|
allowedTasks,
|
||||||
onChange,
|
onChange,
|
||||||
onValidate,
|
onValidate,
|
||||||
}) => {
|
}) => {
|
||||||
@ -43,6 +45,7 @@ export const TaskEditor: React.FC<TaskEditorProps> = ({
|
|||||||
<>
|
<>
|
||||||
<TaskCoreEditor
|
<TaskCoreEditor
|
||||||
task={task}
|
task={task}
|
||||||
|
allowedTasks={allowedTasks}
|
||||||
allTasks={allTasks}
|
allTasks={allTasks}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onValidate={(result) => onCapabilityValidate("core", result)}
|
onValidate={(result) => onCapabilityValidate("core", result)}
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import {
|
import templateVersionsService, {
|
||||||
CreateWorkflowTemplateVersion,
|
CreateWorkflowTemplateVersion,
|
||||||
TaskDefinition,
|
TaskDefinition,
|
||||||
|
TaskMetadata,
|
||||||
} from "../services/WorkflowTemplateService";
|
} from "../services/WorkflowTemplateService";
|
||||||
import TaskList from "./TaskList";
|
import TaskList from "./TaskList";
|
||||||
import { TaskEditor } from "./TasksEditor";
|
import { TaskEditor } from "./TasksEditor";
|
||||||
@ -25,6 +26,16 @@ const TasksTab: React.FC<TasksTabProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const tasks = data.tasks;
|
const tasks = data.tasks;
|
||||||
const [selectedTask, setSelectedTask] = useState<TaskDefinition | null>(null);
|
const [selectedTask, setSelectedTask] = useState<TaskDefinition | null>(null);
|
||||||
|
const [allowedTasks, setAllowedTasks] = React.useState<TaskMetadata[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchTaskMetadata = async () => {
|
||||||
|
const meta = await templateVersionsService.getTaskMetadata("GeneralTask");
|
||||||
|
setAllowedTasks(meta);
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchTaskMetadata();
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tasks.length === 0) {
|
if (tasks.length === 0) {
|
||||||
@ -91,7 +102,7 @@ const TasksTab: React.FC<TasksTabProps> = ({
|
|||||||
<TaskList
|
<TaskList
|
||||||
tasks={tasks}
|
tasks={tasks}
|
||||||
validTasksList={taskValidation}
|
validTasksList={taskValidation}
|
||||||
taskType="GeneralTask"
|
allowedTasks={allowedTasks}
|
||||||
onChange={handleTasksChange}
|
onChange={handleTasksChange}
|
||||||
selectedTask={selectedTask}
|
selectedTask={selectedTask}
|
||||||
onSelectTask={setSelectedTask}
|
onSelectTask={setSelectedTask}
|
||||||
@ -99,6 +110,7 @@ const TasksTab: React.FC<TasksTabProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
{selectedTask && (
|
{selectedTask && (
|
||||||
<TaskEditor
|
<TaskEditor
|
||||||
|
allowedTasks={allowedTasks}
|
||||||
task={selectedTask}
|
task={selectedTask}
|
||||||
allTasks={tasks}
|
allTasks={tasks}
|
||||||
onChange={(updatedTask) => {
|
onChange={(updatedTask) => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user