Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
dc8b2b8
Add breakdown notice orchestrator component.
zutigrm Jun 8, 2026
d4bafc2
Add breakdown notice area test.
zutigrm Jun 8, 2026
fe68959
Add breakdown notice area stories.
zutigrm Jun 8, 2026
e3579ee
Add breakdown error notice component.
zutigrm Jun 8, 2026
13ab9fc
Add breakdown error notice tests.
zutigrm Jun 8, 2026
849750f
Add breakdown error notice stories.
zutigrm Jun 8, 2026
b59d78e
Add breakdown success notice component.
zutigrm Jun 8, 2026
0acf507
Add breakdown success notice tests.
zutigrm Jun 8, 2026
0c2862a
Add breakdown success notice stories.
zutigrm Jun 8, 2026
8f2b559
Add breakdown result copy hook.
zutigrm Jun 8, 2026
d1d44e7
Add breakdown notice copy tests.
zutigrm Jun 8, 2026
229f499
Add breakdown enable handler hook.
zutigrm Jun 8, 2026
b62260f
Add enable handler hook tests.
zutigrm Jun 8, 2026
eff19b6
Add second fake ecommerce provider.
zutigrm Jun 8, 2026
ef3d070
Add breakdown notice/form constants.
zutigrm Jun 8, 2026
5023257
Make notice presentational, required CTA.
zutigrm Jun 8, 2026
3cb6551
Update notice tests.
zutigrm Jun 8, 2026
49f3a1d
Branch ecommerce copy by provider count.
zutigrm Jun 8, 2026
a538661
Render breakdown notice per section.
zutigrm Jun 8, 2026
e83fd85
Update panel content notice tests.
zutigrm Jun 8, 2026
b4733ca
Add breakdown sync cache.
zutigrm Jun 8, 2026
bfb41a9
Render breakdown notice area.
zutigrm Jun 8, 2026
5a06a32
Render breakdown notice area.
zutigrm Jun 8, 2026
39fb997
Update lead widget breakdown tests.
zutigrm Jun 8, 2026
cea832c
Expose multiple ecommerce providers flag.
zutigrm Jun 8, 2026
be2604e
Test multiple ecommerce providers selector.
zutigrm Jun 8, 2026
15c3645
Add breakdown notice spacing styles.
zutigrm Jun 8, 2026
79e7059
Expose multiple ecommerce providers flag.
zutigrm Jun 8, 2026
19d3e9d
Test multiple ecommerce providers data.
zutigrm Jun 8, 2026
9fd355b
Update store widget breakdown tests.
zutigrm Jun 8, 2026
3ab3c11
Merge remote-tracking branch 'origin/develop' into enhancement/12801-…
zutigrm Jun 8, 2026
04f0289
Fix tests.
zutigrm Jun 9, 2026
035fb22
Update VRT.
zutigrm Jun 9, 2026
45e0212
Add BreakdownScope type.
zutigrm Jun 9, 2026
3058b83
Add breakdown scope constants.
zutigrm Jun 9, 2026
0f8ce27
Add scope-based combined notice copy.
zutigrm Jun 9, 2026
e65c472
Add combined scope result copy.
zutigrm Jun 9, 2026
d92c37c
Record enabled breakdown scope.
zutigrm Jun 9, 2026
6ac49b8
Support combined goalTypes notice.
zutigrm Jun 9, 2026
35c5f6e
Render single combined breakdown notice.
zutigrm Jun 9, 2026
fac05a5
Pass goalTypes to notice.
zutigrm Jun 9, 2026
404335b
Pass goalTypes to notice.
zutigrm Jun 9, 2026
151e840
Support combined goalTypes notice.
zutigrm Jun 9, 2026
a9370fc
Update tests.
zutigrm Jun 9, 2026
06fa047
Update VRT.
zutigrm Jun 9, 2026
6232036
Prevent sync call for view only users.
zutigrm Jun 9, 2026
35348c5
Update tests.
zutigrm Jun 9, 2026
aff875e
Resolve merge conflicts.
zutigrm Jun 9, 2026
ec4ff9b
Place panel feedback row above engagement.
zutigrm Jun 9, 2026
f0a8fcd
Update VRT.
zutigrm Jun 9, 2026
dea2543
Update comment format.
tofumatt Jun 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions assets/js/googlesitekit/datastore/site/info.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ export const reducer = createReducer( ( state, { payload, type } ) => {
isMultisite,
hasActiveLeadEventProviders,
hasActiveEcommerceEventProviders,
hasMultipleActiveEcommerceEventProviders,
} = payload.siteInfo;

state.siteInfo = {
Expand Down Expand Up @@ -234,6 +235,7 @@ export const reducer = createReducer( ( state, { payload, type } ) => {
isMultisite,
hasActiveLeadEventProviders,
hasActiveEcommerceEventProviders,
hasMultipleActiveEcommerceEventProviders,
};
break;

Expand Down Expand Up @@ -325,6 +327,7 @@ export const resolvers = {
isMultisite,
hasActiveLeadEventProviders,
hasActiveEcommerceEventProviders,
hasMultipleActiveEcommerceEventProviders,
} = global._googlesitekitBaseData;

const {
Expand Down Expand Up @@ -370,6 +373,7 @@ export const resolvers = {
isMultisite,
hasActiveLeadEventProviders,
hasActiveEcommerceEventProviders,
hasMultipleActiveEcommerceEventProviders,
} );
},
};
Expand Down Expand Up @@ -1018,6 +1022,18 @@ export const selectors = {
hasActiveEcommerceEventProviders: getSiteInfoProperty(
'hasActiveEcommerceEventProviders'
),

/**
* Checks if more than one ecommerce event provider plugin is active.
*
* @since n.e.x.t
*
* @param {Object} state Data store's state.
* @return {boolean|undefined} `true` if multiple ecommerce event providers are active; `false` if not. Returns `undefined` if not yet loaded.
*/
hasMultipleActiveEcommerceEventProviders: getSiteInfoProperty(
'hasMultipleActiveEcommerceEventProviders'
),
};

export default {
Expand Down
5 changes: 5 additions & 0 deletions assets/js/googlesitekit/datastore/site/info.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ describe( 'core/site site info', () => {
isMultisite: false,
hasActiveLeadEventProviders: false,
hasActiveEcommerceEventProviders: false,
hasMultipleActiveEcommerceEventProviders: false,
};
const entityInfoVar = '_googlesitekitEntityData';
const entityInfo = {
Expand Down Expand Up @@ -490,6 +491,10 @@ describe( 'core/site site info', () => {
'hasActiveEcommerceEventProviders',
'hasActiveEcommerceEventProviders',
],
[
'hasMultipleActiveEcommerceEventProviders',
'hasMultipleActiveEcommerceEventProviders',
],
] )( '%s', ( selector, infoKey ) => {
it( 'uses a resolver to load site info then returns the info when this specific selector is used', async () => {
global[ baseInfoVar ] = baseInfo;
Expand Down
26 changes: 22 additions & 4 deletions assets/js/modules/analytics-4/components/site-goals/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,28 @@ export const SITE_GOALS_MAX_SELECTED_DRIVERS = 6;

export const SITE_GOALS_BREAKDOWN_NOTICE = 'site_goals_breakdown_notice';

export const SITE_GOALS_BREAKDOWN_CUSTOM_DIMENSIONS = [
'googlesitekit_event_provider',
'googlesitekit_form_id',
];
export const SITE_GOALS_BREAKDOWN_CUSTOM_DIMENSION_BY_GOAL_TYPE: Record<
string,
string
> = {
[ GOAL_TYPES.ECOMMERCE ]: 'googlesitekit_event_provider',
[ GOAL_TYPES.LEAD ]: 'googlesitekit_form_id',
};

export const SITE_GOALS_BREAKDOWN_CUSTOM_DIMENSIONS = Object.values(
SITE_GOALS_BREAKDOWN_CUSTOM_DIMENSION_BY_GOAL_TYPE
);

export const SITE_GOALS_BREAKDOWN_NOTIFICATION = 'site_goals_breakdown';

export const BREAKDOWN_ORIGIN_WIDGET = 'widget';
export const BREAKDOWN_ORIGIN_PANEL = 'panel';

export const BREAKDOWN_ORIGIN_FORM_KEY = 'breakdownOrigin';
export const BREAKDOWN_SCOPE_FORM_KEY = 'breakdownScope';
export const BREAKDOWN_DISMISSED_FORM_KEY = 'breakdownDismissed';

export const BREAKDOWN_SCOPE_BOTH = 'both';

export const SITE_GOALS_DEFAULT_SELECTED_DRIVERS = {
[ GOAL_TYPES.ECOMMERCE ]: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import { GOAL_DRIVER_IDS, GOAL_TYPES } from './constants';
export type GoalDriverID =
typeof GOAL_DRIVER_IDS[ keyof typeof GOAL_DRIVER_IDS ];
export type GoalType = typeof GOAL_TYPES[ keyof typeof GOAL_TYPES ];

export type BreakdownScope = GoalType | 'both';
export type GoalDriverSelectionState = Record< GoalType, GoalDriverID[] >;

export interface GoalDriverRow {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* Site Goals BreakdownErrorNotice stories.
*
* Site Kit by Google, Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* WordPress dependencies
*/
import { WPDataRegistry } from '@wordpress/data/build-types/registry';

/**
* Internal dependencies
*/
import { Story } from '@/js/types/Story';
import { provideSiteInfo } from '@tests/js/utils';
import WithRegistrySetup from '@tests/js/WithRegistrySetup';
import BreakdownErrorNotice from './BreakdownErrorNotice';

const GENERIC_ERROR = {
code: 'internal_server_error',
message: 'Internal server error',
data: { status: 500 },
};

const PERMISSIONS_ERROR = {
code: 'insufficient_permissions',
message: 'Insufficient permissions',
data: { status: 403, reason: 'insufficientPermissions' },
};

interface BreakdownErrorNoticeStoryProps {
error: typeof GENERIC_ERROR;
permissionsTitle: string;
}

function Template( {
error,
permissionsTitle,
}: BreakdownErrorNoticeStoryProps ) {
return (
<WithRegistrySetup
func={ ( registry: WPDataRegistry ) => provideSiteInfo( registry ) }
>
<BreakdownErrorNotice
error={ error }
permissionsTitle={ permissionsTitle }
onRetry={ () => {} }
onDismiss={ () => {} }
/>
</WithRegistrySetup>
);
}

export const Generic = Template.bind(
{}
) as Story< BreakdownErrorNoticeStoryProps >;
Generic.storyName = 'Generic';
Generic.args = {
error: GENERIC_ERROR,
permissionsTitle: 'Individual form tracking setup failed',
};

export const Permissions = Template.bind(
{}
) as Story< BreakdownErrorNoticeStoryProps >;
Permissions.storyName = 'Insufficient permissions';
Permissions.args = {
error: PERMISSIONS_ERROR,
permissionsTitle: 'Individual form tracking setup failed',
};

export default {
title: 'Modules/Analytics4/Components/Site Goals/Notifications/BreakdownErrorNotice',
component: BreakdownErrorNotice,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* Site Goals BreakdownErrorNotice tests.
*
* Site Kit by Google, Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* WordPress dependencies
*/
import { WPDataRegistry } from '@wordpress/data/build-types/registry';

/**
* Internal dependencies
*/
import { fireEvent, render } from '@tests/js/test-utils';
import { createTestRegistry, provideSiteInfo } from '@tests/js/utils';
import BreakdownErrorNotice from './BreakdownErrorNotice';

describe( 'BreakdownErrorNotice', () => {
let registry: WPDataRegistry;

const genericError = {
code: 'internal_server_error',
message: 'Internal server error',
data: { status: 500 },
};
const permissionsError = {
code: 'insufficient_permissions',
message: 'Insufficient permissions',
data: { status: 403, reason: 'insufficientPermissions' },
};

beforeEach( () => {
registry = createTestRegistry();
provideSiteInfo( registry );
} );

it( 'renders the generic error variant with a retry CTA and troubleshooting link', () => {
const { getByRole, getByText } = render(
<BreakdownErrorNotice
error={ genericError }
permissionsTitle="Individual form tracking setup failed"
onRetry={ () => {} }
onDismiss={ () => {} }
/>,
{ registry }
);

expect( getByText( /Analytics update failed/ ) ).toBeInTheDocument();
expect( getByRole( 'button', { name: 'Retry' } ) ).toBeInTheDocument();
expect( getByText( 'Got it' ) ).toBeInTheDocument();
expect(
getByRole( 'link', { name: /Learn more/ } )
).toBeInTheDocument();
} );

it( 'renders the permissions error variant with the parent-supplied title', () => {
const { getByText } = render(
<BreakdownErrorNotice
error={ permissionsError }
permissionsTitle="Individual form tracking setup failed"
onRetry={ () => {} }
onDismiss={ () => {} }
/>,
{ registry }
);

expect(
getByText( /Individual form tracking setup failed/ )
).toBeInTheDocument();
expect( getByText( /insufficient permissions/ ) ).toBeInTheDocument();
} );

it( 'invokes onRetry and onDismiss from their respective buttons', () => {
const onRetry = jest.fn();
const onDismiss = jest.fn();

const { getByRole, getByText } = render(
<BreakdownErrorNotice
error={ genericError }
permissionsTitle="Setup failed"
onRetry={ onRetry }
onDismiss={ onDismiss }
/>,
{ registry }
);

fireEvent.click( getByRole( 'button', { name: 'Retry' } ) );
fireEvent.click( getByText( 'Got it' ) );

expect( onRetry ).toHaveBeenCalledTimes( 1 );
expect( onDismiss ).toHaveBeenCalledTimes( 1 );
} );
} );
Loading
Loading