Implementation of the Pre-Built Integration To Automate Jira Summary Emails - #67
Implementation of the Pre-Built Integration To Automate Jira Summary Emails#67Ranvin36 wants to merge 18 commits into
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughPeriodic 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (12)
ballerina-integrator/jira_sprint_summary_email/.choreo/config-schema.jsonballerina-integrator/jira_sprint_summary_email/.choreo/diagram.mdballerina-integrator/jira_sprint_summary_email/.choreo/instructions.mdballerina-integrator/jira_sprint_summary_email/.gitignoreballerina-integrator/jira_sprint_summary_email/Ballerina.tomlballerina-integrator/jira_sprint_summary_email/README.mdballerina-integrator/jira_sprint_summary_email/config.balballerina-integrator/jira_sprint_summary_email/connections.balballerina-integrator/jira_sprint_summary_email/functions.balballerina-integrator/jira_sprint_summary_email/html_formatter.balballerina-integrator/jira_sprint_summary_email/main.balballerina-integrator/jira_sprint_summary_email/types.bal
| decimal thresholdSeconds = pollingIntervalSeconds * 2; | ||
|
|
||
| return timeDiff < thresholdSeconds; |
There was a problem hiding this comment.
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.
| 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(); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
git ls-files | grep -E "jira_sprint_summary_email" | head -20Repository: wso2/integration-samples
Length of output: 816
🏁 Script executed:
git ls-files | grep -E "\.bal$" | head -30Repository: 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.balRepository: wso2/integration-samples
Length of output: 208
🏁 Script executed:
sed -n '280,315p' ballerina-integrator/jira_sprint_summary_email/functions.balRepository: wso2/integration-samples
Length of output: 1547
🏁 Script executed:
sed -n '85,105p' ballerina-integrator/jira_sprint_summary_email/main.balRepository: wso2/integration-samples
Length of output: 887
🏁 Script executed:
grep -n "getSprintAddedDate" ballerina-integrator/jira_sprint_summary_email/functions.bal | head -10Repository: wso2/integration-samples
Length of output: 263
🏁 Script executed:
sed -n '1,50p' ballerina-integrator/jira_sprint_summary_email/functions.balRepository: wso2/integration-samples
Length of output: 2232
🏁 Script executed:
sed -n '344,380p' ballerina-integrator/jira_sprint_summary_email/functions.balRepository: wso2/integration-samples
Length of output: 1034
🏁 Script executed:
sed -n '344,420p' ballerina-integrator/jira_sprint_summary_email/functions.balRepository: wso2/integration-samples
Length of output: 2553
🏁 Script executed:
sed -n '90,100p' ballerina-integrator/jira_sprint_summary_email/main.balRepository: 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).
| ${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> |
There was a problem hiding this comment.
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}`); |
There was a problem hiding this comment.
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.
| 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 | ||
| ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "main.bal" -path "*/jira_sprint_summary_email/*" -type fRepository: wso2/integration-samples
Length of output: 125
🏁 Script executed:
wc -l ballerina-integrator/jira_sprint_summary_email/main.balRepository: wso2/integration-samples
Length of output: 127
🏁 Script executed:
sed -n '40,60p' ballerina-integrator/jira_sprint_summary_email/main.balRepository: wso2/integration-samples
Length of output: 860
🏁 Script executed:
sed -n '80,100p' ballerina-integrator/jira_sprint_summary_email/main.balRepository: wso2/integration-samples
Length of output: 800
🏁 Script executed:
sed -n '1,30p' ballerina-integrator/jira_sprint_summary_email/main.balRepository: wso2/integration-samples
Length of output: 1348
🏁 Script executed:
grep -n "import\|jira:" ballerina-integrator/jira_sprint_summary_email/main.bal | head -20Repository: 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(whentrue, 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.) plusnextPageToken=<token>(treat the token as opaque). The endpoint supportsnextPageTokenas 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": trueAtlassian also notes that pagination for this endpoint relies on nextPageToken (instead of the old startAt). [2]
Sources
- Atlassian Jira Cloud REST API v3 – Issue search (“Search for issues using JQL enhanced search (GET)”) [1]
- 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:
- 1: https://mrrefactoring.github.io/jira.js/interfaces/Version2Models.SearchAndReconcileResults.html
- 2: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-search/?utm_source=openai
- 3: https://glama.ai/mcp/servers/%40brukhabtu/jira-mcp/blob/081c6b8fc0a8f74418cd48e67dba21c1ea6d362d/jira_mcp/jira_openapi_spec.json?utm_source=openai
🌐 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 isnull/absent. [1]isLast: boolean—truewhen the current page is the last page;falsemeans more pages may be available (and you should usenextPageTokento 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:
- 1: https://central.ballerina.io/ballerinax/jira/latest?utm_source=openai
- 2: https://developer.atlassian.com/cloud/jira/platform/search-and-reconcile/?utm_source=openai
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.
| ``` | ||
| ┌─────────────────────────────────────────────────────────────────┐ | ||
| │ 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 │ | ||
| │ 📧 📧 📧 │ | ||
| └───────────────────────┘ | ||
| ``` |
There was a problem hiding this comment.
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.
|
|
||
| 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 | ||
| ``` |
There was a problem hiding this comment.
| 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 | |
| ``` |
| ```mermaid | ||
| graph TD |
There was a problem hiding this comment.
| ```mermaid | |
| graph TD |
| ## Architecture | ||
|
|
||
| ``` | ||
| ┌─────────────────────────────────────────────────────────────────┐ | ||
| │ Jira Sprint Summary Email │ | ||
| │ Integration │ | ||
| └─────────────────────────────────────────────────────────────────┘ | ||
| │ | ||
| │ Polls every 5 min (configurable) | ||
| ▼ | ||
| ┌───────────────────────┐ | ||
| │ Jira Cloud API │ | ||
| │ (REST API v3) │ | ||
| └───────────────────────┘ | ||
| │ |
There was a problem hiding this comment.
This section is not needed
|
|
||
|   | ||
|
|
||
| ## Overview |
There was a problem hiding this comment.
Please refer to salesforce ops to google sheets integration and follow the same structure
| - 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 |
There was a problem hiding this comment.
let's limit this to 5 bullet points maximum
| 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 |
There was a problem hiding this comment.
Let's simplify this flow chart. let's have maximum of 5-6 nodes
| io:println(""); | ||
|
|
||
| // Test Jira connection | ||
| io:println("Testing Jira connection..."); |
There was a problem hiding this comment.
Use ballerina/log package
|
Prebuilt Integration Checklist
|
Co-authored-by: Haritha Hasathcharu <42366642+hasathcharu@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (3)
ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json (1)
158-244:⚠️ Potential issue | 🔴 CriticalSchema-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 includeIncompleteIssuesincludeCarriedOverIssuesName mismatch includeVelocityNot 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 | 🟠 MajorPagination not implemented - results truncated at 100 issues.
The JQL search stops at
maxResults = 100without usingnextPageTokenfor subsequent pages. Projects with more than 100 issues matchingsprint in closedSprints()will have incomplete sprint discovery.The
SearchAndReconcileResultstype includesnextPageTokenandisLastfields 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 | 🟠 MajorMulti-sprint handling still returns only the last sprint.
When
sprintValueis an array (lines 55-62), extracting onlysprintValue[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: UseparseJiraDateTimefor consistency.Line 270 uses
time:utcFromStringdirectly, but Jira datetime fields may have non-standard timezone offsets (e.g.,+0000without colon). TheparseJiraDateTimefunction infunctions.balhandles 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
📒 Files selected for processing (15)
ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.jsonballerina-integrator/jira-sprint-summary-email/.choreo/diagram.mdballerina-integrator/jira-sprint-summary-email/.choreo/instructions.mdballerina-integrator/jira-sprint-summary-email/.gitignoreballerina-integrator/jira-sprint-summary-email/Ballerina.tomlballerina-integrator/jira-sprint-summary-email/Dependencies.tomlballerina-integrator/jira-sprint-summary-email/README.mdballerina-integrator/jira-sprint-summary-email/config.balballerina-integrator/jira-sprint-summary-email/connections.balballerina-integrator/jira-sprint-summary-email/functions.balballerina-integrator/jira-sprint-summary-email/html_formatter.balballerina-integrator/jira-sprint-summary-email/main.balballerina-integrator/jira-sprint-summary-email/persistence.balballerina-integrator/jira-sprint-summary-email/processed_sprints.jsonballerina-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
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json (1)
176-244:⚠️ Potential issue | 🔴 CriticalSchema 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
📒 Files selected for processing (3)
ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.jsonballerina-integrator/jira-sprint-summary-email/.choreo/instructions.mdballerina-integrator/jira-sprint-summary-email/README.md
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json (1)
1-4:⚠️ Potential issue | 🟠 MajorMissing root
requiredLevelmetadata 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
📒 Files selected for processing (4)
ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.jsonballerina-integrator/jira-sprint-summary-email/.choreo/instructions.mdballerina-integrator/jira-sprint-summary-email/README.mdballerina-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
There was a problem hiding this comment.
♻️ Duplicate comments (2)
ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.json (2)
91-94:⚠️ Potential issue | 🟠 MajorMissing
requiredarray in thewso2object.The
wso2object 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 | 🟠 MajorMissing
requiredLevelmetadata key and root-levelrequiredarray.The Choreo prebuilt integration checklist requires a
requiredLevelkey 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 addingminItems: 1forgmailRecipients.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
📒 Files selected for processing (2)
ballerina-integrator/jira-sprint-summary-email/.choreo/config-schema.jsonballerina-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
There was a problem hiding this comment.
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"], |
There was a problem hiding this comment.
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.
| fields = ["*all"], | |
| fields = ["sprint", "updated"], |
| // 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 | ||
| ); |
There was a problem hiding this comment.
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.
| } | ||
| }; | ||
|
|
||
| final string jiraApiBaseUrl = jiraBaseUrl.endsWith("/rest") ? jiraBaseUrl : jiraBaseUrl + "/rest"; |
There was a problem hiding this comment.
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.
| 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; |
| 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 `'`; |
There was a problem hiding this comment.
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.
| - 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. | ||
|
|
There was a problem hiding this comment.
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).
| 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 ?: []; | ||
|
|
There was a problem hiding this comment.
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.
| 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; | |
| } |
| // Lookback Configuration (permanent settings) | ||
| const decimal lookbackHours = 1460.0; // ~2 months (61 days) |
There was a problem hiding this comment.
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).
| // Lookback Configuration (permanent settings) | |
| const decimal lookbackHours = 1460.0; // ~2 months (61 days) | |
| // Lookback Configuration | |
| configurable decimal lookbackHours = 1460.0; // ~2 months (61 days) |
| "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" | ||
| ] |
There was a problem hiding this comment.
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).
| ### 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. |
There was a problem hiding this comment.
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).
| ### 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. |
| Send --> Poll | ||
|
|
||
| classDef startNode fill:#90EE90,stroke:#333,stroke-width:2px,color:#000 | ||
| classDef processNode fill:#87CEEB,stroke:#333,stroke-width:2px,color:#000 | ||
|
|
||
|
|
||
|
|
||
|
|
There was a problem hiding this comment.
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.
| 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 |
Shall we fix these |
| 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 |
There was a problem hiding this comment.
| 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
| 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 |
There was a problem hiding this comment.
Co-authored-by: Haritha Hasathcharu <42366642+hasathcharu@users.noreply.github.com>
Co-authored-by: Haritha Hasathcharu <42366642+hasathcharu@users.noreply.github.com>
|
|
||
| if issueJson is map<json> { |
There was a problem hiding this comment.
Use optional field access instead of type narrowing one by one
Purpose
$title
Task : #67
Summary by CodeRabbit