Commit f1717c43 authored by Alejandro Celaya's avatar Alejandro Celaya Committed by Alejandro Celaya

Display error when loading dashboard auth token fails

parent 32b6aa5f
import { useEffect, useState } from 'preact/hooks'; import { useCallback, useEffect, useState } from 'preact/hooks';
import { withServices } from '../service-context'; import { withServices } from '../service-context';
import type { DashboardService } from '../services/dashboard'; import type { DashboardService } from '../services/dashboard';
import type { ToastMessengerService } from '../services/toast-messenger';
import MenuItem from './MenuItem'; import MenuItem from './MenuItem';
export type OpenDashboardMenuItemProps = { export type OpenDashboardMenuItemProps = {
...@@ -9,36 +10,54 @@ export type OpenDashboardMenuItemProps = { ...@@ -9,36 +10,54 @@ export type OpenDashboardMenuItemProps = {
// Injected // Injected
dashboard: DashboardService; dashboard: DashboardService;
toastMessenger: ToastMessengerService;
}; };
function OpenDashboardMenuItem({ function OpenDashboardMenuItem({
dashboard, dashboard,
isMenuOpen, isMenuOpen,
toastMessenger,
}: OpenDashboardMenuItemProps) { }: OpenDashboardMenuItemProps) {
const [authToken, setAuthToken] = useState<string>(); const [authTokenOrError, setAuthTokenOrError] = useState<string | Error>();
// Token is loading until we get it or an error occurs
const loading = !authTokenOrError;
const onClick = useCallback(() => {
if (typeof authTokenOrError === 'string') {
dashboard.open(authTokenOrError);
return;
}
if (authTokenOrError) {
toastMessenger.error("Can't open dashboard: You must reload the page.", {
autoDismiss: false,
});
}
}, [authTokenOrError, dashboard, toastMessenger]);
useEffect(() => { useEffect(() => {
// Fetch a new auth token every time the menu containing this item is open, // Fetch a new auth token every time the menu containing this item is open,
// to make sure we always have an up-to-date one // to make sure we always have an up-to-date one
if (isMenuOpen) { if (isMenuOpen) {
dashboard dashboard
.getAuthToken() .getAuthToken()
.then(setAuthToken) .then(setAuthTokenOrError)
.catch(error => .catch(error => {
console.warn('An error occurred while getting auth token', error), console.warn('An error occurred while getting auth token', error);
); setAuthTokenOrError(error);
});
} }
// Discard previous token just before trying to fetch a new one // Discard previous token and error, just before trying to fetch a new one
return () => setAuthToken(undefined); return () => setAuthTokenOrError(undefined);
}, [dashboard, isMenuOpen]); }, [dashboard, isMenuOpen]);
return ( return (
<MenuItem <MenuItem label="Open dashboard" isDisabled={loading} onClick={onClick} />
label="Open dashboard"
isDisabled={!authToken}
onClick={() => authToken && dashboard.open(authToken)}
/>
); );
} }
export default withServices(OpenDashboardMenuItem, ['dashboard']); export default withServices(OpenDashboardMenuItem, [
'dashboard',
'toastMessenger',
]);
...@@ -6,12 +6,16 @@ import OpenDashboardMenuItem from '../OpenDashboardMenuItem'; ...@@ -6,12 +6,16 @@ import OpenDashboardMenuItem from '../OpenDashboardMenuItem';
describe('OpenDashboardMenuItem', () => { describe('OpenDashboardMenuItem', () => {
let fakeDashboard; let fakeDashboard;
let fakeToastMessenger;
beforeEach(() => { beforeEach(() => {
fakeDashboard = { fakeDashboard = {
getAuthToken: sinon.stub().resolves('auth_token'), getAuthToken: sinon.stub().resolves('auth_token'),
open: sinon.stub(), open: sinon.stub(),
}; };
fakeToastMessenger = {
error: sinon.stub(),
};
}); });
function createComponent({ isMenuOpen = false } = {}) { function createComponent({ isMenuOpen = false } = {}) {
...@@ -19,10 +23,23 @@ describe('OpenDashboardMenuItem', () => { ...@@ -19,10 +23,23 @@ describe('OpenDashboardMenuItem', () => {
<OpenDashboardMenuItem <OpenDashboardMenuItem
isMenuOpen={isMenuOpen} isMenuOpen={isMenuOpen}
dashboard={fakeDashboard} dashboard={fakeDashboard}
toastMessenger={fakeToastMessenger}
/>, />,
); );
} }
async function createOpenComponent() {
const wrapper = createComponent({ isMenuOpen: true });
// Wait for an enabled menu item, which means loading the auth token finished
await waitFor(() => {
wrapper.update();
return wrapper.exists('MenuItem[isDisabled=false]');
});
return wrapper;
}
context('when menu is closed', () => { context('when menu is closed', () => {
it('does not try to load auth token', () => { it('does not try to load auth token', () => {
createComponent(); createComponent();
...@@ -39,20 +56,11 @@ describe('OpenDashboardMenuItem', () => { ...@@ -39,20 +56,11 @@ describe('OpenDashboardMenuItem', () => {
wrapper.find('MenuItem').props().onClick(); wrapper.find('MenuItem').props().onClick();
assert.notCalled(fakeDashboard.open); assert.notCalled(fakeDashboard.open);
assert.notCalled(fakeToastMessenger.error);
}); });
}); });
context('when menu is open', () => { context('when menu is open', () => {
async function createOpenComponent() {
const wrapper = createComponent({ isMenuOpen: true });
// Wait for an enabled menu item, which means the auth token was loaded
await waitFor(() => wrapper.find('MenuItem[isDisabled=false]'));
wrapper.update();
return wrapper;
}
it('loads auth token', async () => { it('loads auth token', async () => {
await createOpenComponent(); await createOpenComponent();
assert.called(fakeDashboard.getAuthToken); assert.called(fakeDashboard.getAuthToken);
...@@ -68,33 +76,61 @@ describe('OpenDashboardMenuItem', () => { ...@@ -68,33 +76,61 @@ describe('OpenDashboardMenuItem', () => {
wrapper.find('MenuItem').props().onClick(); wrapper.find('MenuItem').props().onClick();
assert.calledWith(fakeDashboard.open, 'auth_token'); assert.calledWith(fakeDashboard.open, 'auth_token');
assert.notCalled(fakeToastMessenger.error);
}); });
});
it('logs error if getting auth token fails', async () => { context('when menu opening changes', () => {
const error = new Error('Error loading auth token'); it('goes back to loading state', async () => {
const wrapper = await createOpenComponent();
assert.isFalse(wrapper.find('MenuItem').prop('isDisabled'));
wrapper.setProps({ isMenuOpen: false });
assert.isTrue(wrapper.find('MenuItem').prop('isDisabled'));
});
});
context('when loading auth token fails', () => {
const error = new Error('Error loading auth token');
beforeEach(() => {
fakeDashboard.getAuthToken.rejects(error); fakeDashboard.getAuthToken.rejects(error);
sinon.stub(console, 'warn'); sinon.stub(console, 'warn');
});
afterEach(() => console.warn.restore());
it('logs error if getting auth token fails', async () => {
createOpenComponent();
try { assert.called(fakeDashboard.getAuthToken);
createOpenComponent();
await waitFor(() => {
assert.called(fakeDashboard.getAuthToken); const { lastCall } = console.warn;
if (!lastCall) {
await waitFor(() => { return false;
const { lastCall } = console.warn; }
if (!lastCall) {
return false; const { args } = lastCall;
} return (
args[0] === 'An error occurred while getting auth token' &&
const { args } = lastCall; args[1] === error
return ( );
args[0] === 'An error occurred while getting auth token' && });
args[1] === error });
);
}); it('shows toast message when trying to open dashboard', async () => {
} finally { const wrapper = await createOpenComponent();
console.warn.restore(); const menuItem = wrapper.find('MenuItem');
}
menuItem.props().onClick();
assert.notCalled(fakeDashboard.open);
assert.calledWith(
fakeToastMessenger.error,
"Can't open dashboard: You must reload the page.",
{ autoDismiss: false },
);
}); });
}); });
}); });
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment