Farmbot-Web-App/src/resources/selectors.ts

512 lines
15 KiB
TypeScript

import * as _ from "lodash";
import { error } from "farmbot-toastr";
import { ResourceIndex, SlotWithTool } from "./interfaces";
import { joinKindAndId } from "./reducer";
import {
isTaggedFarmEvent,
isTaggedPlantPointer,
isTaggedGenericPointer,
isTaggedRegimen,
isTaggedResource,
isTaggedSequence,
isTaggedTool,
isTaggedToolSlotPointer,
ResourceName,
sanityCheck,
TaggedCrop,
TaggedFarmEvent,
TaggedGenericPointer,
TaggedImage,
TaggedLog,
TaggedPeripheral,
TaggedPlantPointer,
TaggedRegimen,
TaggedResource,
TaggedSequence,
TaggedTool,
TaggedToolSlotPointer,
TaggedUser
} from "./tagged_resources";
import { CowardlyDictionary, betterCompact, sortResourcesById } from "../util";
type StringMap = CowardlyDictionary<string>;
export let findId = (index: ResourceIndex, kind: ResourceName, id: number) => {
let uuid = index.byKindAndId[joinKindAndId(kind, id)];
assertUuid(kind, uuid);
if (uuid) {
return uuid;
} else {
throw new Error("UUID not found for id " + id);
}
};
export function findResourceById(index: ResourceIndex, kind: ResourceName,
id: number) {
return findId(index, kind, id);
}
export let isKind = (name: ResourceName) => (tr: TaggedResource) => tr.kind === name;
function findAll(index: ResourceIndex, name: ResourceName) {
let results: TaggedResource[] = [];
index.byKind[name].map(function (uuid) {
let item = index.references[uuid];
(item && isTaggedResource(item) && results.push(item));
});
return sortResourcesById(results);
}
export function selectAllFarmEvents(index: ResourceIndex) {
return findAll(index, "farm_events") as TaggedFarmEvent[];
}
export function selectAllPoints(index: ResourceIndex) {
return findAll(index, "points") as
(TaggedGenericPointer | TaggedPlantPointer | TaggedToolSlotPointer)[];
}
export function groupPointsByType(index: ResourceIndex) {
return _(selectAllPoints(index))
// If this fiails to compile....
.tap(x => x[0].body.pointer_type)
// ... this line must be updated:
.groupBy("body.pointer_type")
.value();
}
export function findPointerByTypeAndId(index: ResourceIndex,
type_: string,
id: number) {
let p = selectAllPoints(index)
.filter(({ body }) => (body.id === id) && (body.pointer_type === type_))[0];
if (p) {
return p;
} else {
// We might have a sequence dependency leak if this exception is ever
// thrown.
throw new Error(`Tried to fetch bad point ${type_} ${id}`);
}
}
export function selectAllGenericPointers(index: ResourceIndex):
TaggedGenericPointer[] {
let genericPointers = selectAllPoints(index)
.map(p => (isTaggedGenericPointer(p)) ? p : undefined);
return betterCompact(genericPointers);
}
export function selectAllPlantPointers(index: ResourceIndex): TaggedPlantPointer[] {
let genericPointers = selectAllPoints(index)
.map(p => (isTaggedPlantPointer(p)) ? p : undefined);
return betterCompact(genericPointers);
}
export function selectAllToolSlotPointers(index: ResourceIndex):
TaggedToolSlotPointer[] {
let genericPointers = selectAllPoints(index)
.map(p => (isTaggedToolSlotPointer(p)) ? p : undefined);
return betterCompact(genericPointers);
}
export function selectAllTools(index: ResourceIndex) {
return findAll(index, "tools") as TaggedTool[];
}
export function selectAllPeripherals(index: ResourceIndex) {
return findAll(index, "peripherals") as TaggedPeripheral[];
}
export function selectAllLogs(index: ResourceIndex) {
return findAll(index, "logs") as TaggedLog[];
}
interface Finder<T> {
(i: ResourceIndex, u: string): T;
}
/** Generalized way to stamp out "finder" functions.
* Pass in a `ResourceName` and it will add all the relevant checks.
* WARNING: WILL THROW ERRORS IF RESOURCE NOT FOUND!
*/
let find = (r: ResourceName) =>
function findResource(i: ResourceIndex, u: string) {
assertUuid(r, u);
let result = i.references[u];
if (result && isTaggedResource(result) && sanityCheck(result)) {
return result as TaggedResource;
} else {
error("Resource error");
throw new Error(`Tagged resource ${r} was not found or malformed: ` +
JSON.stringify(result));
}
};
export function findToolSlot(i: ResourceIndex, uuid: string): TaggedToolSlotPointer {
let ts = selectAllToolSlotPointers(i).filter(x => x.uuid === uuid)[0];
if (ts) {
return ts;
} else {
throw new Error("ToolSlotPointer not found: " + uuid);
}
}
export let findTool = find("tools") as Finder<TaggedTool>;
export let findSequence = find("sequences") as Finder<TaggedSequence>;
export let findRegimen = find("regimens") as Finder<TaggedRegimen>;
export let findFarmEvent = find("farm_events") as Finder<TaggedFarmEvent>;
export let findPoints = find("points") as Finder<TaggedPlantPointer>;
export function findPlant(i: ResourceIndex, uuid: string):
TaggedPlantPointer {
let point = findPoints(i, uuid);
if (point && sanityCheck(point) && point.body.pointer_type === "Plant") {
return point;
} else {
throw new Error("That is not a true plant pointer");
}
}
export function selectCurrentToolSlot(index: ResourceIndex, uuid: string) {
let x = index.references[uuid];
if (x && isTaggedToolSlotPointer(x)) {
return x;
} else {
throw new Error("selectCurrentToolSlot: Not a tool slot: ");
}
}
export function selectAllImages(index: ResourceIndex) {
return findAll(index, "images") as TaggedImage[];
}
export function selectAllRegimens(index: ResourceIndex) {
return findAll(index, "regimens") as TaggedRegimen[];
}
export function selectAllCrops(index: ResourceIndex) {
return findAll(index, "crops") as TaggedCrop[];
}
export function getRegimenByUUID(index: ResourceIndex, uuid: string) {
assertUuid("regimens", uuid);
return index.references[uuid];
}
export function getSequenceByUUID(index: ResourceIndex,
uuid: string): TaggedSequence {
assertUuid("sequences", uuid);
let result = index.references[uuid];
if (result && isTaggedSequence(result)) {
return result;
} else {
throw new Error("BAD Sequence UUID;");
}
}
export function selectAllSequences(index: ResourceIndex) {
return findAll(index, "sequences") as TaggedSequence[];
}
export function indexSequenceById(index: ResourceIndex) {
let output: CowardlyDictionary<TaggedSequence> = {};
let uuids = index.byKind.sequences;
uuids.map(uuid => {
assertUuid("sequences", uuid);
let sequence = index.references[uuid];
if (sequence && isTaggedSequence(sequence) && sequence.body.id) {
output[sequence.body.id] = sequence;
}
});
return output;
}
export function indexRegimenById(index: ResourceIndex) {
let output: CowardlyDictionary<TaggedRegimen> = {};
let uuids = index.byKind.regimens;
uuids.map(uuid => {
assertUuid("regimens", uuid);
let regimen = index.references[uuid];
if (regimen && isTaggedRegimen(regimen) && regimen.body.id) {
output[regimen.body.id] = regimen;
}
});
return output;
}
export function indexFarmEventById(index: ResourceIndex) {
let output: CowardlyDictionary<TaggedFarmEvent> = {};
let uuids = index.byKind.farm_events;
uuids.map(uuid => {
assertUuid("farm_events", uuid);
let farmEvent = index.references[uuid];
if (farmEvent && isTaggedFarmEvent(farmEvent) && farmEvent.body.id) {
output[farmEvent.body.id] = farmEvent;
}
});
return output;
}
export function indexByToolId(index: ResourceIndex) {
let output: CowardlyDictionary<TaggedTool> = {};
let uuids = index.byKind.tools;
uuids.map(uuid => {
assertUuid("tools", uuid);
let Tool = index.references[uuid];
if (Tool && isTaggedTool(Tool) && Tool.body.id) {
output[Tool.body.id] = Tool;
}
});
return output;
}
export function indexBySlotId(index: ResourceIndex) {
let output: CowardlyDictionary<TaggedToolSlotPointer> = {};
let uuids = index.byKind.points;
uuids.map(uuid => {
assertUuid("points", uuid);
let tool_slot = index.references[uuid];
if (tool_slot && isTaggedToolSlotPointer(tool_slot) && tool_slot.body.id) {
output[tool_slot.body.id] = tool_slot;
}
});
return output;
}
export function assertUuid(expected: ResourceName, actual: string | undefined) {
if (actual && !actual.startsWith(expected)) {
console.warn(`
BAD NEWS!!! You thought this was a ${expected} UUID, but here's what it
actually was:
${actual}
`);
return false;
} else {
return true;
}
}
export function toArray(index: ResourceIndex) {
return index.all.map(function (uuid) {
let tr = index.references[uuid];
if (tr) {
return tr;
} else {
throw new Error("Fund bad index UUID: " + uuid);
}
});
}
/** GIVEN: a slot UUID.
* FINDS: Tool in that slot (if any) */
export let currentToolInSlot = (index: ResourceIndex) =>
(toolSlotUUID: string): TaggedTool | undefined => {
let currentSlot = selectCurrentToolSlot(index, toolSlotUUID);
if (currentSlot
&& currentSlot.kind === "points") {
let toolUUID = index
.byKindAndId[joinKindAndId("tools", currentSlot.body.tool_id)];
let tool = index.references[toolUUID || "NOPE!"];
if (tool && isTaggedTool(tool)) {
return tool;
}
}
};
/** FINDS: all tagged resources with particular ID */
export function findAllById(i: ResourceIndex, ids: number[], k: ResourceName) {
let output: TaggedResource[] = [];
findAll(i, k).map(x => x.kind === k ? output.push(x) : "");
return output;
}
/** FINDS: All tools that are in use. */
export function toolsInUse(index: ResourceIndex): TaggedTool[] {
let ids = betterCompact(selectAllToolSlotPointers(index).map(ts => ts.body.tool_id));
return findAllById(index, ids, "tools") as TaggedTool[];
}
export let byId = <T extends TaggedResource>(name: ResourceName) =>
(index: ResourceIndex, id: number): T | undefined => {
let tools = findAll(index, name);
let f = (x: TaggedResource) => (x.kind === name) && (x.body.id === id);
// Maybe we should add a throw here?
return tools.filter(f)[0] as T | undefined;
};
export function hasId(ri: ResourceIndex, k: ResourceName, id: number): boolean {
return !!ri.byKindAndId[joinKindAndId(k, id)];
}
export let findFarmEventById = (ri: ResourceIndex, fe_id: number) => {
let fe = byId("farm_events")(ri, fe_id);
if (fe && isTaggedFarmEvent(fe) && sanityCheck(fe)) {
return fe;
} else {
let e = new Error(`Bad farm_event id: ${fe_id}`);
throw e;
}
};
export let maybeFindToolById = (ri: ResourceIndex, tool_id?: number):
TaggedTool | undefined => {
let tool = tool_id && byId("tools")(ri, tool_id);
if (tool && isTaggedTool(tool) && sanityCheck(tool)) {
return tool;
} else {
return undefined;
}
};
export let findToolById = (ri: ResourceIndex, tool_id: number) => {
let tool = maybeFindToolById(ri, tool_id);
if (tool) {
return tool;
} else {
throw new Error("Bad tool id: " + tool_id);
}
};
export let findSequenceById = (ri: ResourceIndex, sequence_id: number) => {
let sequence = byId("sequences")(ri, sequence_id);
if (sequence && isTaggedSequence(sequence) && sanityCheck(sequence)) {
return sequence;
} else {
throw new Error("Bad sequence id: " + sequence_id);
}
};
export let findRegimenById = (ri: ResourceIndex, regimen_id: number) => {
let regimen = byId("regimens")(ri, regimen_id);
if (regimen && isTaggedRegimen(regimen) && sanityCheck(regimen)) {
return regimen;
} else {
throw new Error("Bad regimen id: " + regimen_id);
}
};
export let findSlotById = byId<TaggedToolSlotPointer>("points");
/** Find a Tool's corresponding Slot. */
export let findSlotByToolId = (index: ResourceIndex, tool_id: number) => {
let tool = findToolById(index, tool_id);
let query: any = { body: { tool_id: tool.body.id } };
let every = Object
.keys(index.references)
.map(x => index.references[x]);
let tts = _.find(every, query);
if (tts && isTaggedToolSlotPointer(tts) && sanityCheck(tts)) {
return tts;
} else {
return undefined;
}
};
export function maybeGetSequence(index: ResourceIndex,
uuid: string | undefined): TaggedSequence | undefined {
if (uuid) {
return getSequenceByUUID(index, uuid);
} else {
return undefined;
}
}
export function maybeGetRegimen(index: ResourceIndex,
uuid: string | undefined): TaggedRegimen | undefined {
let tr = uuid && getRegimenByUUID(index, uuid);
if (tr && isTaggedRegimen(tr)) { return tr; }
}
/** Unlike other findById methods, this one allows undefined (missed) values */
export function maybeFindPlantById(index: ResourceIndex, id: number) {
let uuid = index.byKindAndId[joinKindAndId("points", id)];
let resource = index.references[uuid || "nope"];
if (resource && isTaggedPlantPointer(resource)) { return resource; }
}
export function getDeviceAccountSettings(index: ResourceIndex) {
let list = index.byKind.device;
let uuid = list[0];
let device = index.references[uuid || -1];
if ((list.length === 1) && device && device.kind === "device") {
sanityCheck(device);
return device;
} else {
throw new Error(`
PROBLEM: Expected getDeviceAccountSettings() to return exactly 1 device.
We got some other number back, indicating a hazardous condition.`);
}
}
export function maybeFetchUser(index: ResourceIndex):
TaggedUser | undefined {
let list = index.byKind.users;
let uuid = list[0];
let user = index.references[uuid || -1];
if (user && sanityCheck(user) && list.length > 1) {
throw new Error("Index is broke. Expected exactly 1 user.");
}
if ((list.length === 1) && user && user.kind === "users") {
return user;
} else {
return undefined;
}
}
export function getUserAccountSettings(index: ResourceIndex): TaggedUser {
let user = maybeFetchUser(index);
if (user) {
return user;
} else {
throw new Error(`PROBLEM: Expected getUserAccountSettings() to return
exactly 1 device. We got some other number back, indicating a hazardous
condition.`);
}
}
export function all(index: ResourceIndex) {
return betterCompact(index.all.map(uuid => index.references[uuid]));
}
/** For those times that you need to ref a tool and slot together. */
export function joinToolsAndSlot(index: ResourceIndex): SlotWithTool[] {
return selectAllToolSlotPointers(index)
.map(function (toolSlot) {
return {
toolSlot,
tool: maybeFindToolById(index, toolSlot.body.tool_id)
};
});
}
export function mapToolIdToName(input: ResourceIndex) {
return selectAllTools(input)
.map(x => ({ key: "" + x.body.id, val: x.body.name }))
.reduce((x, y) => ({ ...{ [y.key]: y.val, ...x } }), {} as StringMap);
}
/** I dislike this method. */
export function findToolBySlotId(input: ResourceIndex, tool_slot_id: number):
TaggedTool | undefined {
let wow = input
.byKind
.points
.map(x => input.references[x])
.map((x) => {
if (x
&& (x.kind === "points")
&& x.body.pointer_type === "ToolSlot"
&& x.body.tool_id) {
return maybeFindToolById(input, x.body.tool_id);
} else {
return undefined;
}
})
.filter(x => x)[0];
if (wow && wow.kind === "tools") {
return wow;
} else {
return undefined;
}
}