import {z} from 'zod' export type JIRAComment = {author: string, created: string, rendered_body: string}; export type JIRAEpicChild = {id: string, title: string, created: string, updated: string, work_began?: string | undefined, resolved?: string | undefined, acknowledge_time: string, lead_time?: string | undefined, life_time: string, reporter: string, assignee?: string | undefined, comments: number, comment_list?: Array | undefined, priority: string | null, rendered_description?: string | undefined, status: string, story_points?: number | undefined, type: string, url: string, subtasks: number, prs: number}; export type JIRAEpic = {id: string, title: string, created: string, updated: string, work_began?: string | undefined, resolved?: string | undefined, acknowledge_time: string, lead_time?: string | undefined, life_time: string, reporter: string, assignee?: string | undefined, comments: number, comment_list?: Array | undefined, priority: string | null, rendered_description?: string | undefined, status: string, story_points?: number | undefined, type: string, url: string, project: string, children?: Array | undefined, prs: number}; export type JIRAFilterWith = {assignees?: (Array) | undefined, reporters?: Array | undefined, commenters?: Array | undefined}; export type JIRAMetricID = 'jira-open' | 'jira-resolved' | 'jira-raised' | 'jira-acknowledged' | 'jira-acknowledged-q' | 'jira-life-time' | 'jira-life-time-below-threshold-ratio' | 'jira-lead-time' | 'jira-lead-time-below-threshold-ratio' | 'jira-acknowledge-time' | 'jira-acknowledge-time-below-threshold-ratio' | 'jira-pr-lag-time' | 'jira-backlog-time' | 'jira-resolution-rate'; export type TimeDuration = string; export type JIRAHistogramDefinition = {metric: JIRAMetricID, scale?: ('linear' | 'log') | undefined, bins?: number | undefined, ticks?: (Array) | undefined}; export type TimeZone = number; export type Quantiles = Array; export type JIRAHistogramsRequest = {priorities?: Array | undefined, types?: Array | undefined, epics?: Array | undefined, labels_include?: Array | undefined, labels_exclude?: Array | undefined, with?: Array | undefined, projects?: Array | undefined, histograms: Array, date_from: string, date_to: string, timezone?: TimeZone | undefined, exclude_inactive: boolean, quantiles?: Quantiles | undefined, account: number}; export type CreateApiHistogramsJiraArgs = {body: JIRAHistogramsRequest}; export type CreateApiHistogramsJiraOptions = {baseUrl: string, bearerAuth?: string | undefined, fetch?: typeof fetch | undefined}; export const jIRAFilterWith = z.object({assignees: z.array(z.string().nullable()).optional(), reporters: z.array(z.string()).optional(), commenters: z.array(z.string()).optional()}); export const jIRAMetricID = z.enum(["jira-open", "jira-resolved", "jira-raised", "jira-acknowledged", "jira-acknowledged-q", "jira-life-time", "jira-life-time-below-threshold-ratio", "jira-lead-time", "jira-lead-time-below-threshold-ratio", "jira-acknowledge-time", "jira-acknowledge-time-below-threshold-ratio", "jira-pr-lag-time", "jira-backlog-time", "jira-resolution-rate"]); export const histogramScale = z.enum(["linear", "log"]); export const timeDuration = z.string(); export const interquartile = z.object({left: z.union([z.number(), timeDuration]), right: z.union([z.number(), timeDuration])}); export const calculatedJIRAHistogram = z.object({with: jIRAFilterWith.optional(), metric: jIRAMetricID, scale: histogramScale, ticks: z.array(z.union([z.number(), timeDuration])), frequencies: z.array(z.number().int().gte(0)), interquartile: interquartile}); export const calculatedJIRAHistograms = z.array(calculatedJIRAHistogram); export const validateResponse = async ( schema: { readonly '~standard': { readonly validate: (value: unknown) => ({ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }) | Promise<{ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }> } }, value: unknown ): Promise => { const result = await schema['~standard'].validate(value) if (result.issues !== undefined) { const detail = result.issues .map(({ message, path }) => path?.length ? `${path.map((segment) => String(typeof segment === 'object' ? segment.key : segment)).join('.')}: ${message}` : message ) .join('; ') throw new Error(`Response does not match the schema: ${detail}`, { cause: { issues: result.issues } }) } return result.value }; /** * Calculate histograms over JIRA issue activities. * * POST /histograms/jira * * Also accepts auth: apiKeyAuth */ export const createApiHistogramsJira = async (args: CreateApiHistogramsJiraArgs, options: CreateApiHistogramsJiraOptions) => { const {baseUrl, fetch: fetchFn = fetch, bearerAuth} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('content-type', "application/json") headers.set('accept', "application/json") if (bearerAuth !== undefined) headers.set('authorization', `Bearer ${bearerAuth}`) const url = `${baseUrl.replace(/\/+$/, '')}/histograms/jira${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "POST", headers, body: JSON.stringify(args.body)}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(calculatedJIRAHistograms, await res.json()) };