traceability
This commit is contained in:
128
public/data/get_traceability.py
Normal file
128
public/data/get_traceability.py
Normal file
@@ -0,0 +1,128 @@
|
||||
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')
|
||||
3008
public/data/traceability_export.csv
Normal file
3008
public/data/traceability_export.csv
Normal file
File diff suppressed because one or more lines are too long
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
public/images/nabd-logo.png
Normal file
BIN
public/images/nabd-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
1
public/placeholder.svg
Normal file
1
public/placeholder.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
14
public/robots.txt
Normal file
14
public/robots.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
User-agent: Googlebot
|
||||
Allow: /
|
||||
|
||||
User-agent: Bingbot
|
||||
Allow: /
|
||||
|
||||
User-agent: Twitterbot
|
||||
Allow: /
|
||||
|
||||
User-agent: facebookexternalhit
|
||||
Allow: /
|
||||
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Reference in New Issue
Block a user