Skip to main content

Jira

Our Jira integration allows you to import issues and projects from your Jira cloud account into Port, according to your mapping and definition.

Note

This integration supports Jira Cloud at the moment, support for Jira Server is in development.

Common use casesโ€‹

  • Map issues and projects in your Jira organization environment.
  • Watch for object changes (create/update/delete) in real-time, and automatically apply the changes to your entities in Port.
  • Create/delete Jira objects using self-service actions.

Prerequisitesโ€‹

To install the integration, you need a Kubernetes cluster that the integration's container chart will be deployed to.

Please make sure that you have kubectl and helm installed on your machine, and that your kubectl CLI is connected to the Kubernetes cluster where you plan to install the integration.

installationโ€‹

Choose one of the following installation methods:

Using this installation option means that the integration will be able to update Port in real time using webhooks.

This table summarizes the available parameters for the installation. Set them as you wish in the script below, then copy it and run it in your terminal:

ParameterDescriptionExampleRequired
port.clientIdYour port client id (How to get the credentials)โœ…
port.clientSecretYour port client secret (How to get the credentials)โœ…
integration.secrets.atlassianUserEmailThe email of the user used to query Jirauser@example.comโœ…
integration.secrets.atlassianUserTokenJira API token generated by the userโœ…
integration.config.jiraHostThe URL of your Jirahttps://example.atlassian.netโœ…
integration.config.appHostThe host of the Port Ocean app. Used to set up the integration endpoint as the target for webhooks created in Jirahttps://my-ocean-integration.comโŒ

Advanced configuration

ParameterDescription
integration.eventListener.typeThe event listener type. Read more about event listeners
integration.typeThe integration to be installed
scheduledResyncIntervalThe number of minutes between each resync. When not set the integration will resync for each event listener resync event. Read more about scheduledResyncInterval
initializePortResourcesDefault true, When set to true the integration will create default blueprints and the port App config Mapping. Read more about initializePortResources

helm repo add --force-update port-labs https://port-labs.github.io/helm-charts
helm upgrade --install my-jira-integration port-labs/port-ocean \
--set port.clientId="PORT_CLIENT_ID" \
--set port.clientSecret="PORT_CLIENT_SECRET" \
--set port.baseUrl="https://api.getport.io" \
--set initializePortResources=true \
--set scheduledResyncInterval=120 \
--set integration.identifier="my-jira-integration" \
--set integration.type="jira" \
--set integration.eventListener.type="POLLING" \
--set integration.config.jiraHost="string" \
--set integration.secrets.atlassianUserEmail="string" \
--set integration.secrets.atlassianUserToken="string"

Ingesting Jira objectsโ€‹

The Jira integration uses a YAML configuration to describe the process of loading data into the developer portal.

Here is an example snippet from the config which demonstrates the process for getting project data from Jira:

createMissingRelatedEntities: true
deleteDependentEntities: true
resources:
- kind: project
selector:
query: "true"
port:
entity:
mappings:
identifier: .key
title: .name
blueprint: '"project"'
properties:
url: (.self | split("/") | .[:3] | join("/")) + "/projects/" + .key

The integration makes use of the JQ JSON processor to select, modify, concatenate, transform and perform other operations on existing fields and values from Jira's API events.

Configuration structureโ€‹

The integration configuration determines which resources will be queried from Jira, and which entities and properties will be created in Port.

Supported resources

The following resources can be used to map data from Jira, it is possible to reference any field that appears in the API responses linked below for the mapping configuration.

  • The root key of the integration configuration is the resources key:

    resources:
    - kind: project
    selector:
    ...
  • The kind key is a specifier for a Jira object:

      resources:
    - kind: project
    selector:
    ...
  • The selector and the query keys allow you to filter which objects of the specified kind will be ingested into your software catalog:

    resources:
    - kind: project
    selector:
    query: "true" # JQ boolean expression. If evaluated to false - this object will be skipped.
    port:
  • The port, entity and the mappings keys are used to map the Jira object fields to Port entities. To create multiple mappings of the same kind, you can add another item in the resources array;

    resources:
    - kind: project
    selector:
    query: "true"
    port:
    entity:
    mappings: # Mappings between one Jira object to a Port entity. Each value is a JQ query.
    identifier: .key
    title: .name
    blueprint: '"project"'
    properties:
    url: (.self | split("/") | .[:3] | join("/")) + "/projects/" + .key
    - kind: project # In this instance project is mapped again with a different filter
    selector:
    query: '.name == "MyProjectName"'
    port:
    entity:
    mappings: ...
Blueprint key

Note the value of the blueprint key - if you want to use a hardcoded string, you need to encapsulate it in 2 sets of quotes, for example use a pair of single-quotes (') and then another pair of double-quotes (")

Ingest data into Portโ€‹

To ingest Jira objects using the integration configuration, you can follow the steps below:

  1. Go to the DevPortal Builder page.
  2. Select a blueprint you want to ingest using Jira.
  3. Choose the Ingest Data option from the menu.
  4. Select Jira under the Project management providers category.
  5. Modify the configuration according to your needs.
  6. Click Resync.

Examplesโ€‹

Examples of blueprints and the relevant integration configurations:

Issueโ€‹

Issue blueprint
{
"identifier": "issue",
"title": "Jira Issue",
"icon": "Jira",
"schema": {
"properties": {
"url": {
"title": "Issue URL",
"type": "string",
"format": "url",
"description": "URL to the issue in Jira"
},
"status": {
"title": "Status",
"type": "string",
"description": "The status of the issue"
},
"issueType": {
"title": "Type",
"type": "string",
"description": "The type of the issue"
},
"components": {
"title": "Components",
"type": "array",
"description": "The components related to this issue"
},
"assignee": {
"title": "Assignee",
"type": "string",
"format": "user",
"description": "The user assigned to the issue"
},
"reporter": {
"title": "Reporter",
"type": "string",
"description": "The user that reported to the issue",
"format": "user"
},
"creator": {
"title": "Creator",
"type": "string",
"description": "The user that created to the issue",
"format": "user"
}
}
},
"relations": {
"project": {
"target": "project",
"title": "Project",
"description": "The Jira project that contains this issue",
"required": false,
"many": false
},
"parentIssue": {
"target": "issue",
"title": "Parent Issue",
"required": false,
"many": false
},
"subtasks": {
"target": "issue",
"title": "Subtasks",
"required": false,
"many": true
}
}
}
Integration configuration
createMissingRelatedEntities: true
deleteDependentEntities: true
resources:
- kind: issue
selector:
query: "true"
port:
entity:
mappings:
identifier: .key
title: .fields.summary
blueprint: '"issue"'
properties:
url: (.self | split("/") | .[:3] | join("/")) + "/browse/" + .key
status: .fields.status.name
issueType: .fields.issuetype.name
components: .fields.components
assignee: .fields.assignee.displayName
reporter: .fields.reporter.displayName
creator: .fields.creator.displayName
relations:
project: .fields.project.key
parentIssue: .fields.parent.key
subtasks: .fields.subtasks | map(.key)

Projectโ€‹

Project blueprint
{
"identifier": "project",
"title": "Jira Project",
"icon": "Jira",
"description": "A Jira project",
"schema": {
"properties": {
"url": {
"title": "Project URL",
"type": "string",
"format": "url",
"description": "URL to the project in Jira"
}
}
}
}
Integration configuration
createMissingRelatedEntities: true
deleteDependentEntities: true
resources:
- kind: project
selector:
query: "true"
port:
entity:
mappings:
identifier: .key
title: .name
blueprint: '"project"'
properties:
url: (.self | split("/") | .[:3] | join("/")) + "/projects/" + .key

Let's Test Itโ€‹

This section includes a sample response data from Jira. In addition, it includes the entity created from the resync event based on the Ocean configuration provided in the previous section.

Payloadโ€‹

Here is an example of the payload structure from Jira:

Project response data
{
"expand": "description,lead,issueTypes,url,projectKeys,permissions,insight",
"self": "https://myaccount.atlassian.net/rest/api/3/project/10000",
"id": "10000",
"key": "PA",
"name": "Port-AI",
"avatarUrls": {
"48x48": "https://myaccount.atlassian.net/rest/api/3/universal_avatar/view/type/project/avatar/10413",
"24x24": "https://myaccount.atlassian.net/rest/api/3/universal_avatar/view/type/project/avatar/10413?size=small",
"16x16": "https://myaccount.atlassian.net/rest/api/3/universal_avatar/view/type/project/avatar/10413?size=xsmall",
"32x32": "https://myaccount.atlassian.net/rest/api/3/universal_avatar/view/type/project/avatar/10413?size=medium"
},
"projectTypeKey": "software",
"simplified": true,
"style": "next-gen",
"isPrivate": false,
"properties": {},
"entityId": "7f4f8d6f-705b-4074-84be-46f0d012cd8e",
"uuid": "7f4f8d6f-705b-4074-84be-46f0d012cd8e"
}
Issue response data
{
"expand": "operations,versionedRepresentations,editmeta,changelog,customfield_10010.requestTypePractice,renderedFields",
"id": "10000",
"self": "https://myaccount.atlassian.net/rest/api/3/issue/10000",
"key": "PA-1",
"fields": {
"statuscategorychangedate": "2023-11-06T11:02:59.341+0000",
"issuetype": {
"self": "https://myaccount.atlassian.net/rest/api/3/issuetype/10001",
"id": "10001",
"description": "Tasks track small, distinct pieces of work.",
"iconUrl": "https://myaccount.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10318?size=medium",
"name": "Task",
"subtask": false,
"avatarId": 10318,
"entityId": "a7309bf9-70c5-4237-bdaf-0261037b6ecc",
"hierarchyLevel": 0
},
"timespent": "None",
"customfield_10030": "None",
"project": {
"self": "https://myaccount.atlassian.net/rest/api/3/project/10000",
"id": "10000",
"key": "PA",
"name": "Port-AI",
"projectTypeKey": "software",
"simplified": true,
"avatarUrls": {
"48x48": "https://myaccount.atlassian.net/rest/api/3/universal_avatar/view/type/project/avatar/10413",
"24x24": "https://myaccount.atlassian.net/rest/api/3/universal_avatar/view/type/project/avatar/10413?size=small",
"16x16": "https://myaccount.atlassian.net/rest/api/3/universal_avatar/view/type/project/avatar/10413?size=xsmall",
"32x32": "https://myaccount.atlassian.net/rest/api/3/universal_avatar/view/type/project/avatar/10413?size=medium"
}
},
"customfield_10031": "None",
"customfield_10032": "None",
"fixVersions": [],
"aggregatetimespent": "None",
"resolution": "None",
"customfield_10027": "None",
"customfield_10028": "None",
"customfield_10029": "None",
"resolutiondate": "None",
"workratio": -1,
"watches": {
"self": "https://myaccount.atlassian.net/rest/api/3/issue/PA-1/watchers",
"watchCount": 1,
"isWatching": true
},
"lastViewed": "None",
"created": "2023-11-06T11:02:59.000+0000",
"customfield_10020": "None",
"customfield_10021": "None",
"customfield_10022": "None",
"priority": {
"self": "https://myaccount.atlassian.net/rest/api/3/priority/3",
"iconUrl": "https://myaccount.atlassian.net/images/icons/priorities/medium.svg",
"name": "Medium",
"id": "3"
},
"customfield_10023": "None",
"customfield_10024": "None",
"customfield_10025": "None",
"labels": ["infra"],
"customfield_10026": "None",
"customfield_10016": "None",
"customfield_10017": "None",
"customfield_10018": {
"hasEpicLinkFieldDependency": false,
"showField": false,
"nonEditableReason": {
"reason": "PLUGIN_LICENSE_ERROR",
"message": "The Parent Link is only available to Jira Premium users."
}
},
"customfield_10019": "0|hzzzzz:",
"timeestimate": "None",
"aggregatetimeoriginalestimate": "None",
"versions": [],
"issuelinks": [],
"assignee": {
"self": "https://myaccount.atlassian.net/rest/api/3/user?accountId=712020%3A05acda87-42da-44d8-b21e-f71a508e5d11",
"accountId": "712020:05acda87-42da-44d8-b21e-f71a508e5d11",
"emailAddress": "username@example.com.io",
"avatarUrls": {
"48x48": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png",
"24x24": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png",
"16x16": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png",
"32x32": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png"
},
"displayName": "User Name",
"active": true,
"timeZone": "UTC",
"accountType": "atlassian"
},
"updated": "2023-11-06T11:03:18.244+0000",
"status": {
"self": "https://myaccount.atlassian.net/rest/api/3/status/10000",
"description": "",
"iconUrl": "https://myaccount.atlassian.net/",
"name": "To Do",
"id": "10000",
"statusCategory": {
"self": "https://myaccount.atlassian.net/rest/api/3/statuscategory/2",
"id": 2,
"key": "new",
"colorName": "blue-gray",
"name": "To Do"
}
},
"components": [],
"timeoriginalestimate": "None",
"description": "None",
"customfield_10010": "None",
"customfield_10014": "None",
"customfield_10015": "None",
"customfield_10005": "None",
"customfield_10006": "None",
"security": "None",
"customfield_10007": "None",
"customfield_10008": "None",
"aggregatetimeestimate": "None",
"customfield_10009": "None",
"summary": "Setup infra",
"creator": {
"self": "https://myaccount.atlassian.net/rest/api/3/user?accountId=712020%3A05acda87-42da-44d8-b21e-f71a508e5d11",
"accountId": "712020:05acda87-42da-44d8-b21e-f71a508e5d11",
"emailAddress": "username@example.com.io",
"avatarUrls": {
"48x48": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png",
"24x24": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png",
"16x16": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png",
"32x32": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png"
},
"displayName": "User Name",
"active": true,
"timeZone": "UTC",
"accountType": "atlassian"
},
"subtasks": [],
"reporter": {
"self": "https://myaccount.atlassian.net/rest/api/3/user?accountId=712020%3A05acda87-42da-44d8-b21e-f71a508e5d11",
"accountId": "712020:05acda87-42da-44d8-b21e-f71a508e5d11",
"emailAddress": "username@example.com.io",
"avatarUrls": {
"48x48": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png",
"24x24": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png",
"16x16": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png",
"32x32": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png"
},
"displayName": "User Name",
"active": true,
"timeZone": "UTC",
"accountType": "atlassian"
},
"aggregateprogress": {
"progress": 0,
"total": 0
},
"customfield_10001": "None",
"customfield_10002": "None",
"customfield_10003": "None",
"customfield_10004": "None",
"environment": "None",
"duedate": "None",
"progress": {
"progress": 0,
"total": 0
},
"votes": {
"self": "https://myaccount.atlassian.net/rest/api/3/issue/PA-1/votes",
"votes": 0,
"hasVoted": false
}
}
}

Mapping Resultโ€‹

The combination of the sample payload and the Ocean configuration generates the following Port entity:

Project entity in Port
{
"identifier": "PA",
"title": "Port-AI",
"icon": null,
"blueprint": "project",
"team": [],
"properties": {
"url": "https://myaccount.atlassian.net/projects/PA"
},
"relations": {},
"createdAt": "2023-11-06T11:22:05.433Z",
"createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW",
"updatedAt": "2023-11-06T11:22:05.433Z",
"updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW"
}
Issue entity in Port
{
"identifier": "PA-1",
"title": "Setup infra",
"icon": null,
"blueprint": "issue",
"team": [],
"properties": {
"url": "https://myaccount.atlassian.net/browse/PA-1",
"status": "To Do",
"issueType": "Task",
"components": [],
"assignee": "User Name",
"reporter": "User Name",
"creator": "User Name"
},
"relations": {
"parentIssue": null,
"project": "PA",
"subtasks": []
},
"createdAt": "2023-11-06T11:22:07.550Z",
"createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW",
"updatedAt": "2023-11-06T11:22:07.550Z",
"updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW"
}