diff --git a/.storybook/fetch-mocks.js b/.storybook/fetch-mocks.js
index cf20d7abfb8..33f6f58776e 100644
--- a/.storybook/fetch-mocks.js
+++ b/.storybook/fetch-mocks.js
@@ -54,6 +54,20 @@ export function fetchMockCatchAll() {
fetchMock.catch( ( url, opts ) => {
global.console.warn( 'fetch', opts.method, url, opts );
+ if (
+ url.startsWith(
+ '/google-site-kit/v1/modules/search-console/data/searchanalytics'
+ ) ||
+ url.startsWith(
+ '/google-site-kit/v1/modules/analytics/data/report'
+ )
+ ) {
+ return {
+ status: 200,
+ body: '[]',
+ };
+ }
+
return {
status: 200,
body: '{}',
diff --git a/assets/js/modules/analytics/datastore/report.js b/assets/js/modules/analytics/datastore/report.js
index feabeebef43..0c0f2dedd8d 100644
--- a/assets/js/modules/analytics/datastore/report.js
+++ b/assets/js/modules/analytics/datastore/report.js
@@ -310,14 +310,24 @@ const baseSelectors = {
args.url = url;
}
+ // Disable reason: select needs to be called here or it will never run.
+ // eslint-disable-next-line @wordpress/no-unused-vars-before-return
const report = select( MODULES_ANALYTICS ).getReport( args );
- if ( report === undefined ) {
+ const hasResolvedReport = select(
+ MODULES_ANALYTICS
+ ).hasFinishedResolution( 'getReport', [ args ] );
+
+ if ( ! hasResolvedReport ) {
return undefined;
}
+ if ( ! Array.isArray( report ) ) {
+ return false;
+ }
+
if (
- ! Array.isArray( report?.[ 0 ]?.data?.rows ) ||
- report?.[ 0 ]?.data?.rows?.length === 0
+ ! Array.isArray( report[ 0 ]?.data?.rows ) ||
+ report[ 0 ]?.data?.rows?.length === 0
) {
return true;
}
@@ -349,13 +359,26 @@ const baseSelectors = {
args.url = url;
}
- const report = select( MODULES_ANALYTICS ).getReport( args );
const isGatheringData = select( MODULES_ANALYTICS ).isGatheringData();
+ if ( isGatheringData === undefined ) {
+ return undefined;
+ }
+
+ // Disable reason: select needs to be called here or it will never run.
+ // eslint-disable-next-line @wordpress/no-unused-vars-before-return
+ const report = select( MODULES_ANALYTICS ).getReport( args );
+ const hasResolvedReport = select(
+ MODULES_ANALYTICS
+ ).hasFinishedResolution( 'getReport', [ args ] );
- if ( report === undefined || isGatheringData === undefined ) {
+ if ( ! hasResolvedReport ) {
return undefined;
}
+ if ( ! Array.isArray( report ) ) {
+ return false;
+ }
+
const hasZeroReport = isZeroReport( report );
if ( isGatheringData === false && hasZeroReport === false ) {
return false;
diff --git a/assets/js/modules/analytics/datastore/report.test.js b/assets/js/modules/analytics/datastore/report.test.js
index c4cb04440c1..84c0ea37f84 100644
--- a/assets/js/modules/analytics/datastore/report.test.js
+++ b/assets/js/modules/analytics/datastore/report.test.js
@@ -29,6 +29,7 @@ import {
subscribeUntil,
} from '../../../../../tests/js/utils';
import * as fixtures from './__fixtures__';
+import { isZeroReport } from '../util';
describe( 'modules/analytics report', () => {
let registry;
@@ -50,6 +51,8 @@ describe( 'modules/analytics report', () => {
} );
describe( 'selectors', () => {
+ const zeroRowsReport = [ { data: { rows: [] } } ];
+
describe( 'getReport', () => {
const options = {
dateRange: 'last-90-days',
@@ -293,6 +296,7 @@ describe( 'modules/analytics report', () => {
expect( console ).toHaveErrored(); // fetch will trigger 400 error.
} );
} );
+
describe( 'getPageTitles', () => {
it( 'generates a map using a getReport call.', async () => {
const startDate = '2021-01-01';
@@ -347,17 +351,18 @@ describe( 'modules/analytics report', () => {
} );
} );
} );
+
describe( 'isGatheringData', () => {
it( 'should return undefined if getReport is not resolved yet', async () => {
freezeFetch(
/^\/google-site-kit\/v1\/modules\/analytics\/data\/report/
);
- const isGatheringData = registry
- .select( MODULES_ANALYTICS )
- .isGatheringData();
+ const { isGatheringData } = registry.select(
+ MODULES_ANALYTICS
+ );
- expect( isGatheringData ).toBeUndefined();
+ expect( isGatheringData() ).toBeUndefined();
} );
it( 'should return TRUE if the returned report is null', async () => {
@@ -368,54 +373,40 @@ describe( 'modules/analytics report', () => {
}
);
- const isGatheringData = registry
- .select( MODULES_ANALYTICS )
- .isGatheringData();
+ const { isGatheringData } = registry.select(
+ MODULES_ANALYTICS
+ );
- expect( isGatheringData ).toBeUndefined();
+ expect( isGatheringData() ).toBeUndefined();
await subscribeUntil(
registry,
- () =>
- registry
- .select( MODULES_ANALYTICS )
- .isGatheringData() !== undefined
+ () => isGatheringData() !== undefined
);
- const isNotGathered = registry
- .select( MODULES_ANALYTICS )
- .isGatheringData();
-
- expect( isNotGathered ).toBe( true );
+ expect( isGatheringData() ).toBe( true );
} );
it( 'should return TRUE if the returned report is an empty array', async () => {
fetchMock.getOnce(
/^\/google-site-kit\/v1\/modules\/analytics\/data\/report/,
{
- body: [ { data: { rows: [] } } ],
+ body: zeroRowsReport,
}
);
- const isGatheringData = registry
- .select( MODULES_ANALYTICS )
- .isGatheringData();
+ const { isGatheringData } = registry.select(
+ MODULES_ANALYTICS
+ );
- expect( isGatheringData ).toBeUndefined();
+ expect( isGatheringData() ).toBeUndefined();
await subscribeUntil(
registry,
- () =>
- registry
- .select( MODULES_ANALYTICS )
- .isGatheringData() !== undefined
+ () => isGatheringData() !== undefined
);
- const isNotGathered = registry
- .select( MODULES_ANALYTICS )
- .isGatheringData();
-
- expect( isNotGathered ).toBe( true );
+ expect( isGatheringData() ).toBe( true );
} );
it( 'should return FALSE if the returned report has rows', async () => {
@@ -426,25 +417,18 @@ describe( 'modules/analytics report', () => {
}
);
- const isGatheringData = registry
- .select( MODULES_ANALYTICS )
- .isGatheringData();
+ const { isGatheringData } = registry.select(
+ MODULES_ANALYTICS
+ );
- expect( isGatheringData ).toBeUndefined();
+ expect( isGatheringData() ).toBeUndefined();
await subscribeUntil(
registry,
- () =>
- registry
- .select( MODULES_ANALYTICS )
- .isGatheringData() !== undefined
+ () => isGatheringData() !== undefined
);
- const isNotGathered = registry
- .select( MODULES_ANALYTICS )
- .isGatheringData();
-
- expect( isNotGathered ).toBe( false );
+ expect( isGatheringData() ).toBe( false );
} );
} );
@@ -454,11 +438,9 @@ describe( 'modules/analytics report', () => {
/^\/google-site-kit\/v1\/modules\/analytics\/data\/report/
);
- const hasZeroData = registry
- .select( MODULES_ANALYTICS )
- .hasZeroData();
+ const { hasZeroData } = registry.select( MODULES_ANALYTICS );
- expect( hasZeroData ).toBeUndefined();
+ expect( hasZeroData() ).toBeUndefined();
} );
it( 'should return TRUE if isGatheringData is true', async () => {
@@ -466,63 +448,40 @@ describe( 'modules/analytics report', () => {
/^\/google-site-kit\/v1\/modules\/analytics\/data\/report/,
// When `rows` is `null` it means we're still gathering data for
// this report.
- { body: { data: { rows: null } } }
+ { body: [ { data: { rows: null } } ] }
);
- const hasZeroData = registry
- .select( MODULES_ANALYTICS )
- .hasZeroData();
-
- expect( hasZeroData ).toBeUndefined();
-
- await Promise.all( [
- subscribeUntil(
- registry,
- () =>
- registry
- .select( MODULES_ANALYTICS )
- .isGatheringData() !== undefined
- ),
- subscribeUntil(
- registry,
- () =>
- registry
- .select( MODULES_ANALYTICS )
- .hasZeroData() !== undefined
- ),
- ] );
-
- const zeroData = registry
- .select( MODULES_ANALYTICS )
- .hasZeroData();
+ const { hasZeroData, isGatheringData } = registry.select(
+ MODULES_ANALYTICS
+ );
+
+ expect( hasZeroData() ).toBeUndefined();
+
+ await subscribeUntil(
+ registry,
+ () => isGatheringData() !== undefined,
+ () => hasZeroData() !== undefined
+ );
- expect( zeroData ).toBe( true );
+ expect( hasZeroData() ).toBe( true );
} );
it( 'should return TRUE if isZeroReport is true', async () => {
fetchMock.getOnce(
/^\/google-site-kit\/v1\/modules\/analytics\/data\/report/,
- { body: { data: { rows: [] } } }
+ { body: zeroRowsReport }
);
- const hasZeroData = registry
- .select( MODULES_ANALYTICS )
- .hasZeroData();
+ const { hasZeroData } = registry.select( MODULES_ANALYTICS );
- expect( hasZeroData ).toBeUndefined();
+ expect( hasZeroData() ).toBeUndefined();
await subscribeUntil(
registry,
- () =>
- registry.select( MODULES_ANALYTICS ).hasZeroData() !==
- undefined
+ () => hasZeroData() !== undefined
);
- const zeroData = registry
- .select( MODULES_ANALYTICS )
- .hasZeroData();
-
- expect( zeroData ).toBe( true );
+ expect( hasZeroData() ).toBe( true );
} );
it( 'should return FALSE if isGatheringData returns FALSE', async () => {
@@ -533,37 +492,24 @@ describe( 'modules/analytics report', () => {
}
);
- const hasZeroData = registry
- .select( MODULES_ANALYTICS )
- .hasZeroData();
-
- expect( hasZeroData ).toBeUndefined();
-
- await Promise.all( [
- subscribeUntil(
- registry,
- () =>
- registry
- .select( MODULES_ANALYTICS )
- .isGatheringData() !== undefined
- ),
- subscribeUntil(
- registry,
- () =>
- registry
- .select( MODULES_ANALYTICS )
- .hasZeroData() !== undefined
- ),
- ] );
-
- const noZeroData = registry
- .select( MODULES_ANALYTICS )
- .hasZeroData();
+ const { hasZeroData, isGatheringData } = registry.select(
+ MODULES_ANALYTICS
+ );
+
+ expect( hasZeroData() ).toBeUndefined();
+
+ await subscribeUntil(
+ registry,
+ () => isGatheringData() !== undefined,
+ () => hasZeroData() !== undefined
+ );
- expect( noZeroData ).toBe( false );
+ expect( isGatheringData() ).toBe( false );
+ expect( hasZeroData() ).toBe( false );
} );
it( 'should return FALSE if isZeroReport returns FALSE', async () => {
+ expect( isZeroReport( fixtures.report ) ).toBe( false );
fetchMock.getOnce(
/^\/google-site-kit\/v1\/modules\/analytics\/data\/report/,
{
@@ -571,24 +517,16 @@ describe( 'modules/analytics report', () => {
}
);
- const hasZeroData = registry
- .select( MODULES_ANALYTICS )
- .hasZeroData();
+ const { hasZeroData } = registry.select( MODULES_ANALYTICS );
- expect( hasZeroData ).toBeUndefined();
+ expect( hasZeroData() ).toBeUndefined();
await subscribeUntil(
registry,
- () =>
- registry.select( MODULES_ANALYTICS ).hasZeroData() !==
- undefined
+ () => hasZeroData() !== undefined
);
- const noZeroData = registry
- .select( MODULES_ANALYTICS )
- .hasZeroData();
-
- expect( noZeroData ).toBe( false );
+ expect( hasZeroData() ).toBe( false );
} );
} );
} );
diff --git a/assets/js/modules/search-console/components/dashboard/SearchFunnelWidget/index.js b/assets/js/modules/search-console/components/dashboard/SearchFunnelWidget/index.js
index 94adb7925f5..cfb1e463ffa 100644
--- a/assets/js/modules/search-console/components/dashboard/SearchFunnelWidget/index.js
+++ b/assets/js/modules/search-console/components/dashboard/SearchFunnelWidget/index.js
@@ -293,11 +293,6 @@ const SearchFunnelWidget = ( {
analyticsStatsLoading ||
analyticsVisitorsOverviewAndStatsLoading ||
analyticsGoalsLoading ||
- searchConsoleData === undefined ||
- analyticsOverviewData === undefined ||
- analyticsStatsData === undefined ||
- analyticsVisitorsOverviewAndStatsData === undefined ||
- analyticsGoalsData === undefined ||
isAnalyticsGatheringData === undefined ||
isSearchConsoleGatheringData === undefined
) {
diff --git a/assets/js/modules/search-console/datastore/report.js b/assets/js/modules/search-console/datastore/report.js
index 7fccd3f08fe..b101aaf8e38 100644
--- a/assets/js/modules/search-console/datastore/report.js
+++ b/assets/js/modules/search-console/datastore/report.js
@@ -155,12 +155,22 @@ const baseSelectors = {
args.url = url;
}
+ // Disable reason: select needs to be called here or it will never run.
+ // eslint-disable-next-line @wordpress/no-unused-vars-before-return
const report = select( MODULES_SEARCH_CONSOLE ).getReport( args );
- if ( report === undefined ) {
+ const hasResolvedReport = select(
+ MODULES_SEARCH_CONSOLE
+ ).hasFinishedResolution( 'getReport', [ args ] );
+
+ if ( ! hasResolvedReport ) {
return undefined;
}
- if ( ! Array.isArray( report ) || ! report.length ) {
+ if ( ! Array.isArray( report ) ) {
+ return false;
+ }
+
+ if ( ! report.length ) {
return true;
}
@@ -195,15 +205,29 @@ const baseSelectors = {
args.url = url;
}
- const report = select( MODULES_SEARCH_CONSOLE ).getReport( args );
const isGatheringData = select(
MODULES_SEARCH_CONSOLE
).isGatheringData();
+ if ( isGatheringData === undefined ) {
+ return undefined;
+ }
- if ( report === undefined || isGatheringData === undefined ) {
+ // Disable reason: select needs to be called here or it will never run.
+ // eslint-disable-next-line @wordpress/no-unused-vars-before-return
+ const report = select( MODULES_SEARCH_CONSOLE ).getReport( args );
+
+ const hasResolvedReport = select(
+ MODULES_SEARCH_CONSOLE
+ ).hasFinishedResolution( 'getReport', [ args ] );
+
+ if ( ! hasResolvedReport ) {
return undefined;
}
+ if ( ! Array.isArray( report ) ) {
+ return false;
+ }
+
const hasZeroReport = isZeroReport( report );
if ( isGatheringData === false && hasZeroReport === false ) {
return false;
diff --git a/assets/js/modules/search-console/datastore/report.test.js b/assets/js/modules/search-console/datastore/report.test.js
index 013652056e0..c8afe0f2d47 100644
--- a/assets/js/modules/search-console/datastore/report.test.js
+++ b/assets/js/modules/search-console/datastore/report.test.js
@@ -31,6 +31,23 @@ import * as fixtures from './__fixtures__';
describe( 'modules/search-console report', () => {
const searchAnalyticsRegexp = /^\/google-site-kit\/v1\/modules\/search-console\/data\/searchanalytics/;
+ const errorResponse = {
+ status: 403,
+ body: {
+ code: 403,
+ message:
+ 'User does not have sufficient permissions for this profile.',
+ data: { status: 403, reason: 'forbidden' },
+ },
+ };
+ const consoleError = [
+ 'Google Site Kit API Error',
+ 'method:GET',
+ 'datapoint:searchanalytics',
+ 'type:modules',
+ 'identifier:search-console',
+ 'error:"User does not have sufficient permissions for this profile."',
+ ];
let registry;
@@ -145,67 +162,66 @@ describe( 'modules/search-console report', () => {
it( 'should return undefined if getReport is not resolved yet', async () => {
freezeFetch( searchAnalyticsRegexp );
- const isGatheringData = registry
- .select( MODULES_SEARCH_CONSOLE )
- .isGatheringData();
+ const { isGatheringData } = registry.select(
+ MODULES_SEARCH_CONSOLE
+ );
- expect( isGatheringData ).toBeUndefined();
+ expect( isGatheringData() ).toBeUndefined();
} );
- it.each( [
- [ 'an empty array', [] ],
- [ 'not an array', null ],
- ] )(
- 'should return TRUE if the returned report is %s',
- async ( _, body ) => {
- fetchMock.getOnce( searchAnalyticsRegexp, { body } );
+ it( 'should return TRUE if the returned report is an empty array', async () => {
+ fetchMock.getOnce( searchAnalyticsRegexp, { body: [] } );
- const isGatheringData = registry
- .select( MODULES_SEARCH_CONSOLE )
- .isGatheringData();
+ const { isGatheringData } = registry.select(
+ MODULES_SEARCH_CONSOLE
+ );
- expect( isGatheringData ).toBeUndefined();
+ expect( isGatheringData() ).toBeUndefined();
- await subscribeUntil(
- registry,
- () =>
- registry
- .select( MODULES_SEARCH_CONSOLE )
- .isGatheringData() !== undefined
- );
+ await subscribeUntil(
+ registry,
+ () => isGatheringData() !== undefined
+ );
- const isNotGathered = registry
- .select( MODULES_SEARCH_CONSOLE )
- .isGatheringData();
+ expect( isGatheringData() ).toBe( true );
+ } );
+
+ it( 'should return FALSE if the report API returns error', async () => {
+ fetchMock.getOnce( searchAnalyticsRegexp, errorResponse );
+
+ const { isGatheringData } = registry.select(
+ MODULES_SEARCH_CONSOLE
+ );
+
+ expect( isGatheringData() ).toBeUndefined();
- expect( isNotGathered ).toBe( true );
- }
- );
+ await subscribeUntil(
+ registry,
+ () => isGatheringData() !== undefined
+ );
+
+ expect( console ).toHaveErroredWith( ...consoleError );
+
+ expect( isGatheringData() ).toBe( false );
+ } );
it( 'should return FALSE if the returned report has rows', async () => {
fetchMock.getOnce( searchAnalyticsRegexp, {
body: fixtures.report,
} );
- const isGatheringData = registry
- .select( MODULES_SEARCH_CONSOLE )
- .isGatheringData();
+ const { isGatheringData } = registry.select(
+ MODULES_SEARCH_CONSOLE
+ );
- expect( isGatheringData ).toBeUndefined();
+ expect( isGatheringData() ).toBeUndefined();
await subscribeUntil(
registry,
- () =>
- registry
- .select( MODULES_SEARCH_CONSOLE )
- .isGatheringData() !== undefined
+ () => isGatheringData() !== undefined
);
- const isNotGathered = registry
- .select( MODULES_SEARCH_CONSOLE )
- .isGatheringData();
-
- expect( isNotGathered ).toBe( false );
+ expect( isGatheringData() ).toBe( false );
} );
} );
@@ -213,67 +229,66 @@ describe( 'modules/search-console report', () => {
it( 'should return undefined if getReport or isGatheringData is not resolved yet', async () => {
freezeFetch( searchAnalyticsRegexp );
- const hasZeroData = registry
- .select( MODULES_SEARCH_CONSOLE )
- .hasZeroData();
+ const { hasZeroData } = registry.select(
+ MODULES_SEARCH_CONSOLE
+ );
- expect( hasZeroData ).toBeUndefined();
+ expect( hasZeroData() ).toBeUndefined();
} );
- it.each( [
- [ 'an empty array', [] ],
- [ 'not an array', null ],
- ] )(
- 'should return TRUE if report data in isGatheringData OR isZeroReport is %s',
- async ( _, body ) => {
- fetchMock.getOnce( searchAnalyticsRegexp, { body } );
+ it( 'should return TRUE if report data in isGatheringData OR isZeroReport is an empty array', async () => {
+ fetchMock.getOnce( searchAnalyticsRegexp, { body: [] } );
- const hasZeroData = registry
- .select( MODULES_SEARCH_CONSOLE )
- .hasZeroData();
+ const { hasZeroData } = registry.select(
+ MODULES_SEARCH_CONSOLE
+ );
- expect( hasZeroData ).toBeUndefined();
+ expect( hasZeroData() ).toBeUndefined();
- await subscribeUntil(
- registry,
- () =>
- registry
- .select( MODULES_SEARCH_CONSOLE )
- .hasZeroData() !== undefined
- );
+ await subscribeUntil(
+ registry,
+ () => hasZeroData() !== undefined
+ );
- const zeroData = registry
- .select( MODULES_SEARCH_CONSOLE )
- .hasZeroData();
+ expect( hasZeroData() ).toBe( true );
+ } );
+
+ it( 'should return FALSE if report API returns error', async () => {
+ fetchMock.getOnce( searchAnalyticsRegexp, errorResponse );
+
+ const { hasZeroData } = registry.select(
+ MODULES_SEARCH_CONSOLE
+ );
+
+ expect( hasZeroData() ).toBeUndefined();
- expect( zeroData ).toBe( true );
- }
- );
+ await subscribeUntil(
+ registry,
+ () => hasZeroData() !== undefined
+ );
+
+ expect( console ).toHaveErroredWith( ...consoleError );
+
+ expect( hasZeroData() ).toBe( false );
+ } );
it( 'should return false if isGatheringData and isZeroReport return false', async () => {
fetchMock.getOnce( searchAnalyticsRegexp, {
body: fixtures.report,
} );
- const hasZeroData = registry
- .select( MODULES_SEARCH_CONSOLE )
- .hasZeroData();
+ const { hasZeroData } = registry.select(
+ MODULES_SEARCH_CONSOLE
+ );
- expect( hasZeroData ).toBeUndefined();
+ expect( hasZeroData() ).toBeUndefined();
await subscribeUntil(
registry,
- () =>
- registry
- .select( MODULES_SEARCH_CONSOLE )
- .hasZeroData() !== undefined
+ () => hasZeroData() !== undefined
);
- const noZeroData = registry
- .select( MODULES_SEARCH_CONSOLE )
- .hasZeroData();
-
- expect( noZeroData ).toBe( false );
+ expect( hasZeroData() ).toBe( false );
} );
} );
} );
diff --git a/stories/wp-dashboard.stories.js b/stories/wp-dashboard.stories.js
index 8369f223687..7e26e523c5a 100644
--- a/stories/wp-dashboard.stories.js
+++ b/stories/wp-dashboard.stories.js
@@ -279,12 +279,12 @@ storiesOf( 'WordPress', module )
// For