dev stuff
This commit is contained in:
parent
0fdc454560
commit
d347413c04
|
@ -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
|
49
webpack/account/__tests__/dev_widget_test.tsx
Normal file
49
webpack/account/__tests__/dev_widget_test.tsx
Normal 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);
|
||||
});
|
||||
});
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
85
webpack/account/dev_widget.tsx
Normal file
85
webpack/account/dev_widget.tsx
Normal 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>;
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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?")}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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")}
|
||||
|
|
Loading…
Reference in a new issue