Stream Jira Issues to Redpanda for Real-Time Metrics

This lab demonstrates a production-ready pipeline that streams Jira issues to Redpanda topics in real-time. The pipeline transforms raw Jira API responses into a normalized, consumer-friendly schema and routes issues to different topics, enabling use cases SLA monitoring and team performance analytics.

Architecture

Redpanda Connect periodically queries the Jira REST API for recently updated issues in your project. Each issue is transformed to extract key fields, flatten nested objects, and compute flags like is_high_priority and is_completed. Based on these flags, issues are routed to different Redpanda topics:

  • jira.issues.all: All issues

  • jira.issues.high-priority: Issues with Highest/High priority

  • jira.issues.completed: Issues marked as Done/Closed/Resolved

Use cases

SLA monitoring and alerting

  • Automatically detect stale high-priority issues.

  • Send alerts when issues haven’t been updated in a certain number of days.

  • Track response times and prevent SLA breaches.

  • Route critical issues to on-call teams.

Team performance analytics

  • Calculate team velocity and throughput.

  • Identify bottlenecks in the development process.

  • Track individual contributor metrics.

  • Generate sprint reports automatically.

Compliance and audit trail

  • Maintain a complete history of issue changes.

  • Immutable event log in Redpanda.

  • Audit who changed what and when.

Prerequisites

You must have the following installed on your host machine:

This lab also requires:

  • Jira instance with API access

  • Jira API token from Atlassian

This lab is intended for Linux and macOS users. If you are using Windows, you must use the Windows Subsystem for Linux (WSL) to run the commands in this lab.

Run the lab

  1. Clone the repository:

    git clone https://github.com/redpanda-data/redpanda-labs.git
  2. Change into the docker-compose/jira-metrics-pipeline/ directory:

    cd redpanda-labs/docker-compose/jira-metrics-pipeline
  3. Copy the example environment file:

    cp .env.example .env
  4. Edit the .env file and configure:

    # Versions (optional - defaults are provided)
    REDPANDA_VERSION=v25.3.1
    REDPANDA_CONSOLE_VERSION=v3.3.1
    REDPANDA_CONNECT_VERSION=4.70.0
    
    # Jira credentials (required)
    JIRA_BASE_URL=https://<domain>.atlassian.net
    JIRA_USERNAME=<email-address>
    JIRA_API_TOKEN=<api-token>
    JIRA_PROJECT=<jira-project-key>
  5. Generate a Redpanda Enterprise trial license:

    rpk generate license \
      --name "<first-name>" \
      --last-name "<last-name>" \
      --email "<email-address>" \
      --company "<company-name>"

    This creates a 30-day trial license in ./redpanda.license.

  6. Export the license as a shell environment variable:

    export REDPANDA_LICENSE=$(cat ./redpanda.license)

    The REDPANDA_LICENSE must be exported in your shell. It cannot be loaded from the .env file. The --env-file flag loads application variables (like JIRA_*), but the license must be exported separately.

  7. Start the Docker containers:

    docker compose up -d
  8. Create the Kafka topics:

    docker compose exec redpanda rpk topic create jira.issues.all -p 3
    docker compose exec redpanda rpk topic create jira.issues.high-priority -p 3
    docker compose exec redpanda rpk topic create jira.issues.completed -p 3
  9. Open Redpanda Console at localhost:8080 to view your topics and messages.

  10. Monitor the Jira issues streaming into Redpanda:

    # Watch all issues
    docker compose exec redpanda rpk topic consume jira.issues.all --format json
    
    # Watch high-priority issues
    docker compose exec redpanda rpk topic consume jira.issues.high-priority --format json
    
    # Watch completed issues
    docker compose exec redpanda rpk topic consume jira.issues.completed --format json

How it works

The pipeline performs the following steps:

  1. Query Jira: Every 30 seconds, Redpanda Connect queries Jira for recently updated issues in your project

  2. Transform data: Each Jira issue is transformed into a normalized schema:

    • Flattened fields: status, priority, issue type (extracted from nested Jira objects)

    • Null-safe people fields: assignee and reporter (defaults to "Unassigned"/"Unknown")

    • Raw timestamps: created, updated, resolved (preserved in original Jira format)

    • Extracted component names from component objects

    • Computed boolean flags: is_completed, is_high_priority

    • Generated issue URL: {JIRA_BASE_URL}/browse/{key}

    • Pipeline processing timestamp

  3. Route messages: Issues are intelligently routed to topics based on computed flags:

    • High priority (Highest/High) → jira.issues.high-priority

    • Completed (Done/Closed/Resolved) → jira.issues.completed

    • All others → jira.issues.all

  4. Consume and analyze: Multiple downstream applications can consume from these topics independently:

    • Metrics dashboards (Grafana, Kibana)

    • Alert systems (Slack, PagerDuty)

    • Data warehouses (Snowflake, BigQuery)

    • Workflow automation

Pipeline configuration

The complete Redpanda Connect pipeline configuration:

# JIRA Metrics Pipeline
#
# This pipeline queries JIRA issues and streams them to Redpanda topics
# for real-time metrics, alerting, and analytics.
#
# Environment variables required:
# - REDPANDA_LICENSE: Enterprise license (must be exported in shell)
# - JIRA_BASE_URL: https://your-domain.atlassian.net
# - JIRA_USERNAME: your-email@example.com
# - JIRA_API_TOKEN: your-api-token
# - JIRA_PROJECT: YOUR_PROJECT_KEY
# - REDPANDA_BROKERS: redpanda:9092 (set in docker-compose.yml)

input:
  generate:
    mapping: |
      # Query JIRA for recent issues (last 7 days for testing)
      root.jql = "project = ${JIRA_PROJECT} AND updated >= -7d ORDER BY updated DESC"
      root.maxResults = 10
      root.fields = [
        "key",
        "summary",
        "status",
        "priority",
        "assignee",
        "reporter",
        "created",
        "updated",
        "resolutiondate",
        "issuetype",
        "labels",
        "components"
      ]
    # Query every 30 seconds
    interval: 30s

pipeline:
  processors:
    # Execute JIRA query
    - jira:
        base_url: "${JIRA_BASE_URL}"
        username: "${JIRA_USERNAME}"
        api_token: "${JIRA_API_TOKEN}"
        max_results_per_page: 100
        request_timeout: 30s
        max_retries: 10

    # Transform and enrich each issue with metrics
    - mapping: |
        # Basic issue info
        root.issue_key = this.key
        root.summary = this.fields.summary
        root.status = this.fields.status.name
        root.priority = this.fields.priority.name
        root.issue_type = this.fields.issuetype.name
        root.url = "${JIRA_BASE_URL}/browse/" + this.key

        # People
        root.assignee = if this.fields.assignee != null {
          this.fields.assignee.displayName
        } else { "Unassigned" }

        root.reporter = if this.fields.reporter != null {
          this.fields.reporter.displayName
        } else { "Unknown" }

        # Dates (keep original format for downstream processing)
        root.created = this.fields.created
        root.updated = this.fields.updated
        root.resolved = this.fields.resolutiondate

        # Labels and components
        root.labels = this.fields.labels
        root.components = this.fields.components.map_each(c -> c.name)

        # Flags for routing
        root.is_completed = this.fields.status.name.lowercase().contains("done") ||
                           this.fields.status.name.lowercase().contains("closed") ||
                           this.fields.status.name.lowercase().contains("resolved")

        root.is_high_priority = ["Highest", "High"].contains(this.fields.priority.name)

        # Note: Timestamp-based metrics (age, staleness, lead time) can be calculated
        # by downstream consumers using the raw `created`, `updated`, and `resolved` fields.

        # Add pipeline processing timestamp
        root.pipeline_timestamp = now()

    # Route to primary topic based on issue properties
    - mapping: |
        # Route based on priority and completion status
        meta kafka_topic = if this.is_high_priority {
          "jira.issues.high-priority"
        } else if this.is_completed {
          "jira.issues.completed"
        } else {
          "jira.issues.all"
        }

output:
  kafka:
    addresses: ["${REDPANDA_BROKERS}"]
    topic: '${! meta("kafka_topic") }'
    max_in_flight: 1
    batching:
      count: 100
      period: 1s
    compression: snappy

Customize the pipeline

Adjust polling frequency

Edit connect-configs/jira-pipeline.yaml:

input:
  generate:
    interval: 60s  # Change from 30s to 1m, 5m, etc.

Modify JQL query

Target different issues by changing the JQL query:

root.jql = "project = YOUR_PROJECT AND status != Closed AND updated >= -1h"

Add custom fields

Include Jira custom fields in your pipeline:

root.fields = [
  "key",
  "summary",
  "customfield_10001",  # Add your custom field ID
  # ... other fields
]

Add more routing rules

Route issues to additional topics based on labels, components, or other criteria:

- mapping: |
    meta kafka_topic = if this.labels.contains("security") {
      "jira.issues.security"
    } else { deleted() }

View logs and metrics

Redpanda Connect logs

docker compose logs -f connect

Redpanda logs

docker compose logs -f redpanda

Redpanda Connect metrics

curl http://localhost:4195/metrics

Troubleshooting

License errors

If you see license-related errors in the Connect logs:

  1. Verify the license is exported:

    echo $REDPANDA_LICENSE
  2. If the environment variable is empty, regenerate and export the license:

    rpk generate license --name "Your Name" \
      --last-name "Last" \
      --email "you@example.com" \
      --company "Company"
    
    export REDPANDA_LICENSE=$(cat ./redpanda.license)
  3. Restart the containers:

    docker compose restart connect

Jira authentication errors

If you receive 401 Unauthorized errors:

  1. Verify your API token is correct.

  2. Ensure you’re using your email address as the username.

  3. Check that your Jira instance URL is correct (include https://).

  4. Test your credentials with curl:

    curl -u "${JIRA_USERNAME}:${JIRA_API_TOKEN}" \
      "${JIRA_BASE_URL}/rest/api/3/myself"

No issues appearing

If no issues are being published:

  1. Check the JQL query returns results in Jira directly.

  2. Verify the JIRA_PROJECT environment variable matches your project key.

  3. Check Connect logs for errors:

    docker compose logs connect
  4. Adjust the query timeframe:

    root.jql = "project = YOUR_PROJECT AND updated >= -24h"  # Last 24 hours

Rate limiting

If you hit Jira rate limits (HTTP 429):

  1. Increase the polling interval in connect-configs/jira-pipeline.yaml:

    interval: 60s  # Reduce frequency to 60 seconds
  2. Increase retry settings:

    - jira:
        max_retries: 20
        request_timeout: 60s
  3. Use more specific JQL queries to reduce result sizes.

Clean up

To shut down and delete the containers along with all cluster data:

docker compose down -v

Next steps

After you have Jira issues streaming into Redpanda, you can extend this pipeline with Redpanda Connect outputs:

Send alerts and notifications

Stream to data warehouses

Calculate metrics

Use the raw timestamp fields (created, updated, resolved) to calculate:

  • Lead time: Average time from creation to completion

  • Cycle time: Average time from "In Progress" to "Done"

  • Throughput: Issues completed per time period

  • Aging: Distribution of issue ages

  • SLA compliance: Percentage of issues resolved within target timeframes