Workong on making the outcome editor completely data driven
This commit is contained in:
parent
ecb306c698
commit
f3798c6988
13
public/locales/en/enumValues.json
Normal file
13
public/locales/en/enumValues.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"ApprovalVerdict": {
|
||||||
|
"Approved": "Approved",
|
||||||
|
"ApprovedWithComments": "Approved with Comments",
|
||||||
|
"None": "None",
|
||||||
|
"Pending": "Pending",
|
||||||
|
"Rejected": "Rejected",
|
||||||
|
"Reviewed": "Reviewed"
|
||||||
|
},
|
||||||
|
"DefaultOutcome": {
|
||||||
|
"Complete": "Complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"Approved": "Approved",
|
|
||||||
"ApprovedWithComments": "Approved with Comments",
|
|
||||||
"None": "None",
|
|
||||||
"Pending": "Pending",
|
|
||||||
"Rejected": "Rejected",
|
|
||||||
"Reviewed": "Reviewed"
|
|
||||||
}
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
import React, { useEffect, useState, useCallback } from "react";
|
|
||||||
import Select from "../common/Select";
|
|
||||||
import Option from "../common/option";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { Namespaces } from "../../i18n/i18n";
|
|
||||||
|
|
||||||
interface VerdictPickerProps {
|
|
||||||
name: string;
|
|
||||||
label: string;
|
|
||||||
error?: string;
|
|
||||||
value: string;
|
|
||||||
onChange?: (name: string, value: string) => void;
|
|
||||||
includeLabel?: boolean;
|
|
||||||
}
|
|
||||||
export default function VerdictPicker({
|
|
||||||
name,
|
|
||||||
label,
|
|
||||||
error,
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
includeLabel = true,
|
|
||||||
}: VerdictPickerProps) {
|
|
||||||
const [options, setOptions] = useState<Option[] | undefined>(undefined);
|
|
||||||
const { t } = useTranslation(Namespaces.Verdict);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
async function load() {
|
|
||||||
const opts: Option[] = [
|
|
||||||
{ _id: "None", name: t("None") },
|
|
||||||
{ _id: "Pending", name: t("Pending") },
|
|
||||||
{ _id: "ApprovedWithComments", name: t("ApprovedWithComments") },
|
|
||||||
{ _id: "Approved", name: t("Approved") },
|
|
||||||
{ _id: "Rejected", name: t("Rejected") },
|
|
||||||
{ _id: "Reviewed", name: t("Reviewed") },
|
|
||||||
];
|
|
||||||
|
|
||||||
setOptions(opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
load();
|
|
||||||
}, [t]);
|
|
||||||
|
|
||||||
const handleChange = useCallback(
|
|
||||||
(e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
||||||
const input = e.currentTarget;
|
|
||||||
|
|
||||||
if (onChange) onChange(input.name, input.value);
|
|
||||||
},
|
|
||||||
[onChange],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Select
|
|
||||||
name={name}
|
|
||||||
label={label}
|
|
||||||
error={error}
|
|
||||||
value={value}
|
|
||||||
options={options}
|
|
||||||
includeBlankFirstEntry={false}
|
|
||||||
onChange={handleChange}
|
|
||||||
includeLabel={includeLabel}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -14,7 +14,7 @@ export const Namespaces = {
|
|||||||
Priority: "priority",
|
Priority: "priority",
|
||||||
Raci: "raci",
|
Raci: "raci",
|
||||||
TaskTypes: "taskTypes",
|
TaskTypes: "taskTypes",
|
||||||
Verdict: "verdict",
|
enumValues: "enumValues",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type Namespace = (typeof Namespaces)[keyof typeof Namespaces];
|
export type Namespace = (typeof Namespaces)[keyof typeof Namespaces];
|
||||||
|
|||||||
@ -237,6 +237,7 @@ export function defaultsAssignment(
|
|||||||
|
|
||||||
export const assigneesOfIApprovalTaskAssigneeRegistryEntry: capabilityEditorRegistryEntry =
|
export const assigneesOfIApprovalTaskAssigneeRegistryEntry: capabilityEditorRegistryEntry =
|
||||||
{
|
{
|
||||||
|
match: (cap) => cap === "IAssignees<ApprovalTaskAssignee>",
|
||||||
Editor: AssigneesOfIApprovalTaskAssigneeEditor,
|
Editor: AssigneesOfIApprovalTaskAssigneeEditor,
|
||||||
DefaultsAssignment: defaultsAssignment,
|
DefaultsAssignment: defaultsAssignment,
|
||||||
ValidationRunner: runValidation,
|
ValidationRunner: runValidation,
|
||||||
|
|||||||
@ -229,6 +229,7 @@ export function defaultsAssignment(
|
|||||||
|
|
||||||
export const assigneesOfITaskAssigneeRegistryEntry: capabilityEditorRegistryEntry =
|
export const assigneesOfITaskAssigneeRegistryEntry: capabilityEditorRegistryEntry =
|
||||||
{
|
{
|
||||||
|
match: (cap) => cap === "IAssignees<TaskAssignee>",
|
||||||
Editor: AssigneesOfITaskAssigneeEditor,
|
Editor: AssigneesOfITaskAssigneeEditor,
|
||||||
DefaultsAssignment: defaultsAssignment,
|
DefaultsAssignment: defaultsAssignment,
|
||||||
ValidationRunner: runValidation,
|
ValidationRunner: runValidation,
|
||||||
|
|||||||
@ -88,6 +88,7 @@ export function defaultsAssignment(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const budgetEditorRegistryEntry: capabilityEditorRegistryEntry = {
|
export const budgetEditorRegistryEntry: capabilityEditorRegistryEntry = {
|
||||||
|
match: (cap) => cap === "IBudget",
|
||||||
Editor: BudgetEditor,
|
Editor: BudgetEditor,
|
||||||
DefaultsAssignment: defaultsAssignment,
|
DefaultsAssignment: defaultsAssignment,
|
||||||
ValidationRunner: runValidation,
|
ValidationRunner: runValidation,
|
||||||
|
|||||||
@ -42,6 +42,7 @@ export function defaultsAssignment(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const bypassableEditorRegistryEntry: capabilityEditorRegistryEntry = {
|
export const bypassableEditorRegistryEntry: capabilityEditorRegistryEntry = {
|
||||||
|
match: (cap) => cap === "IBypassable",
|
||||||
Editor: BypassableEditor,
|
Editor: BypassableEditor,
|
||||||
DefaultsAssignment: defaultsAssignment,
|
DefaultsAssignment: defaultsAssignment,
|
||||||
ValidationRunner: runValidation,
|
ValidationRunner: runValidation,
|
||||||
|
|||||||
@ -1,10 +1,7 @@
|
|||||||
import Button, { ButtonType } from "../../../../../components/common/Button";
|
import Button, { ButtonType } from "../../../../../components/common/Button";
|
||||||
import ErrorBlock from "../../../../../components/common/ErrorBlock";
|
import ErrorBlock from "../../../../../components/common/ErrorBlock";
|
||||||
import { InputType } from "../../../../../components/common/Input";
|
|
||||||
import VerdictPicker from "../../../../../components/pickers/VerdictPicker";
|
|
||||||
import ValidationErrorIcon from "../../../../../components/validationErrorIcon";
|
import ValidationErrorIcon from "../../../../../components/validationErrorIcon";
|
||||||
import { TaskDefinition } from "../../services/WorkflowTemplateService";
|
import { TaskDefinition } from "../../services/WorkflowTemplateService";
|
||||||
import { renderTaskField } from "../taskEditorHelpers";
|
|
||||||
import TaskPicker from "../TaskPicker";
|
import TaskPicker from "../TaskPicker";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -12,20 +9,33 @@ import {
|
|||||||
capabilityEditorRegistryEntry,
|
capabilityEditorRegistryEntry,
|
||||||
defaultsContext,
|
defaultsContext,
|
||||||
} from "../useCapabilityDefaults";
|
} from "../useCapabilityDefaults";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Namespaces } from "../../../../../i18n/i18n";
|
||||||
|
import Select from "../../../../../components/common/Select";
|
||||||
|
import Option from "../../../../../components/common/option";
|
||||||
|
|
||||||
export interface IOutcomeAction {
|
export interface IOutcomeAction {
|
||||||
verdict: string;
|
outcome: string;
|
||||||
task: string | null;
|
task: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const outcomeEditor: React.FC<CapabilityEditorProps> = (props) => {
|
export const outcomeEditor: React.FC<CapabilityEditorProps> = (props) => {
|
||||||
const { task, tasks, onChange, fieldErrors } = props;
|
const { task, tasks, onChange, fieldErrors, taskMetadata } = props;
|
||||||
|
|
||||||
|
const { t } = useTranslation(Namespaces.Common);
|
||||||
|
const { t: tEnum } = useTranslation(Namespaces.enumValues);
|
||||||
|
|
||||||
|
const getOutcomeId = (value: string) => value.split(".").pop() ?? value;
|
||||||
|
const outcomeOptions: Option[] = taskMetadata.outcomes.map((outcome) => ({
|
||||||
|
_id: getOutcomeId(outcome),
|
||||||
|
name: tEnum(outcome),
|
||||||
|
}));
|
||||||
|
|
||||||
function addOutcome() {
|
function addOutcome() {
|
||||||
const clone = structuredClone(task);
|
const clone = structuredClone(task);
|
||||||
const list = (clone.config.outcomeActions ?? []) as IOutcomeAction[];
|
const list = (clone.config.outcomeActions ?? []) as IOutcomeAction[];
|
||||||
list.push({
|
list.push({
|
||||||
verdict: "None",
|
outcome: outcomeOptions[0]._id as string, // default to first outcome
|
||||||
task: null,
|
task: null,
|
||||||
});
|
});
|
||||||
clone.config.outcomeActions = list;
|
clone.config.outcomeActions = list;
|
||||||
@ -43,7 +53,7 @@ export const outcomeEditor: React.FC<CapabilityEditorProps> = (props) => {
|
|||||||
|
|
||||||
function removeOutcome(index: number) {
|
function removeOutcome(index: number) {
|
||||||
const clone = structuredClone(task);
|
const clone = structuredClone(task);
|
||||||
const list = clone.config.outcomeActions ?? [];
|
const list = (clone.config.outcomeActions ?? []) as IOutcomeAction[];
|
||||||
clone.config.outcomeActions = list.filter((_, i) => i !== index);
|
clone.config.outcomeActions = list.filter((_, i) => i !== index);
|
||||||
onChange(clone);
|
onChange(clone);
|
||||||
}
|
}
|
||||||
@ -60,7 +70,7 @@ export const outcomeEditor: React.FC<CapabilityEditorProps> = (props) => {
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th>Verdict</th>
|
<th>{t(taskMetadata.outcomeLabel)}</th>
|
||||||
<th>Task</th>
|
<th>Task</th>
|
||||||
<th>
|
<th>
|
||||||
<Button onClick={addOutcome} buttonType={ButtonType.secondary}>
|
<Button onClick={addOutcome} buttonType={ButtonType.secondary}>
|
||||||
@ -76,24 +86,29 @@ export const outcomeEditor: React.FC<CapabilityEditorProps> = (props) => {
|
|||||||
<ValidationErrorIcon
|
<ValidationErrorIcon
|
||||||
visible={
|
visible={
|
||||||
!!fieldErrors?.[
|
!!fieldErrors?.[
|
||||||
`${guid}.outcomeActions[${index}].verdict`
|
`${guid}.outcomeActions[${index}].outcome`
|
||||||
] ||
|
] ||
|
||||||
!!fieldErrors?.[`${guid}.outcomeActions[${index}].task`]
|
!!fieldErrors?.[`${guid}.outcomeActions[${index}].task`]
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<VerdictPicker
|
<Select
|
||||||
includeLabel={false}
|
name={"outcome"}
|
||||||
name="verdict"
|
label={t(taskMetadata.outcomeLabel)}
|
||||||
label="Verdict"
|
|
||||||
value={outcomeAction.verdict}
|
|
||||||
error={
|
error={
|
||||||
fieldErrors?.[`${guid}.outcomeActions[${index}].verdict`]
|
fieldErrors?.[`${guid}.outcomeActions[${index}].outcome`]
|
||||||
}
|
}
|
||||||
onChange={(name: string, val: string) =>
|
value={getOutcomeId(outcomeAction.outcome)}
|
||||||
updateOutcome(index, { ...outcomeAction, verdict: val })
|
options={outcomeOptions}
|
||||||
|
includeBlankFirstEntry={false}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateOutcome(index, {
|
||||||
|
...outcomeAction,
|
||||||
|
outcome: e.target.value,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
includeLabel={false}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@ -133,18 +148,6 @@ export const outcomeEditor: React.FC<CapabilityEditorProps> = (props) => {
|
|||||||
.join("; ")}
|
.join("; ")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{renderTaskField(
|
|
||||||
task,
|
|
||||||
onChange,
|
|
||||||
"overrideDefaultTaskProgression",
|
|
||||||
"Override Default Task Progression",
|
|
||||||
InputType.checkbox,
|
|
||||||
fieldErrors,
|
|
||||||
undefined, //placeholder
|
|
||||||
undefined, //maxLength
|
|
||||||
undefined, //extraProps
|
|
||||||
"Checking this will override the default task progression, allowing manual control over task flow. If unchecked, the default task progression will be followed if no outcome actions are triggered.", //title
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -170,23 +173,23 @@ const runValidation = (
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Rule 2: Detect duplicates (verdict + task) ---
|
// --- Rule 2: Detect duplicates (outcome + task) ---
|
||||||
const keyCounts = new Map<string, number>();
|
const keyCounts = new Map<string, number>();
|
||||||
|
|
||||||
outcomeActions.forEach((o) => {
|
outcomeActions.forEach((o) => {
|
||||||
if (!o.task) return; // skip empties; already handled above
|
if (!o.task) return; // skip empties; already handled above
|
||||||
const key = `${o.verdict}|${o.task}`;
|
const key = `${o.outcome}|${o.task}`;
|
||||||
keyCounts.set(key, (keyCounts.get(key) ?? 0) + 1);
|
keyCounts.set(key, (keyCounts.get(key) ?? 0) + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Any key with count > 1 is invalid
|
// Any key with count > 1 is invalid
|
||||||
outcomeActions.forEach((o, index) => {
|
outcomeActions.forEach((o, index) => {
|
||||||
if (!o.task) return;
|
if (!o.task) return;
|
||||||
const key = `${o.verdict}|${o.task}`;
|
const key = `${o.outcome}|${o.task}`;
|
||||||
if ((keyCounts.get(key) ?? 0) > 1) {
|
if ((keyCounts.get(key) ?? 0) > 1) {
|
||||||
const base = `${guid}.outcomeActions[${index}]`;
|
const base = `${guid}.outcomeActions[${index}]`;
|
||||||
errors[`${base}.verdict`] = "Duplicate verdict/task combination";
|
errors[`${base}.outcome`] = "Duplicate outcome/task combination";
|
||||||
errors[`${base}.task`] = "Duplicate verdict/task combination";
|
errors[`${base}.task`] = "Duplicate outcome/task combination";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -204,6 +207,7 @@ export function defaultsAssignment(
|
|||||||
|
|
||||||
export const outcomeOfApprovalVerdictRegistryEntry: capabilityEditorRegistryEntry =
|
export const outcomeOfApprovalVerdictRegistryEntry: capabilityEditorRegistryEntry =
|
||||||
{
|
{
|
||||||
|
match: (cap) => cap.startsWith("IOutcome<"),
|
||||||
Editor: outcomeEditor,
|
Editor: outcomeEditor,
|
||||||
DefaultsAssignment: defaultsAssignment,
|
DefaultsAssignment: defaultsAssignment,
|
||||||
ValidationRunner: runValidation,
|
ValidationRunner: runValidation,
|
||||||
@ -117,6 +117,7 @@ export function createStageEditorRegistryEntry(
|
|||||||
taskType: string,
|
taskType: string,
|
||||||
): capabilityEditorRegistryEntry {
|
): capabilityEditorRegistryEntry {
|
||||||
return {
|
return {
|
||||||
|
match: (cap) => cap === `IStage<${taskType}Attribute>`,
|
||||||
Editor: createStageEditor(taskType),
|
Editor: createStageEditor(taskType),
|
||||||
DefaultsAssignment: defaultsAssignment,
|
DefaultsAssignment: defaultsAssignment,
|
||||||
ValidationRunner: createValidationRunner(taskType),
|
ValidationRunner: createValidationRunner(taskType),
|
||||||
|
|||||||
@ -42,6 +42,7 @@ export function defaultsAssignment(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const tagsEditorRegistryEntry: capabilityEditorRegistryEntry = {
|
export const tagsEditorRegistryEntry: capabilityEditorRegistryEntry = {
|
||||||
|
match: (cap) => cap === "ITags",
|
||||||
Editor: TagsEditor,
|
Editor: TagsEditor,
|
||||||
DefaultsAssignment: defaultsAssignment,
|
DefaultsAssignment: defaultsAssignment,
|
||||||
ValidationRunner: runValidation,
|
ValidationRunner: runValidation,
|
||||||
|
|||||||
@ -140,6 +140,7 @@ export function defaultsAssignment(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const taskCoreEditorRegistryEntry: capabilityEditorRegistryEntry = {
|
export const taskCoreEditorRegistryEntry: capabilityEditorRegistryEntry = {
|
||||||
|
match: (cap) => cap === "ITask",
|
||||||
Editor: TaskCoreEditor,
|
Editor: TaskCoreEditor,
|
||||||
DefaultsAssignment: defaultsAssignment,
|
DefaultsAssignment: defaultsAssignment,
|
||||||
ValidationRunner: runValidation,
|
ValidationRunner: runValidation,
|
||||||
|
|||||||
@ -7,7 +7,7 @@ interface TaskPickerProps {
|
|||||||
name: string;
|
name: string;
|
||||||
label: string;
|
label: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
value?: TaskDefinition; // taskId
|
value?: string | null;
|
||||||
onChange?: (name: string, value: string) => void;
|
onChange?: (name: string, value: string) => void;
|
||||||
tasks: TaskDefinition[];
|
tasks: TaskDefinition[];
|
||||||
includeLabel?: boolean;
|
includeLabel?: boolean;
|
||||||
|
|||||||
@ -107,7 +107,8 @@ const TaskEditorComponent: React.FC<TaskEditorProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{taskMeta?.capabilities.map((capability) => {
|
{taskMeta?.capabilities.map((capability) => {
|
||||||
const entry = capabilityEditorRegistry[capability];
|
const entry = capabilityEditorRegistry.find((r) => r.match(capability));
|
||||||
|
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
console.log(`No editor entry found for capability ${capability}.`);
|
console.log(`No editor entry found for capability ${capability}.`);
|
||||||
return null;
|
return null;
|
||||||
@ -124,6 +125,7 @@ const TaskEditorComponent: React.FC<TaskEditorProps> = ({
|
|||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors}
|
||||||
onValidateTask={onValidate}
|
onValidateTask={onValidate}
|
||||||
onTaskAdded={onTaskAdded}
|
onTaskAdded={onTaskAdded}
|
||||||
|
taskMetadata={taskMeta}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@ -2,26 +2,23 @@ import { tagsEditorRegistryEntry } from "./CapabilityEditors/TagsEditor";
|
|||||||
import { assigneesOfITaskAssigneeRegistryEntry } from "./CapabilityEditors/AssigneesOfITaskAssigneeEditor";
|
import { assigneesOfITaskAssigneeRegistryEntry } from "./CapabilityEditors/AssigneesOfITaskAssigneeEditor";
|
||||||
import { taskCoreEditorRegistryEntry } from "./CapabilityEditors/TaskCoreEditor";
|
import { taskCoreEditorRegistryEntry } from "./CapabilityEditors/TaskCoreEditor";
|
||||||
import { capabilityEditorRegistryEntry } from "./useCapabilityDefaults";
|
import { capabilityEditorRegistryEntry } from "./useCapabilityDefaults";
|
||||||
import { outcomeOfApprovalVerdictRegistryEntry } from "./CapabilityEditors/OutcomeOfApprovalVerdictRegistryEntry";
|
import { outcomeOfApprovalVerdictRegistryEntry } from "./CapabilityEditors/OutcomeEditor";
|
||||||
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";
|
import { assigneesOfIApprovalTaskAssigneeRegistryEntry } from "./CapabilityEditors/AssigneesOfIApprovalTaskAssigneeEditor";
|
||||||
|
|
||||||
export const capabilityEditorRegistry: Record<
|
type CapabilityEditorRegistry = Array<capabilityEditorRegistryEntry>;
|
||||||
string,
|
|
||||||
capabilityEditorRegistryEntry
|
export const capabilityEditorRegistry: CapabilityEditorRegistry = [
|
||||||
> = {
|
taskCoreEditorRegistryEntry,
|
||||||
ITask: taskCoreEditorRegistryEntry,
|
tagsEditorRegistryEntry,
|
||||||
ITags: tagsEditorRegistryEntry,
|
budgetEditorRegistryEntry,
|
||||||
IBudget: budgetEditorRegistryEntry,
|
assigneesOfITaskAssigneeRegistryEntry,
|
||||||
"IAssignees<TaskAssignee>": assigneesOfITaskAssigneeRegistryEntry,
|
assigneesOfIApprovalTaskAssigneeRegistryEntry,
|
||||||
"IAssignees<ApprovalTaskAssignee>":
|
outcomeOfApprovalVerdictRegistryEntry,
|
||||||
assigneesOfIApprovalTaskAssigneeRegistryEntry,
|
|
||||||
"IOutcome<ApprovalVerdict>": outcomeOfApprovalVerdictRegistryEntry,
|
|
||||||
// IFormTemplate: null, //ToDo implement this
|
// IFormTemplate: null, //ToDo implement this
|
||||||
IBypassable: bypassableEditorRegistryEntry,
|
bypassableEditorRegistryEntry,
|
||||||
"IStage<GeneralTaskAttribute>": createStageEditorRegistryEntry("GeneralTask"),
|
createStageEditorRegistryEntry("GeneralTask"),
|
||||||
"IStage<ApprovalTaskAttribute>":
|
createStageEditorRegistryEntry("ApprovalTask"),
|
||||||
createStageEditorRegistryEntry("ApprovalTask"),
|
];
|
||||||
};
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@ export interface CapabilityEditorProps {
|
|||||||
onValidateTask?: (taskId: string, isValid: boolean) => void;
|
onValidateTask?: (taskId: string, isValid: boolean) => void;
|
||||||
onTaskAdded?: (taskGuid: string) => void;
|
onTaskAdded?: (taskGuid: string) => void;
|
||||||
fieldErrors: Record<string, string>;
|
fieldErrors: Record<string, string>;
|
||||||
|
taskMetadata: TaskMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface defaultsContext {
|
export interface defaultsContext {
|
||||||
@ -28,6 +29,7 @@ export interface defaultsContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface capabilityEditorRegistryEntry {
|
export interface capabilityEditorRegistryEntry {
|
||||||
|
match: (capability: string) => boolean;
|
||||||
Editor: React.FC<any>;
|
Editor: React.FC<any>;
|
||||||
DefaultsAssignment?: (
|
DefaultsAssignment?: (
|
||||||
task: TaskDefinition,
|
task: TaskDefinition,
|
||||||
@ -51,7 +53,7 @@ export async function validateTask(
|
|||||||
const errors: Record<string, string> = {};
|
const errors: Record<string, string> = {};
|
||||||
|
|
||||||
for (const capability of taskMeta?.capabilities ?? []) {
|
for (const capability of taskMeta?.capabilities ?? []) {
|
||||||
const entry = capabilityEditorRegistry[capability];
|
const entry = capabilityEditorRegistry.find((r) => r.match(capability));
|
||||||
|
|
||||||
if (!entry?.ValidationRunner) {
|
if (!entry?.ValidationRunner) {
|
||||||
continue;
|
continue;
|
||||||
@ -72,7 +74,7 @@ export function useCapabilityDefaults(taskMetadata: TaskMetadata[]) {
|
|||||||
|
|
||||||
const runDefaults = React.useCallback(
|
const runDefaults = React.useCallback(
|
||||||
(capability: string, task: TaskDefinition, tasks: TaskDefinition[]) => {
|
(capability: string, task: TaskDefinition, tasks: TaskDefinition[]) => {
|
||||||
const entry = capabilityEditorRegistry[capability];
|
const entry = capabilityEditorRegistry.find((r) => r.match(capability));
|
||||||
if (!entry?.DefaultsAssignment) return;
|
if (!entry?.DefaultsAssignment) return;
|
||||||
|
|
||||||
entry.DefaultsAssignment(task, tasks, {
|
entry.DefaultsAssignment(task, tasks, {
|
||||||
|
|||||||
@ -47,6 +47,8 @@ export interface TaskMetadata {
|
|||||||
taskType: string;
|
taskType: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
capabilities: string[];
|
capabilities: string[];
|
||||||
|
outcomeLabel: string;
|
||||||
|
outcomes: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getTemplates(
|
export async function getTemplates(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user