Refactored the code so that the capability editors have even less duplicated code
This commit is contained in:
parent
e7a084c301
commit
86ebdc8b72
@ -1,25 +1,15 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useCallback } from "react";
|
||||
import { InputType } from "../../../../../components/common/Input";
|
||||
import {
|
||||
TaskDefinition,
|
||||
TaskMetadata,
|
||||
} from "../../services/WorkflowTemplateService";
|
||||
import { TaskDefinition } from "../../services/WorkflowTemplateService";
|
||||
import { renderTaskField } from "../taskEditorHelpers";
|
||||
import { TaskValidationResult } from "../TasksEditor";
|
||||
import { CapabilityEditorProps } from "../TasksEditor";
|
||||
import { Namespaces } from "../../../../../i18n/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getAllDescendants } from "../workflowGraphUtils";
|
||||
import { useValidation } from "../useValidation";
|
||||
import { useCapabilityDefaults } from "../useCapabilityDefaults";
|
||||
|
||||
interface TaskCoreEditorProps {
|
||||
task: TaskDefinition;
|
||||
allTasks: TaskDefinition[];
|
||||
allowedTasks: TaskMetadata[];
|
||||
onChange: (updated: TaskDefinition) => void;
|
||||
onValidate: (result: TaskValidationResult) => void;
|
||||
shouldAssignDefaults: boolean;
|
||||
}
|
||||
|
||||
export const TaskCoreEditor: React.FC<TaskCoreEditorProps> = ({
|
||||
export const TaskCoreEditor: React.FC<CapabilityEditorProps> = ({
|
||||
task,
|
||||
allTasks,
|
||||
allowedTasks,
|
||||
@ -28,8 +18,6 @@ export const TaskCoreEditor: React.FC<TaskCoreEditorProps> = ({
|
||||
shouldAssignDefaults,
|
||||
}) => {
|
||||
const { t: tTaskType } = useTranslation(Namespaces.TaskTypes);
|
||||
const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});
|
||||
const prevErrorsRef = useRef<Record<string, string>>({});
|
||||
|
||||
// Generate a unique default name
|
||||
const nameExists = (tasks: TaskDefinition[], candidate: string): boolean => {
|
||||
@ -49,7 +37,7 @@ export const TaskCoreEditor: React.FC<TaskCoreEditorProps> = ({
|
||||
if (!displayName) return "New Task";
|
||||
|
||||
const base = `${tTaskType(displayName)} `;
|
||||
let index = 1;
|
||||
let index = 0;
|
||||
|
||||
while (nameExists(tasks, `${base}${index}`)) {
|
||||
index++;
|
||||
@ -60,49 +48,33 @@ export const TaskCoreEditor: React.FC<TaskCoreEditorProps> = ({
|
||||
[allowedTasks, task.type, tTaskType],
|
||||
);
|
||||
|
||||
const assignDefaults = (
|
||||
newConfig: Record<string, unknown>,
|
||||
task: TaskDefinition,
|
||||
allTasks: TaskDefinition[],
|
||||
allowedTasks: TaskMetadata[],
|
||||
formatNewTaskName: (tasks: TaskDefinition[]) => string,
|
||||
) => {
|
||||
const displayName = allowedTasks.find(
|
||||
(t) => t.taskType === task.type,
|
||||
)?.displayName;
|
||||
const assignDefaults = useCallback(
|
||||
(newConfig: Record<string, unknown>) => {
|
||||
const displayName = allowedTasks.find(
|
||||
(t) => t.taskType === task.type,
|
||||
)?.displayName;
|
||||
|
||||
// Assign default name
|
||||
if (displayName) {
|
||||
newConfig.name = formatNewTaskName(allTasks);
|
||||
}
|
||||
// Assign default name
|
||||
if (displayName) {
|
||||
newConfig.name = formatNewTaskName(allTasks);
|
||||
}
|
||||
|
||||
// Assign default predecessor (the task immediately before this one)
|
||||
const index = allTasks.findIndex((t) => t.config.guid === task.config.guid);
|
||||
// Assign default predecessor (the task immediately before this one)
|
||||
const index = allTasks.findIndex(
|
||||
(t) => t.config.guid === task.config.guid,
|
||||
);
|
||||
|
||||
if (index > 0) {
|
||||
const previousTask = allTasks[index - 1];
|
||||
newConfig.predecessors = [previousTask.config.guid as string];
|
||||
}
|
||||
};
|
||||
if (index > 0) {
|
||||
const previousTask = allTasks[index - 1];
|
||||
newConfig.predecessors = [previousTask.config.guid as string];
|
||||
}
|
||||
},
|
||||
[formatNewTaskName, task, allTasks, allowedTasks],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!shouldAssignDefaults) return;
|
||||
|
||||
const newConfig = { ...task.config };
|
||||
|
||||
assignDefaults(newConfig, task, allTasks, allowedTasks, formatNewTaskName);
|
||||
|
||||
onChange({
|
||||
...task,
|
||||
config: newConfig,
|
||||
});
|
||||
}, [
|
||||
shouldAssignDefaults,
|
||||
task,
|
||||
useCapabilityDefaults(shouldAssignDefaults, task, onChange, assignDefaults, [
|
||||
allTasks,
|
||||
allowedTasks,
|
||||
formatNewTaskName,
|
||||
onChange,
|
||||
]);
|
||||
|
||||
const runValidation = useCallback(() => {
|
||||
@ -156,30 +128,7 @@ export const TaskCoreEditor: React.FC<TaskCoreEditorProps> = ({
|
||||
return { errors, isValid };
|
||||
}, [task, allTasks]);
|
||||
|
||||
const prevInitialValidationRef = useRef<{
|
||||
isValid: boolean;
|
||||
errors: Record<string, string>;
|
||||
} | null>(null);
|
||||
|
||||
// Validate when task changes (new task selected / created)
|
||||
useEffect(() => {
|
||||
const result = runValidation();
|
||||
|
||||
const prev = prevInitialValidationRef.current;
|
||||
|
||||
const changed =
|
||||
!prev ||
|
||||
prev.isValid !== result.isValid ||
|
||||
Object.keys(prev.errors).length !== Object.keys(result.errors).length ||
|
||||
Object.entries(result.errors).some(([k, v]) => prev.errors[k] !== v);
|
||||
|
||||
if (changed) {
|
||||
setFieldErrors(result.errors);
|
||||
prevErrorsRef.current = result.errors;
|
||||
onValidate(result);
|
||||
prevInitialValidationRef.current = result;
|
||||
}
|
||||
}, [
|
||||
const { fieldErrors } = useValidation(runValidation, onValidate, [
|
||||
task.config.guid,
|
||||
task.config.name,
|
||||
task.config.description,
|
||||
@ -211,7 +160,7 @@ export const TaskCoreEditor: React.FC<TaskCoreEditorProps> = ({
|
||||
"name",
|
||||
"Name",
|
||||
InputType.text,
|
||||
fieldErrors["name"],
|
||||
fieldErrors,
|
||||
)}
|
||||
|
||||
{renderTaskField(
|
||||
@ -220,7 +169,7 @@ export const TaskCoreEditor: React.FC<TaskCoreEditorProps> = ({
|
||||
"description",
|
||||
"Description",
|
||||
InputType.textarea,
|
||||
fieldErrors["description"],
|
||||
fieldErrors,
|
||||
)}
|
||||
|
||||
{renderTaskField(
|
||||
@ -229,7 +178,7 @@ export const TaskCoreEditor: React.FC<TaskCoreEditorProps> = ({
|
||||
"predecessors",
|
||||
"Predecessors",
|
||||
InputType.multiselect,
|
||||
fieldErrors["predecessors"],
|
||||
fieldErrors,
|
||||
"",
|
||||
0,
|
||||
{
|
||||
|
||||
@ -5,6 +5,15 @@ import {
|
||||
} from "../services/WorkflowTemplateService";
|
||||
import { TaskCoreEditor } from "./CapabilityEditors/TaskCoreEditor";
|
||||
|
||||
export interface CapabilityEditorProps {
|
||||
task: TaskDefinition;
|
||||
allTasks: TaskDefinition[];
|
||||
allowedTasks: TaskMetadata[];
|
||||
onChange: (updated: TaskDefinition) => void;
|
||||
onValidate: (result: TaskValidationResult) => void;
|
||||
shouldAssignDefaults: boolean;
|
||||
}
|
||||
|
||||
export interface TaskValidationResult {
|
||||
isValid: boolean;
|
||||
errors: Record<string, string>;
|
||||
|
||||
@ -7,7 +7,7 @@ export const renderTaskField = (
|
||||
field: string,
|
||||
label: string,
|
||||
type: InputType,
|
||||
error?: string,
|
||||
errors: Record<string, string>,
|
||||
placeholder?: string,
|
||||
maxLength?: number,
|
||||
extraProps?: {
|
||||
@ -34,7 +34,7 @@ export const renderTaskField = (
|
||||
field,
|
||||
label,
|
||||
(task.config as any)[field],
|
||||
error,
|
||||
errors[field],
|
||||
type,
|
||||
handleChange,
|
||||
false,
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
import { useEffect } from "react";
|
||||
import { TaskDefinition } from "../services/WorkflowTemplateService";
|
||||
|
||||
export function useCapabilityDefaults(
|
||||
shouldAssignDefaults: boolean,
|
||||
task: TaskDefinition,
|
||||
onChange: (updated: TaskDefinition) => void,
|
||||
assignDefaults: (newConfig: Record<string, unknown>) => void,
|
||||
deps: unknown[],
|
||||
) {
|
||||
useEffect(() => {
|
||||
if (!shouldAssignDefaults) return;
|
||||
|
||||
const newConfig = { ...task.config };
|
||||
|
||||
assignDefaults(newConfig);
|
||||
|
||||
onChange({
|
||||
...task,
|
||||
config: newConfig,
|
||||
});
|
||||
}, [shouldAssignDefaults, task, onChange, assignDefaults, ...deps]);
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { TaskValidationResult } from "./TasksEditor";
|
||||
|
||||
export function useValidation(
|
||||
runValidation: () => TaskValidationResult,
|
||||
onValidate: (result: TaskValidationResult) => void,
|
||||
deps: unknown[],
|
||||
) {
|
||||
const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});
|
||||
const prevErrorsRef = useRef<Record<string, string>>({});
|
||||
const prevInitialValidationRef = useRef<TaskValidationResult | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const result = runValidation();
|
||||
const prev = prevInitialValidationRef.current;
|
||||
|
||||
const changed =
|
||||
!prev ||
|
||||
prev.isValid !== result.isValid ||
|
||||
Object.keys(prev.errors).length !== Object.keys(result.errors).length ||
|
||||
Object.entries(result.errors).some(([k, v]) => prev.errors[k] !== v);
|
||||
|
||||
if (changed) {
|
||||
setFieldErrors(result.errors);
|
||||
prevErrorsRef.current = result.errors;
|
||||
onValidate(result);
|
||||
prevInitialValidationRef.current = result;
|
||||
}
|
||||
}, deps);
|
||||
|
||||
return { fieldErrors };
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user