Before reading this documentation make sure you have read the baseline documentation.

Jira Cloud Managed API implementation details can be found here.

Implementation details table contains the following columns:

  • Original Grouping - This is the original group name that Atlassian has given.
  • Original Function Name - This is the original function name that Atlassian has given.
  • Status - Managed API status, any function that has green status can be expected to have full Managed API support.
  • Remapped Grouping - Grouping used in Managed API level, please note, the Managed API grouping can differ from the official vendor's grouping strategy, mostly for convenience and consistency sake. Remapped groupings are used for following cases:
    • Denotes an import path for the function when working on HTTP API or Managed API level. For exampleTransition issue (original name) with remapped group nameIssue.Transition and with remapped function full nameperformIssueTransition  can be imported from following locations:

      // HTTP API
      import { performIssueTransition } from '@avst-hzn/jira-cloud-api-v3/http-api/issue/transition';
       
      // Managed API
      import { performIssueTransition } from '@avst-hzn/jira-cloud-api-v3/managed-api/issue/transition';
      JS
    • Denotes grouping for Fluent API groups. The sameTransition issue (original name) with remapped group nameIssue.Transition and with remapped function short nameperformTransition can be used as following:

      import { JiraCloudConnection } from '@avst-hzn/jira-cloud-api-v3';
       
      export async function run() {
          const JiraCloud = JiraCloudConnection.connect('CONNECTION_ID');
       
          await JiraCloud.Issue.Transition.performTransition({ ... });
      }
      JS
  • Remapped Function Short Name - Short function name is used in Fluent API level as shown above, exception being when function is accessed via secondary All group then the function full name will be used as following, where the Issue transition  (original name) full function name is performIssueTransition:

    import { JiraCloudConnection } from '@avst-hzn/jira-cloud-api-v3';
     
    export async function run() {
        const JiraCloud = JiraCloudConnection.connect('CONNECTION_ID');
     
        await JiraCloud.All.performIssueTransition({ ... });
    }
    JS
  • Remapped Function Full Name - Full function name used in HTTP API, Managed API and Fluent API level, but only on Fluent API level when the function is accessed via secondary All group as shown above, otherwise a short name is used.

Error handling

Error handling adheres to the same rules as outlined in the base documentation, with a minor addition where HttpErrorhas a generic type that is always AtlassianErrorResponsewhich denotes the body type of the HTTP error response. It can be used in following ways:

import { JiraCloudConnection } from '@avst-hzn/jira-cloud-api-v3';
import { HttpError } from '@avst-hzn/jira-cloud-api-v3';
import { AtlassianErrorResponse } from '@avst-hzn/jira-cloud-api-v3/common';
 
export async function run() {
    const JiraCloud = JiraCloudConnection.connect('CONNECTION_ID');
 
    // Promise style
    JiraCloud.Issue.getIssue({
        issueIdOrKey: 'ISSUE-1'
    })
    .then(issue => {
        // Do something with the issue
    })
    .catch(error => {
        if (error instanceof HttpError) {
            console.error(error.response.body.errorMessages.join(', ')) // Print out Jira error messages that were sent back
        }
    });
 
    // Async/await style
    try {
        const issue = await JiraCloud.Issue.getIssue({
            issueIdOrKey: 'ISSUE-1'
        });
        // Do something with the issue
    } catch (error) {
        if (error instanceof HttpError) {
            const body = error.response.body as AtlassianErrorResponse; // Explicit type cast is needed here because HttpError's generic type cannot be automatically inferred the same way as it works in Promise based example
            console.error(body.errorMessages.join(', ')) // Print out Jira error messages that were sent back
        }
    }
 
    // Using error strategy
    const issue = await JiraCloud.Issue.getIssue({
        issueIdOrKey: 'ISSUE-1',
        errorStrategy: {
            handleHttpAnyError: error => {
                console.error(error.response.body.errorMessages.join(', ')) // Print out Jira error messages that were sent back
                return null; // Return null if something goes wrong
            }
        }
    });
    if (issue) {
        // Do something with issue
    }
}
JS

Common HTTP Errors

As mentioned in the baseline documentation that in place of common HTTP errors instead of throwing HttpError a more specific error is thrown that can be caught directly. Since more specific errors derive from HttpError you can still catch that one too.

Here is a mapping of common HTTP errors and error types that they are represented by:

HTTP ErrorError Type
400BadRequestError
401UnauthorizedError
403ForbiddenError
404NotFoundError
429TooManyRequestsError
5xxServerError

Working with custom fields

Since working with Issue custom fields can be major PITA then there is an additional highly experimental non-standard abstraction built on top of existing layers to make working with custom fields more civilised. The only requirement to work on that abstraction layer is to know what is the custom field type you are working with. To find you a custom field type you can check it in Jira Admin panel, or ask your your Jira admin to provide that information, or use Issue.Fields.getFields function to retrieve the list of all available fields in the Jira instance you are working with, and then filter out the custom field you are interested in.

Reading custom field data

Here is a example how to read custom field data without using helper methods:

import { JiraCloudConnection } from '@avst-hzn/jira-cloud-api-v3';
 
export async function run() {
    const JiraCloud = JiraCloudConnection.connect('CONNECTION_ID');
 
    const issue = await JiraCloud.Issue.getIssue({
        issueIdOrKey: 'ISSUE-1'
    });
 
    const fields = await JiraCloud.Issue.Field.getFields(); // Get the list of available field
    const projectPickerField = fields.find(field => field.name == 'ProjectPickerField'); // Filter out the field you are interested in
 
    if (projectPickerField) { // Check if the custom field exists, this example does not check if there are duplicate fields with the same name that otherwise needs to be checked
        const project = issue.fields[projectPickerField.id]; // Extract the field value from issue object
        // Do something with project field value
    }
}
JS

The same thing can be achieved slightly more conveniently by using special .getCustomFields() helper method. Using that approach also takes care of certain error checks that you otherwise would have to perform manually, such as an error will be thrown if there are more than one matching custom field for the given name:

import { JiraCloudConnection } from '@avst-hzn/jira-cloud-api-v3';
import { CustomFieldMultipleMatches } from '@avst-hzn/jira-cloud-api-v3/reader/customField';
 
export async function run() {
    const JiraCloud = JiraCloudConnection.connect('CONNECTION_ID');
 
    const issue = await JiraCloud.Issue.getIssue({
        issueIdOrKey: 'ISSUE-1'
    });
 
    try {
        const customFields = await issue.getCustomFields(); // Using special convenience function to retrieve populated custom fields collection, note that this is async operation
        const project = customFields.projectPickerFields.getValue('ProjectPickerField'); // Extracting custom field value by name without having to look up this custom field's ID manually
        if (project) { // Check if project field value is set
            // Do something with project field value
        }
    } catch (e) {
        if (e instanceof CustomFieldMultipleMatches) { // Thrown when more than one custom field was found for given name
            // Handle error
        }
    }
}
JS

When working on this abstraction level you can expect to get a proper type of information that otherwise would not be available, as shown below:

The example above works only on Fluent API level, if you are working on a lower level you can use IssueHelper to get a hold of CustomFieldReader as following:

import { getIssue } from '@avst-hzn/jira-cloud-api-v3/managed-api/issue';
import { IssueHelper } from '@avst-hzn/jira-cloud-api-v3/helper/issue';
 
export async function run() {
    const issueHelper = IssueHelper.create('CONNECTION_ID'); // Construct issue helper
 
    const issue = await getIssue({
        connection: 'CONNECTION_ID',
        issueIdOrKey: 'ISSUE-1'
    });
 
    const customFields = await issueHelper.getCustomFieldReader(issue); // Get custom field reader from issue helper
 
    const project = customFields.projectPickerFields.getValue('ProjectPickerField');
    // Do something with project
}
JS

Creating issue with custom fields

Similar convenience abstraction exists for building issue objects that can be achieved by using IssueBuilder. In addition to helping to set custom field values more conveniently this builder can also set system fields so you can use this builder to construct the entire issue object.

Following example demonstrates how to create an issue and set a custom field value without using IssueBuilder:

import { JiraCloudConnection } from '@avst-hzn/jira-cloud-api-v3';
 
export async function run() {
    const JiraCloud = JiraCloudConnection.connect('CONNECTION_ID');
 
    const project = await JiraCloud.Project.getProject({ // Get the project that we need to attach to issue and to issue's Project Picker custom field
        projectIdOrKey: 'PROJECT'
    });
 
    const issueTypes = await JiraCloud.Issue.Type.getTypesForUser(); // Get all issue types
    const issuetype = issueTypes.find(it => it.name == 'Task'); // Find the issue type we want to use to create a new issue with
 
    const fields = await JiraCloud.Issue.Field.getFields(); // Find all the fields
    const projectPickerField = fields.find(field => field.name == 'ProjectPickerField'); // Find the custom field we want to use
 
    const issue = await JiraCloud.Issue.createIssue({
        body: {
            fields: {
                project, // Set issue project
                issuetype, // Set issue type
                summary: 'Issue Summary', // Set issue summary
                [projectPickerField.id]: project // Set the custom field value to same project value as used for the issue itself
            }
        }
    });
    console.log(`Created issue key: ${issue.key}`); // Print out newly created issue key
}
JS

And the same thing can be achieved more conveniently using IssueBuilder as following, note how we don't have to manually find the project, issue type and the custom field, but instead we can set field values by names:

import { JiraCloudConnection } from '@avst-hzn/jira-cloud-api-v3';
 
export async function run() {
    const JiraCloud = JiraCloudConnection.connect('CONNECTION_ID');
 
    const issueRequest = await JiraCloud.Builder.IssueBuilder
        .withSummary('Issue Summary') // Set issue summary
        .withProject('PROJECT') // Set issue project
        .withIssueType('Task') // Set issue task
        .withProjectPickerCustomField('ProjectPickerField', 'PROJECT') // Set the custom field value
        .buildCreateRequest() // use .buildUpdateRequest() if you need to create a request for updating the issue, notice that it is async operation
 
    const issue = await JiraCloud.Issue.createIssue({
        body: issueRequest
    });
    console.log(`Created issue key: ${issue.key}`); // Print out newly created issue key
}
JS

IssueBuilder can also be injected when constructing body property which allows to reduce a verbosity a bit. The example above could be written slightly shorter by using injected builder as following:

import { JiraCloudConnection } from '@avst-hzn/jira-cloud-api-v3';
 
export async function run() {
    const JiraCloud = JiraCloudConnection.connect('CONNECTION_ID');
 
    const issue = await JiraCloud.Issue.createIssue({
        body: builder => builder // Calling .buildCreateRequest or .buildUpdateRequest is not necessary here, but you can still do it if you want
            .withSummary('Issue Summary') // Set issue summary
            .withProject('PROJECT') // Set issue project
            .withIssueType('Task') // Set issue task
            .withProjectPickerCustomField('ProjectPickerField', 'PROJECT') // Set the custom field value
    });
    console.log(`Created issue key: ${issue.key}`); // Print out newly created issue key
}
JS

The example above works only on Fluent API level, if you are working on a lower level you can use IssueHelper to get a hold of IssueBuilder as following:

import { createIssue } from '@avst-hzn/jira-cloud-api-v3/managed-api/issue';
import { IssueHelper } from '@avst-hzn/jira-cloud-api-v3/helper/issue';
 
export async function run() {
    const issueHelper = IssueHelper.create('CONNECTION_ID'); // Construct issue helper
 
    const issueRequest = await issueHelper.getIssueBuilder() // Get issue builder from issue helper
        .withSummary('Issue Summary')
        .withProject('PROJECT')
        .withIssueType('Task')
        .withProjectPickerCustomField('ProjectPickerField', 'PROJECT')
        .buildCreateRequest()
 
    const issue = await createIssue({
        connection: 'CONNECTION_ID',
        body: issueRequest
    });
    console.log(`Created issue key: ${issue.key}`); // Print out newly created issue key
}
JS