diff --git a/projects/packages/premium-analytics/changelog/add-premium-analytics-tags-widget-traffic b/projects/packages/premium-analytics/changelog/add-premium-analytics-tags-widget-traffic new file mode 100644 index 000000000000..4aa2c88b497d --- /dev/null +++ b/projects/packages/premium-analytics/changelog/add-premium-analytics-tags-widget-traffic @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Move the Top tags & categories widget from Insights to Traffic, where it and its report follow the date picker instead of a fixed seven days. diff --git a/projects/packages/premium-analytics/packages/data/src/hooks/use-stats-tags.ts b/projects/packages/premium-analytics/packages/data/src/hooks/use-stats-tags.ts index 6fcff57eff2a..503a35665ece 100644 --- a/projects/packages/premium-analytics/packages/data/src/hooks/use-stats-tags.ts +++ b/projects/packages/premium-analytics/packages/data/src/hooks/use-stats-tags.ts @@ -10,6 +10,6 @@ import type { StatsTagsParams } from '../queries/stats-tags-query'; export type StatsTagsResponse = StatsNormalizedReport< StatsTagsItem >; export type { StatsTagsParams } from '../queries/stats-tags-query'; -export function useStatsTags( params: StatsTagsParams = {}, options?: UseStatsOptions ) { +export function useStatsTags( params: StatsTagsParams, options?: UseStatsOptions ) { return useStatsQuery< StatsTagsResponse >( statsTagsQuery( params ), options ); } diff --git a/projects/packages/premium-analytics/packages/data/src/processing/stats/tags.ts b/projects/packages/premium-analytics/packages/data/src/processing/stats/tags.ts index e9525bd5d734..21d4e634726b 100644 --- a/projects/packages/premium-analytics/packages/data/src/processing/stats/tags.ts +++ b/projects/packages/premium-analytics/packages/data/src/processing/stats/tags.ts @@ -88,7 +88,7 @@ export function sanitizeStatsTagsResponse( response: unknown, query?: StatsQueryParams ): StatsNormalizedReport< StatsTagsItem > { - // `stats/tags` takes only `max`, so it never returns `days` or a `summary`. + // `stats/tags` returns one ranked list, never `days` or a `summary`. const tags = coerceStatsArray< StatsRecord >( coerceStatsRecord( response ).tags ).map( normalizeStatsTagsItem ); diff --git a/projects/packages/premium-analytics/packages/data/src/queries/__tests__/fetch-stats-report-rows.test.ts b/projects/packages/premium-analytics/packages/data/src/queries/__tests__/fetch-stats-report-rows.test.ts index 6b5cd22f47c4..434a90079260 100644 --- a/projects/packages/premium-analytics/packages/data/src/queries/__tests__/fetch-stats-report-rows.test.ts +++ b/projects/packages/premium-analytics/packages/data/src/queries/__tests__/fetch-stats-report-rows.test.ts @@ -307,7 +307,12 @@ describe( 'all-time report fetchers', () => { ], } ); - const rows = await fetchStatsTagsRows( { max: 1000 } ); + const rows = await fetchStatsTagsRows( { + from: '2026-01-01', + to: '2026-01-31', + interval: 'day', + max: 1000, + } ); expect( requestedPaths() ).toHaveLength( 1 ); expect( requestedPaths()[ 0 ] ).toContain( 'stats/tags' ); diff --git a/projects/packages/premium-analytics/packages/data/src/queries/__tests__/stats-queries.test.ts b/projects/packages/premium-analytics/packages/data/src/queries/__tests__/stats-queries.test.ts index 8e1942085e55..61510aaed1a9 100644 --- a/projects/packages/premium-analytics/packages/data/src/queries/__tests__/stats-queries.test.ts +++ b/projects/packages/premium-analytics/packages/data/src/queries/__tests__/stats-queries.test.ts @@ -621,39 +621,51 @@ describe( 'Stats query factories', () => { // The endpoint rewrites `max < 1` back to its default of 10 rather than reading // it as "all rows", so a `0` must not reach it. - it( 'leaves a non-positive tags max off the request', () => { - expect( statsTagsQuery( { max: 0 } ).queryKey ).toEqual( [ + it( 'sends the tags window as date and start_date, with max only when positive', () => { + const query = statsTagsQuery( { + from: '2026-06-01', + to: '2026-06-07', + interval: 'day', + max: 0, + } ); + + expect( query.queryKey ).toEqual( [ 'stats', 'tags', '1.1', 'stats/tags', 'GET', - {}, + { period: 'day', date: '2026-06-07', start_date: '2026-06-01', summarize: 1 }, undefined, 'tags', 'UTC', ] ); + expect( + statsTagsQuery( { from: '2026-06-01', to: '2026-06-07', interval: 'day', max: 10 } ) + .queryKey[ 5 ] + ).toEqual( { + period: 'day', + date: '2026-06-07', + start_date: '2026-06-01', + max: 10, + summarize: 1, + } ); } ); - // A date would change the cache key without changing a row. `StatsTagsParams` - // already rejects one, so this spreads past the type to cover untyped callers. - it( 'never sends a date on the tags query, whatever the selected period', () => { - const tagsQueryKey = ( period: Record< string, unknown > ) => - statsTagsQuery( { max: 10, ...period } ).queryKey; - const maxOnly = [ - 'stats', - 'tags', - '1.1', - 'stats/tags', - 'GET', - { max: 10 }, - undefined, - 'tags', - 'UTC', - ]; - - expect( tagsQueryKey( { to: '2026-01-31T23:59:59.999-08:00' } ) ).toEqual( maxOnly ); - expect( tagsQueryKey( { date: '2026-06-30', period: 'year', days: 365 } ) ).toEqual( maxOnly ); + // One ranking pass whatever the length: per-day keeps 50 posts a day and one + // pass keeps 50 for the window, so switching by length would make a longer + // range count fewer posts than a shorter one. + it.each( [ + [ '1 day', '2026-06-01', '2026-06-01' ], + [ '365 days', '2025-07-02', '2026-07-01' ], + ] )( 'summarizes the tags query at every length (%s)', ( _label, from, to ) => { + const params = statsTagsQuery( { from, to, interval: 'day' } ).queryKey[ 5 ] as Record< + string, + unknown + >; + + expect( params.summarize ).toBe( 1 ); + expect( params.days ).toBeUndefined(); } ); it( 'builds devices query keys from the selected device property', () => { diff --git a/projects/packages/premium-analytics/packages/data/src/queries/stats-tags-query.ts b/projects/packages/premium-analytics/packages/data/src/queries/stats-tags-query.ts index f72734ce98f3..7d7455e2da84 100644 --- a/projects/packages/premium-analytics/packages/data/src/queries/stats-tags-query.ts +++ b/projects/packages/premium-analytics/packages/data/src/queries/stats-tags-query.ts @@ -1,14 +1,13 @@ /** * Internal dependencies */ -import { statsProxyQuery, type StatsReportQueryOptions } from './stats-query'; +import { + statsReportQuery, + type StatsReportParams, + type StatsReportQueryOptions, +} from './stats-query'; -/** - * `stats/tags` declares `max` as its only query parameter, so WPCOM strips - * everything else — `date` included — before the handler runs: the window is a - * hardcoded seven days ending yesterday in site time, and nothing can move it. - */ -export type StatsTagsParams = { +export type StatsTagsParams = StatsReportParams & { /** * Rows to request. `max` only truncates an already-ranked list, so a larger one * adds rows without moving a row's views. `0` is not "all rows" here — anything @@ -18,11 +17,24 @@ export type StatsTagsParams = { max?: number; }; -export const statsTagsQuery = ( params: StatsTagsParams = {} ): StatsReportQueryOptions< 'tags' > => - statsProxyQuery( { - name: 'tags', - version: '1.1', - endpoint: 'stats/tags', - params: ( params.max ?? 0 ) > 0 ? { max: params.max } : {}, - sanitizer: 'tags', - } ); +/** + * `stats/tags` sizes its window from `date` and `start_date` (the days are + * derived server-side, so `days` stays off the request) and always ranks the + * window's top posts in one pass. Per-day ranking keeps 50 posts a day while + * one pass keeps 50 for the window, so mixing the two by window length would + * let a longer range count fewer posts than a shorter one. + */ +export const statsTagsQuery = ( params: StatsTagsParams ): StatsReportQueryOptions< 'tags' > => { + const { max, ...reportParams } = params; + + return statsReportQuery( + 'tags', + 'stats/tags', + reportParams, + 'tags', + '1.1', + // Explicit: the shared derivation adds `summarize` only past one day. + { ...( ( max ?? 0 ) > 0 ? { max } : {} ), summarize: 1 }, + { omitParams: [ 'days' ] } + ); +}; diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/report-exports/tags.ts b/projects/packages/premium-analytics/packages/widgets-toolkit/src/report-exports/tags.ts index 982b046d618c..fe1ad476fc9a 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/report-exports/tags.ts +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/report-exports/tags.ts @@ -10,14 +10,14 @@ import type { ReportCsvExporter } from './types'; /** * `stats/tags` has no "all rows" value (see `StatsTagsParams`), so the report names a ceiling - * past what a real site produces (the endpoint ranks at most ~51 posts a day over seven days). + * past what a real site produces. */ export const TAGS_REPORT_ROW_LIMIT = 1000; export const tagsCsvExporter: ReportCsvExporter< StatsTagsItem, StatsTagsItem > = { filenamePrefix: 'tags-and-categories', - hasDateRange: false, - fetchItems: () => fetchStatsTagsRows( { max: TAGS_REPORT_ROW_LIMIT } ), + hasDateRange: true, + fetchItems: reportParams => fetchStatsTagsRows( { ...reportParams, max: TAGS_REPORT_ROW_LIMIT } ), toCsvRows: items => [ ...items ].sort( ( a, b ) => b.value - a.value ), getColumns: () => [ { diff --git a/projects/packages/premium-analytics/routes/reports/registry.test.ts b/projects/packages/premium-analytics/routes/reports/registry.test.ts index 61abfd1e168e..37fd38d5da81 100644 --- a/projects/packages/premium-analytics/routes/reports/registry.test.ts +++ b/projects/packages/premium-analytics/routes/reports/registry.test.ts @@ -123,11 +123,12 @@ describe( 'REPORTS', () => { 'locations', 'posts', 'search-terms', + 'tags', 'videos', 'utm', 'referrers', ], - insights: [ 'annual-insights', 'comments', 'tags' ], + insights: [ 'annual-insights', 'comments' ], subscribers: [ 'comment-followers', 'emails' ], ads: [ 'earnings' ], } ); diff --git a/projects/packages/premium-analytics/routes/reports/registry.ts b/projects/packages/premium-analytics/routes/reports/registry.ts index a8d3b50b9128..f27433adb90c 100644 --- a/projects/packages/premium-analytics/routes/reports/registry.ts +++ b/projects/packages/premium-analytics/routes/reports/registry.ts @@ -146,7 +146,7 @@ export const REPORTS: Record< string, ReportDefinition > = { }, tags: { id: 'tags', - dashboardSection: 'insights', + dashboardSection: 'traffic', getLabel: () => __( 'Tags & categories', 'jetpack-premium-analytics-pkg' ), load: () => import( './tags/page' ), }, diff --git a/projects/packages/premium-analytics/routes/reports/tags/config/use-report-records.test.ts b/projects/packages/premium-analytics/routes/reports/tags/config/use-report-records.test.ts index d45a5eba74b6..b004b7b963bb 100644 --- a/projects/packages/premium-analytics/routes/reports/tags/config/use-report-records.test.ts +++ b/projects/packages/premium-analytics/routes/reports/tags/config/use-report-records.test.ts @@ -1,7 +1,7 @@ /** * External dependencies */ -import { useStatsTags } from '@jetpack-premium-analytics/data'; +import { getDefaultQueryParams, useStatsTags } from '@jetpack-premium-analytics/data'; import { renderHook } from '@testing-library/react'; /** * Internal dependencies @@ -64,14 +64,15 @@ describe( 'useTagsReportRecords', () => { jest.clearAllMocks(); } ); - it( 'requests more rows than the endpoint would return by default', () => { - renderHook( () => useTagsReportRecords() ); + it( 'requests the report window with more rows than the endpoint returns by default', () => { + const reportParams = getDefaultQueryParams(); + renderHook( () => useTagsReportRecords( reportParams ) ); - expect( mockUseStatsTags ).toHaveBeenCalledWith( { max: 1000 } ); + expect( mockUseStatsTags ).toHaveBeenCalledWith( { ...reportParams, max: 1000 } ); } ); it( 'returns the normalized rows', () => { - const { result } = renderHook( () => useTagsReportRecords() ); + const { result } = renderHook( () => useTagsReportRecords( getDefaultQueryParams() ) ); expect( result.current.rows ).toEqual( [ expect.objectContaining( { labelText: 'Recipes', value: 1240 } ), diff --git a/projects/packages/premium-analytics/routes/reports/tags/config/use-report-records.ts b/projects/packages/premium-analytics/routes/reports/tags/config/use-report-records.ts index 52613e126bd1..df84ea9e964a 100644 --- a/projects/packages/premium-analytics/routes/reports/tags/config/use-report-records.ts +++ b/projects/packages/premium-analytics/routes/reports/tags/config/use-report-records.ts @@ -1,7 +1,11 @@ /** * External dependencies */ -import { useStatsTags, type StatsTagsItem } from '@jetpack-premium-analytics/data'; +import { + useStatsTags, + type ReportParams, + type StatsTagsItem, +} from '@jetpack-premium-analytics/data'; import { TAGS_REPORT_ROW_LIMIT } from '@jetpack-premium-analytics/widgets-toolkit'; import { useMemo } from '@wordpress/element'; @@ -16,12 +20,13 @@ export function getTagRowId( item: StatsTagsItem ): string { } /** - * Fetch the Tags & categories rows. + * Fetch the Tags & categories rows for the report window. * + * @param reportParams - The report's date window. * @return Table rows and fetch state. */ -export function useTagsReportRecords() { - const tags = useStatsTags( { max: TAGS_REPORT_ROW_LIMIT } ); +export function useTagsReportRecords( reportParams: ReportParams ) { + const tags = useStatsTags( { ...reportParams, max: TAGS_REPORT_ROW_LIMIT } ); const rows = useMemo< StatsTagsItem[] >( () => tags.data?.data?.[ 0 ]?.items ?? [], [ tags.data ] diff --git a/projects/packages/premium-analytics/routes/reports/tags/page.tsx b/projects/packages/premium-analytics/routes/reports/tags/page.tsx index 8cca6e885a9b..e3d87fc34ceb 100644 --- a/projects/packages/premium-analytics/routes/reports/tags/page.tsx +++ b/projects/packages/premium-analytics/routes/reports/tags/page.tsx @@ -1,6 +1,7 @@ /** * External dependencies */ +import { useReportDateFilters } from '@jetpack-premium-analytics/routing'; import { StatsBreadcrumbs, StatsPageIcon } from '@jetpack-premium-analytics/ui'; import { ExporterCsvAction, @@ -15,12 +16,15 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ +import { route } from '../package.json'; import { REPORTS } from '../registry'; import { useReportParams } from '../use-report-params'; import { getTagRowId, getTagsFields, useTagsReportRecords } from './config'; import type { StatsTagsItem } from '@jetpack-premium-analytics/data'; import type { JSX } from 'react'; +const ROUTE_FROM = route.path; + /** * Initial records-table view: views sort descending, the label column absorbs * spare width, and the numeric column stays compact and right-aligned. @@ -38,16 +42,17 @@ const RECORDS_VIEW = { /** * Premium Analytics Tags & categories report page component. * - * `stats/tags` returns one flat list over the seven days ending yesterday and ignores - * date-window params, so this page has no date filters, tabs, or performance chart — - * just the header and records table. + * `stats/tags` returns one flat list ranked over the report window and reports no + * comparison, so this page has date filters and the records table, with no tabs or + * performance chart. * * @return The Tags & categories report page. */ function TagsReport(): JSX.Element { - const records = useTagsReportRecords(); const reportParams = useReportParams(); + const records = useTagsReportRecords( reportParams ); const fields = useMemo( () => getTagsFields(), [] ); + const dateFilters = useReportDateFilters( ROUTE_FROM ); const { getLabel } = REPORTS.tags; @@ -64,7 +69,7 @@ function TagsReport(): JSX.Element { /> } > - + { } ); + // Dated like the CASES above, but with no comparison to fail. + it( 'downloads the same tags-and-categories CSV from the widget as from the report page', async () => { + const reportFile = await download( ); + queryClient.clear(); + const widgetFile = await download( + + ); + + expect( widgetFile ).toEqual( reportFile ); + expect( widgetFile.filename ).toBe( 'tags-and-categories-2026-03-01_2026-03-10.csv' ); + expect( widgetFile.csv.replace( '\ufeff', '' ).split( '\n' ).length ).toBeGreaterThan( 11 ); + } ); + it.each( [ [ 'annual-insights.csv', AnnualInsightsReportPage, AnnualHighlightsWidget, undefined ], [ 'comments-authors.csv', CommentsReportPage, MostCommentedAuthorsWidget, 'authors' ], [ 'comments-posts.csv', CommentsReportPage, MostCommentedPostsWidget, 'posts' ], - [ 'tags-and-categories.csv', TagsReportPage, TagsWidget, undefined ], [ 'emails.csv', EmailsReportPage, EmailsWidget, undefined ], ] as const )( 'downloads the same all-time %s from the widget as from the report page', diff --git a/projects/packages/premium-analytics/tests/php/Dashboard_Layout_Test.php b/projects/packages/premium-analytics/tests/php/Dashboard_Layout_Test.php index e63308448167..9acb17bfda60 100644 --- a/projects/packages/premium-analytics/tests/php/Dashboard_Layout_Test.php +++ b/projects/packages/premium-analytics/tests/php/Dashboard_Layout_Test.php @@ -194,7 +194,7 @@ public function test_insights_default_excludes_shares_on_self_hosted() { $layout_types = $this->served_layout_types( 'analytics/insights' ); $this->assertNotContains( 'jpa/shares', $layout_types, 'Simple-only widget instances must not be part of the default layout on self-hosted sites.' ); - $this->assertContains( 'jpa/tags', $layout_types, 'Regular widget instances remain in the default layout.' ); + $this->assertContains( 'jpa/most-commented-posts', $layout_types, 'Regular widget instances remain in the default layout.' ); } /** @@ -431,6 +431,7 @@ public function test_traffic_section_declares_the_bundled_widgets() { 'default-authors-widget-instance' => array( 'jpa/authors', 1, 2, 9 ), 'default-search-terms-widget-instance' => array( 'jpa/search-terms', 1, 2, 10 ), 'default-file-downloads-widget-instance' => array( 'jpa/file-downloads', 1, 2, 11 ), + 'default-tags-widget-instance' => array( 'jpa/tags', 2, 2, 12 ), ), $layout ); @@ -462,15 +463,16 @@ public function test_insights_section_declares_the_bundled_widgets() { 'default-latest-post-widget-instance' => array( 'jpa/latest-post', 1, 2, 5 ), 'default-posting-activity-widget-instance' => array( 'jpa/posting-activity', 3, 2, 6 ), 'default-views-over-years-widget-instance' => array( 'jpa/views-over-years', 3, 2, 7 ), - 'default-tags-widget-instance' => array( 'jpa/tags', 2, 2, 8 ), - 'default-most-commented-posts-widget-instance' => array( 'jpa/most-commented-posts', 1, 2, 9 ), + 'default-most-commented-posts-widget-instance' => array( 'jpa/most-commented-posts', 1, 2, 8 ), + 'default-most-commented-authors-widget-instance' => array( 'jpa/most-commented-authors', 2, 2, 9 ), 'default-shares-widget-instance' => array( 'jpa/shares', 1, 2, 10 ), - 'default-most-commented-authors-widget-instance' => array( 'jpa/most-commented-authors', 2, 2, 11 ), ), $layout ); $this->assertNotContains( 'jpa/authors', $layout_types ); + // Tags follow the date picker, so they sit on the Traffic tab. + $this->assertNotContains( 'jpa/tags', $layout_types ); // Emails is not an Insights module — it lives on the Subscribers tab. $this->assertNotContains( 'jpa/stats-emails', $layout_types ); // The Comments module ships as two focused widgets, not one toggled widget. diff --git a/projects/packages/premium-analytics/widgets/tags/__tests__/tags.test.tsx b/projects/packages/premium-analytics/widgets/tags/__tests__/tags.test.tsx index db99a1f8a87f..c678ad9b63e7 100644 --- a/projects/packages/premium-analytics/widgets/tags/__tests__/tags.test.tsx +++ b/projects/packages/premium-analytics/widgets/tags/__tests__/tags.test.tsx @@ -69,6 +69,14 @@ function rowLink( label: string ) { return screen.getByText( label ).closest( 'a' ); } +/** The query string of every `stats/tags` request so far. */ +function tagsRequests(): URLSearchParams[] { + return mockApiFetch.mock.calls + .map( ( [ options ] ) => String( options?.path ?? '' ) ) + .filter( path => path.includes( 'stats/tags' ) ) + .map( path => new URLSearchParams( path.split( '?' )[ 1 ] ) ); +} + describe( 'TagsWidget', () => { beforeEach( () => { queryClient.clear(); @@ -132,20 +140,47 @@ describe( 'TagsWidget', () => { } ); // Calypso sends no query, so Jetpack Stats gets the endpoint's default of 10. - it( 'asks for the row count Jetpack Stats gets by default', async () => { + it( 'asks for the row count Jetpack Stats gets by default, over the dashboard window', async () => { render( ); await expect( screen.findByText( 'Recipes' ) ).resolves.toBeInTheDocument(); - const requestedPaths = mockApiFetch.mock.calls - .map( ( [ options ] ) => String( options?.path ?? '' ) ) - .filter( path => path.includes( 'stats/tags' ) ); - - expect( requestedPaths ).not.toHaveLength( 0 ); - requestedPaths.forEach( path => { - expect( new URLSearchParams( path.split( '?' )[ 1 ] ).get( 'max' ) ).toBe( '10' ); + const requested = tagsRequests(); + expect( requested ).not.toHaveLength( 0 ); + requested.forEach( params => { + expect( params.get( 'max' ) ).toBe( '10' ); + expect( params.get( 'date' ) ).toBe( getDefaultQueryParams().to ); + expect( params.get( 'start_date' ) ).toBe( getDefaultQueryParams().from ); + expect( params.get( 'summarize' ) ).toBe( '1' ); } ); } ); + + it( 'refetches with the new window when the dashboard period changes', async () => { + const { rerender } = render( + + ); + await expect( screen.findByText( 'Recipes' ) ).resolves.toBeInTheDocument(); + + rerender( + + ); + + await waitFor( () => + expect( tagsRequests().map( params => params.get( 'start_date' ) ) ).toContain( + '2026-01-01T00:00:00.000+00:00' + ) + ); + expect( tagsRequests().at( -1 )?.get( 'date' ) ).toBe( '2026-01-31T23:59:59.999+00:00' ); + } ); } ); describe( 'TagsWidget CSV export', () => { @@ -199,6 +234,5 @@ describe( 'TagsWidget CSV export', () => { expect( lines[ 0 ] ).toBe( '"Tag or category","Views","URL"' ); expect( lines ).toContain( '"Desserts, chocolate","760",""' ); expect( lines ).toHaveLength( 13 ); - expect( downloads.files[ 0 ].filename ).toBe( 'tags-and-categories.csv' ); } ); } ); diff --git a/projects/packages/premium-analytics/widgets/tags/render.tsx b/projects/packages/premium-analytics/widgets/tags/render.tsx index 5bbe57c3925c..cb589d1eb59f 100644 --- a/projects/packages/premium-analytics/widgets/tags/render.tsx +++ b/projects/packages/premium-analytics/widgets/tags/render.tsx @@ -17,6 +17,7 @@ import { sharePercentage, tagRowGlyph, useWidgetDrillDown, + useWidgetRootContext, ExporterCsvDownloadButton, tagsCsvExporter, type LeaderboardChartData, @@ -68,7 +69,9 @@ function TagGroupMembers( { members }: TagGroupMembersProps ) { } function TagsInner() { + const { reportParams } = useWidgetRootContext(); const { data, isLoading, isFetching, isError, refetch } = useTagViews( { + reportParams, max: WIDGET_ROW_LIMIT, } ); diff --git a/projects/packages/premium-analytics/widgets/tags/stories/tags-widget.stories.tsx b/projects/packages/premium-analytics/widgets/tags/stories/tags-widget.stories.tsx index a469c4ef9fc1..fa81b4257c73 100644 --- a/projects/packages/premium-analytics/widgets/tags/stories/tags-widget.stories.tsx +++ b/projects/packages/premium-analytics/widgets/tags/stories/tags-widget.stories.tsx @@ -43,7 +43,7 @@ const meta = { docs: { description: { component: - 'The "Tags & categories" widget. Displays the site\'s most visited tags and categories over the seven days ending yesterday, ranked by views — the endpoint takes no date parameters, so the section\'s date filter does not reach it. Single tags/categories link to their archive; grouped rows (several tags/categories sharing posts) drill down to their members. Ported from the Jetpack Stats Tags & categories module.', + 'The "Tags & categories" widget. Displays the tags and categories of the site\'s most viewed posts over the dashboard\'s period, ranked by views; the endpoint reports no comparison. Single tags/categories link to their archive; grouped rows (several tags/categories sharing posts) drill down to their members. Ported from the Jetpack Stats Tags & categories module.', }, }, }, diff --git a/projects/packages/premium-analytics/widgets/tags/use-tag-views.ts b/projects/packages/premium-analytics/widgets/tags/use-tag-views.ts index 8cf01eb4f37f..5698a2024438 100644 --- a/projects/packages/premium-analytics/widgets/tags/use-tag-views.ts +++ b/projects/packages/premium-analytics/widgets/tags/use-tag-views.ts @@ -6,7 +6,11 @@ import { useMemo } from '@wordpress/element'; * Internal dependencies */ import { useStatsTags } from '@jetpack-premium-analytics/data'; -import type { StatsNormalizedReport, StatsTagsItem } from '@jetpack-premium-analytics/data'; +import type { + ReportParams, + StatsNormalizedReport, + StatsTagsItem, +} from '@jetpack-premium-analytics/data'; /** * A single tag or category grouped under a parent row. Grouped rows have no @@ -66,6 +70,10 @@ export interface TagView { } interface UseTagViewsArgs { + /** + * The dashboard window the rows are ranked over. + */ + reportParams: ReportParams; /** * Rows to request and display. */ @@ -85,11 +93,13 @@ interface TagViewsState { * via the shared Stats data layer. * * Delegates to `useStatsTags`, then maps the normalized rows onto the leaderboard - * shape and trims to `max`. Single period, no comparison: `stats/tags` takes no - * date parameters, so the dashboard's date filter never reaches this widget. + * shape and trims to `max`. Single period: `stats/tags` reports no comparison. */ -export default function useTagViews( { max }: UseTagViewsArgs ): TagViewsState { - const { data, isLoading, isFetching, isError, refetch } = useStatsTags( { max } ); +export default function useTagViews( { reportParams, max }: UseTagViewsArgs ): TagViewsState { + const { data, isLoading, isFetching, isError, refetch } = useStatsTags( { + ...reportParams, + max, + } ); // Memoize on the query's stable `data` reference so the row array keeps a // stable identity across unrelated re-renders; otherwise every render hands a diff --git a/projects/packages/premium-analytics/widgets/tags/widget.json b/projects/packages/premium-analytics/widgets/tags/widget.json index 08555d5d1462..94d93e5107f9 100644 --- a/projects/packages/premium-analytics/widgets/tags/widget.json +++ b/projects/packages/premium-analytics/widgets/tags/widget.json @@ -1,10 +1,10 @@ { "name": "jpa/tags", "icon": "jpa/category", - "title": "Top tags & categories in the last 7 days", - "description": "Your most visited tags and categories, ranked by views.", + "title": "Top tags & categories", + "description": "The tags and categories of your most viewed posts in the selected period, ranked by views.", "help": { - "content": "The tags and categories associated with your most-viewed content, sorted by views.", + "content": "The tags and categories of your most viewed posts in the selected period, ranked by views. A post counts toward each of its current tags and categories, so this is a breakdown of popular posts by topic rather than a filter over all traffic.", "links": [ { "label": "Learn more", diff --git a/projects/plugins/jetpack/changelog/add-premium-analytics-tags-widget-traffic b/projects/plugins/jetpack/changelog/add-premium-analytics-tags-widget-traffic new file mode 100644 index 000000000000..523e5e20a20c --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-premium-analytics-tags-widget-traffic @@ -0,0 +1,4 @@ +Significance: minor +Type: enhancement + +Premium Analytics: Move the Top tags & categories widget to the Traffic tab, where it and its report follow the date picker. diff --git a/projects/plugins/premium-analytics/changelog/add-premium-analytics-tags-widget-traffic b/projects/plugins/premium-analytics/changelog/add-premium-analytics-tags-widget-traffic new file mode 100644 index 000000000000..fbe2a3d30c3e --- /dev/null +++ b/projects/plugins/premium-analytics/changelog/add-premium-analytics-tags-widget-traffic @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Move the Top tags & categories widget to the Traffic tab, where it and its report follow the date picker. diff --git a/projects/plugins/wpcomsh/changelog/add-premium-analytics-tags-widget-traffic b/projects/plugins/wpcomsh/changelog/add-premium-analytics-tags-widget-traffic new file mode 100644 index 000000000000..fea4f697aa62 --- /dev/null +++ b/projects/plugins/wpcomsh/changelog/add-premium-analytics-tags-widget-traffic @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Premium Analytics: Move the Top tags & categories widget to the Traffic tab, where it and its report follow the date picker.