Skip to content

Commit

Permalink
Migrate issue functionality in server (#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
maximilianruesch authored Nov 21, 2023
1 parent de6812c commit b0a36f7
Showing 1 changed file with 232 additions and 15 deletions.
247 changes: 232 additions & 15 deletions electron/providers/jira-server-provider/JiraServerProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export class JiraServerProvider implements IProvider {

private customFields = new Map<string, string>()

private reversedCustomFields = new Map<string, string>()

private getAuthHeader() {
return `Basic ${Buffer.from(
`${this.loginOptions.username}:${this.loginOptions.password}`
Expand Down Expand Up @@ -95,7 +97,7 @@ export class JiraServerProvider implements IProvider {
this.loginOptions.username = basicLoginOptions.username
this.loginOptions.password = basicLoginOptions.password

// await this.mapCustomFields()
await this.mapCustomFields()
return this.isLoggedIn()
}

Expand Down Expand Up @@ -139,6 +141,7 @@ export class JiraServerProvider implements IProvider {
.then((response) => {
response.data.forEach((field: { name: string; id: string }) => {
this.customFields.set(field.name, field.id)
this.reversedCustomFields.set(field.id, field.name)
})
resolve()
})
Expand Down Expand Up @@ -262,19 +265,25 @@ export class JiraServerProvider implements IProvider {
response.data.issues.map(async (element: JiraIssue) => ({
issueKey: element.key,
summary: element.fields.summary,
creator: element.fields.creator.name,
creator: element.fields.creator.displayName,
status: element.fields.status.name,
type: element.fields.issuetype.name,
storyPointsEstimate: await this.getIssueStoryPointsEstimate(
element.key
),
epic: element.fields.epic?.name,
storyPointsEstimate: await this.getIssueStoryPointsEstimate(element.key),
epic: 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,
subtasks: element.fields.subtasks,
created: element.fields.created,
updated: element.fields.updated,
comment: element.fields.comment,
projectId: element.fields.project.id,
sprint: element.fields.sprint,
attachments: element.fields.attachment,
}))
)
}
Expand Down Expand Up @@ -405,19 +414,63 @@ export class JiraServerProvider implements IProvider {
}

getIssuesBySprint(sprintId: number): Promise<Issue[]> {
throw new Error("Method not implemented for Jira Server")
return new Promise((resolve, reject) => {
this.getAgileRestApiClient('1.0')
.get(`/sprint/${sprintId}/issue`)
.then(async (response) => {
resolve(this.fetchIssues(response))
})
.catch((error) => {
reject(new Error(`Error fetching issues by sprint ${sprintId}: ${error}`))
})
})
}

getLabels(): Promise<string[]> {
throw new Error("Method not implemented for Jira Server")
}

getPriorities(): Promise<Priority[]> {
throw new Error("Method not implemented for Jira Server")
return new Promise((resolve, reject) => {
this.getRestApiClient(2)
.get('/priority')
.then((response) => {
const priorityData: Priority[] = response.data
resolve(priorityData)
})
.catch((error) =>
reject(new Error(`Error in fetching priorities: ${error}`))
)
})
}

getIssueTypesWithFieldsMap(): Promise<{ [key: string]: string[] }> {
throw new Error("Method not implemented for Jira Server")
return new Promise((resolve) => {
// IMPROVE: This is barely scalable
this.getProjects()
.then(async (projects) => {
const issueTypeToFieldsMap: { [key: string]: string[] } = {}
await Promise.all(projects.map((project) =>
// IMPROVE: This call currently only supports 50 issue types
this.getRestApiClient(2)
.get(`/issue/createmeta/${project.id}/issuetypes`)
.then(async (response) => {
await Promise.all(response.data.values.map((issueType: { id: string }) =>
// IMPROVE: This call currently only supports 50 issue types
this.getRestApiClient(2)
.get(`/issue/createmeta/${project.id}/issuetypes/${issueType.id}`)
.then((issueTypesResponse) => {
issueTypeToFieldsMap[issueType.id] = issueTypesResponse.data.values.map(
(issueTypeField: { fieldId: string }) => this.reversedCustomFields.get(issueTypeField.fieldId)!
)
})
))
})
))

return resolve(issueTypeToFieldsMap)
})
})
}

getResource(): Promise<Resource> {
Expand All @@ -429,7 +482,25 @@ export class JiraServerProvider implements IProvider {
}

deleteIssue(issueIdOrKey: string): Promise<void> {
throw new Error("Method not implemented for Jira Server")
return new Promise((resolve, reject) => {
this.getRestApiClient(2)
.delete(`/issue/${issueIdOrKey}?deleteSubtasks`)
.then(async () => { resolve() })
.catch((error) => {
let specificError = error
if (error.response) {
if (error.response.status === 403) {
specificError = new Error("The user does not have permission to delete the issue")
} else if (error.response.status === 404) {
specificError = new Error("The issue was not found or the user does not have the necessary permissions")
} else if (error.response.status === 405) {
specificError = new Error("An anonymous call has been made to the operation")
}
}

reject(new Error(`Error deleting the issue ${issueIdOrKey}: ${specificError}`))
})
})
}

createSubtask(
Expand All @@ -441,20 +512,157 @@ export class JiraServerProvider implements IProvider {
throw new Error("Method not implemented for Jira Server")
}

editIssue(issue: Issue, issueIdOrKey: string): Promise<void> {
throw new Error("Method not implemented for Jira Server")
editIssue(
{
summary,
type,
projectId,
reporter,
assignee,
sprint,
storyPointsEstimate,
description,
epic,
startDate,
dueDate,
labels,
priority,
}: Issue,
issueIdOrKey: string
): Promise<void> {
const offsetStartDate = this.offsetDate(startDate)
const offsetDueDate = this.offsetDate(dueDate)

return new Promise((resolve, reject) => {
this.getRestApiClient(2)
.put(
`/issue/${issueIdOrKey}`,
{
fields: {
...(summary && {
summary,
}),
...(epic && {
parent: { key: epic },
}),
...(type && {
issuetype: { id: type },
}),
...(projectId && {
project: {
id: projectId,
},
}),
...(reporter && {
reporter: {
id: reporter,
},
}),
...(priority && priority.id && { priority }),
...(assignee &&
assignee.id && {
assignee,
}),
...(description && {
description
}),
...(labels && {
labels,
}),
...(offsetStartDate && {
[this.customFields.get("Start date")!]: offsetStartDate,
}),
...(offsetDueDate && {
[this.customFields.get("Due date")!]: offsetDueDate,
}),
...(sprint && {
[this.customFields.get("Sprint")!]: sprint.id,
}),
...(storyPointsEstimate !== undefined && {
[this.customFields.get("Story point estimate")!]:
storyPointsEstimate,
}),
},
}
)
.then(async () => { resolve() })
.catch((error) => {
let specificError = error
if (error.response) {
if (error.response.status === 404) {
specificError = new Error(
"The issue was not found or the user does not have the necessary permissions"
)
}
}

reject(new Error(`Error creating issue: ${specificError}`))
})
})
}

setTransition(issueIdOrKey: string, targetStatus: string): Promise<void> {
throw new Error("Method not implemented for Jira Server")
return new Promise((resolve, reject) => {
this.getRestApiClient(2)
.get(`/issue/${issueIdOrKey}/transitions`)
.then((response) => {
const transitions = new Map<string, string>()
response.data.transitions.forEach((field: { name: string; id: string }) => {
transitions.set(field.name, field.id)
})

const transitionId = +transitions.get(targetStatus)!

return this
.getRestApiClient(2)
.post(
`/issue/${issueIdOrKey}/transitions`,
{ transition: { id: transitionId } }
)
})
.then(() => resolve())
.catch((error) => {
reject(new Error(`Error setting transition: ${error}`))
})
})
}

getEditableIssueFields(issueIdOrKey: string): Promise<string[]> {
throw new Error("Method not implemented for Jira Server")
return new Promise((resolve, reject) => {
this.getRestApiClient(2)
.get(`/issue/${issueIdOrKey}/editmeta`)
.then(async (response) => {
const fieldKeys = Object.keys(response.data.fields).map(
(fieldKey) => this.reversedCustomFields.get(fieldKey)!
)
resolve(fieldKeys)
})
.catch((error) =>
reject(new Error(`Error in fetching the issue types map: ${error}`))
)
})
}

getIssueReporter(issueIdOrKey: string): Promise<User> {
throw new Error("Method not implemented for Jira Server")
return new Promise((resolve, reject) => {
this.getRestApiClient(2)
.get(`/issue/${issueIdOrKey}?fields=reporter`)
.then(async (response) => {
resolve(response.data.fields.reporter as User)
})
.catch((error) => {
let specificError = error
if (error.response) {
if (error.response.status === 404) {
specificError = new Error(
`The issue was not found or the user does not have permission to view it: ${error.response.data}`
)
}
}

reject(new Error(`Error in fetching the issue reporter: ${specificError}`))
})
})
}

addCommentToIssue(issueIdOrKey: string, commentText: string): Promise<void> {
Expand All @@ -479,4 +687,13 @@ export class JiraServerProvider implements IProvider {
}): Promise<void> {
throw new Error("Method not implemented for Jira Server")
}

offsetDate(date: Date) {
if (!date) {
return date
}
const convertedDate = new Date(date)
const timezoneOffset = convertedDate.getTimezoneOffset()
return new Date(convertedDate.getTime() - timezoneOffset * 60 * 1000)
}
}

0 comments on commit b0a36f7

Please sign in to comment.