dev stuff

pull/1062/head
gabrielburnworth 2018-12-03 19:06:13 -08:00
parent 0fdc454560
commit d347413c04
12 changed files with 179 additions and 9 deletions

View File

@ -0,0 +1,9 @@
class AddShowDevMenuToWebAppConfigs < ActiveRecord::Migration[5.2]
safety_assured
def change
add_column :web_app_configs,
:show_dev_menu,
:boolean,
default: false
end
end

View File

@ -0,0 +1,49 @@
jest.mock("../../config_storage/actions", () => ({
setWebAppConfigValue: jest.fn()
}));
import * as React from "react";
import { mount, shallow } from "enzyme";
import {
DevWidget, FUTURE_FE_FEATURES, FBOS_VERSION_OVERRIDE,
DevWidgetFERow, DevWidgetFBOSRow
} from "../dev_widget";
import { setWebAppConfigValue } from "../../config_storage/actions";
describe("<DevWidget />", () => {
const fakeProps = () => ({ dispatch: jest.fn() });
it("renders", () => {
const wrapper = mount(<DevWidget {...fakeProps()} />);
localStorage[FUTURE_FE_FEATURES] = "true";
localStorage[FBOS_VERSION_OVERRIDE] = "1.0.0";
wrapper.find("button").first().simulate("click");
expect(setWebAppConfigValue).toHaveBeenCalledWith("show_dev_menu", false);
expect(localStorage[FUTURE_FE_FEATURES]).toEqual(undefined);
expect(localStorage[FBOS_VERSION_OVERRIDE]).toEqual(undefined);
});
it("changes override value", () => {
const wrapper = shallow(<DevWidgetFBOSRow />);
wrapper.find("BlurableInput").simulate("commit",
{ currentTarget: { value: "1.2.3" } });
expect(localStorage[FBOS_VERSION_OVERRIDE]).toEqual("1.2.3");
wrapper.find(".fa-times").simulate("click");
expect(localStorage[FBOS_VERSION_OVERRIDE]).toEqual(undefined);
});
it("increases override value", () => {
const wrapper = mount(<DevWidgetFBOSRow />);
wrapper.find(".fa-angle-double-up").simulate("click");
expect(localStorage[FBOS_VERSION_OVERRIDE]).toEqual("1000.0.0");
wrapper.find(".fa-times").simulate("click");
expect(localStorage[FBOS_VERSION_OVERRIDE]).toEqual(undefined);
});
it("toggles unstable FE features", () => {
localStorage[FUTURE_FE_FEATURES] = "true";
const wrapper = mount(<DevWidgetFERow />);
wrapper.find("button").simulate("click");
expect(localStorage[FUTURE_FE_FEATURES]).toEqual(undefined);
});
});

View File

@ -5,7 +5,7 @@ jest.mock("react-redux", () => ({
import * as React from "react";
import { fakeState } from "../../__test_support__/fake_state";
import { mapStateToProps } from "../state_to_props";
import { shallow } from "enzyme";
import { shallow, mount } from "enzyme";
import { Account } from "../index";
import { edit } from "../../api/crud";
@ -41,4 +41,18 @@ describe("<Account />", () => {
el.find("Settings").simulate("save");
expect(props.dispatch).toHaveBeenCalledTimes(1);
});
it("doesn't show dev widget", () => {
const props = mapStateToProps(fakeState());
props.getConfigValue = () => false;
const wrapper = mount(<Account {...props} />);
expect(wrapper.text()).not.toContain("Dev options");
});
it("shows dev widget", () => {
const props = mapStateToProps(fakeState());
props.getConfigValue = () => true;
const wrapper = mount(<Account {...props} />);
expect(wrapper.text()).toContain("Dev options");
});
});

View File

@ -0,0 +1,85 @@
import * as React from "react";
import {
Widget, WidgetHeader, WidgetBody, Row, Col, BlurableInput
} from "../ui";
import { ToggleButton } from "../controls/toggle_button";
import { setWebAppConfigValue } from "../config_storage/actions";
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
export const FUTURE_FE_FEATURES = "FUTURE_FEATURES";
/** Unstable FE features enabled? */
export const futureFeaturesEnabled = () =>
!!localStorage.getItem(FUTURE_FE_FEATURES);
/** Show unstable FE features for development purposes. */
export const enableFutureFeatures = () =>
localStorage.setItem(FUTURE_FE_FEATURES, "true");
const disableFutureFeatures = () =>
localStorage.removeItem(FUTURE_FE_FEATURES);
export const FBOS_VERSION_OVERRIDE = "IM_A_DEVELOPER";
/** Escape hatch for platform developers doing offline development. */
const overriddenFbosVersion = () =>
localStorage.getItem(FBOS_VERSION_OVERRIDE);
const resetFbosVersionOverride = () =>
localStorage.removeItem(FBOS_VERSION_OVERRIDE);
const setFbosVersionOverride = (override: string) =>
localStorage.setItem(FBOS_VERSION_OVERRIDE, override);
export const DevWidgetFERow = () =>
<Row>
<Col xs={8}>
<label>
{"Enable unstable FE features"}
</label>
</Col>
<Col xs={4}>
<ToggleButton
toggleValue={futureFeaturesEnabled()}
toggleAction={futureFeaturesEnabled()
? disableFutureFeatures
: enableFutureFeatures} />
</Col>
</Row>;
export const DevWidgetFBOSRow = () => {
return <Row>
<Col xs={6}>
<label>
{"Change FBOS version"}
</label>
</Col>
<Col xs={1}>
<button className="fb-button red fa fa-times"
onClick={resetFbosVersionOverride} />
</Col>
<Col xs={1}>
<button className="fb-button green fa fa-angle-double-up"
onClick={() => setFbosVersionOverride("1000.0.0")} />
</Col>
<Col xs={4}>
<BlurableInput type="text"
value={overriddenFbosVersion() || ""}
onCommit={e =>
setFbosVersionOverride(e.currentTarget.value)} />
</Col>
</Row>;
};
export const DevWidget = ({ dispatch }: { dispatch: Function }) =>
<Widget>
<WidgetHeader title={"Dev options"}>
<button className="fb-button red"
onClick={() => {
disableFutureFeatures();
resetFbosVersionOverride();
dispatch(setWebAppConfigValue(
"show_dev_menu" as BooleanConfigKey, false));
}}>
{"Reset all and remove this widget"}
</button>
</WidgetHeader>
<WidgetBody>
<DevWidgetFERow />
<DevWidgetFBOSRow />
</WidgetBody>
</Widget>;

View File

@ -13,6 +13,8 @@ import { success } from "farmbot-toastr/dist";
import { LabsFeatures } from "./labs/labs_features";
import { ExportAccountPanel } from "./components/export_account_panel";
import { requestAccountExport } from "./request_account_export";
import { DevWidget } from "./dev_widget";
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
const KEYS: (keyof User)[] = ["id", "name", "email", "created_at", "updated_at"];
@ -89,6 +91,10 @@ export class Account extends React.Component<Props, State> {
<Row>
<ExportAccountPanel onClick={requestAccountExport} />
</Row>
<Row>
{this.props.getConfigValue("show_dev_menu" as BooleanConfigKey) &&
<DevWidget dispatch={this.props.dispatch} />}
</Row>
</Col>
</Page>;
}

View File

@ -2,6 +2,7 @@ import * as React from "react";
import { mount } from "enzyme";
import { BooleanSetting } from "../../../session_keys";
import { moveWidgetSetting, MoveWidgetSettingsMenu } from "../settings_menu";
import { enableFutureFeatures } from "../../../account/dev_widget";
describe("moveWidgetSetting()", () => {
it("renders setting", () => {
@ -23,7 +24,7 @@ describe("<MoveWidgetSettingsMenu />", () => {
it("displays motor plot toggle", () => {
const noToggle = mount(<MoveWidgetSettingsMenu {...fakeProps()} />);
expect(noToggle.text()).not.toContain("Motor position plot");
localStorage.setItem("FUTURE_FEATURES", "true");
enableFutureFeatures();
const wrapper = mount(<MoveWidgetSettingsMenu {...fakeProps()} />);
expect(wrapper.text()).toContain("Motor position plot");
});

View File

@ -4,6 +4,7 @@ import { BooleanSetting } from "../../session_keys";
import { ToggleButton } from "../toggle_button";
import { ToggleWebAppBool, GetWebAppBool } from "./interfaces";
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
import { futureFeaturesEnabled } from "../../account/dev_widget";
export const moveWidgetSetting = (toggle: ToggleWebAppBool, getValue: GetWebAppBool) =>
({ label, setting }: { label: string, setting: BooleanConfigKey }) =>
@ -43,7 +44,7 @@ export const MoveWidgetSettingsMenu = ({ toggle, getValue }: {
label={t("perform homing (find home)")}
setting={BooleanSetting.home_button_homing} />
{localStorage.getItem("FUTURE_FEATURES") &&
{futureFeaturesEnabled() &&
<div>
<p>{t("Motor position plot")}</p>
<Setting

View File

@ -18,6 +18,7 @@ import { GardenMapLegendProps } from "../../interfaces";
import { clickButton } from "../../../../__test_support__/helpers";
import { history } from "../../../../history";
import { BooleanSetting } from "../../../../session_keys";
import { enableFutureFeatures } from "../../../../account/dev_widget";
describe("<GardenMapLegend />", () => {
const fakeProps = (): GardenMapLegendProps => ({
@ -47,7 +48,7 @@ describe("<GardenMapLegend />", () => {
});
it("shows submenu", () => {
localStorage.setItem("FUTURE_FEATURES", "true");
enableFutureFeatures();
const wrapper = mount(<GardenMapLegend {...fakeProps()} />);
expect(wrapper.html()).toContain("filter");
expect(wrapper.html()).toContain("extras");

View File

@ -11,6 +11,7 @@ import { MoveModeLink } from "../../plants/move_to";
import { SavedGardensLink } from "../../saved_gardens/saved_gardens";
import { GetWebAppConfigValue } from "../../../config_storage/actions";
import { BooleanSetting } from "../../../session_keys";
import { futureFeaturesEnabled } from "../../../account/dev_widget";
const OriginSelector = ({ quadrant, update }: {
quadrant: BotOriginQuadrant,
@ -78,7 +79,7 @@ const LayerToggles = (props: GardenMapLegendProps) => {
label={t("Points?")}
onClick={toggle("show_points")}
submenuTitle={t("extras")}
popover={!!localStorage.getItem("FUTURE_FEATURES")
popover={futureFeaturesEnabled()
? <PointsSubMenu toggle={toggle} getConfigValue={getConfigValue} />
: undefined} />
<LayerToggle
@ -99,7 +100,7 @@ const LayerToggles = (props: GardenMapLegendProps) => {
dispatch={props.dispatch}
getConfigValue={getConfigValue}
imageAgeInfo={props.imageAgeInfo} />} />
{localStorage.getItem("FUTURE_FEATURES") &&
{futureFeaturesEnabled() &&
<LayerToggle
value={props.showSensorReadings}
label={t("Readings?")}

View File

@ -11,6 +11,7 @@ import * as moment from "moment";
import { Actions } from "../../constants";
import { Link } from "../../link";
import { DesignerPanelContent } from "./designer_panel";
import { futureFeaturesEnabled } from "../../account/dev_widget";
export interface PlantPanelProps {
info: FormattedPlantInfo;
@ -69,7 +70,7 @@ export function EditPlantStatus(props: EditPlantStatusProps) {
const MoveToPlant =
(props: { x: number, y: number, dispatch: Function, isEditing: boolean }) =>
<button className="fb-button gray"
hidden={!localStorage.getItem("FUTURE_FEATURES") || props.isEditing}
hidden={!futureFeaturesEnabled() || props.isEditing}
onClick={() => {
props.dispatch({
type: Actions.CHOOSE_LOCATION,

View File

@ -16,6 +16,7 @@ import { Content } from "../../constants";
import {
DesignerPanel, DesignerPanelHeader, DesignerPanelContent
} from "../plants/designer_panel";
import { futureFeaturesEnabled } from "../../account/dev_widget";
export const mapStateToProps = (props: Everything): SavedGardensProps => ({
savedGardens: selectAllSavedGardens(props.resources.index),
@ -63,7 +64,7 @@ export class SavedGardens extends React.Component<SavedGardensProps, {}> {
/** Link to SavedGardens panel for garden map legend. */
export const SavedGardensLink = () =>
<button className="fb-button green"
hidden={!(localStorage.getItem("FUTURE_FEATURES"))}
hidden={!futureFeaturesEnabled()}
onClick={() => history.push("/app/designer/saved_gardens")}>
{t("Saved Gardens")}
</button>;

View File

@ -4,6 +4,7 @@ import { AccountMenuProps } from "./interfaces";
import { docLink } from "../ui/doc_link";
import { Link } from "../link";
import { shortRevision } from "../util";
import { futureFeaturesEnabled } from "../account/dev_widget";
export const AdditionalMenu = (props: AccountMenuProps) => {
return <div className="nav-additional-menu">
@ -13,7 +14,7 @@ export const AdditionalMenu = (props: AccountMenuProps) => {
{t("Account Settings")}
</Link>
</div>
{localStorage.getItem("FUTURE_FEATURES") &&
{futureFeaturesEnabled() &&
<Link to="/app/help" onClick={props.close("accountMenuOpen")}>
<i className="fa fa-question-circle"></i>
{t("Help")}