bissell-wrike-python/discover_board_info.py
Dave Porter a0cfe4b796 Add LGL Team migration with recursive duplicate detection
- Add wrike_monitor_lgl.py for LGL Team board
  * Updated API token and space configuration
  * All 11 custom fields mapped correctly
  * OMG# field now stores HTML links (matching LGL Team format)
  * Recursive duplicate detection across entire Business Areas folder
  * Handles HTML link extraction for accurate comparison

- Add discover_board_info.py
  * Automated discovery tool for board configuration
  * Finds space IDs, custom field IDs, and item types
  * Generates configuration snippets

- Add config_lgl_team.py
  * Reference configuration for LGL Team space
  * Complete field mapping documentation

- Add test_duplicate_detection.py
  * Testing tool to verify duplicate detection logic
  * Can search for specific OMG# values

- Update requirements.txt

- Remove wrike_import.py (moved to OLD/)

Key Features:
- NO DUPLICATES: Searches entire Business Areas folder before creating
- HTML Link Support: OMG# stored as clickable links matching existing format
- Global Search: Uses descendants=true for efficient recursive search
- Format Matching: Generates OMG# links identical to existing entries

🤖 Generated with Claude Code
2025-12-17 15:19:17 -05:00

229 lines
8.6 KiB
Python

#!/usr/bin/env python3
"""
Wrike Board Discovery Script
Discovers all necessary IDs and configuration for a Wrike board/space
to configure wrike_monitor.py for a new environment
"""
import json
import requests
from typing import Dict, Optional, Any
class WrikeBoardDiscovery:
"""Discover Wrike board configuration"""
def __init__(self, api_token: str):
self.api_base = "https://www.wrike.com/api/v4"
self.api_token = api_token
self.headers = {
"Authorization": f"Bearer {api_token}",
"Content-Type": "application/json"
}
def make_request(self, endpoint: str) -> Optional[Dict[str, Any]]:
"""Make a GET request to Wrike API"""
url = f"{self.api_base}{endpoint}"
try:
response = requests.get(url, headers=self.headers, timeout=30)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"❌ API Error: {e}")
return None
def get_all_spaces(self):
"""Get all available spaces"""
print("\n" + "="*80)
print("DISCOVERING SPACES")
print("="*80)
result = self.make_request("/spaces")
if result and "data" in result:
spaces = result["data"]
print(f"\nFound {len(spaces)} space(s):\n")
for space in spaces:
print(f"{space['title']}")
print(f" ID: {space['id']}")
print(f" Access Type: {space.get('accessType', 'N/A')}")
print()
return spaces
return []
def get_space_by_name(self, space_name: str) -> Optional[str]:
"""Get space ID by name"""
result = self.make_request("/spaces")
if result and "data" in result:
for space in result["data"]:
if space["title"].lower() == space_name.lower():
return space["id"]
return None
def get_custom_item_types(self, space_id: str):
"""Get custom item types (like 'Deliverable')"""
print("\n" + "="*80)
print("DISCOVERING CUSTOM ITEM TYPES")
print("="*80)
result = self.make_request(f"/spaces/{space_id}/item_types")
if result and "data" in result:
item_types = result["data"]
print(f"\nFound {len(item_types)} custom item type(s):\n")
for item_type in item_types:
print(f"{item_type['title']}")
print(f" ID: {item_type['id']}")
print(f" Type: {item_type.get('type', 'N/A')}")
print()
return item_types
return []
def get_custom_fields(self):
"""Get all custom fields"""
print("\n" + "="*80)
print("DISCOVERING CUSTOM FIELDS")
print("="*80)
result = self.make_request("/customfields")
if result and "data" in result:
custom_fields = result["data"]
print(f"\nFound {len(custom_fields)} custom field(s):\n")
# Group by account/space
for field in custom_fields:
print(f"{field['title']}")
print(f" ID: {field['id']}")
print(f" Type: {field['type']}")
if 'accountId' in field:
print(f" Account ID: {field['accountId']}")
if 'spaceId' in field:
print(f" Space ID: {field['spaceId']}")
print()
return custom_fields
return []
def generate_config_snippet(self, space_name: str, space_id: str,
deliverable_type_id: Optional[str],
custom_fields: list):
"""Generate Python config snippet for the discovered board"""
print("\n" + "="*80)
print("CONFIGURATION SNIPPET FOR wrike_monitor.py")
print("="*80)
config = f'''
# === WRIKE SPACE CONFIGURATION - {space_name} ===
WRIKE_SPACE_ID = "{space_id}"
WRIKE_SPACE_NAME = "{space_name}"
'''
if deliverable_type_id:
config += f'DELIVERABLE_ITEM_TYPE_ID = "{deliverable_type_id}" # Custom item type "Deliverable"\n'
else:
config += 'DELIVERABLE_ITEM_TYPE_ID = "XXXXXX" # ⚠️ NOT FOUND - Update manually\n'
config += '\n# Custom field IDs\n'
config += 'CUSTOM_FIELDS = {\n'
# Try to match custom fields by name
field_mapping = {
"budget": None,
"impact": None,
"notes": None,
"rag": None,
"deliverable_category": None,
"actions": None,
"shoot_date": None,
"omg_number": None,
"omg_url": None,
"box_link": None,
"owner": None
}
for field in custom_fields:
field_title = field['title'].lower().replace(" ", "_").replace("#", "number")
for key in field_mapping.keys():
if key in field_title or field_title in key:
field_mapping[key] = field['id']
break
for key, value in field_mapping.items():
if value:
config += f' "{key}": "{value}",\n'
else:
config += f' "{key}": "XXXXXX", # ⚠️ NOT FOUND - Update manually\n'
config += '}\n'
print(config)
# Save to file
output_file = f"config_{space_name.replace(' ', '_').lower()}.py"
with open(output_file, 'w') as f:
f.write(config)
print(f"\n✅ Configuration saved to: {output_file}")
return config
def discover_board(self, space_name: str):
"""Main discovery function for a specific board"""
print(f"\n🔍 Discovering configuration for: {space_name}\n")
# Get all spaces
spaces = self.get_all_spaces()
# Find target space
space_id = self.get_space_by_name(space_name)
if not space_id:
print(f"\n❌ Space '{space_name}' not found!")
print(f"Available spaces: {[s['title'] for s in spaces]}")
return
print(f"\n✅ Found space: {space_name} (ID: {space_id})")
# Get custom item types
item_types = self.get_custom_item_types(space_id)
deliverable_type_id = None
for item_type in item_types:
if "deliverable" in item_type['title'].lower():
deliverable_type_id = item_type['id']
print(f"✅ Found 'Deliverable' custom item type: {deliverable_type_id}")
break
if not deliverable_type_id:
print("⚠️ WARNING: No 'Deliverable' custom item type found!")
# Get custom fields
custom_fields = self.get_custom_fields()
# Generate config
self.generate_config_snippet(space_name, space_id, deliverable_type_id, custom_fields)
print("\n" + "="*80)
print("DISCOVERY COMPLETE")
print("="*80)
print("\n📋 Next Steps:")
print(" 1. Review the generated config file")
print(" 2. Update any fields marked with ⚠️ XXXXXX")
print(" 3. Copy the configuration into wrike_monitor.py")
print(" 4. Test with a sample JSON file\n")
def main():
"""Main entry point"""
print("""
╔════════════════════════════════════════════════════════════════╗
║ WRIKE BOARD DISCOVERY TOOL ║
║ Discovers IDs and configuration for Wrike monitor script ║
╚════════════════════════════════════════════════════════════════╝
""")
# Configuration
API_TOKEN = "eyJ0dCI6InAiLCJhbGciOiJIUzI1NiIsInR2IjoiMiJ9.eyJkIjoie1wiYVwiOjY5NjM3MzksXCJpXCI6OTU5MTM2MSxcImNcIjo0NzAyNjA1LFwidVwiOjIzMjcyNDA0LFwiclwiOlwiVVNcIixcInNcIjpbXCJXXCIsXCJGXCIsXCJJXCIsXCJVXCIsXCJLXCIsXCJDXCIsXCJEXCIsXCJNXCIsXCJBXCIsXCJMXCIsXCJQXCJdLFwielwiOltdLFwidFwiOjB9IiwiaWF0IjoxNzY2MDAwNjMyfQ.FwtNCIiBUbb82MlEnI_Z3vKt-W8IgKRwLwZY6IY6fEI"
TARGET_SPACE = "LGL team" # Change this to discover a different space
discoverer = WrikeBoardDiscovery(API_TOKEN)
discoverer.discover_board(TARGET_SPACE)
if __name__ == "__main__":
main()