Started simplifying the Assignee capability editor, want to make it generic and data driven
This commit is contained in:
parent
5bcdf9a5ac
commit
4c8ff7378b
@ -1,244 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import UserPicker from "../../../../../components/pickers/UserPicker";
|
|
||||||
import {
|
|
||||||
GeneralIdRef,
|
|
||||||
MakeGeneralIdRef,
|
|
||||||
} from "../../../../../utils/GeneralIdRef";
|
|
||||||
import { TaskDefinition } from "../../services/WorkflowTemplateService";
|
|
||||||
import {
|
|
||||||
CapabilityEditorProps,
|
|
||||||
capabilityEditorRegistryEntry,
|
|
||||||
defaultsContext,
|
|
||||||
} from "../useCapabilityDefaults";
|
|
||||||
import Button, { ButtonType } from "../../../../../components/common/Button";
|
|
||||||
import ValidationErrorIcon from "../../../../../components/validationErrorIcon";
|
|
||||||
import ErrorBlock from "../../../../../components/common/ErrorBlock";
|
|
||||||
import RolePicker from "../../../../../components/pickers/RolePicker";
|
|
||||||
import { getCurrentUser } from "../../../../frame/services/authenticationService";
|
|
||||||
import RaciPicker from "../../../../../components/pickers/RaciPicker";
|
|
||||||
|
|
||||||
// ---------------------------
|
|
||||||
// Domain Interfaces
|
|
||||||
// ---------------------------
|
|
||||||
|
|
||||||
export interface IApprovalTaskAssignee {
|
|
||||||
role?: GeneralIdRef | null;
|
|
||||||
user?: GeneralIdRef | null;
|
|
||||||
raci: string; // Raci enum as string
|
|
||||||
allowNoVerdict: boolean;
|
|
||||||
bypassable: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IApprovalTaskAssigneesCapability {
|
|
||||||
assignees: IApprovalTaskAssignee[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------
|
|
||||||
// Editor Component
|
|
||||||
// ---------------------------
|
|
||||||
|
|
||||||
export const AssigneesOfIApprovalTaskAssigneeEditor: React.FC<
|
|
||||||
CapabilityEditorProps
|
|
||||||
> = (props) => {
|
|
||||||
const { task, onChange, fieldErrors } = props;
|
|
||||||
const assignees = (task.config.assignees ?? []) as IApprovalTaskAssignee[];
|
|
||||||
|
|
||||||
function updateAssignee(index: number, updated: IApprovalTaskAssignee) {
|
|
||||||
const clone = structuredClone(task);
|
|
||||||
const list = clone.config.assignees ?? [];
|
|
||||||
list[index] = updated;
|
|
||||||
clone.config.assignees = list;
|
|
||||||
onChange(clone);
|
|
||||||
}
|
|
||||||
|
|
||||||
function addAssignee() {
|
|
||||||
const clone = structuredClone(task);
|
|
||||||
const list = clone.config.assignees ?? [];
|
|
||||||
list.push({
|
|
||||||
role: null,
|
|
||||||
user: null,
|
|
||||||
raci: "Responsible",
|
|
||||||
allowNoVerdict: false,
|
|
||||||
bypassable: false,
|
|
||||||
});
|
|
||||||
clone.config.assignees = list;
|
|
||||||
onChange(clone);
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeAssignee(index: number) {
|
|
||||||
const clone = structuredClone(task);
|
|
||||||
const list = clone.config.assignees ?? [];
|
|
||||||
clone.config.assignees = list.filter((_, i) => i !== index);
|
|
||||||
onChange(clone);
|
|
||||||
}
|
|
||||||
|
|
||||||
const guid = task.config.guid as string;
|
|
||||||
|
|
||||||
const domain = MakeGeneralIdRef(getCurrentUser()?.domainid);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
Select assigness (you can select either a role or a user)
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th></th>
|
|
||||||
<th>Role</th>
|
|
||||||
<th>User</th>
|
|
||||||
<th>RACI</th>
|
|
||||||
<th>Allow No Verdict</th>
|
|
||||||
<th>Bypassable</th>
|
|
||||||
<th>
|
|
||||||
<Button onClick={addAssignee} buttonType={ButtonType.secondary}>
|
|
||||||
Add Assignee
|
|
||||||
</Button>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{assignees.map((assignee, index) => (
|
|
||||||
<tr key={index} className="align-top">
|
|
||||||
<td className="form-group">
|
|
||||||
<ValidationErrorIcon
|
|
||||||
visible={
|
|
||||||
!!fieldErrors?.[`${guid}.assignees[${index}].role`] ||
|
|
||||||
!!fieldErrors?.[`${guid}.assignees[${index}].user`] ||
|
|
||||||
!!fieldErrors?.[`${guid}.assignees[${index}].raci`]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<RolePicker
|
|
||||||
includeLabel={false}
|
|
||||||
name="role"
|
|
||||||
label="Role"
|
|
||||||
value={assignee.role}
|
|
||||||
domain={domain}
|
|
||||||
error={fieldErrors?.[`${guid}.assignees[${index}].role`]}
|
|
||||||
onChange={(name: string, val: GeneralIdRef | null) =>
|
|
||||||
updateAssignee(index, { ...assignee, role: val })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<UserPicker
|
|
||||||
includeLabel={false}
|
|
||||||
name="user"
|
|
||||||
label="User"
|
|
||||||
value={assignee.user}
|
|
||||||
domain={domain}
|
|
||||||
error={fieldErrors?.[`${guid}.assignees[${index}].user`]}
|
|
||||||
onChange={(name: string, val: GeneralIdRef | null) =>
|
|
||||||
updateAssignee(index, { ...assignee, user: val })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<RaciPicker
|
|
||||||
includeLabel={false}
|
|
||||||
name="raci"
|
|
||||||
label="RACI"
|
|
||||||
value={assignee.raci}
|
|
||||||
error={fieldErrors?.[`${guid}.assignees[${index}].raci`]}
|
|
||||||
onChange={(name: string, val: string) =>
|
|
||||||
updateAssignee(index, { ...assignee, raci: val })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>allowNoVerdict goes here.</td>
|
|
||||||
<td>Bypass goes here.</td>
|
|
||||||
<td className="form-group">
|
|
||||||
<Button
|
|
||||||
onClick={() => removeAssignee(index)}
|
|
||||||
buttonType={ButtonType.secondary}
|
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<ErrorBlock
|
|
||||||
error={Object.values(fieldErrors ?? {})
|
|
||||||
.filter((_, i) =>
|
|
||||||
Object.keys(fieldErrors ?? {}).some(
|
|
||||||
(key) => key === `${guid}.assignees`,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.join("; ")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------------------------
|
|
||||||
// Validation
|
|
||||||
// ---------------------------
|
|
||||||
|
|
||||||
const runValidation = (
|
|
||||||
task: TaskDefinition,
|
|
||||||
tasks: TaskDefinition[],
|
|
||||||
): Promise<Record<string, string>> => {
|
|
||||||
const errors: Record<string, string> = {};
|
|
||||||
const guid = task.config.guid as string;
|
|
||||||
const assignees = task.config.assignees as ITaskAssignee[] | undefined;
|
|
||||||
|
|
||||||
if (!assignees || assignees.length === 0) {
|
|
||||||
errors[`${guid}.assignees`] = "At least one assignee is required.";
|
|
||||||
return Promise.resolve(errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
assignees.forEach((a, i) => {
|
|
||||||
const noUserSelected = !a.user || a.user?.id === BigInt(0);
|
|
||||||
const noRoleSelected = !a.role || a.role?.id === BigInt(0);
|
|
||||||
|
|
||||||
if (!noUserSelected && !noRoleSelected) {
|
|
||||||
errors[`${guid}.assignees[${i}].user`] =
|
|
||||||
"Cannot select both a user and a role.";
|
|
||||||
errors[`${guid}.assignees[${i}].role`] =
|
|
||||||
"Cannot select both a user and a role.";
|
|
||||||
} else {
|
|
||||||
if (!(!noUserSelected || !noRoleSelected)) {
|
|
||||||
if (noUserSelected) {
|
|
||||||
errors[`${guid}.assignees[${i}].user`] = "A user must be selected.";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (noRoleSelected) {
|
|
||||||
errors[`${guid}.assignees[${i}].role`] = "A role must be selected.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!a.raci) {
|
|
||||||
errors[`${guid}.assignees[${i}].raci`] = "RACI is required.";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------------------------
|
|
||||||
// Defaults Assignment
|
|
||||||
// ---------------------------
|
|
||||||
|
|
||||||
export function defaultsAssignment(
|
|
||||||
task: TaskDefinition,
|
|
||||||
tasks: TaskDefinition[],
|
|
||||||
ctx: defaultsContext,
|
|
||||||
) {
|
|
||||||
task.config.assignees = [{ raci: "Responsible" } as ITaskAssignee];
|
|
||||||
//task.config.assignees = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------
|
|
||||||
// Registry Entry
|
|
||||||
// ---------------------------
|
|
||||||
|
|
||||||
export const assigneesOfIApprovalTaskAssigneeRegistryEntry: capabilityEditorRegistryEntry =
|
|
||||||
{
|
|
||||||
match: (cap) => cap === "IAssignees<ApprovalTaskAssignee>",
|
|
||||||
Editor: AssigneesOfIApprovalTaskAssigneeEditor,
|
|
||||||
DefaultsAssignment: defaultsAssignment,
|
|
||||||
ValidationRunner: runValidation,
|
|
||||||
};
|
|
||||||
@ -229,7 +229,7 @@ export function defaultsAssignment(
|
|||||||
|
|
||||||
export const assigneesOfITaskAssigneeRegistryEntry: capabilityEditorRegistryEntry =
|
export const assigneesOfITaskAssigneeRegistryEntry: capabilityEditorRegistryEntry =
|
||||||
{
|
{
|
||||||
match: (cap) => cap === "IAssignees<TaskAssignee>",
|
match: (cap) => cap.startsWith("IAssignees<"),
|
||||||
Editor: AssigneesOfITaskAssigneeEditor,
|
Editor: AssigneesOfITaskAssigneeEditor,
|
||||||
DefaultsAssignment: defaultsAssignment,
|
DefaultsAssignment: defaultsAssignment,
|
||||||
ValidationRunner: runValidation,
|
ValidationRunner: runValidation,
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import { outcomeOfApprovalVerdictRegistryEntry } from "./CapabilityEditors/Outco
|
|||||||
import { budgetEditorRegistryEntry } from "./CapabilityEditors/BudgetEditorRegistryEntry";
|
import { budgetEditorRegistryEntry } from "./CapabilityEditors/BudgetEditorRegistryEntry";
|
||||||
import { bypassableEditorRegistryEntry } from "./CapabilityEditors/BypassableEditor";
|
import { bypassableEditorRegistryEntry } from "./CapabilityEditors/BypassableEditor";
|
||||||
import { createStageEditorRegistryEntry } from "./CapabilityEditors/StageEditor";
|
import { createStageEditorRegistryEntry } from "./CapabilityEditors/StageEditor";
|
||||||
import { assigneesOfIApprovalTaskAssigneeRegistryEntry } from "./CapabilityEditors/AssigneesOfIApprovalTaskAssigneeEditor";
|
|
||||||
|
|
||||||
type CapabilityEditorRegistry = Array<capabilityEditorRegistryEntry>;
|
type CapabilityEditorRegistry = Array<capabilityEditorRegistryEntry>;
|
||||||
|
|
||||||
@ -15,7 +14,6 @@ export const capabilityEditorRegistry: CapabilityEditorRegistry = [
|
|||||||
tagsEditorRegistryEntry,
|
tagsEditorRegistryEntry,
|
||||||
budgetEditorRegistryEntry,
|
budgetEditorRegistryEntry,
|
||||||
assigneesOfITaskAssigneeRegistryEntry,
|
assigneesOfITaskAssigneeRegistryEntry,
|
||||||
assigneesOfIApprovalTaskAssigneeRegistryEntry,
|
|
||||||
outcomeOfApprovalVerdictRegistryEntry,
|
outcomeOfApprovalVerdictRegistryEntry,
|
||||||
// IFormTemplate: null, //ToDo implement this
|
// IFormTemplate: null, //ToDo implement this
|
||||||
bypassableEditorRegistryEntry,
|
bypassableEditorRegistryEntry,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user