Skip to content

Commit

Permalink
Newsletter imports: Handle errors correctly (#99162)
Browse files Browse the repository at this point in the history
* Newsletter imports: Handle errors correctly rebase.

* Update props.

* Pass tests

* Make TS happy.

* remove optional param

* Show the notice till the user dimisses it.

* Error handling and add upgrade link based on site.

* Make tests happy.

* dispatch all errors correctly.

* Make test happy

* Update atomic site checks.
  • Loading branch information
spsiddarthan authored Feb 7, 2025
1 parent 5406049 commit 921d762
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { SiteDetails } from '@automattic/data-stores';
import { localizeUrl } from '@automattic/i18n-utils';
import { AddSubscriberForm, UploadSubscribersForm } from '@automattic/subscriber';
import { useHasStaleImportJobs } from '@automattic/subscriber/src/hooks/use-has-stale-import-jobs';
import { useImportError } from '@automattic/subscriber/src/hooks/use-import-error';
import { useInProgressState } from '@automattic/subscriber/src/hooks/use-in-progress-state';
import { ExternalLink, Modal, __experimentalVStack as VStack } from '@wordpress/components';
import { copy, upload, reusableBlock } from '@wordpress/icons';
Expand Down Expand Up @@ -67,15 +68,17 @@ const AddSubscribersModal = ( { site }: AddSubscribersModalProps ) => {

const [ isUploading, setIsUploading ] = useState( false );
const onImportStarted = ( hasFile: boolean ) => setIsUploading( hasFile );

const importError = useImportError();
const isImportInProgress = useInProgressState();
const hasStaleImportJobs = useHasStaleImportJobs();

const onImportFinished = () => {
setIsUploading( false );
setAddingMethod( '' );
addSubscribersCallback();
addSubscribersCallback( importError );
};

const isImportInProgress = useInProgressState();
const hasStaleImportJobs = useHasStaleImportJobs();

if ( ! showAddSubscribersModal ) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { isEnabled } from '@automattic/calypso-config';
import { updateLaunchpadSettings } from '@automattic/data-stores';
import { ImportSubscribersError } from '@automattic/data-stores/src/subscriber/types';
import { useActiveJobRecognition } from '@automattic/subscriber';
import { useQueryClient } from '@tanstack/react-query';
import { translate } from 'i18n-calypso';
Expand All @@ -10,6 +11,7 @@ import { usePagination } from 'calypso/my-sites/subscribers/hooks';
import { Subscriber } from 'calypso/my-sites/subscribers/types';
import { useSelector } from 'calypso/state';
import { successNotice, errorNotice } from 'calypso/state/notices/actions';
import isJetpackSite from 'calypso/state/sites/selectors/is-jetpack-site';
import { getSelectedSiteSlug } from 'calypso/state/ui/selectors';
import { SubscribersFilterBy, SubscribersSortBy } from '../../constants';
import useManySubsSite from '../../hooks/use-many-subs-site';
Expand Down Expand Up @@ -49,7 +51,7 @@ type SubscribersPageContextProps = {
showAddSubscribersModal: boolean;
showMigrateSubscribersModal: boolean;
setShowAddSubscribersModal: ( show: boolean ) => void;
addSubscribersCallback: () => void;
addSubscribersCallback: ( importError?: ImportSubscribersError ) => void;
migrateSubscribersCallback: ( sourceSiteId: number, targetSiteId: number ) => void;
closeAllModals: typeof closeAllModals;
siteId: number | null;
Expand Down Expand Up @@ -94,7 +96,9 @@ export const SubscribersPageProvider = ( {
const [ showAddSubscribersModal, setShowAddSubscribersModal ] = useState( false );
const [ showMigrateSubscribersModal, setShowMigrateSubscribersModal ] = useState( false );
const [ debouncedSearchTerm ] = useDebounce( searchTerm, 300 );

const isJetpackNonAtomic = useSelector(
( state ) => siteId && isJetpackSite( state, siteId, { treatAtomicAsJetpackSite: false } )
);
useEffect( () => {
if ( hasManySubscribers ) {
setDataViewFilterOption( SubscribersFilterBy.WPCOM );
Expand Down Expand Up @@ -161,42 +165,72 @@ export const SubscribersPageProvider = ( {
queryClient.invalidateQueries( { queryKey: [ 'launchpad' ] } );
};

const addSubscribersCallback = () => {
const addSubscribersCallback = ( importError?: ImportSubscribersError ) => {
setShowAddSubscribersModal( false );
completeImportSubscribersTask();

if ( completedJob ) {
const { email_count, subscribed_count, already_subscribed_count, failed_subscribed_count } =
completedJob;
dispatch(
successNotice(
translate(
'Import completed. %(added)d subscribed, %(skipped)d already subscribed, and %(failed)d failed out of %(total)d %(totalLabel)s.',
if ( ! importError ) {
if ( completedJob ) {
const { email_count, subscribed_count, already_subscribed_count, failed_subscribed_count } =
completedJob;
dispatch(
successNotice(
translate(
'Import completed. %(added)d subscribed, %(skipped)d already subscribed, and %(failed)d failed out of %(total)d %(totalLabel)s.',
{
args: {
added: subscribed_count,
skipped: already_subscribed_count,
failed: failed_subscribed_count,
total: email_count,
totalLabel: translate( 'subscriber', 'subscribers', {
count: email_count,
} ),
},
}
)
)
);
} else {
dispatch(
successNotice(
translate(
"Your subscriber list is being processed. We'll send you an email when it's finished importing."
),
{
args: {
added: subscribed_count,
skipped: already_subscribed_count,
failed: failed_subscribed_count,
total: email_count,
totalLabel: translate( 'subscriber', 'subscribers', {
count: email_count,
} ),
},
duration: 5000,
}
)
)
);
);
}
} else {
dispatch(
successNotice(
translate(
"Your subscriber list is being processed. We'll send you an email when it's finished importing."
),
{
duration: 5000,
}
)
);
let notice = translate( 'An unknown error has occurred. Please try again in a second.' );
interface NoticeArgs {
isPersistent: boolean;
button?: string;
href?: string;
}
const noticeArgs: NoticeArgs = { isPersistent: true };

if (
'error' in importError &&
typeof importError.error === 'object' &&
importError.error &&
'code' in importError.error &&
'message' in importError.error
) {
const { code, message } = importError.error;
notice = message as string;
if ( code === 'subscriber_import_limit_reached' && typeof message === 'string' ) {
noticeArgs.button = translate( 'Upgrade' );
const siteSlug = selectedSiteSlug || ''; // Use a default if siteSlug is not available
noticeArgs.href = isJetpackNonAtomic
? `https://cloud.jetpack.com/pricing/${ siteSlug }`
: `https://wordpress.com/plans/${ siteSlug }`;
}
}

dispatch( errorNotice( notice, noticeArgs ) );
}
};

Expand Down
10 changes: 7 additions & 3 deletions packages/data-stores/src/subscriber/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,19 @@ export function createActions() {
[ 'parse_only', String( parseOnly ) ] as [ string, string ],
];

const data: ImportSubscribersResponse = await wpcomProxyRequest( {
const data = ( await wpcomProxyRequest( {
path: `/sites/${ encodeURIComponent( siteId ) }/subscribers/import`,
method: 'POST',
apiNamespace: 'wpcom/v2',
token: typeof token === 'string' ? token : undefined,
formData: formDataEntries as ( string | File )[][],
} );
} ) ) as ImportSubscribersResponse & { error?: { code: string; message: string } };

dispatch.importCsvSubscribersStartSuccess( siteId, data.upload_id );
if ( data.upload_id ) {
dispatch.importCsvSubscribersStartSuccess( siteId, data.upload_id );
} else {
dispatch.importCsvSubscribersStartFailed( siteId, data as ImportSubscribersError );
}
} catch ( error ) {
dispatch.importCsvSubscribersStartFailed( siteId, error as ImportSubscribersError );
}
Expand Down
13 changes: 13 additions & 0 deletions packages/subscriber/src/hooks/use-import-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Subscriber } from '@automattic/data-stores';
import { useSelect } from '@wordpress/data';

export function useImportError() {
const { importSelector } = useSelect( ( select ) => {
const subscriber = select( Subscriber.store );
return {
importSelector: subscriber.getImportSubscribersSelector(),
};
}, [] );

return importSelector?.error;
}

0 comments on commit 921d762

Please sign in to comment.