DigitalConsumer.ISS/playwright-tests/impl/api/JiraApiUtil.ts
chase-safelite 55bb84cca4
Jira writeback reporter to add subtasks with test results (#935)
* Progress toward Jira integration

* Jira Writeback reporter working

* Added correct timeouts

* Added Jira Writeback reporter to pipeline

* Added tag for testing

* Removed unnecessary async

* Fixed rate limit issue

* Moved to only transition existing test case subtasks

* Changing to transition issues by key

* Enabled mocking for scenario 2

* Added better error handling in api util

* Changed workers to 1 to eliminate duplicate test runs

* Streamlined batch creation and retry check

* Modified test data structure to remove undeterministic sort

* Increased workers

* Increased heap size for jira upload

* Cleanup

* Made logs more specific for JiraWritebackReporter
2025-04-09 10:36:30 -04:00

143 lines
No EOL
5.7 KiB
TypeScript

import { PostBulkFetchIssuesRequestBody, PostBulkFetchIssuesResponse, GetIssueResponse, GetIssueTransitionsResponse, GetIssueTypesResponse, PostAddCommentResponse, PostBulkCreateIssueRequestBody, PostBulkCreateIssueResponse, PostCreateIssueRequestBody, PostCreateIssueResponse, PostTransitionIssueRequestBody, PutEditIssueRequestBody, PostBulkTransitionIssuesRequestBody } from "@business-logic/types/JiraApi";
import axios, { request } from "axios";
import axiosRetry from "axios-retry";
const jiraUrl = process.env.JIRA_SERVER!;
const jiraUsername = process.env.JIRA_USERNAME!;
const jiraApiKey = process.env.JIRA_API_KEY!;
const encodedAuthKey = Buffer.from(`${jiraUsername}:${jiraApiKey}`).toString('base64');
export default class JiraApiUtil {
readonly baseUrl: string;
readonly issueUrl: string;
readonly bulkIssueCreateUrl: string;
readonly bulkIssueFetchUrl: string;
readonly bulkTransitionIssuesUrl: string;
readonly axiosClient: axios.AxiosInstance;
constructor() {
this.baseUrl = jiraUrl;
this.issueUrl = `${this.baseUrl}/rest/api/3/issue`;
this.bulkIssueCreateUrl = `${this.issueUrl}/bulk`;
this.bulkIssueFetchUrl = `${this.issueUrl}/bulkfetch`;
this.bulkTransitionIssuesUrl = `${this.baseUrl}/rest/api/3/bulk/issues/transition`
this.axiosClient = axios.create();
// interceptor to log error message from api
this.axiosClient.interceptors.response.use(
response => response,
error => {
console.error('Axios Error:', error?.response?.data || error.message);
return Promise.reject(error);
}
);
// Set up retries if requests are made too quickly
axiosRetry(this.axiosClient, {
retries: 4,
retryDelay: (retryCount) => { return Math.pow(2, retryCount) * 1000; }, // Exponential backoff
retryCondition: (error) => { return error.response?.status === 429 } // If rate-limit error
});
}
getIssue(issueKey: string) {
const url = `${this.issueUrl}/${issueKey}`;
return this.axiosClient.get<GetIssueResponse>(url, {
headers: {
Authorization: `Basic ${encodedAuthKey}`,
Accept: 'application/json'
}
});
}
postBulkFetchIssues(requestBody: PostBulkFetchIssuesRequestBody) {
return this.axiosClient.post<PostBulkFetchIssuesResponse>(this.bulkIssueFetchUrl, requestBody, {
headers: {
Authorization: `Basic ${encodedAuthKey}`,
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
}
getIssueTransitions(issueKey: string) {
const url = `${this.issueUrl}/${issueKey}/transitions`
return this.axiosClient.get<GetIssueTransitionsResponse>(url, {
headers: {
Authorization: `Basic ${encodedAuthKey}`,
Accept: 'application/json'
}
});
}
getIssueTypes(projectKey: string) {
const url = `${this.baseUrl}/rest/api/3/issue/createmeta/${projectKey}/issuetypes`;
return this.axiosClient.get<GetIssueTypesResponse>(url, {
headers: {
Authorization: `Basic ${encodedAuthKey}`,
Accept: 'application/json'
}
});
}
postAddComment(issueKey: string, commentBody: PostAddCommentResponse) {
const url = `${this.issueUrl}/${issueKey}/comment`;
return this.axiosClient.post(url, commentBody, {
headers: {
Authorization: `Basic ${encodedAuthKey}`,
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
}
postTransitionIssue(issueKey: string, requestBody: PostTransitionIssueRequestBody) {
const transitionUrl = `${this.issueUrl}/${issueKey}/transitions`
return this.axiosClient.post(transitionUrl, requestBody, {
headers: {
Authorization: `Basic ${encodedAuthKey}`,
'Accept': 'application/json',
'Content-Type': 'application/json'
},
});
}
putEditIssue(issueKey: string, requestBody: PutEditIssueRequestBody) {
const editIssueUrl = `${this.issueUrl}/${issueKey}`
return this.axiosClient.put(editIssueUrl, requestBody, {
headers: {
Authorization: `Basic ${encodedAuthKey}`,
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
}
postBulkTransitionIssues(requestBody: PostBulkTransitionIssuesRequestBody) {
return this.axiosClient.post(this.bulkTransitionIssuesUrl, requestBody, {
headers: {
Authorization: `Basic ${encodedAuthKey}`,
'Accept': 'application/json',
'Content-Type': 'application/json'
},
});
}
postCreateIssue(requestBody: PostCreateIssueRequestBody) {
return this.axiosClient.post<PostCreateIssueResponse>(this.issueUrl, requestBody, {
headers: {
Authorization: `Basic ${encodedAuthKey}`,
'Accept': 'application/json',
'Content-Type': 'application/json'
},
});
}
postBulkCreateIssue(requestBody: PostBulkCreateIssueRequestBody) {
return this.axiosClient.post<PostBulkCreateIssueResponse>(this.bulkIssueCreateUrl, requestBody, {
headers: {
Authorization: `Basic ${encodedAuthKey}`,
'Accept': 'application/json',
'Content-Type': 'application/json'
},
});
}
}