Forums

Articles
Create
cancel
Showing results for 
Search instead for 
Did you mean: 

Python Script: Retrieve and analyze Jira Cloud project statuses

Hi Atlassian Community!

I'm back with another helpful Python script designed to retrieve and analyze Jira project statuses. This script offers valuable insights into your project workflows by extracting all available statuses for specified projects, providing a clear overview of their configurations.

The solution

This script leverages Jira Cloud REST API endpoints (this and this) to fetch project details and then retrieve all associated statuses. The collected data is logged directly to the console and saved to a detailed log file (.log) and a structured CSV file (.csv) for easy analysis and filtering.

Here's what the script focuses on:

  • Status retrieval per project: Gathers all statuses configured within one or multiple specified Jira projects
  • Detailed status information: For each status, it extracts its name, ID, and category (e.g., "To Do," "In Progress," "Done").
  • Export to CSV: Compiles all retrieved status data into a comprehensive CSV file, linking each status back to its respective project

Key Features

  • Analyzes statuses from one or multiple projects in a single run
  • Provides a clear breakdown of status names, IDs, and categories per project.
  • Generates a detailed .log file with all analysis results
  • Additionally saves comprehensive status metadata to a well-structured CSV file, including: Status Name, Status ID, Status Category, Project Key, Project ID
  • Prompts you for your Jira Cloud domain, email, and API token for secure authentication
  • Includes robust error handling for API requests and authentication issues

Preparation

  1. Ensure the user running the script has "Browse Projects" and "View Issues" permissions in the relevant Jira projects
  2. Install the requests library: pip install requests
  3. Prepare your Jira Cloud domain (your-domain.atlassian.net), email address, and API token

The Script

import requests
from requests.auth import HTTPBasicAuth
from getpass import getpass
import logging
import os
import csv
import json
from datetime import datetime

log_filename = f"jira_statuses_retrieval_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
file_handler = logging.FileHandler(log_filename)
file_handler.setLevel(logging.INFO)
file_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(file_formatter)
logging.getLogger().addHandler(file_handler)

def get_jira_auth():
    """Prompts the user for Jira Cloud domain, email, and API token."""
    jira_domain = input("Enter your Jira Cloud domain (e.g., your-domain.atlassian.net): ")
    email = input("Enter your Jira email: ")
    api_token = getpass("Enter your Jira API token: ")
    return jira_domain, HTTPBasicAuth(email, api_token)

def get_project_details(jira_domain, auth, project_key):
    """
    Retrieves the project ID and name (key) for a given project key.
    """
    url = f"https://{jira_domain}/rest/api/3/project/{project_key}"
    headers = {"Accept": "application/json"}

    logging.info(f"Attempting to retrieve project details for key: {project_key}")
    try:
        response = requests.get(url, headers=headers, auth=auth)
        response.raise_for_status()
        project_data = response.json()
        project_id = project_data.get('id')
        project_name = project_data.get('key')

        if project_id and project_name:
            logging.info(f"Successfully retrieved project details for '{project_key}': ID={project_id}, Name={project_name}")
            return project_id, project_name
        else:
            logging.error(f"Could not find project ID or name for project key '{project_key}'. Response: {project_data}")
            return None, None
    except requests.exceptions.RequestException as e:
        logging.error(f"Error fetching project details for '{project_key}': {e}")
        if response is not None:
            if response.status_code == 401:
                logging.error("Authentication failed. Please verify your email and API token.")
            elif response.status_code == 404:
                logging.error(f"Project with key '{project_key}' not found.")
        return None, None

def get_project_statuses(jira_domain, auth, project_id):
    """
    Retrieves all statuses associated with a given project ID.
    Handles pagination if necessary, though Jira's /statuses/search often returns all in one go.
    """
    base_url = f"https://{jira_domain}/rest/api/3/statuses/search"
    headers = {"Accept": "application/json"}
    all_statuses = []
    start_at = 0
    max_results = 200 # Default max results, can be adjusted

    logging.info(f"Searching for statuses for project ID: {project_id}")

    while True:
        params = {
            "projectId": project_id,
            "startAt": start_at,
            "maxResults": max_results
        }
        try:
            response = requests.get(base_url, headers=headers, auth=auth, params=params)
            response.raise_for_status()
            status_search_results = response.json()
            
            statuses = status_search_results.get('values', [])
            all_statuses.extend(statuses)

            total = status_search_results.get('total', 0)
            is_last = status_search_results.get('isLast', True)

            logging.info(f"Retrieved {len(all_statuses)} of {total} statuses so far for project ID {project_id}.")

            if is_last or (start_at + max_results) >= total:
                break
            else:
                start_at += max_results

        except requests.exceptions.RequestException as e:
            logging.error(f"Error fetching statuses for project ID {project_id}: {e}")
            if response is not None and response.status_code == 401:
                logging.error("Authentication failed. Please verify your email and API token.")
            elif response is not None and response.status_code == 404:
                logging.error(f"Statuses for project ID '{project_id}' not found.")
            return None
    
    logging.info(f"Successfully retrieved {len(all_statuses)} statuses for project ID {project_id}.")
    return all_statuses

def save_to_csv(data, filename="jira_project_statuses.csv"):
    """Saves the processed status data to a CSV file."""
    if not data:
        logging.warning("No status data to save to CSV.")
        return

    fieldnames = ["Status Name", "Status ID", "Status Category", "Project Key", "Project ID"]
    try:
        with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
            writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
            writer.writeheader()
            writer.writerows(data)
        logging.info(f"Crafting CSV file with the retrieved data.")
        logging.info(f"Operation completed, CSV file saved in {os.path.abspath(filename)}")
    except Exception as e:
        logging.error(f"Error saving data to CSV file: {e}")

def main():
    jira_domain, auth = get_jira_auth()

    project_keys_input = input("Enter the Jira project key(s) separated by comma (e.g., PROJ1,PROJ2): ").strip()
    project_keys = [key.strip().upper() for key in project_keys_input.split(',') if key.strip()]

    if not project_keys:
        logging.error("No project keys provided. Exiting.")
        return

    all_statuses_for_csv = []
    processed_project_ids = set()

    for project_key in project_keys:
        logging.info(f"\n--- Processing project: {project_key} ---")
        project_id, project_name = get_project_details(jira_domain, auth, project_key)

        if project_id and project_name:
            if project_id in processed_project_ids:
                logging.info(f"Project ID {project_id} for key '{project_key}' already processed. Skipping redundant status retrieval.")
                continue

            project_statuses = get_project_statuses(jira_domain, auth, project_id)
            
            if project_statuses:
                for status in project_statuses:
                    all_statuses_for_csv.append({
                        "Status Name": status.get('name', ''),
                        "Status ID": status.get('id', ''),
                        "Status Category": status.get('statusCategory', ''),
                        "Project Key": project_name,
                        "Project ID": project_id
                    })
                processed_project_ids.add(project_id)
            else:
                logging.warning(f"No statuses found or an error occurred for project '{project_name}' (ID: {project_id}).")
        else:
            logging.error(f"Skipping project key '{project_key}' due to inability to retrieve project ID/name.")

    if all_statuses_for_csv:
        all_statuses_for_csv.sort(key=lambda x: (x['Project Key'], x['Status Name']))
        save_to_csv(all_statuses_for_csv)
    else:
        logging.info("No status data retrieved across all selected projects. CSV file will not be created.")

    logging.info(f"\nAnalysis complete. Detailed log saved to {os.path.abspath(log_filename)}")

if __name__ == "__main__":
    main()

Output example:

image.png

Disclaimer

This script is provided "as is" and "as available" without warranties and is not officially supported or endorsed by Atlassian. Use at your own risk. Jira API changes may impact functionality.

Cheers!

Comment

Log in or Sign up to comment
TAGS
AUG Leaders

Atlassian Community Events