14 KiB
Integration Plan: Parent Story and Story Points for Global Tasks
Overview
This document outlines the plan to enhance the fetch_all_tasks_with_due_dates() method in client_jira.py to include parent story information and story points for each task. This enhancement applies only to global tasks that appear in the Tasks report table, not to individual assignee-specific tasks. The enhancement will include a caching mechanism to optimize performance when multiple tasks belong to the same parent story.
Scope Clarification
Included in this enhancement:
- Global tasks fetched via
fetch_all_tasks_with_due_dates() - Tasks that appear in the Tasks report table
- Parent story key and storypoints extraction
Excluded from this enhancement:
- Individual assignee-specific tasks
- Tasks fetched via other methods
- Other report types
Current State Analysis
Current Data Flow
graph TB
subgraph "Current Implementation"
A[fetch_all_tasks_with_due_dates] --> B[_make_search_request]
B --> C[JIRA API]
C --> D[Task Data]
D --> E[format_task_data]
E --> F[Report Generation]
F --> G[Spreadsheet Export]
end
subgraph "Current Task Data Structure"
T1[issue]
T2[start]
T3[resolved]
T4[due_date]
T5[labels]
T6[responsible]
end
Current Task Data Structure
The current task data includes:
- issue: Task key
- start: Creation date
- resolved: Resolution date
- due_date: Due date
- labels: Task labels
- responsible: Responsible person
Target Architecture
Enhanced Data Flow
graph TB
subgraph "Enhanced Implementation"
A[fetch_all_tasks_with_due_dates] --> B[_make_search_request]
B --> C[JIRA API]
C --> D[Task Data]
D --> E[Process Parent Stories]
E --> F{Parent Story Cache}
F -->|Cache Hit| G[Use Cached Data]
F -->|Cache Miss| H[Fetch Parent Story]
H --> I[Update Cache]
I --> J[Merge Task + Story Data]
G --> J
J --> K[format_task_data]
K --> L[Report Generation]
L --> M[Spreadsheet Export]
end
subgraph "Enhanced Task Data Structure"
T1[issue]
T2[start]
T3[resolved]
T4[due_date]
T5[labels]
T6[responsible]
T7[parent_story]
T8[storypoints]
end
Parent Story Link Structure
Based on the example JSON (WPCMU-9082.json), the parent story information is located in:
- Path:
fields.issuelinks - Filter:
type.inward = "Parent task" - Parent Key:
inwardIssue.key
Implementation Plan
Phase 1: Core Infrastructure
1.1 Create Parent Story Cache Manager
File: src/api/story_cache.py
class StoryCache:
"""
Manages caching of parent story data to optimize API calls
when multiple tasks belong to the same parent story.
"""
def __init__(self):
self.cache = {} # {story_key: storypoints}
def get(self, story_key: str) -> Optional[int]:
"""Get storypoints from cache"""
def set(self, story_key: str, storypoints: int) -> None:
"""Set storypoints in cache"""
def clear(self) -> None:
"""Clear the cache"""
1.2 Create Parent Story Fetcher
File: src/api/client_jira.py
def _fetch_parent_story_data(jira_url: str, bearer_token: str, story_key: str) -> Optional[Dict]:
"""
Fetches parent story data including storypoints
Args:
jira_url: URL Jira API
bearer_token: Bearer токен для аутентификации
story_key: Key of the parent story
Returns:
Dictionary with story data or None if error
"""
# Implementation to fetch single story by key
# Extract storypoints from customfield_10003
# Return cached data structure
Phase 2: Integration with Task Fetching
2.1 Modify fetch_all_tasks_with_due_dates
File: src/api/client_jira.py
def fetch_all_tasks_with_due_dates(
jira_url: str,
bearer_token: str,
start_date: str,
end_date: str,
max_results: int = 50,
story_cache: Optional[StoryCache] = None
) -> Optional[List[Dict]]:
"""
Enhanced version that includes parent story and storypoints data
"""
# Initialize cache if not provided
# Fetch tasks as before
# For each task:
# - Extract parent story key from issuelinks
# - Check cache for storypoints
# - Fetch from API if not in cache
# - Update cache
# - Add parent_story and storypoints to task data
2.2 Update Task Data Structure
File: src/formatting/reports_data_formatter.py
def format_task_data(task_issue: Dict, target_tz: Optional[ZoneInfo]) -> Dict:
"""
Enhanced version that includes parent story and storypoints
"""
# Extract parent story information
parent_story = None
storypoints = None
# Extract from issuelinks where type.inward = "Parent task"
issuelinks = task_issue.get('fields', {}).get('issuelinks', [])
for link in issuelinks:
if link.get('type', {}).get('inward') == 'Parent task':
parent_story = link.get('inwardIssue', {}).get('key')
break
# Get storypoints from the enhanced task data
storypoints = task_issue.get('parent_storypoints')
return {
"issue": issue_key,
"start": formatted_created_date,
"resolved": formatted_resolution_date,
"due_date": formatted_due_date,
"labels": labels_str,
"responsible": formatted_responsible,
"parent_story": parent_story,
"storypoints": storypoints
}
Phase 3: Report Integration
3.1 Update GlobalDataManager with Cache Pre-population
File: src/reports/report_generator.py
class GlobalDataManager:
def __init__(self, jira_url: str, bearer_token: str):
self.jira_url = jira_url
self.bearer_token = bearer_token
self.cached_bugs = None
self.cached_stories = None
self.cached_tasks = None
self.story_cache = StoryCache() # New cache manager
def _populate_story_cache(self, stories: List[Dict]) -> None:
"""
Pre-populates the story cache with all stories and their storypoints
to optimize task processing later.
Args:
stories: List of story dictionaries from JIRA API
"""
for story in stories:
story_key = story.get('key')
storypoints = story.get('fields', {}).get('customfield_10003')
if story_key and storypoints is not None:
self.story_cache.set(story_key, storypoints)
def fetch_global_data(self, start_date: str, end_date: str, max_results: int = 50) -> Dict:
results = {}
# Get stories first and populate cache
self.cached_stories = fetch_all_stories_with_due_dates(
self.jira_url, self.bearer_token, start_date, end_date, max_results
)
results['stories'] = self.cached_stories or []
# Pre-populate cache with story data for optimization
if self.cached_stories:
self._populate_story_cache(self.cached_stories)
# Get bugs
self.cached_bugs = fetch_all_bugs(
self.jira_url, self.bearer_token, start_date, end_date, max_results
)
results['bugs'] = self.cached_bugs or []
# Get tasks with pre-populated cache
self.cached_tasks = fetch_all_tasks_with_due_dates(
self.jira_url, self.bearer_token, start_date, end_date, max_results, self.story_cache
)
results['tasks'] = self.cached_tasks or []
return results
3.2 Update Report Transformation
File: src/reports/transform_report_data.py
def _transform_task_section(report_data):
"""
Enhanced version that includes storypoints
"""
transformed_data = []
for task in report_data:
transformed_data.append([
task['issue'],
task['start'],
task['resolved'],
task['due_date'],
task['labels'],
task['responsible'],
task['parent_story'], # New field
task['storypoints'] # New field
])
return transformed_data
3.3 Update Spreadsheet Formatter
File: src/formatting/spreadsheet_formatter.py
def format_task_sheet(reports_data):
"""
Formats data for 'Tasks' sheet as a single long vertical list.
Returns: List of lists for sheet data
"""
headers = ["Task", "Start", "Resolved", "Due Date", "Labels", "Responsible", "Parent Story", "Story Points"]
return _format_single_list_sheet_global_data(reports_data['global_tasks'], headers)
Implementation Details
Parent Story Extraction Logic
def _extract_parent_story_key(task_data: Dict) -> Optional[str]:
"""
Extracts parent story key from task issuelinks
Args:
task_data: Task data from JIRA API
Returns:
Parent story key or None if not found
"""
issuelinks = task_data.get('fields', {}).get('issuelinks', [])
for link in issuelinks:
link_type = link.get('type', {})
if link_type.get('inward') == 'Parent task':
inward_issue = link.get('inwardIssue')
if inward_issue:
return inward_issue.get('key')
return None
Caching Strategy
Simple in-memory cache without expiration for maximum performance:
def _get_or_fetch_storypoints(
story_key: str,
jira_url: str,
bearer_token: str,
story_cache: StoryCache
) -> Optional[int]:
"""
Gets storypoints from cache or fetches from API
Args:
story_key: Parent story key
jira_url: URL Jira API
bearer_token: Bearer токен для аутентификации
story_cache: Story cache instance
Returns:
Storypoints value or None if not found
"""
# Check cache first
cached_points = story_cache.get(story_key)
if cached_points is not None:
return cached_points
# Fetch from API
story_data = _fetch_parent_story_data(jira_url, bearer_token, story_key)
if story_data:
storypoints = story_data.get('fields', {}).get('customfield_10003')
story_cache.set(story_key, storypoints)
return storypoints
return None
Cache Design:
- Simple dictionary-based storage:
{story_key: storypoints} - No expiration policy (cache lasts for the duration of report generation)
- Automatic clearing when GlobalDataManager is reset
- Minimal memory footprint as only storypoints are stored
Data Flow Diagram
sequenceDiagram
participant Client
participant ReportGen as Report Generator
participant GlobalDM as GlobalDataManager
participant JiraAPI as JIRA API
participant StoryCache as Story Cache
Client->>ReportGen: Generate Report
ReportGen->>GlobalDM: fetch_global_data
Note over GlobalDM: Phase 1: Fetch Stories and Pre-populate Cache
GlobalDM->>JiraAPI: fetch_all_stories_with_due_dates
JiraAPI-->>GlobalDM: Stories List
GlobalDM->>StoryCache: Pre-populate with all story storypoints
Note over GlobalDM: Phase 2: Fetch Tasks with Optimized Cache
GlobalDM->>JiraAPI: fetch_all_tasks_with_due_dates
JiraAPI-->>GlobalDM: Task List
loop For Each Task
GlobalDM->>GlobalDM: Extract Parent Story Key
alt Parent Story Found
GlobalDM->>StoryCache: get(story_key)
alt Cache Hit (Optimized)
StoryCache-->>GlobalDM: Cached Storypoints
else Cache Miss (Rare)
GlobalDM->>JiraAPI: _fetch_parent_story_data
JiraAPI-->>GlobalDM: Story Data
GlobalDM->>StoryCache: set(story_key, storypoints)
end
GlobalDM->>GlobalDM: Merge Task + Story Data
end
end
GlobalDM-->>ReportGen: Enhanced Task Data
ReportGen-->>Client: Report with Storypoints
Error Handling
API Error Handling
- Handle cases where parent story is not accessible
- Handle cases where story doesn't exist
- Implement retry logic for failed API calls
- Log errors appropriately
Cache Error Handling
- Handle cache initialization failures
- Implement cache size limits
- Handle cache corruption scenarios
Performance Considerations
Caching Benefits
- Reduces API calls when multiple tasks share the same parent story
- Improves response time for large task sets
- Reduces load on JIRA API
- Cache pre-population optimization: Stories are fetched before tasks, so most parent story data is already cached when processing tasks
Cache Pre-population Strategy
The implementation includes a key optimization: the cache is pre-populated with all stories and their storypoints before processing tasks. This provides significant benefits:
- Higher cache hit rate: Since stories are fetched first, most parent stories referenced by tasks will already be in cache
- Reduced API calls: Minimizes additional API requests during task processing
- Faster processing: Tasks can be processed more efficiently with cached data
Memory Management
- Simple in-memory cache without expiration
- Cache cleared when GlobalDataManager is reset
- Minimal memory footprint as only storypoints are stored
- Cache is populated once per report generation cycle
Implementation Steps
- Create StoryCache class in
src/api/story_cache.py - Create parent story fetcher function in
src/api/client_jira.py - Modify
fetch_all_tasks_with_due_datesto include parent story data - Update
format_task_datato include parent story and storypoints - Update
_transform_task_sectionto include new fields - Update
format_task_sheetto include new columns - Update GlobalDataManager to use story cache with pre-population
Testing Strategy
The service runs in Docker Compose and can be tested by sending requests to the running service.
Test Procedure
- Start the service with
docker compose up - Send a test request to generate a report:
curl -X POST "http://localhost:8055/create_report_in_spreadsheet" \
-H "Content-Type: application/json" \
-d '{
"table_id": "1HRhWYzVql1cTv0mfGPd1B0qMXA5yD0xchirlfQJJjEo",
"secret": "BRgln-IUjja-1kH6B-XONN9"
}'
- If the report generates successfully without errors, the implementation is working correctly
- The actual validation of the table data and storypoints will be performed manually