From c7967f70927b3d462c7a6d46d705783e08220a53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= <78490564+maximilianruesch@users.noreply.github.com> Date: Mon, 11 Mar 2024 18:58:57 +0100 Subject: [PATCH] Simplify and fix issue creation (#145) --- src/components/BacklogView/BacklogView.tsx | 6 +- .../CreateIssue/CreateIssueModal.tsx | 210 +----------------- .../CreateIssue/CreateIssueModalContent.tsx | 179 +++++++++++++++ .../CreateIssue/Fields/EpicSelect.tsx | 3 +- .../CreateIssue/Fields/LabelsSelect.tsx | 3 +- .../CreateIssue/Fields/PrioritySelect.tsx | 3 +- .../CreateIssue/Fields/SprintSelect.tsx | 5 +- src/components/CreateIssue/queryFunctions.ts | 40 ---- .../DetailView/Components/IssueSprint.tsx | 5 +- .../DetailView/Components/IssueStatusMenu.tsx | 5 +- .../DetailView/Components/Labels.tsx | 3 +- .../DetailView/Components/ReporterMenu.tsx | 4 +- .../SplitIssue/SplitIssueCreate.tsx | 18 +- .../Components/StoryPointsEstimateMenu.tsx | 3 +- .../EpicDetailView/Components/ChildIssues.tsx | 3 +- src/components/EpicView/EpicView.tsx | 3 +- src/components/layout/LayoutHeader.tsx | 3 +- 17 files changed, 216 insertions(+), 280 deletions(-) create mode 100644 src/components/CreateIssue/CreateIssueModalContent.tsx delete mode 100644 src/components/CreateIssue/queryFunctions.ts diff --git a/src/components/BacklogView/BacklogView.tsx b/src/components/BacklogView/BacklogView.tsx index 01f935da..8ac92758 100644 --- a/src/components/BacklogView/BacklogView.tsx +++ b/src/components/BacklogView/BacklogView.tsx @@ -146,7 +146,11 @@ export function BacklogView() { isLoadingContent={isFetchingSprints || issueQueries.some((query) => query.isFetching)} > - + setCreateIssueModalOpened(false)} + onCreate={() => setCreateIssueModalOpened(false)} + /> >, + onCancel: () => void, + onCreate: () => void, }) { - const queryClient = useQueryClient(); const theme = useMantineTheme(); const colorScheme = useColorScheme(); - const projects = useCanvasStore((state) => state.projects); - const selectedProject = useCanvasStore((state) => state.selectedProject); - - const { data: currentUser } = useQuery({ - queryKey: ["currentUser"], - queryFn: () => getCurrentUser(), - }); - - const form = useForm({ - initialValues: { - projectId: selectedProject?.id, - type: "", - sprint: { id: undefined as unknown as number }, - summary: "", - description: "", - assignee: { id: "" }, - status: "To Do", - reporter: currentUser, - priority: { id: "" }, - epic: { issueKey: undefined }, - } as Issue, - }); - - const { data: issueTypes, isLoading } = useQuery({ - queryKey: ["issueTypes", form.getInputProps("projectId").value], - queryFn: () => getIssueTypes(form.getInputProps("projectId").value!), - enabled: !!projects && !!form.getInputProps("projectId").value, - }); - - const { data: assignableUsers } = useQuery({ - queryKey: ["assignableUsers", form.getInputProps("projectId").value], - queryFn: () => { - const relevantProject = projects.find( - (project) => project.id === form.getInputProps("projectId").value!, - )!; - - return getAssignableUsersByProject(relevantProject.key); - }, - enabled: !!projects && !!form.getInputProps("projectId").value, - }); - - const { data: issueTypesWithFieldsMap } = useQuery({ - queryKey: ["issueTypesWithFieldsMap"], - queryFn: () => getIssueTypesWithFieldsMap(), - }); - - const mutation = useMutation({ - mutationFn: (issue: Issue) => createIssue(issue), - onError: () => { - showNotification({ - message: "The issue couldn't be created! 😢", - color: "red", - }); - }, - onSuccess: (issueKey) => { - const files: File[] = form.getInputProps("attachment").value; - const filesForm = new FormData(); - if (files) { - files.forEach((f) => filesForm.append("file", f, f.name)); - getResource().then((r) => uploadAttachment(issueKey, r, filesForm)); - } - showNotification({ - message: `The issue ${issueKey} has been created!`, - color: "green", - }); - queryClient.invalidateQueries({ queryKey: ["issues"] }); - queryClient.invalidateQueries({ queryKey: ["epics"] }); - setOpened(false); - form.reset(); - }, - }); return ( setOpened(false)} + onClose={onCancel} title="Create Issue" size="70vw" overlayProps={{ - color: - colorScheme === "dark" ? theme.colors.dark[9] : theme.colors.gray[2], + color: colorScheme === "dark" ? theme.colors.dark[9] : theme.colors.gray[2], opacity: 0.55, blur: 5, }} > - - -
{ - event?.preventDefault(); - mutation.mutate(issue); - })} - > - - - - - - - - - - - - - - - - - - - - - - -
-
+
); } diff --git a/src/components/CreateIssue/CreateIssueModalContent.tsx b/src/components/CreateIssue/CreateIssueModalContent.tsx new file mode 100644 index 00000000..c4ecaf2d --- /dev/null +++ b/src/components/CreateIssue/CreateIssueModalContent.tsx @@ -0,0 +1,179 @@ +import { Button, Divider, Group, ScrollArea, Stack } from "@mantine/core"; +import { useForm } from "@mantine/form"; +import { showNotification } from "@mantine/notifications"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { Issue } from "types"; +import { useCanvasStore } from "../../lib/Store"; +import { getResource, uploadAttachment } from "../DetailView/Components/Attachments/queryFunctions"; +import { + ProjectSelect, + IssueTypeSelect, + StatusSelect, + SummaryInput, + DescriptionInput, + AssigneeSelect, + PrioritySelect, + SprintSelect, + EpicSelect, + StoryPointsEstimateInput, + ReporterSelect, + StartDatePicker, + DueDatePicker, + LabelsSelect, + AttachmentFileInput, +} from "./Fields"; + +export function CreateIssueModalContent({ + onCancel, + onCreate, +}: { + onCancel: () => void, + onCreate: () => void, +}) { + const queryClient = useQueryClient(); + const { projects, selectedProject, issueTypesWithFieldsMap } = useCanvasStore(); + + const { data: currentUser } = useQuery({ + queryKey: ["currentUser"], + queryFn: () => window.provider.getCurrentUser(), + }); + + const form = useForm({ + initialValues: { + projectId: selectedProject?.key, + type: "", + sprint: { id: undefined as unknown as number }, + summary: "", + description: "", + assignee: { id: "" }, + status: "To Do", + reporter: currentUser, + priority: { id: "" }, + epic: { issueKey: undefined }, + } as Issue, + }); + + const { data: issueTypes, isLoading } = useQuery({ + queryKey: ["issueTypes", form.getInputProps("projectId").value], + queryFn: () => window.provider.getIssueTypesByProject(form.getInputProps("projectId").value!), + enabled: !!projects && !!form.getInputProps("projectId").value, + }); + + const { data: assignableUsers } = useQuery({ + queryKey: ["assignableUsers", form.getInputProps("projectId").value], + queryFn: () => window.provider.getAssignableUsersByProject(form.getInputProps("projectId").value!), + enabled: !!projects && !!form.getInputProps("projectId").value, + }); + + const mutation = useMutation({ + mutationFn: (issue: Issue) => window.provider.createIssue(issue), + onError: () => { + showNotification({ + message: "The issue couldn't be created! 😢", + color: "red", + }); + }, + onSuccess: (issueKey) => { + const files: File[] = form.getInputProps("attachment").value; + const filesForm = new FormData(); + if (files) { + files.forEach((f) => filesForm.append("file", f, f.name)); + getResource().then((r) => uploadAttachment(issueKey, r, filesForm)); + } + showNotification({ + message: `The issue ${issueKey} has been created!`, + color: "green", + }); + queryClient.invalidateQueries({ queryKey: ["issues"] }); + queryClient.invalidateQueries({ queryKey: ["epics"] }); + onCreate(); + form.reset(); + }, + }); + + return ( + +
{ + event?.preventDefault(); + mutation.mutate(issue); + })} + > + + + + + + + + + + + + + + + + + + + + + + +
+
+ ); +} diff --git a/src/components/CreateIssue/Fields/EpicSelect.tsx b/src/components/CreateIssue/Fields/EpicSelect.tsx index 7ae86e26..0aa535dc 100644 --- a/src/components/CreateIssue/Fields/EpicSelect.tsx +++ b/src/components/CreateIssue/Fields/EpicSelect.tsx @@ -2,7 +2,6 @@ import { Select, Tooltip, Box } from "@mantine/core"; import { UseFormReturnType } from "@mantine/form"; import { useQuery } from "@tanstack/react-query"; import { Issue, IssueType } from "types"; -import { getEpicsByProject } from "../queryFunctions"; export function EpicSelect({ form, @@ -20,7 +19,7 @@ export function EpicSelect({ }) { const { data: epics } = useQuery({ queryKey: ["epics", form.getInputProps("projectId").value], - queryFn: () => getEpicsByProject(form.getInputProps("projectId").value!), + queryFn: () => window.provider.getEpicsByProject(form.getInputProps("projectId").value!), enabled: enabled && !!form.getInputProps("projectId").value, }); diff --git a/src/components/CreateIssue/Fields/LabelsSelect.tsx b/src/components/CreateIssue/Fields/LabelsSelect.tsx index 81332e85..b2ecfa1a 100644 --- a/src/components/CreateIssue/Fields/LabelsSelect.tsx +++ b/src/components/CreateIssue/Fields/LabelsSelect.tsx @@ -2,12 +2,11 @@ import { MultiSelect } from "@mantine/core"; import { UseFormReturnType } from "@mantine/form"; import { useQuery } from "@tanstack/react-query"; import { Issue } from "types"; -import { getLabels } from "../queryFunctions"; export function LabelsSelect({ form }: { form: UseFormReturnType }) { const { data: labels } = useQuery({ queryKey: ["labels"], - queryFn: () => getLabels(), + queryFn: () => window.provider.getLabels(), }); return ( diff --git a/src/components/CreateIssue/Fields/PrioritySelect.tsx b/src/components/CreateIssue/Fields/PrioritySelect.tsx index e5249039..05138cfd 100644 --- a/src/components/CreateIssue/Fields/PrioritySelect.tsx +++ b/src/components/CreateIssue/Fields/PrioritySelect.tsx @@ -2,7 +2,6 @@ import { Box, Tooltip } from "@mantine/core"; import { UseFormReturnType } from "@mantine/form"; import { useQuery } from "@tanstack/react-query"; import { Issue } from "types"; -import { getPriorities } from "../queryFunctions"; import { SelectItem } from "../SelectItem"; import { CustomItemSelect } from "../../common/CustomItemSelect"; @@ -17,7 +16,7 @@ export function PrioritySelect({ }) { const { data: priorities } = useQuery({ queryKey: ["priorities"], - queryFn: () => getPriorities(), + queryFn: () => window.provider.getPriorities(), }); const isDisabled = issueTypesWithFieldsMap diff --git a/src/components/CreateIssue/Fields/SprintSelect.tsx b/src/components/CreateIssue/Fields/SprintSelect.tsx index c2e4cbc3..3fd68b96 100644 --- a/src/components/CreateIssue/Fields/SprintSelect.tsx +++ b/src/components/CreateIssue/Fields/SprintSelect.tsx @@ -2,7 +2,6 @@ import { Select, Tooltip, Box } from "@mantine/core"; import { UseFormReturnType } from "@mantine/form"; import { useQuery } from "@tanstack/react-query"; import { Issue, IssueType } from "types"; -import { getBoardIds, getSprints } from "../queryFunctions"; export function SprintSelect({ form, @@ -19,13 +18,13 @@ export function SprintSelect({ }) { const { data: boardIds } = useQuery({ queryKey: ["boards", form.getInputProps("projectId").value], - queryFn: () => getBoardIds(form.getInputProps("projectId").value!), + queryFn: () => window.provider.getBoardIds(form.getInputProps("projectId").value!), enabled: enabled && !!form.getInputProps("projectId").value, }); const { data: sprints } = useQuery({ queryKey: ["sprints"], // TODO: fetch when boards are fetched (iterate over all boards) or select a specific one - queryFn: () => getSprints(boardIds![0]), + queryFn: () => window.provider.getSprints(boardIds![0]), enabled: enabled && !!boardIds && !!boardIds[0], }); diff --git a/src/components/CreateIssue/queryFunctions.ts b/src/components/CreateIssue/queryFunctions.ts deleted file mode 100644 index 7fb97b55..00000000 --- a/src/components/CreateIssue/queryFunctions.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Issue, IssueType, Priority, Sprint, User } from "types"; - -export const getIssueTypes = (projectIdOrKey: string): Promise => window.provider.getIssueTypesByProject(projectIdOrKey); - -export const createIssue = (issue: Issue): Promise => window.provider.createIssue(issue); - -export const moveIssueToBacklog = (issueIdOrKey: string): Promise => window.provider.moveIssueToBacklog(issueIdOrKey); - -export const getAssignableUsersByProject = ( - projectIdOrKey: string, -): Promise => window.provider.getAssignableUsersByProject(projectIdOrKey); - -export const getBoardIds = (projectIdOrKey: string): Promise => window.provider.getBoardIds(projectIdOrKey); - -export const getSprints = (boardId: number): Promise => window.provider.getSprints(boardId); - -export const getLabels = (): Promise => window.provider.getLabels(); - -export const getCurrentUser = (): Promise => window.provider.getCurrentUser(); - -export const getPriorities = (): Promise => window.provider.getPriorities(); - -export const getIssueTypesWithFieldsMap = (): Promise> => window.provider.getIssueTypesWithFieldsMap().then(async (mapResponse) => { - const map = new Map(); - Object.entries(mapResponse).forEach(([key, value]) => map.set(key, value)); - return map; -}); - -export const setStatus = ( - issueKey: string, - targetStatus: string, -): Promise => window.provider.setTransition(issueKey, targetStatus); - -export const getIssueReporter = (issueIdOrKey: string): Promise => window.provider.getIssueReporter(issueIdOrKey); - -export const getEditableIssueFields = ( - issueIdOrKey: string, -): Promise => window.provider.getEditableIssueFields(issueIdOrKey); - -export const getEpicsByProject = (projectIdOrKey: string): Promise => window.provider.getEpicsByProject(projectIdOrKey); diff --git a/src/components/DetailView/Components/IssueSprint.tsx b/src/components/DetailView/Components/IssueSprint.tsx index 6ceeb1fe..a2a6e3f1 100644 --- a/src/components/DetailView/Components/IssueSprint.tsx +++ b/src/components/DetailView/Components/IssueSprint.tsx @@ -4,7 +4,6 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import { Issue, Sprint } from "types"; import { useState } from "react"; import { useCanvasStore } from "@canvas/lib/Store"; -import { getSprints, moveIssueToBacklog } from "../../CreateIssue/queryFunctions"; export function IssueSprint(props: { sprint: Sprint | undefined, @@ -18,7 +17,7 @@ export function IssueSprint(props: { const currentBoardId = boardIds[0]; const { data: sprints } = useQuery({ queryKey: ["sprints"], - queryFn: () => getSprints(currentBoardId), + queryFn: () => window.provider.getSprints(currentBoardId), enabled: !!currentBoardId, }); @@ -38,7 +37,7 @@ export function IssueSprint(props: { }, }); const mutationBacklog = useMutation({ - mutationFn: () => moveIssueToBacklog(props.issueKey), + mutationFn: () => window.provider.moveIssueToBacklog(props.issueKey), onError: () => { showNotification({ message: "An error occurred while modifing the sprint 😢", diff --git a/src/components/DetailView/Components/IssueStatusMenu.tsx b/src/components/DetailView/Components/IssueStatusMenu.tsx index d713a9b5..a6c78119 100644 --- a/src/components/DetailView/Components/IssueStatusMenu.tsx +++ b/src/components/DetailView/Components/IssueStatusMenu.tsx @@ -2,7 +2,6 @@ import { Box, Button, Menu } from "@mantine/core"; import { useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { IconCaretDown } from "@tabler/icons-react"; -import { getIssueTypes, setStatus } from "../../CreateIssue/queryFunctions"; import classes from "./IssueStatusMenu.module.css"; export function IssueStatusMenu({ @@ -20,14 +19,14 @@ export function IssueStatusMenu({ const { data: issueTypes } = useQuery({ queryKey: ["issueTypes", projectId], - queryFn: () => getIssueTypes(projectId), + queryFn: () => window.provider.getIssueTypesByProject(projectId), enabled: !!projectId, }); const queryClient = useQueryClient(); const [defaultStatus, setDefaultStatus] = useState(status); const statusMutation = useMutation({ - mutationFn: (targetStatus: string) => setStatus(issueKey, targetStatus), + mutationFn: (targetStatus: string) => window.provider.setTransition(issueKey, targetStatus), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["issues"] }); }, diff --git a/src/components/DetailView/Components/Labels.tsx b/src/components/DetailView/Components/Labels.tsx index 48e7a154..1a0a18c9 100644 --- a/src/components/DetailView/Components/Labels.tsx +++ b/src/components/DetailView/Components/Labels.tsx @@ -3,7 +3,6 @@ import { showNotification } from "@mantine/notifications"; import { useMutation, useQuery } from "@tanstack/react-query"; import { Issue } from "types"; import { useState } from "react"; -import { getLabels } from "../../CreateIssue/queryFunctions"; import { IssueLabelBadge } from "../../common/IssueLabelBadge"; export function Labels({ @@ -20,7 +19,7 @@ export function Labels({ const [showLabelsInput, setShowLabelsInput] = useState(false); const { data: allLabels } = useQuery({ queryKey: ["labels"], - queryFn: () => getLabels(), + queryFn: () => window.provider.getLabels(), }); const mutationLabels = useMutation({ mutationFn: (issue: Issue) => window.provider.editIssue(issue, issueKey), diff --git a/src/components/DetailView/Components/ReporterMenu.tsx b/src/components/DetailView/Components/ReporterMenu.tsx index a42b1d10..2eefa64d 100644 --- a/src/components/DetailView/Components/ReporterMenu.tsx +++ b/src/components/DetailView/Components/ReporterMenu.tsx @@ -2,8 +2,6 @@ import { Text, Group } from "@mantine/core"; import { showNotification } from "@mantine/notifications"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Issue } from "types"; -import { getIssueReporter } from "../../CreateIssue/queryFunctions"; - import { UserSelectMenu } from "../../common/UserSelect/UserSelectMenu"; export function ReporterMenu({ issueKey }: { issueKey: string }) { @@ -11,7 +9,7 @@ export function ReporterMenu({ issueKey }: { issueKey: string }) { const { data: issueReporter } = useQuery({ queryKey: ["issueReporter", issueKey], - queryFn: () => getIssueReporter(issueKey), + queryFn: () => window.provider.getIssueReporter(issueKey), enabled: !!issueKey, }); diff --git a/src/components/DetailView/Components/SplitIssue/SplitIssueCreate.tsx b/src/components/DetailView/Components/SplitIssue/SplitIssueCreate.tsx index f59d984f..0835c5fa 100644 --- a/src/components/DetailView/Components/SplitIssue/SplitIssueCreate.tsx +++ b/src/components/DetailView/Components/SplitIssue/SplitIssueCreate.tsx @@ -22,8 +22,6 @@ import { AttachmentFileInput, } from "../../../CreateIssue/Fields"; -import { createIssue, getAssignableUsersByProject, getCurrentUser } from "../../../CreateIssue/queryFunctions"; - export function SplitIssueCreate({ onCreate, onCancel, @@ -42,7 +40,7 @@ export function SplitIssueCreate({ const { data: currentUser } = useQuery({ queryKey: ["currentUser"], - queryFn: () => getCurrentUser(), + queryFn: () => window.provider.getCurrentUser(), }); const form = useForm({ @@ -61,19 +59,13 @@ export function SplitIssueCreate({ }); const { data: assignableUsers, isLoading } = useQuery({ - queryKey: ["assignableUsers", form.getInputProps("projectId").value], - queryFn: () => { - const relevantProject = projects.find( - (project) => project.id === form.getInputProps("projectId").value!, - )!; - - return getAssignableUsersByProject(relevantProject.key); - }, - enabled: !!projects && !!form.getInputProps("projectId").value, + queryKey: ["assignableUsers", selectedProject!.key], + queryFn: () => window.provider.getAssignableUsersByProject(selectedProject!.key), + enabled: !!projects, }); const mutation = useMutation({ - mutationFn: (issue: Issue) => createIssue(issue), + mutationFn: (issue: Issue) => window.provider.createIssue(issue), onError: () => { showNotification({ message: "The issue couldn't be created! 😢", diff --git a/src/components/DetailView/Components/StoryPointsEstimateMenu.tsx b/src/components/DetailView/Components/StoryPointsEstimateMenu.tsx index 8562a2c2..033992db 100644 --- a/src/components/DetailView/Components/StoryPointsEstimateMenu.tsx +++ b/src/components/DetailView/Components/StoryPointsEstimateMenu.tsx @@ -3,7 +3,6 @@ import { Text, Group, NumberInput, Chip, Loader, Box, useMantineTheme } from "@m import { showNotification } from "@mantine/notifications"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Issue } from "types"; -import { getEditableIssueFields } from "../../CreateIssue/queryFunctions"; export function StoryPointsEstimateMenu({ issueKey, @@ -27,7 +26,7 @@ export function StoryPointsEstimateMenu({ const { data: editableFields } = useQuery({ queryKey: ["editableFields", issueKey], - queryFn: () => getEditableIssueFields(issueKey), + queryFn: () => window.provider.getEditableIssueFields(issueKey), enabled: !!issueKey, }); diff --git a/src/components/EpicDetailView/Components/ChildIssues.tsx b/src/components/EpicDetailView/Components/ChildIssues.tsx index dfbe43c2..4ff2943a 100644 --- a/src/components/EpicDetailView/Components/ChildIssues.tsx +++ b/src/components/EpicDetailView/Components/ChildIssues.tsx @@ -51,7 +51,8 @@ export function ChildIssues({ issues }: { issues: Issue[] }) { setCreateIssueModalOpened(false)} + onCreate={() => setCreateIssueModalOpened(false)} /> diff --git a/src/components/EpicView/EpicView.tsx b/src/components/EpicView/EpicView.tsx index d37477b7..8ea562cf 100644 --- a/src/components/EpicView/EpicView.tsx +++ b/src/components/EpicView/EpicView.tsx @@ -79,7 +79,8 @@ export function EpicView() { setCreateIssueModalOpened(false)} + onCreate={() => setCreateIssueModalOpened(false)} /> diff --git a/src/components/layout/LayoutHeader.tsx b/src/components/layout/LayoutHeader.tsx index b43dcce5..74dde4ef 100644 --- a/src/components/layout/LayoutHeader.tsx +++ b/src/components/layout/LayoutHeader.tsx @@ -56,7 +56,8 @@ export function LayoutHeader() { setCreateIssueModalOpened(false)} + onCreate={() => setCreateIssueModalOpened(false)} />