Skip to content

Implementation of the Pre-Built Integration To Automate Jira Summary Emails - #67

Open
Ranvin36 wants to merge 18 commits into
wso2:mainfrom
Ranvin36:main
Open

Implementation of the Pre-Built Integration To Automate Jira Summary Emails#67
Ranvin36 wants to merge 18 commits into
wso2:mainfrom
Ranvin36:main

Conversation

@Ranvin36

@Ranvin36 Ranvin36 commented Mar 13, 2026

Copy link
Copy Markdown

Purpose

$title

Task : #67

Summary by CodeRabbit

  • New Features
    • Automated Jira sprint monitoring that sends configurable HTML summary emails via Gmail, including per-sprint metrics, issue lists, assignee breakdowns, and mid-sprint additions detection.
    • End-to-end polling, summary generation, email composition, and delivery with persistent processed-sprints state.
  • Documentation
    • Setup, deployment, authentication guidance, configuration options, and a workflow diagram.
  • Chores
    • Project scaffold, manifest, config schema, gitignore, and sample persisted state file.

@CLAassistant

CLAassistant commented Mar 13, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Periodic Jira polling detects completed sprints, builds detailed sprint summaries (issues, assignees, mid‑sprint additions), formats an HTML email, sends via Gmail, and persists processed sprint IDs to avoid duplicate notifications.

Changes

Cohort / File(s) Summary
Configuration & Manifest
ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json, ballerina-integrator/jira-sprint-summary-email/config.bal, ballerina-integrator/jira-sprint-summary-email/Ballerina.toml, ballerina-integrator/jira-sprint-summary-email/.gitignore
Added JSON config schema for Jira/Gmail and logging; introduced configurable variables for credentials, polling, templates, and toggles; added package manifest and gitignore.
Docs & Diagrams
ballerina-integrator/jira-sprint-summary-email/README.md, ballerina-integrator/jira-sprint-summary-email/.choreo/instructions.md, ballerina-integrator/jira-sprint-summary-email/.choreo/diagram.md
Added README, deployment/setup instructions, and a Mermaid flowchart describing the polling → summarize → email process and configuration guidance.
API Clients
ballerina-integrator/jira-sprint-summary-email/connections.bal
Initialized Jira and Gmail clients, normalized Jira base URL, and configured Gmail OAuth2 refresh-token auth.
Types
ballerina-integrator/jira-sprint-summary-email/types.bal
Added public record types: Sprint, IssueDetails, AssigneeStats, and SprintSummary.
Core Utilities
ballerina-integrator/jira-sprint-summary-email/functions.bal
Implemented extraction/normalization of sprint and issue data, robust Jira date parsing, changelog analysis to detect mid‑sprint additions, and assignee breakdown aggregation.
HTML Formatting
ballerina-integrator/jira-sprint-summary-email/html_formatter.bal
Added HTML escaping, subject templating, and functions to render email body sections (completed/carried‑over issues, assignee breakdown, mid‑sprint additions) with conditional inclusion.
Orchestration / Runtime
ballerina-integrator/jira-sprint-summary-email/main.bal
Added main polling loop, Jira queries for completed sprints, per‑sprint summary generation, email sending via Gmail, state persistence, and per‑sprint error isolation.
Persistence
ballerina-integrator/jira-sprint-summary-email/persistence.bal, ballerina-integrator/jira-sprint-summary-email/processed_sprints.json
Added load/save helpers for processed sprint IDs and initial processed_sprints.json entry ({"30":true}).

Sequence Diagram

sequenceDiagram
    participant Scheduler as Scheduler
    participant Main as main.bal
    participant Jira as Jira API
    participant Storage as LocalStorage
    participant Generator as SummaryGenerator
    participant Formatter as HTMLFormatter
    participant Gmail as Gmail API

    Scheduler->>Main: start polling loop
    Main->>Storage: loadProcessedSprints()
    Storage-->>Main: processedSprints

    Main->>Jira: Query issues (JQL for completed sprints)
    Jira-->>Main: issue results

    Main->>Main: identify completed sprints
    loop for each new sprint
        Main->>Generator: generateSprintSummary(sprint)
        Generator->>Jira: fetch sprint issues & changelogs
        Jira-->>Generator: issues + changelogs
        Generator-->>Main: SprintSummary

        Main->>Formatter: formatEmailSubject/body(summary)
        Formatter-->>Main: subject + html

        Main->>Gmail: send email(subject, html, recipients)
        Gmail-->>Main: send result

        Main->>Storage: saveProcessedSprints()
        Storage-->>Main: persisted
    end

    Main->>Scheduler: sleep pollingIntervalHours
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I nibble at sprints as they close each day,
I stitch their tales in HTML and send away.
With carrot‑bright subjects and tidy assignee charts,
I hop through changelogs and sort all the parts.
State saved, no repeats — I bound on my way.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is largely incomplete, providing only a purpose statement that repeats the title and a task reference, while missing most required sections from the template (Goals, Approach, User stories, Release note, Documentation, Training, Certification, Marketing, Testing, Security checks, Samples, Related PRs, Migrations, Test environment, Learning). Expand the description to include at least Goals, Approach, Documentation, and Testing sections from the template, and address the checklist items mentioned in reviewer comments (github/workflows/projects.json updates, config-schema.json validation, etc.).
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: implementation of a pre-built integration for automating Jira summary emails, which aligns with the comprehensive changeset adding this integration.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ballerina-integrator/jira_sprint_summary_email/.choreo/config-schema.json`:
- Around line 176-244: The schema mismatches the runtime contract: update
config-schema.json to match config.bal/README by replacing "gmailRecipient":
{"type":"string"} with "gmailRecipients":
{"type":"array","items":{"type":"string"}}; add "includeCarriedOverIssues":
{"type":"boolean"}; remove or stop requiring keys that don't exist in config.bal
("includeIncompleteIssues" and "includeVelocity") and either remove
"jiraBoardId" from the "required" array or add the corresponding jiraBoardId
declaration to config.bal/README so both schema and runtime agree; ensure
"required" lists the exact keys present in config.bal (e.g., include
"gmailRecipients" instead of "gmailRecipient").

In `@ballerina-integrator/jira_sprint_summary_email/.choreo/instructions.md`:
- Around line 5-7: The docs describe board-scoped monitoring and stale toggle
names, but the code is project-scoped and uses different config keys; update the
instructions to match the runtime and config identifiers: change scope wording
from "board" to "project" (referencing main.bal where project-scoped behavior is
implemented) and update the toggles/defaults to reflect the actual config keys
used in config.bal and functions.bal—replace any mention of
includeCarriedOverIssues with includeMidSprintAdditions (and ensure default
value matches config.bal Line 22/24), and confirm examples reference the
implementation in functions.bal (e.g., logic around includeMidSprintAdditions at
or near Line 275). Also update the duplicate note for lines 71–75 to the
corrected project-scoped wording and toggle names so the guide matches the code.

In `@ballerina-integrator/jira_sprint_summary_email/functions.bal`:
- Around line 243-245: The recent-completion window is too small: replace the
hard-coded thresholdSeconds = pollingIntervalSeconds * 2 in functions.bal with a
larger, configurable window (e.g., add recentCompletionWindowSeconds to
config.bal and compute thresholdSeconds = recentCompletionWindowSeconds) and
update callers to use that value; alternatively, if you prefer keeping a
multiplier, increase it substantially (e.g., * 24*3600) so restarts don't skip
completions. Also persist or reload processedSprints (main.bal symbol
processedSprints) to durable storage on shutdown/startup so completed sprints
are not lost from memory-only state. Ensure you update config.bal (symbol
pollingIntervalSeconds) and default docs accordingly.
- Around line 290-305: The loop is making redundant per-issue API calls via
jiraClient->/api/'3/issue/[issueDetail.key] to fetch changelogs already included
in the initial sprintIssues; remove that call and reuse the changelog from the
cloned issue JSON (issue.cloneWithType() / detailedIssueJson derived from
issueJson) so getSprintAddedDate (or the logic in extractIssueDetails) reads
history from issueJson instead of performing
jiraClient->/api/'3/issue/[issueDetail.key]; delete the jiraClient fetch and the
detailedIssueJson assignment and update any downstream usage to reference the
changelog field on issueJson (or on the IssueBean returned in sprintIssues).
- Around line 55-59: extractSprint() and toSprintFromValue() currently collapse
multi-valued sprint fields to a single (last) entry causing closed sprints to be
lost; change these functions to return all candidate sprints instead of just the
last element: when sprintValue is json[] iterate over every element, convert
each element via toSprint (or a new helper) and return a collection (list/array)
of sprint objects; update toSprintFromValue() to likewise map arrays to multiple
sprint results rather than picking the final item; finally adjust callers such
as checkCompletedSprints() to accept and iterate/filter the returned sprint list
(or filter returned sprints to closed ones) so closed sprints aren’t overwritten
by active sprints.

In `@ballerina-integrator/jira_sprint_summary_email/html_formatter.bal`:
- Around line 57-60: Add a shared escapeHtml(string) helper in this module and
use it everywhere Jira-derived values are interpolated into the template: wrap
summary.sprintName, summary.sprintId, summary.completedDate and all occurrences
of issue fields (e.g., issue.summary, issue.status, issue.assignee, issue.key)
with escapeHtml(...) before inserting into the HTML strings so that characters
like <, >, & are escaped and no raw HTML can be injected; implement the helper
once and replace raw interpolations in the template generation functions/classes
that build the email HTML.

In `@ballerina-integrator/jira_sprint_summary_email/main.bal`:
- Line 10: Replace raw printing of PII (the io:println call that interpolates
jiraEmail and any prints of the recipient list) with non-identifying output:
either a masked value (e.g., show only first/last chars) or a recipient count.
Update the io:println usage that references jiraEmail and any similar prints at
the end of the file (including the occurrences around lines 139-140) to log a
masked string or "N recipients" instead of the full email addresses.
- Around line 71-82: The loop in checkCompletedSprints() currently uses "check"
on generateSprintSummary(sprint) and sendSprintSummaryEmail(summary) so a single
failure aborts the whole poll; wrap the per-sprint work in a localized
error-handling block (e.g., try-catch or use trap) around generateSprintSummary
and sendSprintSummaryEmail so failures are logged (include sprint.id/name) and
the loop continues, only marking processedSprints[sprintKey] = true after a
successful send; reference generateSprintSummary, sendSprintSummaryEmail,
processedSprints, isRecentlyCompleted and the enclosing checkCompletedSprints()
loop to locate where to apply this change.
- Around line 48-52: The two Jira searches using jiraClient->/api/'3/search/jql
(returning jira:SearchAndReconcileResults) currently set maxResults = 100 and
stop; implement cursor pagination by looping requests: call the same endpoint
repeatedly passing the returned nextPageToken (or omitting for first request),
append each response's issues to a cumulative list, and stop when isLast is
true; do this for both the search used to fetch closed sprints (the block
creating searchResults) and the second search (lines ~89–94) so you accumulate
all pages while preserving fields and maxResults.

In `@ballerina-integrator/jira_sprint_summary_email/README.md`:
- Around line 11-65: The fenced ASCII diagram block in README.md is missing a
language tag which triggers markdownlint; update the opening backticks for the
diagram (the fenced code block that begins with ``` on the ASCII diagram) to
include the language identifier text (i.e. change ``` to ```text) so the diagram
renders as a plain text code block and the linter stops flagging it.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bf620039-ee28-402e-af01-69d900a1c63c

📥 Commits

Reviewing files that changed from the base of the PR and between c7cb295 and 9a24d04.

📒 Files selected for processing (12)
  • ballerina-integrator/jira_sprint_summary_email/.choreo/config-schema.json
  • ballerina-integrator/jira_sprint_summary_email/.choreo/diagram.md
  • ballerina-integrator/jira_sprint_summary_email/.choreo/instructions.md
  • ballerina-integrator/jira_sprint_summary_email/.gitignore
  • ballerina-integrator/jira_sprint_summary_email/Ballerina.toml
  • ballerina-integrator/jira_sprint_summary_email/README.md
  • ballerina-integrator/jira_sprint_summary_email/config.bal
  • ballerina-integrator/jira_sprint_summary_email/connections.bal
  • ballerina-integrator/jira_sprint_summary_email/functions.bal
  • ballerina-integrator/jira_sprint_summary_email/html_formatter.bal
  • ballerina-integrator/jira_sprint_summary_email/main.bal
  • ballerina-integrator/jira_sprint_summary_email/types.bal

Comment thread ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json Outdated
Comment thread ballerina-integrator/jira_sprint_summary_email/.choreo/instructions.md Outdated
Comment thread ballerina-integrator/jira-sprint-summary-email/functions.bal
Comment on lines +243 to +245
decimal thresholdSeconds = pollingIntervalSeconds * 2;

return timeDiff < thresholdSeconds;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

The recent-completion window is too small to survive ordinary downtime.

With config.bal Line 14's default pollingIntervalSeconds = 300, this only accepts the last 10 minutes. Any restart or outage longer than that means a completed sprint is skipped forever, because the poller keeps processedSprints only in memory (main.bal Line 33) and never replays older completions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ballerina-integrator/jira_sprint_summary_email/functions.bal` around lines
243 - 245, The recent-completion window is too small: replace the hard-coded
thresholdSeconds = pollingIntervalSeconds * 2 in functions.bal with a larger,
configurable window (e.g., add recentCompletionWindowSeconds to config.bal and
compute thresholdSeconds = recentCompletionWindowSeconds) and update callers to
use that value; alternatively, if you prefer keeping a multiplier, increase it
substantially (e.g., * 24*3600) so restarts don't skip completions. Also persist
or reload processedSprints (main.bal symbol processedSprints) to durable storage
on shutdown/startup so completed sprints are not lost from memory-only state.
Ensure you update config.bal (symbol pollingIntervalSeconds) and default docs
accordingly.

Comment on lines +290 to +305
foreach jira:IssueBean issue in sprintIssues {
json|error issueJson = issue.cloneWithType();
if issueJson is error {
continue;
}

IssueDetails|error issueDetail = extractIssueDetails(issueJson);
if issueDetail is error {
continue;
}

jira:IssueBean issueWithChangelog = check jiraClient->/api/'3/issue/[issueDetail.key](
expand = "changelog",
fields = ["summary", "status", "assignee", "created"]
);
json detailedIssueJson = check issueWithChangelog.cloneWithType();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

git ls-files | grep -E "jira_sprint_summary_email" | head -20

Repository: wso2/integration-samples

Length of output: 816


🏁 Script executed:

git ls-files | grep -E "\.bal$" | head -30

Repository: wso2/integration-samples

Length of output: 1298


🏁 Script executed:

wc -l ballerina-integrator/jira_sprint_summary_email/functions.bal ballerina-integrator/jira_sprint_summary_email/main.bal

Repository: wso2/integration-samples

Length of output: 208


🏁 Script executed:

sed -n '280,315p' ballerina-integrator/jira_sprint_summary_email/functions.bal

Repository: wso2/integration-samples

Length of output: 1547


🏁 Script executed:

sed -n '85,105p' ballerina-integrator/jira_sprint_summary_email/main.bal

Repository: wso2/integration-samples

Length of output: 887


🏁 Script executed:

grep -n "getSprintAddedDate" ballerina-integrator/jira_sprint_summary_email/functions.bal | head -10

Repository: wso2/integration-samples

Length of output: 263


🏁 Script executed:

sed -n '1,50p' ballerina-integrator/jira_sprint_summary_email/functions.bal

Repository: wso2/integration-samples

Length of output: 2232


🏁 Script executed:

sed -n '344,380p' ballerina-integrator/jira_sprint_summary_email/functions.bal

Repository: wso2/integration-samples

Length of output: 1034


🏁 Script executed:

sed -n '344,420p' ballerina-integrator/jira_sprint_summary_email/functions.bal

Repository: wso2/integration-samples

Length of output: 2553


🏁 Script executed:

sed -n '90,100p' ballerina-integrator/jira_sprint_summary_email/main.bal

Repository: wso2/integration-samples

Length of output: 421


Remove the per-issue API fetch or reuse changelog from the initial search results.

The /api/3/issue/{key} calls inside the loop (lines 302-304) are redundant. The initial search at main.bal:92 already expands the changelog, so that data is available in the sprintIssues response. Since getSprintAddedDate only extracts changelog history, the per-issue fetches create O(n) unnecessary network requests—a serious performance issue for large sprints that will hit Jira rate limits.

Either reuse the changelog already in sprintIssues or remove the expand = "changelog" from the initial search if per-issue fetching is intentional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ballerina-integrator/jira_sprint_summary_email/functions.bal` around lines
290 - 305, The loop is making redundant per-issue API calls via
jiraClient->/api/'3/issue/[issueDetail.key] to fetch changelogs already included
in the initial sprintIssues; remove that call and reuse the changelog from the
cloned issue JSON (issue.cloneWithType() / detailedIssueJson derived from
issueJson) so getSprintAddedDate (or the logic in extractIssueDetails) reads
history from issueJson instead of performing
jiraClient->/api/'3/issue/[issueDetail.key]; delete the jiraClient fetch and the
detailedIssueJson assignment and update any downstream usage to reference the
changelog field on issueJson (or on the IssueBean returned in sprintIssues).

Comment on lines +57 to +60
${summary.sprintName}
<div style="font-size: 14px; color: #deebff; font-weight: 400; margin-top: 5px;">Sprint ID: ${summary.sprintId} • ${formattedTime}</div>
<div style="font-size: 14px; color: #deebff; font-weight: 400; margin-top: 5px;">Completed: ${summary.completedDate}</div>
</td>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Escape Jira text before inserting it into the HTML email.

Sprint names, issue summaries, statuses, assignee names, and keys are interpolated raw throughout this template. A normal ticket title containing < or & can break the markup, and a malicious one can inject arbitrary HTML into the outbound message. Please add a shared escapeHtml() helper and apply it to every Jira-derived string in this module.

Also applies to: 143-145, 204-204, 265-267

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ballerina-integrator/jira_sprint_summary_email/html_formatter.bal` around
lines 57 - 60, Add a shared escapeHtml(string) helper in this module and use it
everywhere Jira-derived values are interpolated into the template: wrap
summary.sprintName, summary.sprintId, summary.completedDate and all occurrences
of issue fields (e.g., issue.summary, issue.status, issue.assignee, issue.key)
with escapeHtml(...) before inserting into the HTML strings so that characters
like <, >, & are escaped and no raw HTML can be injected; implement the helper
once and replace raw interpolations in the template generation functions/classes
that build the email HTML.

public function main() returns error? {
io:println("✓ Jira Sprint Summary Automation started!");
io:println(string `✓ Jira Base URL: ${jiraBaseUrl}`);
io:println(string `✓ Jira Email: ${jiraEmail}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Stop logging raw email addresses.

jiraEmail and the full recipient list are PII. Writing them to stdout will leak them into container/runtime logs; log counts or masked values instead.

Suggested change
-    io:println(string `✓ Jira Email: ${jiraEmail}`);
+    io:println("✓ Jira credentials loaded");-    string recipientList = string:'join(", ", ...gmailRecipients);
-    log:printInfo(string `Sending sprint summary email to ${gmailRecipients.length()} recipient(s): ${recipientList}`);
+    log:printInfo(string `Sending sprint summary email to ${gmailRecipients.length()} recipient(s)`);

Also applies to: 139-140

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ballerina-integrator/jira_sprint_summary_email/main.bal` at line 10, Replace
raw printing of PII (the io:println call that interpolates jiraEmail and any
prints of the recipient list) with non-identifying output: either a masked value
(e.g., show only first/last chars) or a recipient count. Update the io:println
usage that references jiraEmail and any similar prints at the end of the file
(including the occurrences around lines 139-140) to log a masked string or "N
recipients" instead of the full email addresses.

Comment on lines +48 to +52
jira:SearchAndReconcileResults searchResults = check jiraClient->/api/'3/search/jql(
jql = string `project = ${jiraProjectKey} AND sprint in closedSprints() ORDER BY updated DESC`,
fields = ["*all"],
maxResults = 100
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "main.bal" -path "*/jira_sprint_summary_email/*" -type f

Repository: wso2/integration-samples

Length of output: 125


🏁 Script executed:

wc -l ballerina-integrator/jira_sprint_summary_email/main.bal

Repository: wso2/integration-samples

Length of output: 127


🏁 Script executed:

sed -n '40,60p' ballerina-integrator/jira_sprint_summary_email/main.bal

Repository: wso2/integration-samples

Length of output: 860


🏁 Script executed:

sed -n '80,100p' ballerina-integrator/jira_sprint_summary_email/main.bal

Repository: wso2/integration-samples

Length of output: 800


🏁 Script executed:

sed -n '1,30p' ballerina-integrator/jira_sprint_summary_email/main.bal

Repository: wso2/integration-samples

Length of output: 1348


🏁 Script executed:

grep -n "import\|jira:" ballerina-integrator/jira_sprint_summary_email/main.bal | head -20

Repository: wso2/integration-samples

Length of output: 626


🌐 Web query:

Jira Cloud REST API v3 search jql pagination nextPageToken

💡 Result:

To paginate a JQL search in Jira Cloud REST API v3 using the enhanced search endpoint, use:

  • GET /rest/api/3/search/jql?jql=...&maxResults=...
  • Read the response fields:
    • isLast (when true, you’re done)
    • nextPageToken (present when there’s another page)
  • Request the next page by sending the same query params (same jql, fields, expand, etc.) plus nextPageToken=<token> (treat the token as opaque). The endpoint supports nextPageToken as a query parameter. [1]

Example flow:

GET /rest/api/3/search/jql?jql=project%20%3D%20HSP&maxResults=100
# -> response: { "isLast": false, "nextPageToken": "..." , "issues": [...] }

GET /rest/api/3/search/jql?jql=project%20%3D%20HSP&maxResults=100&nextPageToken=...
# repeat until "isLast": true

Atlassian also notes that pagination for this endpoint relies on nextPageToken (instead of the old startAt). [2]

Sources

  1. Atlassian Jira Cloud REST API v3 – Issue search (“Search for issues using JQL enhanced search (GET)”) [1]
  2. Atlassian Support KB – “Run JQL search query using Jira Cloud REST API” [2]

🏁 Script executed:

grep -rn "SearchAndReconcileResults" --include="*.bal" ballerina-integrator/

Repository: wso2/integration-samples

Length of output: 363


🌐 Web query:

Ballerina jira connector SearchAndReconcileResults type definition fields

💡 Result:

In the Ballerina Jira connector (ballerinax/jira), SearchAndReconcileResults corresponds to Jira Cloud’s SearchAndReconcileResults response model and contains these (optional) fields:

  • issues?: list of issues returned by the search (array of issue objects / IssueBean[]). [1]
  • names?: mapping of fieldId → fieldName for fields present in the results (object/map of strings). [1][2]
  • schema?: schema describing the field types in the search results (object/map). [2]
  • nextPageToken?: continuation token used to fetch the next page (cursor-style pagination). [1][3]

Sources: Jira.js model docs [1], Jira Cloud REST API “Issue search” group [2], and an OpenAPI schema that includes SearchAndReconcileResults [3].

[1] (mrrefactoring.github.io)
[2] (developer.atlassian.com)
[3] (glama.ai)

Citations:


🌐 Web query:

ballerinax/jira SearchAndReconcileResults nextPageToken isLast

💡 Result:

In ballerinax/jira, SearchAndReconcileResults mirrors Jira Cloud’s /rest/api/3/search/jql cursor pagination:

  • nextPageToken?: string — cursor for the next page. If you’re on the last (or only) page, this is null/absent. [1]
  • isLast: booleantrue when the current page is the last page; false means more pages may be available (and you should use nextPageToken to fetch them). [1][2]

Pagination usage: keep calling the same endpoint with nextPageToken=<previous nextPageToken> until isLast == true (or nextPageToken becomes null). [2]

Sources: [1] Ballerina Central package docs for ballerinax/jira (SearchAndReconcileResults) (central.ballerina.io), [2] Atlassian “Search and reconcile” guidance (developer.atlassian.com)

Citations:


Implement pagination for both Jira searches using nextPageToken and isLast.

Both /search/jql calls currently stop at maxResults = 100 without handling pagination. This causes:

  • First search (lines 48–52): missed closed sprints once the project exceeds 100 total issues
  • Second search (lines 89–94): silently truncated sprint summaries for large sprints (>100 issues)

The SearchAndReconcileResults type includes nextPageToken and isLast fields for cursor-based pagination. Loop through pages until isLast is true.

Also applies to: lines 89–94

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ballerina-integrator/jira_sprint_summary_email/main.bal` around lines 48 -
52, The two Jira searches using jiraClient->/api/'3/search/jql (returning
jira:SearchAndReconcileResults) currently set maxResults = 100 and stop;
implement cursor pagination by looping requests: call the same endpoint
repeatedly passing the returned nextPageToken (or omitting for first request),
append each response's issues to a cumulative list, and stop when isLast is
true; do this for both the search used to fetch closed sprints (the block
creating searchResults) and the second search (lines ~89–94) so you accumulate
all pages while preserving fields and maxResults.

Comment thread ballerina-integrator/jira-sprint-summary-email/main.bal
Comment on lines +11 to +65
```
┌─────────────────────────────────────────────────────────────────┐
│ Jira Sprint Summary Email │
│ Integration │
└─────────────────────────────────────────────────────────────────┘
│ Polls every 5 min (configurable)
┌───────────────────────┐
│ Jira Cloud API │
│ (REST API v3) │
└───────────────────────┘
│ JQL Query: closedSprints()
┌───────────────────────┐
│ Sprint Detection │
│ - Extract sprint info│
│ - Check completion │
│ - Deduplicate │
└───────────────────────┘
│ Recently completed sprint found
┌───────────────────────┐
│ Data Collection │
│ - Fetch all issues │
│ - Get changelog │
│ - Extract details │
└───────────────────────┘
│ Process sprint data
┌───────────────────────┐
│ Summary Generation │
│ - Completed issues │
│ - Carried over │
│ - Team breakdown │
│ - Mid-sprint adds │
└───────────────────────┘
│ Format HTML email
┌───────────────────────┐
│ Gmail API │
│ (OAuth2) │
└───────────────────────┘
│ Send to recipients
┌───────────────────────┐
│ Team Inboxes │
│ 📧 📧 📧 │
└───────────────────────┘
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add a language to the architecture code fence.

Line 11 starts a fenced block without a language, so markdownlint will keep flagging this file. Use text here to preserve the ASCII diagram cleanly.

🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 11-11: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ballerina-integrator/jira_sprint_summary_email/README.md` around lines 11 -
65, The fenced ASCII diagram block in README.md is missing a language tag which
triggers markdownlint; update the opening backticks for the diagram (the fenced
code block that begins with ``` on the ASCII diagram) to include the language
identifier text (i.e. change ``` to ```text) so the diagram renders as a plain
text code block and the linter stops flagging it.

Comment on lines +20 to +25

classDef startNode fill:#90EE90,stroke:#333,stroke-width:2px,color:#000
classDef endNode fill:#FFB6C1,stroke:#333,stroke-width:2px,color:#000
classDef processNode fill:#87CEEB,stroke:#333,stroke-width:2px,color:#000
classDef decisionNode fill:#FFD700,stroke:#333,stroke-width:2px,color:#000
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
classDef startNode fill:#90EE90,stroke:#333,stroke-width:2px,color:#000
classDef endNode fill:#FFB6C1,stroke:#333,stroke-width:2px,color:#000
classDef processNode fill:#87CEEB,stroke:#333,stroke-width:2px,color:#000
classDef decisionNode fill:#FFD700,stroke:#333,stroke-width:2px,color:#000
```

Comment on lines +1 to +2
```mermaid
graph TD

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
```mermaid
graph TD

Comment on lines +9 to +23
## Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│ Jira Sprint Summary Email │
│ Integration │
└─────────────────────────────────────────────────────────────────┘
│ Polls every 5 min (configurable)
┌───────────────────────┐
│ Jira Cloud API │
│ (REST API v3) │
└───────────────────────┘

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section is not needed


![Ballerina](https://img.shields.io/badge/Ballerina-2201.8.0+-blue) ![License](https://img.shields.io/badge/License-Apache%202.0-green)

## Overview

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please refer to salesforce ops to google sheets integration and follow the same structure

Comment on lines +8 to +17
- Generates comprehensive sprint summaries including:
- Total, completed, and incomplete issue counts
- Detailed lists of completed and incomplete issues with assignee information
- Team contribution breakdown with completion rates
- Sends beautifully formatted HTML emails via Gmail with:
- Professional Jira-themed design
- Responsive layout for mobile and desktop
- Color-coded metrics and progress bars
- Timestamped sprint completion information
- Prevents duplicate emails by tracking processed sprints

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's limit this to 5 bullet points maximum

Comment on lines +3 to +19
Start([Start Monitoring]):::startNode --> TestConnection[Test Jira Connection]:::processNode
TestConnection --> ListProjects[List Available Projects]:::processNode
ListProjects --> Poll[Poll for Completed Sprints]:::processNode
Poll --> CheckSprints{Sprints Found?}:::decisionNode
CheckSprints -->|No| Wait[Wait for Polling Interval]:::processNode
CheckSprints -->|Yes| CheckProcessed{Already Processed?}:::decisionNode
CheckProcessed -->|Yes| Wait
CheckProcessed -->|No| CheckRecent{Recently Completed?}:::decisionNode
CheckRecent -->|No| Wait
CheckRecent -->|Yes| FetchIssues[Fetch Sprint Issues]:::processNode
FetchIssues --> CategorizeIssues[Categorize Issues by Status]:::processNode
CategorizeIssues --> CalculateStats[Calculate Team Statistics]:::processNode
CalculateStats --> GenerateEmail[Generate HTML Email]:::processNode
GenerateEmail --> SendEmail[Send via Gmail]:::processNode
SendEmail --> MarkProcessed[Mark Sprint as Processed]:::processNode
MarkProcessed --> Wait
Wait --> Poll

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's simplify this flow chart. let's have maximum of 5-6 nodes

io:println("");

// Test Jira connection
io:println("Testing Jira connection...");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use ballerina/log package

@hasathcharu

hasathcharu commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

Prebuilt Integration Checklist

  • github/workflows/projects.json is updated with the new prebuilt integration path
  • Path in the .github/workflows/projects.json matches the project path exactly
  • Directory path is delimited with '-'
  • Directory path is identical to the integration name in Ballerina.toml
  • README.MD is correctly structured
  • Configurations are organized by records based on vendor
  • choreo/config-schema.json exists and is up to date
  • choreo/config-schema.json includes the requiredLevel key value pair at the root of the json
  • choreo/instructions.md is correctly structured
  • choreo/diagram.md is correctly written and parsable by Mermaid

Comment thread ballerina-integrator/jira-sprint-summary-email/html_formatter.bal Outdated
Ranvin36 and others added 2 commits March 13, 2026 15:19
Co-authored-by: Haritha Hasathcharu <42366642+hasathcharu@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (3)
ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json (1)

158-244: ⚠️ Potential issue | 🔴 Critical

Schema-to-runtime contract mismatch persists.

The schema properties still don't align with config.bal:

Schema Property config.bal Property Issue
gmailRecipient (string) gmailRecipients (string[]) Type & name mismatch
pollingIntervalSeconds (number) pollingIntervalHours (decimal) Unit & name mismatch
jiraBoardId (required) Not declared Missing in runtime
includeIncompleteIssues includeCarriedOverIssues Name mismatch
includeVelocity Not declared Missing in runtime

This will cause Choreo deployments to fail because the runtime expects different configuration keys than what the schema provides.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json`
around lines 158 - 244, The JSON schema's jira_sprint_summary_email properties
must match the runtime config in config.bal: rename gmailRecipient to
gmailRecipients and change its type to array of strings, replace
pollingIntervalSeconds with pollingIntervalHours (type number/decimal) and
adjust descriptions, remove jiraBoardId from the required list (or add
jiraBoardId to config.bal if intended) to resolve the missing runtime key,
rename includeIncompleteIssues to includeCarriedOverIssues, and add the
includeVelocity property to the schema if it exists in config.bal; update
additionalProperties/required arrays accordingly so schema keys/types match the
config.bal symbols (gmailRecipients, pollingIntervalHours,
includeCarriedOverIssues, includeVelocity, and jiraBoardId decision).
ballerina-integrator/jira-sprint-summary-email/main.bal (1)

44-48: ⚠️ Potential issue | 🟠 Major

Pagination not implemented - results truncated at 100 issues.

The JQL search stops at maxResults = 100 without using nextPageToken for subsequent pages. Projects with more than 100 issues matching sprint in closedSprints() will have incomplete sprint discovery.

The SearchAndReconcileResults type includes nextPageToken and isLast fields for cursor-based pagination.

Also applies to lines 91-96.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ballerina-integrator/jira-sprint-summary-email/main.bal` around lines 44 -
48, The JQL call using jiraClient->/api/'3/search/jql with maxResults = 100
truncates results; update the logic around jira:SearchAndReconcileResults to
implement cursor-based pagination: call the same endpoint repeatedly, passing
the returned nextPageToken (and keeping a sensible page size) until isLast is
true, and append/merge each response's issues into the accumulated result set
instead of returning the first page only; do the same fix for the other
identical call site that uses maxResults = 100 and relies on
SearchAndReconcileResults (use nextPageToken and isLast to drive the loop).
ballerina-integrator/jira-sprint-summary-email/functions.bal (1)

55-62: ⚠️ Potential issue | 🟠 Major

Multi-sprint handling still returns only the last sprint.

When sprintValue is an array (lines 55-62), extracting only sprintValue[sprintValue.length() - 1] may return an active sprint instead of the closed one when issues carry over. This could cause closed sprints to be missed during discovery.

The same pattern exists in toSprintFromValue() (lines 90-107).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ballerina-integrator/jira-sprint-summary-email/functions.bal` around lines 55
- 62, The code in toSprint (and toSprintFromValue) currently picks only the last
element of sprintValue (sprintValue[sprintValue.length() - 1]) which can return
an active sprint; instead iterate the sprintValue array to find and return the
most recent closed sprint (e.g., check each element for its sprint status/closed
flag in the json/map<json> and pick the latest one that is closed), falling back
to the last element if no closed sprint is found; update the logic inside the
sprintValue is json[] branches of toSprint and toSprintFromValue to perform this
scan (use the existing latest/local variables and toSprint mapping) rather than
unconditionally returning the last array element.
🧹 Nitpick comments (2)
ballerina-integrator/jira-sprint-summary-email/.choreo/diagram.md (1)

12-15: Remove trailing empty lines.

The trailing empty lines don't affect functionality but reduce code cleanliness. Consider removing them for a cleaner file.

✨ Proposed cleanup
 
     classDef startNode fill:`#90EE90`,stroke:`#333`,stroke-width:2px,color:`#000`
     classDef processNode fill:`#87CEEB`,stroke:`#333`,stroke-width:2px,color:`#000`
-
-
-
-
-  
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ballerina-integrator/jira-sprint-summary-email/.choreo/diagram.md` around
lines 12 - 15, Remove the trailing empty lines at the end of diagram.md so the
file ends cleanly (leave at most one final newline); open diagram.md, delete any
blank lines after the last meaningful content, and save so there are no
extraneous trailing empty lines.
ballerina-integrator/jira-sprint-summary-email/html_formatter.bal (1)

268-274: Use parseJiraDateTime for consistency.

Line 270 uses time:utcFromString directly, but Jira datetime fields may have non-standard timezone offsets (e.g., +0000 without colon). The parseJiraDateTime function in functions.bal handles this normalization.

♻️ Proposed fix
         if issue.created is string {
             string createdValue = <string>issue.created;
-            time:Utc|error parsedTime = time:utcFromString(createdValue);
+            time:Utc|error parsedTime = parseJiraDateTime(createdValue);
             if parsedTime is time:Utc {
                 createdDate = getFormattedTimeStamp(parsedTime);
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ballerina-integrator/jira-sprint-summary-email/html_formatter.bal` around
lines 268 - 274, The code block handling issue.created should use the shared
parseJiraDateTime function for normalization instead of calling
time:utcFromString directly: call parseJiraDateTime(<string>issue.created) (from
functions.bal), check the returned time:Utc|error like the existing parsedTime
handling, and pass the successful time:Utc into getFormattedTimeStamp to set
createdDate; ensure you keep the same type checks and error flow but replace the
direct time:utcFromString invocation with parseJiraDateTime to normalize Jira
timezone offsets.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ballerina-integrator/jira-sprint-summary-email/.choreo/instructions.md`:
- Line 61: Documentation and implementation disagree on the configuration name:
documentation uses includeIncompleteIssues while config.bal defines
includeCarriedOverIssues; pick one and make both places consistent — either
update the docs line that mentions includeIncompleteIssues to read
includeCarriedOverIssues (so it matches the existing config.bal), or rename the
boolean in config.bal from includeCarriedOverIssues to includeIncompleteIssues
(and update any references) so the code semantics and config key match the
documentation; ensure you update any usages/readers of the config (e.g., where
the config value is accessed) to the chosen symbol so the toggle works as
expected.
- Around line 47-48: The docs currently declare pollingIntervalSeconds (default
300) but the implementation in config.bal uses pollingIntervalHours (decimal);
update the documentation entry to match the code by renaming
pollingIntervalSeconds to pollingIntervalHours and changing the default value
from 300 to 0.0833 (hours ≈ 5 minutes) so names, types and units align with the
config.bal declaration and avoid the 3600x unit mismatch.
- Around line 56-57: The docs and schema use the singular config name
gmailRecipient (string) while the implementation uses gmailRecipients (array of
strings), causing validation failures; update all three artifacts to the same
shape by renaming and typing the schema and docs to match the implementation:
change config-schema entry gmailRecipient to gmailRecipients and make its type
an array with string items, and update instructions.md to document
gmailRecipients as an array of email strings (or alternatively change config.bal
to gmailRecipient:string if you prefer singular) so the symbol names
gmailRecipient/gmailRecipients and their types are consistent across config.bal,
config-schema.json, and instructions.md.

In `@ballerina-integrator/jira-sprint-summary-email/config.bal`:
- Line 18: The default value of the configurable string emailSubjectTemplate
contains a typo ("Sprint Summer")—update the value of emailSubjectTemplate to
the correct phrase "Sprint Summary" so the template reads "Sprint Summary:
{{sprintName}}" (locate the configurable string emailSubjectTemplate in the
config.bal file and correct its default string).

---

Duplicate comments:
In `@ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json`:
- Around line 158-244: The JSON schema's jira_sprint_summary_email properties
must match the runtime config in config.bal: rename gmailRecipient to
gmailRecipients and change its type to array of strings, replace
pollingIntervalSeconds with pollingIntervalHours (type number/decimal) and
adjust descriptions, remove jiraBoardId from the required list (or add
jiraBoardId to config.bal if intended) to resolve the missing runtime key,
rename includeIncompleteIssues to includeCarriedOverIssues, and add the
includeVelocity property to the schema if it exists in config.bal; update
additionalProperties/required arrays accordingly so schema keys/types match the
config.bal symbols (gmailRecipients, pollingIntervalHours,
includeCarriedOverIssues, includeVelocity, and jiraBoardId decision).

In `@ballerina-integrator/jira-sprint-summary-email/functions.bal`:
- Around line 55-62: The code in toSprint (and toSprintFromValue) currently
picks only the last element of sprintValue (sprintValue[sprintValue.length() -
1]) which can return an active sprint; instead iterate the sprintValue array to
find and return the most recent closed sprint (e.g., check each element for its
sprint status/closed flag in the json/map<json> and pick the latest one that is
closed), falling back to the last element if no closed sprint is found; update
the logic inside the sprintValue is json[] branches of toSprint and
toSprintFromValue to perform this scan (use the existing latest/local variables
and toSprint mapping) rather than unconditionally returning the last array
element.

In `@ballerina-integrator/jira-sprint-summary-email/main.bal`:
- Around line 44-48: The JQL call using jiraClient->/api/'3/search/jql with
maxResults = 100 truncates results; update the logic around
jira:SearchAndReconcileResults to implement cursor-based pagination: call the
same endpoint repeatedly, passing the returned nextPageToken (and keeping a
sensible page size) until isLast is true, and append/merge each response's
issues into the accumulated result set instead of returning the first page only;
do the same fix for the other identical call site that uses maxResults = 100 and
relies on SearchAndReconcileResults (use nextPageToken and isLast to drive the
loop).

---

Nitpick comments:
In `@ballerina-integrator/jira-sprint-summary-email/.choreo/diagram.md`:
- Around line 12-15: Remove the trailing empty lines at the end of diagram.md so
the file ends cleanly (leave at most one final newline); open diagram.md, delete
any blank lines after the last meaningful content, and save so there are no
extraneous trailing empty lines.

In `@ballerina-integrator/jira-sprint-summary-email/html_formatter.bal`:
- Around line 268-274: The code block handling issue.created should use the
shared parseJiraDateTime function for normalization instead of calling
time:utcFromString directly: call parseJiraDateTime(<string>issue.created) (from
functions.bal), check the returned time:Utc|error like the existing parsedTime
handling, and pass the successful time:Utc into getFormattedTimeStamp to set
createdDate; ensure you keep the same type checks and error flow but replace the
direct time:utcFromString invocation with parseJiraDateTime to normalize Jira
timezone offsets.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 28f5f2b2-a501-4ab2-b043-1fbd2128486a

📥 Commits

Reviewing files that changed from the base of the PR and between 9a24d04 and c0a5551.

📒 Files selected for processing (15)
  • ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json
  • ballerina-integrator/jira-sprint-summary-email/.choreo/diagram.md
  • ballerina-integrator/jira-sprint-summary-email/.choreo/instructions.md
  • ballerina-integrator/jira-sprint-summary-email/.gitignore
  • ballerina-integrator/jira-sprint-summary-email/Ballerina.toml
  • ballerina-integrator/jira-sprint-summary-email/Dependencies.toml
  • ballerina-integrator/jira-sprint-summary-email/README.md
  • ballerina-integrator/jira-sprint-summary-email/config.bal
  • ballerina-integrator/jira-sprint-summary-email/connections.bal
  • ballerina-integrator/jira-sprint-summary-email/functions.bal
  • ballerina-integrator/jira-sprint-summary-email/html_formatter.bal
  • ballerina-integrator/jira-sprint-summary-email/main.bal
  • ballerina-integrator/jira-sprint-summary-email/persistence.bal
  • ballerina-integrator/jira-sprint-summary-email/processed_sprints.json
  • ballerina-integrator/jira-sprint-summary-email/types.bal
✅ Files skipped from review due to trivial changes (4)
  • ballerina-integrator/jira-sprint-summary-email/Ballerina.toml
  • ballerina-integrator/jira-sprint-summary-email/README.md
  • ballerina-integrator/jira-sprint-summary-email/.gitignore
  • ballerina-integrator/jira-sprint-summary-email/processed_sprints.json

Comment thread ballerina-integrator/jira-sprint-summary-email/.choreo/instructions.md Outdated
Comment thread ballerina-integrator/jira-sprint-summary-email/.choreo/instructions.md Outdated
Comment thread ballerina-integrator/jira-sprint-summary-email/.choreo/instructions.md Outdated
Comment thread ballerina-integrator/jira-sprint-summary-email/config.bal Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json (1)

176-244: ⚠️ Potential issue | 🔴 Critical

Schema contract is still out of sync with runtime config (blocking).

Line 176–244 currently defines keys that do not match the actual integration contract (config.bal, main.bal, html_formatter.bal). This will cause configuration failures at startup despite schema-valid input.

🛠️ Minimal alignment patch
-            "jiraBoardId": {
-              "type": "integer",
-              "description": ""
-            },
...
-            "gmailRecipient": {
-              "type": "string",
-              "description": ""
-            },
-            "pollingIntervalSeconds": {
+            "gmailRecipients": {
+              "type": "array",
+              "items": {
+                "type": "string"
+              },
+              "minItems": 1,
+              "description": ""
+            },
+            "pollingIntervalHours": {
               "type": "number",
               "description": ""
             },
...
-            "includeIncompleteIssues": {
+            "includeCarriedOverIssues": {
               "type": "boolean",
               "description": ""
             },
...
-            "includeVelocity": {
-              "type": "boolean",
-              "description": ""
-            }
...
-            "jiraBoardId",
...
-            "gmailRecipient"
+            "gmailRecipients"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json`
around lines 176 - 244, The JSON schema's properties and required list (e.g.,
jiraBoardId, jiraProjectKey, gmailClientId, gmailClientSecret,
gmailRefreshToken, gmailRecipient, pollingIntervalSeconds, timeZone,
emailSubjectTemplate, includeCompletedIssues, includeIncompleteIssues,
includeAssigneeBreakdown, includeMidSprintAdditions, includeVelocity) are out of
sync with the actual runtime config used by config.bal, main.bal, and
html_formatter.bal; update the schema to exactly match the keys and required
fields referenced by those Ballerina files (or vice versa: rename/align the
variables in config.bal/main.bal/html_formatter.bal to match the schema),
ensuring property names, types, and the "required" array reflect the runtime
contract so startup validation succeeds.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json`:
- Around line 1-3: The root JSON schema is missing the requiredLevel metadata
key; update the root object in config-schema.json (the schema that currently
contains "$schema" and "type": "object") to declare a "requiredLevel" property
in "properties" (with the appropriate type/enum per Choreo prebuilt integration
checklist) and add "requiredLevel" to the root "required" array so the schema
validator recognizes it as mandatory.

In `@ballerina-integrator/jira-sprint-summary-email/.choreo/instructions.md`:
- Line 61: The label text for includeCarriedOverIssues should use the hyphenated
form "carried-over issues"; locate the UI/label string tied to
includeCarriedOverIssues in the instructions or localization content and update
the displayed text from "carried over issues" to "carried-over issues" so the
label reads correctly.

In `@ballerina-integrator/jira-sprint-summary-email/README.md`:
- Line 74: Update the README entry for the includeCarriedOverIssues option to
hyphenate the compound adjective; replace "Show carried over issues list
(default: `true`)" with "Show carried-over issues list (default: `true`)" so the
description for includeCarriedOverIssues uses consistent technical wording.

---

Duplicate comments:
In `@ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json`:
- Around line 176-244: The JSON schema's properties and required list (e.g.,
jiraBoardId, jiraProjectKey, gmailClientId, gmailClientSecret,
gmailRefreshToken, gmailRecipient, pollingIntervalSeconds, timeZone,
emailSubjectTemplate, includeCompletedIssues, includeIncompleteIssues,
includeAssigneeBreakdown, includeMidSprintAdditions, includeVelocity) are out of
sync with the actual runtime config used by config.bal, main.bal, and
html_formatter.bal; update the schema to exactly match the keys and required
fields referenced by those Ballerina files (or vice versa: rename/align the
variables in config.bal/main.bal/html_formatter.bal to match the schema),
ensuring property names, types, and the "required" array reflect the runtime
contract so startup validation succeeds.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 15393cd2-1fc9-4721-95ac-b6e8442d731b

📥 Commits

Reviewing files that changed from the base of the PR and between c0a5551 and c8193a7.

📒 Files selected for processing (3)
  • ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json
  • ballerina-integrator/jira-sprint-summary-email/.choreo/instructions.md
  • ballerina-integrator/jira-sprint-summary-email/README.md

Comment thread ballerina-integrator/jira-sprint-summary-email/.choreo/instructions.md Outdated
Comment thread ballerina-integrator/jira-sprint-summary-email/README.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json (1)

1-4: ⚠️ Potential issue | 🟠 Major

Missing root requiredLevel metadata key.

Line 1–4 defines the schema root but does not include requiredLevel, which is explicitly part of the prebuilt integration checklist and was already flagged earlier.

#!/bin/bash
set -euo pipefail

target="ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json"

echo "== Target file root keys =="
python - <<'PY'
import json
p="ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json"
with open(p) as f:
    d=json.load(f)
print("has requiredLevel:", "requiredLevel" in d)
print("root keys:", sorted(list(d.keys())))
PY

echo
echo "== Compare with other config-schema.json files in repo (if any) =="
fd 'config-schema.json' | while read -r f; do
  [ "$f" = "$target" ] && continue
  if rg -n '"requiredLevel"' "$f" >/dev/null; then
    echo "-- $f"
    rg -n '"requiredLevel"|^\s*"required"\s*:' "$f" -C1
  fi
done
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json`
around lines 1 - 4, The schema root is missing the requiredLevel metadata key;
update the JSON object in config-schema.json by adding a top-level
"requiredLevel" property with the appropriate value (matching other prebuilt
schemas) and ensure it's present alongside existing root keys like "$schema",
"type", and "properties"; locate the schema root in this file and insert
"requiredLevel": "<appropriate-level>" (e.g., "optional" or the value used in
other config-schema.json files) so the root contains the required metadata.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json`:
- Around line 5-93: The schema currently allows empty configs because the root
and the "wso2" object lack required declarations; add a top-level "required":
["wso2"] and inside the "wso2" object add "required":
["jira_sprint_summary_email"] so the validator enforces presence of the wso2
block and the jira_sprint_summary_email object (the latter already has its own
required array for its internal fields).

---

Duplicate comments:
In `@ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json`:
- Around line 1-4: The schema root is missing the requiredLevel metadata key;
update the JSON object in config-schema.json by adding a top-level
"requiredLevel" property with the appropriate value (matching other prebuilt
schemas) and ensure it's present alongside existing root keys like "$schema",
"type", and "properties"; locate the schema root in this file and insert
"requiredLevel": "<appropriate-level>" (e.g., "optional" or the value used in
other config-schema.json files) so the root contains the required metadata.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9cb307f9-accb-49b4-b0e3-2c45724f25fc

📥 Commits

Reviewing files that changed from the base of the PR and between c8193a7 and dc0a1a0.

📒 Files selected for processing (4)
  • ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json
  • ballerina-integrator/jira-sprint-summary-email/.choreo/instructions.md
  • ballerina-integrator/jira-sprint-summary-email/README.md
  • ballerina-integrator/jira-sprint-summary-email/config.bal
✅ Files skipped from review due to trivial changes (1)
  • ballerina-integrator/jira-sprint-summary-email/README.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • ballerina-integrator/jira-sprint-summary-email/.choreo/instructions.md
  • ballerina-integrator/jira-sprint-summary-email/config.bal

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json (2)

91-94: ⚠️ Potential issue | 🟠 Major

Missing required array in the wso2 object.

The wso2 object needs "required": ["jira_sprint_summary_email"] to enforce that the nested integration config block is present. Without this, {"wso2":{}} passes schema validation but causes runtime failures when Ballerina attempts to read the required configuration variables.

,

🛠️ Suggested fix
         }
       },
+      "required": [
+        "jira_sprint_summary_email"
+      ],
       "additionalProperties": false
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json`
around lines 91 - 94, The wso2 object schema is missing a required array so
empty {"wso2":{}} validates but breaks at runtime; update the wso2 JSON schema
to include "required": ["jira_sprint_summary_email"] inside the wso2 object
definition so the nested jira_sprint_summary_email block is enforced (i.e., add
the required property alongside the existing properties definition for the wso2
object).

1-3: ⚠️ Potential issue | 🟠 Major

Missing requiredLevel metadata key and root-level required array.

The Choreo prebuilt integration checklist requires a requiredLevel key at the root of the schema. Additionally, without "required": ["wso2"] at the root level, an empty config {} will pass schema validation but fail at runtime initialization.

,

🛠️ Suggested fix
 {
   "$schema": "http://json-schema.org/draft-07/schema#",
   "type": "object",
+  "requiredLevel": "must",
   "properties": {
     "wso2": {

And at the end of the file before the final closing brace:

   },
-  "additionalProperties": false
+  "additionalProperties": false,
+  "required": [
+    "wso2"
+  ]
 }

Also applies to: 215-216

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json`
around lines 1 - 3, Add the missing Choreo metadata and root-required array to
the JSON schema: insert a root-level "requiredLevel" key with the appropriate
value expected by Choreo (e.g., "required" or the project-standard value) and
add a root "required" array containing "wso2" so an empty object will fail
validation (i.e., add "required": ["wso2"]). Update the top-level object in
config-schema.json (where "$schema" and "type" are declared) to include these
keys before the final closing brace.
🧹 Nitpick comments (1)
ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json (1)

39-45: Consider adding minItems: 1 for gmailRecipients.

The array currently allows zero recipients, which would result in the integration running successfully but never sending any emails. Adding a minimum constraint would catch this misconfiguration early during schema validation.

💡 Optional improvement
             "gmailRecipients": {
               "type": "array",
               "items": {
                 "type": "string"
               },
+              "minItems": 1,
               "description": ""
             },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json`
around lines 39 - 45, The schema for the gmailRecipients array currently allows
zero items; update the "gmailRecipients" JSON Schema entry to require at least
one recipient by adding "minItems": 1 to its definition so schema validation
fails on empty arrays (locate the "gmailRecipients" property in the config
schema and add the minItems constraint).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json`:
- Around line 91-94: The wso2 object schema is missing a required array so empty
{"wso2":{}} validates but breaks at runtime; update the wso2 JSON schema to
include "required": ["jira_sprint_summary_email"] inside the wso2 object
definition so the nested jira_sprint_summary_email block is enforced (i.e., add
the required property alongside the existing properties definition for the wso2
object).
- Around line 1-3: Add the missing Choreo metadata and root-required array to
the JSON schema: insert a root-level "requiredLevel" key with the appropriate
value expected by Choreo (e.g., "required" or the project-standard value) and
add a root "required" array containing "wso2" so an empty object will fail
validation (i.e., add "required": ["wso2"]). Update the top-level object in
config-schema.json (where "$schema" and "type" are declared) to include these
keys before the final closing brace.

---

Nitpick comments:
In `@ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json`:
- Around line 39-45: The schema for the gmailRecipients array currently allows
zero items; update the "gmailRecipients" JSON Schema entry to require at least
one recipient by adding "minItems": 1 to its definition so schema validation
fails on empty arrays (locate the "gmailRecipients" property in the config
schema and add the minItems constraint).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9e3bf656-68df-4f55-8acd-15f4bb6a0411

📥 Commits

Reviewing files that changed from the base of the PR and between dc0a1a0 and c02febf.

📒 Files selected for processing (2)
  • ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json
  • ballerina-integrator/jira-sprint-summary-email/README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • ballerina-integrator/jira-sprint-summary-email/README.md

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Ballerina scheduled-task integration (jira_sprint_summary_email) that detects recently completed Jira sprints, generates an HTML summary, and emails it to configured recipients via Gmail, with Jira-label-based de-duplication.

Changes:

  • Introduces sprint/issue/summary data types plus summary generation logic (JQL queries, mid-sprint addition detection, assignee breakdown).
  • Adds Gmail HTML email composition and delivery.
  • Adds Choreo artifacts (config schema, instructions, diagram) and end-user documentation.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
ballerina-integrator/jira-sprint-summary-email/types.bal Defines records for sprint, issue details, assignee stats, and sprint summary.
ballerina-integrator/jira-sprint-summary-email/main.bal Orchestrates Jira querying, summary generation, Gmail sending, and persistence marking.
ballerina-integrator/jira-sprint-summary-email/jira_persistence.bal Implements processed-sprint detection/marking using a Jira label.
ballerina-integrator/jira-sprint-summary-email/functions.bal Implements JSON extraction, date parsing, mid-sprint detection, and assignee stats.
ballerina-integrator/jira-sprint-summary-email/html_formatter.bal Builds the HTML email body/subject and attempts to escape HTML.
ballerina-integrator/jira-sprint-summary-email/connections.bal Initializes Jira and Gmail clients from configuration.
ballerina-integrator/jira-sprint-summary-email/config.bal Declares configurable values/constants for Jira, Gmail, email formatting, and toggles.
ballerina-integrator/jira-sprint-summary-email/README.md Documents setup, configuration, scheduling, and deployment on Choreo.
ballerina-integrator/jira-sprint-summary-email/Ballerina.toml Defines the Ballerina package metadata/build options.
ballerina-integrator/jira-sprint-summary-email/.gitignore Ignores build artifacts and local Config.toml.
ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json Defines Choreo configuration schema for the integration.
ballerina-integrator/jira-sprint-summary-email/.choreo/instructions.md Provides Choreo-facing setup instructions.
ballerina-integrator/jira-sprint-summary-email/.choreo/diagram.md Provides an execution-flow diagram for documentation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


jira:SearchAndReconcileResults searchResults = check jiraClient->/api/'3/search/jql(
jql = string `project = ${jiraProjectKey} AND sprint in closedSprints() AND updated >= "${cutoffDateString}" ORDER BY updated DESC`,
fields = ["*all"],

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the “find recently completed sprints” query, fields = ["*all"] can significantly increase payload size and latency. Since this code only needs sprint metadata (and maybe a small subset of fields), request only the required fields (e.g., sprint/custom sprint field + updated) to reduce load and avoid Jira response size limits.

Suggested change
fields = ["*all"],
fields = ["sprint", "updated"],

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +16
// Search for any issue in this sprint with the processed label
string jqlQuery = string `sprint = ${sprintId} AND labels = "${processedSprintLabel}"`;

jira:SearchAndReconcileResults searchResults = check jiraClient->/api/'3/search/jql(
jql = jqlQuery,
maxResults = 1
);

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isSprintProcessedByLabel doesn’t constrain the JQL by project = ${jiraProjectKey}. If the same sprint spans multiple projects/boards (or another project’s issue is labeled), this can cause false positives and skip sending the summary for the target project. Add the project filter to keep processing decisions scoped to the configured project.

Copilot uses AI. Check for mistakes.
}
};

final string jiraApiBaseUrl = jiraBaseUrl.endsWith("/rest") ? jiraBaseUrl : jiraBaseUrl + "/rest";

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jiraApiBaseUrl construction only checks for a /rest suffix. If users configure jiraBaseUrl with a trailing slash (e.g., https://…net/), this will produce a double-slash (…net//rest). Also, if users provide …/rest/api/3, appending /rest will be incorrect. Normalize the base URL (trim trailing /, and handle .../rest vs .../rest/api/3) before creating the Jira client.

Suggested change
final string jiraApiBaseUrl = jiraBaseUrl.endsWith("/rest") ? jiraBaseUrl : jiraBaseUrl + "/rest";
string normalizedJiraBaseUrl = jiraBaseUrl;
// Remove any trailing slashes to avoid double-slash when appending paths.
while normalizedJiraBaseUrl.endsWith("/") {
normalizedJiraBaseUrl = normalizedJiraBaseUrl.substring(0, normalizedJiraBaseUrl.length() - 1);
}
// If the URL ends with '/rest/api/3' (or '/rest/api/2'), normalize it back to just '/rest'.
if normalizedJiraBaseUrl.endsWith("/rest/api/3") {
normalizedJiraBaseUrl = normalizedJiraBaseUrl.substring(0, normalizedJiraBaseUrl.length() - "/api/3".length());
} else if normalizedJiraBaseUrl.endsWith("/rest/api/2") {
normalizedJiraBaseUrl = normalizedJiraBaseUrl.substring(0, normalizedJiraBaseUrl.length() - "/api/2".length());
} else if !normalizedJiraBaseUrl.endsWith("/rest") {
// If it doesn't already end with '/rest', append it.
normalizedJiraBaseUrl = normalizedJiraBaseUrl + "/rest";
}
final string jiraApiBaseUrl = normalizedJiraBaseUrl;

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +11
function escapeHtml(string text) returns string {
string:RegExp ampersand = re `&`;
string:RegExp lessThan = re `<`;
string:RegExp greaterThan = re `>`;
string:RegExp doubleQuote = re `"`;
string:RegExp singleQuote = re `'`;

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string:RegExp is not a valid regexp type here, and this file doesn’t import the regexp module. As written, re literals won’t type-check against string:RegExp, and the .replaceAll() calls will fail to compile. Use the ballerina/lang.regexp module’s regexp:RegExp type (or omit the explicit type annotation) consistently for these patterns.

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +49
- Monitors a specified Jira board for newly completed sprints using a configurable polling interval.
- Detects sprint completion and queries all sprint issues via JQL.
- Builds a comprehensive sprint summary with issue counts, issue details, and team contribution insights.
- Sends a professionally formatted, responsive HTML email through Gmail with clear visual metrics and timestamps.
- Prevents duplicate notifications by tracking already processed sprint IDs.

<details>

<summary>Jira Setup Guide</summary>

1. A Jira account with API access
2. API credentials:
1. Email (Your Jira account email)
2. API Token (Generate from [Atlassian Account Security](https://id.atlassian.com/manage-profile/security/api-tokens))
3. Base URL (Your Jira instance URL, e.g., `https://yourcompany.atlassian.net`)
4. Project Key (e.g., `PROJ`, `DEV`)

This integration uses Basic Authentication with API tokens. [Learn how to create Jira API tokens](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/).

</details>

<details>

<summary>Gmail Setup Guide</summary>

1. A Google account with Gmail access
2. OAuth2 credentials:
1. Client ID
2. Client Secret
3. Refresh Token
3. Scopes Required:
1. `https://www.googleapis.com/auth/gmail.send`
2. `https://www.googleapis.com/auth/gmail.compose`

This integration uses refresh token flow for auth. [Learn how to Develop on Google Workspace](https://developers.google.com/workspace/guides/get-started).

</details>

<details>

<summary>Additional Configurations</summary>

1. `pollingIntervalHours`
How often to check for completed sprints (in hours). Use `0.0833` for approximately 5 minutes.

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This integration runs once and exits, but the instructions describe a “configurable polling interval” and reference pollingIntervalHours, which doesn’t exist in config.bal or the config schema. Update this doc to reflect scheduled-task execution (or implement the pollingIntervalHours loop/config if that’s intended).

Copilot uses AI. Check for mistakes.
Comment on lines +108 to +116
jira:SearchAndReconcileResults searchResults = check jiraClient->/api/'3/search/jql(
jql = string `project = ${jiraProjectKey} AND sprint = ${sprint.id}`,
fields = ["summary", "status", "assignee", "created"],
expand = "changelog",
maxResults = 100
);

jira:IssueBean[] sprintIssues = searchResults.issues ?: [];

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maxResults = 100 will truncate sprint issue retrieval for larger sprints, leading to incorrect counts, missing issues in the email, and incomplete mid-sprint addition detection. Consider paginating the Jira search (using startAt/maxResults) until all issues are fetched, or at minimum log/handle the case where results are incomplete.

Suggested change
jira:SearchAndReconcileResults searchResults = check jiraClient->/api/'3/search/jql(
jql = string `project = ${jiraProjectKey} AND sprint = ${sprint.id}`,
fields = ["summary", "status", "assignee", "created"],
expand = "changelog",
maxResults = 100
);
jira:IssueBean[] sprintIssues = searchResults.issues ?: [];
// Fetch all sprint issues with pagination to avoid truncation at maxResults.
jira:IssueBean[] sprintIssues = [];
int startAt = 0;
int pageSize = 100;
while true {
jira:SearchAndReconcileResults pageResults = check jiraClient->/api/'3/search/jql(
jql = string `project = ${jiraProjectKey} AND sprint = ${sprint.id}`,
fields = ["summary", "status", "assignee", "created"],
expand = "changelog",
maxResults = pageSize,
startAt = startAt
);
jira:IssueBean[] pageIssues = pageResults.issues ?: [];
// Append issues from this page to the full sprint issue list.
sprintIssues.push(...pageIssues);
// If fewer than pageSize issues returned, we've reached the last page.
if pageIssues.length() < pageSize {
break;
}
startAt += pageSize;
}

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +14
// Lookback Configuration (permanent settings)
const decimal lookbackHours = 1460.0; // ~2 months (61 days)

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lookbackHours is declared as a const, but the rest of the integration (README + Choreo config schema) treats it as a configurable setting. With the current code, users can’t set the lookback window and deployments providing lookbackHours will be out of sync with runtime behavior. Make this a configurable decimal lookbackHours = <default>; (or update the docs/schema to remove it).

Suggested change
// Lookback Configuration (permanent settings)
const decimal lookbackHours = 1460.0; // ~2 months (61 days)
// Lookback Configuration
configurable decimal lookbackHours = 1460.0; // ~2 months (61 days)

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +90
"lookbackHours": {
"type": "number",
"description": ""
},
"timeZone": {
"type": "string",
"description": ""
},
"emailSubjectTemplate": {
"type": "string",
"description": ""
},
"includeCompletedIssues": {
"type": "boolean",
"description": ""
},
"includeCarriedOverIssues": {
"type": "boolean",
"description": ""
},
"includeAssigneeBreakdown": {
"type": "boolean",
"description": ""
},
"includeMidSprintAdditions": {
"type": "boolean",
"description": ""
}
},
"additionalProperties": false,
"required": [
"jiraEmail",
"jiraApiToken",
"jiraBaseUrl",
"jiraProjectKey",
"gmailClientId",
"gmailClientSecret",
"gmailRefreshToken",
"gmailRecipients",
"lookbackHours",
"includeCompletedIssues",
"includeCarriedOverIssues",
"includeAssigneeBreakdown",
"includeMidSprintAdditions"
]

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The schema requires lookbackHours, but the Ballerina module currently defines it as a const (not configurable). This mismatch can cause configuration validation/runtime config loading issues in Choreo. Align the schema with actual configurable variables (either make lookbackHours configurable in code, or remove it from required/properties here).

Copilot uses AI. Check for mistakes.
Comment on lines +64 to +78
### Lookback Configuration
- `lookbackHours` - How far back to search for completed sprints (default: `24.0`)
- **Recommended:** Match your execution schedule (e.g., `24.0` for daily runs, `2.0` for every 2 hours)
- Examples: `2.0` (last 2 hours), `6.0` (last 6 hours), `24.0` (last 24 hours)
- Set this to slightly more than your execution frequency to avoid missing sprints
- The integration uses Jira labels to prevent duplicate emails even if sprints appear in multiple runs

### Scheduling
This integration is designed to run once per execution and exit. Schedule it using:
- **Cron jobs** (Linux/Mac): `0 */6 * * * /path/to/bal run` (every 6 hours)
- **Task Scheduler** (Windows): Create a scheduled task
- **Choreo Scheduled Tasks**: Configure execution frequency in Choreo
- **Kubernetes CronJob**: Deploy as a CronJob resource

**Important:** Set `lookbackHours` to slightly more than your execution frequency to ensure no sprints are missed.

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lookbackHours is documented as configurable with a default of 24.0, but the code currently hard-codes it as 1460.0 via a const. Please update the README to match the implemented configuration behavior (or, preferably, make lookbackHours configurable and keep the README as-is).

Suggested change
### Lookback Configuration
- `lookbackHours` - How far back to search for completed sprints (default: `24.0`)
- **Recommended:** Match your execution schedule (e.g., `24.0` for daily runs, `2.0` for every 2 hours)
- Examples: `2.0` (last 2 hours), `6.0` (last 6 hours), `24.0` (last 24 hours)
- Set this to slightly more than your execution frequency to avoid missing sprints
- The integration uses Jira labels to prevent duplicate emails even if sprints appear in multiple runs
### Scheduling
This integration is designed to run once per execution and exit. Schedule it using:
- **Cron jobs** (Linux/Mac): `0 */6 * * * /path/to/bal run` (every 6 hours)
- **Task Scheduler** (Windows): Create a scheduled task
- **Choreo Scheduled Tasks**: Configure execution frequency in Choreo
- **Kubernetes CronJob**: Deploy as a CronJob resource
**Important:** Set `lookbackHours` to slightly more than your execution frequency to ensure no sprints are missed.
### Lookback Window
- The integration uses a fixed lookback window of `1460.0` hours (approximately 60 days) to determine which completed sprints to process.
- This value is currently hard-coded in the implementation and is **not configurable** via deployment configuration.
- The wide window helps ensure that newly completed sprints are not missed even if the integration is run infrequently.
- The integration uses Jira labels to prevent duplicate emails even if sprints appear in multiple runs.
### Scheduling
This integration is designed to run once per execution and exit. Schedule it using:
This integration is designed to run once per execution and exit. Schedule it using:
- **Cron jobs** (Linux/Mac): `0 */6 * * * /path/to/bal run` (every 6 hours)
- **Task Scheduler** (Windows): Create a scheduled task
- **Choreo Scheduled Tasks**: Configure execution frequency in Choreo
- **Kubernetes CronJob**: Deploy as a CronJob resource
**Important:** The integration uses a fixed lookback window of `1460.0` hours (~60 days) to help ensure no sprints are missed, even if executions are not very frequent.

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +14
Send --> Poll

classDef startNode fill:#90EE90,stroke:#333,stroke-width:2px,color:#000
classDef processNode fill:#87CEEB,stroke:#333,stroke-width:2px,color:#000




Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The diagram shows an infinite loop (Send --> Poll), but the code is implemented as a scheduled task that runs once and exits. Update the diagram to match the actual control flow (e.g., external scheduler triggers each run) to avoid confusing deployers/operators.

Suggested change
Send --> Poll
classDef startNode fill:#90EE90,stroke:#333,stroke-width:2px,color:#000
classDef processNode fill:#87CEEB,stroke:#333,stroke-width:2px,color:#000
Send --> End([End]):::endNode
classDef startNode fill:#90EE90,stroke:#333,stroke-width:2px,color:#000
classDef processNode fill:#87CEEB,stroke:#333,stroke-width:2px,color:#000
classDef endNode fill:#F08080,stroke:#333,stroke-width:2px,color:#000

Copilot uses AI. Check for mistakes.
Comment thread ballerina-integrator/jira-sprint-summary-email/README.md Outdated
Comment thread ballerina-integrator/jira-sprint-summary-email/README.md Outdated
@hasathcharu

Copy link
Copy Markdown
Contributor

Prebuilt Integration Checklist

  • github/workflows/projects.json is updated with the new prebuilt integration path
  • Path in the .github/workflows/projects.json matches the project path exactly
  • Directory path is delimited with '-'
  • Directory path is identical to the integration name in Ballerina.toml
  • README.MD is correctly structured
  • Configurations are organized by records based on vendor
  • choreo/config-schema.json exists and is up to date
  • choreo/config-schema.json includes the requiredLevel key value pair at the root of the json
  • choreo/instructions.md is correctly structured
  • choreo/diagram.md is correctly written and parsable by Mermaid

Shall we fix these

Comment on lines +8 to +15
classDef startNode fill:#90EE90,stroke:#333,stroke-width:2px,color:#000
classDef processNode fill:#87CEEB,stroke:#333,stroke-width:2px,color:#000
classDef endNode fill:#F08080,stroke:#333,stroke-width:2px,color:#000





No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
classDef startNode fill:#90EE90,stroke:#333,stroke-width:2px,color:#000
classDef processNode fill:#87CEEB,stroke:#333,stroke-width:2px,color:#000
classDef endNode fill:#F08080,stroke:#333,stroke-width:2px,color:#000

We have defined these internally, hence no need to define at this level

Comment thread ballerina-integrator/jira-sprint-summary-email/.choreo/diagram.md Outdated
Comment on lines +2 to +7
Start([Start]):::startNode --> Poll[Poll Jira API<br/>closedSprints]:::processNode
Poll --> Detect[Detect New<br/>Completed Sprints]:::processNode
Detect --> Generate[Fetch Issues &<br/>Generate Summary]:::processNode
Generate --> Format[Format HTML<br/>Email]:::processNode
Format --> Send[Send via Gmail<br/>to Recipients]:::processNode
Send --> End([End]):::endNode

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ranvin36 and others added 2 commits March 25, 2026 16:00
Co-authored-by: Haritha Hasathcharu <42366642+hasathcharu@users.noreply.github.com>
Comment thread ballerina-integrator/jira-sprint-summary-email/.choreo/diagram.md Outdated
Co-authored-by: Haritha Hasathcharu <42366642+hasathcharu@users.noreply.github.com>
hasathcharu
hasathcharu previously approved these changes Mar 25, 2026
Comment on lines +65 to +66

if issueJson is map<json> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use optional field access instead of type narrowing one by one

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants