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>
);
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.