mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-15 12:47:56 +08:00
Merge branch 'main' into colinmegill/#632
This commit is contained in:
1
.github/workflows/compatibility_tests.yml
vendored
1
.github/workflows/compatibility_tests.yml
vendored
@@ -9,6 +9,7 @@ on:
|
||||
|
||||
env:
|
||||
JEST_ENV: prod
|
||||
CXG_AUTH_TYPE: none
|
||||
|
||||
jobs:
|
||||
docker-build:
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
include ../common.mk
|
||||
|
||||
ANNOTATIONS := $(if $(ANNOTATIONS),$(ANNOTATIONS),../backend/test/fixtures/pbmc3k-annotations.csv)
|
||||
GENE_SETS := $(if $(GENE_SETS),$(GENE_SETS),../backend/test/fixtures/pbmc3k-genesets.csv)
|
||||
GENE_SETS := $(if $(GENE_SETS),$(GENE_SETS),../backend/test/fixtures/pbmc3k-genesets.csv)
|
||||
ANNOTATIONS_FILENAME := $(shell basename $(ANNOTATIONS))
|
||||
GENE_SETS_FILENAME := $(shell basename $(GENE_SETS))
|
||||
|
||||
CXG_CONFIG := $(if $(CXG_CONFIG), $(CXG_CONFIG), ./__tests__/e2e/test_config.yaml)
|
||||
CXG_CONFIG := $(if $(CXG_CONFIG),$(CXG_CONFIG),./__tests__/e2e/test_config.yaml)
|
||||
|
||||
CXG_AUTH_TYPE := $(if $(CXG_AUTH_TYPE),$(CXG_AUTH_TYPE),"test")
|
||||
|
||||
|
||||
# Packaging
|
||||
@@ -38,7 +40,7 @@ smoke-test:
|
||||
start_server_and_test \
|
||||
'CXG_OPTIONS="--config-file $(CXG_CONFIG)" $(MAKE) start-server' \
|
||||
$(CXG_SERVER_PORT) \
|
||||
'CXG_URL_BASE="http://localhost:$(CXG_SERVER_PORT)" CXG_AUTH_TYPE="test" npm run e2e -- --verbose false'
|
||||
'CXG_URL_BASE="http://localhost:$(CXG_SERVER_PORT)" CXG_AUTH_TYPE=$(CXG_AUTH_TYPE) npm run e2e -- --verbose false'
|
||||
|
||||
# start an instance of cellxgene and run the end-to-end annotations tests
|
||||
.PHONY: smoke-test-annotations
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Reducer } from "redux";
|
||||
import undoable from "../../src/reducers/undoable";
|
||||
|
||||
describe("create", () => {
|
||||
test("no keys", () => {
|
||||
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2-3 arguments, but got 1.
|
||||
expect(() => undoable(() => {})).toThrow();
|
||||
expect(() => undoable(() => {}, null)).toThrow();
|
||||
expect(() =>
|
||||
undoable(() => {}, undefined as unknown as string[])
|
||||
).toThrow();
|
||||
expect(() => undoable(() => {}, null as unknown as string[])).toThrow();
|
||||
expect(() => undoable(() => {}, [])).toThrow();
|
||||
expect(() => undoable(() => {}, [], {})).toThrow();
|
||||
});
|
||||
@@ -24,8 +26,7 @@ describe("create", () => {
|
||||
describe("undo", () => {
|
||||
test("expected state modifications", () => {
|
||||
const initialState = { a: 0, b: 1000 };
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const reducer = (state: any) => ({ a: state.a + 1, b: state.b + 1 });
|
||||
const reducer: Reducer = (state) => ({ a: state.a + 1, b: state.b + 1 });
|
||||
const undoableReducer = undoable(reducer, ["a"]);
|
||||
|
||||
const s1 = undoableReducer(initialState, { type: "test" });
|
||||
@@ -43,10 +44,8 @@ describe("undo", () => {
|
||||
|
||||
describe("redo", () => {
|
||||
const initialState = { a: 0, b: 1000 };
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const reducer = (state: any) => ({ a: state.a + 1, b: state.b + 1 });
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
let UR: any;
|
||||
const reducer: Reducer = (state) => ({ a: state.a + 1, b: state.b + 1 });
|
||||
let UR: Reducer;
|
||||
|
||||
beforeEach(() => {
|
||||
UR = undoable(reducer, ["a"]);
|
||||
|
||||
@@ -34,7 +34,7 @@ function makeMockColumn(s: any, length: any) {
|
||||
return new Array(length).fill(s.categories[0]);
|
||||
|
||||
default:
|
||||
throw new Error("unkonwn type");
|
||||
throw new Error("unknown type");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RawSchema } from "../../../../src/common/types/entities";
|
||||
import { RawSchema } from "../../../../src/common/types/schema";
|
||||
|
||||
export const schema: { schema: RawSchema } = {
|
||||
schema: {
|
||||
|
||||
@@ -5,7 +5,7 @@ import zip from "lodash.zip";
|
||||
import _ from "lodash";
|
||||
import { flatbuffers } from "flatbuffers";
|
||||
import { NetEncoding } from "../../../src/util/stateManager/matrix_generated";
|
||||
import { RawSchema } from "../../../src/common/types/entities";
|
||||
import { RawSchema } from "../../../src/common/types/schema";
|
||||
|
||||
/*
|
||||
test data mocking REST 0.2 API responses. Used in several tests.
|
||||
|
||||
@@ -37,7 +37,10 @@ module.exports = {
|
||||
jsx: true,
|
||||
generators: true,
|
||||
},
|
||||
project: "./tsconfig.json",
|
||||
// (thuang): Pairing with `tsconfigRootDir`, which points to the directory
|
||||
// of eslint.js
|
||||
project: "../../tsconfig.json",
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
rules: {
|
||||
"react/jsx-no-target-blank": "off",
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
removeObsAnnoCategory,
|
||||
addObsLayout,
|
||||
} from "../util/stateManager/schemaHelpers";
|
||||
import { isArrayOrTypedArray } from "../util/typeHelpers";
|
||||
import { isAnyArray } from "../common/types/arraytypes";
|
||||
import { _whereCacheCreate } from "./whereCache";
|
||||
import AnnoMatrix from "./annoMatrix";
|
||||
import PromiseLimit from "../util/promiseLimit";
|
||||
@@ -134,7 +134,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
|
||||
|
||||
const newAnnoMatrix = this._clone();
|
||||
let data;
|
||||
if (isArrayOrTypedArray(value)) {
|
||||
if (isAnyArray(value)) {
|
||||
if (value.constructor !== Ctor)
|
||||
throw new Error("Mismatched value array type");
|
||||
if (value.length !== this.nObs)
|
||||
|
||||
85
client/src/common/types/arraytypes.ts
Normal file
85
client/src/common/types/arraytypes.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Utility type and interface definitions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* TypedArrays that can be assigned to a number.
|
||||
*/
|
||||
export type TypedArray =
|
||||
| Int8Array
|
||||
| Uint8Array
|
||||
| Int16Array
|
||||
| Uint16Array
|
||||
| Int32Array
|
||||
| Uint32Array
|
||||
| Float32Array
|
||||
| Float64Array;
|
||||
|
||||
export type UnsignedTypedArray = Uint8Array | Uint16Array | Uint32Array;
|
||||
export type FloatTypedArray = Float32Array | Float64Array;
|
||||
|
||||
export type TypedArrayConstructor =
|
||||
| Int8ArrayConstructor
|
||||
| Uint8ArrayConstructor
|
||||
| Int16ArrayConstructor
|
||||
| Uint16ArrayConstructor
|
||||
| Int32ArrayConstructor
|
||||
| Uint32ArrayConstructor
|
||||
| Float32ArrayConstructor
|
||||
| Float64ArrayConstructor;
|
||||
|
||||
export type AnyArray = Array<unknown> | TypedArray;
|
||||
|
||||
export type NumberArray = Array<number> | TypedArray;
|
||||
|
||||
export type Int8 = Int8Array[0];
|
||||
export type Uint8 = Uint8Array[0];
|
||||
export type Int16 = Int16Array[0];
|
||||
export type Uint16 = Uint16Array[0];
|
||||
export type Int32 = Int32Array[0];
|
||||
export type Uint32 = Uint32Array[0];
|
||||
export type Float32 = Float32Array[0];
|
||||
export type Float64 = Float64Array[0];
|
||||
|
||||
/**
|
||||
* Test if the parameter is a TypedArray.
|
||||
* @param tbd - value to be tested
|
||||
* @returns true if `tbd` is a TypedArray, false if not.
|
||||
*/
|
||||
export function isTypedArray(tbd: unknown): tbd is TypedArray {
|
||||
return (
|
||||
ArrayBuffer.isView(tbd) &&
|
||||
Object.prototype.toString.call(tbd) !== "[object DataView]"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the paramter is a float TypedArray
|
||||
* @param tbd - value to be tested
|
||||
* @returns - true if `tbd` is a float typed array.
|
||||
*/
|
||||
export function isFloatTypedArray(tbd: unknown): tbd is FloatTypedArray {
|
||||
return tbd instanceof Float32Array || tbd instanceof Float64Array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the paramter is a float TypedArray
|
||||
* @param tbd - value to be tested
|
||||
* @returns - true if `tbd` is a float typed array.
|
||||
*/
|
||||
export function isUnsignedTypedArray(tbd: unknown): tbd is UnsignedTypedArray {
|
||||
return (
|
||||
tbd instanceof Uint8Array ||
|
||||
tbd instanceof Uint16Array ||
|
||||
tbd instanceof Uint32Array
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the parameter is a TypedArray or Array
|
||||
* @param tbd - value to be tested
|
||||
* @returns - true if `tbd` is a TypedArray or Array
|
||||
*/
|
||||
export function isAnyArray(tbd: unknown): tbd is AnyArray {
|
||||
return Array.isArray(tbd) || isTypedArray(tbd);
|
||||
}
|
||||
@@ -1,60 +1,3 @@
|
||||
// If a globally shared type or interface doesn't have a clear owner, put it here
|
||||
|
||||
export type Category = number | string | boolean;
|
||||
|
||||
export interface AnnotationColumn {
|
||||
categories?: Category[];
|
||||
name: string;
|
||||
type: "string" | "float32" | "int32" | "categorical" | "boolean";
|
||||
writable: boolean;
|
||||
}
|
||||
|
||||
interface DataFrame {
|
||||
nObs: number;
|
||||
nVar: number;
|
||||
// TODO(thuang): Not sure what other types are available
|
||||
type: "float32";
|
||||
}
|
||||
|
||||
export interface LayoutColumn {
|
||||
dims: string[];
|
||||
name: string;
|
||||
// TODO(thuang): Not sure what other types are available
|
||||
type: "float32";
|
||||
}
|
||||
interface RawLayout {
|
||||
obs: LayoutColumn[];
|
||||
var?: LayoutColumn[];
|
||||
}
|
||||
|
||||
interface RawAnnotations {
|
||||
obs: {
|
||||
columns: AnnotationColumn[];
|
||||
index: string;
|
||||
};
|
||||
var: {
|
||||
columns: AnnotationColumn[];
|
||||
index: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RawSchema {
|
||||
annotations: RawAnnotations;
|
||||
dataframe: DataFrame;
|
||||
layout: RawLayout;
|
||||
}
|
||||
|
||||
interface Annotations extends RawAnnotations {
|
||||
obsByName: { [name: string]: AnnotationColumn };
|
||||
varByName: { [name: string]: AnnotationColumn };
|
||||
}
|
||||
|
||||
interface Layout extends RawLayout {
|
||||
obsByName: { [name: string]: LayoutColumn };
|
||||
varByName: { [name: string]: LayoutColumn };
|
||||
}
|
||||
|
||||
export interface Schema extends RawSchema {
|
||||
annotations: Annotations;
|
||||
layout: Layout;
|
||||
}
|
||||
export {};
|
||||
|
||||
58
client/src/common/types/schema.ts
Normal file
58
client/src/common/types/schema.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
type Category = number | string | boolean;
|
||||
|
||||
export interface AnnotationColumnSchema {
|
||||
categories?: Category[];
|
||||
name: string;
|
||||
type: "string" | "float32" | "int32" | "categorical" | "boolean";
|
||||
writable: boolean;
|
||||
}
|
||||
|
||||
interface XMatrixSchema {
|
||||
nObs: number;
|
||||
nVar: number;
|
||||
// TODO(thuang): Not sure what other types are available
|
||||
type: "float32";
|
||||
}
|
||||
|
||||
export interface EmbeddingSchema {
|
||||
dims: string[];
|
||||
name: string;
|
||||
// TODO(thuang): Not sure what other types are available
|
||||
type: "float32";
|
||||
}
|
||||
interface RawLayoutSchema {
|
||||
obs: EmbeddingSchema[];
|
||||
var?: EmbeddingSchema[];
|
||||
}
|
||||
|
||||
interface RawAnnotationsSchema {
|
||||
obs: {
|
||||
columns: AnnotationColumnSchema[];
|
||||
index: string;
|
||||
};
|
||||
var: {
|
||||
columns: AnnotationColumnSchema[];
|
||||
index: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RawSchema {
|
||||
annotations: RawAnnotationsSchema;
|
||||
dataframe: XMatrixSchema;
|
||||
layout: RawLayoutSchema;
|
||||
}
|
||||
|
||||
interface AnnotationsSchema extends RawAnnotationsSchema {
|
||||
obsByName: { [name: string]: AnnotationColumnSchema };
|
||||
varByName: { [name: string]: AnnotationColumnSchema };
|
||||
}
|
||||
|
||||
interface LayoutSchema extends RawLayoutSchema {
|
||||
obsByName: { [name: string]: EmbeddingSchema };
|
||||
varByName: { [name: string]: EmbeddingSchema };
|
||||
}
|
||||
|
||||
export interface Schema extends RawSchema {
|
||||
annotations: AnnotationsSchema;
|
||||
layout: LayoutSchema;
|
||||
}
|
||||
@@ -51,27 +51,55 @@ history state processing. The undoable action object contents, by key:
|
||||
filter state are entirely at the discretion of the action filter.
|
||||
|
||||
*/
|
||||
import { Reducer, AnyAction } from "redux";
|
||||
import fromEntries from "../util/fromEntries";
|
||||
|
||||
const historyKeyPrefix = "@@undoable/";
|
||||
const pastKey = `${historyKeyPrefix}past`;
|
||||
const futureKey = `${historyKeyPrefix}future`;
|
||||
const filterStateKey = `${historyKeyPrefix}filterState`;
|
||||
const filterActionKey = `${historyKeyPrefix}filterAction`;
|
||||
const pendingKey = `${historyKeyPrefix}pending`;
|
||||
export const pastKey = "@@undoable/past";
|
||||
export const futureKey = "@@undoable/future";
|
||||
export const filterStateKey = "@@undoable/filterState";
|
||||
export const filterActionKey = "@@undoable/filterAction";
|
||||
export const pendingKey = "@@undoable/pending";
|
||||
const defaultHistoryLimit = -100;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
const Undoable = (reducer: any, undoableKeys: any, options = {}) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'debug' does not exist on type '{}'.
|
||||
const { debug } = options;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'historyLimit' does not exist on type '{}... Remove this comment to see the full error message
|
||||
export interface UndoableFilterState {
|
||||
[name: string]: unknown;
|
||||
}
|
||||
|
||||
export interface UndoableConfig<FilterStateType extends UndoableFilterState> {
|
||||
debug?: boolean | number;
|
||||
historyLimit?: number;
|
||||
actionFilter?: ActionFilterFn<FilterStateType>;
|
||||
}
|
||||
|
||||
export interface UndoableAction<FilterStateType extends UndoableFilterState> {
|
||||
[filterActionKey]: string;
|
||||
[filterStateKey]?: FilterStateType;
|
||||
}
|
||||
|
||||
export type ActionFilterFn<FilterStateType extends UndoableFilterState> = (
|
||||
undoableState: UndoableState<FilterStateType>,
|
||||
action: AnyAction,
|
||||
filterState?: FilterStateType
|
||||
) => UndoableAction<FilterStateType>;
|
||||
|
||||
export interface UndoableState<FilterStateType extends UndoableFilterState> {
|
||||
[pastKey]: [string, unknown][][];
|
||||
[futureKey]: [string, unknown][][];
|
||||
[pendingKey]: [string, unknown][] | null;
|
||||
[filterStateKey]: FilterStateType | undefined;
|
||||
}
|
||||
|
||||
const Undoable = <FilterStateType extends UndoableFilterState>(
|
||||
reducer: Reducer,
|
||||
undoableKeys: string[],
|
||||
options: UndoableConfig<FilterStateType> = {}
|
||||
): Reducer => {
|
||||
const debug = options?.debug ?? false;
|
||||
let { historyLimit } = options;
|
||||
if (!historyLimit) historyLimit = defaultHistoryLimit;
|
||||
if (historyLimit > 0) historyLimit = -historyLimit;
|
||||
const actionFilter =
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(options as any).actionFilter || (() => ({ [filterActionKey]: "save" }));
|
||||
const actionFilter: ActionFilterFn<FilterStateType> =
|
||||
options?.actionFilter ?? (() => ({ [filterActionKey]: "save" }));
|
||||
|
||||
if (!Array.isArray(undoableKeys) || undoableKeys.length === 0)
|
||||
throw new Error("undoable keys array must be specified");
|
||||
@@ -80,8 +108,9 @@ const Undoable = (reducer: any, undoableKeys: any, options = {}) => {
|
||||
/*
|
||||
Undo the current to previous history
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function undo(currentState: any) {
|
||||
function undo(
|
||||
currentState: UndoableState<FilterStateType>
|
||||
): UndoableState<FilterStateType> {
|
||||
const past = currentState[pastKey];
|
||||
const future = currentState[futureKey];
|
||||
if (past.length === 0) return currentState;
|
||||
@@ -89,7 +118,7 @@ const Undoable = (reducer: any, undoableKeys: any, options = {}) => {
|
||||
undoableKeysSet.has(kv[0])
|
||||
);
|
||||
const newPast = [...past];
|
||||
const newState = newPast.pop();
|
||||
const newState = newPast.pop() || [];
|
||||
const newFuture = push(future, currentUndoableState);
|
||||
const nextState = {
|
||||
...currentState,
|
||||
@@ -104,8 +133,9 @@ const Undoable = (reducer: any, undoableKeys: any, options = {}) => {
|
||||
/*
|
||||
Replay future, previously undone.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function redo(currentState: any) {
|
||||
function redo(
|
||||
currentState: UndoableState<FilterStateType>
|
||||
): UndoableState<FilterStateType> {
|
||||
const past = currentState[pastKey] || [];
|
||||
const future = currentState[futureKey] || [];
|
||||
if (future.length === 0) return currentState;
|
||||
@@ -113,7 +143,7 @@ const Undoable = (reducer: any, undoableKeys: any, options = {}) => {
|
||||
undoableKeysSet.has(kv[0])
|
||||
);
|
||||
const newFuture = [...future];
|
||||
const newState = newFuture.pop();
|
||||
const newState = newFuture.pop() || [];
|
||||
const newPast = push(past, currentUndoableState);
|
||||
const nextState = {
|
||||
...currentState,
|
||||
@@ -128,13 +158,14 @@ const Undoable = (reducer: any, undoableKeys: any, options = {}) => {
|
||||
/*
|
||||
Clear the history state. No side-effects on current state.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function clear(currentState: any) {
|
||||
function clear(
|
||||
currentState: UndoableState<FilterStateType>
|
||||
): UndoableState<FilterStateType> {
|
||||
return {
|
||||
...currentState,
|
||||
[pastKey]: [],
|
||||
[futureKey]: [],
|
||||
[filterStateKey]: {},
|
||||
[filterStateKey]: undefined,
|
||||
[pendingKey]: null,
|
||||
};
|
||||
}
|
||||
@@ -142,8 +173,11 @@ const Undoable = (reducer: any, undoableKeys: any, options = {}) => {
|
||||
/*
|
||||
Reduce current action, with no history side-effects
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function skip(currentState: any, action: any, filterState: any) {
|
||||
function skip(
|
||||
currentState: UndoableState<FilterStateType>,
|
||||
action: AnyAction,
|
||||
filterState: UndoableFilterState
|
||||
): UndoableState<FilterStateType> {
|
||||
const past = currentState[pastKey] || [];
|
||||
const future = currentState[futureKey] || [];
|
||||
const pending = currentState[pendingKey];
|
||||
@@ -160,8 +194,11 @@ const Undoable = (reducer: any, undoableKeys: any, options = {}) => {
|
||||
/*
|
||||
Save current state in the history, then reduce action.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function save(currentState: any, action: any, filterState: any) {
|
||||
function save(
|
||||
currentState: UndoableState<FilterStateType>,
|
||||
action: AnyAction,
|
||||
filterState: UndoableFilterState
|
||||
): UndoableState<FilterStateType> {
|
||||
const past = currentState[pastKey] || [];
|
||||
const currentUndoableState = Object.entries(currentState).filter((kv) =>
|
||||
undoableKeysSet.has(kv[0])
|
||||
@@ -181,8 +218,9 @@ const Undoable = (reducer: any, undoableKeys: any, options = {}) => {
|
||||
/*
|
||||
Save current state as pending history change. No other side effects.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function stashPending(currentState: any) {
|
||||
function stashPending(
|
||||
currentState: UndoableState<FilterStateType>
|
||||
): UndoableState<FilterStateType> {
|
||||
const currentUndoableState = Object.entries(currentState).filter((kv) =>
|
||||
undoableKeysSet.has(kv[0])
|
||||
);
|
||||
@@ -195,8 +233,9 @@ const Undoable = (reducer: any, undoableKeys: any, options = {}) => {
|
||||
/*
|
||||
Cancel pending history state change. No other side effects.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function cancelPending(currentState: any) {
|
||||
function cancelPending(
|
||||
currentState: UndoableState<FilterStateType>
|
||||
): UndoableState<FilterStateType> {
|
||||
return {
|
||||
...currentState,
|
||||
[pendingKey]: null,
|
||||
@@ -206,10 +245,12 @@ const Undoable = (reducer: any, undoableKeys: any, options = {}) => {
|
||||
/*
|
||||
Push pending state onto the history stack
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function applyPending(currentState: any) {
|
||||
const past = currentState[pastKey] || [];
|
||||
function applyPending(
|
||||
currentState: UndoableState<FilterStateType>
|
||||
): UndoableState<FilterStateType> {
|
||||
const past = currentState[pastKey];
|
||||
const pendingState = currentState[pendingKey];
|
||||
if (pendingState === null) return currentState;
|
||||
const newPast = push(past, pendingState, historyLimit);
|
||||
const nextState = {
|
||||
...currentState,
|
||||
@@ -221,14 +262,13 @@ const Undoable = (reducer: any, undoableKeys: any, options = {}) => {
|
||||
}
|
||||
|
||||
return (
|
||||
currentState = {
|
||||
currentState: UndoableState<FilterStateType> = {
|
||||
[pastKey]: [],
|
||||
[futureKey]: [],
|
||||
[filterStateKey]: {},
|
||||
[filterStateKey]: undefined,
|
||||
[pendingKey]: null,
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
action: any
|
||||
action: AnyAction
|
||||
) => {
|
||||
if (debug > 1) console.log("---- ACTION", action.type);
|
||||
const aType = action.type;
|
||||
@@ -288,8 +328,7 @@ const Undoable = (reducer: any, undoableKeys: any, options = {}) => {
|
||||
};
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function push(arr: any, val: any, limit = undefined) {
|
||||
function push<T = unknown>(arr: T[], val: T, limit?: number) {
|
||||
/*
|
||||
functional array push, with a max length limit to the new array.
|
||||
Like Array.push, except it returns new array and discards as needed
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import StateMachine from "../util/statemachine";
|
||||
import { AnyAction } from "redux";
|
||||
import { StateMachine, FsmActionFn, FsmErrorFn } from "../util/statemachine";
|
||||
import {
|
||||
UndoableConfig,
|
||||
UndoableState,
|
||||
UndoableFilterState,
|
||||
UndoableAction,
|
||||
filterActionKey,
|
||||
filterStateKey,
|
||||
} from "./undoable";
|
||||
import createFsmTransitions from "./undoableFsm";
|
||||
|
||||
const actionKey = "@@undoable/filterAction";
|
||||
const stateKey = "@@undoable/filterState";
|
||||
|
||||
/*
|
||||
these actions will not affect history
|
||||
*/
|
||||
const skipOnActions = new Set([
|
||||
const skipOnActions = new Set<string>([
|
||||
"annoMatrix: init complete",
|
||||
"url changed",
|
||||
"initial data load start",
|
||||
@@ -58,12 +64,12 @@ const skipOnActions = new Set([
|
||||
identical, repeated occurances of these action types will be debounced.
|
||||
Entire action must be identical (all keys).
|
||||
*/
|
||||
const debounceOnActions = new Set([]);
|
||||
const debounceOnActions = new Set<string>([]);
|
||||
|
||||
/*
|
||||
history will be cleared when these actions occur
|
||||
*/
|
||||
const clearOnActions = new Set([
|
||||
const clearOnActions = new Set<string>([
|
||||
"initial data load complete",
|
||||
"initial data load error",
|
||||
]);
|
||||
@@ -71,7 +77,7 @@ const clearOnActions = new Set([
|
||||
/*
|
||||
An immediate history save will be done for these
|
||||
*/
|
||||
const saveOnActions = new Set([
|
||||
const saveOnActions = new Set<string>([
|
||||
"categorical metadata filter select",
|
||||
"categorical metadata filter deselect",
|
||||
"categorical metadata filter all of these",
|
||||
@@ -123,35 +129,43 @@ StateMachine - processing complex action handling - see FSM graph for
|
||||
actual structure, in undoableFsm.js
|
||||
**/
|
||||
|
||||
interface MyFilterState extends UndoableFilterState {
|
||||
prevAction?: AnyAction;
|
||||
fsm: StateMachine<MyUndoableAction> | null;
|
||||
}
|
||||
type MyUndoableAction = UndoableAction<MyFilterState>;
|
||||
|
||||
/*
|
||||
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
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const stashPending = (fsm: any) => ({
|
||||
[actionKey]: "stashPending",
|
||||
[stateKey]: { fsm },
|
||||
const stashPending: FsmActionFn<MyUndoableAction> = (
|
||||
fsm: StateMachine<MyUndoableAction>
|
||||
) => ({
|
||||
[filterActionKey]: "stashPending",
|
||||
[filterStateKey]: { fsm },
|
||||
});
|
||||
const cancelPending = () => ({
|
||||
[actionKey]: "cancelPending",
|
||||
[stateKey]: { fsm: null },
|
||||
const cancelPending: FsmActionFn<MyUndoableAction> = () => ({
|
||||
[filterActionKey]: "cancelPending",
|
||||
[filterStateKey]: { fsm: null },
|
||||
});
|
||||
const applyPending = () => ({
|
||||
[actionKey]: "applyPending",
|
||||
[stateKey]: { fsm: null },
|
||||
const applyPending: FsmActionFn<MyUndoableAction> = () => ({
|
||||
[filterActionKey]: "applyPending",
|
||||
[filterStateKey]: { fsm: null },
|
||||
});
|
||||
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'fsm' implicitly has an 'any' type.
|
||||
const skip = (fsm, transition) => ({
|
||||
[actionKey]: "skip",
|
||||
[stateKey]: { fsm: transition.to !== "done" ? fsm : null },
|
||||
const skip: FsmActionFn<MyUndoableAction> = (fsm, transition) => ({
|
||||
[filterActionKey]: "skip",
|
||||
[filterStateKey]: { fsm: transition.to !== "done" ? fsm : null },
|
||||
});
|
||||
const clear = () => ({ [actionKey]: "clear", [stateKey]: { fsm: null } });
|
||||
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'fsm' implicitly has an 'any' type.
|
||||
const save = (fsm, transition) => ({
|
||||
[actionKey]: "save",
|
||||
[stateKey]: { fsm: transition.to !== "done" ? fsm : null },
|
||||
const clear: FsmActionFn<MyUndoableAction> = () => ({
|
||||
[filterActionKey]: "clear",
|
||||
[filterStateKey]: { fsm: null },
|
||||
});
|
||||
const save: FsmActionFn<MyUndoableAction> = (fsm, transition) => ({
|
||||
[filterActionKey]: "save",
|
||||
[filterStateKey]: { fsm: transition.to !== "done" ? fsm : null },
|
||||
});
|
||||
|
||||
/*
|
||||
@@ -160,11 +174,13 @@ StateMachine when it doesn't know what to do.
|
||||
|
||||
Signature: (fsm, event, from) => undoableAction
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const onFsmError = (fsm: any, event: any, from: any) => {
|
||||
const onFsmError: FsmErrorFn<MyUndoableAction> = (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();
|
||||
return {
|
||||
[filterActionKey]: "clear",
|
||||
[filterStateKey]: { fsm: null },
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -179,7 +195,11 @@ const fsmTransitions = createFsmTransitions(
|
||||
save
|
||||
);
|
||||
/* State machine we clone whenever we need to run it */
|
||||
const seedFsm = new StateMachine("init", fsmTransitions, onFsmError);
|
||||
const seedFsm = new StateMachine<MyUndoableAction>(
|
||||
"init",
|
||||
fsmTransitions,
|
||||
onFsmError
|
||||
);
|
||||
|
||||
/*
|
||||
See undoable.js for description action filter interface description.
|
||||
@@ -189,53 +209,52 @@ Basic approach:
|
||||
* only implement complex state machines where absolutely required (eg,
|
||||
multi-event selection and the like)
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const actionFilter = (debug: any) => (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
state: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
action: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
prevFilterState: any
|
||||
) => {
|
||||
const actionType = action.type;
|
||||
const filterState = {
|
||||
...prevFilterState,
|
||||
prevAction: action,
|
||||
};
|
||||
if (skipOnActions.has(actionType)) {
|
||||
return { [actionKey]: "skip", [stateKey]: filterState };
|
||||
}
|
||||
if (
|
||||
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'any' is not assignable to parame... Remove this comment to see the full error message
|
||||
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 };
|
||||
}
|
||||
const actionFilter =
|
||||
(debug: boolean) =>
|
||||
(
|
||||
state: UndoableState<MyFilterState>,
|
||||
action: AnyAction,
|
||||
prevFilterState: MyFilterState | undefined
|
||||
): UndoableAction<MyFilterState> => {
|
||||
const actionType = action.type;
|
||||
prevFilterState = prevFilterState || { fsm: null };
|
||||
const filterState: MyFilterState = {
|
||||
...prevFilterState,
|
||||
prevAction: action,
|
||||
};
|
||||
if (skipOnActions.has(actionType)) {
|
||||
return { [filterActionKey]: "skip", [filterStateKey]: filterState };
|
||||
}
|
||||
if (
|
||||
debounceOnActions.has(actionType) &&
|
||||
prevFilterState.prevAction &&
|
||||
shallowObjectEq(action, prevFilterState.prevAction)
|
||||
) {
|
||||
return { [filterActionKey]: "skip", [filterStateKey]: filterState };
|
||||
}
|
||||
if (clearOnActions.has(actionType)) {
|
||||
return { [filterActionKey]: "clear", [filterStateKey]: filterState };
|
||||
}
|
||||
if (saveOnActions.has(actionType)) {
|
||||
return { [filterActionKey]: "save", [filterStateKey]: 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");
|
||||
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 });
|
||||
}
|
||||
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 };
|
||||
};
|
||||
/* else, we have no idea what this is - skip it */
|
||||
if (debug) console.log("**** ACTION FILTER EVENT HANDLER MISS", actionType);
|
||||
return { [filterActionKey]: "skip", [filterStateKey]: filterState };
|
||||
};
|
||||
|
||||
/*
|
||||
return true if objA and objB are ===, OR if:
|
||||
@@ -243,8 +262,10 @@ return true if objA and objB are ===, OR if:
|
||||
- have same own properties
|
||||
- all values are strict equal (===)
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function shallowObjectEq(objA: any, objB: any) {
|
||||
function shallowObjectEq(
|
||||
objA: Record<string | number | symbol, unknown>,
|
||||
objB: Record<string | number | symbol, unknown>
|
||||
) {
|
||||
if (objA === objB) return true;
|
||||
if (!objA || !objB) return false;
|
||||
if (!shallowArrayEq(Object.keys(objA), Object.keys(objB))) return false;
|
||||
@@ -256,8 +277,7 @@ function shallowObjectEq(objA: any, objB: any) {
|
||||
return true if arrA and arrB contain the same strict-equal values,
|
||||
in the same order.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function shallowArrayEq(arrA: any, arrB: any) {
|
||||
function shallowArrayEq(arrA: unknown[], arrB: unknown[]) {
|
||||
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;
|
||||
@@ -272,7 +292,7 @@ Set to true or 1 for base logging, high number for more verbosity (currently onl
|
||||
or 2).
|
||||
*/
|
||||
const debug = false;
|
||||
const undoableConfig = {
|
||||
const undoableConfig: UndoableConfig<MyFilterState> = {
|
||||
debug,
|
||||
historyLimit: 50, // maximum history size
|
||||
actionFilter: actionFilter(debug),
|
||||
@@ -311,7 +331,7 @@ if (debug) {
|
||||
);
|
||||
if (trivialOverlapWithFsm.size > 0) {
|
||||
console.error(
|
||||
"Undoable misconfiguration - trivival action filter blocking FSM filter",
|
||||
"Undoable misconfiguration - trivial action filter blocking FSM filter",
|
||||
[...trivialOverlapWithFsm]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,22 +18,16 @@ b) compound actions that should be collapsed into a single history change.
|
||||
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
const createFsmTransitions = (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
stashPending: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
cancelPending: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
applyPending: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
skip: any,
|
||||
// @ts-expect-error ts-migrate(6133) FIXME: 'clear' is declared but its value is never read.
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
clear: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
save: any
|
||||
) => [
|
||||
import { StateMachine, FsmTransition, FsmActionFn } from "../util/statemachine";
|
||||
|
||||
const createFsmTransitions = <ActionReturnType>(
|
||||
stashPending: FsmActionFn<ActionReturnType>,
|
||||
cancelPending: FsmActionFn<ActionReturnType>,
|
||||
applyPending: FsmActionFn<ActionReturnType>,
|
||||
skip: FsmActionFn<ActionReturnType>,
|
||||
_clear: FsmActionFn<ActionReturnType>,
|
||||
save: FsmActionFn<ActionReturnType>
|
||||
): FsmTransition<ActionReturnType>[] => [
|
||||
/* graph selection brushing */
|
||||
{
|
||||
event: "graph brush start",
|
||||
@@ -52,12 +46,14 @@ const createFsmTransitions = (
|
||||
from: "graph brush in progress",
|
||||
to: "done",
|
||||
/* if current selection is all, cancelPending. Else, applyPending */
|
||||
// @ts-expect-error ts-migrate(6133) FIXME: 'fsm' is declared but its value is never read.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
action: (fsm: any, transition: any, data: any) =>
|
||||
action: (
|
||||
fsm: StateMachine<ActionReturnType>,
|
||||
transition: FsmTransition<ActionReturnType>,
|
||||
data: any // eslint-disable-line @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. Requires state typing.
|
||||
) =>
|
||||
data.state.graphSelection.selection.mode === "all"
|
||||
? cancelPending()
|
||||
: applyPending(),
|
||||
? cancelPending(fsm, transition, data)
|
||||
: applyPending(fsm, transition, data),
|
||||
},
|
||||
{
|
||||
event: "graph brush end",
|
||||
@@ -84,12 +80,14 @@ const createFsmTransitions = (
|
||||
from: "graph lasso in progress",
|
||||
to: "done",
|
||||
/* if current selection is all, cancelPending. Else, applyPending */
|
||||
// @ts-expect-error ts-migrate(6133) FIXME: 'fsm' is declared but its value is never read.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
action: (fsm: any, transition: any, data: any) =>
|
||||
action: (
|
||||
fsm: StateMachine<ActionReturnType>,
|
||||
transition: FsmTransition<ActionReturnType>,
|
||||
data: any // eslint-disable-line @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. Requires state typing.
|
||||
) =>
|
||||
data.state.graphSelection.selection.mode === "all"
|
||||
? cancelPending()
|
||||
: applyPending(),
|
||||
? cancelPending(fsm, transition, data)
|
||||
: applyPending(fsm, transition, data),
|
||||
},
|
||||
{
|
||||
event: "graph lasso end",
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { IdentityInt32Index, isLabelIndex } from "./labelIndex";
|
||||
// weird cross-dependency that we should clean up someday...
|
||||
import {
|
||||
isTypedArray,
|
||||
isArrayOrTypedArray,
|
||||
callOnceLazy,
|
||||
memoize,
|
||||
__getMemoId,
|
||||
} from "./util";
|
||||
import { callOnceLazy, memoize, __getMemoId } from "./util";
|
||||
import { isTypedArray, isAnyArray } from "../../common/types/arraytypes";
|
||||
import {
|
||||
summarizeContinuous,
|
||||
summarizeCategorical as _summarizeCategorical,
|
||||
@@ -163,7 +158,7 @@ class Dataframe {
|
||||
if (!Array.isArray(columnarData)) {
|
||||
throw new TypeError("Dataframe constructor requires array of columns");
|
||||
}
|
||||
if (!columnarData.every((c) => isArrayOrTypedArray(c))) {
|
||||
if (!columnarData.every((c) => isAnyArray(c))) {
|
||||
throw new TypeError("Dataframe columns must all be Array or TypedArray");
|
||||
}
|
||||
if (!isLabelIndex(rowIndex)) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
Dataframe histogram
|
||||
*/
|
||||
import { isTypedArray } from "./util";
|
||||
import { isTypedArray } from "../../common/types/arraytypes";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function _histogramContinuous(column: any, bins: any, min: any, max: any) {
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
Private utility code for dataframe
|
||||
*/
|
||||
|
||||
export { isTypedArray, isArrayOrTypedArray } from "../typeHelpers";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function callOnceLazy(f: any) {
|
||||
/*
|
||||
|
||||
@@ -3,7 +3,7 @@ Helper functions for user-editable annotations state management.
|
||||
See also reducers/annotations.js
|
||||
*/
|
||||
|
||||
import { Schema } from "../../common/types/entities";
|
||||
import { Schema } from "../../common/types/schema";
|
||||
|
||||
/*
|
||||
There are a number of state constraints assumed throughout the
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { flatbuffers } from "flatbuffers";
|
||||
import { NetEncoding } from "./matrix_generated";
|
||||
import { isTypedArray, isFpTypedArray } from "../typeHelpers";
|
||||
import { isTypedArray, isFloatTypedArray } from "../../common/types/arraytypes";
|
||||
import {
|
||||
Dataframe,
|
||||
IdentityInt32Index,
|
||||
@@ -188,7 +188,7 @@ function promoteTypedArray(o: any) {
|
||||
TODO - future optimization: not all int32/uint32 data series require
|
||||
promotion to float64. We COULD simply look at the data to decide.
|
||||
*/
|
||||
if (isFpTypedArray(o) || Array.isArray(o)) return o;
|
||||
if (isFloatTypedArray(o) || Array.isArray(o)) return o;
|
||||
|
||||
let TypedArrayCtor;
|
||||
switch (o.constructor) {
|
||||
@@ -246,7 +246,7 @@ export function matrixFBSToDataframe(arrayBuffers: any) {
|
||||
.map((fb: any) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
fb.columns.map((c: any) => {
|
||||
if (isFpTypedArray(c) || Array.isArray(c)) return c;
|
||||
if (isFloatTypedArray(c) || Array.isArray(c)) return c;
|
||||
return promoteTypedArray(c);
|
||||
})
|
||||
)
|
||||
|
||||
@@ -11,9 +11,9 @@ import catLabelSort from "../catLabelSort";
|
||||
import {
|
||||
RawSchema,
|
||||
Schema,
|
||||
LayoutColumn,
|
||||
AnnotationColumn,
|
||||
} from "../../common/types/entities";
|
||||
EmbeddingSchema,
|
||||
AnnotationColumnSchema,
|
||||
} from "../../common/types/schema";
|
||||
|
||||
/*
|
||||
System wide schema assumptions:
|
||||
@@ -86,7 +86,7 @@ export function removeObsAnnoColumn(schema: Schema, name: string): Schema {
|
||||
export function addObsAnnoColumn(
|
||||
schema: Schema,
|
||||
_: string,
|
||||
defn: AnnotationColumn
|
||||
defn: AnnotationColumnSchema
|
||||
): Schema {
|
||||
const newSchema = _copyObsAnno(schema);
|
||||
|
||||
@@ -142,7 +142,7 @@ export function addObsAnnoCategory(schema: any, name: any, category: any) {
|
||||
return newSchema;
|
||||
}
|
||||
|
||||
export function addObsLayout(schema: Schema, layout: LayoutColumn): Schema {
|
||||
export function addObsLayout(schema: Schema, layout: EmbeddingSchema): Schema {
|
||||
/* add or replace a layout */
|
||||
const newSchema = _copyObsLayout(schema);
|
||||
newSchema.layout.obs.push(layout);
|
||||
|
||||
@@ -13,18 +13,17 @@ Where:
|
||||
to: state_name_transitioning_to,
|
||||
from: state_name_transitioning_from,
|
||||
event: value_that_will_cause_transition,
|
||||
action: optional_callback_upon_transition
|
||||
action: callback_upon_transition
|
||||
}
|
||||
The transition will be provided to the action callback, so other data
|
||||
may be stored in the transition object for use by the action callback.
|
||||
* onErrorCallback - a callback function called if the FSM receives an event
|
||||
for which it has no defined transition.
|
||||
|
||||
|
||||
Interface:
|
||||
* states - property containing the state names. A Set(), contianing the
|
||||
* states - property containing the state names. A Set(), containing the
|
||||
union of to: and from: values.
|
||||
* events - property containing all of the accepted event values. Set().
|
||||
* events - property containing all of the accepted event values. Set().
|
||||
* graph - a Map of Maps, organized as graph[eventValue][fromStateValue]
|
||||
* clone() - clone the entire statemachine.
|
||||
* next(eventValue) - drive the FSM to the next state. If the event
|
||||
@@ -40,47 +39,69 @@ Example:
|
||||
const fsm = new StateMachine("A", transitions, () => { throw new Error("oops") });
|
||||
fsm.next("yo"); // returns 42
|
||||
|
||||
|
||||
*/
|
||||
export default class StateMachine {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
events: any;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
graph: any;
|
||||
export type FsmState = number | string;
|
||||
export type FsmEvent = string; // by convention, we assume Events are redux action types, aka strings
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
onError: any;
|
||||
export type FsmActionFn<ActionReturnType> = (
|
||||
fsm: StateMachine<ActionReturnType>,
|
||||
transition: FsmTransition<ActionReturnType>,
|
||||
data: unknown
|
||||
) => ActionReturnType;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
state: any;
|
||||
export interface FsmTransition<ActionReturnType> {
|
||||
from: FsmState;
|
||||
to: FsmState;
|
||||
event: FsmEvent;
|
||||
action: FsmActionFn<ActionReturnType>;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
states: any;
|
||||
export type FsmErrorFn<ActionReturnType> = (
|
||||
fsm: StateMachine<ActionReturnType>,
|
||||
event: FsmEvent,
|
||||
state: FsmState
|
||||
) => ActionReturnType;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(initState: any, transitions: any, onError: any) {
|
||||
this.onError = onError || (() => undefined);
|
||||
export class StateMachine<ActionReturnType> {
|
||||
events: Set<FsmEvent>;
|
||||
|
||||
graph: Map<FsmEvent, Map<FsmState, FsmTransition<ActionReturnType>>>;
|
||||
|
||||
onError: FsmErrorFn<ActionReturnType>;
|
||||
|
||||
state: FsmState;
|
||||
|
||||
states: Set<FsmState>;
|
||||
|
||||
constructor(
|
||||
initState: FsmState,
|
||||
transitions: FsmTransition<ActionReturnType>[],
|
||||
onError: FsmErrorFn<ActionReturnType>
|
||||
) {
|
||||
this.onError = onError;
|
||||
this.state = initState;
|
||||
|
||||
// all states
|
||||
this.states = new Set(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
transitions.reduce((names: any, tsn: any) => {
|
||||
names.push(tsn.from);
|
||||
names.push(tsn.to);
|
||||
return names;
|
||||
}, [])
|
||||
transitions.reduce(
|
||||
(names: Array<FsmState>, tsn: FsmTransition<ActionReturnType>) => {
|
||||
names.push(tsn.from);
|
||||
names.push(tsn.to);
|
||||
return names;
|
||||
},
|
||||
[]
|
||||
)
|
||||
);
|
||||
|
||||
// all transition names (aka events)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
this.events = new Set(transitions.map((tsn: any) => tsn.event));
|
||||
this.events = new Set(
|
||||
transitions.map((tsn: FsmTransition<ActionReturnType>) => tsn.event)
|
||||
);
|
||||
|
||||
// the transition graph.
|
||||
// graph[event][from] -> transition
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
this.graph = transitions.reduce((graph: any, tsn: any) => {
|
||||
this.graph = transitions.reduce((graph, tsn) => {
|
||||
const { event, from } = tsn;
|
||||
if (!graph.has(event)) graph.set(event, new Map());
|
||||
const tsnMap = graph.get(event);
|
||||
@@ -89,29 +110,23 @@ export default class StateMachine {
|
||||
}, new Map());
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
clone(initState: any) {
|
||||
// @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 2.
|
||||
const fsm = new StateMachine(initState, []);
|
||||
fsm.onError = this.onError;
|
||||
clone(initState: FsmState): StateMachine<ActionReturnType> {
|
||||
const fsm = new StateMachine<ActionReturnType>(initState, [], this.onError);
|
||||
fsm.states = this.states;
|
||||
fsm.events = this.events;
|
||||
fsm.graph = this.graph;
|
||||
return fsm;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
next(event: any, data: any) {
|
||||
next(event: FsmEvent, data: unknown): ActionReturnType {
|
||||
const { graph, state } = this;
|
||||
const tsnMap = graph.get(event);
|
||||
if (!tsnMap) return this.onError(this, event, state, undefined);
|
||||
if (!tsnMap) return this.onError(this, event, state);
|
||||
|
||||
const transition = tsnMap.get(state);
|
||||
if (!transition) return this.onError(this, event, state, undefined);
|
||||
if (!transition) return this.onError(this, event, state);
|
||||
|
||||
this.state = transition.to;
|
||||
return transition.action
|
||||
? transition.action(this, transition, data)
|
||||
: undefined;
|
||||
return transition.action(this, transition, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
Various type and schema related helper functions.
|
||||
*/
|
||||
|
||||
/*
|
||||
Utility function to test for a typed array
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function isTypedArray(x: any) {
|
||||
return (
|
||||
ArrayBuffer.isView(x) &&
|
||||
Object.prototype.toString.call(x) !== "[object DataView]"
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
Test for float typed array, ie, Float32TypedArray or Float64TypedArray
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function isFpTypedArray(x: any) {
|
||||
let constructor;
|
||||
const isFloatArray =
|
||||
x &&
|
||||
({ constructor } = x) &&
|
||||
(constructor === Float32Array || constructor === Float64Array);
|
||||
return isFloatArray;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function isArrayOrTypedArray(x: any) {
|
||||
return Array.isArray(x) || isTypedArray(x);
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
upperBoundIndirect,
|
||||
} from "./sort";
|
||||
import { makeSortIndex } from "./util";
|
||||
import { isAnyArray } from "../../common/types/arraytypes";
|
||||
|
||||
class NotImplementedError extends Error {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
@@ -450,7 +451,7 @@ class ImmutableScalarDimension extends _ImmutableBaseDimension {
|
||||
value,
|
||||
new ValueArrayType(data.length)
|
||||
);
|
||||
} else if (isArrayOrTypedArray(value)) {
|
||||
} else if (isAnyArray(value)) {
|
||||
// Create value array from user-provided array. Typically used
|
||||
// only by enumerated dimensions
|
||||
array = this._createValueArray(
|
||||
@@ -729,15 +730,6 @@ export const DimTypes = {
|
||||
spatial: ImmutableSpatialDimension,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function isArrayOrTypedArray(x: any) {
|
||||
return (
|
||||
Array.isArray(x) ||
|
||||
(ArrayBuffer.isView(x) &&
|
||||
Object.prototype.toString.call(x) !== "[object DataView]")
|
||||
);
|
||||
}
|
||||
|
||||
/* return bounding box of the polygon */
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function polygonBoundingBox(polygon: any) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isTypedArray, isFpTypedArray } from "../typeHelpers";
|
||||
import { isTypedArray, isFloatTypedArray } from "../../common/types/arraytypes";
|
||||
|
||||
/* eslint-disable no-bitwise -- code relies on bitwise ops */
|
||||
|
||||
@@ -219,7 +219,7 @@ export function sortArray(arr: any) {
|
||||
return quicksort(arr, 0, arr.length - 1);
|
||||
}
|
||||
if (isTypedArray(arr)) {
|
||||
if (isFpTypedArray(arr)) {
|
||||
if (isFloatTypedArray(arr)) {
|
||||
return quicksortFloats(arr, 0, arr.length - 1);
|
||||
}
|
||||
return quicksort(arr, 0, arr.length - 1);
|
||||
@@ -230,7 +230,7 @@ export function sortArray(arr: any) {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function sortIndex(index: any, source: any) {
|
||||
if (isFpTypedArray(source))
|
||||
if (isFloatTypedArray(source))
|
||||
return quicksortFloatsIndirect(index, source, 0, index.length - 1);
|
||||
return quicksortIndirect(index, source, 0, index.length - 1);
|
||||
}
|
||||
@@ -293,7 +293,7 @@ function lowerBoundFloat(valueArray: any, value: any, first: any, last: any) {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function lowerBound(valueArray: any, value: any, first: any, last: any) {
|
||||
if (isFpTypedArray(valueArray)) {
|
||||
if (isFloatTypedArray(valueArray)) {
|
||||
return lowerBoundFloat(valueArray, value, first, last);
|
||||
}
|
||||
return lowerBoundNonFloat(valueArray, value, first, last);
|
||||
@@ -366,7 +366,7 @@ export function lowerBoundIndirect(
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
last: any
|
||||
) {
|
||||
if (isFpTypedArray(valueArray)) {
|
||||
if (isFloatTypedArray(valueArray)) {
|
||||
return lowerBoundFloatIndirect(valueArray, indexArray, value, first, last);
|
||||
}
|
||||
return lowerBoundNonFloatIndirect(valueArray, indexArray, value, first, last);
|
||||
@@ -425,7 +425,7 @@ function upperBoundFloat(valueArray: any, value: any, first: any, last: any) {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function upperBound(valueArray: any, value: any, first: any, last: any) {
|
||||
if (isFpTypedArray(valueArray)) {
|
||||
if (isFloatTypedArray(valueArray)) {
|
||||
return upperBoundFloat(valueArray, value, first, last);
|
||||
}
|
||||
return upperBoundNonFloat(valueArray, value, first, last);
|
||||
@@ -498,7 +498,7 @@ export function upperBoundIndirect(
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
last: any
|
||||
) {
|
||||
if (isFpTypedArray(valueArray)) {
|
||||
if (isFloatTypedArray(valueArray)) {
|
||||
return upperBoundFloatIndirect(valueArray, indexArray, value, first, last);
|
||||
}
|
||||
return upperBoundNonFloatIndirect(valueArray, indexArray, value, first, last);
|
||||
|
||||
@@ -26,7 +26,7 @@ Please scroll down the section below for how to release a patch version. Follow
|
||||
- Write the release title and release notes and add to [release notes document](https://docs.google.com/document/d/1KnHwkYfhyWO5H8BDcMu7y3ogjvq5Yi4OwpmZ8DB6w0Y/edit)
|
||||
2. Create a release branch, eg, `release-version-0.16.0`
|
||||
3. In the release branch, run `make create-release-candidate PART=[major | minor | patch]` where you choose major/minor/patch depending on which part of the version is being bumped (e.g., `0.2.9` -> `0.3.0` is minor version bump). This will bump the version and create a release *candidate* version (i.e. `0.3.0-rc.0`).
|
||||
4. Commit and push the new branch. This will trigger tests to ensure that your branch isn't broken.
|
||||
4. Push the new branch to origin and open a `DO NOT MERGE` PR, this will run tests on your branch.
|
||||
5. Upload the release candidate to Test PyPI by running the command `make release-candidate-to-test-pypi`. (Make sure you are registered for PyPI and Test PyPI and you have write access to the cellxgene PyPI package for both).
|
||||
6. Verify the release candidate in a fresh virtual environment by running `make install-release-test` which installs the cellxgene build you just uploaded the Test PyPI.
|
||||
7. If you find errors with the release candidate, run `make recreate-release-candidate` to increment the release candidate version (i.e. `0.3.0-rc.0` -> `0.3.0-rc.1`). Then go back to Steps 5 and 6 to re-upload and re-test the new release candidate.
|
||||
|
||||
Reference in New Issue
Block a user