Skip to content

Commit

Permalink
DOP-4287: Retry feedback submission after 401 errors (#987)
Browse files Browse the repository at this point in the history
  • Loading branch information
rayangler authored Jan 23, 2024
1 parent 3c9fe3e commit 82b26d8
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 6 deletions.
19 changes: 18 additions & 1 deletion src/components/Widgets/FeedbackWidget/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function FeedbackProvider({ page, hideHeader, test = {}, ...props }) {
const [view, setView] = useState(test.view || 'waiting');
const [screenshotTaken, setScreenshotTaken] = useState(test.screenshotTaken || false);
const [progress, setProgress] = useState([true, false, false]);
const user = useRealmUser();
const { user, reassignCurrentUser } = useRealmUser();

// Create a new feedback document
const initializeFeedback = (nextView = 'rating') => {
Expand Down Expand Up @@ -40,6 +40,17 @@ export function FeedbackProvider({ page, hideHeader, test = {}, ...props }) {
}
};

const retryFeedbackSubmission = async (newFeedback) => {
try {
const newUser = await reassignCurrentUser();
newFeedback.user.stitch_id = newUser.id;
await createNewFeedback(newFeedback);
setFeedback(newFeedback);
} catch (e) {
console.error('Error when retrying feedback submission', e);
}
};

const submitAllFeedback = async ({ comment = '', email = '', snootyEnv, dataUri, viewport }) => {
// Route the user to their "next steps"

Expand Down Expand Up @@ -75,7 +86,13 @@ export function FeedbackProvider({ page, hideHeader, test = {}, ...props }) {
await createNewFeedback(newFeedback);
setFeedback(newFeedback);
} catch (err) {
// This catch block will most likely only be hit after Realm attempts internal retry logic
// after access token is refreshed
console.error('There was an error submitting feedback', err);
if (err.statusCode === 401) {
// Explicitly retry 1 time to avoid any infinite loop
await retryFeedbackSubmission(newFeedback);
}
}
};

Expand Down
61 changes: 57 additions & 4 deletions src/components/Widgets/FeedbackWidget/realm.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,81 @@ import { isBrowser } from '../../../utils/is-browser';
const APP_ID = 'feedbackwidgetv3-dgcsv';
export const app = isBrowser ? Realm.App.getApp(APP_ID) : { auth: {} };

/**
* @param {object} storage
* @returns The prefix for the storage key used by Realm for localStorage access
*/
function parseStorageKey(storage) {
if (!storage.keyPart) {
return '';
}
const prefix = parseStorageKey(storage.storage);
return prefix + storage.keyPart + ':';
}

/**
* Deletes localStorage data for all users
*/
function deleteLocalStorageData() {
const { allUsers } = app;
// The accessToken and refreshToken are automatically removed if invalid, but not the following keys
const keysToDelete = ['profile', 'providerType'];

Object.values(allUsers).forEach((user) => {
const storageKeyPrefix = parseStorageKey(user.storage);
keysToDelete.forEach((key) => {
localStorage.removeItem(storageKeyPrefix + key);
});
});
}

// User Authentication & Management
export async function loginAnonymous() {
if (!app.currentUser) {
const user = await app.logIn(Realm.Credentials.anonymous());
return user;
} else {
return app.currentUser;
}
return app.currentUser;
}

export async function logout() {
if (app.auth.isLoggedIn) {
await app.auth.logoutUserWithId(app.id);
} else {
console.warn('No logged in user.');
}
}

export const useRealmUser = () => {
const [user, setUser] = React.useState(app.currentUser);

async function reassignCurrentUser() {
const oldUser = app.currentUser;
try {
await app.removeUser(oldUser);
} catch (e) {
console.error(e);
}

// Clean up invalid data from local storage to avoid bubbling up local storage sizes for broken user credentials
// This should be safe since only old users' data would be deleted, and we make a new user right after
deleteLocalStorageData();

const newUser = await app.logIn(Realm.Credentials.anonymous());
setUser(newUser);
return newUser;
}

// Initial user login
React.useEffect(() => {
loginAnonymous().then(setUser);
loginAnonymous()
.then(setUser)
.catch((e) => {
console.error(e);
});
}, []);
return user;

return { user, reassignCurrentUser };
};

// Feedback Widget Functions
Expand Down
17 changes: 17 additions & 0 deletions tests/unit/FeedbackWidget.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,23 @@ describe('FeedbackWidget', () => {
await tick();
expect(wrapper.getByText(EMAIL_ERROR_TEXT)).toBeTruthy();
});

it('attempts to resubmit on 401 error', async () => {
const customError = new Error('mock error message');
customError.statusCode = 401;
stitchFunctionMocks['createNewFeedback'].mockRejectedValueOnce(customError);

wrapper = await mountFormWithFeedbackState({
view: 'comment',
comment: 'This is a test comment.',
sentiment: 'Positive',
rating: 5,
user: { email: '[email protected]' },
});
userEvent.click(wrapper.getByText(FEEDBACK_SUBMIT_BUTTON_TEXT).closest('button'));
await tick();
expect(stitchFunctionMocks['createNewFeedback']).toHaveBeenCalledTimes(2);
});
});
});

Expand Down
6 changes: 5 additions & 1 deletion tests/utils/feedbackWidgetStitchFunctions.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ export function mockStitchFunctions() {

stitchFunctionMocks['useRealmUser'] = jest.spyOn(realm, 'useRealmUser').mockImplementation(() => {
return {
id: 'test-user-id',
user: {
id: 'test-user-id',
},
// Most of this logic is dependent on Realm app working
reassignCurrentUser: () => ({ id: 'another-test-user-id' }),
};
});
}
Expand Down

0 comments on commit 82b26d8

Please sign in to comment.