I want to have .csv files in which I can see all the Jira apps we are using, which users and groups are using those, Also when was the last time they used those apps

#Below python script is giving an error. It hangs, i have attached file of it

import requests
import csv
from datetime import datetime, timedelta

Configuration

JIRA_URL = “https://domain.atlassian.net
EMAIL = “emailid.com” # Replace with your Jira admin email
API_TOKEN = “1234aasdd” # Replace with your API token
AUTH = (EMAIL, API_TOKEN)
HEADERS = {
“Content-Type”: “application/json”,
“Accept”: “application/json”,
“X-ExperimentalApi”: “opt-in”
}
CSV_FILE = “jira_apps_usage_report.csv”
DATE_RANGE = (datetime.now().astimezone() - timedelta(days=90)).strftime(“%Y-%m-%d”) # Last 90 days

Manual app list (populate from Settings > Apps > Manage apps)

MANUAL_APPS = [
# Example: (“App Name”, “app.key”, True/False for enabled)
# (“ScriptRunner”, “com.onresolve.scriptrunner.jira”, True),
# (“Automation for Jira”, “com.codebarrel.addons.automation”, True)
]

def test_admin_permissions():
“”“Test if the account has admin permissions.”“”
url = f"{JIRA_URL}/rest/api/3/serverInfo"
try:
response = requests.get(url, headers=HEADERS, auth=AUTH)
response.raise_for_status()
print(“Admin permissions confirmed”)
return True
except requests.exceptions.RequestException as e:
print(f"Admin permissions test failed: {e}")
return False

def get_installed_apps():
“”“Fetch list of installed apps or use manual list.”“”
if MANUAL_APPS:
print(“Using manual app list”)
return MANUAL_APPS

# Try /rest/atlassian-connect/1/addons
url = f"{JIRA_URL}/rest/atlassian-connect/1/addons"
try:
    response = requests.get(url, headers=HEADERS, auth=AUTH)
    response.raise_for_status()
    addons = response.json()
    return [(addon['name'], addon['key'], addon['enabled']) for addon in addons]
except requests.exceptions.RequestException as e:
    print(f"Error fetching apps from /rest/atlassian-connect/1/addons: {e}")

# Fallback to /rest/plugins/1.0/
fallback_headers = {"Accept": "*/*", "Content-Type": "application/json"}
url = f"{JIRA_URL}/rest/plugins/1.0/"
try:
    response = requests.get(url, headers=fallback_headers, auth=AUTH)
    response.raise_for_status()
    plugins = response.json().get('plugins', [])
    return [(plugin['name'], plugin['key'], plugin['enabled']) for plugin in plugins]
except requests.exceptions.RequestException as e:
    print(f"Error fetching apps from /rest/plugins/1.0/: {e}")
    return []

def get_audit_logs(start=0, limit=1000):
“”“Fetch audit log records with pagination and date filter.”“”
url = f"{JIRA_URL}/rest/api/3/auditing/record?startAt={start}&maxResults={limit}&from={DATE_RANGE}"
try:
response = requests.get(url, headers=HEADERS, auth=AUTH)
response.raise_for_status()
return response.json().get(‘records’, )
except requests.exceptions.RequestException as e:
print(f"Error fetching audit logs: {e}")
return

def analyze_app_usage(apps):
“”“Analyze audit logs for app usage and user activity.”“”
app_usage = {app_key: {‘users’: set(), ‘last_used’: None} for _, app_key, _ in apps} if apps else {}
start = 0
limit = 1000
while True:
audit_logs = get_audit_logs(start=start, limit=limit)
if not audit_logs:
break
for record in audit_logs:
summary = record.get(‘summary’, ‘’).lower()
description = record.get(‘description’, ‘’).lower() if record.get(‘description’) else ‘’
category = record.get(‘category’, ‘’).lower() if record.get(‘category’) else ‘’
changed_values = str(record.get(‘changedValues’, ‘’)).lower() if record.get(‘changedValues’) else ‘’
# Look for app-related events
app_key = None
if apps:
app_key = next((key for _, key, _ in apps if key.lower() in summary or key.lower() in description or key.lower() in category or key.lower() in changed_values), None)
if not app_key:
# Extract potential app keys
for text in [summary, description, category, changed_values]:
for word in text.split():
if word.startswith((‘com.’, ‘org.’)):
app_key = word
app_usage.setdefault(app_key, {‘users’: set(), ‘last_used’: None})
break
if app_key:
break
if app_key:
user = record.get(‘author’, {}).get(‘displayName’, ‘Unknown’)
timestamp = record.get(‘created’, None)
app_usage[app_key][‘users’].add(user)
if timestamp:
try:
current_time = datetime.strptime(timestamp, “%Y-%m-%dT%H:%M:%S.%f%z”)
if not app_usage[app_key][‘last_used’] or current_time > app_usage[app_key][‘last_used’]:
app_usage[app_key][‘last_used’] = current_time
except ValueError:
continue
start += limit
if len(audit_logs) < limit:
break
return app_usage

def export_to_csv(apps, app_usage):
“”“Export app usage data to a CSV file.”“”
with open(CSV_FILE, mode=‘w’, newline=‘’, encoding=‘utf-8’) as file:
writer = csv.writer(file)
writer.writerow([‘App Name’, ‘App Key’, ‘Status’, ‘Users’, ‘Last Used’])

    for app in apps or [(f"Unknown ({key})", key, True) for key in app_usage.keys()]:
        name, key, enabled = app if apps else (f"Unknown ({app[1]})", app[1], True)
        status = "Enabled" if enabled else "Disabled"
        users = app_usage.get(key, {}).get('users', set())
        last_used = app_usage.get(key, {}).get('last_used', 'No recent activity found')
        users_str = ', '.join(users) if users else 'No user activity found'
        last_used_str = last_used.strftime('%Y-%m-%d %H:%M:%S %Z') if isinstance(last_used, datetime) else last_used
        writer.writerow([name, key, status, users_str, last_used_str])

print(f"Report exported to {CSV_FILE}")

def main():
# Test authentication and permissions
if not test_admin_permissions():
print(“Please ensure the account has Jira System Administrator or Organization Admin permissions.”)
return

# Fetch installed apps
apps = get_installed_apps()
if not apps:
    print("No apps found via API. Using manual list or extracting from audit logs.")

# Analyze usage from audit logs
app_usage = analyze_app_usage(apps)

# Export to CSV
export_to_csv(apps, app_usage)

if name == “main”:
main()