Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow different implementations for server versions #74

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 113 additions & 5 deletions electron/providers/jira-server-provider/JiraServerProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from "../../../types"
import {JiraIssue, JiraIssueType, JiraProject, JiraSprint,} from "../../../types/jira"
import {IProvider} from "../base-provider"
import {JiraServerUser} from "./server-types";
import {JiraServerInfo, JiraServerUser} from "./server-types";

export class JiraServerProvider implements IProvider {
private loginOptions = {
Expand All @@ -22,10 +22,55 @@ export class JiraServerProvider implements IProvider {
password: "",
}

private serverInfo?: JiraServerInfo = undefined

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

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

private executeVersioned<R>(functionsByVersionMatcher: { [versionMatcher: string]: (...args: never[]) => R }, ...args: never[]) {
if (!this.serverInfo) {
throw new Error('Server info not set!')
}

const matches = (matcher: string): boolean => {
let match = true
matcher.split('.').forEach((matcherPart, index) => {
match = match && (
matcherPart === '*'
|| matcherPart === this.serverInfo!.versionNumbers[index].toString()
)
})

return match
}

const isAMoreSpecificThanB = (matcherA: string, matcherB: string): boolean => {
const matcherBParts = matcherB.split('.')
let isMoreSpecific = false;
matcherA.split('.').forEach((matcherAPart, index) => {
if (matcherBParts[index] === '*' && matcherAPart !== '*') {
isMoreSpecific = true;
}
})

return isMoreSpecific;
}

let selectedMatcher: string | undefined
Object.keys(functionsByVersionMatcher).forEach((matcher) => {
if (matches(matcher) && (selectedMatcher === undefined || isAMoreSpecificThanB(matcher, selectedMatcher))) {
selectedMatcher = matcher
}
})

if (!selectedMatcher) {
throw new Error(`No version matcher found for version: ${this.serverInfo.version}`)
}

return functionsByVersionMatcher[selectedMatcher](...args)
}

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

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

async getServerInfo(): Promise<void> {
return new Promise((resolve, reject) => {
this.getRestApiClient(2)
.get('/serverInfo')
.then((response: AxiosResponse<JiraServerInfo>) => {
this.serverInfo = response.data
if (this.serverInfo.versionNumbers[0] < 7) {
reject(new Error(
`Your Jira server version is unsupported. Minimum major version: 7. Your version: ${this.serverInfo.versionNumbers[0]}`,
))
}

resolve()
})
.catch((error) => {
reject(new Error(`Error in checking server info: ${error}`))
})
})
}

async isLoggedIn(): Promise<void> {
return new Promise((resolve, reject) => {
this.getAuthRestApiClient(1)
Expand Down Expand Up @@ -292,16 +358,24 @@ export class JiraServerProvider implements IProvider {
async moveIssueToSprintAndRank(
sprint: number,
issue: string,
rankBefore: string,
rankAfter: string
): Promise<void> {
return new Promise((resolve, reject) => {
const rankCustomField = this.customFields.get("Rank")
this.getAgileRestApiClient('1.0')
.post(
`/sprint/${sprint}/issue`,
{ issues: [issue] } // Ranking issues in the sprints is not supported by Jira server
{
rankCustomFieldId: rankCustomField!.match(/_(\d+)/)![1],
issues: [issue],
...(rankAfter && { rankAfterIssue: rankAfter }),
...(rankBefore && { rankBeforeIssue: rankBefore }),
}
)
.then(() => resolve())
.catch((error) => {
reject(new Error(`Error in moving this issue to the Sprint with id ${sprint}: ${JSON.stringify(error.response.data)}`))
reject(new Error(`Error in moving this issue to the Sprint with id ${sprint}: ${error}`))
})
})
}
Expand Down Expand Up @@ -345,7 +419,7 @@ export class JiraServerProvider implements IProvider {
.put('/issue/rank', body)
.then(() => resolve())
.catch((error) =>
reject(new Error(`Error in ranking this issue in the Backlog: ${error}`))
reject(new Error(`Error in moving this issue to the Backlog: ${error}`))
)
})
}
Expand Down Expand Up @@ -573,6 +647,40 @@ export class JiraServerProvider implements IProvider {
}

getIssueTypesWithFieldsMap(): Promise<{ [key: string]: string[] }> {
return this.executeVersioned({
'7.*': this.getIssueTypesWithFieldsMap_7.bind(this),
'*': this.getIssueTypesWithFieldsMap_8and9.bind(this)
})
}

getIssueTypesWithFieldsMap_7(): Promise<{ [key: string]: string[] }> {
return new Promise((resolve) => {
this.getRestApiClient(2)
.get('/issue/createmeta?expand=projects.issuetypes.fields')
.then(async (response) => {
const issueTypeToFieldsMap: { [key: string]: string[] } = {}
response.data.projects.forEach(
(project: {
id: string
issuetypes: {
fields: {}
id: string
}[]
}) => {
project.issuetypes.forEach((issueType) => {
const fieldKeys = Object.keys(issueType.fields)
issueTypeToFieldsMap[issueType.id] = fieldKeys.map(
(fieldKey) => this.reversedCustomFields.get(fieldKey)!
)
})
}
)
resolve(issueTypeToFieldsMap)
})
})
}

getIssueTypesWithFieldsMap_8and9(): Promise<{ [key: string]: string[] }> {
return new Promise((resolve) => {
// IMPROVE: This is barely scalable
this.getProjects()
Expand All @@ -583,7 +691,7 @@ export class JiraServerProvider implements IProvider {
this.getRestApiClient(2)
.get(`/issue/createmeta/${project.id}/issuetypes`)
.then(async (response) => {
await Promise.all(response.data.values.map((issueType: { id: string }) =>
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}`)
Expand Down
12 changes: 12 additions & 0 deletions electron/providers/jira-server-provider/server-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,15 @@ export interface JiraServerUser {
"48x48": string
}
}

export interface JiraServerInfo {
baseUrl: string
version: string
versionNumbers: [number, number, number]
buildNumber: number
buildDate: string
serverTime: string
scmInfo: string
buildPartnerName: string
serverTitle: string
}