Skip to content

Commit

Permalink
Merge branch 'main' into story/add-comments-to-epic-detail-view
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/components/EpicDetailView/EpicDetailView.tsx
  • Loading branch information
maximilianruesch committed Dec 20, 2023
2 parents 7f03873 + 8aeffef commit a11fa20
Show file tree
Hide file tree
Showing 27 changed files with 681 additions and 174 deletions.
6 changes: 4 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@ VITE_CLIENT_ID=
VITE_CLIENT_SECRET=
VITE_REDIRECT_URI=



# JIRA SERVER CREDENTIALS
VITE_JIRA_SERVER_DEFAULT_URL=
VITE_JIRA_SERVER_DEFAULT_USERNAME=
VITE_JIRA_SERVER_DEFAULT_PASSWORD=
38 changes: 26 additions & 12 deletions electron/providers/jira-cloud-provider/JiraCloudProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,9 +410,9 @@ export class JiraCloudProvider implements IProvider {
async getIssuesByProject(project: string): Promise<Issue[]> {
return new Promise((resolve, reject) => {
this.getRestApiClient(3)
.get(`/search?jql=project=${project}&maxResults=10000`)
.get(`/search?jql=project=${project}&maxResults=10000&fields=*all`)
.then(async (response) => {
resolve(this.fetchIssues(response))
resolve(this.fetchIssues(response, false))
})
.catch((error) => {
reject(new Error(`Error fetching issues by project: ${this.handleFetchIssuesError(error)}`))
Expand All @@ -425,7 +425,7 @@ export class JiraCloudProvider implements IProvider {
this.getAgileRestApiClient('1.0')
.get(`/sprint/${sprintId}/issue`)
.then(async (response) => {
resolve(this.fetchIssues(response))
resolve(this.fetchIssues(response, true))
})
.catch((error) => {
reject(new Error(`Error fetching issues by sprint: ${this.handleFetchIssuesError(error)}`))
Expand All @@ -441,15 +441,15 @@ export class JiraCloudProvider implements IProvider {
this.getAgileRestApiClient('1.0')
.get(`/board/${boardId}/backlog?jql=project=${project}&maxResults=500`)
.then(async (response) => {
resolve(this.fetchIssues(response))
resolve(this.fetchIssues(response, true))
})
.catch((error) => {
reject(new Error(`Error fetching issues by project: ${this.handleFetchIssuesError(error)}`))
})
})
}

async fetchIssues(response: AxiosResponse): Promise<Issue[]> {
async fetchIssues(response: AxiosResponse, isAgile: boolean): Promise<Issue[]> {
const rankCustomField = this.customFields.get("Rank") || ""
return new Promise((resolve) => {
const issues: Promise<Issue[]> = Promise.all(
Expand All @@ -460,18 +460,30 @@ export class JiraCloudProvider implements IProvider {
status: element.fields.status.name,
type: element.fields.issuetype.name,
storyPointsEstimate: await this.getIssueStoryPointsEstimate(element.key),
epic: element.fields.parent?.fields.summary,
epic: {
issueKey: element.fields.parent?.key,
summary: element.fields.parent?.fields.summary,
},
labels: element.fields.labels,
assignee: {
displayName: element.fields.assignee?.displayName,
avatarUrls: element.fields.assignee?.avatarUrls,
},
rank: element.fields[rankCustomField],
description: element.fields.description,
// IMPROVE: Remove boolean flag
description: (isAgile ? element.fields.description : element.fields.description?.content),
subtasks: element.fields.subtasks,
created: element.fields.created,
updated: element.fields.updated,
comment: element.fields.comment,
comment: {
comments: element.fields.comment.comments.map((commentElement) => ({
id: commentElement.id,
body: (isAgile ? commentElement.body : commentElement.body[0]?.content[0]?.text),
author: commentElement.author,
created: commentElement.created,
updated: commentElement.updated,
})),
},
projectId: element.fields.project.id,
sprint: element.fields.sprint,
attachments: element.fields.attachment,
Expand Down Expand Up @@ -659,7 +671,7 @@ export class JiraCloudProvider implements IProvider {
{
fields: {
summary,
parent: { key: epic },
parent: { key: epic.issueKey },
issuetype: { id: type },
project: {
id: projectId,
Expand Down Expand Up @@ -757,8 +769,8 @@ export class JiraCloudProvider implements IProvider {
...(summary && {
summary,
}),
...(epic && {
parent: { key: epic },
...(epic && epic.issueKey && {
parent: { key: epic.issueKey },
}),
...(type && {
issuetype: { id: type },
Expand Down Expand Up @@ -821,7 +833,7 @@ export class JiraCloudProvider implements IProvider {
}
}

reject(new Error(`Error creating issue: ${specificError}`))
reject(new Error(`Error editing issue: ${specificError}`))
})
})
}
Expand Down Expand Up @@ -854,7 +866,9 @@ export class JiraCloudProvider implements IProvider {
response.data.issues.map(async (element: JiraEpic) => ({
issueKey: element.key,
summary: element.fields.summary,
epic: element.fields.epic,
labels: element.fields.labels,
description: element.fields.description.content[0]?.content[0]?.text,

Check failure on line 871 in electron/providers/jira-cloud-provider/JiraCloudProvider.ts

View workflow job for this annotation

GitHub Actions / Type Check

Property 'content' does not exist on type 'string'.
assignee: {
displayName: element.fields.assignee?.displayName,
avatarUrls: element.fields.assignee?.avatarUrls,
Expand Down
16 changes: 11 additions & 5 deletions electron/providers/jira-server-provider/JiraServerProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ export class JiraServerProvider implements IProvider {
async getIssuesByProject(project: string, boardId: number): Promise<Issue[]> {
return new Promise((resolve, reject) => {
this.getAgileRestApiClient('1.0')
.get(`/board/${boardId}/issue?jql=project=${project}&maxResults=10000`)
.get(`/board/${boardId}/issue?jql=project=${project}&maxResults=10000&fields=*all`)
.then((response) => resolve(this.fetchIssues(response)))
.catch((error) => reject(new Error(`Error in fetching issues: ${error}`)))
})
Expand Down Expand Up @@ -336,7 +336,10 @@ export class JiraServerProvider implements IProvider {
status: element.fields.status.name,
type: element.fields.issuetype.name,
storyPointsEstimate: await this.getIssueStoryPointsEstimate(element.key),
epic: element.fields.parent?.fields.summary,
epic: {
issueKey: element.fields.parent?.key,
summary: element.fields.parent?.fields.summary,
},
labels: element.fields.labels,
assignee: {
displayName: element.fields.assignee?.displayName,
Expand Down Expand Up @@ -515,7 +518,7 @@ export class JiraServerProvider implements IProvider {
{
fields: {
summary,
parent: { key: epic },
parent: { key: epic.issueKey },
issuetype: { id: type },
project: {
id: projectId,
Expand Down Expand Up @@ -583,6 +586,9 @@ export class JiraServerProvider implements IProvider {
issueKey: element.key,
summary: element.fields.summary,
labels: element.fields.labels,
created: element.fields.created,
updated: element.fields.updated,
description: element.fields.description.content,

Check failure on line 591 in electron/providers/jira-server-provider/JiraServerProvider.ts

View workflow job for this annotation

GitHub Actions / Type Check

Property 'content' does not exist on type 'string'.
assignee: {
displayName: element.fields.assignee?.displayName,
avatarUrls: element.fields.assignee?.avatarUrls,
Expand Down Expand Up @@ -869,8 +875,8 @@ export class JiraServerProvider implements IProvider {
...(summary && {
summary,
}),
...(epic && {
parent: { key: epic },
...(epic && epic.issueKey && {
parent: { key: epic.issueKey },
}),
...(type && {
issuetype: { id: type },
Expand Down
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@
"lint": "eslint ./src",
"test": "jest",
"typecheck": "tsc --noEmit",
"preview": "vite preview",
"pree2e": "yarn run package",
"e2e": "playwright test",
"canvas": "yarn --cwd project-canvas start",
"prepare": "husky install",
"pre-commit": "lint-staged"
},
Expand Down Expand Up @@ -101,6 +99,6 @@
},
"repository": {
"type": "git",
"url": "https://github.com/TU-TeamCanvas/ProjectCanvas"
"url": "https://github.com/MaibornWolff/ProjectCanvas"
}
}
3 changes: 2 additions & 1 deletion src/components/BacklogView/Issue/DeleteButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ export function DeleteButton({
}} />
<DeleteIssueAlert
issueKey={issueKey}
closeModal={() => setIssuePopoverOpened(false)}
cancelAlert={() => setIssuePopoverOpened(false)}
confirmAlert={() => setIssuePopoverOpened(false)}
/>
</Popover.Dropdown>
</Popover>
Expand Down
4 changes: 2 additions & 2 deletions src/components/BacklogView/Issue/IssueCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,9 @@ export function IssueCard({
>
{issueKey}
</Text>
{epic && (
{epic.issueKey && (
<Badge mr={5} color="violet">
{epic}
{epic.summary}
</Badge>
)}
{labels?.length !== 0 &&
Expand Down
2 changes: 1 addition & 1 deletion src/components/BacklogView/helpers/backlogHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export const searchIssuesFilter = (
.issues.filter(
(issue: Issue) =>
issue.summary.toLowerCase().includes(currentSearch.toLowerCase()) ||
issue.epic?.toLowerCase().includes(currentSearch.toLowerCase()) ||
issue.epic.summary?.toLowerCase().includes(currentSearch.toLowerCase()) ||
issue.assignee?.displayName
?.toLowerCase()
.includes(currentSearch.toLowerCase()) ||
Expand Down
5 changes: 5 additions & 0 deletions src/components/BacklogView/helpers/queryFetchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import { Issue, Sprint, SprintCreate } from "types"
export const getSprints = (boardId: number): Promise<Sprint[]> =>
window.provider.getSprints(boardId)

export const getIssuesByProject = (
projectKey: string | undefined,
boardId: number
): Promise<Issue[]> => window.provider.getIssuesByProject(projectKey || "", boardId)

export const getIssuesBySprint = (
sprintId: number | undefined
): Promise<Issue[]> => window.provider.getIssuesBySprint(sprintId || 0)
Expand Down
1 change: 1 addition & 0 deletions src/components/CreateIssue/CreateIssueModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export function CreateIssueModal({
status: "To Do",
reporter: currentUser,
priority: { id: "" },
epic: { issueKey: undefined }
} as Issue,
})

Expand Down
2 changes: 1 addition & 1 deletion src/components/CreateIssue/Fields/EpicSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export function EpicSelect({
searchable
clearable
withinPortal
{...form.getInputProps("epic")}
{...form.getInputProps("epic.issueKey")}
/>
</Box>
</Tooltip>
Expand Down
2 changes: 1 addition & 1 deletion src/components/CreateIssue/Fields/IssueTypeSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function IssueTypeSelect({
) {
form.setFieldValue("sprintId", null as unknown as string)
form.setFieldValue("storyPointsEstimate", null as unknown as number)
form.setFieldValue("epic", null as unknown as string)
form.setFieldValue("epic.issueKey", undefined)
}
form.setFieldValue("status", "To Do")
form.setFieldValue("priority.id", null)
Expand Down
23 changes: 20 additions & 3 deletions src/components/DetailView/Components/DeleteIssue/DeleteIssue.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Button, Popover } from "@mantine/core"
import { IconTrash } from "@tabler/icons"
import {useState} from "react";
import { DeleteIssueAlert } from "./DeleteIssueAlert"

export function DeleteIssue({
Expand All @@ -9,10 +10,22 @@ export function DeleteIssue({
issueKey: string
closeModal: () => void
}) {
const [issuePopoverOpened, setIssuePopoverOpened] = useState(false)

return (
<Popover width="40vh" trapFocus position="bottom" withArrow shadow="md">
<Popover
width="40vh"
trapFocus
position="bottom"
withArrow
shadow="md"
opened={issuePopoverOpened}
>
<Popover.Target>
<Button color="red" rightIcon={<IconTrash size={16} />}>
<Button color="red" rightIcon={<IconTrash size={16} />} onClick={(e) => {
e.stopPropagation()
setIssuePopoverOpened(!issuePopoverOpened)
}}>
Delete
</Button>
</Popover.Target>
Expand All @@ -22,7 +35,11 @@ export function DeleteIssue({
theme.colorScheme === "dark" ? theme.colors.dark[7] : theme.white,
})}
>
<DeleteIssueAlert issueKey={issueKey} closeModal={closeModal} />
<DeleteIssueAlert
issueKey={issueKey}
cancelAlert={() => setIssuePopoverOpened(false)}
confirmAlert={closeModal}
/>
</Popover.Dropdown>
</Popover>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@ import { deleteIssueMutation } from "./queries"

export function DeleteIssueAlert({
issueKey,
closeModal,
cancelAlert,
confirmAlert,
}: {
issueKey: string
closeModal: () => void
cancelAlert: () => void
confirmAlert: () => void
}) {
const queryClient = useQueryClient()
const deleteIssue = deleteIssueMutation(queryClient)

return (
<Stack onMouseLeave={closeModal}>
<Stack onMouseLeave={cancelAlert}>
<Alert
icon={<IconAlertCircle size={16} />}
title="Attention!"
Expand All @@ -26,7 +28,7 @@ export function DeleteIssueAlert({
onClick={(e) => {
e.stopPropagation()
deleteIssue.mutate(issueKey)
closeModal()
confirmAlert()
}}
>
Confirm
Expand Down
22 changes: 12 additions & 10 deletions src/components/DetailView/Components/EditableEpic/EditableEpic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,22 @@ export function EditableEpic({
}: {
projectId: string
issueKey: string
epic: string
epic: {
issueKey?: string,
summary?: string,
}
}) {
const [showEpicInput, setshowEpicInput] = useState(false)
const [showEpicInput, setShowEpicInput] = useState(false)
const { data: epics } = useQuery({
queryKey: ["epics", projectId],
queryFn: () => getEpicsByProject(projectId),
})
const [selectedEpic, setselectedEpic] = useState(epic)
const [selectedEpic, setSelectedEpic] = useState(epic.issueKey)
const mutationEpic = useMutation({
mutationFn: (epicKey: string) =>
editIssue({ epic: epicKey } as Issue, issueKey),
mutationFn: (epicKey: string) => editIssue({ epic: { issueKey: epicKey } } as Issue, issueKey),
onError: () => {
showNotification({
message: `An error occured while modifing the Epic 😢`,
message: `An error occurred while modifing the Epic 😢`,
color: "red",
})
},
Expand All @@ -49,7 +51,7 @@ export function EditableEpic({
placeholder=""
nothingFound="No Options"
data={
epics && epics instanceof Array
epics
? epics.map((epicItem) => ({
value: epicItem.issueKey,
label: epicItem.summary,
Expand All @@ -61,16 +63,16 @@ export function EditableEpic({
itemComponent={SelectItem}
value={selectedEpic}
onChange={(value) => {
setselectedEpic(value!)
setSelectedEpic(value!)
mutationEpic.mutate(value!)
setshowEpicInput(false)
setShowEpicInput(false)
}}
w="300px"
/>
) : (
<Group>
<Text
onClick={() => setshowEpicInput(true)}
onClick={() => setShowEpicInput(true)}
sx={{
":hover": { textDecoration: "underline", cursor: "pointer" },
}}
Expand Down
Loading

0 comments on commit a11fa20

Please sign in to comment.