Commit c46edafe867 for woocommerce

commit c46edafe8673438f4684072a2284d6d10529b6b7
Author: Cem Ünalan <raicem@users.noreply.github.com>
Date:   Mon Mar 2 16:18:30 2026 +0300

    In-App Marketplace: improve Discover page events (#63465)

    * In-App Marketplace: add utm_group information to wcadmin_marketplace_discover_viewed event

    * In-App Marketplace: add wcadmin_marketplace_discover_group_viewed event

    * In-App Marketplace: change group name

    * In-App Marketplace: don't use ref for seenGroups

    * In-App Marketplace: fix a bug where switching tabs stopping the observer

diff --git a/plugins/woocommerce/client/admin/client/marketplace/components/discover/discover.tsx b/plugins/woocommerce/client/admin/client/marketplace/components/discover/discover.tsx
index 138d99fba55..d8ef942ef62 100644
--- a/plugins/woocommerce/client/admin/client/marketplace/components/discover/discover.tsx
+++ b/plugins/woocommerce/client/admin/client/marketplace/components/discover/discover.tsx
@@ -1,7 +1,7 @@
 /**
  * External dependencies
  */
-import { useContext, useEffect, useState } from '@wordpress/element';
+import { useContext, useEffect, useRef, useState } from '@wordpress/element';
 import { recordEvent } from '@woocommerce/tracks';

 /**
@@ -19,20 +19,28 @@ export default function Discover(): JSX.Element | null {
 	const [ productGroups, setProductGroups ] = useState<
 		Array< ProductGroup >
 	>( [] );
+	const groupElements = useRef< Record< string, HTMLDivElement | null > >(
+		{}
+	);
 	const marketplaceContextValue = useContext( MarketplaceContext );
 	const { isLoading, setIsLoading } = marketplaceContextValue;

 	function recordTracksEvent( products: ProductGroup[] ) {
 		const product_ids = products
 			.flatMap( ( group ) => group.items )
-			.map( ( product ) => {
-				return product.id;
-			} );
+			.map( ( product ) => product.id );
+		const groups = Object.fromEntries(
+			products.map( ( group ) => [
+				group.id,
+				group.items.map( ( product ) => product.id ),
+			] )
+		);

 		// This is a new event specific to the Discover tab, added with Woo 8.4.
 		recordEvent( 'marketplace_discover_viewed', {
 			view: 'discover',
 			product_ids,
+			groups,
 		} );

 		// This is the new page view event added with Woo 8.3. It's improved with the marketplace_discover_viewed event
@@ -42,6 +50,14 @@ export default function Discover(): JSX.Element | null {
 		} );
 	}

+	function recordGroupViewedTrackEvent( group: ProductGroup ) {
+		recordEvent( 'marketplace_discover_group_viewed', {
+			view: 'discover',
+			group_id: group.id,
+			product_ids: group.items.map( ( product ) => product.id ),
+		} );
+	}
+
 	// Get the content for this screen
 	useEffect( () => {
 		setIsLoading( true );
@@ -62,7 +78,65 @@ export default function Discover(): JSX.Element | null {
 			.finally( () => {
 				setIsLoading( false );
 			} );
-	}, [] );
+	}, [ setIsLoading ] );
+
+	useEffect( () => {
+		if (
+			isLoading ||
+			! productGroups.length ||
+			! ( 'IntersectionObserver' in window )
+		) {
+			return;
+		}
+
+		const productGroupsById = new Map(
+			productGroups.map( ( productGroup ) => [
+				productGroup.id,
+				productGroup,
+			] )
+		);
+		const seenGroups = new Set< string >();
+
+		const observer = new IntersectionObserver(
+			( entries ) => {
+				entries.forEach( ( entry ) => {
+					if ( ! entry.isIntersecting ) {
+						return;
+					}
+
+					const groupId = ( entry.target as HTMLDivElement ).dataset
+						.groupId;
+
+					if ( ! groupId || seenGroups.has( groupId ) ) {
+						return;
+					}
+
+					const group = productGroupsById.get( groupId );
+
+					if ( ! group ) {
+						return;
+					}
+
+					recordGroupViewedTrackEvent( group );
+					seenGroups.add( groupId );
+					observer.unobserve( entry.target );
+				} );
+			},
+			{ threshold: 0.25 }
+		);
+
+		productGroups.forEach( ( group ) => {
+			const groupElement = groupElements.current[ group.id ];
+
+			if ( groupElement ) {
+				observer.observe( groupElement );
+			}
+		} );
+
+		return () => {
+			observer.disconnect();
+		};
+	}, [ isLoading, productGroups ] );

 	if ( isLoading ) {
 		return (
@@ -90,6 +164,10 @@ export default function Discover(): JSX.Element | null {
 					groupURLType={ groups.url_type }
 					type={ groups.itemType }
 					cardType={ groups.cardType ?? ProductCardType.regular }
+					groupId={ groups.id }
+					containerRef={ ( element ) => {
+						groupElements.current[ groups.id ] = element;
+					} }
 				/>
 			) ) }
 		</div>
diff --git a/plugins/woocommerce/client/admin/client/marketplace/components/product-list/product-list.tsx b/plugins/woocommerce/client/admin/client/marketplace/components/product-list/product-list.tsx
index 8184c6e88f2..1f836eb3a63 100644
--- a/plugins/woocommerce/client/admin/client/marketplace/components/product-list/product-list.tsx
+++ b/plugins/woocommerce/client/admin/client/marketplace/components/product-list/product-list.tsx
@@ -15,6 +15,8 @@ interface ProductListProps {
 	cardType?: ProductCardType;
 	groupURLText: string | null;
 	groupURLType: 'wc-admin' | 'wp-admin' | 'external' | undefined; // types defined by Link component
+	groupId?: string;
+	containerRef?: ( element: HTMLDivElement | null ) => void;
 }

 export default function ProductList( props: ProductListProps ): JSX.Element {
@@ -28,10 +30,16 @@ export default function ProductList( props: ProductListProps ): JSX.Element {
 		groupURLText,
 		groupURLType,
 		cardType,
+		groupId,
+		containerRef,
 	} = props;

 	return (
-		<div className="woocommerce-marketplace__product-list">
+		<div
+			className="woocommerce-marketplace__product-list"
+			data-group-id={ groupId }
+			ref={ containerRef }
+		>
 			<ProductListHeader
 				title={ title }
 				groupURL={ groupURL }