-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
2,572 additions
and
2,364 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"semi": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import { JiraClient, JiraKey } from "./jira-client"; | ||
import nock from "nock"; | ||
|
||
describe("basics", () => { | ||
it("constructs", () => { | ||
const client = new JiraClient("base-url", "username", "token", "PRJ"); | ||
expect(client).toBeDefined(); | ||
}); | ||
}); | ||
|
||
describe("get jira issue type", () => { | ||
let client: JiraClient; | ||
|
||
beforeEach(() => { | ||
client = new JiraClient("https://base-url", "username", "token", "PRJ"); | ||
}); | ||
|
||
it("gets the issue type of a jira issue", async () => { | ||
const response = { | ||
fields: { | ||
issuetype: { | ||
name: "Story", | ||
}, | ||
summary: "My Issue", | ||
}, | ||
}; | ||
|
||
nock("https://base-url") | ||
.get("/rest/api/3/issue/PRJ-123?fields=issuetype,summary,fixVersions") | ||
.reply(200, () => response); | ||
|
||
const issue = await client.getIssue(new JiraKey("PRJ", "123")); | ||
expect(issue?.type).toBe("story"); | ||
}); | ||
}); | ||
|
||
describe("get jira issue fixVersions", () => { | ||
let client: JiraClient; | ||
|
||
beforeEach(() => { | ||
client = new JiraClient("https://base-url", "username", "token", "PRJ"); | ||
}); | ||
|
||
it("gets the fixVersions property of a jira issue", async () => { | ||
const response = { | ||
fields: { | ||
issuetype: { | ||
name: "Story", | ||
}, | ||
summary: "My Issue", | ||
fixVersions: [ | ||
{ | ||
description: "", | ||
name: "v1.0.0", | ||
archived: false, | ||
released: false, | ||
releaseDate: "2023-10-31", | ||
}, | ||
], | ||
}, | ||
}; | ||
|
||
nock("https://base-url") | ||
.get("/rest/api/3/issue/PRJ-123?fields=issuetype,summary,fixVersions") | ||
.reply(200, () => response); | ||
|
||
const issue = await client.getIssue(new JiraKey("PRJ", "123")); | ||
expect(issue?.type).toBe("story"); | ||
expect(issue?.fixVersions![0]).toBe("v1.0.0"); | ||
}); | ||
}); | ||
|
||
describe("extract jira key", () => { | ||
let client: JiraClient; | ||
|
||
beforeEach(() => { | ||
client = new JiraClient("base-url", "username", "token", "PRJ"); | ||
}); | ||
|
||
it("extracts the jira key if present", () => { | ||
const jiraKey = client.extractJiraKey( | ||
"PRJ-3721_actions-workflow-improvements", | ||
); | ||
expect(jiraKey?.toString()).toBe("PRJ-3721"); | ||
}); | ||
|
||
it("extracts the jira key if present without underscore", () => { | ||
const jiraKey = client.extractJiraKey( | ||
"PRJ-3721-actions-workflow-improvements", | ||
); | ||
expect(jiraKey?.toString()).toBe("PRJ-3721"); | ||
}); | ||
|
||
it("extracts the jira key from a feature branch if present", () => { | ||
const jiraKey = client.extractJiraKey( | ||
"feature/PRJ-3721_actions-workflow-improvements", | ||
); | ||
expect(jiraKey?.toString()).toBe("PRJ-3721"); | ||
}); | ||
|
||
it("extracts the jira key case insensitive", () => { | ||
const jiraKey = client.extractJiraKey( | ||
"PRJ-3721_actions-workflow-improvements", | ||
); | ||
expect(jiraKey?.toString()).toBe("PRJ-3721"); | ||
}); | ||
|
||
it("returns undefined if not present", () => { | ||
const jiraKey = client.extractJiraKey( | ||
"prj3721_actions-workflow-improvements", | ||
); | ||
expect(jiraKey).toBeUndefined(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import axios, { AxiosInstance } from "axios"; | ||
|
||
export class JiraKey { | ||
constructor( | ||
public project: string, | ||
public keyNumber: string, | ||
) {} | ||
|
||
toString(): string { | ||
return `${this.project}-${this.keyNumber}`; | ||
} | ||
} | ||
|
||
export class JiraIssue { | ||
constructor( | ||
public key: JiraKey, | ||
public link: string, | ||
public title: string | undefined, | ||
public type: string | undefined, | ||
public fixVersions?: string[], | ||
) {} | ||
|
||
toString(): string { | ||
return `${this.key} | ${this.type} | ${this.title}`; | ||
} | ||
} | ||
|
||
export class JiraClient { | ||
client: AxiosInstance; | ||
|
||
constructor( | ||
private baseUrl: string, | ||
private username: string, | ||
private token: string, | ||
private projectKey: string, | ||
) { | ||
this.client = axios.create({ | ||
baseURL: this.baseUrl, | ||
auth: { | ||
username: this.username, | ||
password: this.token, | ||
}, | ||
timeout: 2000, | ||
}); | ||
} | ||
|
||
extractJiraKey(input: string): JiraKey | undefined { | ||
const regex = new RegExp(`${this.projectKey}-(?<number>\\d+)`, "i"); | ||
const match = input.match(regex); | ||
|
||
if (!match?.groups?.number) { | ||
return undefined; | ||
} | ||
|
||
return new JiraKey(this.projectKey, match?.groups?.number); | ||
} | ||
|
||
async getIssue(key: JiraKey): Promise<JiraIssue | undefined> { | ||
try { | ||
const res = await this.client.get( | ||
this.getRestApiUrl(`issue/${key}?fields=issuetype,summary,fixVersions`), | ||
); | ||
const obj = res.data; | ||
|
||
let issuetype: string | undefined; | ||
let title: string | undefined; | ||
let fixVersions: string[] | undefined; | ||
for (const field in obj.fields) { | ||
if (field === "issuetype") { | ||
issuetype = obj.fields[field].name?.toLowerCase(); | ||
} else if (field === "summary") { | ||
title = obj.fields[field]; | ||
} else if (field === "fixVersions") { | ||
fixVersions = obj.fields[field] | ||
.map(({ name }) => name) | ||
.filter(Boolean); | ||
} | ||
} | ||
|
||
return new JiraIssue( | ||
key, | ||
`${this.baseUrl}/browse/${key}`, | ||
title, | ||
issuetype, | ||
fixVersions, | ||
); | ||
} catch (error) { | ||
if (error.response) { | ||
throw new Error(JSON.stringify(error.response, null, 4)); | ||
} | ||
throw error; | ||
} | ||
} | ||
|
||
private getRestApiUrl(endpoint: string): string { | ||
return `${this.baseUrl}/rest/api/3/${endpoint}`; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,30 @@ | ||
import jiraPrValidation from "./index" | ||
import jiraPrValidation from "./index"; | ||
|
||
declare const global: any | ||
declare const global: any; | ||
|
||
describe("jiraPrValidation()", () => { | ||
beforeEach(() => { | ||
global.warn = jest.fn() | ||
global.message = jest.fn() | ||
global.fail = jest.fn() | ||
global.markdown = jest.fn() | ||
}) | ||
global.warn = jest.fn(); | ||
global.message = jest.fn(); | ||
global.fail = jest.fn(); | ||
global.markdown = jest.fn(); | ||
}); | ||
|
||
afterEach(() => { | ||
global.warn = undefined | ||
global.message = undefined | ||
global.fail = undefined | ||
global.markdown = undefined | ||
}) | ||
global.warn = undefined; | ||
global.message = undefined; | ||
global.fail = undefined; | ||
global.markdown = undefined; | ||
}); | ||
|
||
it("Checks for a that message has been called", () => { | ||
global.danger = { | ||
github: { pr: { title: "My Test Title" } }, | ||
} | ||
github: { pr: { title: "My Test Title", base: "pr-base" } }, | ||
}; | ||
|
||
jiraPrValidation() | ||
jiraPrValidation(); | ||
|
||
expect(global.message).toHaveBeenCalledWith( | ||
"PR Title: My Test Title", | ||
) | ||
}) | ||
}) | ||
expect(global.message).toHaveBeenCalledWith("PR Title: My Test Title"); | ||
expect(global.message).toHaveBeenCalledWith("PR Base: pr-base"); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,59 @@ | ||
// Provides dev-time type structures for `danger` - doesn't affect runtime. | ||
import {DangerDSLType} from "../node_modules/danger/distribution/dsl/DangerDSL" | ||
declare var danger: DangerDSLType | ||
export declare function message(message: string): void | ||
export declare function warn(message: string): void | ||
export declare function fail(message: string): void | ||
export declare function markdown(message: string): void | ||
import { DangerDSLType } from "../node_modules/danger/distribution/dsl/DangerDSL"; | ||
import { JiraClient } from "./clients/jira-client"; | ||
declare const danger: DangerDSLType; | ||
export declare function message(message: string): void; | ||
export declare function warn(message: string): void; | ||
export declare function fail(message: string): void; | ||
export declare function markdown(message: string): void; | ||
|
||
/** | ||
* Import metadata from the issue on Jira and perform validations | ||
*/ | ||
export default function jiraPrValidation() { | ||
export default async function jiraPrValidation( | ||
baseUrl: string, | ||
username: string, | ||
token: string, | ||
projectKey: string, | ||
) { | ||
// Replace this with the code from your Dangerfile | ||
const title = danger.github.pr.title | ||
message(`PR Title: ${title}`) | ||
const title = danger.github.pr.title; | ||
const base = danger.github.pr.base.ref; | ||
const head = danger.github.pr.head.ref; | ||
message(`PR Title: ${title}`); | ||
message(`PR Base: ${base}`); | ||
message(`PR Head: ${head}`); | ||
|
||
const jiraClient = new JiraClient(baseUrl, username, token, projectKey); | ||
|
||
const jiraKey = jiraClient.extractJiraKey(head); | ||
|
||
message("jiraKey " + jiraKey); | ||
if (!jiraKey) { | ||
warn("⚠️ No Jira key found in branch name, exiting"); | ||
return; | ||
} | ||
|
||
const jiraIssue = await jiraClient.getIssue(jiraKey); | ||
message("jiraIssue " + jiraIssue); | ||
if (!jiraIssue) { | ||
warn("⚠️ Could not get issue, exiting"); | ||
return; | ||
} | ||
|
||
if (fixVersionsMatchesBranch(base, jiraIssue.fixVersions)) { | ||
fail("🚨 Base branch doesn't match Jira fixVersion"); | ||
} | ||
} | ||
|
||
function fixVersionsMatchesBranch(branch: string, fixVersions?: string[]) { | ||
if (!fixVersions?.length) { | ||
return true; | ||
} | ||
|
||
if (fixVersions.some((version) => branch.includes(version))) { | ||
return true; | ||
} | ||
|
||
return false; | ||
} |
Oops, something went wrong.