Skip to content

Conversation

@dtczelo
Copy link
Collaborator

@dtczelo dtczelo commented Sep 23, 2025

Summary by CodeRabbit

  • New Features
    • Added “Copy as Markdown” export for reports, generating a structured summary with legend, team sections, repositories, and status groupings (with emojis).
    • One-click clipboard copy with a brief visual confirmation.
  • Style
    • Introduced new icons and added a Markdown export button alongside existing controls.
    • Minor layout adjustments to accommodate the new export action.

@vercel
Copy link

vercel bot commented Sep 23, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
gnolove Ready Ready Preview Comment Sep 24, 2025 8:06am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 23, 2025

Walkthrough

Adds a Markdown export workflow to the report client page: introduces Markdown generation utilities, status/emoji mappings, clipboard copy logic with UI feedback, updates types for status/repo mapping, and integrates a “Copy as markdown” button in the UI.

Changes

Cohort / File(s) Summary of Changes
Report client page
src/components/features/report/report-client-page.tsx
Added Markdown generation (generateMarkdownReport), status/emoji mappings, STATUS_ORDER typing update, refined Status/Repo types, clipboard copy handler with UI state (copied), and a “Copy as markdown” button using CheckIcon/CopyIcon.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant UI as ReportClientPage UI
  participant MD as Markdown Generator
  participant CB as Clipboard API

  User->>UI: Click "Copy as markdown"
  UI->>MD: generateMarkdownReport(startDate, endDate, selectedTeams, teamRepoStatusMap)
  MD-->>UI: Markdown string
  UI->>CB: navigator.clipboard.writeText(markdown)
  CB-->>UI: Promise resolved
  UI->>UI: Set copied=true (brief visual cue)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • MikaelVallenet

Poem

Thump-thump, I tap with tidy paws,
Stitching bytes to markdown laws.
A hop, a copy—clipboard gleams,
Emojis judge our PR dreams.
Teams and statuses, lined in rows—
Carrot-checked, the summary glows. 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat: copy markdown generated reports from report page" accurately and concisely describes the primary change — adding a Copy-as-Markdown action and clipboard export on the report page; the PR's changes (generateMarkdownReport, handleCopyMarkdown, UI button and icons) directly implement that feature. It is specific and clear enough that a teammate scanning history will understand the main purpose of the change.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/add-markdown-copy-report-page

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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

❤️ Share

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

Copy link
Contributor

@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 (1)
src/components/features/report/report-client-page.tsx (1)

80-127: Markdown generation logic is comprehensive and well-structured.

The function properly generates a structured markdown report with:

  • Clear header with date range
  • Legend section for review decision emojis
  • Team-based organization with repository grouping
  • Proper sorting (gnolang repos first, then alphabetical)
  • Status-based PR listings with proper formatting

Minor improvement suggestion for better readability:

-          md += `    - **${pr.title}**  `;
-          md += `([#${pr.number}](${pr.url})) by @${pr.authorLogin}`;
-          if (pr.reviewDecision) {
-            md += ` ${reviewDecisionToEmoji[(pr.reviewDecision as keyof typeof reviewDecisionToEmoji)] || ''}`;
-          }
-          md += `\n \n`;
+          const reviewEmoji = pr.reviewDecision ? 
+            reviewDecisionToEmoji[(pr.reviewDecision as keyof typeof reviewDecisionToEmoji)] || '' : 
+            '';
+          md += `    - **${pr.title}** ([#${pr.number}](${pr.url})) by @${pr.authorLogin}${reviewEmoji ? ` ${reviewEmoji}` : ''}\n\n`;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f9d0941 and ad6ac76.

📒 Files selected for processing (1)
  • src/components/features/report/report-client-page.tsx (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/components/features/report/report-client-page.tsx (1)
src/utils/schemas.ts (1)
  • TPullRequest (131-131)
🔇 Additional comments (6)
src/components/features/report/report-client-page.tsx (6)

10-11: LGTM! Imported icons support the markdown functionality.

The CheckIcon and CopyIcon imports are properly added to support the visual feedback in the markdown copy button.


43-59: Type definitions look well-structured.

The type definitions provide good structure for organizing the data:

  • RepoStatusMap properly constrains status keys to the defined union
  • TeamRepoStatusMap includes both the data map and a foundAny flag
  • RepoStatusArray provides a clean tuple type for the sorted repo processing

65-78: Status and review decision emoji mappings are well-chosen.

The emoji mappings provide good visual representations:

  • Status emojis are intuitive (🕒 for waiting, 🚧 for in progress, etc.)
  • Review decision emojis follow a clear color pattern (🟢 approved, 🟠 changes requested, 🔵 review required)
  • Proper handling of empty review decisions

165-165: State management for copy feedback is appropriate.

The copied state provides good user feedback for the clipboard operation.


215-236: Explicit typing improves code clarity.

The explicit TeamRepoStatusMap typing in the useMemo makes the code more maintainable and provides better type safety.


281-284: UI implementation for markdown button is clean.

The button provides clear visual feedback with appropriate icons and text. The conditional rendering of CheckIcon vs CopyIcon effectively communicates the operation status.

Comment on lines +238 to +247
const handleCopyMarkdown = async () => {
const md = generateMarkdownReport(startDate, endDate, selectedTeams, teamRepoStatusMap);
try {
await navigator.clipboard.writeText(md);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (e) {
setCopied(false);
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Clipboard implementation needs browser compatibility improvements.

The current implementation only uses the modern navigator.clipboard.writeText() API, which requires HTTPS/secure contexts and may not work in all browsers or deployment scenarios.

Consider implementing a fallback mechanism for better browser compatibility:

 const handleCopyMarkdown = async () => {
   const md = generateMarkdownReport(startDate, endDate, selectedTeams, teamRepoStatusMap);
   try {
-    await navigator.clipboard.writeText(md);
-    setCopied(true);
-    setTimeout(() => setCopied(false), 2000);
+    // Try modern clipboard API first
+    if (navigator.clipboard && window.isSecureContext) {
+      await navigator.clipboard.writeText(md);
+      setCopied(true);
+      setTimeout(() => setCopied(false), 2000);
+    } else {
+      // Fallback for insecure contexts or unsupported browsers
+      const textArea = document.createElement('textarea');
+      textArea.value = md;
+      textArea.style.position = 'absolute';
+      textArea.style.left = '-999999px';
+      document.body.appendChild(textArea);
+      textArea.select();
+      document.execCommand('copy');
+      document.body.removeChild(textArea);
+      setCopied(true);
+      setTimeout(() => setCopied(false), 2000);
+    }
   } catch (e) {
+    console.warn('Clipboard operation failed:', e);
     setCopied(false);
   }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleCopyMarkdown = async () => {
const md = generateMarkdownReport(startDate, endDate, selectedTeams, teamRepoStatusMap);
try {
await navigator.clipboard.writeText(md);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (e) {
setCopied(false);
}
};
const handleCopyMarkdown = async () => {
const md = generateMarkdownReport(startDate, endDate, selectedTeams, teamRepoStatusMap);
try {
// Try modern clipboard API first
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(md);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} else {
// Fallback for insecure contexts or unsupported browsers
const textArea = document.createElement('textarea');
textArea.value = md;
textArea.style.position = 'absolute';
textArea.style.left = '-999999px';
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
} catch (e) {
console.warn('Clipboard operation failed:', e);
setCopied(false);
}
};
🤖 Prompt for AI Agents
In src/components/features/report/report-client-page.tsx around lines 238 to
247, the clipboard write currently only uses navigator.clipboard.writeText which
fails in non-secure or older browsers; add feature detection and a fallback: try
navigator.clipboard.writeText(md) inside a try/catch, and if it throws or
navigator.clipboard is unavailable fall back to creating a hidden textarea, set
its value to md, append it to document.body, select its contents (using
range/selectAll if needed), call document.execCommand('copy'), then remove the
textarea; ensure setCopied(true)/setCopied(false) are set based on the success
of either method and keep the existing timeout for clearing the copied state,
and catch/log errors for debugging.

Copy link
Contributor

@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: 0

🧹 Nitpick comments (7)
src/components/features/report/report-client-page.tsx (7)

43-47: De-duplicate status key unions; reuse the Status type.

The inline union is repeated in two places. Prefer referencing Status to avoid drift.

Apply these diffs:

-  [repo: string]: {
-    [status in 'in_progress' | 'waiting_for_review' | 'reviewed' | 'merged' | 'blocked']?: TPullRequest[];
-  };
+  [repo: string]: {
+    [status in Status]?: TPullRequest[];
+  };
 type RepoStatusArray = [
   repo: string,
   statusMap: {
-    [status in 'in_progress' | 'waiting_for_review' | 'reviewed' | 'merged' | 'blocked']?: TPullRequest[];
+    [status in Status]?: TPullRequest[];
   }
 ];

Optionally, introduce a reusable alias and use it in both places (outside these ranges):

type StatusPRMap = Partial<Record<Status, TPullRequest[]>>;

Also applies to: 54-59


65-71: Type the emoji map for stronger guarantees.

Annotate to ensure all Status keys are covered.

-const statusToEmoji = {
+const statusToEmoji: Record<Status, string> = {
   waiting_for_review: '🕒',
   in_progress: '🚧',
   reviewed: '✅',
   merged: '🔀',
   blocked: '⛔',
-};
+};

73-79: Freeze reviewDecisionToEmoji to prevent accidental mutation.

-const reviewDecisionToEmoji = {
+const reviewDecisionToEmoji = {
   APPROVED: '🟢',
   CHANGES_REQUESTED: '🟠',
   REVIEW_REQUIRED: '🔵',
   '': '',
-};
+} as const;

88-94: Use return; inside forEach instead of returning md.

Returning a value from a forEach callback is unnecessary; use a bare return.

-      md += `No pull requests found for team **${teamName}**.\n\n`;
-      return md;
+      md += `No pull requests found for team **${teamName}**.\n\n`;
+      return;

113-113: Avoid headings inside list items for cleaner Markdown.

Headings within list bullets render inconsistently. Use plain text or bold.

-        md += `\n  - #### ${statusToEmoji[status] || ''} ${status.replace(/_/g, ' ').toUpperCase()}\n`;
+        md += `\n  - ${statusToEmoji[status] ?? ''} ${status.replace(/_/g, ' ').toUpperCase()}\n`;

120-120: Remove stray space between newlines.

-          md += '\n \n';
+          md += '\n\n';

281-284: Add accessible label/title to the Markdown button.

Improves a11y and UX when the icon changes.

-            <Button onClick={handleCopyMarkdown} variant="soft" mb="2">
+            <Button
+              onClick={handleCopyMarkdown}
+              variant="soft"
+              mb="2"
+              aria-label={copied ? 'Copied' : 'Copy markdown report'}
+              title={copied ? 'Copied!' : 'Copy markdown report'}
+            >
               {copied ? <CheckIcon /> : <CopyIcon />}
               Markdown
             </Button>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad6ac76 and 77ab5a1.

📒 Files selected for processing (1)
  • src/components/features/report/report-client-page.tsx (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/components/features/report/report-client-page.tsx (1)
src/utils/schemas.ts (1)
  • TPullRequest (131-131)
🔇 Additional comments (5)
src/components/features/report/report-client-page.tsx (5)

10-11: Icon imports for copy feedback look good.


49-52: TeamRepoStatusMap shape reads well.


165-165: Copied state addition looks good.


215-236: Memoized aggregation with explicit typing looks correct.


238-247: Add clipboard fallback for non-secure/older browsers.

Current code only uses navigator.clipboard; add a textarea fallback.

 const handleCopyMarkdown = async () => {
   const md = generateMarkdownReport(startDate, endDate, selectedTeams, teamRepoStatusMap);
   try {
-    await navigator.clipboard.writeText(md);
-    setCopied(true);
-    setTimeout(() => setCopied(false), 2000);
+    if (navigator.clipboard && window.isSecureContext) {
+      await navigator.clipboard.writeText(md);
+      setCopied(true);
+      setTimeout(() => setCopied(false), 2000);
+    } else {
+      const textArea = document.createElement('textarea');
+      textArea.value = md;
+      textArea.style.position = 'fixed';
+      textArea.style.left = '-9999px';
+      textArea.style.top = '0';
+      document.body.appendChild(textArea);
+      textArea.focus();
+      textArea.select();
+      const ok = document.execCommand('copy');
+      document.body.removeChild(textArea);
+      setCopied(ok);
+      if (ok) setTimeout(() => setCopied(false), 2000);
+    }
   } catch (e) {
+    console.warn('Clipboard operation failed:', e);
     setCopied(false);
   }
 };

@dtczelo dtczelo requested a review from naim-ea September 23, 2025 10:18
Copy link
Collaborator

@naim-ea naim-ea left a comment

Choose a reason for hiding this comment

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

Nice job!

Copy link
Contributor

@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: 0

🧹 Nitpick comments (3)
src/components/features/report/report-client-page.tsx (3)

43-47: Unify status typing and simplify maps.

Avoid duplicating the status union. Derive from Status and use Partial<Record<...>> for brevity and consistency.

-type RepoStatusMap = {
-  [repo: string]: {
-    [status in 'in_progress' | 'waiting_for_review' | 'reviewed' | 'merged' | 'blocked']?: TPullRequest[];
-  };
-};
+type RepoStatusMap = Record<string, Partial<Record<Status, TPullRequest[]>>>;
 
-type RepoStatusArray = [
-  repo: string,
-  statusMap: {
-    [status in Status]?: TPullRequest[];
-  }
-];
+type RepoStatusArray = [
+  repo: string,
+  statusMap: Partial<Record<Status, TPullRequest[]>>
+];

Also applies to: 54-59


87-121: Minor: fix misleading return and trim extra whitespace in Markdown.

Returning a value inside forEach is ignored; use return; to skip the rest of the current iteration. Also remove the stray space in the blank line.

     if (!repoStatusMap || Object.keys(repoStatusMap).length === 0) {
       md += `No pull requests found for team **${teamName}**.\n\n`;
-      return md;
+      return;
     }
@@
-          md += '\n \n';
+          md += '\n\n';

62-62: Nit: remove redundant semicolon.

Cosmetic only; keeps formatting clean.

-export const STATUS_ORDER = ['waiting_for_review', 'in_progress', 'reviewed', 'merged', 'blocked'] as const satisfies readonly Status[];;
+export const STATUS_ORDER = ['waiting_for_review', 'in_progress', 'reviewed', 'merged', 'blocked'] as const satisfies readonly Status[];
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 77ab5a1 and 0d69f22.

📒 Files selected for processing (1)
  • src/components/features/report/report-client-page.tsx (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/components/features/report/report-client-page.tsx (1)
src/utils/schemas.ts (2)
  • TPullRequest (131-131)
  • TPullRequestReport (141-141)
🔇 Additional comments (2)
src/components/features/report/report-client-page.tsx (2)

237-246: Clipboard: add fallback for non-secure/older browsers (duplicate of earlier feedback).

Implement a fallback to a hidden textarea when navigator.clipboard is unavailable or the context isn’t secure. Also log failures for diagnostics.

   const handleCopyMarkdown = async () => {
     const md = generateMarkdownReport(startDate, endDate, selectedTeams, teamRepoStatusMap);
     try {
-      await navigator.clipboard.writeText(md);
-      setCopied(true);
-      setTimeout(() => setCopied(false), 2000);
+      // Prefer modern API when available in secure contexts
+      if (typeof navigator !== 'undefined' && navigator.clipboard && window.isSecureContext) {
+        await navigator.clipboard.writeText(md);
+      } else {
+        // Fallback for non-secure contexts or older browsers
+        const textArea = document.createElement('textarea');
+        textArea.value = md;
+        textArea.style.position = 'fixed';
+        textArea.style.left = '-9999px';
+        textArea.style.top = '0';
+        document.body.appendChild(textArea);
+        textArea.focus();
+        textArea.select();
+        try {
+          const ok = document.execCommand && document.execCommand('copy');
+          if (ok === false) throw new Error('execCommand returned false');
+        } finally {
+          document.body.removeChild(textArea);
+        }
+      }
+      setCopied(true);
+      setTimeout(() => setCopied(false), 2000);
     } catch (e) {
+      console.warn('Clipboard operation failed:', e);
       setCopied(false);
     }
   };

214-235: Status keys match TPullRequestReport and STATUS_ORDER.

PullRequestReportSchema (src/utils/schemas.ts) defines merged, in_progress, reviewed, waiting_for_review, blocked; report-client-page.tsx declares Status = keyof TPullRequestReport and STATUS_ORDER is typed to satisfy readonly Status[], so the cast is consistent.

Copy link
Contributor

@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: 0

🧹 Nitpick comments (2)
src/components/features/report/report-client-page.tsx (2)

43-47: Improve type safety with readonly status mapping.

The RepoStatusMap type uses a mapped type with optional status keys, which is good. However, for better maintainability, consider making the status keys readonly to prevent accidental modification.

Apply this diff to make the status keys readonly:

-type RepoStatusMap = {
-  [repo: string]: {
-    [status in 'in_progress' | 'waiting_for_review' | 'reviewed' | 'merged' | 'blocked']?: TPullRequest[];
-  };
-};
+type RepoStatusMap = {
+  readonly [repo: string]: {
+    readonly [status in Status]?: TPullRequest[];
+  };
+};

79-126: LGTM: Comprehensive markdown generation with minor formatting improvement.

The generateMarkdownReport function is well-structured and handles all the necessary cases for generating a comprehensive markdown report. The logic for sorting repositories (gnolang repos first, then alphabetically) is appropriate.

One minor improvement for markdown formatting:

Apply this diff to improve markdown formatting:

-          md += '\n \n';
+          md += '\n';

The extra space in '\n \n' creates unnecessary whitespace in the markdown output.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d69f22 and a5d7e99.

📒 Files selected for processing (1)
  • src/components/features/report/report-client-page.tsx (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/components/features/report/report-client-page.tsx (1)
src/utils/schemas.ts (2)
  • TPullRequest (131-131)
  • TPullRequestReport (141-141)
🔇 Additional comments (8)
src/components/features/report/report-client-page.tsx (8)

10-12: LGTM: Icon imports added correctly.

The CheckIcon and CopyIcon imports are appropriately added for the copy markdown functionality.


35-35: LGTM: Import added for type derivation.

The TPullRequestReport import is correctly added to derive the Status type from the schema.


49-62: LGTM: Well-structured type definitions.

The new types TeamRepoStatusMap, RepoStatusArray, and Status are well-defined. The use of keyof TPullRequestReport for the Status type ensures consistency with the schema, and the STATUS_ORDER constant provides a proper ordering with type safety.


64-77: LGTM: Well-organized emoji mappings.

The emoji mappings for statuses and review decisions are well-organized and provide clear visual indicators for the markdown output.


164-164: LGTM: State management for copy feedback.

The copied state is appropriately added to provide user feedback for the copy operation.


214-235: LGTM: Proper memoization of team repository status mapping.

The teamRepoStatusMap is correctly memoized and structured to support the markdown generation functionality.


237-246: Critical clipboard compatibility issue requires immediate attention.

The current clipboard implementation only uses the modern navigator.clipboard.writeText() API, which requires HTTPS/secure contexts and may fail in older browsers or insecure contexts. This is a critical usability issue that needs to be addressed.

The existing review comment addresses the same issue with a comprehensive fallback solution. Please implement the suggested clipboard compatibility improvements to ensure the feature works across all deployment scenarios and browser environments.


267-282: LGTM: Clean UI integration of copy functionality.

The copy markdown button is well-integrated into the existing UI layout with proper visual feedback using the CheckIcon/CopyIcon toggle. The button placement within the responsive flex layout is appropriate.

@dtczelo dtczelo merged commit 5f39e6f into main Sep 24, 2025
4 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.

3 participants