UI for quality of service meter. NEXT: Needs tests.
parent
6e9ff55a38
commit
8b9585b145
|
@ -0,0 +1,9 @@
|
|||
import { PingDictionary } from "../../devices/connectivity/qos";
|
||||
|
||||
export function fakePings(): PingDictionary {
|
||||
return {
|
||||
"a": { kind: "timeout", start: 1, end: 4, id: "a" },
|
||||
"b": { kind: "pending", start: 1, end: 4, id: "b" },
|
||||
"c": { kind: "complete", start: 2, end: 3, id: "c" }
|
||||
};
|
||||
}
|
|
@ -20,6 +20,7 @@ import {
|
|||
import { ResourceName } from "farmbot";
|
||||
import { fakeTimeSettings } from "../__test_support__/fake_time_settings";
|
||||
import { error } from "../toast/toast";
|
||||
import { fakePings } from "../__test_support__/fake_state/pings";
|
||||
|
||||
const FULLY_LOADED: ResourceName[] = [
|
||||
"Sequence", "Regimen", "FarmEvent", "Point", "Tool", "Device"];
|
||||
|
@ -42,6 +43,7 @@ const fakeProps = (): AppProps => {
|
|||
resources: buildResourceIndex().index,
|
||||
autoSync: false,
|
||||
alertCount: 0,
|
||||
pings: fakePings()
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import { ResourceIndex } from "./resources/interfaces";
|
|||
import { isBotOnline } from "./devices/must_be_online";
|
||||
import { getStatus } from "./connectivity/reducer_support";
|
||||
import { getAllAlerts } from "./messages/state_to_props";
|
||||
import { PingDictionary } from "./devices/connectivity/qos";
|
||||
|
||||
/** For the logger module */
|
||||
init();
|
||||
|
@ -50,6 +51,7 @@ export interface AppProps {
|
|||
resources: ResourceIndex;
|
||||
autoSync: boolean;
|
||||
alertCount: number;
|
||||
pings: PingDictionary;
|
||||
}
|
||||
|
||||
export function mapStateToProps(props: Everything): AppProps {
|
||||
|
@ -76,6 +78,7 @@ export function mapStateToProps(props: Everything): AppProps {
|
|||
resources: props.resources.index,
|
||||
autoSync: !!(fbosConfig && fbosConfig.auto_sync),
|
||||
alertCount: getAllAlerts(props.resources).length,
|
||||
pings: props.bot.connectivity.pings
|
||||
};
|
||||
}
|
||||
/** Time at which the app gives up and asks the user to refresh */
|
||||
|
@ -133,7 +136,8 @@ export class App extends React.Component<AppProps, {}> {
|
|||
tour={this.props.tour}
|
||||
autoSync={this.props.autoSync}
|
||||
alertCount={this.props.alertCount}
|
||||
device={getDeviceAccountSettings(this.props.resources)} />}
|
||||
device={getDeviceAccountSettings(this.props.resources)}
|
||||
pings={this.props.pings} />}
|
||||
{syncLoaded && this.props.children}
|
||||
{!(["controls", "account", "regimens"].includes(currentPage)) &&
|
||||
<ControlsPopup
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Connectivity, ConnectivityProps } from "../connectivity";
|
|||
import { bot } from "../../../__test_support__/fake_state/bot";
|
||||
import { StatusRowProps } from "../connectivity_row";
|
||||
import { fill } from "lodash";
|
||||
import { fakePings } from "../../../__test_support__/fake_state/pings";
|
||||
|
||||
describe("<Connectivity />", () => {
|
||||
const statusRow = {
|
||||
|
@ -26,6 +27,7 @@ describe("<Connectivity />", () => {
|
|||
bot,
|
||||
rowData,
|
||||
flags,
|
||||
pings: fakePings()
|
||||
});
|
||||
|
||||
it("sets hovered connection", () => {
|
||||
|
|
|
@ -7,6 +7,7 @@ import { SpecialStatus } from "farmbot";
|
|||
import { bot } from "../../../__test_support__/fake_state/bot";
|
||||
import { fakeDevice } from "../../../__test_support__/resource_index_builder";
|
||||
import { resetConnectionInfo } from "../../actions";
|
||||
import { fakePings } from "../../../__test_support__/fake_state/pings";
|
||||
|
||||
describe("<ConnectivityPanel/>", () => {
|
||||
const fakeProps = (): ConnectivityPanel["props"] => ({
|
||||
|
@ -14,6 +15,7 @@ describe("<ConnectivityPanel/>", () => {
|
|||
dispatch: jest.fn(),
|
||||
deviceAccount: fakeDevice(),
|
||||
status: SpecialStatus.SAVED,
|
||||
pings: fakePings()
|
||||
});
|
||||
|
||||
it("renders the default use case", () => {
|
||||
|
|
|
@ -8,11 +8,14 @@ import {
|
|||
ChipTemperatureDisplay, WiFiStrengthDisplay, VoltageDisplay
|
||||
} from "../components/fbos_settings/fbos_details";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { QosPanel } from "./qos_panel";
|
||||
import { PingDictionary } from "./qos";
|
||||
|
||||
export interface ConnectivityProps {
|
||||
bot: BotState;
|
||||
rowData: StatusRowProps[];
|
||||
flags: DiagnosisProps;
|
||||
pings: PingDictionary;
|
||||
}
|
||||
|
||||
interface ConnectivityState {
|
||||
|
@ -42,6 +45,7 @@ export class Connectivity
|
|||
<WiFiStrengthDisplay wifiStrength={wifi_level} />
|
||||
<VoltageDisplay throttled={throttled} />
|
||||
</div>
|
||||
<QosPanel pings={this.props.pings} />
|
||||
</Col>
|
||||
<Col md={12} lg={8}>
|
||||
<ConnectivityRow from={t("from")} to={t("to")} header={true} />
|
||||
|
|
|
@ -8,12 +8,14 @@ import { Connectivity } from "./connectivity";
|
|||
import { BotState } from "../interfaces";
|
||||
import { connectivityData } from "./generate_data";
|
||||
import { resetConnectionInfo } from "../actions";
|
||||
import { PingDictionary } from "./qos";
|
||||
|
||||
interface Props {
|
||||
status: SpecialStatus;
|
||||
dispatch: Function;
|
||||
bot: BotState;
|
||||
deviceAccount: TaggedDevice;
|
||||
pings: PingDictionary;
|
||||
}
|
||||
|
||||
export class ConnectivityPanel extends React.Component<Props, {}> {
|
||||
|
@ -39,7 +41,8 @@ export class ConnectivityPanel extends React.Component<Props, {}> {
|
|||
<Connectivity
|
||||
bot={this.props.bot}
|
||||
rowData={this.data.rowData}
|
||||
flags={this.data.flags} />
|
||||
flags={this.data.flags}
|
||||
pings={this.props.pings} />
|
||||
</WidgetBody>
|
||||
</Widget>;
|
||||
}
|
||||
|
|
|
@ -94,13 +94,17 @@ const mapper = (p: Ping) => (p.kind === "complete") ?
|
|||
|
||||
export const calculateLatency =
|
||||
(s: PingDictionary): LatencyReport => {
|
||||
const latency: number[] =
|
||||
let latency: number[] =
|
||||
betterCompact(getAll(s).map(mapper));
|
||||
|
||||
// Prevents "Infinity" from showing up in UI
|
||||
// when the app is loading or the bot is 100%
|
||||
// offline:
|
||||
if (latency.length == 0) { latency = [0]; }
|
||||
const average = Math.round(latency.reduce((a, b) => a + b, 0) / latency.length);
|
||||
return {
|
||||
best: Math.min(...latency) || 0,
|
||||
worst: Math.max(...latency) || 0,
|
||||
average: latency.reduce((a, b) => a + b, 0) / latency.length,
|
||||
best: Math.min(...latency),
|
||||
worst: Math.max(...latency),
|
||||
average,
|
||||
total: latency.length
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
import {
|
||||
calculateLatency,
|
||||
calculatePingLoss,
|
||||
PingDictionary,
|
||||
} from "./qos";
|
||||
import React from "react";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
|
||||
interface Props {
|
||||
pings: PingDictionary;
|
||||
}
|
||||
|
||||
interface KeyValProps {
|
||||
k: string;
|
||||
v: number | string;
|
||||
}
|
||||
|
||||
function Row({ k, v }: KeyValProps) {
|
||||
return <p>
|
||||
<b>{t(k)}: </b>
|
||||
<span>{v}</span>
|
||||
</p>;
|
||||
|
||||
}
|
||||
|
||||
export class QosPanel extends React.Component<Props, {}> {
|
||||
get pingState(): PingDictionary {
|
||||
return this.props.pings;
|
||||
}
|
||||
|
||||
get latencyReport() {
|
||||
return calculateLatency(this.pingState);
|
||||
}
|
||||
|
||||
get qualityReport() {
|
||||
return calculatePingLoss(this.pingState);
|
||||
}
|
||||
|
||||
render() {
|
||||
const r = { ...this.latencyReport, ...this.qualityReport };
|
||||
const errorRate = ((r.complete) / r.total);
|
||||
const percentage = (r.average).toFixed(1);
|
||||
|
||||
return <div className="fbos-info">
|
||||
<label>{t("Network Quality")}</label>
|
||||
<div className="chip-temp-display">
|
||||
<Row k="Sent" v={r.total} />
|
||||
<Row k="Received" v={r.complete} />
|
||||
<Row k="Lost" v={r.timeout} />
|
||||
<Row k="Pending" v={r.pending} />
|
||||
<Row k="Percent OK" v={(100 * errorRate).toFixed(1)} />
|
||||
<Row k="Best Time (ms)" v={r.best} />
|
||||
<Row k="Worst Time (ms)" v={r.worst} />
|
||||
<Row k="Average Time (ms)" v={percentage} />
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
import {
|
||||
calculateLatency,
|
||||
calculatePingLoss,
|
||||
PingDictionary,
|
||||
} from "./qos";
|
||||
import React from "react";
|
||||
|
||||
interface Props {
|
||||
pings: PingDictionary;
|
||||
}
|
||||
|
||||
export class QosRow extends React.Component<Props, {}> {
|
||||
get pingState(): PingDictionary { return this.props.pings; }
|
||||
|
||||
render() {
|
||||
const reportA = calculateLatency(this.pingState);
|
||||
const reportB = calculatePingLoss(this.pingState);
|
||||
const report = { ...reportA, ...reportB };
|
||||
const ber = ((report.complete || 0) / report.total) || 0;
|
||||
return <div>
|
||||
<br />
|
||||
<ul>
|
||||
<li>best: {report.best || 0}</li>
|
||||
<li>worst: {report.worst || 0}</li>
|
||||
<li>average: {(report.average || 0).toFixed(1)}</li>
|
||||
<li>Pings OK: {report.complete}</li>
|
||||
<li>Pings pending: {report.pending || 0}</li>
|
||||
<li>Pings failed: {report.timeout || 0}</li>
|
||||
<li>Total pings: {report.total || 0}</li>
|
||||
<li>Percent OK: {(100 * ber).toFixed(1)}</li>
|
||||
</ul>
|
||||
</div>;
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ import { NavBarProps } from "../interfaces";
|
|||
import { fakeDevice } from "../../__test_support__/resource_index_builder";
|
||||
import { maybeSetTimezone } from "../../devices/timezones/guess_timezone";
|
||||
import { fakeTimeSettings } from "../../__test_support__/fake_time_settings";
|
||||
import { fakePings } from "../../__test_support__/fake_state/pings";
|
||||
|
||||
describe("NavBar", () => {
|
||||
const fakeProps = (): NavBarProps => ({
|
||||
|
@ -25,6 +26,7 @@ describe("NavBar", () => {
|
|||
device: fakeDevice(),
|
||||
autoSync: false,
|
||||
alertCount: 0,
|
||||
pings: fakePings()
|
||||
});
|
||||
|
||||
it("has correct parent classname", () => {
|
||||
|
|
|
@ -129,7 +129,8 @@ export class NavBar extends React.Component<NavBarProps, Partial<NavBarState>> {
|
|||
<Connectivity
|
||||
bot={this.props.bot}
|
||||
rowData={this.connectivityData.rowData}
|
||||
flags={this.connectivityData.flags} />
|
||||
flags={this.connectivityData.flags}
|
||||
pings={this.props.pings} />
|
||||
</Popover>
|
||||
</div>
|
||||
<RunTour currentTour={this.props.tour} />
|
||||
|
|
|
@ -2,6 +2,7 @@ import { BotState } from "../devices/interfaces";
|
|||
import { TaggedUser, TaggedLog, TaggedDevice } from "farmbot";
|
||||
import { GetWebAppConfigValue } from "../config_storage/actions";
|
||||
import { TimeSettings } from "../interfaces";
|
||||
import { PingDictionary } from "../devices/connectivity/qos";
|
||||
|
||||
export interface SyncButtonProps {
|
||||
dispatch: Function;
|
||||
|
@ -23,6 +24,7 @@ export interface NavBarProps {
|
|||
device: TaggedDevice;
|
||||
autoSync: boolean;
|
||||
alertCount: number;
|
||||
pings: PingDictionary;
|
||||
}
|
||||
|
||||
export interface NavBarState {
|
||||
|
|
Loading…
Reference in New Issue