Skip to content

Add traffic boost post list stats#3293

Merged
alecgeatches merged 38 commits intoadd/traffic-boostfrom
add/traffic-boost-post-list-stats
May 12, 2025
Merged

Add traffic boost post list stats#3293
alecgeatches merged 38 commits intoadd/traffic-boostfrom
add/traffic-boost-post-list-stats

Conversation

@alecgeatches
Copy link
Contributor

@alecgeatches alecgeatches commented Apr 23, 2025

Description

Adds campaign tracking for inserted links in Traffic Boost, and displays those results in the UI:

Screenshot 2025-04-23 at 2 40 15 PM

Testing

Smart Link Insertion

Verify links inserted by Traffic Boost have the correct campaign information:

  1. Go to WordPress Admin -> Parse.ly menu -> Traffic Boost.

  2. Click the "Boost Traffic" button next to a post.

  3. Wait for analysis and see a link suggestion. Note the page's title, then click "Accept" on a link suggestion.

  4. After accepting the suggestion, open the "Inbound Links" page and select the accepted insertion page. Next, click the "..." symbol on the top right and open the post with the inserted link:

    Screenshot 2025-05-07 at 1 56 34 PM

  5. In the modified post, find the inserted link and view the URL. It should look something like this:

    https://mysite.vipdev.lndo.site/blog/website-downtime-cost/?itm_campaign=wp-parsely&itm_medium=smart-link&itm_term=3fbbdf42ebaab27f68f089bc9ed0ae46&itm_source=traffic-boost
    
  6. Ensure itm_campaign=wp-parsely, itm_medium=smart-link, itm_source=traffic-boost, and itm_term has a value.

Traffic Boost Results

"Results" here refer to the new UI displaying impact on a boosted page:

Screenshot 2025-05-07 at 2 04 22 PM

This data is provided by Parse.ly directly, so it's difficult to test locally. However, we have a couple of methods we can use to display real data from Parse.ly:

Test recently-inserted link

For testing purposes, a smart link was inserted into real post data from "How to Ensure Good Governance for WordPress Block Themes" to "Using a Design System with the WordPress Block Editor: Block Types & Styles".

The real data is shown in the screenshot above. Using the same local data, you should be able to find these results by searching "theme.json" in the Traffic Boost search bar as shown above.

You can also increment the number by clicking the "Block Editor" smart link inserted in "How to Ensure Good Governance for WordPress Block Themes":

image

Next, wait for about 5 minutes for the local cache to clear and re-search "theme.json". You should see the counter and overall impact increase.

Historical Traffic Boost Results

The WPVIP story "Pros and Cons of Using GraphQL on WordPress", a recent post, has some historical testing data available with a small change:

image

This data is over 30 days old, so in order to view it we need to modify $request_params in [src/rest-api/stats/class-endpoint-posts.php before making the analytics request:

    // Fill the URLs with the campaign params.
    /** @var array<string, array<string, mixed>> $request_params_with_campaign */
    $request_params_with_campaign['urls'] = $post_urls;

+   $request_params_with_campaign['period_start'] = '60d';

    /**
     * The raw analytics data, received by the API.
     *
     * @var array<array<string, mixed>>|WP_Error $campaign_request
     */
    $campaign_request = $this->content_api->get_posts( $request_params_with_campaign );

After making the above change, you should be able to see some click data for "Pros and Cons of Using GraphQL on WordPress", as displayed above.

Summary by CodeRabbit

  • New Features

    • Dashboard posts table now displays 30-day view counts, inbound/outbound smart link counts, and traffic boost suggestion bubbles.
    • Added campaign filtering and WordPress permalink support to stats API endpoints.
    • REST API responses for posts/pages now include canonical URL, smart link metrics, and traffic boost suggestion counts.
    • New stats provider enables fetching detailed Parse.ly metrics per post.
  • Improvements

    • Enhanced pagination and search handling in dashboard and traffic boost pages.
    • Modularized post row rendering and improved error handling for stats loading.
    • Improved caching for smart link data and post ID lookups for better performance.
    • Added support for Parsely metadata in post data and improved type safety for query parameters.
    • Enhanced error handling and cache invalidation after smart link modifications.
    • Refined campaign metrics extraction and URL processing in stats data.
  • Bug Fixes

    • Fixed issues with taxonomy queries and cache invalidation for smart links.
  • Style

    • Refined dashboard table layout and styling for metrics, actions, and compact mode.
  • Chores

    • Updated and reorganized import paths for improved code structure.
    • Added and updated tests to cover new metadata fields and API parameters.
    • Introduced a global cache group constant for consistent cache management.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Apr 23, 2025

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

This change introduces enhanced smart link and traffic boost metadata to REST API responses and frontend data structures, adds campaign metrics support to stats endpoints, and refactors caching strategies for smart link and post ID lookups. The UI is updated with new components for post stats, link overviews, and suggestion bubbles, and the posts table is modularized. Pagination and query context handling are improved throughout.

Changes

Files/Paths Change Summary
src/Models/class-smart-link.php, src/Models/class-inbound-smart-link.php Refactored caching logic, standardized cache keys/groups, added methods for retrieving and caching smart link counts and suggestions, updated taxonomy query fields, and improved cache invalidation.
src/Endpoints/class-rest-metadata.php, tests/Integration/Endpoints/RestMetadataTest.php Enhanced REST metadata responses with canonical URL, smart link counts, and traffic boost suggestion counts; updated tests to expect new fields.
src/rest-api/content-helper/class-endpoint-smart-linking.php, src/rest-api/content-helper/class-endpoint-traffic-boost.php Added cache flush calls after smart link modifications to ensure cache consistency.
src/rest-api/stats/class-endpoint-posts.php, src/rest-api/stats/trait-post-data.php Added support for campaign metrics, new REST parameters (campaign filters, permalink usage), and logic to merge campaign data into post stats.
src/services/class-cached-service-endpoint.php, src/Utils/class-utils.php, wp-parsely.php Unified cache group usage with a new constant, improved cache key generation (hashing), and set explicit cache expiration times.
src/content-helper/common/providers/base-wordpress-provider.tsx Extended post types with Parsely metadata, made query context configurable, improved type safety, and added canonical URL extraction.
src/content-helper/common/providers/base-provider.tsx, src/content-helper/dashboard-widget/provider.ts, src/content-helper/editor-sidebar/*/provider.ts, src/content-helper/dashboard-page/provider.tsx Updated import paths for base provider modules for consistency.
src/content-helper/dashboard-page/components/posts-table/component.tsx Refactored to use a new SinglePostRow component, added stats fetching, error handling, and externalized pagination control.
src/content-helper/dashboard-page/components/posts-table/components/single-post-row.tsx New component for rendering a single post row with stats, actions, and error handling; includes internal dropdown for actions.
src/content-helper/dashboard-page/components/posts-table/components/post-details.tsx New component for displaying detailed post info (title, date, author, categories, thumbnail).
src/content-helper/dashboard-page/components/posts-table/components/links-overview.tsx New component for inbound/outbound link counts with icons and tooltips.
src/content-helper/dashboard-page/components/posts-table/components/suggestion-bubble.tsx New component for displaying and navigating via a suggestion count bubble.
src/content-helper/dashboard-page/components/posts-table/style.scss Overhauled table and cell styling for improved layout, metrics, actions, compact mode, and new UI elements.
src/content-helper/dashboard-page/pages/dashboard/page-component.tsx, src/content-helper/dashboard-page/pages/traffic-boost/page-component.tsx Introduced local pagination state and passed it to posts table; improved search and query context handling.
src/content-helper/dashboard-page/pages/traffic-boost/provider.ts, src/content-helper/dashboard-page/pages/traffic-boost/store.ts Updated imports for base provider and hydrated post types; enhanced error handling and query options.
src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/*.tsx Updated import paths for hydrated post types and passed new props to posts table where applicable.
src/content-helper/dashboard-page/pages/traffic-boost/preview/*.tsx Updated import paths for hydrated post types.
src/content-helper/common/components/thumbnail/component.tsx Updated import path for hydrated post type.
src/content-helper/common/providers/stats-provider.tsx New provider for fetching Parse.ly post stats with campaign and permalink support; defines types for metrics and stats.
package.json Added @wordpress/date to devDependencies.
src/services/content-api/class-content-api-service.php Updated PHPDoc for parameter types to allow nested arrays.
tests/Integration/RestAPI/Stats/EndpointPostsTest.php Updated test to expect new use_wp_permalink parameter in stats endpoint response.

Sequence Diagram(s)

sequenceDiagram
    participant Frontend
    participant REST API
    participant SmartLinkModel
    participant InboundSmartLinkModel

    Frontend->>REST API: GET /posts/{id}
    REST API->>SmartLinkModel: get_link_counts(post_id)
    SmartLinkModel->>SmartLinkModel: Query & cache inbound/outbound counts
    REST API->>InboundSmartLinkModel: get_suggestions_count(post_id)
    InboundSmartLinkModel->>InboundSmartLinkModel: Query & cache suggestion count
    REST API-->>Frontend: Respond with post data + canonical_url, smart_links, traffic_boost_suggestions_count

    Frontend->>REST API: GET /stats/posts?campaign_id=...&use_wp_permalink=...
    REST API->>REST API: If use_wp_permalink, expand URLs with permalinks
    REST API->>REST API: If campaign params, fetch campaign metrics
    REST API-->>Frontend: Respond with post stats + campaign_metrics
Loading

Possibly related PRs

  • Parsely/wp-parsely#3208: Modifies get_post_id_by_url method to improve URL-to-post ID resolution, directly related to caching and URL handling improvements.
  • Parsely/wp-parsely#3213: Refactors Smart_Link and Inbound_Smart_Link classes to use status taxonomy and centralize status management, related to smart link caching and counts enhancements.
  • Parsely/wp-parsely#3293: Adds traffic boost and smart link statistics to REST API responses and improves caching strategies, closely related to the current PR’s enhancements.

Suggested labels

Feature: PCH

Tip

⚡️ Faster reviews with caching
  • CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.

Enjoy the performance boost—your workflow just got faster.


📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c4edd2 and eb92cb3.

📒 Files selected for processing (1)
  • src/Models/class-smart-link.php (11 hunks)
✨ 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.

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.

@alecgeatches alecgeatches self-assigned this Apr 29, 2025
@alecgeatches alecgeatches force-pushed the add/traffic-boost-post-list-stats branch from 9701c47 to 88c7fb1 Compare April 29, 2025 21:03
@acicovic acicovic added this to the 3.19.0 milestone May 6, 2025
@alecgeatches alecgeatches marked this pull request as ready for review May 7, 2025 20:25
@alecgeatches alecgeatches requested a review from a team as a code owner May 7, 2025 20:25
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: 14

🔭 Outside diff range comments (2)
src/Models/class-smart-link.php (1)

1042-1044: ⚠️ Potential issue

Invalid orderby value – WordPress will ignore it.

WP_Query accepts 'modified' (or 'date') but not 'date modified'. The current value silently falls back to default ordering.

- 'orderby' => 'date modified',
+ 'orderby' => 'modified',
src/rest-api/stats/class-endpoint-posts.php (1)

246-248: 🛠️ Refactor suggestion

sanitize_url() is not a WordPress core function.

Using undefined helpers breaks installs that do not ship custom global functions.
Replace with esc_url_raw() which is the sanctioned equivalent.

-return array_map( 'sanitize_url', $urls );
+return array_map( 'esc_url_raw', $urls );

Also update the @since tag to 3.19.0 if this helper is newly introduced.

♻️ Duplicate comments (1)
src/Models/class-inbound-smart-link.php (1)

1338-1342: Keep cache-group consistency when deleting.

After adopting a named cache group above, make sure the deletion mirrors the group:

- wp_cache_delete( 'traffic_boost_suggestions_count_' . $post_id );
+ wp_cache_delete( 'traffic_boost_suggestions_count_' . $post_id, 'parsely' );
🧹 Nitpick comments (11)
src/content-helper/dashboard-page/components/posts-table/components/suggestion-bubble.tsx (1)

1-56: Well-structured new component for displaying suggestion counts.

This component is well-designed with clear prop types, proper JSDoc comments, and separation of concerns. It effectively leverages React Router for navigation and WordPress components for UI elements.

However, there are a few potential improvements:

  1. Consider adding ARIA attributes for better accessibility
  2. The click handler could be memoized with useCallback to prevent unnecessary re-renders

Here's an improved implementation with these enhancements:

 /**
  * External dependencies
  */
 import { useNavigate } from 'react-router';
+import { useCallback } from '@wordpress/element';

 /**
  * WordPress dependencies
  */
 import { Tooltip } from '@wordpress/components';

 /**
  * Type definition for the SuggestionBubble component.
  *
  * @since 3.18.0
  */
 type SuggestionBubbleProps = {
 	postId: number;
 	numberOfSuggestions: number;
 };

 /**
  * SuggestionBubble component.
  *
  * Used to display the number of pending suggestions for a post.
  *
  * @since 3.18.0
  *
  * @param {SuggestionBubbleProps} props The props for the SuggestionBubble component.
  */
 export const SuggestionBubble = ( { postId, numberOfSuggestions }: SuggestionBubbleProps ) => {
 	const navigate = useNavigate();

 	/**
 	 * Handles the click event on the suggestion bubble.
 	 *
 	 * @since 3.18.0
 	 */
-	const handleClick = () => {
+	const handleClick = useCallback(() => {
 		navigate( `/traffic-boost/${ postId }` );
-	};
+	}, [navigate, postId]);

 	return (
 		<Tooltip
 			text={ `${ numberOfSuggestions } pending suggestions` }
 			className="suggestion-bubble"
 		>
 			<button
 				className="suggestion-bubble"
 				onClick={ handleClick }
+				aria-label={`View ${numberOfSuggestions} pending suggestions for post ID ${postId}`}
+				role="link"
 			>
 				<span className="suggestion-bubble-number">{ numberOfSuggestions }</span>
 			</button>
 		</Tooltip>
 	);
 };
src/content-helper/dashboard-page/components/posts-table/components/links-overview.tsx (3)

25-39: Simplify conditional rendering logic for inbound links.

The code applies a redundant conditional class. Since you're already using conditional rendering with the ternary operator, you don't need to apply the hidden class in the className attribute.

-			{ inboundLinks > 0 ? (
-				<Tooltip text={ __( 'Inbound Links', 'wp-parsely' ) } >
-					<div className={ `boost-inbound ${ inboundLinks > 0 ? '' : 'hidden' }` }>
-						<Icon size={ 16 } icon={ external } className="boost-link-status-icon-inbound" />
-						{ inboundLinks }
-					</div>
-				</Tooltip>
-			) : (
-				<div className={ `boost-inbound hidden` }>
-					<Icon size={ 16 } icon={ external } className="boost-link-status-icon-inbound" />
-				</div>
-			) }
+			{ inboundLinks > 0 ? (
+				<Tooltip text={ __( 'Inbound Links', 'wp-parsely' ) } >
+					<div className="boost-inbound">
+						<Icon size={ 16 } icon={ external } className="boost-link-status-icon-inbound" />
+						{ inboundLinks }
+					</div>
+				</Tooltip>
+			) : (
+				<div className="boost-inbound hidden">
+					<Icon size={ 16 } icon={ external } className="boost-link-status-icon-inbound" />
+				</div>
+			) }

41-52: Simplify conditional rendering logic for outbound links.

Similar to the inbound links section, the code applies a redundant conditional class. Since you're already using conditional rendering with the ternary operator, you don't need to apply the hidden class in the className attribute.

-			{ outboundLinks > 0 ? (
-				<Tooltip text={ __( 'Outbound Links', 'wp-parsely' ) } >
-					<div className={ `boost-outbound ${ outboundLinks > 0 ? '' : 'hidden' }` }>
-						<Icon size={ 16 } icon={ external } className="boost-link-status-icon-outbound" />
-						{ outboundLinks }
-					</div>
-				</Tooltip>
-			) : (
-				<div className={ `boost-outbound hidden` }>
-					<Icon size={ 16 } icon={ external } className="boost-link-status-icon-outbound" />
-				</div>
-			) }
+			{ outboundLinks > 0 ? (
+				<Tooltip text={ __( 'Outbound Links', 'wp-parsely' ) } >
+					<div className="boost-outbound">
+						<Icon size={ 16 } icon={ external } className="boost-link-status-icon-outbound" />
+						{ outboundLinks }
+					</div>
+				</Tooltip>
+			) : (
+				<div className="boost-outbound hidden">
+					<Icon size={ 16 } icon={ external } className="boost-link-status-icon-outbound" />
+				</div>
+			) }

25-55: Consider enhancing component with accessibility improvements.

To improve accessibility, consider adding ARIA attributes to convey the meaning of these icons to screen readers. Also, since these are informational elements rather than interactive controls, ensure they don't receive keyboard focus unnecessarily.

src/content-helper/dashboard-page/components/posts-table/components/post-details.tsx (1)

37-45: Improve title truncation logic.

The current title truncation doesn't consider word boundaries, which might result in words being cut in the middle. Consider using a utility function that truncates at word boundaries and adds an ellipsis.

-	// If the title is longer than 80 characters, truncate it and add an ellipsis.
-	if ( postTitle !== '' ) {
-		if ( postTitle.length > 80 ) {
-			postTitle = postTitle.substring( 0, 80 ) + '&hellip;';
-		}
-	}
+	// If the title is longer than 80 characters, truncate it at word boundary and add an ellipsis.
+	if ( postTitle !== '' && postTitle.length > 80 ) {
+		// Find the last space within the 80 character limit
+		const lastSpace = postTitle.substring( 0, 80 ).lastIndexOf( ' ' );
+		// If no space found, just cut at 80, otherwise cut at the last space
+		const truncateAt = lastSpace > 0 ? lastSpace : 80;
+		postTitle = postTitle.substring( 0, truncateAt ) + '&hellip;';
+	}
src/content-helper/dashboard-page/components/posts-table/style.scss (2)

79-82: Replace hard-coded red with a design-system variable.

#900000 bypasses the shared colour palette and risks breaking dark-mode / theme overrides.
Use an existing variable (e.g. var(--parsely-red) if available) or add one to variables.scss.

- color: #900000;
+ color: var(--parsely-red);

172-178: Convert fixed px padding to to_rem() as per guidelines.

The SCSS rule mandates converting dimensions ≥ 3 px:

- padding: 3px to_rem(6px);
+ padding: to_rem(3px) to_rem(6px);

This keeps typography scalable and consistent.

src/content-helper/dashboard-page/components/posts-table/component.tsx (1)

29-31: Remove stray import inside JSDoc – it renders in docs and confuses readers.

The extra import { SinglePostRow … } line is inside the comment block, not code, but ships in generated docs and violates the “each line comment ends with a period.” rule.
Delete it to keep docs clean.

src/content-helper/common/providers/stats-provider.tsx (1)

89-94: Prefer explicit class reference in static methods.

Using this inside a static context can be confusing because this is bound to the constructor function rather than an instance. Replacing it with the class name makes the intent crystal-clear and satisfies the Biome rule noThisInStatic.

- if ( ! this.instance ) {
-   this.instance = new StatsProvider();
+ if ( ! StatsProvider.instance ) {
+   StatsProvider.instance = new StatsProvider();
 }
- return this.instance;
+ return StatsProvider.instance;
🧰 Tools
🪛 Biome (1.9.4)

[error] 90-90: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 91-91: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 93-93: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

src/content-helper/dashboard-page/components/posts-table/components/single-post-row.tsx (1)

165-171: Skip boost label when percentage is zero.

Right now trafficBoostPercentage might be 0 (or NaN after the fix). Showing “0.0% BOOSTED” provides no value and may confuse users.

Add a simple guard:

-{ ! isLoadingStats && trafficBoostPercentage > 0 && (
+{ ! isLoadingStats && trafficBoostPercentage > 0 && Number.isFinite( trafficBoostPercentage ) && (
src/content-helper/common/providers/base-wordpress-provider.tsx (1)

274-278: Avoid leaking rest_endpoint to the REST API query string.

rest_endpoint is an internal flag yet it is spread into the addQueryArgs() parameter list, resulting in a query param rest_endpoint=/wp/v2/posts that WordPress will silently ignore but clutters logs and can break caching keys.

-const posts = await this.apiFetch<Post[]>( {
-  path: addQueryArgs( restEndpoint, { ...queryParams, _embed: true, context } ),
+const { rest_endpoint, ...apiParams } = queryParams;
+const posts = await this.apiFetch<Post[]>( {
+  path: addQueryArgs( restEndpoint, { ...apiParams, _embed: true, context } ),

Doing the same for getPages() keeps behaviour consistent.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 99c1087 and f92e49f.

⛔ Files ignored due to path filters (5)
  • build/content-helper/dashboard-page-rtl.css is excluded by !build/**
  • build/content-helper/dashboard-page.asset.php is excluded by !build/**
  • build/content-helper/dashboard-page.css is excluded by !build/**
  • build/content-helper/dashboard-page.js is excluded by !build/**
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (40)
  • package.json (1 hunks)
  • src/Endpoints/class-rest-metadata.php (3 hunks)
  • src/Models/class-inbound-smart-link.php (6 hunks)
  • src/Models/class-smart-link.php (7 hunks)
  • src/content-helper/common/components/thumbnail/component.tsx (1 hunks)
  • src/content-helper/common/providers/base-provider.tsx (1 hunks)
  • src/content-helper/common/providers/base-wordpress-provider.tsx (8 hunks)
  • src/content-helper/common/providers/stats-provider.tsx (1 hunks)
  • src/content-helper/dashboard-page/components/posts-table/component.tsx (6 hunks)
  • src/content-helper/dashboard-page/components/posts-table/components/links-overview.tsx (1 hunks)
  • src/content-helper/dashboard-page/components/posts-table/components/post-details.tsx (1 hunks)
  • src/content-helper/dashboard-page/components/posts-table/components/single-post-row.tsx (1 hunks)
  • src/content-helper/dashboard-page/components/posts-table/components/suggestion-bubble.tsx (1 hunks)
  • src/content-helper/dashboard-page/components/posts-table/style.scss (5 hunks)
  • src/content-helper/dashboard-page/pages/dashboard/page-component.tsx (3 hunks)
  • src/content-helper/dashboard-page/pages/traffic-boost/page-component.tsx (3 hunks)
  • src/content-helper/dashboard-page/pages/traffic-boost/preview/components/link-counter.tsx (1 hunks)
  • src/content-helper/dashboard-page/pages/traffic-boost/preview/components/link-options-panel.tsx (1 hunks)
  • src/content-helper/dashboard-page/pages/traffic-boost/preview/components/preview-header.tsx (1 hunks)
  • src/content-helper/dashboard-page/pages/traffic-boost/preview/preview.tsx (1 hunks)
  • src/content-helper/dashboard-page/pages/traffic-boost/provider.ts (3 hunks)
  • src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/add-new-link-button.tsx (2 hunks)
  • src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/header.tsx (1 hunks)
  • src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/post-details.tsx (1 hunks)
  • src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/tabs/suggestions-tab.tsx (1 hunks)
  • src/content-helper/dashboard-page/pages/traffic-boost/store.ts (1 hunks)
  • src/content-helper/dashboard-page/provider.tsx (1 hunks)
  • src/content-helper/dashboard-widget/provider.ts (1 hunks)
  • src/content-helper/editor-sidebar/excerpt-suggestions/provider.ts (1 hunks)
  • src/content-helper/editor-sidebar/performance-stats/provider.ts (1 hunks)
  • src/content-helper/editor-sidebar/related-posts/provider.ts (1 hunks)
  • src/content-helper/editor-sidebar/smart-linking/provider.ts (1 hunks)
  • src/content-helper/editor-sidebar/title-suggestions/provider.ts (1 hunks)
  • src/rest-api/content-helper/class-endpoint-smart-linking.php (4 hunks)
  • src/rest-api/content-helper/class-endpoint-traffic-boost.php (1 hunks)
  • src/rest-api/stats/class-endpoint-posts.php (3 hunks)
  • src/rest-api/stats/trait-post-data.php (2 hunks)
  • src/services/content-api/class-content-api-service.php (1 hunks)
  • tests/Integration/Endpoints/RestMetadataTest.php (6 hunks)
  • tests/Integration/RestAPI/Stats/EndpointPostsTest.php (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
`**/*.{js,ts,tsx,jsx}`: "Perform a detailed review of the provided code with following key aspects in mind: - Review the code to ensure it is well-structured and adheres to best ...

**/*.{js,ts,tsx,jsx}: "Perform a detailed review of the provided code with following key aspects in mind:

  • Review the code to ensure it is well-structured and adheres to best practices.
  • Verify compliance with WordPress coding standards.
  • Ensure the code is well-documented.
  • Check for security vulnerabilities and confirm the code is secure.
  • Optimize the code for performance, removing any unnecessary elements.
  • Validate JSDoc comments for accuracy, currency, and adherence to WordPress coding standards.
  • Ensure each line comment concludes with a period.
  • Confirm every JSDoc comment includes a @SInCE tag indicating the next version of the plugin to include the code.
  • Guarantee compatibility with the latest version of WordPress, avoiding deprecated functions or features."
  • src/content-helper/editor-sidebar/excerpt-suggestions/provider.ts
  • src/content-helper/dashboard-page/pages/traffic-boost/preview/components/link-options-panel.tsx
  • src/content-helper/editor-sidebar/performance-stats/provider.ts
  • src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/tabs/suggestions-tab.tsx
  • src/content-helper/dashboard-page/pages/traffic-boost/preview/components/preview-header.tsx
  • src/content-helper/common/components/thumbnail/component.tsx
  • src/content-helper/editor-sidebar/smart-linking/provider.ts
  • src/content-helper/editor-sidebar/related-posts/provider.ts
  • src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/header.tsx
  • src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/post-details.tsx
  • src/content-helper/editor-sidebar/title-suggestions/provider.ts
  • src/content-helper/common/providers/base-provider.tsx
  • src/content-helper/dashboard-widget/provider.ts
  • src/content-helper/dashboard-page/pages/traffic-boost/store.ts
  • src/content-helper/dashboard-page/pages/traffic-boost/preview/preview.tsx
  • src/content-helper/dashboard-page/pages/traffic-boost/preview/components/link-counter.tsx
  • src/content-helper/dashboard-page/pages/dashboard/page-component.tsx
  • src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/add-new-link-button.tsx
  • src/content-helper/dashboard-page/pages/traffic-boost/page-component.tsx
  • src/content-helper/dashboard-page/provider.tsx
  • src/content-helper/dashboard-page/components/posts-table/components/suggestion-bubble.tsx
  • src/content-helper/dashboard-page/components/posts-table/components/post-details.tsx
  • src/content-helper/dashboard-page/pages/traffic-boost/provider.ts
  • src/content-helper/dashboard-page/components/posts-table/components/links-overview.tsx
  • src/content-helper/dashboard-page/components/posts-table/components/single-post-row.tsx
  • src/content-helper/common/providers/stats-provider.tsx
  • src/content-helper/dashboard-page/components/posts-table/component.tsx
  • src/content-helper/common/providers/base-wordpress-provider.tsx
`**/*.{html,php}`: "Perform a detailed review of the provided code with following key aspects in mind: - Review the HTML and PHP code to ensure it is well-structured and adheres ...

**/*.{html,php}: "Perform a detailed review of the provided code with following key aspects in mind:

  • Review the HTML and PHP code to ensure it is well-structured and adheres to best practices.
  • Ensure the code follows WordPress coding standards and is well-documented.
  • Confirm the code is secure and free from vulnerabilities.
  • Optimize the code for performance, removing any unnecessary elements.
  • Validate comments for accuracy, currency, and adherence to WordPress coding standards.
  • Ensure each line comment concludes with a period.
  • Verify code compatibility with the latest version of WordPress, avoiding deprecated functions or features."
  • src/services/content-api/class-content-api-service.php
  • src/rest-api/content-helper/class-endpoint-traffic-boost.php
  • tests/Integration/RestAPI/Stats/EndpointPostsTest.php
  • src/rest-api/content-helper/class-endpoint-smart-linking.php
  • src/rest-api/stats/trait-post-data.php
  • src/Endpoints/class-rest-metadata.php
  • src/Models/class-smart-link.php
  • src/Models/class-inbound-smart-link.php
  • src/rest-api/stats/class-endpoint-posts.php
  • tests/Integration/Endpoints/RestMetadataTest.php
`**/*.{css,scss}`: "Perform a detailed review of the provided code with following key aspects in mind: - Review the SCSS code to ensure it is well-structured and adheres to best ...

**/*.{css,scss}: "Perform a detailed review of the provided code with following key aspects in mind:

  • Review the SCSS code to ensure it is well-structured and adheres to best practices.
  • Convert dimensions greater than or equal to 3px to rem units using the to_rem function.
  • Utilize variables for sizes and colors defined in src/content-helper/common/css/variables.scss instead of hardcoding values."
  • src/content-helper/dashboard-page/components/posts-table/style.scss
🧬 Code Graph Analysis (8)
src/rest-api/content-helper/class-endpoint-traffic-boost.php (1)
src/Models/class-smart-link.php (1)
  • flush_all_cache (1193-1201)
src/rest-api/content-helper/class-endpoint-smart-linking.php (1)
src/Models/class-smart-link.php (1)
  • flush_all_cache (1193-1201)
src/content-helper/dashboard-page/components/posts-table/components/post-details.tsx (3)
src/content-helper/common/providers/base-wordpress-provider.tsx (1)
  • HydratedPost (79-85)
src/content-helper/common/components/thumbnail/component.tsx (1)
  • Thumbnail (32-60)
src/content-helper/dashboard-page/components/posts-table/components/suggestion-bubble.tsx (1)
  • SuggestionBubble (30-55)
src/content-helper/dashboard-page/pages/traffic-boost/provider.ts (1)
src/content-helper/common/content-helper-error.tsx (1)
  • ContentHelperError (56-228)
src/content-helper/common/providers/stats-provider.tsx (2)
src/content-helper/common/content-helper-error.tsx (1)
  • ContentHelperError (56-228)
src/content-helper/common/providers/base-wordpress-provider.tsx (1)
  • HydratedPost (79-85)
src/Models/class-smart-link.php (3)
src/Models/class-smart-link-status.php (3)
  • Smart_Link_Status (19-46)
  • get_all_statuses (43-45)
  • is_valid_status (32-34)
src/rest-api/content-helper/class-endpoint-smart-linking.php (1)
  • get_smart_links (273-290)
src/Models/class-inbound-smart-link.php (1)
  • flush_cache_by_post_id (1338-1342)
src/Models/class-inbound-smart-link.php (2)
src/Models/class-smart-link.php (2)
  • flush_all_cache (1193-1201)
  • flush_cache_by_post_id (1222-1239)
src/Models/class-smart-link-status.php (1)
  • Smart_Link_Status (19-46)
tests/Integration/Endpoints/RestMetadataTest.php (3)
tests/Integration/TestCase.php (3)
  • get_permalink (315-323)
  • get_post_in_array (275-283)
  • get_post (249-264)
src/Endpoints/class-rest-metadata.php (1)
  • get_callback (74-130)
src/class-parsely.php (2)
  • Parsely (75-1152)
  • get_canonical_url (1012-1031)
🪛 Biome (1.9.4)
src/content-helper/dashboard-page/components/posts-table/components/post-details.tsx

[error] 56-56: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

src/content-helper/common/providers/stats-provider.tsx

[error] 90-90: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 91-91: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 93-93: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

🔇 Additional comments (54)
src/content-helper/editor-sidebar/performance-stats/provider.ts (1)

15-15: Approve import path alignment.
The BaseProvider import has been updated to the new providers directory, reflecting the reorganized module structure. This aligns with similar updates across other provider modules.

src/content-helper/editor-sidebar/related-posts/provider.ts (1)

9-9: Approve import path update.
The BaseProvider import path has been correctly updated to ../../common/providers/base-provider, matching the repository’s new layout for provider classes.

src/content-helper/common/components/thumbnail/component.tsx (1)

10-10: Approve updated type import.
The HydratedPost type import path has been updated to the providers directory, aligning with the refactored module organization.

src/content-helper/editor-sidebar/smart-linking/provider.ts (1)

11-11: Approve provider import adjustment.
The BaseProvider import path now points to ../../common/providers/base-provider, ensuring consistency with the codebase-wide refactoring of provider modules.

src/content-helper/editor-sidebar/excerpt-suggestions/provider.ts (1)

9-9: Import path correctly updated for BaseProvider.

The import has been adjusted to use the new common/providers directory. Ensure that the file src/content-helper/common/providers/base-provider.ts exists and exports BaseProvider, matching other provider imports.

src/content-helper/dashboard-page/pages/traffic-boost/preview/components/link-options-panel.tsx (1)

16-16: Align HydratedPost import to new providers structure.

The HydratedPost type import now points to the common/providers folder. Confirm that base-wordpress-provider.ts exports HydratedPost and the relative path is correct.

src/content-helper/dashboard-page/provider.tsx (1)

4-4: Update BaseWordPressProvider import path.

The import of BaseWordPressProvider has been redirected to common/providers to match the reorganized module layout. Verify that this module path is valid and consistent across dashboard components.

src/content-helper/dashboard-page/pages/traffic-boost/preview/components/preview-header.tsx (1)

13-13: Correct HydratedPost import location.

The HydratedPost import now references the providers subdirectory. Ensure this aligns with other traffic-boost components and that the provider export is intact.

src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/post-details.tsx (1)

11-11: Adjust HydratedPost import to new path.

The import path for HydratedPost has been updated to the common/providers directory. Confirm consistency with similar refactors in related UI components.

src/content-helper/dashboard-page/pages/traffic-boost/preview/components/link-counter.tsx (1)

12-12: Consistent import path corrected.
The path for HydratedPost now aligns with the new providers directory structure and matches other Traffic Boost components. No functional changes introduced.

src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/header.tsx (1)

11-11: Updated import source for HydratedPost.
Importing from the providers subdirectory standardizes the module path. No changes to component logic.

src/content-helper/common/providers/base-provider.tsx (1)

11-11: Adjusted import path for error classes.
Switched to a parent‐directory import for ContentHelperError and ContentHelperErrorCode, reflecting the updated file hierarchy. Behaviour is unchanged.

src/content-helper/dashboard-page/pages/traffic-boost/store.ts (2)

10-10: Standardize HydratedPost import path.
Now references the providers directory, in line with other refactors.


12-12: Reordered TrafficBoostLink import.
Moving this import below LinkType groups related imports logically. No functional impact.

src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/tabs/suggestions-tab.tsx (1)

14-14: Corrected provider import path for HydratedPost.
Aligns with the reorganized common/providers module layout; logic remains intact.

src/content-helper/dashboard-widget/provider.ts (1)

14-15: Import paths reorganized correctly.

The import paths have been updated to reflect the new directory structure, moving from direct imports to the new modular organization under the 'providers' directory. This change maintains consistency with other files in the codebase.

src/content-helper/editor-sidebar/title-suggestions/provider.ts (1)

10-11: Import paths properly restructured.

The import paths have been updated to reflect the new directory structure, moving BaseProvider to the 'providers' subdirectory. The reordering of imports also improves code organization by grouping related imports together.

src/rest-api/content-helper/class-endpoint-traffic-boost.php (1)

827-829: Good addition for cache invalidation.

Adding the cache flush after successfully applying a smart link ensures that subsequent API calls will reflect the latest state of the link, which is critical for consistent data across the application. This is especially important since the link is being changed in the system.

src/content-helper/dashboard-page/pages/traffic-boost/preview/preview.tsx (1)

15-15: Import path correctly updated.

The import path for HydratedPost has been updated to reflect the new directory structure, maintaining consistency with other provider-related imports across the codebase.

tests/Integration/RestAPI/Stats/EndpointPostsTest.php (1)

325-334: New parameter validation for posts endpoint.

This change updates the test to validate that the new use_wp_permalink parameter is correctly included in the API response with its default value set to false. This parameter allows controlling whether URLs are returned as WordPress permalinks or canonical URLs.

src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/add-new-link-button.tsx (2)

14-14: Import path correction.

The import path has been updated to reflect the new file structure, using the provider from the dedicated providers directory.


98-112: Updated PostsTable component with external pagination and context.

The component has been updated with three important additions:

  1. External pagination controls (currentPage and setCurrentPage)
  2. Setting context: 'edit' in the query parameters
  3. Adding hideSuggestionBubble to control UI element visibility

This aligns with the broader refactoring that externalizes pagination state management.

src/content-helper/dashboard-page/pages/dashboard/page-component.tsx (4)

9-9: Added useState import for pagination management.

The component now imports the useState hook from WordPress element to manage pagination state.


15-15: Updated import path for DashboardHeader component.

The import path has been simplified, removing unnecessary relative path segments.


66-66: Added external pagination state management.

The component now maintains pagination state using useState, initializing currentPage to 1. This state will be passed to the PostsTable component.


83-90: Updated PostsTable component with pagination props and explicit query parameters.

The PostsTable component now receives external pagination control via the currentPage and setCurrentPage props, along with explicit query parameters for fetching published posts with a limit of 5 per page.

This change aligns with the broader refactoring to externalize pagination state management across the application.

src/rest-api/content-helper/class-endpoint-smart-linking.php (4)

346-348: Added cache invalidation after adding a smart link.

Cache flushing has been added immediately after saving a smart link to ensure data consistency. This maintains the integrity of count-based metrics and ensures that newly added links appear in statistics immediately.


405-406: Added cache invalidation in bulk smart link addition.

Cache flushing is now performed for each smart link after it's successfully saved in the bulk addition process. This maintains data consistency and ensures metrics are up-to-date.


478-479: Added cache invalidation after removing a smart link.

Cache flushing has been added after deleting a smart link to ensure data consistency. This maintains the integrity of count-based metrics and ensures that removed links are immediately reflected in statistics.


500-501: Added cache invalidation for each smart link in set operation.

Cache flushing is now performed for each smart link after it's successfully saved in the set_smart_links operation. This ensures that any cached data is properly invalidated after modifications.

src/content-helper/dashboard-page/pages/traffic-boost/provider.ts (3)

6-6: Import path updated to reflect new module structure.

The import path has been updated to use the new providers directory structure, which improves code organization by grouping related provider classes together.


267-274: Improved error handling for abort errors.

The error handling has been enhanced to specifically identify and propagate abort errors, which is crucial for proper cancellation handling in asynchronous operations. This change correctly distinguishes between abort-related errors (which should be re-thrown) and other errors (which are logged and lead to retry decrements).

This implementation is robust as it handles both DOM AbortError exceptions and ContentHelperError instances with the specific code.


613-613: Added 'edit' context to request options.

Adding the context: 'edit' parameter to the getPosts request ensures the API returns enriched metadata, which is necessary for the traffic boost feature to properly display information like suggestion counts.

src/services/content-api/class-content-api-service.php (1)

191-191: Updated PHPDoc parameter type to support nested array structures.

The type annotation for the $params parameter has been refined to explicitly allow for nested arrays, which correctly documents the more complex parameter structures now being passed to the content API service, such as campaign parameters.

This change improves code documentation accuracy without modifying the actual implementation logic.

src/content-helper/dashboard-page/pages/traffic-boost/page-component.tsx (4)

22-25: Enhanced search functionality to reset pagination.

The debounced search function now appropriately resets the current page to 1 when a search is performed, which provides a better user experience by showing the first page of search results.


27-27: Added state management for pagination.

Adding state management for the current page allows the component to control pagination externally and pass it down to the PostsTable component, which is a good pattern for state management.


45-47: Lifted pagination state to parent component.

Passing the pagination state and setter to the PostsTable component enables external control of pagination, which is a good React pattern for lifting state up when needed by parent components.


51-51: Enhanced search functionality with targeted column search.

Adding the search_columns parameter to target both post titles and excerpts provides more comprehensive search results for users, improving the overall search experience.

src/content-helper/dashboard-page/components/posts-table/components/links-overview.tsx (2)

1-16: Type definition looks good.

The type definition for LinksOverviewProps is well-structured with proper JSDoc comments and an appropriate @since tag indicating the version this component will be introduced in.


18-24: Good documentation of the component purpose.

The JSDoc for the component itself provides a clear description of its purpose and properly references the type definition using the @param tag.

src/content-helper/dashboard-page/components/posts-table/components/post-details.tsx (3)

1-23: Props interface is well-defined.

The PostDetailsProps type definition is clear and includes appropriate JSDoc comments with the correct @since tag. The use of an optional boolean prop with a default value is a good practice.


59-61: Good conditional rendering of SuggestionBubble.

The component correctly renders the SuggestionBubble only when there are suggestions and when the showSuggestionBubble prop is true. This creates a clean and flexible API for the component.


55-57: ⚠️ Potential issue

Address potential XSS vulnerability with dangerouslySetInnerHTML.

Using dangerouslySetInnerHTML with post content can expose users to cross-site scripting (XSS) attacks if the content contains malicious code. Consider sanitizing the HTML content or using a safer method to render the title.

-					{ postTitle !== ''
-						? <span title={ post.title.rendered } dangerouslySetInnerHTML={ { __html: postTitle } }	/>
-						: __( '(no title)', 'wp-parsely' )
-					}
+					{ postTitle !== ''
+						? <span title={ post.title.rendered }>{ wp.element.RawHTML ? 
+							<wp.element.RawHTML>{ postTitle }</wp.element.RawHTML> : 
+							postTitle.replace(/&hellip;/g, '...') }
+						</span>
+						: __( '(no title)', 'wp-parsely' )
+					}
⛔ Skipped due to learnings
Learnt from: vaurdan
PR: Parsely/wp-parsely#3149
File: src/content-helper/dashboard-page/pages/traffic-boost/preview/components/preview-header.tsx:152-152
Timestamp: 2025-03-06T10:24:43.274Z
Learning: The WordPress REST API sanitizes HTML content in post titles before it's returned, so using dangerouslySetInnerHTML with title.rendered is considered acceptable within this codebase despite typical security concerns.
Learnt from: vaurdan
PR: Parsely/wp-parsely#3149
File: src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/links-list/single-link.tsx:76-79
Timestamp: 2025-03-06T10:24:30.310Z
Learning: Properties with the `.rendered` suffix from WordPress REST API (like `post.title.rendered`, `post.content.rendered`, etc.) are pre-sanitized by WordPress and safe to use with `dangerouslySetInnerHTML` in React components.
🧰 Tools
🪛 Biome (1.9.4)

[error] 56-56: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

src/rest-api/stats/trait-post-data.php (3)

104-106: Good implementation of new recirculation_rate metric.

The new code correctly extracts and formats the recirculation_rate metric from the metrics array, following the same pattern as the existing metrics.


123-142: Well-structured campaign metrics extraction.

The code for handling campaign metrics follows the established pattern for regular metrics, making it consistent and maintainable. The extraction and formatting of each metric is clear and properly documented.


144-153: Improved URL handling with canonical URL update.

The replacement of the direct call to url_to_postid with Utils::get_post_id_by_url is a good improvement. The code now also updates the canonical URL when a valid post ID is found, which helps maintain consistency in the data.

However, consider adding error logging for cases where a valid post ID can't be found:

 			// If we have a post ID, update the post canonical URL.
 			if ( 0 !== $post_id ) {
 				Parsely::set_canonical_url( $post_id, $post_url );
+			} else {
+				// Log that we couldn't find a post ID for this URL if debugging is enabled
+				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
+					error_log( sprintf( 'Parse.ly: Could not find post ID for URL: %s', $item['url'] ) );
+				}
 			}
tests/Integration/Endpoints/RestMetadataTest.php (5)

197-216: Good test updates for canonical_url and smart links.

The test correctly validates the presence of the new fields in the REST API response, including canonical_url, smart_links with inbound and outbound counts, and traffic_boost_suggestions_count. The use of explicit type casting for the permalink is also a good practice.


264-277: Consistent test implementation for filtered responses.

The test for filtered responses follows the same pattern as the main test, ensuring that the new fields are properly included even when certain parts of the response are filtered out.


326-343: Well-structured test for URL filtering.

The test for URL filtering validates that the new fields are still present when URL tracking is disabled, maintaining consistency with the rest of the test suite.


365-372: Good handling of non-existent post case.

The test for non-existent posts correctly verifies that the new fields are present with appropriate default values when the post doesn't exist, ensuring robust error handling.


420-424: Improved type documentation for post IDs.

The addition of explicit type annotations for the post ID variables enhances code clarity and helps maintain type safety throughout the codebase.

Also applies to: 488-492

src/Endpoints/class-rest-metadata.php (1)

123-128: Gracefully handle possible WP_Error returns from helper methods.

Smart_Link::get_link_counts() and Inbound_Smart_Link::get_suggestions_count() currently return scalars, but nothing in their contract guarantees they will never surface a WP_Error.
Guarding against this prevents a fatal JSON-encoding failure:

-$response['smart_links'] = Smart_Link::get_link_counts( $post_id, Smart_Link_Status::APPLIED );
+$counts = Smart_Link::get_link_counts( $post_id, Smart_Link_Status::APPLIED );
+if ( ! is_wp_error( $counts ) ) {
+	$response['smart_links'] = $counts;
+}

The same pattern applies to the suggestions count.

src/content-helper/dashboard-page/components/posts-table/component.tsx (1)

198-216: Potential duplicate-stat entries when retrying with permalinks.

onErrorLoadingStats pushes the new stat unconditionally; if the first call later succeeds you’ll have two entries for the same post ID.
Guard before pushing:

-setStats( ( prev ) => [ ...prev, fetchedStats[ 0 ] ] );
+setStats( ( prev ) =>
+	prev.some( ( s ) => s.postId === post.id )
+		? prev
+		: [ ...prev, fetchedStats[ 0 ] ]
+);
src/rest-api/stats/class-endpoint-posts.php (1)

190-216: Add validation / sanitization callbacks for new campaign parameters.

Unlike author, section, etc., the five new campaign_* parameters lack sanitize_callback and validate_callback.
At minimum these should be:

'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => '__return_true',

This prevents arbitrary HTML injection via the REST API.

Would you like me to open a follow-up PR that adds proper callbacks to all campaign parameters?

Copy link
Collaborator

@acicovic acicovic left a comment

Choose a reason for hiding this comment

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

Hey @alecgeatches, thank you so much for picking this up and continuing the work.

I'm halfway through my review and leaving a couple of comments. I'll also be pushing a nitpicking PR (spacing/comments) very shortly.

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

♻️ Duplicate comments (1)
src/content-helper/common/providers/stats-provider.tsx (1)

133-143: urls may be unintentionally overridden – spread order matters.

By spreading args after the { urls: postURLs } object, a caller could pass its own urls array and silently shadow the URLs derived from posts, defeating the method's purpose.

- return this.getStats( { urls: postURLs, ...args } );
+ return this.getStats( { ...args, urls: postURLs } );

This tiny re-ordering guarantees that the computed postURLs always win while still allowing other parameters to be overridden.

🧹 Nitpick comments (2)
src/content-helper/common/providers/stats-provider.tsx (2)

79-95: Address static this usage in the singleton implementation.

The static analysis tool flagged the use of this in a static context which can lead to confusion.

public static getInstance(): StatsProvider {
-	if ( ! this.instance ) {
-		this.instance = new StatsProvider();
-	}
-	return this.instance;
+	if ( ! StatsProvider.instance ) {
+		StatsProvider.instance = new StatsProvider();
+	}
+	return StatsProvider.instance;
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 90-90: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 91-91: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 94-94: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


133-140: Consider adding null/empty array check for posts parameter.

There's no validation to ensure that the posts array is not empty. If an empty array is passed, the method will make an API call with an empty URLs array, which may not be intended behavior.

public async getStatsForPosts( posts: HydratedPost[], args: StatsRequestParams ): Promise<PostStats[]> {
+	if (!posts || posts.length === 0) {
+		throw new ContentHelperError(
+			__( 'No posts provided to get stats for.', 'wp-parsely' ),
+			ContentHelperErrorCode.InvalidInput
+		);
+	}
	const postURLs = posts.map( ( post ) => {
		if ( args.use_wp_permalink ) {
			return post.link;
		}

		return post.parsely?.canonical_url ?? post.link;
	} );

	return this.getStats( { urls: postURLs, ...args } );
}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f92e49f and 26d2d3c.

📒 Files selected for processing (5)
  • src/Models/class-inbound-smart-link.php (6 hunks)
  • src/content-helper/common/providers/stats-provider.tsx (1 hunks)
  • src/content-helper/dashboard-page/components/posts-table/components/links-overview.tsx (1 hunks)
  • src/content-helper/dashboard-page/pages/dashboard/page-component.tsx (4 hunks)
  • src/content-helper/dashboard-page/pages/traffic-boost/provider.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/content-helper/dashboard-page/pages/traffic-boost/provider.ts
  • src/content-helper/dashboard-page/pages/dashboard/page-component.tsx
  • src/content-helper/dashboard-page/components/posts-table/components/links-overview.tsx
  • src/Models/class-inbound-smart-link.php
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{js,ts,tsx,jsx}`: "Perform a detailed review of the provided code with following key aspects in mind: - Review the code to ensure it is well-structured and adheres to best ...

**/*.{js,ts,tsx,jsx}: "Perform a detailed review of the provided code with following key aspects in mind:

  • Review the code to ensure it is well-structured and adheres to best practices.
  • Verify compliance with WordPress coding standards.
  • Ensure the code is well-documented.
  • Check for security vulnerabilities and confirm the code is secure.
  • Optimize the code for performance, removing any unnecessary elements.
  • Validate JSDoc comments for accuracy, currency, and adherence to WordPress coding standards.
  • Ensure each line comment concludes with a period.
  • Confirm every JSDoc comment includes a @SInCE tag indicating the next version of the plugin to include the code.
  • Guarantee compatibility with the latest version of WordPress, avoiding deprecated functions or features."
  • src/content-helper/common/providers/stats-provider.tsx
🪛 Biome (1.9.4)
src/content-helper/common/providers/stats-provider.tsx

[error] 90-90: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 91-91: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 94-94: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: build
  • GitHub Check: E2E against WordPress latest
  • GitHub Check: build
🔇 Additional comments (6)
src/content-helper/common/providers/stats-provider.tsx (6)

1-13: Import structure looks good.

The file maintains clean separation between WordPress and internal dependencies with proper JSDoc blocks. This follows WordPress coding standards.


14-24: Well-defined PostMetrics interface with proper documentation.

The interface provides a clear type definition for metrics data with appropriate JSDoc including the required @SInCE tag.


26-42: PostStats interface looks good with campaign metrics support.

The interface properly extends PostMetrics and includes the new campaign field to support traffic boost tracking. All fields are properly typed and documented.


44-70: StatsRequestParams interface includes necessary campaign tracking parameters.

The interface correctly implements all the required parameters for campaign tracking (campaign_id, campaign_medium, campaign_source, campaign_content, campaign_term, itm_source) which aligns with the PR objective to track traffic boost links.


106-121: The getStats implementation looks good.

The method properly handles error cases by throwing a ContentHelperError when no data is returned. The WordPress internationalization function is correctly used for the error message.


134-140: Good fallback mechanism for URLs.

The code correctly handles cases where Parse.ly canonical URLs might not be available by falling back to the post link, ensuring robust behavior.

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/content-helper/dashboard-page/components/posts-table/component.tsx (2)

155-166: Consider extracting campaign parameters as constants.

The campaign tracking parameters like 'wp-parsely', 'smart-link' appear to be hard-coded values that align with the PR objectives. For better maintainability, consider extracting these as constants.

+// Campaign tracking parameters
+const CAMPAIGN_ID = 'wp-parsely';
+const CAMPAIGN_MEDIUM = 'smart-link';
+const PERIOD_START = '30d';
+
 const [ isLoadingStats, setIsLoadingStats ] = useState<boolean>( true );
 const didFirstSearch = useRef( false );

 /**
  * Fetches posts from the API, using the query and pagination.
  *
  * @since 3.18.0
  */
 useEffect( () => {
   const fetchPosts = async () => {
     try {
       const fetchedPosts = await DashboardProvider.getInstance().getPosts( {
         context: 'embed',
         ...query,
         per_page: itemsPerPage,
         page: currentPage,
       } );

       if ( ! hideStats ) {
         // Fetch the stats for the posts.
         const fetchedStats = await StatsProvider.getInstance().getStatsForPosts( fetchedPosts.data, {
           limit: fetchedPosts.data.length,
-          period_start: '30d',
+          period_start: PERIOD_START,
           page: 1,
-          campaign_id: 'wp-parsely',
-          campaign_medium: 'smart-link',
+          campaign_id: CAMPAIGN_ID,
+          campaign_medium: CAMPAIGN_MEDIUM,
         } );

200-207: Duplicate campaign parameters should be extracted as constants.

Similar to the previous suggestion, these campaign parameters should use the same constants to ensure consistency.

 try {
   // Try to fetch the stats again for this URL, but instead try with the WordPress permalink.
   // The API will then try to convert the permalink to a canonical URL, and use
   // both the permalink and the canonical URL to fetch the stats.
   const fetchedStats = await StatsProvider.getInstance().getStatsForPosts( [ post ], {
     limit: 1,
-    period_start: '30d',
+    period_start: PERIOD_START,
     page: 1,
-    campaign_id: 'wp-parsely',
-    campaign_medium: 'smart-link',
+    campaign_id: CAMPAIGN_ID,
+    campaign_medium: CAMPAIGN_MEDIUM,
     use_wp_permalink: true,
   } );
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc404bc and fb84dbf.

📒 Files selected for processing (1)
  • src/content-helper/dashboard-page/components/posts-table/component.tsx (6 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{js,ts,tsx,jsx}`: "Perform a detailed review of the provided code with following key aspects in mind: - Review the code to ensure it is well-structured and adheres to best ...

**/*.{js,ts,tsx,jsx}: "Perform a detailed review of the provided code with following key aspects in mind:

  • Review the code to ensure it is well-structured and adheres to best practices.
  • Verify compliance with WordPress coding standards.
  • Ensure the code is well-documented.
  • Check for security vulnerabilities and confirm the code is secure.
  • Optimize the code for performance, removing any unnecessary elements.
  • Validate JSDoc comments for accuracy, currency, and adherence to WordPress coding standards.
  • Ensure each line comment concludes with a period.
  • Confirm every JSDoc comment includes a @SInCE tag indicating the next version of the plugin to include the code.
  • Guarantee compatibility with the latest version of WordPress, avoiding deprecated functions or features."
  • src/content-helper/dashboard-page/components/posts-table/component.tsx
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: E2E against WordPress latest
🔇 Additional comments (10)
src/content-helper/dashboard-page/components/posts-table/component.tsx (10)

9-9: Code looks good, but could add JSDoc comments for imported hooks.

The updated import now includes useCallback, useEffect, useRef, and useState hooks, which is appropriate for the component's enhanced functionality.


19-22: Imports look good, properly organized for new functionality.

The added imports for HydratedPost, QueryParams, PostStats, and StatsProvider support the new stats tracking functionality, and the SinglePostRow import facilitates the component refactoring.


93-94: Good architectural change moving pagination control to parent component.

Lifting state up by making currentPage and setCurrentPage required props enables better control from parent components and improves component reusability.


98-99: Well-structured configuration flags for UI customization.

Adding hideStats and hideActions as configurable props improves the component's flexibility and allows for different use cases.


131-131: Good separation of loading states for posts and stats.

The separate state variables for posts and stats loading provides better granularity for UI feedback.

Also applies to: 137-137


184-184: Ensure dependency array is correct for the useEffect hook.

The addition of hideStats to the dependency array is correct since the effect's behavior changes based on this prop.


195-213: Well-implemented error recovery for stats loading.

The onErrorLoadingStats callback function is a good implementation of graceful degradation. It attempts to fetch stats using WordPress permalink when the initial attempt fails, providing better user experience.


241-243: Good use of the useCallback hook for optimization.

The getStatsForPost helper function is correctly memoized with useCallback to prevent unnecessary re-renders when passed to child components.


290-295: Table headers correctly updated to reflect the new stats functionality.

The table header is now appropriately labeled as "VIEWS (30 days)" to match the stats being displayed.


302-313: Good refactoring to use the SinglePostRow component.

Replacing inline JSX with a dedicated component improves code organization and maintainability. All necessary props are correctly passed to the component.

acicovic
acicovic previously approved these changes May 9, 2025
@acicovic acicovic dismissed their stale review May 9, 2025 13:55

I hit the "Approve" button by mistake, I meant to hit "Comment".

@acicovic acicovic self-requested a review May 9, 2025 13:55
@acicovic
Copy link
Collaborator

acicovic commented May 9, 2025

Also, a few notes/observations about caching:

  1. I'm noticing this CodeRabbit comment, which hasn't been dismissed (resolved as fixed or unnecessary). Is this one to be reviewed or needs discussion?

  2. I see we're using MONTH_IN_SECONDS here. Was there a particular thought leading to that choice of time? The aim of this question is understanding, not questioning the choice.

  3. I see that generally our caching naming scheme/methodology might be a bit inconsistent. Searching for functions such as wp_cache_delete, wp_cache_get and wp_cache_delete can reveal this. Some examples:

    • In some cases we use functions, in others constants (for the cache group) or variables. Not the end of the world though, it might make sense depending on the situation.
    • We use a mix of cache keys with hyphens or underscores.
    • Cache keys and groups can be a bit inconsistent.
    • I'm wondering if we should have a standard prefix to all of them.
  4. @alecgeatches, you had noted that indefinite caching could be sub suboptimal. We could maybe work around this by clearing the cache when a post gets updated (e.g. on save_post).

I don't think that addressing all the above questions is within the scope of this PR, but addressing what we can in the new code in order not to further increase any inconsistencies would definitely be desirable.

I'd be interested to hear your thoughts on all of these.

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

♻️ Duplicate comments (1)
src/content-helper/dashboard-page/components/posts-table/component.tsx (1)

290-296: 🛠️ Refactor suggestion

Make the view period string translatable.

The "(30 days)" string should be translatable to ensure proper localization.

- <span className="views-header-period">{ __( '(30 days)', 'wp-parsely' ) }</span>
+ <span className="views-header-period">
+   { /* translators: %s: number of days */ }
+   { __( '(%s days)', 'wp-parsely' ).replace( '%s', '30' ) }
+ </span>
🧹 Nitpick comments (1)
src/content-helper/dashboard-page/components/posts-table/component.tsx (1)

186-213: Robust error handling for stats loading.

The onErrorLoadingStats callback provides a good fallback mechanism when initial stats loading fails by using WordPress permalinks. This aligns with the PR objective of tracking link performance reliably.

Consider adding user-facing error messaging instead of just logging to console, especially for production environments.

 } catch ( error ) {
 	console.error( error ); // eslint-disable-line no-console
+	// Consider adding user-facing error notification here
 }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb84dbf and f3c3431.

⛔ Files ignored due to path filters (2)
  • build/content-helper/dashboard-page.asset.php is excluded by !build/**
  • build/content-helper/dashboard-page.js is excluded by !build/**
📒 Files selected for processing (2)
  • src/content-helper/dashboard-page/components/posts-table/component.tsx (6 hunks)
  • src/content-helper/dashboard-page/components/posts-table/components/single-post-row.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/content-helper/dashboard-page/components/posts-table/components/single-post-row.tsx
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{js,ts,tsx,jsx}`: "Perform a detailed review of the provided code with following key aspects in mind: - Review the code to ensure it is well-structured and adheres to best ...

**/*.{js,ts,tsx,jsx}: "Perform a detailed review of the provided code with following key aspects in mind:

  • Review the code to ensure it is well-structured and adheres to best practices.
  • Verify compliance with WordPress coding standards.
  • Ensure the code is well-documented.
  • Check for security vulnerabilities and confirm the code is secure.
  • Optimize the code for performance, removing any unnecessary elements.
  • Validate JSDoc comments for accuracy, currency, and adherence to WordPress coding standards.
  • Ensure each line comment concludes with a period.
  • Confirm every JSDoc comment includes a @SInCE tag indicating the next version of the plugin to include the code.
  • Guarantee compatibility with the latest version of WordPress, avoiding deprecated functions or features."
  • src/content-helper/dashboard-page/components/posts-table/component.tsx
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: E2E against WordPress latest
🔇 Additional comments (11)
src/content-helper/dashboard-page/components/posts-table/component.tsx (11)

9-9: Import organization looks good.

The imports are now properly organized with all necessary hooks from WordPress element.


19-22: Properly structured imports for new statistics functionality.

The imports for new types and components are well-organized. The SinglePostRow component is now correctly imported once at the top of the file.


93-94: Good refactoring to external pagination control.

Making the pagination state externally controlled improves component reusability and allows parent components to manage the state appropriately.


98-99: Appropriate addition of toggle props for UI elements.

The new hideStats and hideActions props provide good flexibility for controlling what UI elements are displayed.


117-118: Props correctly destructured from new interface.

The props are properly destructured to match the updated PostsTableType interface.

Also applies to: 122-123


131-132: Well-structured state management for stats functionality.

The new state variables for stats and loading state are properly initialized.

Also applies to: 137-138


155-169: Stats fetching logic looks well implemented.

The conditional stats fetching based on the hideStats prop is appropriately implemented. The campaign parameters align with PR objectives for tracking Traffic Boost links.


176-177: Loading state management is properly handled.

Both isLoading and isLoadingStats states are correctly reset and updated during the fetch process, and hideStats is correctly included in the dependencies array.

Also applies to: 181-184


233-243: Well-implemented stats helper function.

The getStatsForPost helper function is properly memoized with useCallback and efficiently finds stats for a specific post based on its ID.


302-313: Good refactoring to use SinglePostRow component.

The refactoring to use the SinglePostRow component improves code modularity and maintainability. All necessary props are passed down correctly, including the stats data and loading states.


149-153:

Details

❓ Verification inconclusive

Changed context parameter to 'embed'.

Using 'embed' context can improve performance by requesting less data. Make sure this context provides all the necessary fields needed for rendering.


🏁 Script executed:

#!/bin/bash
# Check what fields are included in the 'embed' context versus other contexts
rg -A 5 -B 5 "register_rest_field.*post" --glob "*.php"

Length of output: 57


Confirm required fields are present when using ‘embed’ context

Changing context to 'embed' reduces payload size by returning only embedded fields. Before merging, double-check that all properties the PostsTable component relies on are still included in the REST response, for example:

  • id
  • date (or date_gmt)
  • title.rendered
  • excerpt.rendered
  • slug
  • link
  • _embedded.author[0].name
  • _embedded['wp:featuredmedia'][0].source_url

Inspect the API response in your browser’s network tab or via a curl request to confirm.

@alecgeatches
Copy link
Contributor Author

Good questions and comments, @acicovic!

  1. I'm noticing Add traffic boost post list stats #3293 (comment), which hasn't been dismissed (resolved as fixed or unnecessary). Is this one to be reviewed or needs discussion?

I have now replied to that comment, the area of concern is actually cached.

  1. I see we're using MONTH_IN_SECONDS here. Was there a particular thought leading to that choice of time? The aim of this question is understanding, not questioning the choice.

MONTH_IN_SECONDS was sort of arbitrary. A bot was complaining about an unbounded wp_cache_set() without an expiration, and I agree that this is typically not desirable. In this code, it's fairly low-impact to recalculate but isn't likely to change. I picked a month as a medium ground "long enough" period to benefit from caching, but not exist forever.

It's possible to rely on side-effects to clear cache (e.g. save_post action), but some actions may not fire when we expect (like on database edits), or if our plugin is disabled they may never get called.

If we update the other cache uses, I think MONTH_IN_SECONDS is a decent upper bound alternative to a cache that never expires.

  1. I see that generally our caching naming scheme/methodology might be a bit inconsistent. Searching for functions such as wp_cache_delete, wp_cache_get and wp_cache_delete can reveal this.

This is very true. I'm going to have a go at standardizing these uses.

  1. @alecgeatches, you had noted that indefinite caching could be sub suboptimal. We could maybe work around this by clearing the cache when a post gets updated (e.g. on save_post).

I think this, along with a reasonable upper bound on cache expiration, is the correct way to do things.

@acicovic
Copy link
Collaborator

acicovic commented May 9, 2025

Hey @alecgeatches, thank you for your comments. Everything makes sense!

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: 3

🔭 Outside diff range comments (4)
src/Metadata/class-metadata-builder.php (1)

413-420: ⚠️ Potential issue

Guard against $parent not being a WP_Term.

After tightening the loop condition, the function can still exit with $parent === false (e.g. the original term has been deleted).
Accessing $parent->name on line 419 would then trigger a fatal error.

-		return $parent->name ?? false;
+	// Bail out early if the term chain could not be resolved.
+	if ( ! ( $parent instanceof WP_Term ) ) {
+		return false;
+	}
+
+	return $parent->name;
src/services/class-cached-service-endpoint.php (1)

88-100: ⚠️ Potential issue

Pass resolved cache-group instead of a possibly undefined constant.

Same undefined-constant issue surfaces at every wp_cache_get/set call.

-		$cache     = wp_cache_get( $cache_key, PARSELY_CACHE_GROUP );
+		$group = defined( 'Parsely\\PARSELY_CACHE_GROUP' ) ? PARSELY_CACHE_GROUP : 'wp-parsely';
+		$cache = wp_cache_get( $cache_key, $group );-			wp_cache_set( $cache_key, $response, PARSELY_CACHE_GROUP, $this->cache_ttl );
+			wp_cache_set( $cache_key, $response, $group, $this->cache_ttl );
🧰 Tools
🪛 GitHub Actions: Integration Tests

[error] 89-89: Undefined constant "Parsely\PARSELY_CACHE_GROUP" error in multiple RestAPI Stats endpoint tests.

src/Utils/class-utils.php (1)

430-444: 🛠️ Refactor suggestion

Cache path via wpcom_vip_url_to_postid() is not cached.

When the VIP helper exists, the result (success or miss) isn’t stored, causing repeated look-ups every call.

-			$post_id = wpcom_vip_url_to_postid( $url );
+			$post_id = wpcom_vip_url_to_postid( $url );
+			// Cache VIP result for one week to match the other branches.
+			wp_cache_set( $cache_key, $post_id, PARSELY_CACHE_GROUP, WEEK_IN_SECONDS );
🧰 Tools
🪛 GitHub Actions: Integration Tests

[error] 431-431: Undefined constant "Parsely\PARSELY_CACHE_GROUP" error in EndpointSmartLinking and EndpointSuggestLinkedReference tests.

src/Models/class-smart-link.php (1)

1043-1049: 🛠️ Refactor suggestion

Invalid orderby value will be ignored by WP_Query.

'date modified' is not a recognised orderby parameter.
Use 'modified' (modified date) or 'modified_gmt'.

-				'orderby' => 'date modified',
+				'orderby' => 'modified',
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f3c3431 and 73d4020.

📒 Files selected for processing (7)
  • src/Endpoints/class-graphql-metadata.php (1 hunks)
  • src/Metadata/class-metadata-builder.php (2 hunks)
  • src/Models/class-inbound-smart-link.php (8 hunks)
  • src/Models/class-smart-link.php (14 hunks)
  • src/Utils/class-utils.php (4 hunks)
  • src/services/class-cached-service-endpoint.php (4 hunks)
  • wp-parsely.php (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • wp-parsely.php
  • src/Endpoints/class-graphql-metadata.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Models/class-inbound-smart-link.php
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{html,php}`: "Perform a detailed review of the provided code with following key aspects in mind: - Review the HTML and PHP code to ensure it is well-structured and adheres ...

**/*.{html,php}: "Perform a detailed review of the provided code with following key aspects in mind:

  • Review the HTML and PHP code to ensure it is well-structured and adheres to best practices.
  • Ensure the code follows WordPress coding standards and is well-documented.
  • Confirm the code is secure and free from vulnerabilities.
  • Optimize the code for performance, removing any unnecessary elements.
  • Validate comments for accuracy, currency, and adherence to WordPress coding standards.
  • Ensure each line comment concludes with a period.
  • Verify code compatibility with the latest version of WordPress, avoiding deprecated functions or features."
  • src/services/class-cached-service-endpoint.php
  • src/Utils/class-utils.php
  • src/Metadata/class-metadata-builder.php
  • src/Models/class-smart-link.php
🧠 Learnings (1)
src/Models/class-smart-link.php (2)
Learnt from: alecgeatches
PR: Parsely/wp-parsely#3293
File: src/Models/class-smart-link.php:1099-1105
Timestamp: 2025-05-08T18:42:19.757Z
Learning: When using WP_Query to count posts rather than retrieve them, setting 'posts_per_page' => 0 is a performance optimization. This instructs WordPress to calculate found_posts without retrieving any actual post objects, which is more efficient than using -1 when only needing a count.
Learnt from: alecgeatches
PR: Parsely/wp-parsely#3293
File: src/Models/class-inbound-smart-link.php:1067-1081
Timestamp: 2025-05-08T18:20:50.947Z
Learning: When using WP_Query only to get the count of posts via $query->found_posts, setting posts_per_page to 0 is more efficient than -1 as it minimizes data retrieval while still providing an accurate count.
🪛 GitHub Actions: Integration Tests
src/services/class-cached-service-endpoint.php

[error] 89-89: Undefined constant "Parsely\PARSELY_CACHE_GROUP" error in multiple RestAPI Stats endpoint tests.

src/Utils/class-utils.php

[error] 431-431: Undefined constant "Parsely\PARSELY_CACHE_GROUP" error in EndpointSmartLinking and EndpointSuggestLinkedReference tests.

src/Models/class-smart-link.php

[error] 1250-1250: Undefined constant "Parsely\PARSELY_CACHE_GROUP" used causing errors in multiple tests.


[error] 1097-1097: Undefined constant "Parsely\PARSELY_CACHE_GROUP" referenced in call stack.


[error] 738-738: Undefined constant "Parsely\PARSELY_CACHE_GROUP" error in EndpointSmartLinking and EndpointSuggestLinkedReference tests.

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: E2E against WordPress latest
🔇 Additional comments (2)
src/Metadata/class-metadata-builder.php (1)

16-16: Type import is correct – nice improvement.

Explicitly importing WP_Term makes the new type-check below self-documenting and prevents accidental symbol clashes.

src/services/class-cached-service-endpoint.php (1)

71-74: 👍 Cache-key algorithm simplified and collision-resistant.

Using a single SHA-1 over a formatted string is both shorter and avoids repeated wp_hash calls. Nice clean-up.

@alecgeatches
Copy link
Contributor Author

alecgeatches commented May 9, 2025

@acicovic I've made some updates to wp_cache_ functions in 6389c74. Here's how it was organized:

  • Where possible, all cached values now use the PARSELY_CACHE_GROUP (wp-parsely) shared cache group constant.

  • The one exception is the Smart Link get_smart_links_post_cache_group() function, which uses a group cache for performance reasons. Otherwise, we need to loop through ~12 separate cache values to delete on flush.

  • All cache keys are made with dashes, e.g. smart-link-to-postid-0e7a8793cf7a598dadf42edcc827f8ab instead of a mix of dashes and underscores

  • Any previously unbounded wp_cache_set() functions now use a month at maximum.

  • Any functions that construct cache keys or groups dynamically in more than one place have been moved into functions.

  • class-smart-link.php uses three different caching mechanisms:

    • get_smart_link_object_by_uid() uses get_uid_to_smart_link_cache_key() is used to get a Smart Link object by UID. These are stored under the main PARSELY_CACHE_GROUP. These are flushed in flush_cache().
    • get_smart_links() uses get_smart_links_for_post_cache_key() to return all of the smart links associated with a post. These are saved under a separate group cache that looks like wp-parsely-smart-links-<post-id>.
    • get_link_counts() uses get_smart_link_counts_cache_key() to return the counts of various types of smart links associated with a post. This is also saved under the separate group cache as above, wp-parsely-smart-links-<post-id>.

    The latter two caches above are flushed as a group in flush_cache_by_post_id().

  • There were a handful of places like this function where the generated key was super long, for example:

    parsely-api-4d4d213e8114f16525d726080536279f-075a773dfb85dd6abae83bc94a89a3a0-aab17c6a126b5f7f1101eea66f3388b3
    

    Cache keys can have various limits [1] [2] imposed by SQL and middleware services, so I changed these to hash the result of concatenation where possible to shorten keys. I chose sha1 hashing for consistency, and that it produces 40-character outputs, and we likely don't need to defend against purposeful collisions. I think any hashing functions from md5 to sha256 would work fine here.

Note: Changed hashing to sha256 to make tests happy, and because it doesn't really matter.


One more thing, I just noticed that get_smart_links()'s wp_cache_set() call saves the whole Smart_Link object in the cache which is probably not good. I'll take a look at that and see if we can store IDs instead. I'll also address the test failures.

@alecgeatches
Copy link
Contributor Author

One more thing, I just noticed that get_smart_links()'s wp_cache_set() call saves the whole Smart_Link object in the cache which is probably not good. I'll take a look at that and see if we can store IDs instead. I'll also address the test failures.

Addressed in 32e9869. The problem was that we were calling into wp_cache_set() with an array of objects like this:

Screenshot 2025-05-09 at 1 27 02 PM

This is a bit unusual, and as you can see from the screenshot also includes inner objects like WP_Post. I'm surprised this even worked, and I don't think it's a best practice to stuff that much object data into a cache string. Additionally, inner objects will eventually be stale.

The code has been modified to instead cache the smart link IDs. Although this will incur a performance cost to re-hydrate objects, it'll still cache the heavy taxonomy query and avoid potential object serialization and stale data issues.

Copy link
Collaborator

@acicovic acicovic left a comment

Choose a reason for hiding this comment

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

I think we're ready to merge, left a couple of minor comments.

@alecgeatches alecgeatches force-pushed the add/traffic-boost-post-list-stats branch from 6cd339a to eb92cb3 Compare May 12, 2025 18:39
Copy link
Collaborator

@acicovic acicovic left a comment

Choose a reason for hiding this comment

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

Thank you so much for working on this bringing it to its best version!

@alecgeatches alecgeatches merged commit 3fc9a21 into add/traffic-boost May 12, 2025
39 checks passed
@alecgeatches alecgeatches deleted the add/traffic-boost-post-list-stats branch May 12, 2025 18:54
@acicovic acicovic added the Feature: Engagement Boost Ticket/PR related to Engagement Boost label May 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature: Engagement Boost Ticket/PR related to Engagement Boost

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants