diff --git a/app/serializers/base_point_serializer.rb b/app/serializers/base_point_serializer.rb index 0f8fb4576..32380fd85 100644 --- a/app/serializers/base_point_serializer.rb +++ b/app/serializers/base_point_serializer.rb @@ -1,6 +1,24 @@ class BasePointSerializer < ApplicationSerializer attributes :device_id, :name, :pointer_type, :meta, :x, :y, :z + # PROBLEM: + # * Users need a mutable way to mark a plant's creation time => `planted_at` + # * DB Admin needs to know the _real_ created_at time. + # * We can't change field names (or destroy data) that is in use by legacy devices + # + # SOLUTION: + # * Don't allow users to modify `created_at` + # * Provide `planted_at` if possible. + # * Always provide `planted_at` if it is available + # * Provide a read-only view of `created_at` if `planted_at` is `nil` + def planted_at + object.planted_at || object.created_at + end + + def created_at + planted_at + end + def meta object.meta || {} end diff --git a/frontend/constants.ts b/frontend/constants.ts index 49bc3ffeb..ea11a3eb2 100644 --- a/frontend/constants.ts +++ b/frontend/constants.ts @@ -348,9 +348,6 @@ export namespace ToolTips { For example, you can mark a plant as "planted" during a seeding sequence or mark a weed as "removed" after removing it.`); - export const REBOOT = - trim(`Power cycle FarmBot's onboard computer.`); - export const SET_SERVO_ANGLE = trim(`Move a servo to the provided angle. An angle of 90 degrees corresponds to the servo midpoint (or, for a continuous rotation @@ -362,6 +359,9 @@ export namespace ToolTips { export const MOVE_TO_HOME = trim(`Move FarmBot to home for the provided axis.`); + export const ASSERTION = + trim(`Evaluate Lua commands. For power users and software developers.`); + export const FIRMWARE_ACTION = trim(`FarmBot OS or micro-controller firmware action.`); @@ -740,6 +740,15 @@ export namespace Content { encoders, stall detection, or endstops enabled for the chosen axis. Enable endstops, encoders, or stall detection from the Device page for: `); + export const REBOOT_STEP = + trim(`Power cycle FarmBot's onboard computer.`); + + export const SHUTDOWN_STEP = + trim(`Power down FarmBot's onboard computer.`); + + export const ESTOP_STEP = + trim(`Unlocking a device requires user intervention.`); + export const IN_USE = trim(`Used in another resource. Protected from deletion.`); diff --git a/frontend/css/global.scss b/frontend/css/global.scss index 9e1e23ae6..468e830dc 100644 --- a/frontend/css/global.scss +++ b/frontend/css/global.scss @@ -1314,6 +1314,24 @@ ul { } } +.update-resource-step { + .custom-meta-field { + position: relative; + .fa-undo { + position: absolute; + top: 0.65rem; + right: 0.5rem; + color: $medium_light_gray; + &:hover { + color: $dark_gray; + } + } + } + .update-resource-pair { + margin-top: 1rem; + } +} + .farmware-name-manual-input { margin-top: 1rem; } diff --git a/frontend/css/steps.scss b/frontend/css/steps.scss index d8dc65365..f62097440 100644 --- a/frontend/css/steps.scss +++ b/frontend/css/steps.scss @@ -136,6 +136,9 @@ &.reboot-step { background: $brown; } + &.shutdown-step { + background: $brown; + } &.unknown-step { background: $gray; } @@ -253,6 +256,9 @@ &.emergency-stop-step { background: $light_red; } + &.shutdown-step { + background: $light_brown; + } &.reboot-step { background: $light_brown; } diff --git a/frontend/devices/components/fbos_settings/__tests__/os_update_button_test.tsx b/frontend/devices/components/fbos_settings/__tests__/os_update_button_test.tsx index d83f97440..5b3896b83 100644 --- a/frontend/devices/components/fbos_settings/__tests__/os_update_button_test.tsx +++ b/frontend/devices/components/fbos_settings/__tests__/os_update_button_test.tsx @@ -11,12 +11,15 @@ import { OsUpdateButton } from "../os_update_button"; import { OsUpdateButtonProps } from "../interfaces"; import { ShouldDisplay } from "../../../interfaces"; import { Content } from "../../../../constants"; +import { ConfigurationName } from "farmbot"; + +const UPDATE_CHANNEL = "update_channel" as ConfigurationName; describe("", () => { beforeEach(() => { bot.currentOSVersion = "6.1.6"; bot.hardware.informational_settings.controller_version = "6.1.6"; - bot.hardware.configuration.beta_opt_in = false; + (bot.hardware.configuration[UPDATE_CHANNEL] as string) = "stable"; }); const fakeProps = (): OsUpdateButtonProps => ({ @@ -33,7 +36,6 @@ describe("", () => { availableVersion: string | undefined; availableBetaVersion: string | undefined; availableBetaCommit: string | undefined; - betaOptIn: boolean | undefined; onBeta: boolean | undefined; update_available?: boolean | undefined; shouldDisplay: ShouldDisplay; @@ -46,7 +48,6 @@ describe("", () => { availableVersion: "6.1.6", availableBetaVersion: undefined, availableBetaCommit: undefined, - betaOptIn: false, onBeta: false, shouldDisplay: () => false, update_channel: "stable", @@ -104,7 +105,7 @@ describe("", () => { expected: Results) => { const { installedVersion, installedCommit, onBeta, update_available, - availableVersion, availableBetaVersion, availableBetaCommit, betaOptIn, + availableVersion, availableBetaVersion, availableBetaCommit, shouldDisplay, update_channel, } = testProps; bot.hardware.informational_settings.controller_version = installedVersion; @@ -115,9 +116,7 @@ describe("", () => { bot.currentOSVersion = availableVersion; bot.currentBetaOSVersion = availableBetaVersion; bot.currentBetaOSCommit = availableBetaCommit; - bot.hardware.configuration.beta_opt_in = betaOptIn; - // tslint:disable-next-line:no-any - (bot.hardware.configuration as any).update_channel = update_channel; + (bot.hardware.configuration[UPDATE_CHANNEL] as string) = update_channel; const p = fakeProps(); p.shouldDisplay = shouldDisplay; @@ -156,7 +155,7 @@ describe("", () => { const testProps = defaultTestProps(); testProps.installedVersion = "6.1.6"; testProps.availableVersion = undefined; - testProps.betaOptIn = true; + testProps.update_channel = "beta"; const expectedResults = cantConnect("release server"); testButtonState(testProps, expectedResults); }); @@ -166,7 +165,7 @@ describe("", () => { testProps.installedVersion = "6.1.6"; testProps.availableVersion = undefined; testProps.availableBetaVersion = "6.1.7-beta"; - testProps.betaOptIn = true; + testProps.update_channel = "beta"; const expectedResults = updateNeeded("6.1.7-beta"); testButtonState(testProps, expectedResults); }); @@ -175,7 +174,7 @@ describe("", () => { const testProps = defaultTestProps(); testProps.installedVersion = "6.1.6"; testProps.availableBetaVersion = undefined; - testProps.betaOptIn = true; + testProps.update_channel = "beta"; const expectedResults = upToDate("6.1.6"); testButtonState(testProps, expectedResults); }); @@ -205,7 +204,7 @@ describe("", () => { const testProps = defaultTestProps(); testProps.installedVersion = "6.1.5"; testProps.availableBetaVersion = "7.0.0-beta"; - testProps.betaOptIn = true; + testProps.update_channel = "beta"; const expectedResults = updateNeeded("7.0.0-beta"); testButtonState(testProps, expectedResults); }); @@ -214,7 +213,7 @@ describe("", () => { const testProps = defaultTestProps(); testProps.installedVersion = "6.1.6"; testProps.availableBetaVersion = "6.1.6-beta"; - testProps.betaOptIn = true; + testProps.update_channel = "beta"; const expectedResults = upToDate("6.1.6"); testButtonState(testProps, expectedResults); }); @@ -223,7 +222,7 @@ describe("", () => { const testProps = defaultTestProps(); testProps.installedVersion = "6.1.6"; testProps.availableBetaVersion = "6.1.6-beta"; - testProps.betaOptIn = true; + testProps.update_channel = "beta"; testProps.onBeta = true; const expectedResults = updateNeeded("6.1.6"); testButtonState(testProps, expectedResults); @@ -233,7 +232,7 @@ describe("", () => { const testProps = defaultTestProps(); testProps.installedVersion = "6.1.6"; testProps.availableBetaVersion = "6.1.6-beta"; - testProps.betaOptIn = false; + testProps.update_channel = "stable"; testProps.onBeta = true; const expectedResults = updateNeeded("6.1.6"); testButtonState(testProps, expectedResults); @@ -243,7 +242,7 @@ describe("", () => { const testProps = defaultTestProps(); testProps.installedVersion = "6.1.7"; testProps.availableBetaVersion = "6.1.7-beta"; - testProps.betaOptIn = true; + testProps.update_channel = "beta"; testProps.onBeta = true; const expectedResults = upToDate("6.1.7-beta"); testButtonState(testProps, expectedResults); @@ -253,7 +252,7 @@ describe("", () => { const testProps = defaultTestProps(); testProps.installedVersion = "6.1.7-beta"; testProps.availableBetaVersion = "6.1.7-beta"; - testProps.betaOptIn = true; + testProps.update_channel = "beta"; const expectedResults = upToDate("6.1.7-beta"); testButtonState(testProps, expectedResults); }); @@ -264,7 +263,7 @@ describe("", () => { testProps.installedCommit = "old commit"; testProps.availableBetaVersion = "7.0.0-beta"; testProps.availableBetaCommit = "new commit"; - testProps.betaOptIn = true; + testProps.update_channel = "beta"; testProps.onBeta = true; const expectedResults = updateNeeded("7.0.0-beta"); testButtonState(testProps, expectedResults); @@ -273,7 +272,7 @@ describe("", () => { it("handles installed version newer than available (beta enabled)", () => { const testProps = defaultTestProps(); testProps.installedVersion = "6.1.7"; - testProps.betaOptIn = true; + testProps.update_channel = "beta"; testProps.onBeta = false; testProps.availableBetaVersion = "6.1.7-beta"; const expectedResults = upToDate("6.1.7-beta"); @@ -308,16 +307,6 @@ describe("", () => { testButtonState(testProps, expectedResults); }); - it("doesn't use update_channel value", () => { - const testProps = defaultTestProps(); - testProps.installedVersion = "6.1.6"; - testProps.shouldDisplay = () => false; - testProps.update_channel = "beta"; - testProps.availableBetaVersion = "6.1.7-beta"; - const expectedResults = upToDate("6.1.6"); - testButtonState(testProps, expectedResults); - }); - it("compares release candidates: newer", () => { const testProps = defaultTestProps(); testProps.availableVersion = "6.1.5"; diff --git a/frontend/devices/components/fbos_settings/fbos_details.tsx b/frontend/devices/components/fbos_settings/fbos_details.tsx index 9ddc7de29..3f39e61da 100644 --- a/frontend/devices/components/fbos_settings/fbos_details.tsx +++ b/frontend/devices/components/fbos_settings/fbos_details.tsx @@ -263,7 +263,7 @@ export function FbosDetails(props: FbosDetailsProps) { soc_temp, wifi_level, uptime, memory_usage, disk_usage, throttled, wifi_level_percent, cpu_usage, private_ip, } = props.botInfoSettings; - const { last_ota, last_ota_checkup } = props.deviceAccount.body; + const { last_ota, last_ota_checkup, fbos_version } = props.deviceAccount.body; const infoFwCommit = firmware_version?.includes(".") ? firmware_commit : "---"; const firmwareCommit = firmware_version?.split("-")[1] || infoFwCommit; @@ -273,6 +273,7 @@ export function FbosDetails(props: FbosDetailsProps) { botToMqttLastSeen={props.botToMqttLastSeen} timeSettings={props.timeSettings} device={props.deviceAccount} /> +

{t("Version last seen")}: {fbos_version}

{t("Environment")}: {env}

diff --git a/frontend/devices/components/fbos_settings/os_update_button.tsx b/frontend/devices/components/fbos_settings/os_update_button.tsx index 7cd41412d..b0893a786 100644 --- a/frontend/devices/components/fbos_settings/os_update_button.tsx +++ b/frontend/devices/components/fbos_settings/os_update_button.tsx @@ -4,7 +4,7 @@ import { SemverResult, semverCompare } from "../../../util"; import { OsUpdateButtonProps } from "./interfaces"; import { checkControllerUpdates } from "../../actions"; import { isString } from "lodash"; -import { BotState, Feature } from "../../interfaces"; +import { BotState } from "../../interfaces"; import { Content } from "../../../constants"; import { t } from "../../../i18next_wrapper"; @@ -154,9 +154,8 @@ export const OsUpdateButton = (props: OsUpdateButtonProps) => { const { controller_version } = bot.hardware.informational_settings; /** FBOS beta release opt-in setting. */ - const betaOptIn = props.shouldDisplay(Feature.use_update_channel) - ? sourceFbosConfig("update_channel" as ConfigurationName).value !== "stable" - : !!sourceFbosConfig("beta_opt_in").value; + const betaOptIn = + sourceFbosConfig("update_channel" as ConfigurationName).value !== "stable"; /** FBOS update availability. */ const buttonStatusProps = buttonVersionStatus({ bot, betaOptIn }); diff --git a/frontend/logs/__tests__/index_test.tsx b/frontend/logs/__tests__/index_test.tsx index ac5f67230..95432ba4d 100644 --- a/frontend/logs/__tests__/index_test.tsx +++ b/frontend/logs/__tests__/index_test.tsx @@ -117,7 +117,6 @@ describe("", () => { it("shows filtered overall filter status", () => { const p = fakeProps(); - p.shouldDisplay = () => true; const wrapper = mount(); const state = fakeLogsState(); state.assertion = 2; @@ -129,10 +128,9 @@ describe("", () => { it("shows unfiltered overall filter status", () => { const p = fakeProps(); - p.shouldDisplay = () => false; const wrapper = mount(); const state = fakeLogsState(); - state.assertion = 2; + state.assertion = 3; wrapper.setState(state); const filterBtn = wrapper.find("button").first(); expect(filterBtn.text().toLowerCase()).toEqual("filter"); diff --git a/frontend/logs/components/filter_menu.tsx b/frontend/logs/components/filter_menu.tsx index 8a5da3c0b..7b4c1bd07 100644 --- a/frontend/logs/components/filter_menu.tsx +++ b/frontend/logs/components/filter_menu.tsx @@ -5,7 +5,6 @@ import { Filters } from "../interfaces"; import { startCase } from "lodash"; import { MESSAGE_TYPES, MessageType } from "../../sequences/interfaces"; import { t } from "../../i18next_wrapper"; -import { Feature, ShouldDisplay } from "../../devices/interfaces"; const MENU_ORDER: string[] = [ MessageType.success, @@ -26,11 +25,9 @@ const menuSort = (a: string, b: string) => /** Get log filter keys from LogsState. */ export const filterStateKeys = - (state: LogsState, shouldDisplay: ShouldDisplay) => + (state: LogsState) => Object.keys(state) - .filter(key => !["autoscroll", "markdown", "searchTerm"].includes(key)) - .filter(key => shouldDisplay(Feature.assertion_block) - || key !== "assertion"); + .filter(key => !["autoscroll", "markdown", "searchTerm"].includes(key)); export const LogsFilterMenu = (props: LogsFilterMenuProps) => { /** Filter level 0: logs hidden. */ @@ -56,7 +53,7 @@ export const LogsFilterMenu = (props: LogsFilterMenuProps) => { {t("normal")} - {filterStateKeys(props.state, props.shouldDisplay).sort(menuSort) + {filterStateKeys(props.state).sort(menuSort) .map((logType: keyof Filters) =>