mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-06 04:48:12 +08:00
* revert all commits to before Typescript migration * update compat workflow to match latest deps (#2335) * update compat workflow to match latest deps * attempt to debug * attempt to debug * remove debugging code * typo * update deps to match desktop (#2340) * fix: don't run lint with `--fix` on push tests (#2273) * fix: don't run lint with `--fix` on push tests * npx Co-authored-by: maniarathi <mani.arathi@gmail.com> Co-authored-by: Madison Dunitz <madison.dunitz@chanzuckerberg.com> * rename X_approx_distribution to X_approximate_distribution (#2337) * Correctly handle non-finite numbers in heuristic determination of X distribution (#2342) * handle non-finites explicitly * improve and test edge case handling for distribution estimation * revert debugging changes * code readability * clean up type inferencing (#2332) * unit tests for 64 bit conversion * clean up type handling * type inference tests * more type inference fixes * use schema to determine user intent for data typing * stop using deprecated API * fbs type encoding test * add missing test * add more tests * correctly infer X type for CXG adaptor * lint * fix typo * ts migration * cleanup from PR review * lint * PR review changes * remove unused packages from client (#2359) * remove unused packages from client * add missing peer dep * fix: disable FE auth testing on compatibility tests (#2377) * update: release process (#2277) Co-authored-by: maniarathi <mani.arathi@gmail.com> * fix: remove spaces in param setup (#2380) * delete deploy workflow (#2396) * undo reformatting which now does not pass lint * fix snapshots which changed due to npm dep changes * add missing quoting to snapshot * another snapshot typo fix * TS Revert (2) - replay PR #2347 and #2354 (#2403) * replay edits from PR 2347 * TS Revert (3) - replay edits in PR #2327 (#2404) * replay edits in PR 2327 * TS Revert (4) - replay PR #2355 (#2405) * replay edits in PR 2355 * add additional babel config * reformat with new prettier config Co-authored-by: Severiano Badajoz <sbadajoz@chanzuckerberg.com> Co-authored-by: maniarathi <mani.arathi@gmail.com> Co-authored-by: Madison Dunitz <madison.dunitz@chanzuckerberg.com>
302 lines
8.3 KiB
JavaScript
302 lines
8.3 KiB
JavaScript
import StateMachine from "../util/statemachine";
|
|
import createFsmTransitions from "./undoableFsm";
|
|
|
|
const actionKey = "@@undoable/filterAction";
|
|
const stateKey = "@@undoable/filterState";
|
|
|
|
/*
|
|
these actions will not affect history
|
|
*/
|
|
const skipOnActions = new Set([
|
|
"annoMatrix: init complete",
|
|
"url changed",
|
|
"initial data load start",
|
|
"universe: user color load success",
|
|
"configuration load complete",
|
|
"increment graph render counter",
|
|
"window resize",
|
|
"reset colorscale",
|
|
"reset centroid labels",
|
|
"geneset: initial load",
|
|
"geneset: set tid",
|
|
|
|
"graph brush change",
|
|
"continuous metadata histogram brush",
|
|
|
|
"request user defined gene success",
|
|
|
|
"category value mouse hover start",
|
|
"category value mouse hover end",
|
|
|
|
/* autosave annotations */
|
|
"writable obs annotations - save complete",
|
|
"writable obs annotations - save started",
|
|
"writable obs annotations - save error",
|
|
"autosave: genesets started",
|
|
"autosave: genesets error",
|
|
"autosave: genesets complete",
|
|
|
|
/* annotation component action */
|
|
"annotation: activate add new label mode",
|
|
"annotation: disable add new label mode",
|
|
"annotation: activate category edit mode",
|
|
"annotation: disable category edit mode",
|
|
"annotation: activate edit label mode",
|
|
"annotation: cancel edit label mode",
|
|
"set annotations collection name",
|
|
|
|
/* geneset component action */
|
|
"geneset: activate add new geneset mode",
|
|
"geneset: disable create geneset mode",
|
|
"geneset: activate add new genes mode",
|
|
"geneset: disable add new genes mode",
|
|
"geneset: activate rename geneset mode",
|
|
"geneset: disable rename geneset mode",
|
|
]);
|
|
|
|
/*
|
|
identical, repeated occurances of these action types will be debounced.
|
|
Entire action must be identical (all keys).
|
|
*/
|
|
const debounceOnActions = new Set([]);
|
|
|
|
/*
|
|
history will be cleared when these actions occur
|
|
*/
|
|
const clearOnActions = new Set([
|
|
"initial data load complete",
|
|
"initial data load error",
|
|
]);
|
|
|
|
/*
|
|
An immediate history save will be done for these
|
|
*/
|
|
const saveOnActions = new Set([
|
|
"categorical metadata filter select",
|
|
"categorical metadata filter deselect",
|
|
"categorical metadata filter all of these",
|
|
"categorical metadata filter none of these",
|
|
|
|
"color by categorical metadata",
|
|
"color by continuous metadata",
|
|
"color by expression",
|
|
"color by geneset mean expression",
|
|
|
|
"show centroid labels for category",
|
|
|
|
"set scatterplot x",
|
|
"set scatterplot y",
|
|
|
|
"store current cell selection as differential set 1",
|
|
"store current cell selection as differential set 2",
|
|
|
|
"subset to selection",
|
|
"set clip quantiles",
|
|
|
|
"set layout choice",
|
|
"change graph interaction mode",
|
|
|
|
// user editable annotations
|
|
"annotation: create category",
|
|
"annotation: add new label to category",
|
|
"annotation: delete category",
|
|
"annotation: label edited",
|
|
"annotation: label current cell selection",
|
|
"annotation: delete label",
|
|
"annotation: category edited",
|
|
|
|
/* geneset component action */
|
|
"geneset: create",
|
|
"geneset: delete",
|
|
"geneset: update",
|
|
"geneset: add genes",
|
|
"geneset: delete genes",
|
|
"geneset: set gene description",
|
|
]);
|
|
|
|
/**
|
|
StateMachine - processing complex action handling - see FSM graph for
|
|
actual structure, in undoableFsm.js
|
|
**/
|
|
|
|
/*
|
|
Default FSM actions. Used to side-effect transitions in the graph.
|
|
See graph definition for the transitions that use each.
|
|
|
|
Signature: (fsm, transition, reducerState, reducerAction) => undoableAction
|
|
*/
|
|
const stashPending = (fsm) => ({
|
|
[actionKey]: "stashPending",
|
|
[stateKey]: { fsm },
|
|
});
|
|
const cancelPending = () => ({
|
|
[actionKey]: "cancelPending",
|
|
[stateKey]: { fsm: null },
|
|
});
|
|
const applyPending = () => ({
|
|
[actionKey]: "applyPending",
|
|
[stateKey]: { fsm: null },
|
|
});
|
|
const skip = (fsm, transition) => ({
|
|
[actionKey]: "skip",
|
|
[stateKey]: { fsm: transition.to !== "done" ? fsm : null },
|
|
});
|
|
const clear = () => ({ [actionKey]: "clear", [stateKey]: { fsm: null } });
|
|
const save = (fsm, transition) => ({
|
|
[actionKey]: "save",
|
|
[stateKey]: { fsm: transition.to !== "done" ? fsm : null },
|
|
});
|
|
|
|
/*
|
|
Error handler for state transitions that are unexpected. Called by
|
|
StateMachine when it doesn't know what to do.
|
|
|
|
Signature: (fsm, event, from) => undoableAction
|
|
*/
|
|
const onFsmError = (fsm, event, from) => {
|
|
console.error(`FSM error [event: "${event}", state: "${from}"]`, fsm);
|
|
// In production, try to recover gracefully if we have unexpected state
|
|
return clear();
|
|
};
|
|
|
|
/*
|
|
Definition of the transition graph mapping action types to history side effects.
|
|
*/
|
|
const fsmTransitions = createFsmTransitions(
|
|
stashPending,
|
|
cancelPending,
|
|
applyPending,
|
|
skip,
|
|
clear,
|
|
save
|
|
);
|
|
/* State machine we clone whenever we need to run it */
|
|
const seedFsm = new StateMachine("init", fsmTransitions, onFsmError);
|
|
|
|
/*
|
|
See undoable.js for description action filter interface description.
|
|
|
|
Basic approach:
|
|
* trivial handlers for skip, clear & save cases to keep config simple.
|
|
* only implement complex state machines where absolutely required (eg,
|
|
multi-event selection and the like)
|
|
*/
|
|
const actionFilter = (debug) => (state, action, prevFilterState) => {
|
|
const actionType = action.type;
|
|
const filterState = {
|
|
...prevFilterState,
|
|
prevAction: action,
|
|
};
|
|
if (skipOnActions.has(actionType)) {
|
|
return { [actionKey]: "skip", [stateKey]: filterState };
|
|
}
|
|
if (
|
|
debounceOnActions.has(actionType) &&
|
|
shallowObjectEq(action, prevFilterState.prevAction)
|
|
) {
|
|
return { [actionKey]: "skip", [stateKey]: filterState };
|
|
}
|
|
if (clearOnActions.has(actionType)) {
|
|
return { [actionKey]: "clear", [stateKey]: filterState };
|
|
}
|
|
if (saveOnActions.has(actionType)) {
|
|
return { [actionKey]: "save", [stateKey]: filterState };
|
|
}
|
|
|
|
/*
|
|
Else, something more complex OR unknown to us....
|
|
*/
|
|
if (seedFsm.events.has(actionType)) {
|
|
let { fsm } = filterState;
|
|
if (!fsm) {
|
|
/* no active FSM, so create one in init state */
|
|
fsm = seedFsm.clone("init");
|
|
}
|
|
return fsm.next(action.type, { state, action });
|
|
}
|
|
|
|
/* else, we have no idea what this is - skip it */
|
|
if (debug) console.log("**** ACTION FILTER EVENT HANDLER MISS", actionType);
|
|
return { [actionKey]: "skip", [stateKey]: filterState };
|
|
};
|
|
|
|
/*
|
|
return true if objA and objB are ===, OR if:
|
|
- are both objects and not null
|
|
- have same own properties
|
|
- all values are strict equal (===)
|
|
*/
|
|
function shallowObjectEq(objA, objB) {
|
|
if (objA === objB) return true;
|
|
if (!objA || !objB) return false;
|
|
if (!shallowArrayEq(Object.keys(objA), Object.keys(objB))) return false;
|
|
if (!shallowArrayEq(Object.values(objA), Object.values(objB))) return false;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
return true if arrA and arrB contain the same strict-equal values,
|
|
in the same order.
|
|
*/
|
|
function shallowArrayEq(arrA, arrB) {
|
|
if (arrA.length !== arrB.length) return false;
|
|
for (let i = 0, l = arrA.length; i < l; i += 1) {
|
|
if (arrA[i] !== arrB[i]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* configuration for the undoable meta reducer */
|
|
/*
|
|
debug: set to any falsish value to disable logging of helpful debugging information.
|
|
Set to true or 1 for base logging, high number for more verbosity (currently only 1/true
|
|
or 2).
|
|
*/
|
|
const debug = false;
|
|
const undoableConfig = {
|
|
debug,
|
|
historyLimit: 50, // maximum history size
|
|
actionFilter: actionFilter(debug),
|
|
};
|
|
|
|
/*
|
|
this code is strictly for sanity checking configuration, and is only
|
|
enabled when we are debugging the undoable configuration (ie, debug === true).
|
|
*/
|
|
if (debug) {
|
|
/*
|
|
Confirm no intersection between the various trivial rejection action filters
|
|
*/
|
|
if (
|
|
new Set([...skipOnActions].filter((x) => clearOnActions.has(x))).size > 0 ||
|
|
new Set([...skipOnActions].filter((x) => saveOnActions.has(x))).size > 0 ||
|
|
new Set([...clearOnActions].filter((x) => saveOnActions.has(x))).size > 0
|
|
) {
|
|
console.error(
|
|
"Undoable misconfiguration - action filters have redundant events"
|
|
);
|
|
}
|
|
|
|
/*
|
|
Confirm that no FSM events are blocked by a trivial rejection filter.
|
|
If this occurs, the FSM can't ever see the events needed to process
|
|
state transitions.
|
|
*/
|
|
const trivialFilters = new Set([
|
|
...skipOnActions,
|
|
...clearOnActions,
|
|
...saveOnActions,
|
|
]);
|
|
const trivialOverlapWithFsm = new Set(
|
|
[...trivialFilters].filter((x) => seedFsm.events.has(x))
|
|
);
|
|
if (trivialOverlapWithFsm.size > 0) {
|
|
console.error(
|
|
"Undoable misconfiguration - trivival action filter blocking FSM filter",
|
|
[...trivialOverlapWithFsm]
|
|
);
|
|
}
|
|
}
|
|
|
|
export default undoableConfig;
|