Skip to content

Commit

Permalink
Update tanstack-query monorepo to v5 (major) (#117)
Browse files Browse the repository at this point in the history
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Maximilian Rüsch <[email protected]>
  • Loading branch information
renovate[bot] and maximilianruesch authored Jan 18, 2024
1 parent 6eb095a commit 1b14602
Show file tree
Hide file tree
Showing 13 changed files with 166 additions and 346 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
"@mantine/hooks": "^7.0.0",
"@mantine/notifications": "^7.0.0",
"@tabler/icons-react": "^2.44.0",
"@tanstack/react-query": "^4.23.0",
"@tanstack/react-query-devtools": "^4.23.0",
"@tanstack/react-query": "^5.0.0",
"@tanstack/react-query-devtools": "^5.0.0",
"@types/file-saver": "^2.0.5",
"@types/lodash": "^4.14.202",
"axios": "^1.6.1",
Expand Down
170 changes: 67 additions & 103 deletions src/components/BacklogView/BacklogView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@ import {
Title,
} from "@mantine/core"
import { IconSearch } from "@tabler/icons-react"
import { useQueries, useQuery } from "@tanstack/react-query"
import { ChangeEvent, useEffect, useState } from "react"
import {QueriesResults, useQueries, useQuery} from "@tanstack/react-query"
import { ChangeEvent, useEffect, useMemo, useState } from "react"
import { DragDropContext } from "@hello-pangea/dnd"
import { useNavigate } from "react-router-dom"
import { Issue, Sprint } from "types"
import { UseQueryOptions } from "@tanstack/react-query/src/types";
import { useCanvasStore } from "../../lib/Store"
import { CreateIssueModal } from "../CreateIssue/CreateIssueModal"
import { CreateExportModal } from "../CreateExport/CreateExportModal"
import { CreateSprint } from "./CreateSprint/CreateSprint"
import { searchIssuesFilter, sortIssuesByRank } from "./helpers/backlogHelpers"
import { BacklogKey, IssuesState, searchMatchesIssue, sortIssuesByRank } from "./helpers/backlogHelpers"
import { onDragEnd } from "./helpers/draggingHelpers"
import {
getBacklogIssues,
Expand All @@ -46,93 +47,68 @@ export function BacklogView() {
const [search, setSearch] = useState("")
const [createExportModalOpened, setCreateExportModalOpened] = useState(false)

const [issuesWrappers, setIssuesWrappers] = useState(
new Map<string, { issues: Issue[]; sprint?: Sprint }>()
)
const [searchedissuesWrappers, setSearchedissuesWrappers] = useState(
new Map<string, { issues: Issue[]; sprint?: Sprint }>()
)
const updateIssuesWrapper = (
key: string,
value: { issues: Issue[]; sprint?: Sprint }
) => {
setIssuesWrappers((map) => new Map(map.set(key, value)))
setSearchedissuesWrappers((map) => new Map(map.set(key, value)))
}

const { data: sprints, isError: isErrorSprints } = useQuery({
queryKey: ["sprints", currentBoardId],
queryFn: () => getSprints(currentBoardId),
enabled: !!currentBoardId,
select: (fetchedSprints: Sprint[]) => Object.fromEntries(fetchedSprints.map((s) => [s.id, s])),
initialData: [],
})

const sprintsIssuesResults = useQueries({
queries:
!isErrorSprints && sprints
? sprints?.map((sprint) => ({
queryKey: ["issues", "sprints", projectKey, sprints, sprint.id],
queryFn: () => getIssuesBySprint(sprint.id),
enabled: !!projectKey && !!sprints,
onSuccess: (issues: Issue[]) => {
updateIssuesWrapper(sprint.name, {
sprint,
issues: issues
.filter(
(issue: Issue) =>
issue.type !== "Epic" && issue.type !== "Subtask"
)
.sort((issueA: Issue, issueB: Issue) =>
sortIssuesByRank(issueA, issueB)
),
})
searchIssuesFilter(
search,
issuesWrappers,
searchedissuesWrappers,
setSearchedissuesWrappers
)
},
}))
: [],
})
const isErrorSprintsIssues = sprintsIssuesResults.some(
({ isError }) => isError
)

const { isLoading: isLoadingBacklogIssues, isError: isErrorBacklogIssues } =
useQuery({
queryKey: ["issues", projectKey, currentBoardId],
queryFn: () => getBacklogIssues(projectKey, currentBoardId),
enabled: !!projectKey,
onSuccess: (backlogIssues) => {
updateIssuesWrapper("Backlog", {
sprint: undefined,
issues:
backlogIssues && backlogIssues instanceof Array
? backlogIssues
.filter(
(issue: Issue) =>
issue.type !== "Subtask"
)
.sort((issueA: Issue, issueB: Issue) =>
sortIssuesByRank(issueA, issueB)
)
: [],
})
searchIssuesFilter(
search,
issuesWrappers,
searchedissuesWrappers,
setSearchedissuesWrappers
)
const issueQueries = useQueries<Array<UseQueryOptions<Issue[], unknown, [string, IssuesState]>>>({
queries: [
{
queryKey: ["issues", projectKey, currentBoardId], // IMPROVE: Change this issue key to contain "backlog"
queryFn: () => getBacklogIssues(projectKey, currentBoardId),
enabled: !!projectKey,
select: (backlogIssues: Issue[]): [string, IssuesState] => [
BacklogKey,
{
issues: backlogIssues
.filter((issue: Issue) => issue.type !== "Epic" && issue.type !== "Subtask")
.sort(sortIssuesByRank),
sprintId: undefined
},
],
initialData: [],
},
})
...(Object.values(sprints).map((sprint): UseQueryOptions<Issue[], unknown, [string, IssuesState]> => ({
queryKey: ["issues", "sprints", projectKey, Object.keys(sprints), sprint.id], // IMPROVE: Change this issue key to not contain sprints
queryFn: () => getIssuesBySprint(sprint.id),
enabled: !!projectKey && !!sprints && !isErrorSprints,
select: (issues: Issue[]) => [
sprint.name,
{
issues: issues
.filter((issue: Issue) => issue.type !== "Epic" && issue.type !== "Subtask")
.sort(sortIssuesByRank),
sprintId: sprint.id
},
],
initialData: [],
}))),
],
combine: (results: QueriesResults<Array<UseQueryOptions<Issue[], unknown, [string, IssuesState]>>>) =>
results.map(result => result)
})

const [issuesWrapper, setIssuesWrapper] = useState(new Map<string, IssuesState>());
useEffect(() => {
resizeDivider()
}, [isLoadingBacklogIssues])
// Generally, using useEffect to sync state should be avoided. But since we need our state to be assignable AND
// reactive AND derivable, we found no other solution than to use useEffect.
setIssuesWrapper(new Map<string, IssuesState>(issueQueries.map((query) => query.data!)))
}, [issueQueries])
const updateIssuesWrapper = (key: string, newState: IssuesState) => setIssuesWrapper(new Map(issuesWrapper.set(key, newState)))
const searchedIssuesWrapper = useMemo(() => new Map<string, Issue[]>(
Array.from(issuesWrapper.keys()).map((key) => [
key,
issuesWrapper.get(key)!.issues.filter((i) => searchMatchesIssue(search, i)),
]),
), [issuesWrapper, search])

useEffect(resizeDivider, [issueQueries]);

if (isErrorSprints || isErrorBacklogIssues || isErrorSprintsIssues)
if (isErrorSprints || issueQueries.some(query => query.isError))
return (
<Center style={{ width: "100%", height: "100%" }}>
<Text w="300">
Expand All @@ -144,18 +120,8 @@ export function BacklogView() {
</Center>
)

const handleSearchChange = (event: ChangeEvent<HTMLInputElement>) => {
const currentSearch = event.currentTarget.value
setSearch(currentSearch)
searchIssuesFilter(
currentSearch,
issuesWrappers,
searchedissuesWrappers,
setSearchedissuesWrappers
)
}

if (isLoadingBacklogIssues)
// This check might be broken. It does not trigger everytime we think it does. Might need to force a rerender.
if (issueQueries.some(query => query.isPending))
return (
<Center style={{ width: "100%", height: "100%" }}>
{projectKey ? (
Expand Down Expand Up @@ -206,7 +172,7 @@ export function BacklogView() {
placeholder="Search by issue summary, key, epic, labels, creator or assignee.."
leftSection={<IconSearch size={14} stroke={1.5} />}
value={search}
onChange={handleSearchChange}
onChange={(event: ChangeEvent<HTMLInputElement>) => { setSearch(event.currentTarget.value) }}
/>
</Stack>

Expand All @@ -215,7 +181,7 @@ export function BacklogView() {
onDragEnd={(dropResult) =>
onDragEnd({
...dropResult,
issuesWrappers,
issuesWrapper,
updateIssuesWrapper,
})
}
Expand All @@ -229,11 +195,11 @@ export function BacklogView() {
minWidth: "260px",
}}
>
{searchedissuesWrappers.get("Backlog") && (
{searchedIssuesWrapper.get(BacklogKey) && ( // IMPROVE: Maybe this check can be removed entirely, please evaluate
<Box mr="xs">
<DraggableIssuesWrapper
id="Backlog"
issues={searchedissuesWrappers.get("Backlog")!.issues.filter(issue => issue.type !== "Epic")}
issues={searchedIssuesWrapper.get(BacklogKey)!}
/>
</Box>
)}
Expand Down Expand Up @@ -284,13 +250,11 @@ export function BacklogView() {
}}
>
<SprintsPanel
sprintsWithIssues={
Array.from(searchedissuesWrappers.values()).filter(
(issuesWrapper) => issuesWrapper.sprint !== undefined
) as unknown as {
issues: Issue[]
sprint: Sprint
}[]
sprints={sprints}
issueWrapper={
Object.fromEntries(Array.from(searchedIssuesWrapper.keys())
.filter((key) => key !== BacklogKey)
.map((key) => [issuesWrapper.get(key)!.sprintId, searchedIssuesWrapper.get(key)!]))
}
/>
<CreateSprint />
Expand Down
16 changes: 10 additions & 6 deletions src/components/BacklogView/IssuesWrapper/SprintsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import {storyPointsAccumulator} from "../../common/StoryPoints/status-accumulato
import {useCanvasStore} from "../../../lib/Store";

export function SprintsPanel({
sprintsWithIssues,
sprints,
issueWrapper,
}: {
sprintsWithIssues: { issues: Issue[]; sprint: Sprint }[]
sprints: { [_: string]: Sprint }
issueWrapper: { [_: string]: Issue[] }
}) {
const colorScheme = useColorScheme()

Expand Down Expand Up @@ -43,10 +45,12 @@ export function SprintsPanel({
},
})}
>
{sprintsWithIssues
.sort(({ sprint: sprintA }, { sprint: sprintB }) =>
sortSprintsByActive(sprintA, sprintB)
)
{Object.keys(issueWrapper)
.map((sprintId) => ({
issues: issueWrapper[sprintId],
sprint: sprints[sprintId]
}))
.sort(({ sprint: sprintA }, { sprint: sprintB }) => sortSprintsByActive(sprintA, sprintB))
.map(({ issues, sprint }) => (
<Accordion.Item
key={`accordion-item-key-${sprint.name}`}
Expand Down
67 changes: 11 additions & 56 deletions src/components/BacklogView/helpers/backlogHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Issue, Sprint } from "types"
import { Dispatch, SetStateAction } from "react"

export const BacklogKey = "Backlog";
export type IssuesState = { issues: Issue[], sprintId?: number };

export const pluralize = (count: number, noun: string, suffix = "s") =>
`${count} ${noun}${count !== 1 ? suffix : ""}`
Expand All @@ -17,58 +19,11 @@ export const sortSprintsByActive = (sprintA: Sprint, sprintB: Sprint) => {
export const sortIssuesByRank = (issueA: Issue, issueB: Issue) =>
issueA.rank.localeCompare(issueB.rank)

export const searchIssuesFilter = (
currentSearch: string,
issuesWrappers: Map<
string,
{
issues: Issue[]
sprint?: Sprint | undefined
}
>,
searchedissueWrapper: Map<
string,
{
issues: Issue[]
sprint?: Sprint | undefined
}
>,
setSearchedissueWrapper: Dispatch<
SetStateAction<
Map<
string,
{
issues: Issue[]
sprint?: Sprint | undefined
}
>
>
>
) => {
searchedissueWrapper.forEach((issueWrapper, issueWrapperKey) => {
const newIssueWrapper: {
issues: Issue[]
sprint?: Sprint | undefined
} = { issues: [], sprint: issueWrapper.sprint }
newIssueWrapper.sprint = issueWrapper.sprint
newIssueWrapper.issues = issuesWrappers
.get(issueWrapperKey)!
.issues.filter(
(issue: Issue) =>
issue.summary.toLowerCase().includes(currentSearch.toLowerCase()) ||
issue.epic.summary?.toLowerCase().includes(currentSearch.toLowerCase()) ||
issue.assignee?.displayName
?.toLowerCase()
.includes(currentSearch.toLowerCase()) ||
issue.issueKey.toLowerCase().includes(currentSearch.toLowerCase()) ||
issue.creator?.toLowerCase().includes(currentSearch.toLowerCase()) ||
issue.labels?.some((label: string) =>
label.toLowerCase().includes(currentSearch.toLowerCase())
) ||
currentSearch === ""
)
setSearchedissueWrapper(
(map) => new Map(map.set(issueWrapperKey, newIssueWrapper))
)
})
}
export const searchMatchesIssue = (search: string, issue: Issue) =>
search === "" ||
issue.summary.toLowerCase().includes(search.toLowerCase()) ||
issue.epic.summary?.toLowerCase().includes(search.toLowerCase()) ||
issue.assignee?.displayName?.toLowerCase().includes(search.toLowerCase()) ||
issue.issueKey.toLowerCase().includes(search.toLowerCase()) ||
issue.creator?.toLowerCase().includes(search.toLowerCase()) ||
issue.labels?.some((label: string) => label.toLowerCase().includes(search.toLowerCase()))
Loading

0 comments on commit 1b14602

Please sign in to comment.