Architecture

This document describes the internal architecture of jarkdown, explaining how components work together to export Jira issues.

Overview

jarkdown follows a modular architecture with clear separation of concerns:

User Input → CLI → API Client → Data Processing → File Output
                         ↓
                  Attachment Handler
                         ↓
                  Markdown Converter

Core Components

CLI Layer (jarkdown.py)

The entry point and orchestrator of the application.

Responsibilities:

  • Parse command-line arguments

  • Load environment configuration

  • Coordinate workflow between components

  • Handle all user-facing errors

  • Control exit codes

Key Functions:

  • main() - Entry point, argument parsing

  • export_issue() - Orchestrates the export workflow

Design Decisions:

  • All sys.exit() calls happen here, not in component classes

  • Components raise exceptions; CLI handles them

  • Verbose output controlled at this layer

API Client (jira_api_client.py)

Manages all communication with the Jira REST API.

Class: JiraApiClient

Responsibilities:

  • Authenticate with Jira using Basic Auth

  • Fetch issue data via REST API

  • Handle API errors and rate limiting

  • Manage HTTP session for connection pooling

Key Methods:

  • __init__() - Initialize with credentials

  • get_issue() - Fetch complete issue data

  • _make_request() - Internal method for API calls

Design Decisions:

  • Returns raw JSON data without transformation

  • Uses requests.Session for connection reuse

  • Raises specific exceptions for different failures

  • No retry logic (kept simple)

Attachment Handler (attachment_handler.py)

Downloads and manages file attachments.

Class: AttachmentHandler

Responsibilities:

  • Download attachments from Jira

  • Handle filename conflicts

  • Use streaming for memory efficiency

  • Track download progress (when verbose)

Key Methods:

  • __init__() - Initialize with auth session

  • download_attachments() - Main download orchestrator

  • _download_file() - Stream download implementation

  • _get_unique_filename() - Handle naming conflicts

Design Decisions:

  • Streaming downloads for large files

  • Automatic retry on failure (up to 3 times)

  • Preserves original filenames when possible

  • Returns metadata about downloaded files

Markdown Converter (markdown_converter.py)

Converts Jira HTML content to clean Markdown.

Class: MarkdownConverter

Responsibilities:

  • Convert HTML to Markdown using markdownify

  • Update attachment URLs to local references

  • Format issue metadata

  • Structure the final document

Key Methods:

  • compose_markdown() - Main conversion method

  • replace_attachment_links() - Replace Jira URLs with local paths

  • _compose_comments_section() - Convert and format comments with ADF support

  • _generate_metadata_dict() - Generate YAML frontmatter metadata

Design Decisions:

  • Uses markdownify for robust HTML conversion

  • Handles multiple Jira URL patterns

  • Preserves formatting and structure

  • Creates readable, well-organized output

Exception Hierarchy (exceptions.py)

Custom exceptions for clear error handling.

JarkdownError (base)
├── ConfigurationError     # Missing/invalid config
├── JiraApiError           # API communication issues
│   ├── AuthenticationError    # 401 responses
│   └── IssueNotFoundError     # 404 responses
└── AttachmentDownloadError    # Download failures

Design Decisions:

  • Specific exceptions for different failures

  • Inherit from common base class

  • Include relevant context in messages

  • Allow graceful error handling

Data Flow

1. Initialization

# User runs: jarkdown PROJ-123 --output /path

1. CLI validates arguments
2. Load .env file with python-dotenv
3. Verify required environment variables
4. Create output directory if needed

2. API Communication

1. Create JiraApiClient with credentials
2. Build API URL: https://domain/rest/api/2/issue/PROJ-123
3. Make authenticated GET request
4. Receive JSON response with issue data
5. Extract attachments list from JSON

3. Attachment Processing

1. Create AttachmentHandler with auth session
2. For each attachment in issue:
   a. Stream download from Jira
   b. Generate unique filename if conflict
   c. Save to output directory
   d. Track downloaded files
3. Return mapping of URLs to local paths

4. Markdown Generation

1. Create MarkdownConverter
2. Extract issue fields (title, description, comments)
3. Convert HTML content to Markdown
4. Replace attachment URLs with local paths
5. Format metadata section
6. Combine into final document

5. File Output

1. Create issue directory (e.g., PROJ-123/)
2. Write markdown file (PROJ-123.md)
3. Attachments already saved by handler
4. Report success to user

Configuration Management

Environment variables are loaded using python-dotenv:

# Automatic loading from .env file
from dotenv import load_dotenv
load_dotenv()

# Access via os.environ
domain = os.environ.get('JIRA_DOMAIN')

Security: Credentials never logged or included in error messages.

Error Handling Strategy

Each layer handles errors appropriately:

  1. Components - Raise specific exceptions

  2. CLI - Catches and presents user-friendly messages

  3. Exit Codes - Consistent codes for different failures

# Example error flow
try:
    api_client.get_issue(issue_key)
except AuthenticationError:
    print("Error: Invalid credentials")
    sys.exit(1)
except IssueNotFoundError:
    print(f"Error: Issue {issue_key} not found")
    sys.exit(2)

Performance Considerations

Memory Efficiency

  • Streaming Downloads: Attachments downloaded in chunks

  • Lazy Processing: Data processed as needed

  • Session Reuse: Single HTTP session for all requests

Network Optimization

  • Connection Pooling: Via requests.Session

  • Minimal API Calls: Single call per issue

  • Parallel Downloads: Could be added for multiple attachments

Extension Points

The architecture supports future enhancements:

Potential Extensions

  1. Bulk Export: Process multiple issues

  2. Custom Templates: Different output formats

  3. Plugin System: Custom processors

  4. Caching: Store frequently accessed data

  5. Resume Support: Continue interrupted downloads

Adding New Features

To add a new feature:

  1. Create new module if needed

  2. Raise specific exceptions

  3. Integrate with CLI orchestrator

  4. Update error handling

  5. Add tests

Testing Architecture

Unit Tests

Each component tested in isolation:

# Mock external dependencies
@patch('requests.Session')
def test_api_client(mock_session):
    # Test API client without network

Integration Tests

CLI tested with mocked components:

# Test full workflow with mocks
@patch('jarkdown.JiraApiClient')
def test_export_workflow(mock_client):
    # Test complete export process

Design Principles

  1. Single Responsibility: Each class has one job

  2. Dependency Injection: Components receive dependencies

  3. Fail Fast: Validate early, fail with clear messages

  4. No Magic: Explicit over implicit behavior

  5. Testability: Easy to mock and test

Security Considerations

  • No Credential Storage: Only in environment

  • No Credential Logging: Filtered from all output

  • HTTPS Only: All API communication encrypted

  • Token Authentication: No password storage

Future Improvements

Potential architectural enhancements:

  1. Async Downloads: Use asyncio for parallel downloads

  2. Progress Bars: Rich terminal UI for long operations

  3. Incremental Updates: Only download changed content

  4. Database Cache: SQLite for metadata caching

  5. Plugin Architecture: Allow custom processors

Conclusion

The architecture prioritizes:

  • Simplicity: Easy to understand and maintain

  • Modularity: Components can be updated independently

  • Reliability: Clear error handling and recovery

  • Extensibility: Easy to add new features

This design makes jarkdown both powerful and maintainable.