UNPKG

@roadiehq/backstage-plugin-jira

Version:
279 lines (276 loc) 8.33 kB
import { createApiRef } from '@backstage/core-plugin-api'; const jiraApiRef = createApiRef({ id: "plugin.jira.service" }); const DEFAULT_PROXY_PATH = "/jira/api"; const DEFAULT_REST_API_VERSION = "latest"; const DONE_STATUS_CATEGORY = "Done"; class JiraAPI { discoveryApi; proxyPath; apiVersion; confluenceActivityFilter; fetchApi; constructor(options) { this.discoveryApi = options.discoveryApi; const proxyPath = options.configApi.getOptionalString("jira.proxyPath"); this.proxyPath = proxyPath ?? DEFAULT_PROXY_PATH; const apiVersion = options.configApi.getOptionalNumber("jira.apiVersion"); this.apiVersion = apiVersion ? apiVersion.toString() : DEFAULT_REST_API_VERSION; this.confluenceActivityFilter = options.configApi.getOptionalString( "jira.confluenceActivityFilter" ); this.fetchApi = options.fetchApi; } getDomainFromApiUrl(apiUrl) { const url = new URL(apiUrl); return url.origin; } generateProjectUrl = (url) => new URL(url).origin + new URL(url).pathname.replace(/\/rest\/api\/.*$/g, ""); async getUrls() { const proxyUrl = await this.discoveryApi.getBaseUrl("proxy"); return { apiUrl: `${proxyUrl}${this.proxyPath}/rest/api/${this.apiVersion}/`, baseUrl: `${proxyUrl}${this.proxyPath}` }; } convertToString = (arrayElement) => arrayElement.filter(Boolean).map((i) => `'${i}'`).join(","); async pagedIssuesRequest(apiUrl, jql, startAt, maxResults) { const data = { jql, maxResults: maxResults ?? -1, fields: [ "key", "issuetype", "summary", "status", "assignee", "priority", "parent", "created", "updated", "project" ], startAt }; const request = await this.fetchApi.fetch(`${apiUrl}search`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }); if (!request.ok) { throw new Error( `failed to fetch data, status ${request.status}: ${request.statusText}` ); } const response = await request.json(); const lastElement = response.startAt + response.maxResults; return { issues: response.issues, next: response.total > lastElement ? lastElement : void 0 }; } async getIssuesPaged({ apiUrl, projectKey, component, label, statusesNames }) { const statusesString = this.convertToString(statusesNames); const jql = `project = "${projectKey}" ${statusesString ? `AND status in (${statusesString})` : ""} ${component ? `AND component = "${component}"` : ""} ${label ? `AND labels in (${label})` : ""} AND statuscategory not in ("Done") `; let startAt = 0; const issues = []; while (startAt !== void 0) { const res = await this.pagedIssuesRequest( apiUrl, jql, startAt ); startAt = res.next; issues.push(...res.issues); } return issues; } async getProjectDetails(projectKey, component, label, statusesNames) { const { apiUrl } = await this.getUrls(); const request = await this.fetchApi.fetch( `${apiUrl}project/${projectKey}`, { headers: { "Content-Type": "application/json" } } ); if (!request.ok) { throw new Error( `failed to fetch data, status ${request.status}: ${request.statusText}` ); } const project = await request.json(); const foundIssues = await this.getIssuesPaged({ apiUrl, projectKey, component, label, statusesNames }); const issuesCounter = project.issueTypes.filter((issueType) => issueType.name !== "Sub-task").map( (issueType) => ({ name: issueType.name, iconUrl: issueType.iconUrl, total: foundIssues.filter( (issue) => issue.fields?.issuetype.name === issueType.name ).length }) ); const ticketIds = foundIssues.map((issue) => issue.key); const tickets = foundIssues.map((index) => { return { key: index.key, summary: index?.fields?.summary, assignee: { displayName: index?.fields?.assignee?.displayName, avatarUrl: index?.fields?.assignee?.avatarUrls["48x48"] }, status: index?.fields?.status?.name, priority: index?.fields?.priority, created: index?.fields?.created, updated: index?.fields?.updated }; }); return { project: { name: project.name, iconUrl: project.avatarUrls["48x48"], type: project.projectTypeKey, url: this.generateProjectUrl(project.self) }, issues: issuesCounter, ticketIds, tickets }; } async getActivityStream(size, projectKey, componentName, ticketIds, label, isBearerAuth) { const { baseUrl } = await this.getUrls(); let filterUrl = `streams=key+IS+${projectKey}`; if (ticketIds && (componentName || label)) { filterUrl += `&streams=issue-key+IS+${ticketIds.join("+")}`; filterUrl += this.confluenceActivityFilter ? `&${this.confluenceActivityFilter}=activity+IS+NOT+*` : ""; } const request = await this.fetchApi.fetch( isBearerAuth ? `${baseUrl}/activity?maxResults=${size}&${filterUrl}` : `${baseUrl}/activity?maxResults=${size}&${filterUrl}&os_authType=basic`, {} ); if (!request.ok) { throw new Error( `failed to fetch data, status ${request.status}: ${request.statusText}` ); } const activityStream = await request.text(); return activityStream; } async getStatuses(projectKey) { const { apiUrl } = await this.getUrls(); const request = await this.fetchApi.fetch( `${apiUrl}project/${projectKey}/statuses`, { headers: { "Content-Type": "application/json" } } ); if (!request.ok) { throw new Error( `failed to fetch data, status ${request.status}: ${request.statusText}` ); } const statuses = await request.json(); return [ ...new Set( statuses.flatMap((status) => status.statuses).filter( (status) => status.statusCategory?.name !== DONE_STATUS_CATEGORY ).map((it) => it.name) ) ]; } async getUserDetails(userId) { const { apiUrl } = await this.getUrls(); const request = await this.fetchApi.fetch( `${apiUrl}user?username=${userId}`, { headers: { "Content-Type": "application/json" } } ); if (!request.ok) { throw new Error( `failed to fetch data, status ${request.status}: ${request.statusText}` ); } const user = await request.json(); let tickets = []; const jql = `assignee = "${userId}" AND statusCategory in ("To Do", "In Progress")`; let startAt = 0; const foundIssues = []; while (startAt !== void 0) { const res = await this.pagedIssuesRequest( apiUrl, jql, startAt ); startAt = res.next; foundIssues.push(...res.issues); } tickets = foundIssues.map((index) => { return { key: index.key, parent: index?.fields?.parent?.key, summary: index?.fields?.summary, assignee: { displayName: index?.fields?.assignee?.displayName, avatarUrl: index?.fields?.assignee?.avatarUrls["48x48"] }, status: index?.fields?.status, issuetype: index?.fields?.issuetype, priority: index?.fields?.priority, created: index?.fields?.created, updated: index?.fields?.updated }; }); return { user: { name: user.displayName, avatarUrl: user.avatarUrls["48x48"], url: this.getDomainFromApiUrl(user.self) }, tickets }; } async jqlQuery(query, maxResults) { const { apiUrl } = await this.getUrls(); const issues = []; let startAt = 0; while (startAt !== void 0) { const res = await this.pagedIssuesRequest( apiUrl, query, startAt, maxResults ); startAt = res.next; issues.push(...res.issues); } return issues; } } export { JiraAPI, jiraApiRef }; //# sourceMappingURL=index.esm.js.map