🤞 Remove TightlyCoupledDropDown?

This commit is contained in:
Rick Carlino 2017-08-05 22:41:25 -05:00
parent 79198130cb
commit bc630124d9
7 changed files with 53 additions and 96 deletions

View file

@ -78,8 +78,7 @@ describe("<FarmEventForm/>", () => {
let i = instance(p);
i.forceUpdate();
expect(i.state.localCopyDirty).toBe(false);
let e = { value: "wow", executable_type: "Sequence" } as any;
i.executableSet(e);
i.executableSet({ value: "wow", label: "hey", headingId: "Sequence" });
i.forceUpdate();
expect(i.state.localCopyDirty).toBe(true);
expect(i.state.fe.executable_type).toEqual("Sequence");
@ -93,7 +92,7 @@ describe("<FarmEventForm/>", () => {
let exe = i.executableGet();
expect(exe.label).toBe("fake");
expect(exe.value).toBe(12);
expect(exe.executable_type).toBe("Sequence");
expect(exe.headingId).toBe("Sequence");
});
it("sets a subfield of state.fe", () => {

View file

@ -1,7 +1,13 @@
describe("utils", () => {
it("warns about missed tasks", () => {
pending("Write this next.");
// maybeWarnAboutMissedTasks
import { executableType } from "../../util";
describe("executableType", () => {
it("handles expected values", () => {
expect(executableType("Sequence")).toEqual("Sequence");
expect(executableType("Regimen")).toEqual("Regimen");
});
it("throws when given bad data", () => {
expect(() => executableType("Nope")).toThrowError();
});
});

View file

@ -11,8 +11,7 @@ import {
} from "../interfaces";
import {
formatTime,
formatDate,
TightlyCoupledFarmEventDropDown
formatDate
} from "./map_state_to_props_add_edit";
import {
BackArrow,
@ -35,6 +34,7 @@ import { maybeWarnAboutMissedTasks } from "./util";
import { TzWarning } from "./tz_warning";
import { FarmEventRepeatForm } from "./farm_event_repeat_form";
import { scheduleForFarmEvent } from "./calendar/scheduler";
import { executableType } from "../util";
type FormEvent = React.SyntheticEvent<HTMLInputElement>;
export const NEVER: TimeUnit = "never";
@ -83,7 +83,7 @@ export function recombine(vm: FarmEventViewModel): Partial<TaggedFarmEvent["body
export interface EditFEProps {
deviceTimezone: string | undefined;
executableOptions: TightlyCoupledFarmEventDropDown[];
executableOptions: DropDownItem[];
repeatOptions: DropDownItem[];
farmEvent: TaggedFarmEvent;
dispatch: Function;
@ -122,11 +122,11 @@ export class EditFEForm extends React.Component<EditFEProps, State> {
}
}
executableSet = (e: TightlyCoupledFarmEventDropDown) => {
executableSet = (e: DropDownItem) => {
if (e.value) {
this.setState(betterMerge(this.state, {
fe: {
executable_type: e.executable_type,
executable_type: executableType(e.headingId),
executable_id: (e.value || "").toString()
},
localCopyDirty: true
@ -134,13 +134,13 @@ export class EditFEForm extends React.Component<EditFEProps, State> {
}
}
executableGet = (): TightlyCoupledFarmEventDropDown => {
let executable_type: ExecutableType =
executableGet = (): DropDownItem => {
let headingId: ExecutableType =
(this.executable.kind === "sequences") ? "Sequence" : "Regimen";
return {
value: this.executable.body.id || 0,
label: this.executable.body.name,
executable_type
headingId
};
}

View file

@ -22,33 +22,7 @@ import {
TaggedSequence,
TaggedRegimen
} from "../../resources/tagged_resources";
// import { DropDownItem } from "../../ui/fb_select";
/**
* TODO: Not a fan of this one, but don't have time to refactor today.
* DropDownItem[]s should not know what a Regimen is. - RC Apr '17.
*
* PROBLEM: Drop down item had an id of '6'. But that `id` could have been for a
* "regimen" or a "sequence". There's no way to diferentiate as a user
* of <FBSelect/>
*
* Fast Solution: Tack extra information into DropDownItem. This results in
* us needing to do type casts and coupling DropDownItem to
* FarmEvent, which is unsafe and won't be totally obvious to
* new devs.
*
* Ideal solution: Add a `parentHeading: string;` field to DropDownItem. It
* would tell us which heading the DropDownItem came from. we
* could infer the `executable_type` based on the heading it
* was under. Then do a `groupBy` inside
* of <FBSelect/>
*/
export interface TightlyCoupledFarmEventDropDown {
label: string;
executable_type: ExecutableType;
value: number;
heading?: undefined | boolean;
}
import { DropDownItem } from "../../ui/fb_select";
export let formatTime = (input: string) => {
let iso = new Date(input).toISOString();
@ -105,43 +79,37 @@ export function mapStateToPropsAddEdit(props: Everything): AddEditFarmEventProps
}
};
let executableOptions: TightlyCoupledFarmEventDropDown[] = [];
let executableOptions: DropDownItem[] = [];
// Rick, we should talk about these. -CV 8/3/2017
executableOptions.push({
label: t("REGIMENS"),
heading: true,
value: 0,
executable_type: "Regimen"
headingId: "Regimen"
});
selectAllRegimens(props.resources.index).map(regimen => {
// TODO: Remove executable_type from obj since it's
// not declared in the interface.
if (regimen.kind === "regimens" && regimen.body.id) {
executableOptions.push({
label: regimen.body.name,
executable_type: "Regimen",
headingId: "Regimen",
value: regimen.body.id
});
}
});
// Rick, we should talk about these and make sure it's OK. -CV 8/3/2017
executableOptions.push({
label: t("SEQUENCES"),
heading: true,
value: 0,
executable_type: "Sequence"
headingId: "Sequence"
});
selectAllSequences(props.resources.index).map(sequence => {
// TODO: Remove executable_type from obj since it's
// not declared in the interface.
if (sequence.kind === "sequences" && sequence.body.id) {
executableOptions.push({
label: sequence.body.name,
executable_type: "Sequence",
headingId: "Sequence",
value: sequence.body.id
});
}
@ -159,28 +127,15 @@ export function mapStateToPropsAddEdit(props: Everything): AddEditFarmEventProps
* better. I think it would be better to handle this at the source and keep
* the ui logic less involved. -CV 8/3/2017
* -------------------------- BEGIN -------------------------------------*/
// let newExecutableOptions: DropDownItem[] = [
// { label: "Regimen", value: 0, children: [] },
// { label: "Sequence", value: 0, children: [] }
// ];
// executableOptions
// .filter(x => !x.heading)
// .map(x => {
// newExecutableOptions.map(container => {
// x.executable_type === container.label && container.children &&
// container.children.push(x);
// });
// });
let newExecutableOptions = executableOptions
.filter(x => !x.heading)
.map(x => {
return {
label: `${x.executable_type}: ${x.label}`,
label: `${x.headingId}: ${x.label}`,
value: x.value,
executable_type: (_.capitalize(x.executable_type) as ExecutableType)
headingId: _.capitalize(x.headingId)
};
});
/* -------------------------- END ---------------------------------------*/
let regimensById = indexRegimenById(props.resources.index);
let sequencesById = indexSequenceById(props.resources.index);

View file

@ -9,9 +9,6 @@ import {
TaggedPlantPointer,
TaggedCrop,
} from "../resources/tagged_resources";
import {
TightlyCoupledFarmEventDropDown
} from "./farm_events/map_state_to_props_add_edit";
import { PlantPointer } from "../interfaces";
import { SlotWithTool } from "../resources/interfaces";
import { BotState, BotPosition } from "../devices/interfaces";
@ -100,7 +97,7 @@ export type TaggedExecutable = TaggedSequence | TaggedRegimen;
export type ExecutableQuery = (kind: ExecutableType, id: number) => TaggedExecutable;
export interface AddEditFarmEventProps {
deviceTimezone: string | undefined;
executableOptions: TightlyCoupledFarmEventDropDown[];
executableOptions: DropDownItem[];
repeatOptions: DropDownItem[];
farmEvents: TaggedFarmEvent[];
regimensById: CowardlyDictionary<TaggedRegimen>;

View file

@ -3,6 +3,7 @@ import * as _ from "lodash";
import { OpenFarm, CropSearchResult } from "./openfarm";
import { DEFAULT_ICON } from "../open_farm/index";
import { HttpPromise } from "../util";
import { ExecutableType } from "./interfaces";
let url = (q: string) => `${OpenFarm.cropUrl}?include=pictures&filter=${q}`;
type X = HttpPromise<CropSearchResult>;
@ -27,3 +28,26 @@ export let OFSearch = (searchTerm: string) =>
dispatch({ type: "OF_SEARCH_RESULTS_OK", payload });
});
};
function isExecutableType(x?: string): x is ExecutableType {
const EXECUTABLES: ExecutableType[] = ["Sequence", "Regimen"];
return !!EXECUTABLES.includes(x as ExecutableType);
}
/** USE CASE: You have a `string?` type that you are *certain*
* is an `ExecutableType`. But the type checker is
* complaining.
*
* PROBLEM: `as ExecutableType` results in less type safety and
* makes bugs harder to pin point in production.
*
* SOLUTION: Run a user defined type guard (`x is ExecutableType`)
* and raise a runtime error with the offending string
*/
export function executableType(input?: string): ExecutableType {
if (isExecutableType(input)) {
return input;
} else {
throw new Error("Assumed string was ExecutableType. Got: " + input);
}
}

View file

@ -43,28 +43,4 @@ export class FBSelect extends React.Component<FBSelectProps, {}> {
</div>
);
}
// recurse(items: DropDownItem[]): React.ReactElement<any> {
// return items.map((item, index) => {
// if (item) {
// return (
// <MenuItem key={index} text={item.executable_type} />
// );
// }
// });
// }
// render() {
// console.log(this.props.list);
// return (
// <div className="filter-search">
// <Popover position={Position.BOTTOM_LEFT}>
// <button className="fb-button green">Select...</button>
// <Menu>
// {this.recurse(this.props.list)}
// </Menu>
// </Popover>
// </div>
// );
// }
}