Files
whisper-api-server/examples/parent_story_integration_plan.md
T

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:

  1. Higher cache hit rate: Since stories are fetched first, most parent stories referenced by tasks will already be in cache
  2. Reduced API calls: Minimizes additional API requests during task processing
  3. 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

  1. Create StoryCache class in src/api/story_cache.py
  2. Create parent story fetcher function in src/api/client_jira.py
  3. Modify fetch_all_tasks_with_due_dates to include parent story data
  4. Update format_task_data to include parent story and storypoints
  5. Update _transform_task_section to include new fields
  6. Update format_task_sheet to include new columns
  7. 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

  1. Start the service with docker compose up
  2. 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"
         }'
  1. If the report generates successfully without errors, the implementation is working correctly
  2. The actual validation of the table data and storypoints will be performed manually