128 lines
4.2 KiB
Python
128 lines
4.2 KiB
Python
import requests
|
|
import pandas as pd
|
|
import sys
|
|
import json
|
|
|
|
# --- CONFIGURATION ---
|
|
BASE_URL = "https://openproject.nabd-co.com/"
|
|
API_KEY = "dfc009a268f8490c2502458bad364c2f0ae27762c6b4a38c4dac6d394ca3058f"
|
|
PROJECT_IDENTIFIER = "asf"
|
|
AUTH = ('apikey', API_KEY)
|
|
|
|
def get_type_id_map():
|
|
url = f"{BASE_URL}/api/v3/types"
|
|
try:
|
|
response = requests.get(url, auth=AUTH)
|
|
response.raise_for_status()
|
|
return {t['name'].lower(): str(t['id']) for t in response.json()['_embedded']['elements']}
|
|
except Exception as e:
|
|
print(f"❌ Error fetching type definitions: {e}")
|
|
return {}
|
|
|
|
def fetch_work_packages(types_filter=None):
|
|
url = f"{BASE_URL}/api/v3/projects/{PROJECT_IDENTIFIER}/work_packages"
|
|
params = {"pageSize": 1000} # Fetch more items in one go
|
|
|
|
if types_filter:
|
|
type_map = get_type_id_map()
|
|
type_ids = [type_map[t.lower()] for t in types_filter if t.lower() in type_map]
|
|
|
|
if not type_ids:
|
|
print(f"⚠️ Warning: Types {types_filter} not found. Fetching all types instead.")
|
|
else:
|
|
filters = [{"type": {"operator": "=", "values": type_ids}}]
|
|
params['filters'] = json.dumps(filters)
|
|
|
|
try:
|
|
response = requests.get(url, auth=AUTH, params=params)
|
|
response.raise_for_status()
|
|
return response.json()['_embedded']['elements']
|
|
except Exception as e:
|
|
print(f"❌ API Error: {e}")
|
|
return []
|
|
|
|
def get_relations(wp_id):
|
|
url = f"{BASE_URL}/api/v3/work_packages/{wp_id}/relations"
|
|
try:
|
|
resp = requests.get(url, auth=AUTH)
|
|
relations = resp.json()['_embedded']['elements']
|
|
rel_list = []
|
|
for r in relations:
|
|
rel_type = r['type']
|
|
# Safely get the ID of the related work package
|
|
to_link = r['_links'].get('to', {}).get('href', '')
|
|
from_link = r['_links'].get('from', {}).get('href', '')
|
|
|
|
other_id = to_link.split('/')[-1] if f"/{wp_id}" not in to_link else from_link.split('/')[-1]
|
|
if other_id:
|
|
rel_list.append(f"{rel_type}(#{other_id})")
|
|
return "; ".join(rel_list)
|
|
except:
|
|
return ""
|
|
|
|
def export_data(types_list=None, output_format='csv'):
|
|
print(f"🔍 Fetching data for: {types_list if types_list else 'All Types'}...")
|
|
|
|
wps = fetch_work_packages(types_list)
|
|
if not wps:
|
|
print("No work packages found.")
|
|
return
|
|
|
|
data = []
|
|
for wp in wps:
|
|
wp_id = wp['id']
|
|
|
|
# --- SAFE PARENT ID CHECK ---
|
|
parent_id = ""
|
|
parent_link = wp['_links'].get('parent')
|
|
# Check if parent exists AND has an href attribute
|
|
if parent_link and parent_link.get('href'):
|
|
parent_id = parent_link['href'].split('/')[-1]
|
|
|
|
# Relations call
|
|
relations = get_relations(wp_id)
|
|
|
|
data.append({
|
|
"ID": wp_id,
|
|
"Type": wp['_links']['type']['title'],
|
|
"Status": wp['_links']['status']['title'],
|
|
"Title": wp['subject'],
|
|
"Description": wp['description']['raw'] if wp.get('description') else "",
|
|
"Parent_ID": parent_id,
|
|
"Relations": relations
|
|
})
|
|
|
|
df = pd.DataFrame(data)
|
|
filename = f"traceability_export.{output_format}"
|
|
|
|
if output_format == 'xlsx':
|
|
df.to_excel(filename, index=False)
|
|
else:
|
|
df.to_csv(filename, index=False, encoding='utf-8-sig')
|
|
|
|
print(f"✅ Successfully exported {len(df)} items to {filename}")
|
|
|
|
|
|
def get_type_mapping():
|
|
url = f"{BASE_URL}/api/v3/types"
|
|
try:
|
|
response = requests.get(url, auth=AUTH)
|
|
response.raise_for_status()
|
|
return {t['name'].lower(): t['id'] for t in response.json()['_embedded']['elements']}
|
|
except Exception as e:
|
|
print(f"❌ Error fetching types: {e}")
|
|
sys.exit(1)
|
|
|
|
if __name__ == "__main__":
|
|
|
|
type_map = get_type_mapping()
|
|
|
|
print(type(type_map))
|
|
|
|
print(list(type_map.keys()))
|
|
|
|
# Usage: python get_traceability.py requirements task
|
|
|
|
requested_types = [] #list(type_map.keys()) #sys.argv[1:] if len(sys.argv) > 1 else None
|
|
|
|
export_data(types_list=requested_types, output_format='csv') |