Skip to content

Commit

Permalink
Error notifications added
Browse files Browse the repository at this point in the history
  • Loading branch information
contributeless committed Apr 5, 2021
1 parent bdac1db commit 8d2f53c
Show file tree
Hide file tree
Showing 15 changed files with 221 additions and 54 deletions.
3 changes: 3 additions & 0 deletions src/OV.Node/src/Entities/ServerErrorModel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface ServerErrorModel {
errors: string[]
}
17 changes: 12 additions & 5 deletions src/OV.Node/src/controllers/ConfigController.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NextFunction, Request, Response } from 'express';
import { ServerErrorModel } from '../Entities/ServerErrorModel';
import MongoConfigStorage from '../logic/MongoConfigStorage';
import MongoConnectionFactory from '../logic/MongoConnectionFactory';
import { MongoConfig } from '../models/MongoConfig';
Expand All @@ -8,12 +9,18 @@ class ConfigController {
try {
const configData: MongoConfig = req.body;

await MongoConfigStorage.save(configData);

res.json({
isConfigured: await MongoConnectionFactory.isInitialized(),
connectionString: configData?.connectionString
});
if(await MongoConnectionFactory.isConnectionStringValid(configData.connectionString)){
await MongoConfigStorage.save(configData);
res.json({
isConfigured: await MongoConnectionFactory.isInitialized(),
connectionString: configData?.connectionString
});
} else{
res.status(400).json({
errors: ["Invalid connection string or server is unreachable"]
} as ServerErrorModel)
}
} catch (error) {
next(error);
}
Expand Down
17 changes: 16 additions & 1 deletion src/OV.Node/src/logic/MongoConnectionFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@ import { MongoClient } from "mongodb";
import MongoConfigStorage from "./MongoConfigStorage"
class MongoConnectionFactory {

public async isConnectionStringValid(connectionString: string) {
try {
const client = new MongoClient(connectionString);

await client.connect();

await client.db("admin").command({ ping: 1 });

return true;
}
catch {
return false;
}
}

public async isInitialized(): Promise<boolean> {
try{
const client = await this.getConnection();
Expand All @@ -17,7 +32,7 @@ class MongoConnectionFactory {

public async getConnection(): Promise<MongoClient> {
const config = await MongoConfigStorage.get();
const uri = config.connectionString; //"mongodb://localhost:27017/";
const uri = config.connectionString;

const client = new MongoClient(uri);

Expand Down
25 changes: 23 additions & 2 deletions src/OV.React/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/OV.React/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
},
"license": "ISC",
"dependencies": {
"eventemitter3": "^4.0.7",
"moment": "^2.29.1",
"react": "^17.0.2",
"react-datetime": "^3.0.4",
"react-dom": "^17.0.2",
"react-json-view": "^1.21.3",
"react-toastify": "^7.0.3",
"reactjs-popup": "^2.0.4",
"unstated": "^2.1.1"
},
Expand All @@ -32,6 +34,7 @@
"@babel/preset-react": "^7.13.13",
"@babel/preset-typescript": "^7.13.0",
"@svgr/webpack": "^5.5.0",
"@types/eventemitter3": "^2.0.2",
"@types/node": "^14.14.37",
"@types/react": "^17.0.3",
"@types/react-dom": "^17.0.3",
Expand Down
16 changes: 6 additions & 10 deletions src/OV.React/src/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,31 @@
import * as React from 'react';
import { OplogListContainer } from './views/OplogListContainer';
import { ConfigService } from './services/ConfigService';
import { Provider, Subscribe } from 'unstated';
import { OplogFilterContainer } from './state/OplogFilterContainer';
import { OplogContainer } from './state/OplogContainer';
import { Loader } from './views/Loader';
import { ServiceContainer } from './state/ServiceContainer';
import { SettingsContainer } from './state/SettingsContainer';
import { ErrorNotificationList } from './views/ErrorNotificationList';

type Props = {
interface AppProps {

};
type State = {
isDbConnectionConfigured: boolean,

interface AppState {
};

export class App extends React.Component<Props, State> {
export class App extends React.Component<AppProps, AppState> {
filterContainer: OplogFilterContainer;
oplogContainer: OplogContainer;
serviceContainer: ServiceContainer;
settingsContainer: SettingsContainer;
constructor(props: Props){
constructor(props: AppProps){
super(props);

this.serviceContainer = new ServiceContainer();
this.settingsContainer = new SettingsContainer(this.serviceContainer);
this.filterContainer = new OplogFilterContainer(this.serviceContainer);
this.oplogContainer = new OplogContainer(this.filterContainer, this.serviceContainer);
this.state = {
isDbConnectionConfigured: true,
}
}

async componentDidMount() {
Expand All @@ -54,6 +49,7 @@ export class App extends React.Component<Props, State> {
service.isLoadingEnabled() && <Loader />
)}
</Subscribe>
<ErrorNotificationList></ErrorNotificationList>
</>
</Provider>
);
Expand Down
15 changes: 15 additions & 0 deletions src/OV.React/src/src/Services/EventHub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import EventEmitter from 'eventemitter3';
import { EventTypes } from '../models/EventTypes';

const eventEmitter = new EventEmitter();

const EventHub = {
on: (event: EventTypes, fn: (value: object) => void) => eventEmitter.on(event, fn),
once: (event: EventTypes, fn: (value: object) => void) => eventEmitter.once(event, fn),
off: (event: EventTypes, fn?: (value: object) => void) => eventEmitter.off(event, fn),
emit: (event: EventTypes, payload: object) => eventEmitter.emit(event, payload)
}

Object.freeze(EventHub);

export default EventHub;
112 changes: 77 additions & 35 deletions src/OV.React/src/src/Services/HttpUtility.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,92 @@
import {settings as appSettings} from './AppSettings';
import { ServerErrorModel } from '../models/ServerErrorModel';
import { settings as appSettings } from './AppSettings';

export class HttpUtility {
public static makeUrl(relativePart = ''){
public static makeUrl(relativePart = '') {
return HttpUtility.trimEndSlash(appSettings.baseUrl) + "/" + HttpUtility.trimStartSlash(relativePart);
}
private static trimEndSlash(source: string){

private static trimEndSlash(source: string) {
return source.replace(/\/+$/g, '');
}

private static trimStartSlash(source: string){
private static trimStartSlash(source: string) {
return source.replace(/^\/+/g, '');
}

public static async post(url = '', data = {}){
// Default options are marked with *
const response = await fetch(url, {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json'
// 'Content-Type': 'application/x-www-form-urlencoded',
},
redirect: 'follow', // manual, *follow, error
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
body: JSON.stringify(data) // body data type must match "Content-Type" header
});
return response.json(); // parses JSON response into native JavaScript objects
public static async post(url = '', data = {}) {
try {
// Default options are marked with *
const response = await fetch(url, {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json'
// 'Content-Type': 'application/x-www-form-urlencoded',
},
redirect: 'follow', // manual, *follow, error
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
body: JSON.stringify(data) // body data type must match "Content-Type" header
});
console.log(response.ok);

if (response.ok) {
return response.json();
} else {
const parsedBody = await response.json(); // parses JSON response into native JavaScript objects
if ((parsedBody as ServerErrorModel)?.errors?.length > 0) {
return Promise.reject(parsedBody);
} else {
return Promise.reject({
errors: ["Unexpected server error"]
} as ServerErrorModel);
}
}

}
catch {
return {
errors: ["Network error"]
} as ServerErrorModel;
}
}

public static async get(url = ''){
public static async get(url = '') {
// Default options are marked with *
const response = await fetch(url, {
method: 'GET', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json'
// 'Content-Type': 'application/x-www-form-urlencoded',
},
redirect: 'follow', // manual, *follow, error
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
});
return response.json(); // parses JSON response into native JavaScript objects
try {
const response = await fetch(url, {
method: 'GET', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json'
// 'Content-Type': 'application/x-www-form-urlencoded',
},
redirect: 'follow', // manual, *follow, error
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
});

if (response.ok) {
return response.json();
} else {
const parsedBody = await response.json(); // parses JSON response into native JavaScript objects
if ((parsedBody as ServerErrorModel)?.errors?.length > 0) {
return Promise.reject(parsedBody);
} else {
return Promise.reject({
errors: ["Unexpected server error"]
} as ServerErrorModel);
}
}

}
catch {
return Promise.reject({
errors: ["Network error"]
} as ServerErrorModel);
}
}
}
3 changes: 3 additions & 0 deletions src/OV.React/src/src/models/EventTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export enum EventTypes {
FETCH_ERROR = "FETCH_ERROR"
}
3 changes: 3 additions & 0 deletions src/OV.React/src/src/models/ServerErrorModel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface ServerErrorModel {
errors: string[]
}
5 changes: 5 additions & 0 deletions src/OV.React/src/src/state/BaseContainer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Container } from "unstated";
import { ServerErrorModel } from "../models/ServerErrorModel";
import { ServiceContainer } from "./ServiceContainer";

export class BaseContainer<State extends object> extends Container<State> {
Expand All @@ -16,6 +17,10 @@ export class BaseContainer<State extends object> extends Container<State> {
}
return await func();
}
catch(error: any){
this.serviceContainer.onFetchError(error as ServerErrorModel);
throw error;
}
finally {
if(!skipLoadersChange){
await this.serviceContainer.decrementLoadersCount();
Expand Down
9 changes: 9 additions & 0 deletions src/OV.React/src/src/state/ServiceContainer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { Container } from "unstated";
import { EventTypes } from "../models/EventTypes";
import { ServerErrorModel } from "../models/ServerErrorModel";
import EventHub from "../services/EventHub";

export interface ServiceContainerState {
loadersCount: number;
Expand All @@ -24,4 +27,10 @@ export class ServiceContainer extends Container<ServiceContainerState>{
isLoadingEnabled = () => {
return this.state.loadersCount > 0;
}

onFetchError = (error: ServerErrorModel) => {
EventHub.emit(EventTypes.FETCH_ERROR, error ?? {
errors: []
} as ServerErrorModel)
}
}
Loading

0 comments on commit 8d2f53c

Please sign in to comment.