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 type { DashboardService } from '../services/dashboard';
import type { ToastMessengerService } from '../services/toast-messenger';
import MenuItem from './MenuItem';
export type OpenDashboardMenuItemProps = {
......@@ -9,36 +10,54 @@ export type OpenDashboardMenuItemProps = {
// Injected
dashboard: DashboardService;
toastMessenger: ToastMessengerService;
};
function OpenDashboardMenuItem({
dashboard,
isMenuOpen,
toastMessenger,
}: 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(() => {
// 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
if (isMenuOpen) {
dashboard
.getAuthToken()
.then(setAuthToken)
.catch(error =>
console.warn('An error occurred while getting auth token', error),
);
.then(setAuthTokenOrError)
.catch(error => {
console.warn('An error occurred while getting auth token', error);
setAuthTokenOrError(error);
});
}
// Discard previous token just before trying to fetch a new one
return () => setAuthToken(undefined);
// Discard previous token and error, just before trying to fetch a new one
return () => setAuthTokenOrError(undefined);
}, [dashboard, isMenuOpen]);
return (
<MenuItem
label="Open dashboard"
isDisabled={!authToken}
onClick={() => authToken && dashboard.open(authToken)}
/>
<MenuItem label="Open dashboard" isDisabled={loading} onClick={onClick} />
);
}
export default withServices(OpenDashboardMenuItem, ['dashboard']);
export default withServices(OpenDashboardMenuItem, [
'dashboard',
'toastMessenger',
]);
......@@ -6,12 +6,16 @@ import OpenDashboardMenuItem from '../OpenDashboardMenuItem';
describe('OpenDashboardMenuItem', () => {
let fakeDashboard;
let fakeToastMessenger;
beforeEach(() => {
fakeDashboard = {
getAuthToken: sinon.stub().resolves('auth_token'),
open: sinon.stub(),
};
fakeToastMessenger = {
error: sinon.stub(),
};
});
function createComponent({ isMenuOpen = false } = {}) {
......@@ -19,10 +23,23 @@ describe('OpenDashboardMenuItem', () => {
<OpenDashboardMenuItem
isMenuOpen={isMenuOpen}
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', () => {
it('does not try to load auth token', () => {
createComponent();
......@@ -39,20 +56,11 @@ describe('OpenDashboardMenuItem', () => {
wrapper.find('MenuItem').props().onClick();
assert.notCalled(fakeDashboard.open);
assert.notCalled(fakeToastMessenger.error);
});
});
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 () => {
await createOpenComponent();
assert.called(fakeDashboard.getAuthToken);
......@@ -68,33 +76,61 @@ describe('OpenDashboardMenuItem', () => {
wrapper.find('MenuItem').props().onClick();
assert.calledWith(fakeDashboard.open, 'auth_token');
assert.notCalled(fakeToastMessenger.error);
});
});
it('logs error if getting auth token fails', async () => {
const error = new Error('Error loading auth token');
context('when menu opening changes', () => {
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);
sinon.stub(console, 'warn');
});
afterEach(() => console.warn.restore());
it('logs error if getting auth token fails', async () => {
createOpenComponent();
try {
createOpenComponent();
assert.called(fakeDashboard.getAuthToken);
await waitFor(() => {
const { lastCall } = console.warn;
if (!lastCall) {
return false;
}
const { args } = lastCall;
return (
args[0] === 'An error occurred while getting auth token' &&
args[1] === error
);
});
} finally {
console.warn.restore();
}
assert.called(fakeDashboard.getAuthToken);
await waitFor(() => {
const { lastCall } = console.warn;
if (!lastCall) {
return false;
}
const { args } = lastCall;
return (
args[0] === 'An error occurred while getting auth token' &&
args[1] === error
);
});
});
it('shows toast message when trying to open dashboard', async () => {
const wrapper = await createOpenComponent();
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