Skip to content

Add --expand-links flag to fetch linked Confluence pages inline - #8

Open
calin-marian wants to merge 4 commits into
ainoya:mainfrom
dreamproduction:feat/expand-links
Open

Add --expand-links flag to fetch linked Confluence pages inline#8
calin-marian wants to merge 4 commits into
ainoya:mainfrom
dreamproduction:feat/expand-links

Conversation

@calin-marian

Copy link
Copy Markdown

Summary

  • Add --expand-links[=N] flag to jira issue that extracts URLs from the ADF description and recursively fetches linked Confluence pages inline
  • Supports configurable depth and deduplication of visited pages
  • Add URL extraction utilities for both ADF nodes and HTML content

Depends on #7.

Test plan

  • Verify jira issue <key> --expand-links fetches and displays linked Confluence pages after the issue
  • Verify --expand-links=2 recurses into child page links
  • Verify pages are not fetched twice (deduplication)
  • Verify behavior without the flag is unchanged

🤖 Generated with Claude Code

Rewrite HTML tag stripper to preserve links (as "text (url)") and images
(as "[image: url]") instead of stripping all tags. Add Atlassian Document
Format (ADF) renderer for Jira Cloud descriptions, handling text with link
marks, inlineCard/embedCard smart links, mentions, hard breaks, and media
nodes. Fix formatJiraIssue to handle ADF object descriptions in addition
to plain string descriptions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the jira issue command by adding the capability to automatically fetch and display content from linked Confluence pages. This integration provides a more comprehensive view of related documentation directly within the Jira issue context, streamlining information access and improving the overall user experience by reducing the need to navigate between different applications.

Highlights

  • New --expand-links flag: Introduced a new --expand-links[=N] flag for the jira issue command, allowing users to fetch and display linked Confluence pages inline with configurable recursion depth.
  • Recursive Confluence Page Expansion: Implemented functionality to recursively fetch and display content from Confluence pages linked within a Jira issue's description, including deduplication of already visited pages.
  • Enhanced Content Parsing: Developed new utility functions for robust URL extraction from both Atlassian Document Format (ADF) and HTML content, and improved HTML stripping to preserve and format links and images.
  • ADF Description Support: Updated the Jira issue formatter to correctly process and render descriptions provided in Atlassian Document Format (ADF), which is common in Jira Cloud.
Changelog
  • src/formatter.zig
    • Refactored stripHtmlTags to intelligently preserve and format links and images, appending URLs for <a> tags and indicating <img> sources.
    • Added extractHtmlAttr helper function to parse attribute values from HTML tags.
    • Introduced writeAdfText for iteratively traversing and converting Atlassian Document Format (ADF) JSON structures into readable plain text, handling various node types like links, mentions, and media.
    • Modified formatJiraIssue to support Jira issue descriptions in both plain string and ADF object formats, utilizing the new writeAdfText function for ADF content.
    • Added extractAdfUrls to programmatically extract all URLs from an ADF node, covering links and embedded cards.
    • Implemented extractHtmlUrls to parse href attributes from <a> tags within HTML content.
    • Created extractConfluencePageId to reliably extract Confluence page IDs from given URLs.
  • src/main.zig
    • Updated the help message for the jira issue command to include the new --expand-links flag.
    • Added parseExpandLinksDepth to parse the --expand-links flag, determining the recursion depth for Confluence page expansion.
    • Implemented expandConfluenceLink, a recursive function responsible for fetching, formatting, and displaying linked Confluence pages, incorporating a mechanism for deduplication of visited pages.
    • Modified handleJiraCommand to accept the base_url argument, necessary for constructing Confluence client requests.
    • Updated the usage string for jira issue to reflect the new --expand-links option.
    • Integrated the Confluence page expansion logic into the jira issue command, utilizing the new URL extraction utilities from formatter.zig to identify and process linked pages.
    • Adjusted the main function to pass the base_url to handleJiraCommand.
Activity
  • No human activity has been recorded for this pull request since its creation.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request adds a great new feature for expanding Confluence links within Jira issues. The implementation is comprehensive, covering ADF and HTML parsing, recursive fetching with depth limiting, and deduplication.

My review has identified a few areas for improvement. The most critical issue is a recurring incorrect usage of std.ArrayList.append, which passes an extra allocator argument. This will likely cause compilation errors with current Zig versions. I've left several comments pointing out where this occurs.

Additionally, I've noted that in stripHtmlTags, potential allocation errors are silently ignored, which could lead to truncated output. In expandConfluenceLink, network and parsing errors are also handled silently, which could make debugging difficult for users. Improving the error handling in these areas would make the new feature more robust.

Finally, I've included a minor suggestion to improve code readability in one of the new utility functions.

Overall, this is a solid contribution. Addressing these points will ensure the new functionality is both correct and reliable.

Comment thread src/formatter.zig
Comment on lines +108 to +118
if (std.mem.eql(u8, entity, "nbsp")) {
result.append(allocator, ' ') catch {};
} else if (std.mem.eql(u8, entity, "lt")) {
result.append(allocator, '<') catch {};
} else if (std.mem.eql(u8, entity, "gt")) {
result.append(allocator, '>') catch {};
} else if (std.mem.eql(u8, entity, "amp")) {
result.append(allocator, '&') catch {};
} else if (std.mem.eql(u8, entity, "quot")) {
result.append(allocator, '"') catch {};
} else {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

There are a couple of issues with how result.append is being used in this function:

  1. The append method for std.ArrayList does not take an allocator as its first argument. The list already knows about its allocator from initialization. The calls should be in the form result.append(item).
  2. Using catch {} silences potential allocation errors. Since this function can return an error (![]u8), it's better to use try result.append(...) to propagate any memory allocation failures. This will prevent silent truncation of the output.

These issues are present on lines 92, 109, 111, 113, 115, 117, and 126 within this function.

                if (std.mem.eql(u8, entity, "nbsp")) {
                    try result.append(' ');
                } else if (std.mem.eql(u8, entity, "lt")) {
                    try result.append('<');
                } else if (std.mem.eql(u8, entity, "gt")) {
                    try result.append('>');
                } else if (std.mem.eql(u8, entity, "amp")) {
                    try result.append('&');
                } else if (std.mem.eql(u8, entity, "quot")) {
                    try result.append('"');
                } else {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This is a false positive. Zig 0.15 uses an unmanaged ArrayList API where the allocator is passed per-call rather than stored in the struct. result.append(allocator, item) is the correct signature. See the Zig 0.15 std.ArrayList docs.

However, the related point about catch {} silently swallowing errors is valid — fixed in the base branch (1e12dd1), these now use try to propagate allocation failures.

Comment thread src/formatter.zig
if (attrs == .object) {
if (attrs.object.get("url")) |url| {
if (url == .string) {
try urls.append(allocator, try allocator.dupe(u8, url.string));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

The append method for std.ArrayList does not take an allocator as its first argument. The list already knows about its allocator from initialization. The call should be try urls.append(...).

                                try urls.append(try allocator.dupe(u8, url.string));

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Same as above — append(allocator, item) is the correct Zig 0.15 unmanaged ArrayList API. No change needed.

Comment thread src/formatter.zig
if (attrs == .object) {
if (attrs.object.get("href")) |href| {
if (href == .string) {
try urls.append(allocator, try allocator.dupe(u8, href.string));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

The append method for std.ArrayList does not take an allocator as its first argument. The list already knows about its allocator from initialization. The call should be try urls.append(...).

                                            try urls.append(try allocator.dupe(u8, href.string));

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Same as above — append(allocator, item) is the correct Zig 0.15 unmanaged ArrayList API. No change needed.

Comment thread src/formatter.zig
(trimmed.len == 1 or trimmed[1] == ' ' or trimmed[1] == '\t' or trimmed[1] == '\n'))
{
if (extractHtmlAttr(tag_inner, "href")) |href| {
try urls.append(allocator, try allocator.dupe(u8, href));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

The append method for std.ArrayList does not take an allocator as its first argument. The list already knows about its allocator from initialization. The call should be try urls.append(...).

                    try urls.append(try allocator.dupe(u8, href));

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Same as above — append(allocator, item) is the correct Zig 0.15 unmanaged ArrayList API. No change needed.

Comment thread src/main.zig
Comment on lines +178 to +181
visited.append(allocator, owned_id) catch {
allocator.free(owned_id);
return;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

The append method for std.ArrayList does not take an allocator as its first argument. The list already knows about its allocator from initialization. The call should be visited.append(owned_id).

    visited.append(owned_id) catch {
        allocator.free(owned_id);
        return;
    };

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Same Zig 0.15 API — visited.append(allocator, owned_id) is correct. No change needed.

Comment thread src/main.zig Outdated
};

// Fetch and display
const page_response = confluence.getPage(page_id) catch return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

This function uses catch return to handle errors, which means failures will be silent. This can be confusing for the user when a link fails to expand. It would be more user-friendly to log an error message to stderr before returning, so the user knows why an operation failed. This applies to all catch return and if...else return error handling patterns in this function.

    const page_response = confluence.getPage(page_id) catch |err| {
        std.debug.print("Error: Failed to fetch Confluence page ID {s}: {any}\n", .{page_id, err});
        return;
    };

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 9d86c3f. expandConfluenceLink now logs warnings with the page ID and error before returning on fetch or format failures.

Comment thread src/formatter.zig Outdated
const pos = std.mem.indexOf(u8, url, marker) orelse return null;
const start = pos + marker.len;
var end = start;
while (end < url.len and url[end] >= '0' and url[end] <= '9') : (end += 1) {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

For better readability and to make the intent clearer, you could use std.ascii.isDigit to check if a character is a digit.

    while (end < url.len and std.ascii.isDigit(url[end])) : (end += 1) {}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 9d86c3f. Now uses std.ascii.isDigit.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 490f1173b6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/formatter.zig Outdated
if (content_val == .array and depth < max_stack) {
stack[depth] = .{ .items = content_val.array.items, .idx = 0 };
depth += 1;
continue; // process children before emitting newline

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Emit newlines for block ADF nodes after visiting children

The unconditional continue after pushing content onto the stack skips the block-node newline logic for any node that has children, which is the common shape for paragraph, heading, and list nodes in Jira ADF. As a result, multi-paragraph descriptions are flattened into a single run-on line (e.g., two paragraphs render as FirstSecond), making issue output significantly less readable whenever the description is an ADF object.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in the base branch (1e12dd1, rebased into this PR). The DFS stack now tracks is_block per frame and emits newlines when block-node frames are exhausted.

calin-marian and others added 3 commits March 16, 2026 14:49
Fix off-by-one in extractHtmlAttr loop bound and use std.ascii.isWhitespace
for consistent whitespace checks. Propagate allocation errors in
stripHtmlTags instead of silently swallowing them. Fix writeAdfText to emit
newlines after block-level nodes by tracking is_block in the DFS stack,
so paragraphs and headings are properly separated. Add [image] fallback
for external media nodes missing a URL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When viewing a Jira issue with --expand-links[=N], extract URLs from the
ADF description and fetch the linked Confluence pages inline. Supports
recursive depth and deduplication of visited pages. Add URL extraction
utilities for both ADF and HTML content.
Use std.ascii.isDigit for readability in extractConfluencePageId. Log
warnings when Confluence page fetch or format fails instead of silently
returning.
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.

1 participant