Browse Source

Merge #561

561: Replicate missing functionality into new taker UI r=klochowicz a=klochowicz

- display information about errors when possible (e.g. when cfd action failed),
- inform the user if the daemon got shut down

also, as a separate commit, remove checking for daemon's presence in the maker's
UI (automation can poll for it by itself).

Co-authored-by: Mariusz Klochowicz <mariusz@klochowicz.com>
new-http-api
bors[bot] 3 years ago
committed by GitHub
parent
commit
74854230ee
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      maker-frontend/src/MakerApp.tsx
  2. 24
      taker-frontend/src/App.tsx
  3. 50
      taker-frontend/src/components/BackendMonitor.tsx
  4. 28
      taker-frontend/src/components/ErrorToast.tsx
  5. 11
      taker-frontend/src/components/History.tsx
  6. 18
      taker-frontend/src/components/HttpError.tsx

2
maker-frontend/src/MakerApp.tsx

@ -19,7 +19,6 @@ import {
import React, { useEffect, useState } from "react";
import { useAsync } from "react-async";
import { useEventSource } from "react-sse-hooks";
import { useBackendMonitor } from "./components/BackendMonitor";
import { CfdTable } from "./components/cfdtables/CfdTable";
import CurrencyInputField from "./components/CurrencyInputField";
import CurrentPrice from "./components/CurrentPrice";
@ -44,7 +43,6 @@ export default function App() {
const priceInfo = useLatestEvent<PriceInfo>(source, "quote");
const toast = useToast();
useBackendMonitor(toast, 5000); // 5s timeout
let [minQuantity, setMinQuantity] = useState<string>("10");
let [maxQuantity, setMaxQuantity] = useState<string>("100");

24
taker-frontend/src/App.tsx

@ -5,6 +5,8 @@ import { useAsync } from "react-async";
import { Route, Switch } from "react-router-dom";
import { useEventSource } from "react-sse-hooks";
import useWebSocket from "react-use-websocket";
import { useBackendMonitor } from "./components/BackendMonitor";
import createErrorToast from "./components/ErrorToast";
import History from "./components/History";
import Nav from "./components/NavBar";
import Trade from "./components/Trade";
@ -43,6 +45,7 @@ async function postCfdOrderRequest(payload: CfdOrderRequestPayload) {
export const App = () => {
const toast = useToast();
useBackendMonitor(toast, 5000); // 5s timeout
const {
lastMessage,
@ -81,15 +84,7 @@ export const App = () => {
let res = await getMargin(payload as MarginRequestPayload);
setMargin(res.margin.toString());
} catch (e) {
const description = typeof e === "string" ? e : JSON.stringify(e);
toast({
title: "Error",
description,
status: "error",
duration: 9000,
isClosable: true,
});
createErrorToast(toast, e);
}
},
});
@ -99,16 +94,7 @@ export const App = () => {
try {
await postCfdOrderRequest(payload as CfdOrderRequestPayload);
} catch (e) {
console.error(`Error received: ${JSON.stringify(e)}`);
const description = typeof e === "string" ? e : JSON.stringify(e);
toast({
title: "Error",
description,
status: "error",
duration: 9000,
isClosable: true,
});
createErrorToast(toast, e);
}
},
});

50
taker-frontend/src/components/BackendMonitor.tsx

@ -0,0 +1,50 @@
import { useEffect, useRef } from "react";
async function fetchWithTimeout(resource: any, timeout: number) {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
const response = await fetch(resource, {
signal: controller.signal,
});
clearTimeout(id);
return response;
}
// Check for backend's presence by sending a request to '/alive' endpoint.
// When the backend's not there, the request is likely to timeout, triggering a
// persistent toast notification that goes away when the daemon is back online.
export function useBackendMonitor(toast: any, timeout_ms: number): void {
const toastIdRef = useRef();
const checkForBackend: () => void = async () => {
try {
const res = await fetchWithTimeout("/alive", timeout_ms);
// In case we get a response, but it's not what we expected
if (res.status.toString().startsWith("2")) {
if (toastIdRef.current) {
toast.close(toastIdRef.current);
}
} else {
throw new Error(res.statusText);
}
} catch (e) {
if (!toastIdRef.current) {
toastIdRef.current = toast({
title: "Connection Error",
description: "Daemon process is not running",
status: "error",
position: "top",
duration: timeout_ms * 100000, // we don't want this to be closed
isClosable: false,
});
}
}
};
useEffect(() => {
const interval = setInterval(() => checkForBackend(), timeout_ms);
return () => {
clearInterval(interval);
};
});
}

28
taker-frontend/src/components/ErrorToast.tsx

@ -0,0 +1,28 @@
import { HttpError } from "./HttpError";
// A generic way of creating an error toast
// TODO: Don't use any (`toast: typeof useToast` did not work :( )
export default function createErrorToast(toast: any, e: any) {
if (e instanceof HttpError) {
const description = e.detail ? e.detail : "";
toast({
title: "Error: " + e.title,
description,
status: "error",
duration: 10000,
isClosable: true,
});
} else {
console.log(e);
const description = typeof e === "string" ? e : JSON.stringify(e);
toast({
title: "Error",
description,
status: "error",
duration: 10000,
isClosable: true,
});
}
}

11
taker-frontend/src/components/History.tsx

@ -30,6 +30,7 @@ import {
} from "@chakra-ui/react";
import * as React from "react";
import { useAsync } from "react-async";
import createErrorToast from "./ErrorToast";
import { Cfd, StateGroupKey, StateKey, Tx, TxLabel } from "./Types";
interface HistoryProps {
@ -90,15 +91,7 @@ const CfdDetails = ({ cfd }: CfdDetailsProps) => {
console.log(`Closing: ${orderId} ${action}`);
await doPostAction(orderId, action);
} catch (e) {
const description = typeof e === "string" ? e : JSON.stringify(e);
toast({
title: "Error",
description,
status: "error",
duration: 9000,
isClosable: true,
});
createErrorToast(toast, e);
}
},
});

18
taker-frontend/src/components/HttpError.tsx

@ -0,0 +1,18 @@
// A wrapper to parse RFC 7807
// Pass result of `await response.json()` into the constructor.
export class HttpError extends Error {
title: string;
detail?: string;
// FIXME: Constructor can't be async, so we can't pass `Response` here
constructor(json_resp: any) {
let title = json_resp.title;
super(title);
this.title = title;
if (json_resp.detail) {
this.detail = json_resp.detail;
}
Object.setPrototypeOf(this, HttpError.prototype);
}
}
Loading…
Cancel
Save