Skip to content

Conversation

@carlobeltrame
Copy link
Contributor

@carlobeltrame carlobeltrame commented Jun 18, 2025

This is a first attempt to fix #295.
@Codex- please let me know whether this implementation matches how you imagined the solution. I will manually test it with our setup. If you want, I could also write unit tests for the etag file, but the logic should already be tested in the added api test.

Summary by CodeRabbit

  • New Features

    • Introduced ETag-based caching for API requests to improve performance and reduce redundant network calls.
  • Tests

    • Enhanced test coverage to verify correct handling and caching of ETag headers in API responses.
  • Chores

    • Added a new development dependency for improved type support.

@coderabbitai
Copy link

coderabbitai bot commented Jun 18, 2025

Walkthrough

The changes introduce ETag-based HTTP caching for GitHub API requests in the codebase. A new utility module manages ETag storage and conditional requests, and existing API calls are refactored to use this mechanism. Comprehensive tests are added to verify ETag handling, header usage, and cache behavior across repeated requests.

Changes

File(s) Change Summary
src/etags.ts New module implementing ETag caching and conditional request logic with exported withEtag and clearEtags functions.
src/api.ts Refactored API calls to use the withEtag utility for ETag-aware requests to GitHub endpoints.
src/api.spec.ts Enhanced and extended tests to verify ETag header handling, cache behavior, and correct header usage.
package.json Added @octokit/types as a new development dependency.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant API (fetchWorkflowRunIds/fetchWorkflowRunJobSteps)
    participant withEtag
    participant Octokit

    Caller->>API: Call fetchWorkflowRunIds/JobSteps(params)
    API->>withEtag: withEtag(endpoint, params, requester)
    withEtag->>withEtag: Check ETag cache for (endpoint, params)
    alt ETag exists
        withEtag->>Octokit: Make request with If-None-Match: ETag
    else No ETag
        withEtag->>Octokit: Make request without If-None-Match
    end
    Octokit-->>withEtag: Respond (may be 304 Not Modified or new data with ETag)
    alt 304 Not Modified
        withEtag->>withEtag: Return cached response
    else New or changed data
        withEtag->>withEtag: Update cache with new ETag and response
        withEtag-->>API: Return new response
    end
    withEtag-->>API: Return response
    API-->>Caller: Return data
Loading

Assessment against linked issues

Objective Addressed Explanation
Implement conditional API requests using ETag caching for listWorkflowRuns and listJobsForWorkflowRun (#295)
Ensure ETag is stored, reused, and If-None-Match header is sent on repeated requests (#295)
Provide tests verifying ETag behavior, cache usage, and header handling (#295)

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes found.

Poem

In the warren where the code does run,
A clever cache for ETags is spun.
Now with headers and clever store,
API calls are fewer than before!
The rabbit hops with joy anew—
Rate limits shrink, as all code grew.
🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 575a3cc and e14223d.

📒 Files selected for processing (1)
  • src/etags.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/etags.ts
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze
✨ Finishing Touches
  • 📝 Generate Docstrings

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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@carlobeltrame carlobeltrame force-pushed the conditional-api-requests branch from fc3223c to 7c798e9 Compare June 18, 2025 21:50
@carlobeltrame carlobeltrame marked this pull request as ready for review June 18, 2025 21:50
@codecov-commenter
Copy link

codecov-commenter commented Jun 18, 2025

Codecov Report

Attention: Patch coverage is 98.76543% with 1 line in your changes missing coverage. Please review.

Project coverage is 97.04%. Comparing base (5210b85) to head (e14223d).

Files with missing lines Patch % Lines
src/etags.ts 97.87% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #296      +/-   ##
==========================================
+ Coverage   96.91%   97.04%   +0.13%     
==========================================
  Files           6        7       +1     
  Lines         583      643      +60     
  Branches      112      124      +12     
==========================================
+ Hits          565      624      +59     
- Misses         17       18       +1     
  Partials        1        1              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/etags.ts (2)

48-50: Consider cache key generation edge cases.

Using JSON.stringify({ endpoint, params }) for cache keys could lead to issues if params contains functions, circular references, or objects with non-deterministic property ordering.

Consider a more stable cache key generation strategy:

-function getEtag(endpoint: string, params: object): EtagStoreEntry | undefined {
-  return etagStore.get(JSON.stringify({ endpoint, params }));
-}
+function getEtag(endpoint: string, params: object): EtagStoreEntry | undefined {
+  // Create a stable key by sorting object properties
+  const stableKey = JSON.stringify({ endpoint, params }, Object.keys({ endpoint, params }).sort());
+  return etagStore.get(stableKey);
+}

12-12: Consider implementing cache eviction strategy.

The global etagStore Map could grow indefinitely as it never removes entries. For a long-running application, this could lead to memory issues.

Consider adding cache size limits or TTL-based eviction:

+const MAX_CACHE_SIZE = 1000;
+
 const etagStore = new Map<string, EtagStoreEntry>();

Then implement LRU eviction in the rememberEtag function or consider using a library like lru-cache.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5210b85 and 7c798e9.

⛔ Files ignored due to path filters (2)
  • dist/index.mjs is excluded by !**/dist/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • package.json (1 hunks)
  • src/api.spec.ts (23 hunks)
  • src/api.ts (3 hunks)
  • src/etags.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/api.ts (1)
src/etags.ts (1)
  • withEtag (14-41)
🔇 Additional comments (10)
package.json (1)

37-37: LGTM - Appropriate dependency addition.

The @octokit/types dependency correctly supports the TypeScript types used in the new ETag handling functionality.

src/api.ts (3)

5-5: LGTM - Clean ETag integration.

The import and usage of withEtag is well-implemented and maintains backward compatibility.


173-191: ETag caching implementation looks good.

The withEtag wrapper cleanly integrates ETag functionality while preserving the original API logic and error handling.


231-243: Verify caching assumption for fetchWorkflowRunJobSteps.

The ETag caching assumes that the same endpoint with same parameters always returns the same data. As mentioned in the PR objectives, there's uncertainty about whether this assumption holds for fetchWorkflowRunJobSteps.

Consider whether additional request context (like timestamp or run status) should be included in the cache key for this endpoint, since workflow run job steps could potentially change even with the same run_id parameter.

#!/bin/bash
# Search for any documentation or comments about whether workflow run job steps can change
rg -A 10 -B 5 "listJobsForWorkflowRun|job.*step.*change|workflow.*run.*mutable"
src/api.spec.ts (6)

23-23: Good addition for test cleanup.

The clearEtags import enables proper test isolation by resetting ETag cache between tests.


71-73: Excellent test isolation practice.

The afterEach hook properly clears the ETag cache between tests, preventing test interference.


33-33: Good mock interface extension.

Adding the headers property to MockResponse properly supports ETag testing while maintaining consistency with the existing mock structure.


582-636: Comprehensive ETag caching test.

This test thoroughly verifies the ETag functionality:

  • First request gets fresh data with ETag
  • Second request with same parameters sends If-None-Match header
  • 304 response handling works correctly
  • Proper logging verification

638-692: Good test for ETag isolation.

This test correctly verifies that ETags are scoped to request parameters, ensuring different parameters don't incorrectly share cached ETags.


810-866: Excellent test coverage for fetchWorkflowRunJobSteps.

Both tests (ETag reuse and parameter isolation) provide comprehensive coverage of the ETag functionality for the job steps endpoint, mirroring the workflow runs tests appropriately.

Also applies to: 868-924

@Codex-
Copy link
Owner

Codex- commented Jun 19, 2025

Awesome, will review when I can 🫡

@carlobeltrame carlobeltrame force-pushed the conditional-api-requests branch from 7c798e9 to b8c1da8 Compare June 19, 2025 08:36
@carlobeltrame carlobeltrame force-pushed the conditional-api-requests branch from b8c1da8 to 8401a12 Compare June 19, 2025 08:50
@carlobeltrame carlobeltrame requested a review from Codex- June 20, 2025 09:15
src/etags.ts Outdated
paramsWithEtag.headers = {
"If-None-Match": etag,
...(params.headers ?? {}),
} as RequestHeaders;
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
} as RequestHeaders;
} satisfies RequestHeaders;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wow I didn't know that one. Fixed in e14223d

Copy link
Owner

Choose a reason for hiding this comment

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

Very handy! Another great use case when writing tests and such is doing something like {} satisfies Partial<Foo> as <Foo> which lets you correctly type the keys you want to mock and then satisfy the interface required with the final cast.

@Codex-
Copy link
Owner

Codex- commented Jun 25, 2025

I think overall this looks really good, I do wonder about usages when there are long-running things, but can cross that bridge if it becomes an issue

@Codex- Codex- merged commit c721d73 into Codex-:main Jun 25, 2025
5 of 6 checks passed
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.

Use conditional API requests

3 participants