Skip to main content
cancel
Showing results for 
Search instead for 
Did you mean: 

Power BI is turning 10! Let’s celebrate together with dataviz contests, interactive sessions, and giveaways. Register now.

Reply
DanMcG
Frequent Visitor

Connect to Jira with Power BI - code for a function

EDIT:

  1. Please see the 'Accepted Solution' for an updated function that is a bit quicker and works on the V3 API (the original is now depricated).
  2. Also, there's a few comments below advertising their own integrations. If this is the right move for you, then go ahead, however the function in this solution is pretty much a straight copy / paste (2mins to get going), so it's worth giving it a try first before paying someone.

----

Hi,

 

I recently had to pull some data out of Jira, but couldn't see an easy way to do it natively in Power BI / Power Query, so created a custom function that does it for me based on a JQL statement.

 

Thought it might be useful to others, so have posted below (for my Jira instance this also works with an auto refresh in the service).

 

All you need to do is paste the below code into a blank query, then update the jiraDomain to your domain name.

 

You'll then see a function with 2 parameters, the first wants a JQL statement and the second needs a comma seperated list of the fields that you want from Jira (e.g. "key,Summary,Assignee,Status,Created,Project").

 

To autheniticate, I've been using basic auth with the username as my company email and the password as a Jira API token.

 

 

 

 

let Source = (jql as text, optional columnsToKeep as text) =>
let
    // demo data
    // jql = "project = JSD order by created DESC",
    // columnsToKeep = "key,Summary,Assignee,Status,Created,Project",

    jiraDomain = "https://<your-domain>.atlassian.net",

//get data from Jira using JQL
    getJqlResult = (jql as text)=>
        let
            createQueryTable = Table.FromList(List.Generate(() => 0, each _ < Json.Document(Web.Contents(jiraDomain & "/rest/api/3/search",[RelativePath="?fields=key&jql="&jql]))[total], each _ + 50), Splitter.SplitByNothing(), null, null, ExtraValues.Error),
            addQueries = Table.AddColumn(createQueryTable, "queryLists", each Json.Document(Web.Contents(jiraDomain & "/rest/api/3/search",[RelativePath="?startAt="&Text.From([Column1])&"&jql="&jql]))[issues]),
            removeStartAt = Table.RemoveColumns(addQueries,{"Column1"}),
            expandQueryLists = Table.ExpandListColumn(removeStartAt, "queryLists"),
            expandIssues = Table.ExpandRecordColumn(expandQueryLists, "queryLists", {"id", "key", "fields"}, {"id", "key", "fields"})
        in
            expandIssues,

//get data a list of Jira fields and what the display names are
    getJiraFieldsAndNames = () =>
        let
            jiraFieldNameList = Json.Document(Web.Contents(jiraDomain & "/rest/api/3/field")),
            fieldRecordsToTable = Table.FromList(jiraFieldNameList, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
            expandFieldRecords = Table.ExpandRecordColumn(fieldRecordsToTable, "Column1", {"id", "key", "name"}, {"id", "key", "name"})
        in
            expandFieldRecords,

//create a list of lists that can be used to rename from field system names
    createJiraRenameList = (listOfColumnsToRename as list)=>
        let
            fieldNames = getJiraFieldsAndNames(),
            countNameInstances = Table.AddColumn(fieldNames, "# of Name Columns", each let thisName = [name] in Table.RowCount(Table.SelectRows(fieldNames, each [name] = thisName))),
            checkWhetherFieldRequired = Table.AddColumn(countNameInstances, "renameRequired", each List.Contains(listOfColumnsToRename,[key])),
            filterToRequired = Table.SelectRows(checkWhetherFieldRequired, each ([renameRequired] = true) and ([#"# of Name Columns"] = 1)),
            addRenameListItems = Table.AddColumn(filterToRequired, "renameListItem", each {[key],[name]}),
            createRenameList = Table.Column(addRenameListItems,"renameListItem")
        in
            createRenameList,

//main code

    columnsToKeepAsList = 
        if columnsToKeep = null
        then null
        else Text.Split(Text.Replace(Text.Replace(columnsToKeep," ,",","),", ",","),","),
    
    jqlQueryResult = getJqlResult(jql),
    columnNamesAsList = Table.Column(Record.ToTable(jqlQueryResult{0}[fields]),"Name"),
    expandIssueFields = Table.ExpandRecordColumn(jqlQueryResult, "fields", columnNamesAsList, columnNamesAsList),
    renameColumns = Table.RenameColumns(expandIssueFields,createJiraRenameList(columnNamesAsList)),
    selectColumnsToKeep = 
        if columnsToKeep = null
        then renameColumns
        else Table.SelectColumns(renameColumns,columnsToKeepAsList)
in
    selectColumnsToKeep
in Source

 

 

 

 

1 ACCEPTED SOLUTION
DanMcG
Frequent Visitor

I've made an updated version of the funciton that uses the v3 API (the old one is deprecated). Also runs a bit quicker.

 

Instructions on how to use this function:

  1. Copy the code below into a blank query in Power BI (using the advanced editor).
  2. Update the 'jiraDomain' variable using advanced editor (near the top of the code) with the domain you see when you login to your Jira.
  3. Generate and save an access token as per Manage API tokens for your Atlassian account | Atlassian Support.
  4. Invoke the function:
    1. Put the email address you use to login to Jira in the 'jiraUsername'.
    2. Put the token from step 3. above into the 'jiraAccessToken'.
    3. Put a comma demilted list of the fields you want to return in the 'fields' parameter e.g. 'summary, parent, status':
      1. This list is not case sensitive.
      2. You can enter custom field names here.
      3. If you have more than 1 custom field with the same name it will return both fields with the custom field id added as a suffix.
    4. Add a JQL statement that specifies the issues you want to return.
      1. If you want to return everything you can enter 'key IS NOT EMPTY'.

 

let
    Source = (jiraUsername as text, jiraAccessToken as text, fields as text, jql as text) =>
let
//test data
    // jiraUsername = jiraUser,
    // jiraAccessToken = jiraToken,
    // fields = "id, parent, summary",
    // jql = "project = kan",

//!!!UPDATE THE BELOW VARIABLE!!!
    jiraDomain = "https://<your-domain>.atlassian.net",

//authenication
    auth = Binary.ToText(Text.ToBinary(jiraUsername & ":" & jiraAccessToken), BinaryEncoding.Base64),


//convert_field_names_to_ids function
convert_field_names_to_ids = (jiraUsername as text, jiraAccessToken as text, fields as text) =>
let 
    fieldsConverted = if fields = "id" then "key" else Text.Replace(Text.Replace(fields," ,",","),", ",","),

    fieldsToGetAsList = Text.Split(Text.Lower(fieldsConverted),","),
    jiraFieldNameList = Json.Document(Web.Contents(jiraDomain & "/rest/api/3/field", [Headers = [Authorization="Basic " & auth]])),
    fieldRecordsToTable = Table.FromList(jiraFieldNameList, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    expandFieldRecords = Table.ExpandRecordColumn(fieldRecordsToTable, "Column1", {"id", "key", "name"}, {"id", "key", "name"}),
    addGetFieldCheck = Table.AddColumn(expandFieldRecords, "getField?", each List.Contains(fieldsToGetAsList,Text.Lower([name]))),
    filterToFieldsToGet = Table.SelectRows(addGetFieldCheck, each ([#"getField?"] = true)),
    createFieldsString = Text.Combine(Table.Column(filterToFieldsToGet,"id"),",")

in
    createFieldsString,

//get_jira_fields_with_rename_lists

get_jira_fields_with_rename_lists = (jiraUsername as text, jiraAccessToken as text)=>
let
    auth = Binary.ToText(Text.ToBinary(jiraUsername & ":" & jiraAccessToken), BinaryEncoding.Base64),

    jiraFieldNameList = Json.Document(Web.Contents(jiraDomain & "/rest/api/3/field", [Headers = [Authorization="Basic " & auth]])),
    fieldRecordsToTable = Table.FromList(jiraFieldNameList, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    expandFieldRecords = Table.ExpandRecordColumn(fieldRecordsToTable, "Column1", {"id", "key", "name"}, {"id", "key", "name"}),
    #"Added Custom" = Table.AddColumn(expandFieldRecords, "renameList", each if List.Count(let name = [name] in Table.Column(Table.SelectRows(expandFieldRecords, each ([name] = name)),"name"))>1 then {[id],[name] & " (" & [id] & ")"} else {[id],[name]})
in
    #"Added Custom",

//field variables
    fields_list = Text.Lower(Text.Replace(Text.Replace(fields," ,",","),", ",",")),
    id_or_key_request = fields_list = "id" or fields_list = "key" or fields_list = "id,key" or fields_list = "key,id",
    fields_for_query = if fields = null or id_or_key_request then {"key"} else List.RemoveItems(Text.Split(convert_field_names_to_ids(jiraUsername, jiraAccessToken, fields_list), ","), {"issuekey"}),

    get_issues_list_function = (optional next_page_token as text) =>
        Json.Document(Web.Contents(jiraDomain & "/rest/api/3/search/jql",
            [
                Headers = [
                    Authorization="Basic " & auth,
                    #"Content-Type" = "application/json"
                ],
                Content = Json.FromValue(
                    [
                        jql = jql,
                        maxResults = 5000,
                        fields = fields_for_query,
                        nextPageToken = next_page_token
                    ]
                )
            ]
        )),


    get_issues = 
        List.Generate(
            () => get_issues_list_function(),
            each Record.HasFields(_, "issues"),
            each try get_issues_list_function(_[nextPageToken]) otherwise []
        ),
    #"Converted to Table" = Table.FromList(get_issues, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    #"Expanded Column1" = Table.ExpandRecordColumn(#"Converted to Table", "Column1", {"issues"}, {"issues"}),
  #"Expanded issues" = Table.ExpandListColumn(#"Expanded Column1", "issues"),
  #"Expanded issues 1" = Table.ExpandRecordColumn(#"Expanded issues", "issues", {"id", "key", "fields"}, {"id", "key", "fields"}),
  #"Expanded fields" = if id_or_key_request then #"Expanded issues 1" else Table.ExpandRecordColumn(#"Expanded issues 1", "fields", fields_for_query),
  rename_list = Table.SelectRows(get_jira_fields_with_rename_lists(jiraUser, jiraToken), each List.Contains(fields_for_query, [key]))[renameList],
  #"Renamed columns" = Table.RenameColumns(#"Expanded fields", rename_list)
in
    #"Renamed columns"
in
    Source

View solution in original post

7 REPLIES 7
DanMcG
Frequent Visitor

I've made an updated version of the funciton that uses the v3 API (the old one is deprecated). Also runs a bit quicker.

 

Instructions on how to use this function:

  1. Copy the code below into a blank query in Power BI (using the advanced editor).
  2. Update the 'jiraDomain' variable using advanced editor (near the top of the code) with the domain you see when you login to your Jira.
  3. Generate and save an access token as per Manage API tokens for your Atlassian account | Atlassian Support.
  4. Invoke the function:
    1. Put the email address you use to login to Jira in the 'jiraUsername'.
    2. Put the token from step 3. above into the 'jiraAccessToken'.
    3. Put a comma demilted list of the fields you want to return in the 'fields' parameter e.g. 'summary, parent, status':
      1. This list is not case sensitive.
      2. You can enter custom field names here.
      3. If you have more than 1 custom field with the same name it will return both fields with the custom field id added as a suffix.
    4. Add a JQL statement that specifies the issues you want to return.
      1. If you want to return everything you can enter 'key IS NOT EMPTY'.

 

let
    Source = (jiraUsername as text, jiraAccessToken as text, fields as text, jql as text) =>
let
//test data
    // jiraUsername = jiraUser,
    // jiraAccessToken = jiraToken,
    // fields = "id, parent, summary",
    // jql = "project = kan",

//!!!UPDATE THE BELOW VARIABLE!!!
    jiraDomain = "https://<your-domain>.atlassian.net",

//authenication
    auth = Binary.ToText(Text.ToBinary(jiraUsername & ":" & jiraAccessToken), BinaryEncoding.Base64),


//convert_field_names_to_ids function
convert_field_names_to_ids = (jiraUsername as text, jiraAccessToken as text, fields as text) =>
let 
    fieldsConverted = if fields = "id" then "key" else Text.Replace(Text.Replace(fields," ,",","),", ",","),

    fieldsToGetAsList = Text.Split(Text.Lower(fieldsConverted),","),
    jiraFieldNameList = Json.Document(Web.Contents(jiraDomain & "/rest/api/3/field", [Headers = [Authorization="Basic " & auth]])),
    fieldRecordsToTable = Table.FromList(jiraFieldNameList, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    expandFieldRecords = Table.ExpandRecordColumn(fieldRecordsToTable, "Column1", {"id", "key", "name"}, {"id", "key", "name"}),
    addGetFieldCheck = Table.AddColumn(expandFieldRecords, "getField?", each List.Contains(fieldsToGetAsList,Text.Lower([name]))),
    filterToFieldsToGet = Table.SelectRows(addGetFieldCheck, each ([#"getField?"] = true)),
    createFieldsString = Text.Combine(Table.Column(filterToFieldsToGet,"id"),",")

in
    createFieldsString,

//get_jira_fields_with_rename_lists

get_jira_fields_with_rename_lists = (jiraUsername as text, jiraAccessToken as text)=>
let
    auth = Binary.ToText(Text.ToBinary(jiraUsername & ":" & jiraAccessToken), BinaryEncoding.Base64),

    jiraFieldNameList = Json.Document(Web.Contents(jiraDomain & "/rest/api/3/field", [Headers = [Authorization="Basic " & auth]])),
    fieldRecordsToTable = Table.FromList(jiraFieldNameList, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    expandFieldRecords = Table.ExpandRecordColumn(fieldRecordsToTable, "Column1", {"id", "key", "name"}, {"id", "key", "name"}),
    #"Added Custom" = Table.AddColumn(expandFieldRecords, "renameList", each if List.Count(let name = [name] in Table.Column(Table.SelectRows(expandFieldRecords, each ([name] = name)),"name"))>1 then {[id],[name] & " (" & [id] & ")"} else {[id],[name]})
in
    #"Added Custom",

//field variables
    fields_list = Text.Lower(Text.Replace(Text.Replace(fields," ,",","),", ",",")),
    id_or_key_request = fields_list = "id" or fields_list = "key" or fields_list = "id,key" or fields_list = "key,id",
    fields_for_query = if fields = null or id_or_key_request then {"key"} else List.RemoveItems(Text.Split(convert_field_names_to_ids(jiraUsername, jiraAccessToken, fields_list), ","), {"issuekey"}),

    get_issues_list_function = (optional next_page_token as text) =>
        Json.Document(Web.Contents(jiraDomain & "/rest/api/3/search/jql",
            [
                Headers = [
                    Authorization="Basic " & auth,
                    #"Content-Type" = "application/json"
                ],
                Content = Json.FromValue(
                    [
                        jql = jql,
                        maxResults = 5000,
                        fields = fields_for_query,
                        nextPageToken = next_page_token
                    ]
                )
            ]
        )),


    get_issues = 
        List.Generate(
            () => get_issues_list_function(),
            each Record.HasFields(_, "issues"),
            each try get_issues_list_function(_[nextPageToken]) otherwise []
        ),
    #"Converted to Table" = Table.FromList(get_issues, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    #"Expanded Column1" = Table.ExpandRecordColumn(#"Converted to Table", "Column1", {"issues"}, {"issues"}),
  #"Expanded issues" = Table.ExpandListColumn(#"Expanded Column1", "issues"),
  #"Expanded issues 1" = Table.ExpandRecordColumn(#"Expanded issues", "issues", {"id", "key", "fields"}, {"id", "key", "fields"}),
  #"Expanded fields" = if id_or_key_request then #"Expanded issues 1" else Table.ExpandRecordColumn(#"Expanded issues 1", "fields", fields_for_query),
  rename_list = Table.SelectRows(get_jira_fields_with_rename_lists(jiraUser, jiraToken), each List.Contains(fields_for_query, [key]))[renameList],
  #"Renamed columns" = Table.RenameColumns(#"Expanded fields", rename_list)
in
    #"Renamed columns"
in
    Source
Anonymous
Not applicable

Hi @DanMcG!

 

Integrating Jira and Power BI can be challenging, but fortunately, you can use two main methods to achieve this. One option is the Jira REST API, which requires technical skills and can be complex. The second option is Power BI Connector for Jira.
It is a user-friendly app that enables you to export Jira data to Power BI without the need for coding skills. Learn more about the second method here: 

https://marketplace.atlassian.com/apps/1221150/power-bi-connector-for-jira?hosting=cloud&tab=overvie... it's available for Jira Cloud / Server / Data Center.

 We have a fast/responsive support team at support@alpha-serve.com . Please let us know if you need any assistance.

Aryna
Helper V
Helper V

Hi @DanMcG , thank you for your post. I just found another solution that might be helpful. This is 

script that integrates Power Bi into Jira: https://vidi-corp.com/how-to-connect-jira-to-power-bi/

This way you pay once and never have to worry about the subscription

DanMcG
Frequent Visitor

Hi,

 

Just made a better one (doesn't pull all fields, so should be much better for bigger queries). For auth follow the same instructions as before.

 

NOTE: with this one, you can also specifiy how many issues is retured in each request (a larger number makes it run faster), but if you're returing a lot of columns Jira may limit this and so you will miss some info. So if you're returning a lot of info and want to make it >50, be careful and do some testing that you're getting all the data you expect (a way to test this is looking at items returned in the query versus what the JQL returns in Jira) - my advice would be to just leave it at 50 😊.

 

 

 

let Source = (optional jql as text, optional fields as text, optional itemsPerQuery as number) =>

let
    // itemsPerQuery = null,//50,
    // jql = null,//"project = ABC",
    // fields = "assignee,key,summary,cReated",

    jiraDomain = "https://<youdomain>.atlassian.net",

    jqlConverted = if jql = null then "" else jql,
    fieldsConverted = if fields = "id" then "key" else fields,
    itemsPerQueryConverted = if itemsPerQuery = null then 50 else Number.Round(itemsPerQuery,0),

    #"(convertFieldsNamesToIds)" = (fieldsToGet as text) =>
        let
            // jiraDomain = "https://rimestechnologies.atlassian.net",
            // fields = "assignee,key,Client Id (global),summary",

            fieldsToGetAsList = Text.Split(Text.Lower(fieldsToGet),","),

            jiraFieldNameList = Json.Document(Web.Contents(jiraDomain & "/rest/api/3/field")),
            fieldRecordsToTable = Table.FromList(jiraFieldNameList, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
            expandFieldRecords = Table.ExpandRecordColumn(fieldRecordsToTable, "Column1", {"id", "key", "name"}, {"id", "key", "name"}),
            addGetFieldCheck = Table.AddColumn(expandFieldRecords, "getField?", each List.Contains(fieldsToGetAsList,Text.Lower([name]))),
            filterToFieldsToGet = Table.SelectRows(addGetFieldCheck, each ([#"getField?"] = true)),
            createFieldsString = Text.Combine(Table.Column(filterToFieldsToGet,"id"),",")
        in
            createFieldsString,

#"(createRenameListFromIds)" = (idsToGetNames as text) =>
    let
        // jiraDomain = "https://rimestechnologies.atlassian.net",
        // idsToGetNames = "summary,customfield_12976,assignee,created",

        idsToGetNamesAsList = Text.Split(Text.Lower(idsToGetNames),","),

        jiraFieldNameList = Json.Document(Web.Contents(jiraDomain & "/rest/api/3/field")),
        fieldRecordsToTable = Table.FromList(jiraFieldNameList, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
        expandFieldRecords = Table.ExpandRecordColumn(fieldRecordsToTable, "Column1", {"id", "key", "name"}, {"id", "key", "name"}),
        addFieldCheck = Table.AddColumn(expandFieldRecords, "getField?", each List.Contains(idsToGetNamesAsList,Text.Lower([id]))),
        filterToFields = Table.SelectRows(addFieldCheck, each ([#"getField?"] = true)),
        addRenameLists = Table.AddColumn(filterToFields, "renameLists", each {[id],[name]}),
        createListOfLists = Table.Column(addRenameLists,"renameLists")
    in
        createListOfLists,

    createQueryTable = 
        Table.FromList(
            List.Generate(
                () => 0, 
                each _ < Json.Document(Web.Contents(jiraDomain & "/rest/api/3/search",
                    [RelativePath=
                        "?fields=key"
                        & "&jql=" & jqlConverted
                    ]))[total], 
                each _ + itemsPerQueryConverted
            )
            , Splitter.SplitByNothing(), null, null, ExtraValues.Error
        ),
    addQueries = Table.AddColumn(createQueryTable, "queryLists", each 
        Json.Document(Web.Contents(
            jiraDomain & "/rest/api/3/search"
            ,[RelativePath=
                "?startAt=" & Text.From([Column1])
                & "&jql=" & jqlConverted
                & "&maxResults=" & Text.From(itemsPerQueryConverted)
                & "&fields=" & Text.From(if fieldsConverted = null then "*all" else #"(convertFieldsNamesToIds)"(fieldsConverted))
            ]
        ))[issues]),
    removeStartAt = Table.RemoveColumns(addQueries,{"Column1"}),
    expandQueryLists = Table.ExpandListColumn(removeStartAt, "queryLists"),
    expandIssues = Table.ExpandRecordColumn(expandQueryLists, "queryLists", {"id", "key", "fields"}, {"id", "key", "fields"}),
    expandFields = 
        if List.First(Table.Column(expandIssues,"fields")) = null
        then expandIssues
        else Table.ExpandRecordColumn(expandIssues, "fields", Record.FieldNames(List.First(Table.Column(expandIssues,"fields"))), Record.FieldNames(List.First(Table.Column(expandIssues,"fields")))),
    renameFields = 
        if List.First(Table.Column(expandIssues,"fields")) = null
        then Table.RemoveColumns(expandFields,{"fields"})
        else Table.RenameColumns(expandFields,#"(createRenameListFromIds)"(Text.Combine(Record.FieldNames(List.First(Table.Column(expandIssues,"fields"))),",")))
in
    renameFields
in Source

 

 

 

 

 

 

Anonymous
Not applicable

Hi there,
Thanks for sharing this
Is there a modification to this code to add custom fields as well?

Hey @Anonymous,

 

I think it should work with custom fields.

 

If you put in a JQL query that has custom fields included in its definition, that should work.

v-jingzhang
Community Support
Community Support

Thank you for your sharing in the community! 

Helpful resources

Announcements
Join our Fabric User Panel

Join our Fabric User Panel

This is your chance to engage directly with the engineering team behind Fabric and Power BI. Share your experiences and shape the future.

June 2025 Power BI Update Carousel

Power BI Monthly Update - June 2025

Check out the June 2025 Power BI update to learn about new features.

June 2025 community update carousel

Fabric Community Update - June 2025

Find out what's new and trending in the Fabric community.