refactor and document logs
parent
e138e9f28d
commit
490befc4b9
|
@ -7,7 +7,8 @@ import {
|
|||
TaggedWebAppConfig,
|
||||
TaggedSensor,
|
||||
TaggedFirmwareConfig,
|
||||
TaggedPinBinding
|
||||
TaggedPinBinding,
|
||||
TaggedLog
|
||||
} from "../../resources/tagged_resources";
|
||||
import { ExecutableType } from "../../farm_designer/interfaces";
|
||||
import { fakeResource } from "../fake_resource";
|
||||
|
@ -54,6 +55,22 @@ export function fakeFarmEvent(exe_type: ExecutableType,
|
|||
});
|
||||
}
|
||||
|
||||
export function fakeLog(): TaggedLog {
|
||||
return fakeResource("Log", {
|
||||
id: idCounter++,
|
||||
message: "Farmbot is up and Running!",
|
||||
type: "info",
|
||||
x: 1,
|
||||
y: 2,
|
||||
z: 3,
|
||||
verbosity: 1,
|
||||
major_version: 5,
|
||||
minor_version: 1,
|
||||
channels: ["toast"],
|
||||
created_at: 1501703421
|
||||
});
|
||||
}
|
||||
|
||||
export function fakeImage(): TaggedImage {
|
||||
return fakeResource("Image", {
|
||||
id: idCounter++,
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
import { Log } from "../interfaces";
|
||||
|
||||
export let log: Log = {
|
||||
id: 1234567,
|
||||
message: "Farmbot is up and Running!",
|
||||
type: "info",
|
||||
channels: [
|
||||
"toast"
|
||||
],
|
||||
created_at: 1501703421
|
||||
};
|
|
@ -5,12 +5,13 @@ import * as _ from "lodash";
|
|||
import { init, error } from "farmbot-toastr";
|
||||
|
||||
import { NavBar } from "./nav";
|
||||
import { Everything, Log } from "./interfaces";
|
||||
import { Everything } from "./interfaces";
|
||||
import { LoadingPlant } from "./loading_plant";
|
||||
import { BotState, Xyz } from "./devices/interfaces";
|
||||
import { ResourceName, TaggedUser } from "./resources/tagged_resources";
|
||||
import {
|
||||
selectAllLogs,
|
||||
ResourceName, TaggedUser, TaggedLog
|
||||
} from "./resources/tagged_resources";
|
||||
import {
|
||||
maybeFetchUser,
|
||||
maybeGetTimeOffset,
|
||||
getFirmwareConfig
|
||||
|
@ -24,6 +25,7 @@ import { BooleanSetting } from "./session_keys";
|
|||
import { getPathArray } from "./history";
|
||||
import { FirmwareConfig } from "./config_storage/firmware_configs";
|
||||
import { getWebAppConfigValue } from "./config_storage/actions";
|
||||
import { takeSortedLogs } from "./logs/state_to_props";
|
||||
|
||||
/** Remove 300ms delay on touch devices - https://github.com/ftlabs/fastclick */
|
||||
const fastClick = require("fastclick");
|
||||
|
@ -35,7 +37,7 @@ init();
|
|||
export interface AppProps {
|
||||
dispatch: Function;
|
||||
loaded: ResourceName[];
|
||||
logs: Log[];
|
||||
logs: TaggedLog[];
|
||||
user: TaggedUser | undefined;
|
||||
bot: BotState;
|
||||
consistent: boolean;
|
||||
|
@ -51,12 +53,7 @@ function mapStateToProps(props: Everything): AppProps {
|
|||
dispatch: props.dispatch,
|
||||
user: maybeFetchUser(props.resources.index),
|
||||
bot: props.bot,
|
||||
logs: _(selectAllLogs(props.resources.index))
|
||||
.map(x => x.body)
|
||||
.sortBy("created_at")
|
||||
.reverse()
|
||||
.take(250)
|
||||
.value(),
|
||||
logs: takeSortedLogs(250, props.resources.index),
|
||||
loaded: props.resources.loaded,
|
||||
consistent: !!(props.bot || {}).consistent,
|
||||
axisInversion: {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { fetchNewDevice, getDevice } from "../device";
|
||||
import { dispatchNetworkUp, dispatchNetworkDown } from "./index";
|
||||
import { Log } from "../interfaces";
|
||||
import { ALLOWED_CHANNEL_NAMES, Farmbot, BotStateTree } from "farmbot";
|
||||
import { Farmbot, BotStateTree } from "farmbot";
|
||||
import { noop, throttle } from "lodash";
|
||||
import { success, error, info, warning } from "farmbot-toastr";
|
||||
import { HardwareState } from "../devices/interfaces";
|
||||
|
@ -25,6 +25,7 @@ import { getWebAppConfigValue } from "../config_storage/actions";
|
|||
import { BooleanSetting } from "../session_keys";
|
||||
import { versionOK } from "../util";
|
||||
import { onLogs } from "./log_handlers";
|
||||
import { ChannelName } from "../sequences/interfaces";
|
||||
|
||||
export const TITLE = "New message from bot";
|
||||
/** TODO: This ought to be stored in Redux. It is here because of historical
|
||||
|
@ -42,9 +43,9 @@ export let incomingStatus = (statusMessage: HardwareState) =>
|
|||
/** Determine if an incoming log has a certain channel. If it is, execute the
|
||||
* supplied callback. */
|
||||
export function actOnChannelName(
|
||||
log: Log, channelName: ALLOWED_CHANNEL_NAMES, cb: (log: Log) => void) {
|
||||
log: Log, channelName: ChannelName, cb: (log: Log) => void) {
|
||||
const CHANNELS: keyof Log = "channels";
|
||||
const chanList: string[] = log[CHANNELS] || ["ERROR FETCHING CHANNELS"];
|
||||
const chanList: ChannelName[] = log[CHANNELS] || ["ERROR FETCHING CHANNELS"];
|
||||
return log && (chanList.includes(channelName) ? cb(log) : noop());
|
||||
}
|
||||
|
||||
|
@ -96,7 +97,7 @@ export function readStatus() {
|
|||
|
||||
export const onOffline = () => {
|
||||
dispatchNetworkDown("user.mqtt");
|
||||
error(t(Content.MQTT_DISCONNECTED),t("Error"));
|
||||
error(t(Content.MQTT_DISCONNECTED), t("Error"));
|
||||
};
|
||||
|
||||
export const changeLastClientConnected = (bot: Farmbot) => () => {
|
||||
|
|
|
@ -5,6 +5,7 @@ import { Color as FarmBotJsColor, ALLOWED_MESSAGE_TYPES, PlantStage } from "farm
|
|||
import { DraggableState } from "./draggable/interfaces";
|
||||
import { PeripheralState } from "./controls/peripherals/interfaces";
|
||||
import { RestResources } from "./resources/interfaces";
|
||||
import { ChannelName } from "./sequences/interfaces";
|
||||
|
||||
/** Regimens and sequences may have a "color" which determines how it looks
|
||||
in the UI. Only certain colors are valid. */
|
||||
|
@ -47,7 +48,7 @@ export interface Log {
|
|||
verbosity?: number;
|
||||
major_version?: number;
|
||||
minor_version?: number;
|
||||
channels: string[];
|
||||
channels: ChannelName[];
|
||||
created_at: number;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,37 +28,20 @@ import * as React from "react";
|
|||
import { mount } from "enzyme";
|
||||
import { Logs } from "../index";
|
||||
import { ToolTips } from "../../constants";
|
||||
import { TaggedLog, SpecialStatus } from "../../resources/tagged_resources";
|
||||
import { Log } from "../../interfaces";
|
||||
import { generateUuid } from "../../resources/util";
|
||||
import { TaggedLog } from "../../resources/tagged_resources";
|
||||
import { bot } from "../../__test_support__/fake_state/bot";
|
||||
import { Dictionary } from "farmbot";
|
||||
import { NumericSetting } from "../../session_keys";
|
||||
import { fakeLog } from "../../__test_support__/fake_state/resources";
|
||||
|
||||
describe("<Logs />", () => {
|
||||
function fakeLogs(): TaggedLog[] {
|
||||
const logs: Log[] = [{
|
||||
id: 1,
|
||||
created_at: -1,
|
||||
message: "Fake log message 1",
|
||||
type: "info",
|
||||
channels: []
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
created_at: -1,
|
||||
message: "Fake log message 2",
|
||||
type: "success",
|
||||
channels: []
|
||||
}];
|
||||
return logs.map((body: Log): TaggedLog => {
|
||||
return {
|
||||
kind: "Log",
|
||||
uuid: generateUuid(body.id, "Log"),
|
||||
specialStatus: SpecialStatus.SAVED,
|
||||
body
|
||||
};
|
||||
});
|
||||
const log1 = fakeLog();
|
||||
log1.body.message = "Fake log message 1";
|
||||
const log2 = fakeLog();
|
||||
log2.body.message = "Fake log message 2";
|
||||
log2.body.type = "success";
|
||||
return [log1, log2];
|
||||
}
|
||||
|
||||
const fakeProps = () => {
|
||||
|
@ -94,6 +77,8 @@ describe("<Logs />", () => {
|
|||
it("shows position", () => {
|
||||
const p = fakeProps();
|
||||
p.logs[0].body.x = 100;
|
||||
p.logs[0].body.y = undefined;
|
||||
p.logs[0].body.z = undefined;
|
||||
p.logs[1].body.x = 0;
|
||||
p.logs[1].body.y = 1;
|
||||
p.logs[1].body.z = 2;
|
||||
|
|
|
@ -1,29 +1,13 @@
|
|||
import { mapStateToProps } from "../state_to_props";
|
||||
import { fakeState } from "../../__test_support__/fake_state";
|
||||
import { buildResourceIndex } from "../../__test_support__/resource_index_builder";
|
||||
import { TaggedLog, SpecialStatus } from "../../resources/tagged_resources";
|
||||
import { Log } from "../../interfaces";
|
||||
import { generateUuid } from "../../resources/util";
|
||||
import { TaggedLog } from "../../resources/tagged_resources";
|
||||
import { times } from "lodash";
|
||||
import { fakeFbosConfig } from "../../__test_support__/fake_state/resources";
|
||||
import { fakeFbosConfig, fakeLog } from "../../__test_support__/fake_state/resources";
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
function fakeLogs(count: number): TaggedLog[] {
|
||||
const log: Log = {
|
||||
id: 1,
|
||||
created_at: -1,
|
||||
message: "Fake log message",
|
||||
type: "info",
|
||||
channels: []
|
||||
};
|
||||
return times(count, () => log).map((body: Log): TaggedLog => {
|
||||
return {
|
||||
kind: "Log",
|
||||
uuid: generateUuid(body.id, "Log"),
|
||||
specialStatus: SpecialStatus.SAVED,
|
||||
body
|
||||
};
|
||||
});
|
||||
return times(count, fakeLog);
|
||||
}
|
||||
|
||||
it("returns limited number of logs", () => {
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import * as React from "react";
|
||||
import { LogsFilterMenuProps, LogsState } from "../interfaces";
|
||||
import { LogsFilterMenuProps } from "../interfaces";
|
||||
import * as _ from "lodash";
|
||||
import { Slider } from "@blueprintjs/core";
|
||||
import { t } from "i18next";
|
||||
import { Filters } from "../interfaces";
|
||||
|
||||
export const LogsFilterMenu = (props: LogsFilterMenuProps) => {
|
||||
const btnColor = (x: keyof LogsState) => props.state[x] != 0
|
||||
/** Filter level 0: logs hidden. */
|
||||
const btnColor = (x: keyof Filters) => props.state[x] != 0
|
||||
? "green" : "red";
|
||||
/** Set the filter level to the same value for all log message types. */
|
||||
const setAll = (level: number) => () => {
|
||||
["success", "busy", "warn", "error", "info", "fun", "debug"]
|
||||
.map((x: keyof Filters) => props.setFilterLevel(x)(level));
|
||||
|
@ -26,7 +28,7 @@ export const LogsFilterMenu = (props: LogsFilterMenuProps) => {
|
|||
</fieldset>
|
||||
{Object.keys(props.state)
|
||||
.filter(x => { if (!(x == "autoscroll")) { return x; } })
|
||||
.map((logType: keyof LogsState) => {
|
||||
.map((logType: keyof Filters) => {
|
||||
return <fieldset key={logType}>
|
||||
<label>
|
||||
<div className={`saucer ${logType}`} />
|
||||
|
|
|
@ -1,34 +1,36 @@
|
|||
import * as React from "react";
|
||||
import { t } from "i18next";
|
||||
import { TaggedLog } from "../../resources/tagged_resources";
|
||||
import { LogsState, LogsTableProps } from "../interfaces";
|
||||
import { LogsState, LogsTableProps, Filters } from "../interfaces";
|
||||
import { formatLogTime } from "../index";
|
||||
import * as _ from "lodash";
|
||||
import { Classes } from "@blueprintjs/core";
|
||||
import { isNumber, startCase } from "lodash";
|
||||
|
||||
interface LogsRowProps {
|
||||
tlog: TaggedLog;
|
||||
state: LogsState;
|
||||
timeOffset: number;
|
||||
}
|
||||
|
||||
/** A log is displayed in a single row of the logs table. */
|
||||
const LogsRow = ({ tlog, timeOffset }: LogsRowProps) => {
|
||||
const log = tlog.body;
|
||||
const { x, y, z, verbosity, type } = log;
|
||||
const time = formatLogTime(log.created_at, timeOffset);
|
||||
return <tr key={tlog.uuid}>
|
||||
const { uuid } = tlog;
|
||||
const { x, y, z, verbosity, type, created_at, message } = tlog.body;
|
||||
const time = formatLogTime(created_at, timeOffset);
|
||||
return <tr key={uuid}>
|
||||
<td>
|
||||
<div className={`saucer ${type}`}>
|
||||
<p>
|
||||
{verbosity}
|
||||
</p>
|
||||
</div>
|
||||
{_.startCase(type)}
|
||||
{startCase(type)}
|
||||
</td>
|
||||
<td>
|
||||
{log.message || "Loading"}
|
||||
{message || "Loading"}
|
||||
</td>
|
||||
<td>
|
||||
{
|
||||
(_.isNumber(x) && _.isNumber(y) && _.isNumber(z))
|
||||
(isNumber(x) && isNumber(y) && isNumber(z))
|
||||
? `${x}, ${y}, ${z}`
|
||||
: "Unknown"
|
||||
}
|
||||
|
@ -38,12 +40,14 @@ const LogsRow = ({ tlog, timeOffset }: LogsRowProps) => {
|
|||
</td>
|
||||
</tr>;
|
||||
};
|
||||
|
||||
const LOG_TABLE_CLASS = [
|
||||
Classes.HTML_TABLE,
|
||||
Classes.HTML_TABLE_STRIPED,
|
||||
"logs-table"
|
||||
].join(" ");
|
||||
|
||||
/** All log messages with select data in table form for display in the app. */
|
||||
export const LogsTable = (props: LogsTableProps) => {
|
||||
return <table className={LOG_TABLE_CLASS}>
|
||||
<thead>
|
||||
|
@ -55,26 +59,34 @@ export const LogsTable = (props: LogsTableProps) => {
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filterByVerbosity(props.state, props.logs)
|
||||
{filterByVerbosity(getFilterLevel(props.state), props.logs)
|
||||
.map((log: TaggedLog) => {
|
||||
return <LogsRow
|
||||
key={log.uuid}
|
||||
tlog={log}
|
||||
state={props.state}
|
||||
timeOffset={props.timeOffset} />;
|
||||
})}
|
||||
</tbody>
|
||||
</table>;
|
||||
};
|
||||
|
||||
const filterByVerbosity = (state: LogsState, logs: TaggedLog[]) => {
|
||||
return logs
|
||||
.filter((log: TaggedLog) => {
|
||||
const { type, verbosity } = log.body;
|
||||
const filterLevel = state[type as keyof LogsState];
|
||||
const displayLog = verbosity
|
||||
? verbosity <= filterLevel
|
||||
: filterLevel != 0;
|
||||
return displayLog;
|
||||
});
|
||||
};
|
||||
/** Get current verbosity filter level for a message type from LogsState. */
|
||||
const getFilterLevel = (state: LogsState) =>
|
||||
(type: keyof Filters): number => {
|
||||
const filterLevel = state[type as keyof Filters];
|
||||
return isNumber(filterLevel) ? filterLevel : 1;
|
||||
};
|
||||
|
||||
/** Filter TaggedLogs by verbosity level using a fetch filter level function. */
|
||||
export const filterByVerbosity =
|
||||
(getLevelFor: (type: keyof Filters) => number, logs: TaggedLog[]) => {
|
||||
return logs
|
||||
.filter((log: TaggedLog) => {
|
||||
const { type, verbosity } = log.body;
|
||||
const filterLevel = getLevelFor(type);
|
||||
const displayLog = verbosity
|
||||
? verbosity <= filterLevel
|
||||
: filterLevel != 0;
|
||||
return displayLog;
|
||||
});
|
||||
};
|
||||
|
|
|
@ -4,9 +4,7 @@ import { Help } from "../../ui/index";
|
|||
import { ToolTips } from "../../constants";
|
||||
import { ToggleButton } from "../../controls/toggle_button";
|
||||
import { updateConfig } from "../../devices/actions";
|
||||
import {
|
||||
LogSettingProps, LogsSettingsMenuProps, LogsState
|
||||
} from "../interfaces";
|
||||
import { LogSettingProps, LogsSettingsMenuProps, Filters } from "../interfaces";
|
||||
import { Session, safeNumericSetting } from "../../session";
|
||||
import { ConfigurationName } from "farmbot";
|
||||
|
||||
|
@ -54,7 +52,8 @@ const FIRMWARE_LOG_SETTINGS = (): LogSettingRecord[] => [
|
|||
|
||||
const LogSetting = (props: LogSettingProps) => {
|
||||
const { label, setting, toolTip, setFilterLevel, sourceFbosConfig } = props;
|
||||
const updateMinFilterLevel = (name: keyof LogsState, level: number) => {
|
||||
/** Update the current filter level to a minimum needed for log display. */
|
||||
const updateMinFilterLevel = (name: keyof Filters, level: number) => {
|
||||
const currentLevel =
|
||||
Session.deprecatedGetNum(safeNumericSetting(name + "_log")) || 0;
|
||||
if (currentLevel < level) { setFilterLevel(name)(level); }
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Col, Row, Page, ToolTip } from "../ui/index";
|
|||
import { mapStateToProps } from "./state_to_props";
|
||||
import { t } from "i18next";
|
||||
import { Popover, Position } from "@blueprintjs/core";
|
||||
import { LogsState, LogsProps } from "./interfaces";
|
||||
import { LogsState, LogsProps, Filters } from "./interfaces";
|
||||
import { ToolTips } from "../constants";
|
||||
import { LogsSettingsMenu } from "./components/settings_menu";
|
||||
import { LogsFilterMenu } from "./components/filter_menu";
|
||||
|
@ -15,11 +15,14 @@ import { isUndefined } from "lodash";
|
|||
import { NumericSetting } from "../session_keys";
|
||||
import { NumberConfigKey } from "../config_storage/web_app_configs";
|
||||
|
||||
/** Format log date and time for display in the app. */
|
||||
export const formatLogTime = (created_at: number, timeoffset: number) =>
|
||||
moment.unix(created_at).utcOffset(timeoffset).format("MMM D, h:mma");
|
||||
|
||||
@connect(mapStateToProps)
|
||||
export class Logs extends React.Component<LogsProps, Partial<LogsState>> {
|
||||
|
||||
/** Initialize log type verbosity level to the configured or default value. */
|
||||
initialize = (name: NumberConfigKey, defaultValue: number): number => {
|
||||
const currentValue = Session.deprecatedGetNum(safeNumericSetting(name));
|
||||
if (isUndefined(currentValue)) {
|
||||
|
@ -41,33 +44,31 @@ export class Logs extends React.Component<LogsProps, Partial<LogsState>> {
|
|||
debug: this.initialize(NumericSetting.debug_log, 1),
|
||||
};
|
||||
|
||||
toggle = (name: keyof LogsState) => {
|
||||
switch (this.state[name]) {
|
||||
case 0:
|
||||
return () => {
|
||||
this.setState({ [name]: 1 });
|
||||
Session.deprecatedSetNum(safeNumericSetting(name + "_log"), 1);
|
||||
};
|
||||
default:
|
||||
return () => {
|
||||
this.setState({ [name]: 0 });
|
||||
Session.deprecatedSetNum(safeNumericSetting(name + "_log"), 0);
|
||||
};
|
||||
}
|
||||
/** Toggle display of a log type. Verbosity level 0 hides all, 3 shows all.*/
|
||||
toggle = (name: keyof Filters) => {
|
||||
// If log type is off, set it to verbosity level 1, otherwise turn it off
|
||||
const newSetting = this.state[name] === 0 ? 1 : 0;
|
||||
return () => {
|
||||
this.setState({ [name]: newSetting });
|
||||
Session.deprecatedSetNum(safeNumericSetting(name + "_log"), newSetting);
|
||||
};
|
||||
}
|
||||
|
||||
setFilterLevel = (name: keyof LogsState) => {
|
||||
/** Set log type filter level. i.e., level 2 shows verbosity 2 and lower.*/
|
||||
setFilterLevel = (name: keyof Filters) => {
|
||||
return (value: number) => {
|
||||
this.setState({ [name]: value });
|
||||
Session.deprecatedSetNum(safeNumericSetting(name + "_log"), value);
|
||||
};
|
||||
};
|
||||
|
||||
/** Determine if log type filters are active. */
|
||||
get filterActive() {
|
||||
const filterKeys = Object.keys(this.state)
|
||||
.filter(x => !(x === "autoscroll"));
|
||||
const filterValues = filterKeys
|
||||
.map((key: keyof LogsState) => this.state[key]);
|
||||
.map((key: keyof Filters) => this.state[key]);
|
||||
// Filters active if every log type level is not equal to 3 (max verbosity)
|
||||
return !filterValues.every(x => x == 3);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { TaggedLog } from "../resources/tagged_resources";
|
||||
import { BotState, SourceFbosConfig } from "../devices/interfaces";
|
||||
import { ConfigurationName } from "farmbot";
|
||||
import { ConfigurationName, ALLOWED_MESSAGE_TYPES } from "farmbot";
|
||||
|
||||
export interface LogsProps {
|
||||
logs: TaggedLog[];
|
||||
|
@ -10,15 +10,7 @@ export interface LogsProps {
|
|||
sourceFbosConfig: SourceFbosConfig;
|
||||
}
|
||||
|
||||
export interface Filters {
|
||||
success: number;
|
||||
busy: number;
|
||||
warn: number;
|
||||
error: number;
|
||||
info: number;
|
||||
fun: number;
|
||||
debug: number;
|
||||
}
|
||||
export type Filters = Record<ALLOWED_MESSAGE_TYPES, number>;
|
||||
|
||||
export interface LogsState extends Filters {
|
||||
autoscroll: boolean;
|
||||
|
|
|
@ -7,6 +7,18 @@ import {
|
|||
} from "../devices/components/source_config_value";
|
||||
import { getFbosConfig } from "../resources/selectors_by_kind";
|
||||
import { validFbosConfig } from "../util";
|
||||
import { ResourceIndex } from "../resources/interfaces";
|
||||
import { TaggedLog } from "../resources/tagged_resources";
|
||||
|
||||
/** Take the specified number of logs after sorting by time created. */
|
||||
export function takeSortedLogs(
|
||||
numberOfLogs: number, ri: ResourceIndex): TaggedLog[] {
|
||||
return _(selectAllLogs(ri))
|
||||
.sortBy("body.created_at")
|
||||
.reverse()
|
||||
.take(numberOfLogs)
|
||||
.value();
|
||||
}
|
||||
|
||||
export function mapStateToProps(props: Everything): LogsProps {
|
||||
const { hardware } = props.bot;
|
||||
|
@ -14,11 +26,7 @@ export function mapStateToProps(props: Everything): LogsProps {
|
|||
return {
|
||||
dispatch: props.dispatch,
|
||||
sourceFbosConfig: sourceFbosConfigValue(fbosConfig, hardware.configuration),
|
||||
logs: _(selectAllLogs(props.resources.index))
|
||||
.sortBy("body.created_at")
|
||||
.reverse()
|
||||
.take(250)
|
||||
.value(),
|
||||
logs: takeSortedLogs(250, props.resources.index),
|
||||
bot: props.bot,
|
||||
timeOffset: maybeGetTimeOffset(props.resources.index)
|
||||
};
|
||||
|
|
|
@ -3,7 +3,6 @@ import { shallow } from "enzyme";
|
|||
|
||||
import { NavBar } from "../index";
|
||||
import { bot } from "../../__test_support__/fake_state/bot";
|
||||
import { log } from "../../__test_support__/log";
|
||||
import { taggedUser } from "../../__test_support__/user";
|
||||
|
||||
describe("NavBar", () => {
|
||||
|
@ -13,7 +12,7 @@ describe("NavBar", () => {
|
|||
<NavBar
|
||||
timeOffset={0}
|
||||
consistent={true}
|
||||
logs={[log]}
|
||||
logs={[]}
|
||||
bot={bot}
|
||||
user={taggedUser}
|
||||
dispatch={jest.fn()} />
|
||||
|
@ -25,7 +24,7 @@ describe("NavBar", () => {
|
|||
const wrapper = shallow(<NavBar
|
||||
timeOffset={0}
|
||||
consistent={true}
|
||||
logs={[log]}
|
||||
logs={[]}
|
||||
bot={bot}
|
||||
user={taggedUser}
|
||||
dispatch={jest.fn()} />);
|
||||
|
|
|
@ -22,21 +22,14 @@ jest.mock("../../session", () => {
|
|||
|
||||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
|
||||
import { TickerList } from "../ticker_list";
|
||||
import { Log } from "../../interfaces";
|
||||
import { Dictionary } from "farmbot";
|
||||
import { fakeLog } from "../../__test_support__/fake_state/resources";
|
||||
|
||||
describe("<TickerList />", () => {
|
||||
const log: Log = {
|
||||
id: 1234567,
|
||||
message: "Farmbot is up and Running!",
|
||||
type: "info",
|
||||
channels: [
|
||||
"toast"
|
||||
],
|
||||
created_at: 1501703421
|
||||
};
|
||||
const log = fakeLog();
|
||||
log.body.message = "Farmbot is up and Running!";
|
||||
log.body.created_at = 1501703421;
|
||||
|
||||
it("shows log message and datetime", () => {
|
||||
const wrapper = mount(
|
||||
|
@ -85,7 +78,7 @@ describe("<TickerList />", () => {
|
|||
it("all logs filtered out", () => {
|
||||
["success", "busy", "warn", "error", "info", "fun", "debug"]
|
||||
.map(logType => mockStorj[logType + "_log"] = 0);
|
||||
log.verbosity = 1;
|
||||
log.body.verbosity = 1;
|
||||
const wrapper = mount(<TickerList
|
||||
logs={[log]} tickerListOpen={false} toggle={jest.fn()} timeOffset={0} />);
|
||||
const labels = wrapper.find("label");
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { BotState } from "../devices/interfaces";
|
||||
import { Log } from "../interfaces";
|
||||
import { TaggedUser } from "../resources/tagged_resources";
|
||||
import { TaggedUser, TaggedLog } from "../resources/tagged_resources";
|
||||
|
||||
export interface NavButtonProps {
|
||||
user: TaggedUser | undefined;
|
||||
|
@ -12,7 +11,7 @@ export interface NavButtonProps {
|
|||
|
||||
export interface NavBarProps {
|
||||
consistent: boolean;
|
||||
logs: Log[];
|
||||
logs: TaggedLog[];
|
||||
bot: BotState;
|
||||
user: TaggedUser | undefined;
|
||||
dispatch: Function;
|
||||
|
@ -34,7 +33,7 @@ export interface MobileMenuProps {
|
|||
|
||||
export interface TickerListProps {
|
||||
toggle: (property: keyof NavBarState) => ToggleEventHandler;
|
||||
logs: Log[]
|
||||
logs: TaggedLog[]
|
||||
tickerListOpen: boolean;
|
||||
timeOffset: number;
|
||||
}
|
||||
|
|
|
@ -1,61 +1,63 @@
|
|||
import * as React from "react";
|
||||
import { Collapse } from "@blueprintjs/core";
|
||||
import { Markdown } from "../ui/index";
|
||||
import { Log } from "../interfaces";
|
||||
import { TickerListProps } from "./interfaces";
|
||||
import { Link } from "react-router";
|
||||
import { t } from "i18next";
|
||||
import { formatLogTime } from "../logs/index";
|
||||
import { Session, safeNumericSetting } from "../session";
|
||||
import { isNumber } from "lodash";
|
||||
import { ErrorBoundary } from "../error_boundary";
|
||||
import { ALLOWED_MESSAGE_TYPES } from "farmbot";
|
||||
import { filterByVerbosity } from "../logs/components/logs_table";
|
||||
import { TaggedLog, SpecialStatus } from "../resources/tagged_resources";
|
||||
import { isNumber } from "lodash";
|
||||
|
||||
const logFilter = (log: Log): Log | undefined => {
|
||||
const { type, verbosity } = log;
|
||||
const filterLevel = Session.deprecatedGetNum(safeNumericSetting(type + "_log"));
|
||||
const filterLevelCompare = isNumber(filterLevel) ? filterLevel : 1;
|
||||
const displayLog = verbosity
|
||||
? verbosity <= filterLevelCompare
|
||||
: filterLevel != 0;
|
||||
const whitelisted = !log.message.toLowerCase().includes("filtered");
|
||||
if (displayLog && whitelisted) {
|
||||
return log;
|
||||
}
|
||||
return;
|
||||
/** Get current verbosity filter level for a message type from WebAppConfig. */
|
||||
const getFilterLevel = (type: ALLOWED_MESSAGE_TYPES): number => {
|
||||
const filterLevel =
|
||||
Session.deprecatedGetNum(safeNumericSetting(type + "_log"));
|
||||
return isNumber(filterLevel) ? filterLevel : 1;
|
||||
};
|
||||
|
||||
const getfirstTickerLog = (logs: Log[]): Log => {
|
||||
if (logs.length == 0) {
|
||||
return {
|
||||
message: t("No logs yet."),
|
||||
/** Generate a fallback TaggedLog to display in the first line of the ticker. */
|
||||
const generateFallbackLog = (uuid: string, message: string): TaggedLog => {
|
||||
return {
|
||||
kind: "Log",
|
||||
uuid,
|
||||
specialStatus: SpecialStatus.SAVED,
|
||||
body: {
|
||||
message,
|
||||
type: "debug",
|
||||
verbosity: -1,
|
||||
channels: [], created_at: NaN
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/** Choose the log to display in the first line of the ticker. */
|
||||
const getfirstTickerLog = (logs: TaggedLog[]): TaggedLog => {
|
||||
if (logs.length == 0) {
|
||||
return generateFallbackLog("no_logs_yet", t("No logs yet."));
|
||||
} else {
|
||||
const filteredLogs = logs.filter(log => logFilter(log));
|
||||
const filteredLogs = filterByVerbosity(getFilterLevel, logs);
|
||||
if (filteredLogs.length > 0) {
|
||||
return filteredLogs[0];
|
||||
} else {
|
||||
return {
|
||||
message: t("No logs to display. Visit Logs page to view filters."),
|
||||
type: "debug",
|
||||
verbosity: -1,
|
||||
channels: [], created_at: NaN
|
||||
};
|
||||
return generateFallbackLog("no_logs_to_display",
|
||||
t("No logs to display. Visit Logs page to view filters."));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Ticker = (log: Log, index: number, timeOffset: number) => {
|
||||
const time = formatLogTime(log.created_at, timeOffset);
|
||||
const { type } = log;
|
||||
// TODO: Should utilize log's `uuid` instead of index.
|
||||
return <div key={index} className="status-ticker-wrapper">
|
||||
/** Format a single log for display in the ticker. */
|
||||
const Ticker = (log: TaggedLog, timeOffset: number) => {
|
||||
const { message, type, created_at } = log.body;
|
||||
const time = formatLogTime(created_at, timeOffset);
|
||||
return <div key={log.uuid} className="status-ticker-wrapper">
|
||||
<div className={`saucer ${type}`} />
|
||||
<label className="status-ticker-message">
|
||||
<Markdown>
|
||||
{log.message.replace(/\s+/g, " ") || "Loading"}
|
||||
{message.replace(/\s+/g, " ") || "Loading"}
|
||||
</Markdown>
|
||||
</label>
|
||||
<label className="status-ticker-created-at">
|
||||
|
@ -64,18 +66,18 @@ const Ticker = (log: Log, index: number, timeOffset: number) => {
|
|||
</div>;
|
||||
};
|
||||
|
||||
/** The logs ticker, with closed/open views, and a link to the Logs page. */
|
||||
export let TickerList = (props: TickerListProps) => {
|
||||
return <ErrorBoundary>
|
||||
<div className="ticker-list" onClick={props.toggle("tickerListOpen")} >
|
||||
<div className="first-ticker">
|
||||
{Ticker(getfirstTickerLog(props.logs), -1, props.timeOffset)}
|
||||
{Ticker(getfirstTickerLog(props.logs), props.timeOffset)}
|
||||
</div>
|
||||
<Collapse isOpen={props.tickerListOpen}>
|
||||
{props
|
||||
.logs
|
||||
{filterByVerbosity(getFilterLevel, props.logs)
|
||||
// Don't use first log again since it's already displayed in first row
|
||||
.filter((_, index) => index !== 0)
|
||||
.filter((log) => logFilter(log))
|
||||
.map((log: Log, index: number) => Ticker(log, index, props.timeOffset))}
|
||||
.map((log: TaggedLog) => Ticker(log, props.timeOffset))}
|
||||
</Collapse>
|
||||
<Collapse isOpen={props.tickerListOpen}>
|
||||
<Link to={"/app/logs"}>
|
||||
|
|
Loading…
Reference in New Issue