Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance/#8134 - Implement the unhappy paths for the Setup CTA Banner #8866

Merged
merged 52 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
dbac5d6
Handle errors.
hussain-t Jun 12, 2024
f59c18d
Add failedSiteKitAudienceResourceNames param.
hussain-t Jun 12, 2024
91bbe56
Handle retry logic.
hussain-t Jun 13, 2024
3ad8236
Merge branch 'develop' into enhance/#8134-audience-setup-cta-unhappy-…
hussain-t Jun 24, 2024
bbb52a3
Add buttonLink prop to ModalDialog.
hussain-t Jun 24, 2024
8a604e8
Merge branch 'develop' into enhance/#8134-audience-setup-cta-unhappy-…
hussain-t Jun 25, 2024
d99fbae
Fix merge conflicts.
hussain-t Jun 25, 2024
d22d36e
Remove code duplication.
hussain-t Jun 25, 2024
64caec5
Add AudienceErrorModal component.
hussain-t Jun 25, 2024
58470bd
Render Button component if buttonLink is provided.
hussain-t Jun 25, 2024
eeb3242
Return null if skipDefaultErrorNotifications available in ErrorNotifi…
hussain-t Jun 25, 2024
6774427
Return null if skipDefaultErrorNotifications available in SetupErrorN…
hussain-t Jun 25, 2024
ee5986a
Add AudienceErrorModal stories - WIP.
hussain-t Jun 25, 2024
284cf8d
Add logic to render AudienceErrorModal in the setup CTA.
hussain-t Jun 25, 2024
b511ddd
Add enableAudienceGroup tests coverage for error handling.
hussain-t Jun 27, 2024
5a5e82f
Pass failedSiteKitAudienceResourceNames as a non object param.
hussain-t Jun 27, 2024
0ea5b01
Do not navigate to OAuth flow if skipDefaultErrorNotifications is ava…
hussain-t Jun 27, 2024
aa11356
Add onCancel prop.
hussain-t Jun 27, 2024
5cdc31b
Render AudienceErrorModal.
hussain-t Jun 27, 2024
90fe4e4
Merge branch 'develop' into enhance/#8134-audience-setup-cta-unhappy-…
hussain-t Jun 27, 2024
9772b4d
Add AudienceErrorModal tests.
hussain-t Jun 27, 2024
68611c1
Don not render the modal if no errors.
hussain-t Jun 27, 2024
81d4c82
Export default AudienceErrorModal.
hussain-t Jun 27, 2024
35a8ed4
Fix existing AudienceSegmentationSetupCTAWidget tests.
hussain-t Jun 27, 2024
ffe6c1d
Fix hasOAuthError condition to return bool.
hussain-t Jun 27, 2024
3627787
Add OAuth error modal test.
hussain-t Jun 27, 2024
0af641d
Add insufficient error variant test.
hussain-t Jun 28, 2024
e292955
Assert missing retry button assertion.
hussain-t Jun 28, 2024
e78f06d
Add generic error variant test case.
hussain-t Jun 28, 2024
baec29c
Add VRT scenario and clean up.
hussain-t Jun 28, 2024
a31c8c1
Add VRT images for AudienceErrorModal.
hussain-t Jun 28, 2024
eb335b6
Merge branch 'develop' into enhance/#8134-audience-setup-cta-unhappy-…
hussain-t Jun 28, 2024
e0cdc39
Improve conditions.
hussain-t Jun 28, 2024
6d7794b
Wrap the render call with act.
hussain-t Jun 28, 2024
2316d1a
Fix state update leaks in tests.
hussain-t Jun 30, 2024
009f4c9
Merge branch 'develop' into enhance/#8134-audience-setup-cta-unhappy-…
hussain-t Jul 2, 2024
01d1757
Revert the changes in auth permissions modal.
hussain-t Jul 2, 2024
4424a12
Fix logic errors.
hussain-t Jul 2, 2024
650fde3
Render AudienceErrorModal inside the main JSX.
hussain-t Jul 2, 2024
723e5c2
Merge branch 'develop' into enhance/#8134-audience-setup-cta-unhappy-…
hussain-t Jul 3, 2024
9973a4a
Rename param to failedSiteKitAudienceSlugs.
hussain-t Jul 3, 2024
a32b53c
Improve logic to handle successully create audiences - WIP.
hussain-t Jul 3, 2024
2daea42
Dispatch setConfiguredAudiences appropriately.
hussain-t Jul 3, 2024
65ec299
Fix and improvements.
hussain-t Jul 3, 2024
bac7eab
Add setSetupErrorCode action.
hussain-t Jul 3, 2024
77fff95
Add setSetupErrorCode action tests.
hussain-t Jul 3, 2024
6adc446
Clear OAuth error onCancel.
hussain-t Jul 3, 2024
d24032c
Update the logic to look up for the newly available audiences and syn…
hussain-t Jul 3, 2024
1f32174
Remove unnecessary condition.
hussain-t Jul 4, 2024
149be4a
Merge branch 'develop' into enhance/#8134-audience-setup-cta-unhappy-…
hussain-t Jul 4, 2024
04e6bb6
Fix tests.
hussain-t Jul 4, 2024
40bc804
Increase timeout.
hussain-t Jul 4, 2024
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
29 changes: 21 additions & 8 deletions assets/js/components/ModalDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ function ModalDialog( {
inProgress = false,
small = false,
medium = false,
buttonLink = null,
} ) {
const instanceID = useInstanceId( ModalDialog );
const describedByID = `googlesitekit-dialog-description-${ instanceID }`;
Expand Down Expand Up @@ -125,14 +126,25 @@ function ModalDialog( {
>
{ __( 'Cancel', 'google-site-kit' ) }
</Button>
<SpinnerButton
onClick={ handleConfirm }
danger={ danger }
disabled={ inProgress }
isSaving={ inProgress }
>
{ confirmButton || __( 'Disconnect', 'google-site-kit' ) }
</SpinnerButton>
{ buttonLink ? (
<Button
href={ buttonLink }
target="_blank"
danger={ danger }
>
{ confirmButton }
</Button>
) : (
<SpinnerButton
onClick={ handleConfirm }
danger={ danger }
disabled={ inProgress }
isSaving={ inProgress }
>
{ confirmButton ||
__( 'Disconnect', 'google-site-kit' ) }
</SpinnerButton>
) }
</DialogFooter>
</Dialog>
);
Expand All @@ -150,6 +162,7 @@ ModalDialog.propTypes = {
danger: PropTypes.bool,
small: PropTypes.bool,
medium: PropTypes.bool,
buttonLink: PropTypes.string,
};

export default ModalDialog;
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ function AuthenticatedPermissionsModal() {
// If we have a datastores to snapshot before navigating away to the
// authorization page, do that first.
await snapshotAllStores( registry );

// Navigate to the authorization page.
navigateTo( connectURL );
}, [ registry, connectURL, navigateTo, permissionsError, setValues ] );

Expand Down
6 changes: 6 additions & 0 deletions assets/js/components/notifications/ErrorNotifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ export default function ErrorNotifications() {
}
}

if (
temporaryPersistedPermissionsError?.data?.skipDefaultErrorNotifications
) {
return null;
}

return (
<Fragment>
<InternalServerError />
Expand Down
16 changes: 15 additions & 1 deletion assets/js/components/notifications/SetupErrorNotification.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import { __ } from '@wordpress/i18n';
import { useSelect } from 'googlesitekit-data';
import BannerNotification from './BannerNotification';
import { CORE_SITE } from '../../googlesitekit/datastore/site/constants';
import { CORE_FORMS } from '../../googlesitekit/datastore/forms/constants';
import { FORM_TEMPORARY_PERSIST_PERMISSION_ERROR } from '../../googlesitekit/datastore/user/constants';

export default function SetupErrorNotification() {
// These will be `null` if no errors exist.
Expand All @@ -37,7 +39,19 @@ export default function SetupErrorNotification() {
select( CORE_SITE ).getSetupErrorRedoURL()
);

if ( ! setupErrorMessage ) {
const { data: permissionsErrorData } = useSelect(
( select ) =>
select( CORE_FORMS ).getValue(
FORM_TEMPORARY_PERSIST_PERMISSION_ERROR,
'permissionsError'
) || {}
);

// If there's no setup error message or the temporary persisted permissions error has skipDefaultErrorNotifications flag set, return null.
if (
! setupErrorMessage ||
permissionsErrorData?.skipDefaultErrorNotifications
) {
return null;
}

Expand Down
32 changes: 32 additions & 0 deletions assets/js/googlesitekit/datastore/site/info.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const RECEIVE_SITE_INFO = 'RECEIVE_SITE_INFO';
const RECEIVE_PERMALINK_PARAM = 'RECEIVE_PERMALINK_PARAM';
const SET_SITE_KIT_AUTO_UPDATES_ENABLED = 'SET_SITE_KIT_AUTO_UPDATES_ENABLED';
const SET_KEY_METRICS_SETUP_COMPLETED_BY = 'SET_KEY_METRICS_SETUP_COMPLETED_BY';
const SET_SETUP_ERROR_CODE = 'SET_SETUP_ERROR_CODE';

export const initialState = {
siteInfo: undefined,
Expand Down Expand Up @@ -126,6 +127,27 @@ export const actions = {
type: SET_KEY_METRICS_SETUP_COMPLETED_BY,
};
},

/**
* Sets `setupErrorCode` value.
*
* @since n.e.x.t
*
* @param {string|null} setupErrorCode Error code from setup, or `null` if no error.
* @return {Object} Redux-style action.
*/
setSetupErrorCode( setupErrorCode ) {
// setupErrorCode can be a string or null.
invariant(
typeof setupErrorCode === 'string' || setupErrorCode === null,
'setupErrorCode must be a string or null.'
);

return {
payload: { setupErrorCode },
type: SET_SETUP_ERROR_CODE,
};
},
};

export const controls = {};
Expand Down Expand Up @@ -228,6 +250,16 @@ export const reducer = ( state, { payload, type } ) => {
},
};

case SET_SETUP_ERROR_CODE:
const { setupErrorCode } = payload;
return {
...state,
siteInfo: {
...state.siteInfo,
setupErrorCode,
},
};

default: {
return state;
}
Expand Down
38 changes: 38 additions & 0 deletions assets/js/googlesitekit/datastore/site/info.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,44 @@ describe( 'core/site site info', () => {
);
} );
} );

describe( 'setSetupErrorCode', () => {
it( 'sets the `setupErrorCode` property', () => {
registry
.dispatch( CORE_SITE )
.setSetupErrorCode( 'error_code' );

expect( store.getState().siteInfo.setupErrorCode ).toBe(
'error_code'
);
} );

it( 'requires a string or null argument', () => {
expect( () => {
registry.dispatch( CORE_SITE ).setSetupErrorCode();
} ).toThrow( 'setupErrorCode must be a string or null.' );

expect( () => {
registry
.dispatch( CORE_SITE )
.setSetupErrorCode( undefined );
} ).toThrow( 'setupErrorCode must be a string or null.' );

expect( () => {
registry.dispatch( CORE_SITE ).setSetupErrorCode( true );
} ).toThrow( 'setupErrorCode must be a string or null.' );

expect( () => {
registry.dispatch( CORE_SITE ).setSetupErrorCode( 1 );
} ).toThrow( 'setupErrorCode must be a string or null.' );

expect( () => {
registry.dispatch( CORE_SITE ).setSetupErrorCode( null );

registry.dispatch( CORE_SITE ).setSetupErrorCode( 'error' );
} ).not.toThrow( 'setupErrorCode must be a string or null.' );
} );
} );
} );

describe( 'selectors', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/**
* Audience Segmentation AudienceErrorModal component.
*
* Site Kit by Google, Copyright 2024 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.
*/

/**
* External dependencies
*/
import PropTypes from 'prop-types';

/**
* WordPress dependencies
*/
import { createInterpolateElement } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import { useSelect } from 'googlesitekit-data';
import Link from '../../../../../components/Link';
import ModalDialog from '../../../../../components/ModalDialog';
import Portal from '../../../../../components/Portal';
import { CORE_SITE } from '../../../../../googlesitekit/datastore/site/constants';
import { MODULES_ANALYTICS_4 } from '../../../datastore/constants';
import { isInsufficientPermissionsError } from '../../../../../util/errors';

export default function AudienceErrorModal( {
apiErrors,
hasOAuthError,
inProgress,
onCancel = () => {},
onRetry = () => {},
} ) {
const errors = Array.isArray( apiErrors ) ? apiErrors : [ apiErrors ];

const helpLink = useSelect( ( select ) =>
select( CORE_SITE ).getErrorTroubleshootingLinkURL( {
code: 'analytics-4_insufficient_permissions',
} )
);

const requestAccessURL = useSelect( ( select ) =>
select( MODULES_ANALYTICS_4 ).getServiceEntityAccessURL()
);

const errorTroubleshootingLinkURL = useSelect( ( select ) =>
select( CORE_SITE ).getErrorTroubleshootingLinkURL( {
code: 'access_denied',
} )
);

if ( ! errors.length && ! hasOAuthError ) {
return null;
}

const hasInsufficientPermissionsError = errors.some( ( error ) =>
isInsufficientPermissionsError( error )
);

let title, description, confirmButton, buttonLink;

if ( hasOAuthError ) {
title = __( 'Analytics update failed', 'google-site-kit' );
description = createInterpolateElement(
__(
'Setup was interrupted because you did not grant the necessary permissions. <HelpLink />',
'google-site-kit'
),
{
HelpLink: (
<Link
href={ errorTroubleshootingLinkURL }
external
hideExternalIndicator
>
{ __( 'Get help', 'google-site-kit' ) }
</Link>
),
}
);
confirmButton = __( 'Retry', 'google-site-kit' );
} else if ( hasInsufficientPermissionsError ) {
title = __( 'Insufficient permissions', 'google-site-kit' );
description = createInterpolateElement(
__(
'You’ll need to contact your administrator. Trouble getting access? <HelpLink />',
'google-site-kit'
),
{
HelpLink: (
<Link href={ helpLink } external hideExternalIndicator>
{ __( 'Get help', 'google-site-kit' ) }
</Link>
),
}
);
confirmButton = __( 'Request access', 'google-site-kit' );
buttonLink = requestAccessURL;
} else {
title = __( 'Failed to set up visitor groups', 'google-site-kit' );
description = __(
'Oops! Something went wrong. Retry enabling groups.',
'google-site-kit'
);
confirmButton = __( 'Retry', 'google-site-kit' );
}

return (
<Portal>
<ModalDialog
dialogActive
buttonLink={ buttonLink }
title={ title }
subtitle={ description }
handleConfirm={ onRetry }
confirmButton={ confirmButton }
handleDialog={ onCancel }
danger
inProgress={ inProgress }
/>
</Portal>
);
}

AudienceErrorModal.propTypes = {
apiErrors: PropTypes.oneOfType( [
PropTypes.arrayOf( PropTypes.object ),
PropTypes.object,
PropTypes.array,
] ),
hasOAuthError: PropTypes.bool,
inProgress: PropTypes.bool,
onCancel: PropTypes.func,
onRetry: PropTypes.func,
};
Loading
Loading