Merge branch 'staging' into staging
commit
2951baeb05
|
@ -20,19 +20,19 @@ module CeleryScriptSettingsBag
|
|||
"BoxLed3" => BoxLed,
|
||||
"BoxLed4" => BoxLed }
|
||||
ALLOWED_AXIS = %w(x y z all)
|
||||
ALLOWED_ASSERTION_TYPES = %w(abort recover abort_recover)
|
||||
ALLOWED_ASSERTION_TYPES = %w(abort recover abort_recover continue)
|
||||
ALLOWED_CHANGES = %w(add remove update)
|
||||
ALLOWED_CHANNEL_NAMES = %w(ticker toast email espeak)
|
||||
ALLOWED_LHS_STRINGS = [*(0..69)].map { |x| "pin#{x}" }.concat(%w(x y z))
|
||||
ALLOWED_LHS_TYPES = [String, :named_pin]
|
||||
ALLOWED_MESSAGE_TYPES = %w(success busy warn error info fun debug)
|
||||
ALLOWED_MESSAGE_TYPES = %w(assertion busy debug error fun info success warn)
|
||||
ALLOWED_OPS = %w(< > is not is_undefined)
|
||||
ALLOWED_PACKAGES = %w(farmbot_os arduino_firmware)
|
||||
ALLOWED_PIN_MODES = [DIGITAL = 0, ANALOG = 1]
|
||||
ALLOWED_PIN_TYPES = PIN_TYPE_MAP.keys
|
||||
ALLOWED_POINTER_TYPE = %w(GenericPointer ToolSlot Plant)
|
||||
ALLOWED_RESOURCE_TYPE = %w(Device Point Plant ToolSlot GenericPointer)
|
||||
ALLOWED_RPC_NODES = %w(calibrate change_ownership
|
||||
ALLOWED_RPC_NODES = %w(assertion calibrate change_ownership
|
||||
check_updates dump_info emergency_lock
|
||||
emergency_unlock execute execute_script
|
||||
factory_reset find_home flash_firmware home
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
class AddAssertionLogToWebAppConfig < ActiveRecord::Migration[5.2]
|
||||
safety_assured
|
||||
|
||||
def change
|
||||
add_column :web_app_configs,
|
||||
:assertion_log,
|
||||
:integer,
|
||||
default: 1
|
||||
end
|
||||
end
|
|
@ -1728,7 +1728,8 @@ CREATE TABLE public.web_app_configs (
|
|||
confirm_plant_deletion boolean DEFAULT true,
|
||||
confirm_sequence_deletion boolean DEFAULT true,
|
||||
discard_unsaved_sequences boolean DEFAULT false,
|
||||
user_interface_read_only_mode boolean DEFAULT false
|
||||
user_interface_read_only_mode boolean DEFAULT false,
|
||||
assertion_log integer DEFAULT 1
|
||||
);
|
||||
|
||||
|
||||
|
@ -3268,6 +3269,7 @@ INSERT INTO "schema_migrations" (version) VALUES
|
|||
('20190722160305'),
|
||||
('20190729134954'),
|
||||
('20190804194135'),
|
||||
('20190804194154');
|
||||
('20190804194154'),
|
||||
('20190823164837');
|
||||
|
||||
|
||||
|
|
|
@ -271,6 +271,7 @@ export function fakeWebAppConfig(): TaggedWebAppConfig {
|
|||
device_id: idCounter++,
|
||||
created_at: "2018-01-11T20:20:38.362Z",
|
||||
updated_at: "2018-01-22T15:32:41.970Z",
|
||||
assertion_log: 1,
|
||||
confirm_plant_deletion: true,
|
||||
confirm_step_deletion: false,
|
||||
confirm_sequence_deletion: true,
|
||||
|
|
|
@ -1362,3 +1362,15 @@ ul {
|
|||
color: $dark_gray;
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
border: 0;
|
||||
padding: 6px 8px;
|
||||
box-shadow: 0 0 10px #ddd;
|
||||
outline: none!important;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
textarea:focus {
|
||||
box-shadow: 0 0 10px rgba(0,0,0,.2);
|
||||
}
|
||||
|
|
|
@ -65,6 +65,7 @@ export type SourceFwConfig = (config: McuParamName) =>
|
|||
export type ShouldDisplay = (x: Feature) => boolean;
|
||||
/** Names of features that use minimum FBOS version checking. */
|
||||
export enum Feature {
|
||||
assertion_block = "assertion_block",
|
||||
named_pins = "named_pins",
|
||||
sensors = "sensors",
|
||||
change_ownership = "change_ownership",
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
jest.mock("react-redux", () => ({
|
||||
connect: jest.fn()
|
||||
}));
|
||||
jest.mock("react-redux", () => ({ connect: jest.fn() }));
|
||||
|
||||
let mockPath = "";
|
||||
jest.mock("../../../history", () => ({
|
||||
|
@ -8,10 +6,15 @@ jest.mock("../../../history", () => ({
|
|||
getPathArray: jest.fn(() => mockPath.split("/"))
|
||||
}));
|
||||
|
||||
jest.mock("../../../api/crud", () => ({
|
||||
destroy: jest.fn(),
|
||||
jest.mock("../../../api/crud", () => ({ destroy: jest.fn() }));
|
||||
|
||||
let mockDev = false;
|
||||
jest.mock("../../../account/dev/dev_support", () => ({
|
||||
DevSettings: { futureFeaturesEnabled: () => mockDev }
|
||||
}));
|
||||
|
||||
jest.mock("../../point_groups/actions", () => ({ createGroup: jest.fn() }));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { SelectPlants, SelectPlantsProps } from "../select_plants";
|
||||
|
@ -19,6 +22,7 @@ import { fakePlant } from "../../../__test_support__/fake_state/resources";
|
|||
import { Actions } from "../../../constants";
|
||||
import { clickButton } from "../../../__test_support__/helpers";
|
||||
import { destroy } from "../../../api/crud";
|
||||
import { createGroup } from "../../point_groups/actions";
|
||||
|
||||
describe("<SelectPlants />", () => {
|
||||
beforeEach(function () {
|
||||
|
@ -106,4 +110,16 @@ describe("<SelectPlants />", () => {
|
|||
expect(destroy).toHaveBeenCalledWith("plant.1", true);
|
||||
expect(destroy).toHaveBeenCalledWith("plant.2", true);
|
||||
});
|
||||
|
||||
it("shows other buttons", () => {
|
||||
mockDev = true;
|
||||
const wrapper = mount(<SelectPlants {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain("Create");
|
||||
});
|
||||
|
||||
it("creates group", () => {
|
||||
const wrapper = mount(<SelectPlants {...fakeProps()} />);
|
||||
wrapper.find(".blue").simulate("click");
|
||||
expect(createGroup).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -77,7 +77,7 @@ export class PlantInventoryItem extends
|
|||
key={plantId}
|
||||
onMouseEnter={() => toggle("enter")}
|
||||
onMouseLeave={() => toggle("leave")}
|
||||
onClick={click} >
|
||||
onClick={click}>
|
||||
<img
|
||||
className="plant-search-item-image"
|
||||
src={DEFAULT_ICON}
|
||||
|
@ -88,6 +88,6 @@ export class PlantInventoryItem extends
|
|||
<i className="plant-search-item-age">
|
||||
{daysOld} {t("days old")}
|
||||
</i>
|
||||
</div >;
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import * as React from "react";
|
||||
import { history } from "../../history";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { Everything } from "../../interfaces";
|
||||
import { PlantInventoryItem } from "./plant_inventory_item";
|
||||
|
@ -14,6 +13,7 @@ import {
|
|||
} from "./designer_panel";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { createGroup } from "../point_groups/actions";
|
||||
import { DevSettings } from "../../account/dev/dev_support";
|
||||
|
||||
export function mapStateToProps(props: Everything) {
|
||||
return {
|
||||
|
@ -29,8 +29,6 @@ export interface SelectPlantsProps {
|
|||
selected: string[];
|
||||
}
|
||||
|
||||
const YOU_SURE = "Are you sure you want to delete {{length}} plants?";
|
||||
|
||||
@connect(mapStateToProps)
|
||||
export class SelectPlants
|
||||
extends React.Component<SelectPlantsProps, {}> {
|
||||
|
@ -47,7 +45,8 @@ export class SelectPlants
|
|||
|
||||
destroySelected = (plantUUIDs: string[]) => {
|
||||
if (plantUUIDs &&
|
||||
confirm(t(YOU_SURE, { length: plantUUIDs.length }))) {
|
||||
confirm(t("Are you sure you want to delete {{length}} plants?",
|
||||
{ length: plantUUIDs.length }))) {
|
||||
plantUUIDs.map(uuid => {
|
||||
this
|
||||
.props
|
||||
|
@ -74,17 +73,19 @@ export class SelectPlants
|
|||
onClick={() => this.props.dispatch(selectPlant(undefined))}>
|
||||
{t("Select none")}
|
||||
</button>
|
||||
<button className="fb-button blue"
|
||||
onClick={() => createGroup({
|
||||
points: this.props.selected,
|
||||
dispatch: this.props.dispatch
|
||||
})}>
|
||||
{t("Create group")}
|
||||
</button>
|
||||
{/* <button className="fb-button green"
|
||||
onClick={() => { throw new Error("WIP"); }}>
|
||||
{t("Create garden")}
|
||||
</button> */}
|
||||
{DevSettings.futureFeaturesEnabled() &&
|
||||
<button className="fb-button blue"
|
||||
onClick={() => createGroup({
|
||||
points: this.props.selected,
|
||||
dispatch: this.props.dispatch
|
||||
})}>
|
||||
{t("Create group")}
|
||||
</button>}
|
||||
{DevSettings.futureFeaturesEnabled() &&
|
||||
<button className="fb-button green"
|
||||
onClick={() => { throw new Error("WIP"); }}>
|
||||
{t("Create garden")}
|
||||
</button>}
|
||||
</div>;
|
||||
|
||||
render() {
|
||||
|
|
|
@ -98,7 +98,14 @@ describe("<Logs />", () => {
|
|||
it("shows overall filter status", () => {
|
||||
const wrapper = mount(<Logs {...fakeProps()} />);
|
||||
wrapper.setState({
|
||||
success: 3, busy: 3, warn: 3, error: 3, info: 3, fun: 3, debug: 3
|
||||
assertion: 3,
|
||||
busy: 3,
|
||||
debug: 3,
|
||||
error: 3,
|
||||
fun: 3,
|
||||
info: 3,
|
||||
success: 3,
|
||||
warn: 3,
|
||||
});
|
||||
const filterBtn = wrapper.find("button").first();
|
||||
expect(filterBtn.text().toLowerCase()).toEqual("filter");
|
||||
|
|
|
@ -9,7 +9,7 @@ const logTypes = MESSAGE_TYPES;
|
|||
|
||||
describe("<LogsFilterMenu />", () => {
|
||||
const fakeState: LogsState = {
|
||||
autoscroll: true, success: 1, busy: 1, warn: 1,
|
||||
autoscroll: true, assertion: 1, success: 1, busy: 1, warn: 1,
|
||||
error: 1, info: 1, fun: 1, debug: 1
|
||||
};
|
||||
|
||||
|
@ -36,7 +36,7 @@ describe("<LogsFilterMenu />", () => {
|
|||
p.toggle = (x) => () => toggle(x);
|
||||
p.setFilterLevel = (x) => () => setFilterLevel(x);
|
||||
const wrapper = mount(<LogsFilterMenu {...p} />);
|
||||
wrapper.find("button").at(2).simulate("click");
|
||||
wrapper.find("button").at(3).simulate("click");
|
||||
expect(toggle).toHaveBeenCalledWith(MessageType.success);
|
||||
});
|
||||
|
||||
|
@ -46,7 +46,7 @@ describe("<LogsFilterMenu />", () => {
|
|||
const wrapper = mount(<LogsFilterMenu {...fakeProps()} />);
|
||||
const toggles = wrapper.find("button");
|
||||
expect(toggles.last().hasClass("green")).toBeTruthy();
|
||||
expect(toggles.at(2).hasClass("red")).toBeTruthy();
|
||||
expect(toggles.at(3).hasClass("red")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("bulk toggles filter levels", () => {
|
||||
|
|
|
@ -49,6 +49,7 @@ export class Logs extends React.Component<LogsProps, Partial<LogsState>> {
|
|||
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),
|
||||
};
|
||||
|
||||
/** Toggle display of a log type. Verbosity level 0 hides all, 3 shows all.*/
|
||||
|
|
|
@ -128,6 +128,20 @@ export function StepButtonCluster(props: StepButtonProps) {
|
|||
</StepButton>,
|
||||
];
|
||||
|
||||
shouldDisplay(Feature.assertion_block) && ALL_THE_BUTTONS.push(<StepButton
|
||||
{...commonStepProps}
|
||||
step={{
|
||||
kind: "assertion",
|
||||
args: {
|
||||
lua: "return 2 + 2 == 4",
|
||||
_then: { kind: "nothing", args: {} },
|
||||
assertion_type: "abort_recover",
|
||||
}
|
||||
}}
|
||||
color="purple">
|
||||
{t("ASSERTION")}
|
||||
</StepButton>);
|
||||
|
||||
shouldDisplay(Feature.mark_as_step) && ALL_THE_BUTTONS.push(<StepButton
|
||||
{...commonStepProps}
|
||||
step={{
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
import React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
import { TileAssertion, AssertionStepProps } from "../tile_assertion";
|
||||
import { Wait } from "farmbot";
|
||||
import { StepWrapper } from "../../step_ui/step_wrapper";
|
||||
import { DeepPartial } from "redux";
|
||||
import { fakeSequence } from "../../../__test_support__/fake_state/resources";
|
||||
import { buildResourceIndex } from "../../../__test_support__/resource_index_builder";
|
||||
import { renderCeleryNode } from "..";
|
||||
|
||||
const EMPTY: DeepPartial<AssertionStepProps> = {};
|
||||
|
||||
export const fakeAssertProps = (extras = EMPTY): AssertionStepProps => {
|
||||
const currentSequence = fakeSequence();
|
||||
const resources = buildResourceIndex().index;
|
||||
const props: AssertionStepProps = {
|
||||
currentSequence,
|
||||
currentStep: {
|
||||
kind: "assertion",
|
||||
args: {
|
||||
lua: "return 2 + 2 == 4",
|
||||
assertion_type: "continue",
|
||||
_then: {
|
||||
kind: "execute",
|
||||
args: {
|
||||
sequence_id: currentSequence.body.id || 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
dispatch: jest.fn(),
|
||||
index: 1,
|
||||
resources,
|
||||
confirmStepDeletion: false,
|
||||
};
|
||||
|
||||
return { ...props, ...(extras as AssertionStepProps) };
|
||||
};
|
||||
|
||||
describe("renderer", () => {
|
||||
it("displays the correct component", () => {
|
||||
const props = fakeAssertProps();
|
||||
const actual = renderCeleryNode(props);
|
||||
const expected = <TileAssertion {...props} />;
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("<TileAssertion/>", () => {
|
||||
it("crashes on non-assertion steps", () => {
|
||||
const p = fakeAssertProps();
|
||||
const currentStep: Wait =
|
||||
({ kind: "wait", args: { milliseconds: 1 } });
|
||||
const boom = () => TileAssertion({ ...p, currentStep });
|
||||
expect(boom).toThrow("Not an assertion");
|
||||
});
|
||||
|
||||
it("renders default stuff", () => {
|
||||
const p = fakeAssertProps();
|
||||
const el = shallow(<TileAssertion {...p} />);
|
||||
// We test this component's subcomponents in
|
||||
// isolation- no need to dupliacte tests here.
|
||||
expect(el.find(StepWrapper).length).toBe(1);
|
||||
});
|
||||
});
|
|
@ -31,6 +31,7 @@ import { TileSetZero } from "./tile_set_zero";
|
|||
import { TileCalibrate } from "./tile_calibrate";
|
||||
import { TileMoveHome } from "./tile_move_home";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { TileAssertion } from "./tile_assertion";
|
||||
|
||||
interface MoveParams {
|
||||
step: Step;
|
||||
|
@ -157,8 +158,8 @@ export function renderCeleryNode(props: StepParams) {
|
|||
return <TileFirmwareAction {...props} />;
|
||||
case "sync": case "dump_info": case "power_off": case "read_status":
|
||||
case "emergency_unlock": case "emergency_lock":
|
||||
case "install_first_party_farmware":
|
||||
return <TileSystemAction {...props} />;
|
||||
case "install_first_party_farmware": return <TileSystemAction {...props} />;
|
||||
case "assertion": return <TileAssertion {...props} />;
|
||||
default: return <TileUnknown {...props} />;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import { StepParams } from "../interfaces";
|
||||
import React from "react";
|
||||
import { Row, Col } from "../../ui";
|
||||
import { StepHeader } from "../step_ui/step_header";
|
||||
import { StepContent, StepWrapper } from "../step_ui";
|
||||
import { TypePart } from "./tile_assertion/type_part";
|
||||
import { LuaPart } from "./tile_assertion/lua_part";
|
||||
import { SequencePart } from "./tile_assertion/sequence_part";
|
||||
import { Assertion } from "farmbot/dist/corpus";
|
||||
|
||||
export interface AssertionStepProps extends StepParams {
|
||||
currentStep: Assertion;
|
||||
}
|
||||
|
||||
const CLASS_NAME = "if-step";
|
||||
const MOVE_THIS_CSS_PLZ = { marginTop: "10px" };
|
||||
|
||||
export function TileAssertion(props: StepParams) {
|
||||
const step = props.currentStep;
|
||||
|
||||
if (step.kind !== "assertion") { throw new Error("Not an assertion"); }
|
||||
|
||||
const p = props as AssertionStepProps;
|
||||
|
||||
return <StepWrapper>
|
||||
<StepHeader
|
||||
className={CLASS_NAME}
|
||||
helpText={""}
|
||||
currentSequence={p.currentSequence}
|
||||
currentStep={p.currentStep}
|
||||
dispatch={p.dispatch}
|
||||
index={p.index}
|
||||
confirmStepDeletion={props.confirmStepDeletion} />
|
||||
<StepContent className={CLASS_NAME}>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<LuaPart {...p} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row >
|
||||
<Col xs={6}><div style={MOVE_THIS_CSS_PLZ}> <TypePart {...p} /></div> </Col>
|
||||
<Col xs={6}><div style={MOVE_THIS_CSS_PLZ}> <SequencePart {...p} /></div> </Col>
|
||||
</Row>
|
||||
</StepContent>
|
||||
</StepWrapper>;
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
import React from "react";
|
||||
import { LuaPart } from "../lua_part";
|
||||
import { shallow } from "enzyme";
|
||||
import { ReduxAction } from "../../../../redux/interfaces";
|
||||
import { EditResourceParams } from "../../../../api/interfaces";
|
||||
import { Actions } from "../../../../constants";
|
||||
import { TaggedSequence } from "farmbot";
|
||||
import { fakeAssertProps } from "../../__tests__/tile_assertion_test";
|
||||
|
||||
describe("<LuaPart/>", () => {
|
||||
it("renders default verbiage and props", () => {
|
||||
const p = fakeAssertProps();
|
||||
const el = shallow(<LuaPart {...p} />);
|
||||
const fakeEvent =
|
||||
({ currentTarget: { value: "hello" } });
|
||||
el.find("textarea").first().simulate("change", fakeEvent);
|
||||
expect(p.dispatch).toHaveBeenCalled();
|
||||
const calledWith: ReduxAction<EditResourceParams> | undefined =
|
||||
(p.dispatch as jest.Mock).mock.calls[0][0];
|
||||
if (calledWith) {
|
||||
expect(calledWith.type).toEqual(Actions.OVERWRITE_RESOURCE);
|
||||
expect(calledWith.payload.uuid).toEqual(p.currentSequence.uuid);
|
||||
const s = calledWith.payload.update as TaggedSequence["body"];
|
||||
expect(s).toBeTruthy();
|
||||
const item = (s.body || [])[1];
|
||||
if (item.kind === "assertion") {
|
||||
expect(item.args.lua).toEqual("hello");
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
import React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
import { SequencePart } from "../sequence_part";
|
||||
import { SequenceSelectBox } from "../../../sequence_select_box";
|
||||
import { fakeAssertProps } from "../../__tests__/tile_assertion_test";
|
||||
|
||||
describe("<SequencePart/>", () => {
|
||||
it("renders default verbiage and props", () => {
|
||||
const p = fakeAssertProps();
|
||||
const el = shallow(<SequencePart {...p} />);
|
||||
el
|
||||
.find(SequenceSelectBox)
|
||||
.simulate("change", { value: 246, label: "y" });
|
||||
expect(p.dispatch).toHaveBeenCalled();
|
||||
const calls = (p.dispatch as jest.Mock).mock.calls[0][0];
|
||||
const { sequence_id } = calls.payload.update.body[1].args._then.args;
|
||||
expect(sequence_id).toEqual(246);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
import React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
import { TypePart } from "../type_part";
|
||||
import { FBSelect } from "../../../../ui";
|
||||
import { fakeAssertProps } from "../../__tests__/tile_assertion_test";
|
||||
|
||||
describe("<TypePart/>", () => {
|
||||
it("renders default verbiage and props", () => {
|
||||
const p = fakeAssertProps();
|
||||
const el = shallow(<TypePart {...p} />);
|
||||
el
|
||||
.find(FBSelect)
|
||||
.simulate("change", { value: "anything", label: "y" });
|
||||
expect(p.dispatch).toHaveBeenCalled();
|
||||
const calls = (p.dispatch as jest.Mock).mock.calls[0][0];
|
||||
console.log(calls);
|
||||
const { assertion_type } = calls.payload.update.body[1].args;
|
||||
expect(assertion_type).toEqual("anything");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
import { editStep } from "../../../api/crud";
|
||||
import { Assertion } from "farmbot/dist/corpus";
|
||||
import React from "react";
|
||||
import { AssertionStepProps } from "../tile_assertion";
|
||||
|
||||
export function LuaPart(props: AssertionStepProps) {
|
||||
const luaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
props.dispatch(editStep({
|
||||
step: props.currentStep,
|
||||
index: props.index,
|
||||
sequence: props.currentSequence,
|
||||
executor(c: Assertion) {
|
||||
c.args.lua = e.currentTarget.value;
|
||||
}
|
||||
}));
|
||||
};
|
||||
const { lua } = props.currentStep.args;
|
||||
return <div>
|
||||
<textarea
|
||||
value={lua}
|
||||
onChange={luaChange}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: `${((lua.split("\n").length) + 1) * 1.25}em`
|
||||
}} />
|
||||
</div>;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import { DropDownItem } from "../../../ui/fb_select";
|
||||
import { editStep } from "../../../api/crud";
|
||||
import { Assertion } from "farmbot/dist/corpus";
|
||||
import React from "react";
|
||||
import { SequenceSelectBox } from "../../sequence_select_box";
|
||||
import { AssertionStepProps } from "../tile_assertion";
|
||||
|
||||
export function SequencePart(props: AssertionStepProps) {
|
||||
const onChange = (ddi: DropDownItem) => props.dispatch(editStep({
|
||||
step: props.currentStep,
|
||||
index: props.index,
|
||||
sequence: props.currentSequence,
|
||||
executor(c: Assertion) {
|
||||
c.args._then = {
|
||||
kind: "execute",
|
||||
args: { sequence_id: ddi.value as number }
|
||||
};
|
||||
}
|
||||
}));
|
||||
|
||||
let sequenceId: number | undefined;
|
||||
const { _then } = props.currentStep.args;
|
||||
if (_then.kind == "execute") {
|
||||
sequenceId = _then.args.sequence_id;
|
||||
}
|
||||
return <span>
|
||||
<label>Recovery Sequence</label>
|
||||
<SequenceSelectBox
|
||||
onChange={onChange}
|
||||
resources={props.resources}
|
||||
sequenceId={sequenceId} />
|
||||
</span>;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import React from "react";
|
||||
import { ALLOWED_ASSERTION_TYPES, Assertion } from "farmbot";
|
||||
import { DropDownItem, FBSelect } from "../../../ui";
|
||||
import { editStep } from "../../../api/crud";
|
||||
import { AssertionStepProps } from "../tile_assertion";
|
||||
|
||||
const ASSERTION_TYPES: Record<ALLOWED_ASSERTION_TYPES, DropDownItem> = {
|
||||
"continue": { label: "Continue", value: "continue" },
|
||||
"recover": { label: "Recover and continue", value: "recover" },
|
||||
"abort_recover": { label: "Abort and recover", value: "abort_recover" },
|
||||
"abort": { label: "Abort", value: "abort" },
|
||||
};
|
||||
|
||||
export function TypePart(props: AssertionStepProps) {
|
||||
const { assertion_type } = props.currentStep.args;
|
||||
return <span>
|
||||
<label>If Test Fails</label>
|
||||
<FBSelect
|
||||
selectedItem={ASSERTION_TYPES[assertion_type]}
|
||||
onChange={(ddi) => {
|
||||
props.dispatch(editStep({
|
||||
step: props.currentStep,
|
||||
index: props.index,
|
||||
sequence: props.currentSequence,
|
||||
executor(c: Assertion) {
|
||||
c.args.assertion_type = ddi.value as ALLOWED_ASSERTION_TYPES;
|
||||
}
|
||||
}));
|
||||
}}
|
||||
list={Object.values(ASSERTION_TYPES)} />
|
||||
</span>;
|
||||
}
|
|
@ -55,6 +55,7 @@ export const BooleanSetting: Record<BooleanConfigKey, BooleanConfigKey> = {
|
|||
|
||||
export const NumericSetting: Record<NumberConfigKey, NumberConfigKey> = {
|
||||
/** Logs settings */
|
||||
assertion_log: "assertion_log",
|
||||
success_log: "success_log",
|
||||
busy_log: "busy_log",
|
||||
warn_log: "warn_log",
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
"coveralls": "3.0.5",
|
||||
"enzyme": "3.10.0",
|
||||
"enzyme-adapter-react-16": "1.14.0",
|
||||
"farmbot": "8.1.0",
|
||||
"farmbot": "8.1.6",
|
||||
"i18next": "17.0.9",
|
||||
"lodash": "4.17.15",
|
||||
"markdown-it": "9.0.1",
|
||||
|
|
|
@ -17,6 +17,24 @@ describe Api::LogsController do
|
|||
end
|
||||
|
||||
describe "#create" do
|
||||
it "allows `assertion` logs" do
|
||||
sign_in user
|
||||
before_count = Log.count
|
||||
body = {
|
||||
channels: [],
|
||||
major_version: 6,
|
||||
message: "HELLO",
|
||||
minor_version: 4,
|
||||
type: "assertion",
|
||||
verbosity: 1,
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
}
|
||||
post :create, body: body.to_json, params: { format: :json }
|
||||
expect(response.status).to eq(200)
|
||||
end
|
||||
|
||||
it "creates one log (legacy format)" do
|
||||
sign_in user
|
||||
before_count = Log.count
|
||||
|
|
Loading…
Reference in New Issue