FarmBot/Farmbot-Web-App

View on GitHub
frontend/logs/index.tsx

Summary

Maintainability
A
2 hrs
Test Coverage
import React from "react";
import { connect } from "react-redux";
import { Page, Popover } from "../ui";
import { Position } from "@blueprintjs/core";
import { LogsState, LogsProps, Filters, LogsPanelProps } from "./interfaces";
import { LogsSettingsMenu } from "./components/settings_menu";
import { filterStateKeys } from "./components/filter_menu";
import { LogsTable } from "./components/logs_table";
import { safeNumericSetting } from "../session";
import { isUndefined } from "lodash";
import { NumericSetting } from "../session_keys";
import { setWebAppConfigValue } from "../config_storage/actions";
import { NumberConfigKey } from "farmbot/dist/resources/configs/web_app";
import { t } from "../i18next_wrapper";
import { SearchField } from "../ui/search_field";
import { forceOnline } from "../devices/must_be_online";
import { demoAccountLog } from "../nav/ticker_list";
import { Actions } from "../constants";
import { useNavigate } from "react-router-dom";
import { Path } from "../internal_urls";
import { mapStateToProps } from "./state_to_props";

export const RawLogs = (props: LogsPanelProps) => {
  props.dispatch({ type: Actions.OPEN_POPUP, payload: "jobs" });
  props.dispatch({ type: Actions.SET_JOBS_PANEL_OPTION, payload: "logs" });
  const navigate = useNavigate();
  navigate(Path.plants());
  return <Page className="logs-page">
    <p>Logs have moved to the navigation bar.</p>
  </Page>;
};

export const Logs = connect(mapStateToProps)(RawLogs);
// eslint-disable-next-line import/no-default-export
export default Logs;

export class LogsPanel extends React.Component<LogsProps, Partial<LogsState>> {

  /** Initialize log type verbosity level to the configured or default value. */
  initialize = (key: NumberConfigKey, defaultValue: number): number => {
    const currentValue = this.props.getConfigValue(safeNumericSetting(key));
    if (isUndefined(currentValue)) {
      this.props.dispatch(
        setWebAppConfigValue(safeNumericSetting(key), defaultValue));
      return defaultValue;
    } else {
      return currentValue as number;
    }
  };

  state: LogsState = {
    autoscroll: false,
    success: this.initialize(NumericSetting.success_log, 1),
    busy: this.initialize(NumericSetting.busy_log, 1),
    warn: this.initialize(NumericSetting.warn_log, 1),
    error: this.initialize(NumericSetting.error_log, 1),
    info: this.initialize(NumericSetting.info_log, 1),
    fun: this.initialize(NumericSetting.fun_log, 1),
    debug: this.initialize(NumericSetting.debug_log, 1),
    assertion: this.initialize(NumericSetting.assertion_log, 1),
    searchTerm: "",
    markdown: true,
    currentFbosOnly: false,
  };

  /** Toggle display of a log type. Verbosity level 0 hides all, 3 shows all.*/
  toggle = (key: keyof Filters) => {
    // If log type is off, set it to verbosity level 1, otherwise turn it off
    const newSetting = this.state[key] === 0 ? 1 : 0;
    return () => {
      this.setState({ [key]: newSetting });
      this.props.dispatch(
        setWebAppConfigValue(safeNumericSetting(key + "_log"), newSetting));
    };
  };

  /** Set log type filter level. i.e., level 2 shows verbosity 2 and lower.*/
  setFilterLevel = (key: keyof Filters) => {
    return (value: number) => {
      this.setState({ [key]: value });
      this.props.dispatch(
        setWebAppConfigValue(safeNumericSetting(key + "_log"), value));
    };
  };

  toggleCurrentFbosOnly = () =>
    this.setState({ currentFbosOnly: !this.state.currentFbosOnly });

  /** Determine if log type filters are active. */
  get filterActive() {
    const filterKeys = filterStateKeys(this.state);
    const filterValues = filterKeys
      .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) || this.state.currentFbosOnly;
  }

  toggleMarkdown = () => this.setState({ markdown: !this.state.markdown });

  render() {
    const { dispatch, bot } = this.props;
    return <div className={"logs-tab grid"}>
      <div className={"search-row"}>
        <SearchField nameKey={"logs"}
          placeholder={t("Search logs...")}
          searchTerm={this.state.searchTerm}
          onChange={searchTerm => this.setState({ searchTerm })} />
        <div className={"logs-settings-menu-button"}>
          <Popover position={Position.TOP_RIGHT}
            target={<i className={"fa fa-gear fb-icon-button"} />}
            content={<LogsSettingsMenu
              markdown={this.state.markdown}
              toggleMarkdown={this.toggleMarkdown}
              setFilterLevel={this.setFilterLevel}
              dispatch={dispatch}
              sourceFbosConfig={this.props.sourceFbosConfig}
              bot={bot}
              getConfigValue={this.props.getConfigValue} />} />
        </div>
      </div>
      <LogsTable
        logs={this.props.logs.
          concat(forceOnline() ? [demoAccountLog()] : [])}
        dispatch={dispatch}
        state={this.state}
        fbosVersion={bot.hardware.informational_settings.controller_version
          || this.props.fbosVersion}
        filterActive={this.filterActive}
        toggle={this.toggle}
        setFilterLevel={this.setFilterLevel}
        toggleCurrentFbosOnly={this.toggleCurrentFbosOnly}
        device={this.props.device}
        timeSettings={this.props.timeSettings} />
    </div>;
  }
}