Add simple error handling for when restaurant data cannot be fetched

This commit is contained in:
Mikko Ahlroth 2020-01-02 21:27:45 +02:00
parent 400821bf82
commit 1ed1913197
5 changed files with 81 additions and 27 deletions

View file

@ -1,7 +1,7 @@
import { HTML_ID, RESTAURANTS, DAY_NAMES } from './config.js'; import { HTML_ID, RESTAURANTS, DAY_NAMES } from './config.js';
import { getAllMenus } from './sodexo-api.js'; import { getAllMenus, SodexoAPIError } from './sodexo-api.js';
import { parseBlob } from './parser.js'; import { parseBlob } from './parser.js';
import { renderMenu } from './view.js'; import { renderMenu, renderError } from './view.js';
import { el } from './dom.js'; import { el } from './dom.js';
import { VERSION, SOURCE } from './version.js'; import { VERSION, SOURCE } from './version.js';
@ -14,7 +14,13 @@ async function init() {
rootEl.innerText = 'Loading…'; rootEl.innerText = 'Loading…';
const menus = await getAllMenus(RESTAURANTS); const menus = await getAllMenus(RESTAURANTS);
const parsedMenus = menus.map((m, idx) => parseBlob(m, RESTAURANTS[idx])); const parsedMenus = menus.map((m, idx) => {
if (!(m instanceof SodexoAPIError)) {
return parseBlob(m, RESTAURANTS[idx]);
} else {
return m;
}
});
rootEl.innerText = ''; rootEl.innerText = '';
@ -25,8 +31,11 @@ async function init() {
); );
} }
for (const menu of parsedMenus) { for (const data of parsedMenus) {
const [heading, ...elems] = renderMenu(menu); if (data instanceof SodexoAPIError) {
rootEl.appendChild(renderError(data));
} else {
const [heading, ...elems] = renderMenu(data);
rootEl.appendChild(heading); rootEl.appendChild(heading);
for (const day of DAY_NAMES) { for (const day of DAY_NAMES) {
@ -45,6 +54,7 @@ async function init() {
} }
for (const elem of elems) { rootEl.appendChild(elem); } for (const elem of elems) { rootEl.appendChild(elem); }
} }
}
const versionEl = el('div', { classes: ['version'], text: `Versio ${VERSION}. ` }); const versionEl = el('div', { classes: ['version'], text: `Versio ${VERSION}. ` });
versionEl.appendChild( versionEl.appendChild(

View file

@ -3,7 +3,14 @@ import { ServerBlob } from './types.js';
const API_URL = new URL('https://www.sodexo.fi/ruokalistat/output/weekly_json/'); const API_URL = new URL('https://www.sodexo.fi/ruokalistat/output/weekly_json/');
export class SodexoAPIError extends Error { } export class SodexoAPIError extends Error {
public restaurantId: RestaurantId;
constructor(message: string, restaurantId: RestaurantId) {
super(message);
this.restaurantId = restaurantId;
}
}
export async function getMenu(restaurantId: RestaurantId): Promise<ServerBlob> { export async function getMenu(restaurantId: RestaurantId): Promise<ServerBlob> {
const url = new URL(String(restaurantId), API_URL); const url = new URL(String(restaurantId), API_URL);
@ -13,15 +20,28 @@ export async function getMenu(restaurantId: RestaurantId): Promise<ServerBlob> {
return await resp.json(); return await resp.json();
} else { } else {
console.error(resp); console.error(resp);
throw new SodexoAPIError(`Got invalid response: ${resp.status}.`); throw new SodexoAPIError(
`Got invalid response: ${resp.status} ${resp.statusText}.`,
restaurantId
);
} }
} }
export async function getAllMenus(restaurants: readonly RestaurantId[]): Promise<ServerBlob[]> { export type MenuResult = ServerBlob | SodexoAPIError;
const promises = [];
export async function getAllMenus(restaurants: readonly RestaurantId[]): Promise<MenuResult[]> {
const promises: Promise<MenuResult>[] = [];
for (const restaurant of restaurants) { for (const restaurant of restaurants) {
promises.push(getMenu(restaurant)); promises.push(
new Promise(async resolve => {
try {
resolve(await getMenu(restaurant));
} catch (e) {
resolve(e);
}
})
);
} }
return await Promise.all(promises); return await Promise.all(promises);

View file

@ -1,3 +1,3 @@
export const VERSION = '1.0.0'; export const VERSION = '1.0.1';
export const SOURCE = 'https://gitlab.com/Nicd/sodexo-menu'; export const SOURCE = 'https://gitlab.com/Nicd/sodexo-menu';

View file

@ -1,5 +1,7 @@
import { MenuData, Course } from './types.js'; import { MenuData, Course } from './types.js';
import { el } from './dom.js'; import { el } from './dom.js';
import { SodexoAPIError } from './sodexo-api.js';
import { RESTAURANTS } from './config.js';
function renderCourse(course: Course): HTMLLIElement { function renderCourse(course: Course): HTMLLIElement {
const li = el('li'); const li = el('li');
@ -44,3 +46,13 @@ export function renderMenu(menu: MenuData): HTMLElement[] {
return [heading, ...dayElements, metaElem]; return [heading, ...dayElements, metaElem];
} }
export function renderError(data: SodexoAPIError): HTMLElement {
const errorDiv = el('div', { classes: ['error'] });
errorDiv.appendChild(
el('h2', { text: `Unable to fetch menu for "${RESTAURANTS[data.restaurantId]}":` })
);
errorDiv.appendChild(el('p', { text: data.message }));
errorDiv.appendChild(el('p', { classes: ['stack'], text: data.stack }));
return errorDiv;
}

View file

@ -39,7 +39,7 @@ main {
gap: 10px; gap: 10px;
} }
h2, .meta { h2, .meta, .error {
grid-column: span 5; grid-column: span 5;
} }
@ -118,3 +118,15 @@ p.course-price {
.version { .version {
font-size: 75%; font-size: 75%;
} }
.error h2 {
margin-bottom: 0;
}
.error p {
margin: 5px 0;
}
.error .stack {
white-space: pre-wrap;
}