Reducer is now tracking QoS stats. NEXT: Add to UI, write tests.
parent
9858fa343b
commit
6e9ff55a38
|
@ -31,6 +31,8 @@ import { dispatchNetworkUp, dispatchNetworkDown } from "../connectivity";
|
||||||
import { Session } from "../session";
|
import { Session } from "../session";
|
||||||
import { error } from "../toast/toast";
|
import { error } from "../toast/toast";
|
||||||
|
|
||||||
|
const ANY_NUMBER = expect.any(Number);
|
||||||
|
|
||||||
interface FakeProps {
|
interface FakeProps {
|
||||||
uuid: string;
|
uuid: string;
|
||||||
method: Method;
|
method: Method;
|
||||||
|
@ -65,7 +67,7 @@ describe("responseRejected", () => {
|
||||||
it("undefined error", async () => {
|
it("undefined error", async () => {
|
||||||
await expect(responseRejected(undefined)).rejects.toEqual(undefined);
|
await expect(responseRejected(undefined)).rejects.toEqual(undefined);
|
||||||
expect(dispatchNetworkUp).not.toHaveBeenCalled();
|
expect(dispatchNetworkUp).not.toHaveBeenCalled();
|
||||||
expect(dispatchNetworkDown).toHaveBeenCalledWith("user.api");
|
expect(dispatchNetworkDown).toHaveBeenCalledWith("user.api", ANY_NUMBER);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("safe error", async () => {
|
it("safe error", async () => {
|
||||||
|
@ -75,7 +77,7 @@ describe("responseRejected", () => {
|
||||||
};
|
};
|
||||||
await expect(responseRejected(safeError)).rejects.toEqual(safeError);
|
await expect(responseRejected(safeError)).rejects.toEqual(safeError);
|
||||||
expect(dispatchNetworkDown).not.toHaveBeenCalled();
|
expect(dispatchNetworkDown).not.toHaveBeenCalled();
|
||||||
expect(dispatchNetworkUp).toHaveBeenCalledWith("user.api");
|
expect(dispatchNetworkUp).toHaveBeenCalledWith("user.api", ANY_NUMBER);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handles 500", async () => {
|
it("handles 500", async () => {
|
||||||
|
|
|
@ -42,6 +42,8 @@ import { MessageType } from "../../../sequences/interfaces";
|
||||||
import { FbjsEventName } from "farmbot/dist/constants";
|
import { FbjsEventName } from "farmbot/dist/constants";
|
||||||
import { info, error, success, warning, fun, busy } from "../../../toast/toast";
|
import { info, error, success, warning, fun, busy } from "../../../toast/toast";
|
||||||
|
|
||||||
|
const ANY_NUMBER = expect.any(Number);
|
||||||
|
|
||||||
describe("readStatus()", () => {
|
describe("readStatus()", () => {
|
||||||
it("forces a read_status request to FarmBot", () => {
|
it("forces a read_status request to FarmBot", () => {
|
||||||
readStatus();
|
readStatus();
|
||||||
|
@ -160,8 +162,8 @@ describe("initLog", () => {
|
||||||
describe("bothUp()", () => {
|
describe("bothUp()", () => {
|
||||||
it("marks MQTT and API as up", () => {
|
it("marks MQTT and API as up", () => {
|
||||||
bothUp();
|
bothUp();
|
||||||
expect(dispatchNetworkUp).toHaveBeenCalledWith("user.mqtt");
|
expect(dispatchNetworkUp).toHaveBeenCalledWith("user.mqtt", ANY_NUMBER);
|
||||||
expect(dispatchNetworkUp).toHaveBeenCalledWith("bot.mqtt");
|
expect(dispatchNetworkUp).toHaveBeenCalledWith("bot.mqtt", ANY_NUMBER);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -169,7 +171,7 @@ describe("onOffline", () => {
|
||||||
it("tells the app MQTT is down", () => {
|
it("tells the app MQTT is down", () => {
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
onOffline();
|
onOffline();
|
||||||
expect(dispatchNetworkDown).toHaveBeenCalledWith("user.mqtt");
|
expect(dispatchNetworkDown).toHaveBeenCalledWith("user.mqtt", ANY_NUMBER);
|
||||||
expect(error).toHaveBeenCalledWith(Content.MQTT_DISCONNECTED);
|
expect(error).toHaveBeenCalledWith(Content.MQTT_DISCONNECTED);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -178,7 +180,7 @@ describe("onOnline", () => {
|
||||||
it("tells the app MQTT is up", () => {
|
it("tells the app MQTT is up", () => {
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
onOnline();
|
onOnline();
|
||||||
expect(dispatchNetworkUp).toHaveBeenCalledWith("user.mqtt");
|
expect(dispatchNetworkUp).toHaveBeenCalledWith("user.mqtt", ANY_NUMBER);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -204,13 +206,14 @@ describe("onSent", () => {
|
||||||
it("marks MQTT as up", () => {
|
it("marks MQTT as up", () => {
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
onSent({ connected: true })();
|
onSent({ connected: true })();
|
||||||
expect(dispatchNetworkUp).toHaveBeenCalledWith("user.mqtt");
|
expect(dispatchNetworkUp).toHaveBeenCalledWith("user.mqtt", ANY_NUMBER);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("marks MQTT as down", () => {
|
it("marks MQTT as down", () => {
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
onSent({ connected: false })();
|
onSent({ connected: false })();
|
||||||
expect(dispatchNetworkDown).toHaveBeenCalledWith("user.mqtt");
|
expect(dispatchNetworkDown)
|
||||||
|
.toHaveBeenCalledWith("user.mqtt", ANY_NUMBER);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -234,7 +237,8 @@ describe("onLogs", () => {
|
||||||
log.message = "bot xyz is offline";
|
log.message = "bot xyz is offline";
|
||||||
fn(log);
|
fn(log);
|
||||||
globalQueue.maybeWork();
|
globalQueue.maybeWork();
|
||||||
expect(dispatchNetworkDown).toHaveBeenCalledWith("bot.mqtt");
|
expect(dispatchNetworkDown)
|
||||||
|
.toHaveBeenCalledWith("bot.mqtt", ANY_NUMBER);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handles log fields correctly", () => {
|
it("handles log fields correctly", () => {
|
||||||
|
|
|
@ -3,7 +3,7 @@ jest.mock("../index", () => ({
|
||||||
dispatchNetworkUp: jest.fn(),
|
dispatchNetworkUp: jest.fn(),
|
||||||
dispatchQosStart: jest.fn()
|
dispatchQosStart: jest.fn()
|
||||||
}));
|
}));
|
||||||
|
const ANY_NUMBER = expect.any(Number);
|
||||||
const mockTimestamp = 0;
|
const mockTimestamp = 0;
|
||||||
jest.mock("../../util", () => ({ timestamp: () => mockTimestamp }));
|
jest.mock("../../util", () => ({ timestamp: () => mockTimestamp }));
|
||||||
|
|
||||||
|
@ -50,14 +50,14 @@ function fakeBot(): Farmbot {
|
||||||
|
|
||||||
function expectStale() {
|
function expectStale() {
|
||||||
expect(dispatchNetworkDown)
|
expect(dispatchNetworkDown)
|
||||||
.toHaveBeenCalledWith("bot.mqtt");
|
.toHaveBeenCalledWith("bot.mqtt", ANY_NUMBER, "TESTS");
|
||||||
}
|
}
|
||||||
|
|
||||||
function expectActive() {
|
function expectActive() {
|
||||||
expect(dispatchNetworkUp)
|
expect(dispatchNetworkUp)
|
||||||
.toHaveBeenCalledWith("bot.mqtt");
|
.toHaveBeenCalledWith("bot.mqtt", ANY_NUMBER, "TESTS");
|
||||||
expect(dispatchNetworkUp)
|
expect(dispatchNetworkUp)
|
||||||
.toHaveBeenCalledWith("user.mqtt");
|
.toHaveBeenCalledWith("user.mqtt", ANY_NUMBER, "TESTS");
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("ping util", () => {
|
describe("ping util", () => {
|
||||||
|
|
|
@ -28,6 +28,7 @@ import { ChannelName, MessageType } from "../sequences/interfaces";
|
||||||
import { DeepPartial } from "redux";
|
import { DeepPartial } from "redux";
|
||||||
import { slowDown } from "./slow_down";
|
import { slowDown } from "./slow_down";
|
||||||
import { t } from "../i18next_wrapper";
|
import { t } from "../i18next_wrapper";
|
||||||
|
import { now } from "../devices/connectivity/qos";
|
||||||
|
|
||||||
export const TITLE = () => t("New message from bot");
|
export const TITLE = () => t("New message from bot");
|
||||||
/** TODO: This ought to be stored in Redux. It is here because of historical
|
/** TODO: This ought to be stored in Redux. It is here because of historical
|
||||||
|
@ -96,8 +97,8 @@ export const batchInitResources =
|
||||||
};
|
};
|
||||||
|
|
||||||
export const bothUp = () => {
|
export const bothUp = () => {
|
||||||
dispatchNetworkUp("user.mqtt");
|
dispatchNetworkUp("user.mqtt", now());
|
||||||
dispatchNetworkUp("bot.mqtt");
|
dispatchNetworkUp("bot.mqtt", now());
|
||||||
};
|
};
|
||||||
|
|
||||||
export function readStatus() {
|
export function readStatus() {
|
||||||
|
@ -108,7 +109,7 @@ export function readStatus() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const onOffline = () => {
|
export const onOffline = () => {
|
||||||
dispatchNetworkDown("user.mqtt");
|
dispatchNetworkDown("user.mqtt", now());
|
||||||
error(t(Content.MQTT_DISCONNECTED));
|
error(t(Content.MQTT_DISCONNECTED));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -151,7 +152,7 @@ type Client = { connected?: boolean };
|
||||||
export const onSent = (client: Client) => () => {
|
export const onSent = (client: Client) => () => {
|
||||||
const connected = !!client.connected;
|
const connected = !!client.connected;
|
||||||
const cb = connected ? dispatchNetworkUp : dispatchNetworkDown;
|
const cb = connected ? dispatchNetworkUp : dispatchNetworkDown;
|
||||||
cb("user.mqtt");
|
cb("user.mqtt", now());
|
||||||
};
|
};
|
||||||
|
|
||||||
export function onMalformed() {
|
export function onMalformed() {
|
||||||
|
@ -165,7 +166,7 @@ export function onMalformed() {
|
||||||
export const onOnline =
|
export const onOnline =
|
||||||
() => {
|
() => {
|
||||||
success(t("Reconnected to the message broker."), t("Online"));
|
success(t("Reconnected to the message broker."), t("Online"));
|
||||||
dispatchNetworkUp("user.mqtt");
|
dispatchNetworkUp("user.mqtt", now());
|
||||||
};
|
};
|
||||||
export const onReconnect =
|
export const onReconnect =
|
||||||
() => warning(t("Attempting to reconnect to the message broker"),
|
() => warning(t("Attempting to reconnect to the message broker"),
|
||||||
|
|
|
@ -32,16 +32,16 @@ export const dispatchQosStart = (id: string) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export let dispatchNetworkUp = (edge: Edge, at = (new Date()).getTime()) => {
|
export let dispatchNetworkUp = (edge: Edge, at: number, qosPingId?: string) => {
|
||||||
console.log("TODO: Insert ID HERE");
|
console.log("TODO: Insert ID HERE");
|
||||||
if (shouldThrottle(edge, at)) { return; }
|
if (shouldThrottle(edge, at)) { return; }
|
||||||
store.dispatch(networkUp(edge, at));
|
store.dispatch(networkUp(edge, at, qosPingId));
|
||||||
bumpThrottle(edge, at);
|
bumpThrottle(edge, at);
|
||||||
};
|
};
|
||||||
|
|
||||||
export let dispatchNetworkDown = (edge: Edge, at = (new Date()).getTime()) => {
|
export let dispatchNetworkDown = (edge: Edge, at: number, qosPingId?: string) => {
|
||||||
console.log("TODO: Insert ID HERE");
|
console.log("TODO: Insert ID HERE");
|
||||||
if (shouldThrottle(edge, at)) { return; }
|
if (shouldThrottle(edge, at)) { return; }
|
||||||
store.dispatch(networkDown(edge, at));
|
store.dispatch(networkDown(edge, at, qosPingId));
|
||||||
bumpThrottle(edge, at);
|
bumpThrottle(edge, at);
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { Log } from "farmbot/dist/resources/api_resources";
|
||||||
import { globalQueue } from "./batch_queue";
|
import { globalQueue } from "./batch_queue";
|
||||||
import { isUndefined, get } from "lodash";
|
import { isUndefined, get } from "lodash";
|
||||||
import { MessageType } from "../sequences/interfaces";
|
import { MessageType } from "../sequences/interfaces";
|
||||||
|
import { now } from "../devices/connectivity/qos";
|
||||||
|
|
||||||
const LEGACY_META_KEY_NAMES: (keyof Log)[] = [
|
const LEGACY_META_KEY_NAMES: (keyof Log)[] = [
|
||||||
"type",
|
"type",
|
||||||
|
@ -49,6 +50,6 @@ export const onLogs =
|
||||||
// TODO: Make a `bot/device_123/offline` channel.
|
// TODO: Make a `bot/device_123/offline` channel.
|
||||||
const died =
|
const died =
|
||||||
msg.message.includes("is offline") && msg.type === MessageType.error;
|
msg.message.includes("is offline") && msg.type === MessageType.error;
|
||||||
died && dispatchNetworkDown("bot.mqtt");
|
died && dispatchNetworkDown("bot.mqtt", now());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { isNumber } from "lodash";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { API } from "../api/index";
|
import { API } from "../api/index";
|
||||||
import { FarmBotInternalConfig } from "farmbot/dist/config";
|
import { FarmBotInternalConfig } from "farmbot/dist/config";
|
||||||
|
import { now } from "../devices/connectivity/qos";
|
||||||
|
|
||||||
export const PING_INTERVAL = 4000;
|
export const PING_INTERVAL = 4000;
|
||||||
export const ACTIVE_THRESHOLD = PING_INTERVAL * 2;
|
export const ACTIVE_THRESHOLD = PING_INTERVAL * 2;
|
||||||
|
@ -21,19 +22,17 @@ export function readPing(bot: Farmbot, direction: Direction): number | undefined
|
||||||
return isNumber(val) ? val : undefined;
|
return isNumber(val) ? val : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function markStale(_uuid: string) {
|
export function markStale(qosPingId: string) {
|
||||||
// dispatch({ pings: failPing(this.pingState, id) })
|
dispatchNetworkDown("bot.mqtt", now(), qosPingId);
|
||||||
dispatchNetworkDown("bot.mqtt");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function markActive(_uuid: string) {
|
export function markActive(qosPingId: string) {
|
||||||
// dispatch({ pings: completePing(this.pingState, id) })
|
dispatchNetworkUp("user.mqtt", now(), qosPingId);
|
||||||
dispatchNetworkUp("user.mqtt");
|
dispatchNetworkUp("bot.mqtt", now(), qosPingId);
|
||||||
dispatchNetworkUp("bot.mqtt");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isInactive(last: number, now: number): boolean {
|
export function isInactive(last: number, now_: number): boolean {
|
||||||
return last ? (now - last) > ACTIVE_THRESHOLD : true;
|
return last ? (now_ - last) > ACTIVE_THRESHOLD : true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sendOutboundPing(bot: Farmbot) {
|
export function sendOutboundPing(bot: Farmbot) {
|
||||||
|
@ -51,7 +50,7 @@ export function startPinging(bot: Farmbot) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pingAPI() {
|
export function pingAPI() {
|
||||||
const ok = () => dispatchNetworkUp("user.api");
|
const ok = () => dispatchNetworkUp("user.api", now());
|
||||||
const no = () => dispatchNetworkDown("user.api");
|
const no = () => dispatchNetworkDown("user.api", now());
|
||||||
return axios.get(API.current.devicePath).then(ok, no);
|
return axios.get(API.current.devicePath).then(ok, no);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,10 +30,8 @@ export let connectivityReducer =
|
||||||
const { qosPingId, status } = payload;
|
const { qosPingId, status } = payload;
|
||||||
if (qosPingId) {
|
if (qosPingId) {
|
||||||
if (status.state == "up") {
|
if (status.state == "up") {
|
||||||
console.log("OK!!!");
|
|
||||||
s.pings = completePing(s.pings, qosPingId, status.at);
|
s.pings = completePing(s.pings, qosPingId, status.at);
|
||||||
} else {
|
} else {
|
||||||
console.log("FAILED");
|
|
||||||
s.pings = failPing(s.pings, qosPingId);
|
s.pings = failPing(s.pings, qosPingId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ interface Complete {
|
||||||
export type Ping = Complete | Pending | Timeout;
|
export type Ping = Complete | Pending | Timeout;
|
||||||
export type PingDictionary = Record<string, Ping | undefined>;
|
export type PingDictionary = Record<string, Ping | undefined>;
|
||||||
|
|
||||||
const now = () => (new Date()).getTime();
|
export const now = () => (new Date()).getTime();
|
||||||
|
|
||||||
export const startPing =
|
export const startPing =
|
||||||
(s: PingDictionary, id: string, start = now()): PingDictionary => {
|
(s: PingDictionary, id: string, start = now()): PingDictionary => {
|
||||||
|
|
|
@ -10,9 +10,10 @@ import { Session } from "./session";
|
||||||
import { get } from "lodash";
|
import { get } from "lodash";
|
||||||
import { t } from "./i18next_wrapper";
|
import { t } from "./i18next_wrapper";
|
||||||
import { error } from "./toast/toast";
|
import { error } from "./toast/toast";
|
||||||
|
import { now } from "./devices/connectivity/qos";
|
||||||
|
|
||||||
export function responseFulfilled(input: AxiosResponse): AxiosResponse {
|
export function responseFulfilled(input: AxiosResponse): AxiosResponse {
|
||||||
dispatchNetworkUp("user.api");
|
dispatchNetworkUp("user.api", now());
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ export const isLocalRequest = (x: SafeError) =>
|
||||||
let ONLY_ONCE = true;
|
let ONLY_ONCE = true;
|
||||||
export function responseRejected(x: SafeError | undefined) {
|
export function responseRejected(x: SafeError | undefined) {
|
||||||
if (x && isSafeError(x)) {
|
if (x && isSafeError(x)) {
|
||||||
dispatchNetworkUp("user.api");
|
dispatchNetworkUp("user.api", now());
|
||||||
const a = ![451, 401, 422].includes(x.response.status);
|
const a = ![451, 401, 422].includes(x.response.status);
|
||||||
const b = x.response.status > 399;
|
const b = x.response.status > 399;
|
||||||
// Openfarm API was sending too many 404's.
|
// Openfarm API was sending too many 404's.
|
||||||
|
@ -57,7 +58,7 @@ export function responseRejected(x: SafeError | undefined) {
|
||||||
}
|
}
|
||||||
return Promise.reject(x);
|
return Promise.reject(x);
|
||||||
} else {
|
} else {
|
||||||
dispatchNetworkDown("user.api");
|
dispatchNetworkDown("user.api", now());
|
||||||
return Promise.reject(x);
|
return Promise.reject(x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue