Merge branch 'staging' of github.com:FarmBot/Farmbot-Web-App into time_format_24_hour

pull/1154/head
Rick Carlino 2019-04-13 19:52:58 -07:00
commit e2ae388b6d
24 changed files with 163 additions and 61 deletions

View File

@ -24,6 +24,7 @@ import {
TaggedToolSlotPointer,
TaggedFarmwareEnv,
TaggedFarmwareInstallation,
TaggedEnigma,
} from "farmbot";
import { fakeResource } from "../fake_resource";
import { ExecutableType, PinBindingType } from "farmbot/dist/resources/api_resources";
@ -307,7 +308,8 @@ export function fakeWebAppConfig(): TaggedWebAppConfig {
home_button_homing: false,
show_motor_plot: false,
show_historic_points: false,
time_format_24_hour: false
time_format_24_hour: false,
show_pins: false,
});
}
@ -423,3 +425,12 @@ export function fakeFarmwareInstallation(): TaggedFarmwareInstallation {
package_error: undefined,
});
}
export function fakeEnigma(): TaggedEnigma {
return fakeResource("Enigma", {
uuid: "uuid",
created_at: 123,
problem_tag: "api.noun.verb",
priority: 100,
});
}

View File

@ -364,6 +364,7 @@ const KIND_PRIORITY: ResourceLookupTable = {
Point: 1,
Sensor: 1,
Tool: 1,
Enigma: 1,
SensorReading: 2,
Sequence: 2,
Regimen: 3,

View File

@ -152,5 +152,7 @@ export class API {
get farmwareInstallationPath() {
return `${this.baseUrl}/api/farmware_installations/`;
}
/** /api/enigmas/:id */
get enigmaPath() { return `${this.baseUrl}/api/enigmas/`; }
get syncPatch() { return `${this.baseUrl}/api/device/sync/`; }
}

View File

@ -261,6 +261,7 @@ export function urlFor(tag: ResourceName) {
PlantTemplate: API.current.plantTemplatePath,
FarmwareEnv: API.current.farmwareEnvPath,
FarmwareInstallation: API.current.farmwareInstallationPath,
Enigma: API.current.enigmaPath,
};
const url = OPTIONS[tag];
if (url) {

View File

@ -381,6 +381,13 @@ export namespace Content {
trim(`Export request received. Please allow up to 10 minutes for
delivery.`);
export const SEED_DATA_SELECTION =
trim(`To finish setting up your account and FarmBot, please select which
FarmBot you have. Once you make a selection, we'll automatically add some
tools, sensors, peripherals, sequences, and more to get you up and running
faster. If you want to start completely from scratch, feel free to select
"Custom bot" and we won't change a thing.`);
// App Settings
export const CONFIRM_STEP_DELETION =
trim(`Show a confirmation dialog when the sequence delete step

View File

@ -33,3 +33,11 @@
transform: translateX(0)
}
}
.controls-page {
@media (min-width:992px) {
.col-md-offset-1 {
margin-left: 5%;
}
}
}

View File

@ -891,6 +891,12 @@ ul {
.bp3-popover-target {
float: right;
}
@media screen and (max-width: 1075px) {
padding-left: 15px !important;
}
@media screen and (max-width: 974px) {
padding-left: 0 !important;
}
}
.logs-settings-menu {

View File

@ -13,6 +13,13 @@ const FIRMWARE_MISSING_ALERT: Alert = {
uuid: "uuid",
};
const SEED_DATA_MISSING_ALERT: Alert = {
created_at: 123,
problem_tag: "api.seed_data.missing",
priority: 300,
uuid: "uuid",
};
const UNKNOWN_ALERT: Alert = {
created_at: 123,
problem_tag: "farmbot_os.firmware.alert",
@ -41,10 +48,11 @@ describe("<Alerts />", () => {
it("renders alerts", () => {
const p = fakeProps();
p.alerts = [FIRMWARE_MISSING_ALERT];
p.alerts = [FIRMWARE_MISSING_ALERT, SEED_DATA_MISSING_ALERT];
const wrapper = mount(<Alerts {...p} />);
expect(wrapper.text()).toContain("1");
expect(wrapper.text()).toContain("2");
expect(wrapper.text()).toContain("Your device has no firmware installed");
expect(wrapper.text()).toContain("Choose your FarmBot");
});
it("renders unknown alert", () => {

View File

@ -1,9 +1,18 @@
let mockDev = false;
jest.mock("../../account/dev/dev_support", () => ({
DevSettings: {
futureFeaturesEnabled: () => mockDev,
}
}));
import { mapStateToProps } from "../state_to_props";
import { fakeState } from "../../__test_support__/fake_state";
import { buildResourceIndex } from "../../__test_support__/resource_index_builder";
import { TaggedLog } from "farmbot";
import { times } from "lodash";
import { fakeFbosConfig, fakeLog } from "../../__test_support__/fake_state/resources";
import {
fakeFbosConfig, fakeLog, fakeEnigma
} from "../../__test_support__/fake_state/resources";
describe("mapStateToProps()", () => {
function fakeLogs(count: number): TaggedLog[] {
@ -45,9 +54,25 @@ describe("mapStateToProps()", () => {
it("handles undefined", () => {
const state = fakeState();
// tslint:disable-next-line:no-any
state.bot.hardware.enigmas = undefined as any;
state.bot.hardware.enigmas = undefined;
const props = mapStateToProps(state);
expect(props.alerts).toEqual([]);
});
it("doesn't show API alerts", () => {
const state = fakeState();
state.resources = buildResourceIndex([fakeEnigma()]);
mockDev = false;
const props = mapStateToProps(state);
expect(props.alerts).toEqual([]);
});
it("shows API alerts", () => {
const state = fakeState();
const enigma = fakeEnigma();
state.resources = buildResourceIndex([enigma]);
mockDev = true;
const props = mapStateToProps(state);
expect(props.alerts).toEqual([enigma.body]);
});
});

View File

@ -9,6 +9,7 @@ import { formatLogTime } from "./index";
import { TimeSettings } from "../interfaces";
import { Enigma } from "farmbot";
import { sortBy } from "lodash";
import { Content } from "../constants";
export interface AlertsProps {
alerts: Alert[];
@ -71,11 +72,12 @@ export const FirmwareAlerts = (props: FirmwareAlertsProps) => {
const firmwareAlerts = sortAlerts(alerts)
.filter(x => splitTag(x.problem_tag).noun === "firmware");
return <div className="firmware-alerts">
{firmwareAlerts.map((x, i) =>
<AlertCard key={i}
alert={x}
apiFirmwareValue={props.apiFirmwareValue}
timeSettings={props.timeSettings} />)}
{firmwareAlerts.filter(x => x.problem_tag && x.priority && x.created_at)
.map((x, i) =>
<AlertCard key={i}
alert={x}
apiFirmwareValue={props.apiFirmwareValue}
timeSettings={props.timeSettings} />)}
</div>;
};
@ -94,6 +96,10 @@ const AlertCard = (props: AlertCardProps) => {
createdAt={props.alert.created_at}
apiFirmwareValue={props.apiFirmwareValue}
timeSettings={props.timeSettings} />;
case "api.seed_data.missing":
return <SeedDataMissing
createdAt={props.alert.created_at}
timeSettings={props.timeSettings} />;
default:
return UnknownAlert(props.alert, props.timeSettings);
}
@ -118,12 +124,15 @@ const UnknownAlert = (alert: Alert, timeSettings: TimeSettings) => {
</div>;
};
interface FirmwareMissingProps {
interface CommonAlertCardProps {
createdAt: number;
apiFirmwareValue: string | undefined;
timeSettings: TimeSettings;
}
interface FirmwareMissingProps extends CommonAlertCardProps {
apiFirmwareValue: string | undefined;
}
const FirmwareMissing = (props: FirmwareMissingProps) =>
<div className="problem-alert firmware-missing-alert">
<div className="problem-alert-title">
@ -138,3 +147,15 @@ const FirmwareMissing = (props: FirmwareMissingProps) =>
botOnline={true} />
</div>
</div>;
const SeedDataMissing = (props: CommonAlertCardProps) =>
<div className="problem-alert seed-data-missing-alert">
<div className="problem-alert-title">
<i className="fa fa-exclamation-triangle" />
<h3>{t("Choose your FarmBot")}</h3>
<p>{formatLogTime(props.createdAt, props.timeSettings)}</p>
</div>
<div className="problem-alert-content">
<p>{Content.SEED_DATA_SELECTION}</p>
</div>
</div>;

View File

@ -1,5 +1,7 @@
import { Everything } from "../interfaces";
import { selectAllLogs, maybeGetTimeSettings } from "../resources/selectors";
import {
selectAllLogs, maybeGetTimeSettings, selectAllEnigmas
} from "../resources/selectors";
import { LogsProps } from "./interfaces";
import {
sourceFbosConfigValue
@ -11,6 +13,7 @@ import { getWebAppConfigValue } from "../config_storage/actions";
import { getFbosConfig } from "../resources/getters";
import { chain } from "lodash";
import { isFwHardwareValue } from "../devices/components/fbos_settings/board_type";
import { DevSettings } from "../account/dev/dev_support";
/** Take the specified number of logs after sorting by time created. */
export function takeSortedLogs(
@ -28,13 +31,17 @@ export function mapStateToProps(props: Everything): LogsProps {
const sourceFbosConfig =
sourceFbosConfigValue(fbosConfig, hardware.configuration);
const apiFirmwareValue = sourceFbosConfig("firmware_hardware").value;
const botAlerts = betterCompact(Object.values(props.bot.hardware.enigmas || {}));
const apiAlerts = selectAllEnigmas(props.resources.index).map(x => x.body);
const alerts =
botAlerts.concat(DevSettings.futureFeaturesEnabled() ? apiAlerts : []);
return {
dispatch: props.dispatch,
sourceFbosConfig,
logs: takeSortedLogs(250, props.resources.index),
timeSettings: maybeGetTimeSettings(props.resources.index),
getConfigValue: getWebAppConfigValue(() => props),
alerts: betterCompact(Object.values(props.bot.hardware.enigmas || {})),
alerts,
apiFirmwareValue: isFwHardwareValue(apiFirmwareValue)
? apiFirmwareValue : undefined,
};

View File

@ -57,7 +57,8 @@ export const emptyState = (): RestResources => {
PinBinding: {},
PlantTemplate: {},
SavedGarden: {},
DiagnosticDump: {}
DiagnosticDump: {},
Enigma: {},
},
byKindAndId: {},
references: {},

View File

@ -20,6 +20,7 @@ import {
TaggedPlantTemplate,
TaggedFarmwareEnv,
TaggedFarmwareInstallation,
TaggedEnigma,
} from "farmbot";
import {
isTaggedResource,
@ -99,6 +100,8 @@ export const selectAllWebcamFeeds =
(i: ResourceIndex) => findAll<TaggedWebcamFeed>(i, "WebcamFeed");
export const selectAllSavedPeripherals =
(input: ResourceIndex) => selectAllPeripherals(input).filter(isSaved);
export const selectAllEnigmas =
(i: ResourceIndex) => findAll<TaggedEnigma>(i, "Enigma");
export const findByKindAndId = <T extends TaggedResource>(
i: ResourceIndex, kind: T["kind"], id: number | undefined): T => {

View File

@ -71,9 +71,8 @@ describe("<SequenceEditorMiddleActive/>", () => {
farmwareConfigs: {},
},
shouldDisplay: jest.fn(),
confirmStepDeletion: false,
getWebAppConfigValue: jest.fn(),
menuOpen: false,
showPins: true,
};
};
@ -254,13 +253,12 @@ describe("<SequenceSettingsMenu />", () => {
it("renders settings", () => {
const wrapper = mount(<SequenceSettingsMenu
dispatch={jest.fn()}
confirmStepDeletion={false}
showPins={false} />);
getWebAppConfigValue={jest.fn()} />);
wrapper.find("button").first().simulate("click");
expect(setWebAppConfigValue).toHaveBeenCalledWith(
BooleanSetting.confirm_step_deletion, true);
wrapper.find("button").last().simulate("click");
expect(setWebAppConfigValue).toHaveBeenCalledWith(
"show_pins", true);
BooleanSetting.show_pins, true);
});
});

View File

@ -25,9 +25,8 @@ describe("<SequenceEditorMiddle/>", () => {
farmwareConfigs: {},
},
shouldDisplay: jest.fn(),
confirmStepDeletion: false,
getWebAppConfigValue: jest.fn(),
menuOpen: false,
showPins: true,
};
}

View File

@ -34,10 +34,9 @@ describe("<Sequences/>", () => {
farmwareConfigs: {},
},
shouldDisplay: jest.fn(),
confirmStepDeletion: false,
getWebAppConfigValue: jest.fn(),
menuOpen: false,
stepIndex: undefined,
showPins: true,
});
it("renders", () => {

View File

@ -14,6 +14,7 @@ import { StepMoveDataXfer, StepSpliceDataXfer } from "../draggable/interfaces";
import { TaggedSequence } from "farmbot";
import { ResourceIndex, VariableNameSet, UUID } from "../resources/interfaces";
import { ShouldDisplay } from "../devices/interfaces";
import { GetWebAppConfigValue } from "../config_storage/actions";
export interface HardwareFlags {
findHomeEnabled: Record<Xyz, boolean>;
@ -44,10 +45,9 @@ export interface Props {
hardwareFlags: HardwareFlags;
farmwareInfo: FarmwareInfo;
shouldDisplay: ShouldDisplay;
confirmStepDeletion: boolean;
getWebAppConfigValue: GetWebAppConfigValue;
menuOpen: boolean;
stepIndex: number | undefined;
showPins: boolean;
}
export interface SequenceEditorMiddleProps {
@ -58,9 +58,8 @@ export interface SequenceEditorMiddleProps {
hardwareFlags: HardwareFlags;
farmwareInfo: FarmwareInfo;
shouldDisplay: ShouldDisplay;
confirmStepDeletion: boolean;
getWebAppConfigValue: GetWebAppConfigValue;
menuOpen: boolean;
showPins: boolean;
}
export interface ActiveMiddleProps extends SequenceEditorMiddleProps {
@ -76,8 +75,7 @@ export interface SequenceHeaderProps {
menuOpen: boolean;
variablesCollapsed: boolean;
toggleVarShow: () => void;
confirmStepDeletion: boolean;
showPins: boolean;
getWebAppConfigValue: GetWebAppConfigValue;
}
export type ChannelName = ALLOWED_CHANNEL_NAMES;

View File

@ -25,8 +25,7 @@ export class SequenceEditorMiddle
hardwareFlags={this.props.hardwareFlags}
farmwareInfo={this.props.farmwareInfo}
shouldDisplay={this.props.shouldDisplay}
confirmStepDeletion={this.props.confirmStepDeletion}
showPins={this.props.showPins}
getWebAppConfigValue={this.props.getWebAppConfigValue}
menuOpen={this.props.menuOpen} />}
</EmptyStateWrapper>;
}

View File

@ -22,9 +22,8 @@ import { Actions } from "../constants";
import { Popover, Position } from "@blueprintjs/core";
import { ToggleButton } from "../controls/toggle_button";
import { Content } from "../constants";
import { setWebAppConfigValue } from "../config_storage/actions";
import { setWebAppConfigValue, GetWebAppConfigValue } from "../config_storage/actions";
import { BooleanSetting } from "../session_keys";
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
export const onDrop =
(dispatch1: Function, sequence: TaggedSequence) =>
@ -49,13 +48,15 @@ export const onDrop =
export interface SequenceSettingsMenuProps {
dispatch: Function;
confirmStepDeletion: boolean;
showPins: boolean;
getWebAppConfigValue: GetWebAppConfigValue;
}
export const SequenceSettingsMenu =
({ dispatch, confirmStepDeletion, showPins }: SequenceSettingsMenuProps) =>
<div className="sequence-settings-menu">
({ dispatch, getWebAppConfigValue }: SequenceSettingsMenuProps) => {
const confirmStepDeletion =
!!getWebAppConfigValue(BooleanSetting.confirm_step_deletion);
const showPins = !!getWebAppConfigValue(BooleanSetting.show_pins);
return <div className="sequence-settings-menu">
<fieldset>
<label>
{t("Confirm step deletion")}
@ -74,9 +75,10 @@ export const SequenceSettingsMenu =
<ToggleButton
toggleValue={showPins}
toggleAction={() => dispatch(setWebAppConfigValue(
"show_pins" as BooleanConfigKey, !showPins))} />
BooleanSetting.show_pins, !showPins))} />
</fieldset>
</div>;
};
interface SequenceBtnGroupProps {
dispatch: Function;
@ -85,13 +87,12 @@ interface SequenceBtnGroupProps {
resources: ResourceIndex;
shouldDisplay: ShouldDisplay;
menuOpen: boolean;
confirmStepDeletion: boolean;
showPins: boolean;
getWebAppConfigValue: GetWebAppConfigValue;
}
const SequenceBtnGroup = ({
dispatch, sequence, syncStatus, resources, shouldDisplay, menuOpen,
confirmStepDeletion, showPins
getWebAppConfigValue
}: SequenceBtnGroupProps) =>
<div className="button-group">
<SaveBtn status={sequence.specialStatus}
@ -119,8 +120,7 @@ const SequenceBtnGroup = ({
<i className="fa fa-gear" />
<SequenceSettingsMenu
dispatch={dispatch}
showPins={showPins}
confirmStepDeletion={confirmStepDeletion} />
getWebAppConfigValue={getWebAppConfigValue} />
</Popover>
</div>
</div>;
@ -155,8 +155,7 @@ const SequenceHeader = (props: SequenceHeaderProps) => {
syncStatus={props.syncStatus}
resources={props.resources}
shouldDisplay={props.shouldDisplay}
confirmStepDeletion={props.confirmStepDeletion}
showPins={props.showPins}
getWebAppConfigValue={props.getWebAppConfigValue}
menuOpen={props.menuOpen} />
<SequenceNameAndColor {...sequenceAndDispatch} />
<LocalsList
@ -194,6 +193,21 @@ export class SequenceEditorMiddleActive extends
return `calc(100vh - ${subHeight}px)`;
}
get stepProps() {
const getConfig = this.props.getWebAppConfigValue;
return {
sequence: this.props.sequence,
onDrop: onDrop(this.props.dispatch, this.props.sequence),
dispatch: this.props.dispatch,
resources: this.props.resources,
hardwareFlags: this.props.hardwareFlags,
farmwareInfo: this.props.farmwareInfo,
shouldDisplay: this.props.shouldDisplay,
confirmStepDeletion: !!getConfig(BooleanSetting.confirm_step_deletion),
showPins: !!getConfig(BooleanSetting.confirm_step_deletion),
};
}
render() {
const { dispatch, sequence } = this.props;
return <div className="sequence-editor-content">
@ -206,13 +220,12 @@ export class SequenceEditorMiddleActive extends
variablesCollapsed={this.state.variablesCollapsed}
toggleVarShow={() =>
this.setState({ variablesCollapsed: !this.state.variablesCollapsed })}
confirmStepDeletion={this.props.confirmStepDeletion}
showPins={this.props.showPins}
getWebAppConfigValue={this.props.getWebAppConfigValue}
menuOpen={this.props.menuOpen} />
<hr />
<div className="sequence" id="sequenceDiv"
style={{ height: this.stepSectionHeight }}>
<AllSteps onDrop={onDrop(dispatch, sequence)} {...this.props} />
<AllSteps {...this.stepProps} />
<Row>
<Col xs={12}>
<DropArea isLocked={true}

View File

@ -69,8 +69,7 @@ export class Sequences extends React.Component<Props, {}> {
hardwareFlags={this.props.hardwareFlags}
farmwareInfo={this.props.farmwareInfo}
shouldDisplay={this.props.shouldDisplay}
confirmStepDeletion={this.props.confirmStepDeletion}
showPins={this.props.showPins}
getWebAppConfigValue={this.props.getWebAppConfigValue}
menuOpen={this.props.menuOpen} />
</CenterPanel>
<RightPanel

View File

@ -15,7 +15,6 @@ import { getFirmwareConfig } from "../resources/getters";
import { Farmwares } from "../farmware/interfaces";
import { manifestInfo } from "../farmware/generate_manifest_info";
import { DevSettings } from "../account/dev/dev_support";
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
export function mapStateToProps(props: Everything): Props {
const uuid = props.resources.consumers.sequences.current;
@ -70,10 +69,6 @@ export function mapStateToProps(props: Everything): Props {
const installedOsVersion = determineInstalledOsVersion(
props.bot, maybeGetDevice(props.resources.index));
const confirmStepDeletion = !!getConfig(BooleanSetting.confirm_step_deletion);
const showPins = !!getConfig("show_pins" as BooleanConfigKey);
const fbosVersionOverride = DevSettings.overriddenFbosVersion();
const shouldDisplay = shouldDisplayFunc(
installedOsVersion, props.bot.minOsFeatureData, fbosVersionOverride);
@ -96,9 +91,8 @@ export function mapStateToProps(props: Everything): Props {
farmwareConfigs,
},
shouldDisplay,
confirmStepDeletion,
getWebAppConfigValue: getConfig,
menuOpen: props.resources.consumers.sequences.menuOpen,
stepIndex: props.resources.consumers.sequences.stepIndex,
showPins,
};
}

View File

@ -21,6 +21,7 @@ export const BooleanSetting: Record<BooleanConfigKey, BooleanConfigKey> = {
show_motor_plot: "show_motor_plot",
show_historic_points: "show_historic_points",
time_format_24_hour: "time_format_24_hour",
show_pins: "show_pins",
/** "Labs" feature names. (App preferences) */
stub_config: "stub_config",

View File

@ -75,7 +75,8 @@ export async function fetchSyncData(dispatch: Function) {
get("Peripheral", API.current.peripheralsPath),
get("Point", API.current.allPointsPath),
get("Sensor", API.current.sensorPath),
get("Tool", API.current.toolsPath)
get("Tool", API.current.toolsPath),
get("Enigma", API.current.enigmaPath),
]),
2: () => Promise.all<{}>([
get("SensorReading", API.current.sensorReadingPath),

View File

@ -43,7 +43,7 @@
"coveralls": "3.0.3",
"enzyme": "3.9.0",
"enzyme-adapter-react-16": "1.12.1",
"farmbot": "7.0.2",
"farmbot": "7.0.4-rc1",
"farmbot-toastr": "1.0.3",
"i18next": "15.0.9",
"jest": "24.7.1",