Fix error handling to display them correctly without crashing

This commit is contained in:
2020-06-17 18:48:33 +02:00
parent e1c7363d7b
commit eb2be6b94e
7 changed files with 60 additions and 28 deletions

View File

@@ -4,6 +4,6 @@ import { AppModule } from './app.module';
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);
app.enableCors(); app.enableCors();
await app.listen(3000); await app.listen(3500);
} }
bootstrap(); bootstrap();

View File

@@ -0,0 +1,12 @@
// @ts-ignore
interface RequestError extends Error {
response?: any;
}
interface RequestErrorConstructor extends ErrorConstructor {
new(message?: string): RequestError;
(message?: string): RequestError;
readonly prototype: RequestError;
}
declare var RequestError: RequestErrorConstructor;

View File

@@ -31,7 +31,7 @@ export class SinusBotService {
.then(token => this.bearer = token) .then(token => this.bearer = token)
.catch(error => { .catch(error => {
logger.error(`Oh oh! Something went wrong while fetching Bearer token.`, error); logger.error(`Oh oh! Something went wrong while fetching Bearer token.`, error);
throw this.createHttpException(HttpStatus.UNAUTHORIZED, error.message); throw this.createHttpException(HttpStatus.UNAUTHORIZED, `Fetching Bearer token for Sinusbot failed.`);
}); });
logger.debug( logger.debug(
`Seems like I have my Bearer token! `Seems like I have my Bearer token!
@@ -46,7 +46,7 @@ export class SinusBotService {
.then(data => this.consumeTunakillResponse(data)) .then(data => this.consumeTunakillResponse(data))
.catch(error => { .catch(error => {
logger.error(`I couldn't fetch user data.`, error); logger.error(`I couldn't fetch user data.`, error);
throw this.createHttpException(error.statusCode, error.message); throw this.createHttpException(HttpStatus.INTERNAL_SERVER_ERROR, error.message);
}); });
} }
@@ -90,12 +90,12 @@ export class SinusBotService {
private consumeTunakillResponse(data: any): TableEntry[] { private consumeTunakillResponse(data: any): TableEntry[] {
if (!(data !== null || data[0] !== null || data[0].data !== null)) { if (!(data !== null || data[0] !== null || data[0].data !== null)) {
throw Error('Response from SinusBot does not have any data to parse.') throw Error('Response from SinusBot does not have any data to parse.');
} }
const response = data[0].data; const response = data[0].data;
if (!(response.length > 0)) { if (!(response.length > 0)) {
throw Error('User list is empty.') throw Error('Response from SinusBot does not have any data to parse.');
} }
return response return response
@@ -134,7 +134,9 @@ export class SinusBotService {
private checkStatus = response => { private checkStatus = response => {
if (!response.ok) { if (!response.ok) {
throw Error(response.statusText); let err = new RequestError(response.errorText);
err.response = response;
throw err;
} }
return response; return response;
} }

View File

@@ -1,46 +1,50 @@
import React from 'react'; import React from 'react';
import './App.scss'; import './App.scss';
import UserStatsMockService from "./mock/UserStatsMockService"; import UserStatsMockService from "./mock/UserStatsMockService";
import UserStats from "./models/TableEntry";
import UserStatsService from "./services/UserStatsService"; import UserStatsService from "./services/UserStatsService";
import {findDOMNode} from "react-dom"; import {findDOMNode} from "react-dom";
import TableEntry from "./models/TableEntry"; import TableEntry from "./models/TableEntry";
interface State { interface State {
error?: any, error?: string,
isLoaded: boolean, isLoaded: boolean,
users?: UserStats[], users?: TableEntry[],
mock?: UserStats[] mock?: TableEntry[]
} }
export default class App extends React.Component { export default class App extends React.Component {
private apiService: UserStatsService = new UserStatsService(); private apiService: UserStatsService = new UserStatsService();
private mockService: UserStatsService = new UserStatsMockService(); private mockService = new UserStatsMockService();
state: State = { state: State = {
error: null, error: undefined,
isLoaded: false, isLoaded: false,
users: undefined, users: undefined,
mock: undefined mock: undefined
} }
componentDidMount() { componentDidMount() {
this.setState({isLoaded: false}) this.setState({isLoaded: false, mock: this.mockService.getStatsWithoutPromise()});
this.mockService.getStats() // this.mockService.getStats()
.then(data => this.setState({ // .then(data => this.setState({
data: data, // data: data,
mock: data // mock: data
})); // }))
// .finally(() => {
// });
this.apiService.getStats() this.apiService.getStats()
.then(data => this.setState({ .then(data => this.setState({
isLoaded: true, isLoaded: true,
users: data users: data
})) }))
.catch(error => this.setState({ .catch(error => {
isLoaded: true, error.response.json()
error: error .then((err: any) => this.setState({
})); isLoaded: true,
error: err.error
}))
});
} }
componentDidUpdate() { componentDidUpdate() {
@@ -73,6 +77,8 @@ export default class App extends React.Component {
return this.createTableEntries(users); return this.createTableEntries(users);
} else if (isLoaded && error != null && mock != null) { } else if (isLoaded && error != null && mock != null) {
return this.createTableEntries(mock); return this.createTableEntries(mock);
} else if (mock != null) {
return this.createTableEntries(mock);
} }
} }
@@ -80,7 +86,7 @@ export default class App extends React.Component {
return ( return (
<div className="App"> <div className="App">
<p className="title">Humenius' TeamSpeak 3-Ranking</p> <p className="title">Humenius' TeamSpeak 3-Ranking</p>
{ this.state.error != null ? <p className="error-message">Data could not be loaded. Please try again later!</p> : null} { this.state.error != null ? <p className="error-message"> { this.state.error } Please try again later!</p> : null}
<table> <table>
<thead> <thead>
<tr> <tr>
@@ -89,7 +95,7 @@ export default class App extends React.Component {
<th>Online time</th> <th>Online time</th>
</tr> </tr>
</thead> </thead>
<tbody className={this.state.error != null ? "blurred" : undefined}> <tbody className={this.state.error != null || !this.state.isLoaded ? "blurred" : undefined}>
{this.renderTableData()} {this.renderTableData()}
</tbody> </tbody>
</table> </table>

View File

@@ -19,4 +19,8 @@ export default class UserStatsMockService extends UserStatsService {
async getStats(): Promise<TableEntry[]> { async getStats(): Promise<TableEntry[]> {
return Promise.resolve(this.entries); return Promise.resolve(this.entries);
} }
getStatsWithoutPromise(): TableEntry[] {
return this.entries;
}
} }

View File

@@ -0,0 +1,3 @@
export default class RequestError extends Error {
response?: any;
}

View File

@@ -1,4 +1,6 @@
import TableEntry from "../models/TableEntry"; import TableEntry from "../models/TableEntry";
import RequestError from "../models/RequestError";
export default class UserStatsService { export default class UserStatsService {
@@ -13,13 +15,16 @@ export default class UserStatsService {
async getStats(): Promise<TableEntry[]> { async getStats(): Promise<TableEntry[]> {
return fetch(this.apiURL, this.requestInit) return fetch(this.apiURL, this.requestInit)
.then(res => this.checkResponse(res)) .then(res => UserStatsService.checkResponse(res))
.then(data => data.json()) .then(data => data.json());
} }
private checkResponse(response: any): any { private static checkResponse(response: any): any {
if (!response.ok) { if (!response.ok) {
throw Error(response.statusText); console.log(response);
let error = new RequestError(response.statusText);
error.response = response;
throw error;
} }
return response; return response;
} }