@esm-js/jira.js
Version:
A comprehensive JavaScript/TypeScript library designed for both Node.JS and browsers, facilitating seamless interaction with the Atlassian Jira API.
545 lines (497 loc) • 23.8 kB
text/typescript
import type { Mime } from 'mime';
import mime from 'mime';
import type * as Models from './models';
import type * as Parameters from './parameters';
import type { Client } from '../clients';
import type { Callback } from '../callback';
import type { RequestConfig } from '../requestConfig';
export class IssueAttachments {
constructor(private client: Client) {}
/**
* Returns the contents of an attachment. A `Range` header can be set to define a range of bytes within the attachment
* to download. See the [HTTP Range header standard](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range)
* for details.
*
* To return a thumbnail of the attachment, use [Get attachment
* thumbnail](#api-rest-api-3-attachment-thumbnail-id-get).
*
* This operation can be accessed anonymously.
*
* **[Permissions](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#permissions) required:** For the
* issue containing the attachment:
*
* - _Browse projects_ [project permission](https://confluence.atlassian.com/x/yodKLg) for the project that the issue is
* in.
* - If [issue-level security](https://confluence.atlassian.com/x/J4lKLg) is configured, issue-level security permission
* to view the issue.
* - If attachments are added in private comments, the comment-level restriction will be applied.
*/
async getAttachmentContent<T = Buffer>(
parameters: Parameters.GetAttachmentContent | string,
callback: Callback<T>,
): Promise<void>;
/**
* Returns the contents of an attachment. A `Range` header can be set to define a range of bytes within the attachment
* to download. See the [HTTP Range header standard](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range)
* for details.
*
* To return a thumbnail of the attachment, use [Get attachment
* thumbnail](#api-rest-api-3-attachment-thumbnail-id-get).
*
* This operation can be accessed anonymously.
*
* **[Permissions](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#permissions) required:** For the
* issue containing the attachment:
*
* - _Browse projects_ [project permission](https://confluence.atlassian.com/x/yodKLg) for the project that the issue is
* in.
* - If [issue-level security](https://confluence.atlassian.com/x/J4lKLg) is configured, issue-level security permission
* to view the issue.
* - If attachments are added in private comments, the comment-level restriction will be applied.
*/
async getAttachmentContent<T = Buffer>(
parameters: Parameters.GetAttachmentContent | string,
callback?: never,
): Promise<T>;
async getAttachmentContent<T = Buffer>(
parameters: Parameters.GetAttachmentContent | string,
callback?: Callback<T>,
): Promise<void | T> {
const id = typeof parameters === 'string' ? parameters : parameters.id;
const config: RequestConfig = {
url: `/rest/api/3/attachment/content/${id}`,
method: 'GET',
params: {
redirect: typeof parameters !== 'string' && parameters.redirect,
},
responseType: 'arraybuffer',
};
return this.client.sendRequest(config, callback);
}
/**
* Returns the attachment settings, that is, whether attachments are enabled and the maximum attachment size allowed.
*
* Note that there are also [project permissions](https://confluence.atlassian.com/x/yodKLg) that restrict whether
* users can create and delete attachments.
*
* This operation can be accessed anonymously.
*
* **[Permissions](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#permissions) required:** None.
*/
async getAttachmentMeta<T = Models.AttachmentSettings>(callback: Callback<T>): Promise<void>;
/**
* Returns the attachment settings, that is, whether attachments are enabled and the maximum attachment size allowed.
*
* Note that there are also [project permissions](https://confluence.atlassian.com/x/yodKLg) that restrict whether
* users can create and delete attachments.
*
* This operation can be accessed anonymously.
*
* **[Permissions](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#permissions) required:** None.
*/
async getAttachmentMeta<T = Models.AttachmentSettings>(callback?: never): Promise<T>;
async getAttachmentMeta<T = Models.AttachmentSettings>(callback?: Callback<T>): Promise<void | T> {
const config: RequestConfig = {
url: '/rest/api/3/attachment/meta',
method: 'GET',
};
return this.client.sendRequest(config, callback);
}
/**
* Returns the thumbnail of an attachment.
*
* To return the attachment contents, use [Get attachment content](#api-rest-api-3-attachment-content-id-get).
*
* This operation can be accessed anonymously.
*
* **[Permissions](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#permissions) required:** For the
* issue containing the attachment:
*
* - _Browse projects_ [project permission](https://confluence.atlassian.com/x/yodKLg) for the project that the issue is
* in.
* - If [issue-level security](https://confluence.atlassian.com/x/J4lKLg) is configured, issue-level security permission
* to view the issue.
* - If attachments are added in private comments, the comment-level restriction will be applied.
*/
async getAttachmentThumbnail<T = Buffer>(
parameters: Parameters.GetAttachmentThumbnail | string,
callback: Callback<T>,
): Promise<void>;
/**
* Returns the thumbnail of an attachment.
*
* To return the attachment contents, use [Get attachment content](#api-rest-api-3-attachment-content-id-get).
*
* This operation can be accessed anonymously.
*
* **[Permissions](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#permissions) required:** For the
* issue containing the attachment:
*
* - _Browse projects_ [project permission](https://confluence.atlassian.com/x/yodKLg) for the project that the issue is
* in.
* - If [issue-level security](https://confluence.atlassian.com/x/J4lKLg) is configured, issue-level security permission
* to view the issue.
* - If attachments are added in private comments, the comment-level restriction will be applied.
*/
async getAttachmentThumbnail<T = Buffer>(
parameters: Parameters.GetAttachmentThumbnail | string,
callback?: never,
): Promise<T>;
async getAttachmentThumbnail<T = Buffer>(
parameters: Parameters.GetAttachmentThumbnail | string,
callback?: Callback<T>,
): Promise<void | T> {
const id = typeof parameters === 'string' ? parameters : parameters.id;
const config: RequestConfig = {
url: `/rest/api/3/attachment/thumbnail/${id}`,
method: 'GET',
params: {
redirect: typeof parameters !== 'string' && parameters.redirect,
fallbackToDefault: typeof parameters !== 'string' && parameters.fallbackToDefault,
width: typeof parameters !== 'string' && parameters.width,
height: typeof parameters !== 'string' && parameters.height,
},
responseType: 'arraybuffer',
};
return this.client.sendRequest(config, callback);
}
/**
* Returns the metadata for an attachment. Note that the attachment itself is not returned.
*
* This operation can be accessed anonymously.
*
* **[Permissions](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#permissions) required:**
*
* - _Browse projects_ [project permission](https://confluence.atlassian.com/x/yodKLg) for the project that the issue is
* in.
* - If [issue-level security](https://confluence.atlassian.com/x/J4lKLg) is configured, issue-level security permission
* to view the issue.
* - If attachments are added in private comments, the comment-level restriction will be applied.
*/
async getAttachment<T = Models.AttachmentMetadata>(
parameters: Parameters.GetAttachment | string,
callback: Callback<T>,
): Promise<void>;
/**
* Returns the metadata for an attachment. Note that the attachment itself is not returned.
*
* This operation can be accessed anonymously.
*
* **[Permissions](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#permissions) required:**
*
* - _Browse projects_ [project permission](https://confluence.atlassian.com/x/yodKLg) for the project that the issue is
* in.
* - If [issue-level security](https://confluence.atlassian.com/x/J4lKLg) is configured, issue-level security permission
* to view the issue.
* - If attachments are added in private comments, the comment-level restriction will be applied.
*/
async getAttachment<T = Models.AttachmentMetadata>(
parameters: Parameters.GetAttachment | string,
callback?: never,
): Promise<T>;
async getAttachment<T = Models.AttachmentMetadata>(
parameters: Parameters.GetAttachment | string,
callback?: Callback<T>,
): Promise<void | T> {
const id = typeof parameters === 'string' ? parameters : parameters.id;
const config: RequestConfig = {
url: `/rest/api/3/attachment/${id}`,
method: 'GET',
};
return this.client.sendRequest(config, callback);
}
/**
* Deletes an attachment from an issue.
*
* This operation can be accessed anonymously.
*
* **[Permissions](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#permissions) required:** For the
* project holding the issue containing the attachment:
*
* - _Delete own attachments_ [project permission](https://confluence.atlassian.com/x/yodKLg) to delete an attachment
* created by the calling user.
* - _Delete all attachments_ [project permission](https://confluence.atlassian.com/x/yodKLg) to delete an attachment
* created by any user.
*/
async removeAttachment<T = void>(
parameters: Parameters.RemoveAttachment | string,
callback: Callback<T>,
): Promise<void>;
/**
* Deletes an attachment from an issue.
*
* This operation can be accessed anonymously.
*
* **[Permissions](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#permissions) required:** For the
* project holding the issue containing the attachment:
*
* - _Delete own attachments_ [project permission](https://confluence.atlassian.com/x/yodKLg) to delete an attachment
* created by the calling user.
* - _Delete all attachments_ [project permission](https://confluence.atlassian.com/x/yodKLg) to delete an attachment
* created by any user.
*/
async removeAttachment<T = void>(parameters: Parameters.RemoveAttachment | string, callback?: never): Promise<T>;
async removeAttachment<T = void>(
parameters: Parameters.RemoveAttachment | string,
callback?: Callback<T>,
): Promise<void | T> {
const id = typeof parameters === 'string' ? parameters : parameters.id;
const config: RequestConfig = {
url: `/rest/api/3/attachment/${id}`,
method: 'DELETE',
};
return this.client.sendRequest(config, callback);
}
/**
* Returns the metadata for the contents of an attachment, if it is an archive, and metadata for the attachment
* itself. For example, if the attachment is a ZIP archive, then information about the files in the archive is
* returned and metadata for the ZIP archive. Currently, only the ZIP archive format is supported.
*
* Use this operation to retrieve data that is presented to the user, as this operation returns the metadata for the
* attachment itself, such as the attachment's ID and name. Otherwise, use [ Get contents metadata for an expanded
* attachment](#api-rest-api-3-attachment-id-expand-raw-get), which only returns the metadata for the attachment's
* contents.
*
* This operation can be accessed anonymously.
*
* **[Permissions](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#permissions) required:** For the
* issue containing the attachment:
*
* - _Browse projects_ [project permission](https://confluence.atlassian.com/x/yodKLg) for the project that the issue is
* in.
* - If [issue-level security](https://confluence.atlassian.com/x/J4lKLg) is configured, issue-level security permission
* to view the issue.
* - If attachments are added in private comments, the comment-level restriction will be applied.
*/
async expandAttachmentForHumans<T = Models.AttachmentArchiveMetadataReadable>(
parameters: Parameters.ExpandAttachmentForHumans | string,
callback: Callback<T>,
): Promise<void>;
/**
* Returns the metadata for the contents of an attachment, if it is an archive, and metadata for the attachment
* itself. For example, if the attachment is a ZIP archive, then information about the files in the archive is
* returned and metadata for the ZIP archive. Currently, only the ZIP archive format is supported.
*
* Use this operation to retrieve data that is presented to the user, as this operation returns the metadata for the
* attachment itself, such as the attachment's ID and name. Otherwise, use [ Get contents metadata for an expanded
* attachment](#api-rest-api-3-attachment-id-expand-raw-get), which only returns the metadata for the attachment's
* contents.
*
* This operation can be accessed anonymously.
*
* **[Permissions](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#permissions) required:** For the
* issue containing the attachment:
*
* - _Browse projects_ [project permission](https://confluence.atlassian.com/x/yodKLg) for the project that the issue is
* in.
* - If [issue-level security](https://confluence.atlassian.com/x/J4lKLg) is configured, issue-level security permission
* to view the issue.
* - If attachments are added in private comments, the comment-level restriction will be applied.
*/
async expandAttachmentForHumans<T = Models.AttachmentArchiveMetadataReadable>(
parameters: Parameters.ExpandAttachmentForHumans | string,
callback?: never,
): Promise<T>;
async expandAttachmentForHumans<T = Models.AttachmentArchiveMetadataReadable>(
parameters: Parameters.ExpandAttachmentForHumans | string,
callback?: Callback<T>,
): Promise<void | T> {
const id = typeof parameters === 'string' ? parameters : parameters.id;
const config: RequestConfig = {
url: `/rest/api/3/attachment/${id}/expand/human`,
method: 'GET',
};
return this.client.sendRequest(config, callback);
}
/**
* Returns the metadata for the contents of an attachment, if it is an archive. For example, if the attachment is a
* ZIP archive, then information about the files in the archive is returned. Currently, only the ZIP archive format is
* supported.
*
* Use this operation if you are processing the data without presenting it to the user, as this operation only returns
* the metadata for the contents of the attachment. Otherwise, to retrieve data to present to the user, use [ Get all
* metadata for an expanded attachment](#api-rest-api-3-attachment-id-expand-human-get) which also returns the
* metadata for the attachment itself, such as the attachment's ID and name.
*
* This operation can be accessed anonymously.
*
* **[Permissions](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#permissions) required:** For the
* issue containing the attachment:
*
* - _Browse projects_ [project permission](https://confluence.atlassian.com/x/yodKLg) for the project that the issue is
* in.
* - If [issue-level security](https://confluence.atlassian.com/x/J4lKLg) is configured, issue-level security permission
* to view the issue.
* - If attachments are added in private comments, the comment-level restriction will be applied.
*/
async expandAttachmentForMachines<T = Models.AttachmentArchiveImpl>(
parameters: Parameters.ExpandAttachmentForMachines | string,
callback: Callback<T>,
): Promise<void>;
/**
* Returns the metadata for the contents of an attachment, if it is an archive. For example, if the attachment is a
* ZIP archive, then information about the files in the archive is returned. Currently, only the ZIP archive format is
* supported.
*
* Use this operation if you are processing the data without presenting it to the user, as this operation only returns
* the metadata for the contents of the attachment. Otherwise, to retrieve data to present to the user, use [ Get all
* metadata for an expanded attachment](#api-rest-api-3-attachment-id-expand-human-get) which also returns the
* metadata for the attachment itself, such as the attachment's ID and name.
*
* This operation can be accessed anonymously.
*
* **[Permissions](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#permissions) required:** For the
* issue containing the attachment:
*
* - _Browse projects_ [project permission](https://confluence.atlassian.com/x/yodKLg) for the project that the issue is
* in.
* - If [issue-level security](https://confluence.atlassian.com/x/J4lKLg) is configured, issue-level security permission
* to view the issue.
* - If attachments are added in private comments, the comment-level restriction will be applied.
*/
async expandAttachmentForMachines<T = Models.AttachmentArchiveImpl>(
parameters: Parameters.ExpandAttachmentForMachines | string,
callback?: never,
): Promise<T>;
async expandAttachmentForMachines<T = Models.AttachmentArchiveImpl>(
parameters: Parameters.ExpandAttachmentForMachines | string,
callback?: Callback<T>,
): Promise<void | T> {
const id = typeof parameters === 'string' ? parameters : parameters.id;
const config: RequestConfig = {
url: `/rest/api/3/attachment/${id}/expand/raw`,
method: 'GET',
};
return this.client.sendRequest(config, callback);
}
/**
* Adds one or more attachments to an issue. Attachments are posted as multipart/form-data ([RFC
* 1867](https://www.ietf.org/rfc/rfc1867.txt)).
*
* Note that:
*
* - The request must have a `X-Atlassian-Token: no-check` header, if not it is blocked. See [Special
* headers](#special-request-headers) for more information.
* - The name of the multipart/form-data parameter that contains the attachments must be `file`.
*
* This operation can be accessed anonymously.
*
* **[Permissions](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#permissions) required:**
*
* - _Browse Projects_ and _Create attachments_ [ project permission](https://confluence.atlassian.com/x/yodKLg) for the
* project that the issue is in.
* - If [issue-level security](https://confluence.atlassian.com/x/J4lKLg) is configured, issue-level security permission
* to view the issue.
*/
async addAttachment<T = Models.Attachment[]>(
parameters: Parameters.AddAttachment,
callback: Callback<T>,
): Promise<void>;
/**
* Adds one or more attachments to an issue. Attachments are posted as multipart/form-data ([RFC
* 1867](https://www.ietf.org/rfc/rfc1867.txt)).
*
* Note that:
*
* - The request must have a `X-Atlassian-Token: no-check` header, if not it is blocked. See [Special
* headers](#special-request-headers) for more information.
* - The name of the multipart/form-data parameter that contains the attachments must be `file`.
*
* This operation can be accessed anonymously.
*
* **[Permissions](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#permissions) required:**
*
* - _Browse Projects_ and _Create attachments_ [ project permission](https://confluence.atlassian.com/x/yodKLg) for the
* project that the issue is in.
* - If [issue-level security](https://confluence.atlassian.com/x/J4lKLg) is configured, issue-level security permission
* to view the issue.
*/
async addAttachment<T = Models.Attachment[]>(parameters: Parameters.AddAttachment, callback?: never): Promise<T>;
async addAttachment<T = Models.Attachment[]>(
parameters: Parameters.AddAttachment,
callback?: Callback<T>,
): Promise<void | T> {
const formData = new FormData();
const attachments = Array.isArray(parameters.attachment) ? parameters.attachment : [parameters.attachment];
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
let Readable: typeof import('stream').Readable | undefined;
if (typeof window === 'undefined') {
const { Readable: NodeReadable } = await import('stream');
Readable = NodeReadable;
}
for (const attachment of attachments) {
const file = await this._convertToFile(attachment, mime, Readable);
if (!(file instanceof File || file instanceof Blob)) {
throw new Error(`Unsupported file type for attachment: ${typeof file}`);
}
formData.append('file', file, attachment.filename);
}
const config: RequestConfig = {
url: `/rest/api/3/issue/${parameters.issueIdOrKey}/attachments`,
method: 'POST',
headers: {
'X-Atlassian-Token': 'no-check',
'Content-Type': 'multipart/form-data',
},
data: formData,
maxBodyLength: Infinity,
maxContentLength: Infinity,
};
return this.client.sendRequest(config, callback);
}
private async _convertToFile(
attachment: Parameters.Attachment,
mime: Mime,
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
Readable?: typeof import('stream').Readable,
): Promise<File | Blob> {
const mimeType = attachment.mimeType ?? (mime.getType(attachment.filename) || undefined);
if (attachment.file instanceof Blob || attachment.file instanceof File) {
return attachment.file;
}
if (typeof attachment.file === 'string') {
return new File([attachment.file], attachment.filename, { type: mimeType });
}
if (Readable && attachment.file instanceof Readable) {
return this._streamToBlob(attachment.file, attachment.filename, mimeType);
}
if (attachment.file instanceof ReadableStream) {
return this._streamToBlob(attachment.file, attachment.filename, mimeType);
}
if (ArrayBuffer.isView(attachment.file) || attachment.file instanceof ArrayBuffer) {
return new File([attachment.file], attachment.filename, { type: mimeType });
}
throw new Error('Unsupported attachment file type.');
}
private async _streamToBlob(
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
stream: import('stream').Readable | ReadableStream,
filename: string,
mimeType?: string,
): Promise<File> {
if (typeof window === 'undefined' && stream instanceof (await import('stream')).Readable) {
return new Promise((resolve, reject) => {
const chunks: Uint8Array[] = [];
stream.on('data', chunk => chunks.push(chunk));
stream.on('end', () => {
const blob = new Blob(chunks, { type: mimeType });
resolve(new File([blob], filename, { type: mimeType }));
});
stream.on('error', reject);
});
}
if (stream instanceof ReadableStream) {
const reader = stream.getReader();
const chunks: Uint8Array[] = [];
let done = false;
while (!done) {
const { value, done: streamDone } = await reader.read();
if (value) chunks.push(value);
done = streamDone;
}
const blob = new Blob(chunks, { type: mimeType });
return new File([blob], filename, { type: mimeType });
}
throw new Error('Unsupported stream type.');
}
}