Farmbot-Web-App/webpack/resources/sequence_tagging.ts

76 lines
3.2 KiB
TypeScript

import { get, set } from "lodash";
import { SequenceBodyItem, uuid } from "farmbot/dist";
import { TaggedResource } from "./tagged_resources";
/** HISTORICAL NOTES:
* This file is the result of some very subtle bugs relating to dynamic
* children in React components on the sequence editor page. Simply put, the
* sequence editor needs a way to uniquely identify each step when rendering.
*
* PROBLEM:
* - React needs a unique `key` prop when iterating over UI list elements.
* - The only unique key on a sequence step is its index within the array.
* - An array index is not adequate for tracking highly dynamic content changes
* such as changes seen within the sequence editor.
* - If you *do* use only the array index as a `key` prop in the editor,
* the UI may become out of sync with the underlying data.
* - The JSON returned from the server is not adequate for uniquely tracking
* each step in this situation.
* - Changing the underlying structure of CeleryScript globally to fix a problem
* seen only in the sequence editor is a not-so-great idea.
*
* SOLUTION:
* To get around this, we sneakily add a `uuid` property to the all sequence
* steps. They are ignored by the API. `step.uuid` can be used anywhere a
* tracking `key={}` is required when iterating over sequence steps in the UI.
*
* RATIONALE:
* We could change the way CeleryScript is structured globally, but that has
* no usefulness outside of the sequence editor UI. Also, retroactively
* updating sequence steps already in the database can be error prone and time
* consuming. I would rather put a hack in one place (here) rather than force
* all other parts of the product to support it.
*
* OTHER IMPORTANT THINGS:
* My goal is to keep as much UUID tagging logic as possible in this file
* only. When a more elegant solution appears, it will be easier to remove.
* Please *AVOID MOVING THESE FUNCTIONS AND INTERFACES INTO SEPARATE FILES*.
*
* FURTHER READING:
* https://facebook.github.io/react/docs/lists-and-keys.html
*
* -RC 6-Aug-17
*/
/** Type alias for the data type used to tag steps.
* Currently `string`. Formerly `number`. */
export type StepTag = string;
/** Property name where a unique ID is stored in a step. */
const TAG_PROP = "uuid";
/** VERY IMPORTANT FUNCTION.
* SEE HEADER AT TOP OF FILE.
* Retrieves tag from a step object. Assumes that all steps have a tag.
* If no tag is found, crashes. */
export function getStepTag(i: SequenceBodyItem): StepTag {
const tag = get(i, TAG_PROP, "");
if (tag) { return tag; }
throw new Error("No tag on step: " + i.kind);
}
/** Idempotently add a `uuid` property to a step. */
export let setStepTag = (i: SequenceBodyItem) => {
set(i, TAG_PROP, uuid());
};
/** Idempotently add a `uuid` property to all steps in an array. */
export let tagAllSteps = (i: SequenceBodyItem[]) => i.map(setStepTag);
/** REALLY IMPORTANT SEE FILE HEADER FOR MORE INFO! -RC
* Used by Redux within the `resource` reducer. Given a TaggedResource,
* idempotently adds `UUID` property to all steps in all sequences. */
export function maybeTagSteps(x: TaggedResource) {
if (x && (x.kind === "sequences")) { tagAllSteps(x.body.body || []); }
}