Skip to content

Feature: GitHub & Notion Preview Cards#586

Open
arifulhoque7 wants to merge 7 commits intoweDevsOfficial:developfrom
arifulhoque7:feature/github-preview-cards
Open

Feature: GitHub & Notion Preview Cards#586
arifulhoque7 wants to merge 7 commits intoweDevsOfficial:developfrom
arifulhoque7:feature/github-preview-cards

Conversation

@arifulhoque7
Copy link
Copy Markdown
Contributor

@arifulhoque7 arifulhoque7 commented Mar 3, 2026

Feature: GitHub & Notion Preview Cards

Branch: feature/github-preview-cards
Date: 2026-03-06
Scope: 21+ files changed (new files + modifications), ~1,700 lines added


Overview

This feature adds rich preview cards for GitHub (issues/pull requests) and Notion (pages/databases) URLs found in task descriptions, task comments, list comments, and discussion threads. When a user pastes a GitHub or Notion URL, it is automatically detected, the raw URL is stripped from the visible text, and a styled card with live data from the respective API is rendered below the content.


New Files

Backend (PHP)

File Description
routes/github.php Registers 3 REST routes: github/preview, github/batch-preview, github/test-connection
routes/notion.php Registers 3 REST routes: notion/preview, notion/batch-preview, notion/test-connection
src/GitHub/Controllers/GitHub_Preview_Controller.php Controller handling GitHub API integration (~470 lines)
src/Notion/Controllers/Notion_Preview_Controller.php Controller handling Notion API integration (~757 lines)

Frontend (Vue.js)

File Description
views/assets/src/components/common/github-preview-card.vue Single GitHub issue/PR card component (~424 lines)
views/assets/src/components/common/github-preview-container.vue Container that extracts GitHub URLs from content and manages fetching (~273 lines)
views/assets/src/components/common/notion-preview-card.vue Single Notion page/database card component
views/assets/src/components/common/notion-preview-container.vue Container that extracts Notion URLs from content and manages fetching (~265 lines)
views/assets/src/components/settings/github.vue GitHub settings page (~277 lines)
views/assets/src/components/settings/notion.vue Notion settings page (~271 lines)

Modified Files

Settings Infrastructure

src/Settings/Models/Settings.php

  • Added github_access_token and notion_access_token to the $hidden_settings array so tokens are treated as sensitive and never returned in raw form.

src/Settings/Transformers/Settings_Transformer.php

  • Added masking logic for github_access_token and notion_access_token using the existing mask_api_key() method (shows first/last few chars, masks the rest with *).

Settings UI (Navigation & Routing)

views/assets/src/components/settings/header.vue

  • Added "GitHub" and "Notion" tab links in the settings navigation header.

views/assets/src/components/settings/router.js

  • Imported GitHubSettings and NotionSettings components.
  • Registered routes: githubgithub_settings_tab and notionnotion_settings_tab, both requiring admin capability.

Global Component Registration

views/assets/src/helpers/common-components.js

  • Imported GitHubPreviewContainer and NotionPreviewContainer.
  • Registered as global Vue components: pm-github-preview and pm-notion-preview.

Mixin (Shared Methods)

views/assets/src/helpers/mixin/mixin.js

  • Added stripGithubUrls(html) — strips GitHub issue/PR URLs (both <a> tags and plain text) from HTML content when GitHub previews are enabled. Cleans up empty <p> tags.
  • Added stripNotionUrls(html) — same pattern for Notion URLs when Notion previews are enabled.

Integration Points (5 Templates)

Each of these templates was modified to:

  1. Pipe content through stripNotionUrls(stripGithubUrls(content)) in v-html bindings
  2. Add <pm-github-preview> and <pm-notion-preview> components below the content
File Context
views/assets/src/components/common/comments.vue Task comments
views/assets/src/components/project-task-lists/list-comments.vue Task list comments
views/assets/src/components/project-task-lists/task-comments.vue Task detail comments
views/assets/src/components/project-task-lists/single-task.vue Task description area
views/assets/src/components/project-discussions/individual-discussions.vue Discussion body + discussion comments

CSS Layout Fix

views/assets/src/helpers/less/pm-style.less

  • Added flex-wrap: wrap to .pm-desc-content so preview cards wrap to a new line.
  • Added width: 100% to .pm-github-preview-container and .pm-notion-preview-container inside .pm-desc-content for full-width cards.

views/assets/src/components/project-task-lists/single-task.vue (scoped styles)

  • Added &:empty { display: none; } to .pm-task-description to hide empty description div when URLs are fully stripped.

Feature Details

GitHub Preview Cards

URL Detection:

  • Regex pattern: https://github.com/{owner}/{repo}/(issues|pull)/{number}
  • Scans both plain text (after stripping HTML) and href attributes
  • Deduplicates URLs before fetching

Card Display (issue/PR):

  • Type icon (issue dot or PR merge icon) with color based on state (open=green, closed=red/purple, merged=purple)
  • Title with issue/PR number
  • Labels with colored badges
  • Author avatar and login name
  • Relative creation date
  • State badge (Open, Closed, Merged)
  • Repository name as link
  • Clickable → opens GitHub URL in new tab with noopener,noreferrer
  • Error state: warning icon with tooltip (instead of inline error text)

API Integration:

  • Uses GitHub API v3 (api.github.com)
  • Supports authenticated (Personal Access Token) and unauthenticated requests
  • Authenticated: 5,000 req/hr, access to private repos
  • Unauthenticated: 60 req/hr, public repos only
  • Returns: title, state, labels (name + color), assignees (login + avatar), author, timestamps

Notion Preview Cards

URL Detection:

  • Matches notion.so URLs containing a 32-char hex ID (with or without UUID dashes)
  • Supports patterns: /Page-Title-{id}, /{workspace}/{id}, /{id}?v={view_id}
  • Extracts from both plain text and href attributes

Card Display (page/database):

  • Icon (emoji or image) from the Notion page/database
  • Cover image if present (shown as banner)
  • Title
  • Type badge ("Page" or "Database")
  • Last edited by (user name + avatar from Notion Users API)
  • Last edited time (relative)
  • Parent type indicator
  • Error/access states with tooltip warnings
  • Clickable → opens Notion URL in new tab

API Integration:

  • Uses Notion API v1 (api.notion.com) with version header 2022-06-28
  • Requires Internal Integration Token (Bearer auth)
  • Smart endpoint selection: tries page endpoint first, falls back to database (or vice versa if URL has ?v= parameter)
  • Fetches user info separately for last-edited-by display (cached 24 hours)

Caching Strategy

Scenario Cache Duration
Successful preview 1 hour
Connection error 2 minutes
Access denied (401/403/404) 2 minutes
Rate limit exceeded (429) 5 minutes
Generic API error 2 minutes
Notion user info 24 hours

Cache key format: pm_github_preview_{md5(url)} / pm_notion_preview_{md5(url)}

Batch Fetching

  • When multiple URLs are detected in a single content block, a batch endpoint is used instead of individual requests
  • Batch limited to 10 URLs per request
  • Single URL detected → uses single preview endpoint
  • Frontend debounces content changes by 500ms before re-fetching

Settings Pages

Both GitHub and Notion settings pages follow the same pattern:

Token Management:

  • Input field for Personal Access Token / Internal Integration Token
  • Show/Hide toggle for masked token display
  • Change/Cancel buttons when token already saved
  • Token masked on API responses (e.g., ghp_abc****xyz)
  • Link to generate token on GitHub / create integration on Notion

Enable/Disable Toggle:

  • Checkbox to enable/disable preview cards globally
  • When disabled, URLs remain visible as plain text (not stripped)

Connection Test:

  • "Test Connection" button
  • Status indicators: Not tested, Testing (spinner), Connected (green check + user/bot info), Failed (red X + error)
  • Shows rate limit info (GitHub) or workspace name (Notion)
  • Can test with unsaved token before saving

URL Stripping

When previews are enabled, raw URLs are stripped from the rendered content to avoid duplication (URL shown as text + preview card below). The stripping:

  • Removes <a> tags linking to GitHub/Notion URLs
  • Removes plain text GitHub/Notion URLs
  • Cleans up resulting empty <p> tags
  • Only activates when the respective integration's previews are enabled

Security Measures

  • Input sanitization: sanitize_text_field() on all text, esc_url_raw() on URLs, sanitize_hex_color_no_hash() on label colors
  • URL validation: Domain-locked regex patterns; hostname validation against github.com / notion.so only
  • Token security: Stored in $hidden_settings, masked in API responses, never sent to client in full
  • Output safety: noopener,noreferrer on all window.open() calls; v-html only used with API-sourced data (not user HTML)
  • API safety: sslverify: true on all wp_remote_get calls; rawurlencode() on path components
  • Rate protection: Batch limit of 10 URLs; error caching prevents repeated failed requests
  • Auth: Preview/batch endpoints require Authentic permission; test-connection/settings require Settings_Page_Access (admin only)
  • Error handling: Generic error messages returned to client; detailed errors logged server-side when WP_DEBUG is enabled
  • JSON safety: json_last_error() check after json_decode to handle malformed API responses

Review Fixes Applied

During code review, the following issues were identified and fixed:

# Fix Files
1 Added is_string() check on URL params Both PHP controllers
2 Replaced raw WP error messages with generic messages + debug logging Both PHP controllers
3 Added rawurlencode() on owner/repo in GitHub API URL GitHub_Preview_Controller.php
4 Added 500ms debounce on content watchers Both container components
5 Fixed regex global state bug (new instance instead of reusing) github-preview-container.vue
6 Moved error messages to tooltip on warning icon Both preview card components
7 Standardized error cache durations (2min/5min/1hr) Both PHP controllers
8 Added json_last_error() checks after JSON decode Both PHP controllers
9 Tightened hostname regex to /^(www\.)?github\.com$/ Both preview card components
10 Fixed description preview cards not full-width pm-style.less
11 Added null-safe access on icon/cover type keys Notion_Preview_Controller.php
12 Hide empty .pm-task-description div when URLs stripped single-task.vue

Close 291
Close 294
Close 295

Summary by CodeRabbit

  • New Features

    • GitHub preview cards now appear for detected issue/PR URLs in comments, discussions, task descriptions and lists with per-item refresh, batching and caching.
    • Notion preview cards added similarly for detected Notion links.
    • New GitHub and Notion settings tabs to manage tokens, enable/disable previews, and test connections; connection status and rate info shown.
    • Content rendering strips GitHub/Notion links to display previews alongside text.
  • Chores

    • Access tokens are masked in settings and transformer output.

Add GitHub integration to preview issues and pull requests inline and manage a personal access token.

- New routes for preview, batch-preview and test-connection (routes/github.php).
- New GitHub_Preview_Controller to parse GitHub URLs, fetch single or batch previews from GitHub API, handle token/no-token requests, test connection, manage errors (access denied, rate limit), and cache responses (transient caching; successful responses cached 1 hour).
- Persist github_access_token in Settings model and mask it in Settings_Transformer for safe display.
- Add Vue components: github-preview-card (visual preview card), github-preview-container (extracts GitHub URLs and fetches previews), and settings/github (admin UI to view/change token and test connection).
- Integrate previews into comments, tasks and discussions by stripping GitHub URLs from rendered HTML and inserting the preview container where appropriate.

This provides authenticated and anonymous preview support, batch fetching, and UI for storing/masking the access token.
@arifulhoque7 arifulhoque7 added Needs Dev Review This PR needs review by a developer Needs Testing This issue/PR needs further testing labels Mar 3, 2026
@arifulhoque7 arifulhoque7 self-assigned this Mar 3, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 3, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds GitHub and Notion preview support: new REST routes and controllers, settings masking and UI for tokens, Vue preview card/container components, mixin URL-stripping, component registration updates, and view changes to render previews for comments, tasks, and discussions.

Changes

Cohort / File(s) Summary
Routes
routes/github.php, routes/notion.php
Register POST endpoints for preview, batch-preview, and test-connection for GitHub and Notion with appropriate permissions.
Controllers
src/GitHub/Controllers/GitHub_Preview_Controller.php, src/Notion/Controllers/Notion_Preview_Controller.php
New controllers implementing preview, batch_preview, and test_connection endpoints; URL parsing, API requests, transient caching, error normalization, and token/header management.
Settings
src/Settings/Models/Settings.php, src/Settings/Transformers/Settings_Transformer.php
Add github_access_token and notion_access_token to hidden settings and mask them in the settings transformer.
Preview Components (GitHub)
views/assets/src/components/common/github-preview-card.vue, views/assets/src/components/common/github-preview-container.vue
New GitHub preview card and container: URL extraction from content, single and batch fetch flows, per-URL loading, refresh handling, and presentation (loading/error/success).
Preview Components (Notion)
views/assets/src/components/common/notion-preview-card.vue, views/assets/src/components/common/notion-preview-container.vue
New Notion preview card and container: URL extraction, single/batch fetch, caching, and UI states with refresh support.
Frontend Integrations (views)
views/assets/src/components/.../comments.vue, views/assets/src/components/project-discussions/individual-discussions.vue, views/assets/src/components/project-task-lists/{list-comments.vue,single-task.vue,task-comments.vue}
Replace raw v-html with sanitized output via stripGithubUrls/stripNotionUrls and insert pm-github-preview / pm-notion-preview components.
Settings UI & Routing
views/assets/src/components/settings/github.vue, views/assets/src/components/settings/notion.vue, views/assets/src/components/settings/header.vue, views/assets/src/components/settings/router.js
Add GitHub and Notion settings pages, header tabs, router entries, token editing/saving, and connection testing UI.
Helpers & Registrations
views/assets/src/helpers/common-components.js, views/assets/src/helpers/mixin/mixin.js
Switch to ES imports for component registration; register pm-github-preview and pm-notion-preview; add stripGithubUrls and stripNotionUrls mixin methods to sanitize rendered HTML.
Styles / Misc
views/assets/src/helpers/less/pm-style.less
Layout tweaks to allow preview containers to wrap and span full width within descriptions.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

  • weDevsOfficial/pm-pro#291: This PR implements the GitHub issue/PR preview end-to-end (routes, controller, UI, URL stripping), matching the issue's objectives.

Possibly related PRs

Suggested labels

QA Approved

Suggested reviewers

  • iftakharul-islam

Poem

🐇 I nibble links and fetch a view,

Tokens tucked, previews hop through;
Cards that load with title bright,
Issues, pages, PRs in sight—
A joyful rabbit bounces new!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main addition: GitHub and Notion preview cards. It is concise and specific enough for a developer scanning history to understand the primary change.
Docstring Coverage ✅ Passed Docstring coverage is 92.31% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
Copy Markdown

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

🧹 Nitpick comments (5)
views/assets/src/components/common/github-preview-container.vue (3)

212-224: Same i18n issue in batch error handler.

The error message should also be internationalized here for consistency.

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

In `@views/assets/src/components/common/github-preview-container.vue` around lines
212 - 224, The batch error handler sets a static English message "Could not
fetch preview" into self.previews; change it to use the i18n translation helper
so the message is localized (e.g. call this.$t or the component's $t method)
when assigning error on each url in the error function inside the loop that
populates self.previews (referenced alongside extractNumberFromUrl and
extractRepoFromUrl); update the assignment to set error using the translation
key (e.g. this.$t('github.preview.fetch_error')) so all previews use the
localized message.

49-56: Consider debouncing content watch.

If content changes rapidly (e.g., during typing in an editor), this could trigger many API calls. Consider adding a debounce or checking if URLs actually changed before fetching.

Proposed improvement
     watch: {
         content: {
-            handler: function () {
-                this.fetchAllPreviews();
+            handler: function ( newContent, oldContent ) {
+                // Only fetch if URLs actually changed
+                var newUrls = this.extractGitHubUrls( newContent ).map(function(u) { return u.url; }).join(',');
+                var oldUrls = this.extractGitHubUrls( oldContent ).map(function(u) { return u.url; }).join(',');
+                if ( newUrls !== oldUrls ) {
+                    this.fetchAllPreviews();
+                }
             },
             immediate: false
         }
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@views/assets/src/components/common/github-preview-container.vue` around lines
49 - 56, The content watcher (watch: { content: { handler: function () {
this.fetchAllPreviews(); }, immediate: false } }) can fire many times; modify
the content watcher to debounce calls to fetchAllPreviews (or only call when
URLs actually change) by wrapping fetchAllPreviews in a debounced function (or
compare new/old content inside the handler) so rapid edits don't trigger
repeated API calls; update the watcher to call the debounced method (or
conditional logic) instead of calling fetchAllPreviews directly.

163-171: Error message not internationalized.

The error message 'Could not fetch preview' is hardcoded. For consistency with the rest of the application, consider using the __() translation function.

Proposed fix
                 error: function () {
                     self.$set( self.previews, url, {
                         type: 'issue',
                         number: self.extractNumberFromUrl( url ),
                         state: 'error',
                         repository: self.extractRepoFromUrl( url ),
-                        error: 'Could not fetch preview',
+                        error: __('Could not fetch preview', 'wedevs-project-manager'),
                         html_url: url
                     });
                 },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@views/assets/src/components/common/github-preview-container.vue` around lines
163 - 171, Replace the hardcoded error string in the error callback of the
GitHub preview setter with the i18n translation function; in the error handler
inside github-preview-container.vue where self.$set(self.previews, url, { ...
error: 'Could not fetch preview' ... }), change the value to use __('Could not
fetch preview') (or this.__ if that is the component-scoped helper) so the
message is internationalized, keeping the rest of the object (type, number via
extractNumberFromUrl, repository via extractRepoFromUrl, html_url) unchanged.
views/assets/src/components/settings/github.vue (1)

156-195: Consider consolidating settings requests.

The loadGitHubSettings() method makes two separate HTTP requests to load token and preview settings. Consider using a single request with multiple keys or accepting this minor inefficiency for clarity.

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

In `@views/assets/src/components/settings/github.vue` around lines 156 - 195, The
loadGitHubSettings() method issues two requests (tokenRequest and
previewsRequest); replace them with a single call using self.httpRequest that
fetches both settings (either by requesting pm/v2/settings without a key to get
all settings or by passing both keys if the API supports comma-separated keys),
then iterate the returned data array once to set self.token_saved and
self.masked_token (when item.key === 'github_access_token') and
self.enable_previews (when item.key === 'github_enable_previews'), and remove
the separate tokenRequest and previewsRequest objects so only one HTTP call is
made via the existing httpRequest helper.
src/GitHub/Controllers/GitHub_Preview_Controller.php (1)

217-239: Batch endpoint processes URLs sequentially.

The batch_preview method fetches each URL sequentially with a 10-second timeout per request. With up to 10 URLs, this could take up to 100 seconds in the worst case. The caching mechanism helps mitigate this for repeated requests.

Consider documenting this limitation or reducing the batch limit if response times become problematic in practice.

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

In `@src/GitHub/Controllers/GitHub_Preview_Controller.php` around lines 217 - 239,
The batch_preview method currently processes URLs sequentially (using
parse_github_url and fetch_github_data) causing up to 10 × timeout delays;
change it to process the $urls in parallel instead of a foreach loop: keep the
initial validation via parse_github_url, then build concurrent HTTP requests
(e.g., using curl_multi_exec or a parallel HTTP client) to call the same logic
as fetch_github_data for each valid $parsed entry, collect responses into
$results and preserve the existing success/error shape; alternatively, if you
prefer a simpler change, lower the array_slice limit from 10 to a smaller number
to cap worst-case latency.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/GitHub/Controllers/GitHub_Preview_Controller.php`:
- Around line 351-368: In the 429 handling block inside
GitHub_Preview_Controller (the branch checking if $status_code === 429), persist
the constructed rate-limited response to the shared cache (e.g., WP transient or
your existing caching layer) using a key derived from the request identity (for
example owner/repo/type/number or $parsed['url']) and a short TTL (e.g., 30–300
seconds), then return the cached payload; update the block that builds the array
with 'state' => 'rate_limited' to first set that array into cache so subsequent
calls hit the cache instead of GitHub until TTL expires.

In `@views/assets/src/components/common/github-preview-card.vue`:
- Line 2: The clickable card div currently only uses `@click`="openInGitHub" and
is inaccessible to keyboard users; make it focusable and operable via keyboard
by adding tabindex="0" and role="button" to the element and attach a
keydown/keyup handler (e.g., call openInGitHub when Enter or Space is pressed)
so keyboard activation triggers the same openInGitHub method; apply the same
change to the other instances referenced (lines 408-410) to keep behavior
consistent.
- Around line 130-166: Replace hardcoded user-facing strings in the computed
helpers with localized messages: in typeLabel, use the app i18n (e.g.,
this.$t('...')) for "Issue" and "Pull Request"; in stateLabel map states to
localized keys like this.$t('github.state.open'),
this.$t('github.state.closed'), this.$t('github.state.merged'); and in
relativeTime return localized relative-time strings (use i18n pluralization or a
localized format helper) instead of hardcoded English phrases—e.g.,
this.$t('time.years', {count: diffYears}) or this.$t('time.just_now')—so update
the functions typeLabel, stateLabel and relativeTime to call the localization
API and add appropriate translation keys for each label and time unit.
- Around line 18-20: The template directly accesses
previewData.repository.full_name which can throw if repository is missing;
update the github-preview-card.vue template (and any related computed/property
usage) to guard that access — e.g., use optional chaining or a small computed
getter (previewData.repository?.full_name or a getRepoName() computed that
returns previewData.repository ? previewData.repository.full_name : '') and
render a safe fallback (empty string or "Unknown repo") so the component won't
crash on partial error payloads.
- Around line 170-177: The openInGitHub method currently opens targetUrl
directly; validate it first by parsing targetUrl with the URL constructor
(catching exceptions), then ensure url.protocol === 'https:' and url.hostname
endsWith 'github.com' or endsWith 'githubusercontent.com' (or other allowed
GitHub subdomains you want), and only call window.open when validation passes;
if parsing/validation fails, do not open the window (optionally
return/console.warn) — update the openInGitHub function and its use of
this.previewData/this.url accordingly to perform this check before opening.

In `@views/assets/src/components/settings/github.vue`:
- Line 141: The data() initializer currently calls the mixin method
getSettings() to set enable_previews which can be unreliable; change data() to
initialize enable_previews to a safe default (e.g., true) and remove the direct
getSettings call, then in created() or mounted() call getSettings() or let
loadGitHubSettings() overwrite enable_previews (ensure loadGitHubSettings()
assigns to this.enable_previews) so the setting is populated after mixins are
available.

---

Nitpick comments:
In `@src/GitHub/Controllers/GitHub_Preview_Controller.php`:
- Around line 217-239: The batch_preview method currently processes URLs
sequentially (using parse_github_url and fetch_github_data) causing up to 10 ×
timeout delays; change it to process the $urls in parallel instead of a foreach
loop: keep the initial validation via parse_github_url, then build concurrent
HTTP requests (e.g., using curl_multi_exec or a parallel HTTP client) to call
the same logic as fetch_github_data for each valid $parsed entry, collect
responses into $results and preserve the existing success/error shape;
alternatively, if you prefer a simpler change, lower the array_slice limit from
10 to a smaller number to cap worst-case latency.

In `@views/assets/src/components/common/github-preview-container.vue`:
- Around line 212-224: The batch error handler sets a static English message
"Could not fetch preview" into self.previews; change it to use the i18n
translation helper so the message is localized (e.g. call this.$t or the
component's $t method) when assigning error on each url in the error function
inside the loop that populates self.previews (referenced alongside
extractNumberFromUrl and extractRepoFromUrl); update the assignment to set error
using the translation key (e.g. this.$t('github.preview.fetch_error')) so all
previews use the localized message.
- Around line 49-56: The content watcher (watch: { content: { handler: function
() { this.fetchAllPreviews(); }, immediate: false } }) can fire many times;
modify the content watcher to debounce calls to fetchAllPreviews (or only call
when URLs actually change) by wrapping fetchAllPreviews in a debounced function
(or compare new/old content inside the handler) so rapid edits don't trigger
repeated API calls; update the watcher to call the debounced method (or
conditional logic) instead of calling fetchAllPreviews directly.
- Around line 163-171: Replace the hardcoded error string in the error callback
of the GitHub preview setter with the i18n translation function; in the error
handler inside github-preview-container.vue where self.$set(self.previews, url,
{ ... error: 'Could not fetch preview' ... }), change the value to use __('Could
not fetch preview') (or this.__ if that is the component-scoped helper) so the
message is internationalized, keeping the rest of the object (type, number via
extractNumberFromUrl, repository via extractRepoFromUrl, html_url) unchanged.

In `@views/assets/src/components/settings/github.vue`:
- Around line 156-195: The loadGitHubSettings() method issues two requests
(tokenRequest and previewsRequest); replace them with a single call using
self.httpRequest that fetches both settings (either by requesting pm/v2/settings
without a key to get all settings or by passing both keys if the API supports
comma-separated keys), then iterate the returned data array once to set
self.token_saved and self.masked_token (when item.key === 'github_access_token')
and self.enable_previews (when item.key === 'github_enable_previews'), and
remove the separate tokenRequest and previewsRequest objects so only one HTTP
call is made via the existing httpRequest helper.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 28528cf and cb5ca53.

📒 Files selected for processing (16)
  • routes/github.php
  • src/GitHub/Controllers/GitHub_Preview_Controller.php
  • src/Settings/Models/Settings.php
  • src/Settings/Transformers/Settings_Transformer.php
  • views/assets/src/components/common/comments.vue
  • views/assets/src/components/common/github-preview-card.vue
  • views/assets/src/components/common/github-preview-container.vue
  • views/assets/src/components/project-discussions/individual-discussions.vue
  • views/assets/src/components/project-task-lists/list-comments.vue
  • views/assets/src/components/project-task-lists/single-task.vue
  • views/assets/src/components/project-task-lists/task-comments.vue
  • views/assets/src/components/settings/github.vue
  • views/assets/src/components/settings/header.vue
  • views/assets/src/components/settings/router.js
  • views/assets/src/helpers/common-components.js
  • views/assets/src/helpers/mixin/mixin.js

Comment thread src/GitHub/Controllers/GitHub_Preview_Controller.php Outdated
Comment thread views/assets/src/components/common/github-preview-card.vue Outdated
Comment thread views/assets/src/components/common/github-preview-card.vue Outdated
Comment thread views/assets/src/components/common/github-preview-card.vue
Comment thread views/assets/src/components/common/github-preview-card.vue
Comment thread views/assets/src/components/settings/github.vue
PHP: Cache and return 429 responses properly — store the 429 result in a transient before returning to avoid repeated rate-limit hits.

Vue: Add keyboard accessibility (tabindex, role, Enter/Space handlers) and compute repo name via a computed prop. Localize UI strings and relative time using __(), _n() and sprintf for proper i18n. Add robust URL validation (URL parsing, https + github.com host check) with try/catch and early returns to avoid opening or throwing on invalid targets. Overall changes improve accessibility, localization, security and handling of rate-limited responses.
Copy link
Copy Markdown

@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/GitHub/Controllers/GitHub_Preview_Controller.php (2)

67-68: Minor: Redundant condition and consider using Bearer prefix.

The condition $token && $token !== false is redundant—if $token is truthy, it's already not false. Also, GitHub recommends using the Bearer prefix for Personal Access Tokens, though token still works.

♻️ Proposed simplification
-        if ( $token && $token !== false ) {
-            $headers['Authorization'] = 'token ' . $token;
+        if ( $token ) {
+            $headers['Authorization'] = 'Bearer ' . $token;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/GitHub/Controllers/GitHub_Preview_Controller.php` around lines 67 - 68,
Simplify the redundant token check and use the recommended Bearer prefix: in
GitHub_Preview_Controller (the code that sets $headers['Authorization']),
replace the condition "if ($token && $token !== false)" with "if ($token)" and
set the header value to use 'Bearer ' . $token instead of 'token ' . $token so
$headers['Authorization'] = 'Bearer ' . $token;.

98-102: Minor: Redundant condition.

$token && ! empty( $token ) is redundant—! empty() is always true when $token is truthy.

♻️ Proposed simplification
-        if ( $token && ! empty( $token ) ) {
+        if ( $token ) {
             $api_url = 'https://api.github.com/user';
         } else {
             $api_url = 'https://api.github.com/rate_limit';
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/GitHub/Controllers/GitHub_Preview_Controller.php` around lines 98 - 102,
The if condition in the GitHub_Preview_Controller (the block checking $token and
setting $api_url) uses a redundant test ($token && ! empty( $token )); replace
it with a single emptiness check (e.g., ! empty($token)) to determine which API
URL to set so the branch remains the same but the condition is simplified;
update the if controlling the $api_url assignment accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/GitHub/Controllers/GitHub_Preview_Controller.php`:
- Around line 374-391: The error branch in GitHub_Preview_Controller that
returns on unexpected status codes (the check where $status_code !== 200 ||
empty($body)) must also persist the error result to cache to avoid repeated API
calls; update that block to build the same cache key used in the rate-limiting
branch (using $type, $number, $owner, $repo or the existing cache helper) and
store the returned payload (including 'state'=>'error' and the error message)
with the same TTL/mechanism as the rate-limit case (e.g., wp_cache_set or
set_transient via the project's cache helper) before returning it.

---

Nitpick comments:
In `@src/GitHub/Controllers/GitHub_Preview_Controller.php`:
- Around line 67-68: Simplify the redundant token check and use the recommended
Bearer prefix: in GitHub_Preview_Controller (the code that sets
$headers['Authorization']), replace the condition "if ($token && $token !==
false)" with "if ($token)" and set the header value to use 'Bearer ' . $token
instead of 'token ' . $token so $headers['Authorization'] = 'Bearer ' . $token;.
- Around line 98-102: The if condition in the GitHub_Preview_Controller (the
block checking $token and setting $api_url) uses a redundant test ($token && !
empty( $token )); replace it with a single emptiness check (e.g., !
empty($token)) to determine which API URL to set so the branch remains the same
but the condition is simplified; update the if controlling the $api_url
assignment accordingly.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb5ca53 and 842ae06.

📒 Files selected for processing (2)
  • src/GitHub/Controllers/GitHub_Preview_Controller.php
  • views/assets/src/components/common/github-preview-card.vue
🚧 Files skipped from review as they are similar to previous changes (1)
  • views/assets/src/components/common/github-preview-card.vue

Comment thread src/GitHub/Controllers/GitHub_Preview_Controller.php
arifulhoque7 and others added 2 commits March 3, 2026 14:53
When handling unexpected status codes or empty responses in GitHub_Preview_Controller, the constructed fallback result is now stored in a transient (set_transient with $cache_key) for 5 minutes and then returned. This avoids recomputing the same fallback on subsequent requests and reduces repeated processing/API calls.
coderabbitai[bot]

This comment was marked as off-topic.

Introduce Notion preview support and improve GitHub preview handling. Added routes/notion.php and a new Notion_Preview_Controller with single/batch preview and test-connection endpoints, parsing, API calls, caching, error handling and helper methods. Frontend: new notion-preview-card component, notion-preview-container integration, show Notion previews in comments, debounce content parsing, and add force_refresh to preview requests. Settings: persist notion_access_token and mask it in transformers. GitHub preview: better network/error messages and logging, JSON error handling, url-encoding for repo/owner, support force-refresh, reduced cache TTLs and UI/regex tweaks for more robust URL detection and warning display.
Normalize boolean setting checks in GitHub and Notion preview controllers using strict in_array (handles false, 'false', '0', 0). Make GitHub URL type detection case-insensitive for 'pull'. Rework GitHub response handling to detect rate limiting first (429 or 403 with x-ratelimit-remaining = 0), return a rate_limited state and message, and cache those results for 10 minutes. Handle access-denied/404/401 separately with an access_denied state and shorter (2 minutes) cache. Adjusted error messages accordingly.
@arifulhoque7 arifulhoque7 changed the title Add GitHub issue/PR preview and settings Feature: GitHub & Notion Preview Cards Mar 6, 2026
coderabbitai[bot]

This comment was marked as off-topic.

Copy link
Copy Markdown

@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.

🧹 Nitpick comments (6)
src/Notion/Controllers/Notion_Preview_Controller.php (4)

239-254: Sequential batch processing may be slow.

Each URL is fetched sequentially, which could take up to 100+ seconds in worst case (10 URLs × 10s timeout). This is likely acceptable for an initial implementation, but consider noting this as a potential future optimization using parallel requests (e.g., Requests::request_multiple() from WordPress HTTP API).

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

In `@src/Notion/Controllers/Notion_Preview_Controller.php` around lines 239 - 254,
The loop in the Notion preview handler processes $urls sequentially (using
parse_notion_url and fetch_notion_data) which can be slow for up to 10 URLs;
change the implementation to perform parallel HTTP calls (e.g., use WordPress
Requests::request_multiple or another concurrent request utility) by first
mapping $urls through parse_notion_url to build a request list, execute requests
concurrently, then populate $results with success/error and pass responses into
fetch_notion_data (or adapt fetch_notion_data to accept raw response bodies) so
all fetches run in parallel instead of one-by-one.

72-74: Minor: Redundant condition check.

The check $token && $token !== false is slightly redundant since a truthy $token already implies it's not false. However, this is defensive coding and doesn't affect functionality.

♻️ Optional simplification
-        if ( $token && $token !== false ) {
+        if ( $token ) {
             $headers['Authorization'] = 'Bearer ' . $token;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Notion/Controllers/Notion_Preview_Controller.php` around lines 72 - 74,
The condition in Notion_Preview_Controller that currently checks "if ($token &&
$token !== false)" is redundant; replace it with a single truthy check (e.g.,
"if ($token)") where the Authorization header is set (the block that assigns
$headers['Authorization'] = 'Bearer ' . $token). This keeps the behavior
identical while simplifying the conditional; ensure you update the check in the
method that builds the request headers in Notion_Preview_Controller so only a
single truthy $token test is used.

126-144: Consider handling rate limit (429) responses.

The test_connection method handles 401 and generic non-200 statuses, but unlike try_fetch_page and try_fetch_database (which handle 429 explicitly), this method doesn't provide a specific message for rate limiting. Users may get a confusing generic error during rate limits.

♻️ Proposed fix to add 429 handling
         if ( $status_code === 401 ) {
             return [
                 'success' => false,
                 'error'   => __( 'Invalid token. Please check your Notion Internal Integration Token.', 'wedevs-project-manager' ),
             ];
         }

+        if ( $status_code === 429 ) {
+            return [
+                'success' => false,
+                'error'   => __( 'Notion API rate limit exceeded. Please try again later.', 'wedevs-project-manager' ),
+            ];
+        }
+
         if ( $status_code !== 200 || empty( $body ) ) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Notion/Controllers/Notion_Preview_Controller.php` around lines 126 - 144,
The test_connection method currently checks $status_code for 401 and generic
non-200 but misses explicit handling for 429 rate-limit responses; update
Notion_Preview_Controller::test_connection to detect when $status_code === 429
and return the same structured response (array with 'success' => false and a
clear rate-limit message) used by try_fetch_page/try_fetch_database, using
$status_code/$body context and preserving the localization function __() and
keys 'success' and 'error' so callers receive a specific "rate limit" error
instead of the generic message.

490-582: Consider extracting shared logic between try_fetch_page and try_fetch_database.

These two methods share approximately 80% identical code (error handling, caching, response parsing). A single parameterized method (e.g., try_fetch_resource($type, $uuid, $headers, $parsed)) could reduce duplication and make maintenance easier.

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

In `@src/Notion/Controllers/Notion_Preview_Controller.php` around lines 490 - 582,
Both try_fetch_page and try_fetch_database duplicate most HTTP, error-handling,
caching and response-parsing logic; refactor by extracting the shared flow into
a new parameterized method try_fetch_resource($type, $uuid, $headers, $parsed)
and have try_fetch_page and try_fetch_database call it with type='page' or
'database'. The new try_fetch_resource should perform wp_remote_get, WP_Error
handling, status-code branches (404, 401/403 -> access_denied, 429 ->
rate_limited), json decode + json_last_error check, build_error_result usage,
transient caching keys (use md5($parsed['url']) like current cache_key), and
return the common result structure; keep only resource-specific bits (e.g.,
extract_database_title / extract_icon / extract_cover / fetch_user_info and
parent_type handling) in the callers or provide hooks/callbacks to transform the
decoded $body into the final data payload. Update references to set_transient
durations and ensure callers still sanitize/esc_url_raw fields as currently
done.
src/GitHub/Controllers/GitHub_Preview_Controller.php (2)

67-69: Minor: Simplify redundant token check.

The condition $token && $token !== false is redundant since $token alone will short-circuit if falsy.

♻️ Suggested simplification
-        if ( $token && $token !== false ) {
+        if ( $token ) {
             $headers['Authorization'] = 'token ' . $token;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/GitHub/Controllers/GitHub_Preview_Controller.php` around lines 67 - 69,
The if condition in GitHub_Preview_Controller that currently checks "if ( $token
&& $token !== false )" is redundant; simplify it to a single truthiness check
for $token (e.g., use "if ($token)") before setting $headers['Authorization'] to
'token ' . $token so the behavior is unchanged but the expression is clearer and
concise.

223-239: Consider request timeout for uncached batch scenarios.

With a 10-URL limit and 10-second timeout per fetch, worst-case uncached requests could take 100+ seconds. In practice, caching mitigates this. However, if many URLs miss cache simultaneously (e.g., first load with multiple new GitHub links), the overall request may timeout depending on server configuration.

Consider adding early termination if cumulative time exceeds a threshold, or documenting the expected behavior for admins.

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

In `@src/GitHub/Controllers/GitHub_Preview_Controller.php` around lines 223 - 239,
The current loop over $urls (after array_slice) calls parse_github_url and
fetch_github_data for each entry and can exceed total request time when many
uncached items are fetched; add a cumulative timeout guard by recording a start
time (before the foreach) and after each fetch check elapsed time against a
configurable threshold (e.g., $batchTimeoutSeconds) and if exceeded stop
processing remaining URLs and mark them in $results with a timeout error; update
the logic around parse_github_url and fetch_github_data to return early and
ensure $results contains meaningful failure entries for skipped URLs so callers
can handle partial results.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/GitHub/Controllers/GitHub_Preview_Controller.php`:
- Around line 67-69: The if condition in GitHub_Preview_Controller that
currently checks "if ( $token && $token !== false )" is redundant; simplify it
to a single truthiness check for $token (e.g., use "if ($token)") before setting
$headers['Authorization'] to 'token ' . $token so the behavior is unchanged but
the expression is clearer and concise.
- Around line 223-239: The current loop over $urls (after array_slice) calls
parse_github_url and fetch_github_data for each entry and can exceed total
request time when many uncached items are fetched; add a cumulative timeout
guard by recording a start time (before the foreach) and after each fetch check
elapsed time against a configurable threshold (e.g., $batchTimeoutSeconds) and
if exceeded stop processing remaining URLs and mark them in $results with a
timeout error; update the logic around parse_github_url and fetch_github_data to
return early and ensure $results contains meaningful failure entries for skipped
URLs so callers can handle partial results.

In `@src/Notion/Controllers/Notion_Preview_Controller.php`:
- Around line 239-254: The loop in the Notion preview handler processes $urls
sequentially (using parse_notion_url and fetch_notion_data) which can be slow
for up to 10 URLs; change the implementation to perform parallel HTTP calls
(e.g., use WordPress Requests::request_multiple or another concurrent request
utility) by first mapping $urls through parse_notion_url to build a request
list, execute requests concurrently, then populate $results with success/error
and pass responses into fetch_notion_data (or adapt fetch_notion_data to accept
raw response bodies) so all fetches run in parallel instead of one-by-one.
- Around line 72-74: The condition in Notion_Preview_Controller that currently
checks "if ($token && $token !== false)" is redundant; replace it with a single
truthy check (e.g., "if ($token)") where the Authorization header is set (the
block that assigns $headers['Authorization'] = 'Bearer ' . $token). This keeps
the behavior identical while simplifying the conditional; ensure you update the
check in the method that builds the request headers in Notion_Preview_Controller
so only a single truthy $token test is used.
- Around line 126-144: The test_connection method currently checks $status_code
for 401 and generic non-200 but misses explicit handling for 429 rate-limit
responses; update Notion_Preview_Controller::test_connection to detect when
$status_code === 429 and return the same structured response (array with
'success' => false and a clear rate-limit message) used by
try_fetch_page/try_fetch_database, using $status_code/$body context and
preserving the localization function __() and keys 'success' and 'error' so
callers receive a specific "rate limit" error instead of the generic message.
- Around line 490-582: Both try_fetch_page and try_fetch_database duplicate most
HTTP, error-handling, caching and response-parsing logic; refactor by extracting
the shared flow into a new parameterized method try_fetch_resource($type, $uuid,
$headers, $parsed) and have try_fetch_page and try_fetch_database call it with
type='page' or 'database'. The new try_fetch_resource should perform
wp_remote_get, WP_Error handling, status-code branches (404, 401/403 ->
access_denied, 429 -> rate_limited), json decode + json_last_error check,
build_error_result usage, transient caching keys (use md5($parsed['url']) like
current cache_key), and return the common result structure; keep only
resource-specific bits (e.g., extract_database_title / extract_icon /
extract_cover / fetch_user_info and parent_type handling) in the callers or
provide hooks/callbacks to transform the decoded $body into the final data
payload. Update references to set_transient durations and ensure callers still
sanitize/esc_url_raw fields as currently done.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ae3a0398-6a03-4849-abb4-dfa3619f6814

📥 Commits

Reviewing files that changed from the base of the PR and between 4751b4a and 9343362.

📒 Files selected for processing (2)
  • src/GitHub/Controllers/GitHub_Preview_Controller.php
  • src/Notion/Controllers/Notion_Preview_Controller.php

Introduce Loom video preview support: add backend Loom_Preview_Controller with single/batch preview endpoints and a test-connection route (transient caching, error/rate-limit handling, oEmbed fetching). Add routes/loom.php to register endpoints and respect loom_enable_previews setting. Add frontend Vue components (pm-loom-preview-card, pm-loom-preview-container) and a Loom settings view, update settings header and router/helpers/styles/mixins to surface the Loom tab and wire previews into comments, discussions and task descriptions. Batch requests are limited and previews default to enabled when unset.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Needs Dev Review This PR needs review by a developer Needs Testing This issue/PR needs further testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant