How to restrict the creation of subtasks when the parent ticket is finished ?
Asked by Manar El Kefi:

The main idea is to restrict the creation of subtasks when the parent ticket is finished, so I created a creation form in a panel. The problem is that the issue type options are not displayed, and there is the following error: TypeError: Cannot read properties of undefined (reading 'prop') at Object.panel (webpack://jira-project-page-ui-kit/node_modules/@forge/ui/out/backend-runtime.js:37:1)

Here is the code :

import ForgeUI, { useProductContext, IssuePanel, render, Fragment, Text, Button, useState, useEffect, Table, Head, Cell, Row, Link, TextField, Form, Select, Option } from '@forge/ui';  
import api, { route } from '@forge/api';

const PAGE_SIZE = 3;

const fetchChildIssues = async (issueKey) =\> {  
const response = await api.asApp().requestJira(route\`/rest/api/3/search?jql=parent=${issueKey}\`);  
const data = await response.json();  
return data.issues \|\| \[\];  
};  
const fetchLinkedIssues = async (issueKey) =\> {  
const response = await api.asApp().requestJira(route\`/rest/api/3/search?jql=issue in linkedIssues(${issueKey})\`);  
const data = await response.json();  
return data.issues \|\| \[\];  
};  
const fetchIssueTypes = async (projectId) =\> {  
const response = await api.asApp().requestJira(route\`/rest/api/3/issuetype\`);  
const data = await response.json();  
console.log("Issue Types Response:", data);  
return data.issueTypes ? data.issueTypes.filter((issueType) =\> issueType.subtask) : \[\];  
};  
const createSubtask = async (parentKey, summary, assignee, priority, issueTypeId) =\> {  
const projectResponse = await api.asApp().requestJira(route\`/rest/api/3/issue/${parentKey}\`);  
const projectData = await projectResponse.json();  
const projectId = projectData.fields.project.id;  
const response = await api.asApp().requestJira(route\`/rest/api/3/issue\`, {  
method: "POST",  
body: JSON.stringify({  
fields: {  
project: { id: projectId },  
parent: { key: parentKey },  
summary: summary,  
assignee: { id: assignee },  
priority: { id: priority },  
issuetype: { id: issueTypeId },  
},  
}),  
});  
return response.json();  
};  
const fetchUsers = async () =\> {  
const response = await api.asApp().requestJira(route\`/rest/api/3/user/search?query=\`);  
const data = await response.json();  
return data.map((user) =\> ({ id: user.accountId, displayName: user.displayName }));  
};  
const fetchPriorities = async () =\> {  
const response = await api.asApp().requestJira(route\`/rest/api/3/priority\`);  
const data = await response.json();  
return data;  
};  
const deleteIssue = async (issueKey) =\> {  
await api.asApp().requestJira(route\`/rest/api/3/issue/${issueKey}\`, {  
method: "DELETE",  
});  
};  
const Panel = () =\> {  
const { platformContext: { issueKey } } = useProductContext();  
const \[childIssues, setChildIssues\] = useState(\[\]);  
const \[linkedIssues, setLinkedIssues\] = useState(\[\]);  
const \[filteredChildIssues, setFilteredChildIssues\] = useState(\[\]);  
const \[filteredLinkedIssues, setFilteredLinkedIssues\] = useState(\[\]);  
const \[isPanelVisible, setPanelVisible\] = useState(true);  
const \[currentPage, setCurrentPage\] = useState(1);  
const \[parentIssueStatus, setParentIssueStatus\] = useState("");  
const \[issueTypes, setIssueTypes\] = useState(\[\]);  
const \[users, setUsers\] = useState(\[\]);  
const \[priorities, setPriorities\] = useState(\[\]);  
const \[isCreating, setIsCreating\] = useState(false);  
useEffect(async () =\> {  
try {  
const childIssues = await fetchChildIssues(issueKey);  
const linkedIssues = await fetchLinkedIssues(issueKey);  
setChildIssues(childIssues);  
setLinkedIssues(linkedIssues);  
setFilteredChildIssues(childIssues.slice(0, PAGE_SIZE));  
setFilteredLinkedIssues(linkedIssues.slice(0, PAGE_SIZE));  
const response = await api.asApp().requestJira(route\`/rest/api/3/issue/${issueKey}\`);  
const data = await response.json();  
const parentStatus = data.fields.status.name;  
setParentIssueStatus(parentStatus);  
const issueTypes = await fetchIssueTypes(data.fields.project.id);  
console.log("Filtered Issue Types:", issueTypes); // Log filtered issue types  
setIssueTypes(issueTypes);  
const users = await fetchUsers();  
setUsers(users);  
const priorities = await fetchPriorities();  
setPriorities(priorities);  
} catch (error) {  
console.error("Error during useEffect:", error);  
}  
}, \[issueKey\]);  
const handleFilter = (formData, issues, setFilteredIssues) =\> {  
const query = formData.query.toLowerCase();  
const filtered = issues.filter(  
(issue) =\>  
issue.fields.summary.toLowerCase().includes(query) \|\|  
(issue.fields.assignee \&\& issue.fields.assignee.displayName.toLowerCase().includes(query)) \|\|  
issue.fields.status.name.toLowerCase().includes(query) \|\|  
issue.key.toLowerCase().includes(query)  
);  
setFilteredIssues(filtered.slice(0, PAGE_SIZE));  
setCurrentPage(1);  
};  
const handleDeleteIssue = async (issueKey) =\> {  
await deleteIssue(issueKey);  
const childIssues = await fetchChildIssues(issueKey);  
const linkedIssues = await fetchLinkedIssues(issueKey);  
setChildIssues(childIssues);  
setLinkedIssues(linkedIssues);  
setFilteredChildIssues(childIssues.slice(0, PAGE_SIZE));  
setFilteredLinkedIssues(linkedIssues.slice(0, PAGE_SIZE));  
setCurrentPage(1);  
};  
const handlePageChange = (pageNumber, issues, setFilteredIssues) =\> {  
const startIndex = (pageNumber - 1) \* PAGE_SIZE;  
const endIndex = startIndex + PAGE_SIZE;  
setFilteredIssues(issues.slice(startIndex, endIndex));  
setCurrentPage(pageNumber);  
};  
const handleCreateSubtask = async (formData) =\> {  
const { summary, assignee, priority, issueTypeId } = formData;  
setIsCreating(true);  
await createSubtask(issueKey, summary, assignee, priority, issueTypeId);  
const childIssues = await fetchChildIssues(issueKey);  
setChildIssues(childIssues);  
setFilteredChildIssues(childIssues.slice(0, PAGE_SIZE));  
setIsCreating(false);  
};  
const canCreateSubtask = parentIssueStatus !== "Termin?";  
return (  
\<Fragment\>  
\<Button text={isPanelVisible ? "Masquer les tickets enfants et li?s" : "Afficher les tickets enfants et li?s"} onClick={() =\> setPanelVisible(!isPanelVisible)} /\>  
{isPanelVisible \&\& (  
\<Fragment\>  
{canCreateSubtask \&\& (  
\<Fragment\>  
\<Text content="\*\*Cr?er une sous-t?che:\*\*" /\>  
\<Form onSubmit={handleCreateSubtask}\>  
\<TextField name="summary" label="R?sum?" isRequired /\>  
\<Select label="Type de sous-t?che" name="issueTypeId" isRequired\>  
{issueTypes.map((issueType) =\> (  
\<Option key={issueType.id} value={issueType.id} label={issueType.name} /\>  
))}  
\</Select\>  
\<Select label="Assign? ?" name="assignee" isRequired\>  
{users.map((user) =\> (  
\<Option key={user.id} value={user.id} label={user.displayName} /\>  
))}  
\</Select\>  
\<Select label="Priorit?" name="priority" isRequired\>  
{priorities.map((priority) =\> (  
\<Option key={priority.id} value={priority.id} label={priority.name} /\>  
))}  
\</Select\>  
\<Button text="Cr?er" type="submit" isDisabled={isCreating} /\>  
\</Form\>  
\</Fragment\>  
)}  
\<Text content="\*\*Tickets Enfants:\*\*" /\>  
{filteredChildIssues.length === 0 \&\& \<Text\>Aucun ticket enfant trouv?.\</Text\>}  
\<Table\>  
\<Head\>  
\<Cell\>  
\<Text\>  
\<strong\>Key\</strong\>  
\</Text\>  
\</Cell\>  
\<Cell\>  
\<Text\>  
\<strong\>Summary\</strong\>  
\</Text\>  
\</Cell\>  
\<Cell\>  
\<Text\>  
\<strong\>Status\</strong\>  
\</Text\>  
\</Cell\>  
\<Cell\>  
\<Text\>  
\<strong\>Assignee\</strong\>  
\</Text\>  
\</Cell\>  
\<Cell\>  
\<Text\>  
\<strong\>Priority\</strong\>  
\</Text\>  
\</Cell\>  
\<Cell\>  
\<Text\>  
\<strong\>Created\</strong\>  
\</Text\>  
\</Cell\>  
\<Cell\>  
\<Text\>  
\<strong\>Actions\</strong\>  
\</Text\>  
\</Cell\>  
\</Head\>  
{filteredChildIssues.map((issue) =\> (  
\<Row key={issue.id}\>  
\<Cell\>  
\<Text\>  
\<Link href={\`/browse/${issue.key}\`}\>{issue.key}\</Link\>  
\</Text\>  
\</Cell\>  
\<Cell\>  
\<Text\>{issue.fields.summary}\</Text\>  
\</Cell\>  
\<Cell\>  
\<Text\>{issue.fields.status.name}\</Text\>  
\</Cell\>  
\<Cell\>  
\<Text\>{issue.fields.assignee ? issue.fields.assignee.displayName : "Non assign?"}\</Text\>  
\</Cell\>  
\<Cell\>  
\<Text\>{issue.fields.priority ? issue.fields.priority.name : "N/A"}\</Text\>  
\</Cell\>  
\<Cell\>  
\<Text\>{new Date(issue.fields.created).toLocaleString()}\</Text\>  
\</Cell\>  
\<Cell\>  
\<Button text="??" onClick={() =\> handleDeleteIssue(issue.key)} /\>  
\</Cell\>  
\</Row\>  
))}  
\</Table\>  
{childIssues.length \> PAGE_SIZE \&\& (  
\<Fragment\>  
{Array.from({ length: Math.ceil(childIssues.length / PAGE_SIZE) }, (_, index) =\> (  
\<Button key={index + 1} text={index + 1} onClick={() =\> handlePageChange(index + 1, childIssues, setFilteredChildIssues)} style={{ marginRight: "5px" }} /\>  
))}  
\</Fragment\>  
)}  
\<Form onSubmit={(formData) =\> handleFilter(formData, childIssues, setFilteredChildIssues)}\>  
\<TextField name="query" label="Rechercher par Key, Summary, Status, Assignee" /\>  
\</Form\>  
\<Text content="\*\*Tickets Li?s:\*\*" /\>  
{filteredLinkedIssues.length === 0 \&\& \<Text\>Aucun ticket li? trouv?.\</Text\>}  
\<Table\>  
\<Head\>  
\<Cell\>  
\<Text\>  
\<strong\>Key\</strong\>  
\</Text\>  
\</Cell\>  
\<Cell\>  
\<Text\>  
\<strong\>Summary\</strong\>  
\</Text\>  
\</Cell\>  
\<Cell\>  
\<Text\>  
\<strong\>Status\</strong\>  
\</Text\>  
\</Cell\>  
\<Cell\>  
\<Text\>  
\<strong\>Assignee\</strong\>  
\</Text\>  
\</Cell\>  
\<Cell\>  
\<Text\>  
\<strong\>Actions\</strong\>  
\</Text\>  
\</Cell\>  
\</Head\>  
{filteredLinkedIssues.map((issue) =\> (  
\<Row key={issue.id}\>  
\<Cell\>  
\<Text\>  
\<Link href={\`/browse/${issue.key}\`}\>{issue.key}\</Link\>  
\</Text\>  
\</Cell\>  
\<Cell\>  
\<Text\>{issue.fields.summary}\</Text\>  
\</Cell\>  
\<Cell\>  
\<Text\>{issue.fields.status.name}\</Text\>  
\</Cell\>  
\<Cell\>  
\<Text\>{issue.fields.assignee ? issue.fields.assignee.displayName : "Non assign?"}\</Text\>  
\</Cell\>  
\<Cell\>  
\<Button text="??" onClick={() =\> handleDeleteIssue(issue.key)} /\>  
\</Cell\>  
\</Row\>  
))}  
\</Table\>  
{linkedIssues.length \> PAGE_SIZE \&\& (  
\<Fragment\>  
{Array.from({ length: Math.ceil(linkedIssues.length / PAGE_SIZE) }, (_, index) =\> (  
\<Button key={index + 1} text={index + 1} onClick={() =\> handlePageChange(index + 1, linkedIssues, setFilteredLinkedIssues)} style={{ marginRight: "5px" }} /\>  
))}  
\</Fragment\>  
)}  
\<Form onSubmit={(formData) =\> handleFilter(formData, linkedIssues, setFilteredLinkedIssues)}\>  
\<TextField name="query" label="Rechercher par Key, Summary, Status, Assignee" /\>  
\</Form\>  
\</Fragment\>  
)}  
\</Fragment\>  
);  
};  
export const panel = render(  
\<IssuePanel\>  
\<Panel /\>  
\</IssuePanel\>  
);


Answers:
author: Rilwan Ahmed
body: Hi [@Manar El Kefi](/t5/user/viewprofilepage/user-id/5543953) ,

Welcome to the community !!

I have a simple workaround.

In the Subtask workflow, for the create transition, add '**Parent Status Validator**' and give all the status that are not 'Finished' and publish the workflow.

This validator will allow users to create subtask only when the status of the parent ticket is not finished

![image.png](https://community.atlassian.com/t5/image/serverpage/image-id/336075i92371ADB1503BDA7/image-dimensions/276x105?v=v2 "image.png")


Tags: cloud

jira-cloud