- 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
229 lines
8.6 KiB
Python
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()
|