Now able to create a workflow template

This commit is contained in:
Colin Dawson 2026-02-26 20:29:56 +00:00
parent 22c80e82f2
commit 574f254dca
7 changed files with 54 additions and 61 deletions

View File

@ -38,7 +38,7 @@ const WorkflowTemplateDetails: React.FC<{ editMode: boolean }> = ({
const form = useFormWithGuard({ const form = useFormWithGuard({
loaded: false, loaded: false,
data: { data: {
name: "", workflowName: "",
domainId: {} as GeneralIdRef, domainId: {} as GeneralIdRef,
activityNameTemplate: "", activityNameTemplate: "",
description: "", description: "",
@ -56,7 +56,10 @@ const WorkflowTemplateDetails: React.FC<{ editMode: boolean }> = ({
// Joi schema (same pattern as original GeneralTab) // Joi schema (same pattern as original GeneralTab)
// ----------------------------- // -----------------------------
form.schema = { form.schema = {
name: Joi.string().required().max(450).label(t("WorkflowTemplateName")), workflowName: Joi.string()
.required()
.max(450)
.label(t("WorkflowTemplateName")),
activityNameTemplate: Joi.string() activityNameTemplate: Joi.string()
.required() .required()
.max(450) .max(450)
@ -80,7 +83,7 @@ const WorkflowTemplateDetails: React.FC<{ editMode: boolean }> = ({
); );
if (loadedData) { if (loadedData) {
newData.name = loadedData.name ?? ""; newData.workflowName = loadedData.workflowName ?? "";
} }
} catch (ex: any) { } catch (ex: any) {
form.handleGeneralError(ex); form.handleGeneralError(ex);
@ -112,7 +115,9 @@ const WorkflowTemplateDetails: React.FC<{ editMode: boolean }> = ({
//await templateVersionsService.putTemplateVersion({ name }); //await templateVersionsService.putTemplateVersion({ name });
toast.info(t("WorkflowTemplateEdited")); toast.info(t("WorkflowTemplateEdited"));
} else { } else {
//await templateVersionsService.postTemplateVersion({ name }); await templateVersionsService.postTemplateVersion(
data as CreateWorkflowTemplateVersion,
);
toast.info(t("WorkflowTemplateAdded")); toast.info(t("WorkflowTemplateAdded"));
} }
@ -146,7 +151,7 @@ const WorkflowTemplateDetails: React.FC<{ editMode: boolean }> = ({
const generalTabValid = !( const generalTabValid = !(
errors["domainId"] || errors["domainId"] ||
errors["name"] || errors["workflowName"] ||
errors["activityNameTemplate"] || errors["activityNameTemplate"] ||
errors["description"] errors["description"]
); );

View File

@ -16,10 +16,6 @@ import ErrorBlock from "../../../../../components/common/ErrorBlock";
import RolePicker from "../../../../../components/pickers/RolePicker"; import RolePicker from "../../../../../components/pickers/RolePicker";
import { getCurrentUser } from "../../../../frame/services/authenticationService"; import { getCurrentUser } from "../../../../frame/services/authenticationService";
import RaciPicker from "../../../../../components/pickers/RaciPicker"; import RaciPicker from "../../../../../components/pickers/RaciPicker";
import VerdictPicker from "../../../../../components/pickers/VerdictPicker";
import { renderInput } from "../../../../../components/common/formHelpers";
import { InputType } from "../../../../../components/common/Input";
import { error } from "console";
// --------------------------- // ---------------------------
// Domain Interfaces // Domain Interfaces
@ -27,7 +23,7 @@ import { error } from "console";
export interface IApprovalTaskAssignee { export interface IApprovalTaskAssignee {
role?: GeneralIdRef | null; role?: GeneralIdRef | null;
contact?: GeneralIdRef | null; user?: GeneralIdRef | null;
raci: string; // Raci enum as string raci: string; // Raci enum as string
allowNoVerdict: boolean; allowNoVerdict: boolean;
bypassable: boolean; bypassable: boolean;
@ -60,7 +56,7 @@ export const AssigneesOfIApprovalTaskAssigneeEditor: React.FC<
const list = clone.config.assignees ?? []; const list = clone.config.assignees ?? [];
list.push({ list.push({
role: null, role: null,
contact: null, user: null,
raci: "Responsible", raci: "Responsible",
allowNoVerdict: false, allowNoVerdict: false,
bypassable: false, bypassable: false,
@ -82,13 +78,13 @@ export const AssigneesOfIApprovalTaskAssigneeEditor: React.FC<
return ( return (
<div> <div>
Select assigness (you can select either a role or a contact) Select assigness (you can select either a role or a user)
<table> <table>
<thead> <thead>
<tr> <tr>
<th></th> <th></th>
<th>Role</th> <th>Role</th>
<th>Contact</th> <th>User</th>
<th>RACI</th> <th>RACI</th>
<th>Allow No Verdict</th> <th>Allow No Verdict</th>
<th>Bypassable</th> <th>Bypassable</th>
@ -106,7 +102,7 @@ export const AssigneesOfIApprovalTaskAssigneeEditor: React.FC<
<ValidationErrorIcon <ValidationErrorIcon
visible={ visible={
!!fieldErrors?.[`${guid}.assignees[${index}].role`] || !!fieldErrors?.[`${guid}.assignees[${index}].role`] ||
!!fieldErrors?.[`${guid}.assignees[${index}].contact`] || !!fieldErrors?.[`${guid}.assignees[${index}].user`] ||
!!fieldErrors?.[`${guid}.assignees[${index}].raci`] !!fieldErrors?.[`${guid}.assignees[${index}].raci`]
} }
/> />
@ -127,13 +123,13 @@ export const AssigneesOfIApprovalTaskAssigneeEditor: React.FC<
<td> <td>
<UserPicker <UserPicker
includeLabel={false} includeLabel={false}
name="contact" name="user"
label="Contact" label="User"
value={assignee.contact} value={assignee.user}
domain={domain} domain={domain}
error={fieldErrors?.[`${guid}.assignees[${index}].contact`]} error={fieldErrors?.[`${guid}.assignees[${index}].user`]}
onChange={(name: string, val: GeneralIdRef | null) => onChange={(name: string, val: GeneralIdRef | null) =>
updateAssignee(index, { ...assignee, contact: val }) updateAssignee(index, { ...assignee, user: val })
} }
/> />
</td> </td>
@ -194,19 +190,18 @@ const runValidation = (
} }
assignees.forEach((a, i) => { assignees.forEach((a, i) => {
const noContactSelected = !a.contact || a.contact?.id === BigInt(0); const noUserSelected = !a.user || a.user?.id === BigInt(0);
const noRoleSelected = !a.role || a.role?.id === BigInt(0); const noRoleSelected = !a.role || a.role?.id === BigInt(0);
if (!noContactSelected && !noRoleSelected) { if (!noUserSelected && !noRoleSelected) {
errors[`${guid}.assignees[${i}].contact`] = errors[`${guid}.assignees[${i}].user`] =
"Cannot select both a contact and a role."; "Cannot select both a user and a role.";
errors[`${guid}.assignees[${i}].role`] = errors[`${guid}.assignees[${i}].role`] =
"Cannot select both a contact and a role."; "Cannot select both a user and a role.";
} else { } else {
if (!(!noContactSelected || !noRoleSelected)) { if (!(!noUserSelected || !noRoleSelected)) {
if (noContactSelected) { if (noUserSelected) {
errors[`${guid}.assignees[${i}].contact`] = errors[`${guid}.assignees[${i}].user`] = "A user must be selected.";
"A contact must be selected.";
} }
if (noRoleSelected) { if (noRoleSelected) {

View File

@ -23,7 +23,7 @@ import RaciPicker from "../../../../../components/pickers/RaciPicker";
export interface ITaskAssignee { export interface ITaskAssignee {
role?: GeneralIdRef | null; role?: GeneralIdRef | null;
contact?: GeneralIdRef | null; user?: GeneralIdRef | null;
raci: string; // Raci enum as string raci: string; // Raci enum as string
} }
@ -54,7 +54,7 @@ export const AssigneesOfITaskAssigneeEditor: React.FC<CapabilityEditorProps> = (
const list = clone.config.assignees ?? []; const list = clone.config.assignees ?? [];
list.push({ list.push({
role: null, role: null,
contact: null, user: null,
raci: "Responsible", raci: "Responsible",
}); });
clone.config.assignees = list; clone.config.assignees = list;
@ -74,13 +74,13 @@ export const AssigneesOfITaskAssigneeEditor: React.FC<CapabilityEditorProps> = (
return ( return (
<div> <div>
Select assigness (you can select either a role or a contact) Select assigness (you can select either a role or a USER)
<table> <table>
<thead> <thead>
<tr> <tr>
<th></th> <th></th>
<th>Role</th> <th>Role</th>
<th>Contact</th> <th>User</th>
<th>RACI</th> <th>RACI</th>
<th> <th>
<Button onClick={addAssignee} buttonType={ButtonType.secondary}> <Button onClick={addAssignee} buttonType={ButtonType.secondary}>
@ -96,7 +96,7 @@ export const AssigneesOfITaskAssigneeEditor: React.FC<CapabilityEditorProps> = (
<ValidationErrorIcon <ValidationErrorIcon
visible={ visible={
!!fieldErrors?.[`${guid}.assignees[${index}].role`] || !!fieldErrors?.[`${guid}.assignees[${index}].role`] ||
!!fieldErrors?.[`${guid}.assignees[${index}].contact`] || !!fieldErrors?.[`${guid}.assignees[${index}].user`] ||
!!fieldErrors?.[`${guid}.assignees[${index}].raci`] !!fieldErrors?.[`${guid}.assignees[${index}].raci`]
} }
/> />
@ -117,13 +117,13 @@ export const AssigneesOfITaskAssigneeEditor: React.FC<CapabilityEditorProps> = (
<td> <td>
<UserPicker <UserPicker
includeLabel={false} includeLabel={false}
name="contact" name="user"
label="Contact" label="User"
value={assignee.contact} value={assignee.user}
domain={domain} domain={domain}
error={fieldErrors?.[`${guid}.assignees[${index}].contact`]} error={fieldErrors?.[`${guid}.assignees[${index}].user`]}
onChange={(name: string, val: GeneralIdRef | null) => onChange={(name: string, val: GeneralIdRef | null) =>
updateAssignee(index, { ...assignee, contact: val }) updateAssignee(index, { ...assignee, user: val })
} }
/> />
</td> </td>
@ -182,19 +182,18 @@ const runValidation = (
} }
assignees.forEach((a, i) => { assignees.forEach((a, i) => {
const noContactSelected = !a.contact || a.contact?.id === BigInt(0); const noUserSelected = !a.user || a.user?.id === BigInt(0);
const noRoleSelected = !a.role || a.role?.id === BigInt(0); const noRoleSelected = !a.role || a.role?.id === BigInt(0);
if (!noContactSelected && !noRoleSelected) { if (!noUserSelected && !noRoleSelected) {
errors[`${guid}.assignees[${i}].contact`] = errors[`${guid}.assignees[${i}].user`] =
"Cannot select both a contact and a role."; "Cannot select both a user and a role.";
errors[`${guid}.assignees[${i}].role`] = errors[`${guid}.assignees[${i}].role`] =
"Cannot select both a contact and a role."; "Cannot select both a user and a role.";
} else { } else {
if (!(!noContactSelected || !noRoleSelected)) { if (!(!noUserSelected || !noRoleSelected)) {
if (noContactSelected) { if (noUserSelected) {
errors[`${guid}.assignees[${i}].contact`] = errors[`${guid}.assignees[${i}].user`] = "A user must be selected.";
"A contact must be selected.";
} }
if (noRoleSelected) { if (noRoleSelected) {

View File

@ -43,7 +43,7 @@ const GeneralTab: React.FC<GeneralTabProps> = ({
)} )}
{renderInput( {renderInput(
"name", "workflowName",
t("WorkflowTemplateName"), t("WorkflowTemplateName"),
data, data,
errors, errors,

View File

@ -17,10 +17,10 @@ const WorkflowTemplateManagerTable: React.FC<
const columns: Column<ReadWorkflowTemplate>[] = useMemo( const columns: Column<ReadWorkflowTemplate>[] = useMemo(
() => [ () => [
{ {
key: "name", key: "workflowName",
label: t("Name"), label: t("Name"),
order: "asc", order: "asc",
link: canViewSite ? "/site/{0}" : undefined, link: canViewSite ? "/workflowTemplates/{0}" : undefined,
}, },
], ],
[t, canViewSite], [t, canViewSite],

View File

@ -15,8 +15,8 @@ export const capabilityEditorRegistry: Record<
ITask: taskCoreEditorRegistryEntry, ITask: taskCoreEditorRegistryEntry,
ITags: tagsEditorRegistryEntry, ITags: tagsEditorRegistryEntry,
IBudget: budgetEditorRegistryEntry, IBudget: budgetEditorRegistryEntry,
"IAssignees<ITaskAssignee>": assigneesOfITaskAssigneeRegistryEntry, "IAssignees<TaskAssignee>": assigneesOfITaskAssigneeRegistryEntry,
"IAssignees<IApprovalTaskAssignee>": "IAssignees<ApprovalTaskAssignee>":
assigneesOfIApprovalTaskAssigneeRegistryEntry, assigneesOfIApprovalTaskAssigneeRegistryEntry,
"IOutcome<ApprovalVerdict>": outcomeOfApprovalVerdictRegistryEntry, "IOutcome<ApprovalVerdict>": outcomeOfApprovalVerdictRegistryEntry,
// IFormTemplate: null, //ToDo implement this // IFormTemplate: null, //ToDo implement this

View File

@ -33,7 +33,7 @@ export interface TaskDefinition<TConfig = Record<string, unknown>> {
} }
export interface CreateWorkflowTemplateVersion extends FormData { export interface CreateWorkflowTemplateVersion extends FormData {
name: string; workflowName: string;
domainId: GeneralIdRef; domainId: GeneralIdRef;
activityNameTemplate: string; activityNameTemplate: string;
description: string; description: string;
@ -105,15 +105,9 @@ export async function getTemplateVersion(id?: bigint, guid?: string) {
} }
export async function postTemplateVersion( export async function postTemplateVersion(
name: string, data: CreateWorkflowTemplateVersion,
address: string,
status: string,
): Promise<any> { ): Promise<any> {
return await httpService.post(apiEndpoint + "/templateVersion", { return await httpService.post(apiEndpoint + "/templateVersion", data);
name,
address,
status,
});
} }
export async function putTemplateVersion( export async function putTemplateVersion(