merge typescript changes

This commit is contained in:
Colin Megill
2021-08-04 14:42:31 -07:00
287 changed files with 23849 additions and 17636 deletions
+2 -4
View File
@@ -28,8 +28,8 @@ jobs:
continue-on-error: true continue-on-error: true
strategy: strategy:
matrix: matrix:
python-version: [3.6, 3.7] # As of Oct 2020 Anndata is not compatible with 3.8 python-version: [3.6, 3.7, 3.8]
anndata-version: [0.7.0, 0.7.1, 0.7.2, 0.7.3, 0.7.4, 0.7.5] anndata-version: [0.7.6]
test-suite: [smoke-test, smoke-test-annotations] test-suite: [smoke-test, smoke-test-annotations]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -47,8 +47,6 @@ jobs:
make pydist install-dist make pydist install-dist
# 3. install anndata # 3. install anndata
pip install anndata==${{ matrix.anndata-version }} pip install anndata==${{ matrix.anndata-version }}
# workaround for anndata 0.6.22.post1 bug
[[ "0.6.22.post1" = "${{ matrix.anndata-version }}" ]] && pip install h5py==2.9.0 || true
- name: Tests - name: Tests
run: make unit-test ${{ matrix.test-suite }} run: make unit-test ${{ matrix.test-suite }}
+1 -1
View File
@@ -40,7 +40,7 @@ jobs:
- name: Lint src with eslint - name: Lint src with eslint
working-directory: ./client working-directory: ./client
run: | run: |
make lint npx eslint src __tests__
unit-test: unit-test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
+27
View File
@@ -0,0 +1,27 @@
name: Run SASTisfaction
on:
- pull_request
jobs:
sastisfaction:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v2
with:
repository: chanzuckerberg/sastisfaction
ref: main
path: .github/actions/sastisfaction
ssh-key: ${{ secrets.SASTISFACTION_READ_KEY }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker pull
run: docker pull ghcr.io/chanzuckerberg/sastisfaction:main
- name: Run SASTisfaction
uses: ./.github/actions/sastisfaction
with:
snowflake_private_key: ${{ secrets.SASTISFACTION_RSA_KEY }}
+7 -7
View File
@@ -1,6 +1,6 @@
import numpy as np import numpy as np
from scipy import sparse, stats from scipy import sparse, stats
from backend.common.constants import XApproxDistribution from backend.common.constants import XApproximateDistribution
def diffexp_ttest(adaptor, maskA, maskB, top_n=8, diffexp_lfc_cutoff=0.01): def diffexp_ttest(adaptor, maskA, maskB, top_n=8, diffexp_lfc_cutoff=0.01):
@@ -30,13 +30,13 @@ def diffexp_ttest(adaptor, maskA, maskB, top_n=8, diffexp_lfc_cutoff=0.01):
:return: for top N genes, {"positive": for top N genes, [ varindex, foldchange, pval, pval_adj ], "negative": for top N genes, [ varindex, foldchange, pval, pval_adj ]} :return: for top N genes, {"positive": for top N genes, [ varindex, foldchange, pval, pval_adj ], "negative": for top N genes, [ varindex, foldchange, pval, pval_adj ]}
""" """
X_approx_distribution = adaptor.get_X_approx_distribution() X_approximate_distribution = adaptor.get_X_approximate_distribution()
dataA = adaptor.get_X_array(maskA, None) dataA = adaptor.get_X_array(maskA, None)
dataB = adaptor.get_X_array(maskB, None) dataB = adaptor.get_X_array(maskB, None)
# mean, variance, N - calculate for both selections # mean, variance, N - calculate for both selections
meanA, vA, nA = mean_var_n(dataA, X_approx_distribution) meanA, vA, nA = mean_var_n(dataA, X_approximate_distribution)
meanB, vB, nB = mean_var_n(dataB, X_approx_distribution) meanB, vB, nB = mean_var_n(dataB, X_approximate_distribution)
res = diffexp_ttest_from_mean_var(meanA, vA, nA, meanB, vB, nB, top_n, diffexp_lfc_cutoff) res = diffexp_ttest_from_mean_var(meanA, vA, nA, meanB, vB, nB, top_n, diffexp_lfc_cutoff)
return res return res
@@ -113,7 +113,7 @@ def diffexp_ttest_from_mean_var(meanA, varA, nA, meanB, varB, nB, top_n, diffexp
# Convenience function which handles sparse data # Convenience function which handles sparse data
def mean_var_n(X, X_approx_distribution=XApproxDistribution.NORMAL): def mean_var_n(X, X_approximate_distribution=XApproximateDistribution.NORMAL):
""" """
Two-pass variance calculation. Numerically (more) stable Two-pass variance calculation. Numerically (more) stable
than naive methods (and same method used by numpy.var()) than naive methods (and same method used by numpy.var())
@@ -131,14 +131,14 @@ def mean_var_n(X, X_approx_distribution=XApproxDistribution.NORMAL):
with np.errstate(divide="call", invalid="call", call=fp_err_set): with np.errstate(divide="call", invalid="call", call=fp_err_set):
n = X.shape[0] n = X.shape[0]
if sparse.issparse(X): if sparse.issparse(X):
if X_approx_distribution == XApproxDistribution.COUNT: if X_approximate_distribution == XApproximateDistribution.COUNT:
X = X.log1p() X = X.log1p()
mean = X.mean(axis=0).A1 mean = X.mean(axis=0).A1
dfm = X - mean dfm = X - mean
sumsq = np.sum(np.multiply(dfm, dfm), axis=0).A1 sumsq = np.sum(np.multiply(dfm, dfm), axis=0).A1
v = sumsq / (n - 1) v = sumsq / (n - 1)
else: else:
if X_approx_distribution == XApproxDistribution.COUNT: if X_approximate_distribution == XApproximateDistribution.COUNT:
X = np.log1p(X) X = np.log1p(X)
mean = X.mean(axis=0) mean = X.mean(axis=0)
dfm = X - mean dfm = X - mean
+40 -15
View File
@@ -2,34 +2,52 @@ import numba
import concurrent.futures import concurrent.futures
import numpy as np import numpy as np
from scipy import sparse from scipy import sparse
from backend.common.constants import XApproxDistribution from backend.common.constants import XApproximateDistribution
@numba.njit(fastmath=True, error_model="numpy", nogil=True) @numba.njit(error_model="numpy", nogil=True)
def min_max(arr): def min_max(arr: np.ndarray):
"""Return (min, max) values for the ndarray.""" """Return (min, max) values for the ndarray."""
n = arr.size
odd = n % 2 # initialize to first finite value in array. Normally,
if not odd: # this will exit on the first value.
n -= 1 for i in range(arr.size):
max_val = min_val = arr[0] min_val = max_val = arr[i]
i = 1 if np.isfinite(min_val):
while i < n: break
# now find min/max, unrolled by two
odd = arr.size % 2
unrolled_loop_limit = arr.size - 1 if odd else arr.size
i = 0
while i < unrolled_loop_limit:
x = arr[i] x = arr[i]
y = arr[i + 1] y = arr[i + 1]
# ignore non-finites
x = x if np.isfinite(x) else min_val
y = y if np.isfinite(y) else min_val
if x > y: if x > y:
x, y = y, x x, y = y, x
min_val = min(x, min_val) min_val = min(x, min_val)
max_val = max(y, max_val) max_val = max(y, max_val)
i += 2 i += 2
if not odd:
x = arr[n] # handle the tail if any
if odd:
x = arr[arr.size - 1]
# ignore non-finites
x = x if np.isfinite(x) else min_val
min_val = min(x, min_val) min_val = min(x, min_val)
max_val = max(x, max_val) max_val = max(x, max_val)
return min_val, max_val return min_val, max_val
def estimate_approximate_distribution(X) -> XApproxDistribution: def estimate_approximate_distribution(X) -> XApproximateDistribution:
""" """
Estimate the distribution (normal, count) of the X matrix. Estimate the distribution (normal, count) of the X matrix.
@@ -38,6 +56,13 @@ def estimate_approximate_distribution(X) -> XApproxDistribution:
any (max-min) range in excess of 24 is implies tens of millions of any (max-min) range in excess of 24 is implies tens of millions of
observations of a single feature and so is extremely unlikely. observations of a single feature and so is extremely unlikely.
""" """
if X.dtype.kind not in ["i", "u", "f"]:
raise TypeError(f"Unsupported matrix dtype: {X.dtype.name}")
if X.size == 0:
# default for empty array
return XApproximateDistribution.NORMAL
if sparse.isspmatrix_csc(X) or sparse.isspmatrix_csr(X): if sparse.isspmatrix_csc(X) or sparse.isspmatrix_csr(X):
Xdata = X.data Xdata = X.data
elif type(X) is np.ndarray: elif type(X) is np.ndarray:
@@ -45,7 +70,7 @@ def estimate_approximate_distribution(X) -> XApproxDistribution:
X.size, X.size,
) )
else: else:
raise TypeError(f"Unsupported matrix type: {str(type(X))}") raise TypeError(f"Unsupported matrix format: {str(type(X))}")
CHUNKSIZE = 1 << 24 CHUNKSIZE = 1 << 24
if Xdata.size > CHUNKSIZE: if Xdata.size > CHUNKSIZE:
@@ -59,4 +84,4 @@ def estimate_approximate_distribution(X) -> XApproxDistribution:
min_val, max_val = min_max(Xdata) min_val, max_val = min_max(Xdata)
excess_range = (max_val - min_val) > 24 excess_range = (max_val - min_val) > 24
return XApproxDistribution.COUNT if excess_range else XApproxDistribution.NORMAL return XApproximateDistribution.COUNT if excess_range else XApproximateDistribution.NORMAL
+1 -1
View File
@@ -24,7 +24,7 @@ class DiffExpMode(AugmentedEnum):
VAR_FILTER = "varFilter" VAR_FILTER = "varFilter"
class XApproxDistribution(AugmentedEnum): class XApproximateDistribution(AugmentedEnum):
NORMAL = "normal" NORMAL = "normal"
COUNT = "count" COUNT = "count"
+7 -3
View File
@@ -5,6 +5,8 @@ import pandas as pd
from flatbuffers import Builder from flatbuffers import Builder
from scipy import sparse from scipy import sparse
from backend.common.utils.type_conversion_utils import get_encoding_dtype_of_array
import backend.common.fbs.NetEncoding.Column as Column import backend.common.fbs.NetEncoding.Column as Column
import backend.common.fbs.NetEncoding.Float32Array as Float32Array import backend.common.fbs.NetEncoding.Float32Array as Float32Array
import backend.common.fbs.NetEncoding.Float64Array as Float64Array import backend.common.fbs.NetEncoding.Float64Array as Float64Array
@@ -14,6 +16,7 @@ import backend.common.fbs.NetEncoding.Matrix as Matrix
import backend.common.fbs.NetEncoding.TypedArray as TypedArray import backend.common.fbs.NetEncoding.TypedArray as TypedArray
import backend.common.fbs.NetEncoding.Uint32Array as Uint32Array import backend.common.fbs.NetEncoding.Uint32Array as Uint32Array
# Serialization helper # Serialization helper
def serialize_column(builder, typed_arr): def serialize_column(builder, typed_arr):
""" Serialize NetEncoding.Column """ """ Serialize NetEncoding.Column """
@@ -84,7 +87,7 @@ def serialize_typed_array(builder, source_array, encoding_info):
def column_encoding(arr): def column_encoding(arr):
column_encoding_type_map = { column_encoding_type_map = {
# array protocol string: ( array_type, as_type ) # array protocol string: ( array_type, as_type )
np.dtype(np.float64).str: (TypedArray.TypedArray.Float64Array, np.float64), np.dtype(np.float64).str: (TypedArray.TypedArray.Float32Array, np.float32),
np.dtype(np.float32).str: (TypedArray.TypedArray.Float32Array, np.float32), np.dtype(np.float32).str: (TypedArray.TypedArray.Float32Array, np.float32),
np.dtype(np.float16).str: (TypedArray.TypedArray.Float32Array, np.float32), np.dtype(np.float16).str: (TypedArray.TypedArray.Float32Array, np.float32),
np.dtype(np.int8).str: (TypedArray.TypedArray.Int32Array, np.int32), np.dtype(np.int8).str: (TypedArray.TypedArray.Int32Array, np.int32),
@@ -98,7 +101,8 @@ def column_encoding(arr):
} }
column_encoding_default = (TypedArray.TypedArray.JSONEncodedArray, "json") column_encoding_default = (TypedArray.TypedArray.JSONEncodedArray, "json")
return column_encoding_type_map.get(arr.dtype.str, column_encoding_default) encoding_dtype = np.dtype(get_encoding_dtype_of_array(arr))
return column_encoding_type_map.get(encoding_dtype.str, column_encoding_default)
def index_encoding(arr): def index_encoding(arr):
@@ -198,7 +202,7 @@ def deserialize_typed_array(tarr):
arr.Init(u.Bytes, u.Pos) arr.Init(u.Bytes, u.Pos)
narr = arr.DataAsNumpy() narr = arr.DataAsNumpy()
if u_type == TypedArray.TypedArray.JSONEncodedArray: if u_type == TypedArray.TypedArray.JSONEncodedArray:
narr = json.loads(narr.tostring().decode("utf-8")) narr = json.loads(narr.tobytes().decode("utf-8"))
return narr return narr
+145 -112
View File
@@ -1,8 +1,42 @@
from typing import Union, Tuple
import logging import logging
import numpy as np import numpy as np
import pandas as pd import pandas as pd
"""
These routines drive all type inference for the schema generation and the
FBS (REST OTA) encoding. They are also used for CXG generation.
H5AD Type REST REST
(ndarray, Series, Index) FBS encoding schema type ERROR/exceptions
---------------------------- -------------- --------------- ----------------------
bool_/bool uint8 boolean
(u)int8, (u)int16, int32 int32 int32
uint32, (u)int64 int32 int32 CHECKS value bounds
float16, float32, float64 float32 float32[0]
categorical[T is numeric[4]]:
hasna = False T categorical[1]
hasna = True float32 categorical[1] CHECKS value bounds
categorical[T not numeric] JSON/str categorical[1,2]
(other object) JSON/str string
(all other) Always an ERROR[3]
Notes:
[0] IEEE format, includes non-finite numbers (NaN, Inf, ...)
[1] with NO categories enumerated (client side does it to handle rounding)
[2] NA (undefined) categories are assigned a JSON null value
[3] Includes all other numpy types: datetime, complex, etc.
[4] means float, int, uint (dtype.kind in ['i','u','f'])
"""
def get_dtypes_and_schemas_of_dataframe(dataframe: pd.DataFrame): def get_dtypes_and_schemas_of_dataframe(dataframe: pd.DataFrame):
dtypes_by_column_name = {} dtypes_by_column_name = {}
@@ -17,133 +51,132 @@ def get_dtypes_and_schemas_of_dataframe(dataframe: pd.DataFrame):
return dtypes_by_column_name, schema_type_hints_by_column_name return dtypes_by_column_name, schema_type_hints_by_column_name
def get_dtype_of_array(array: pd.Series): def get_encoding_dtype_of_array(array: Union[np.ndarray, pd.Series, pd.Index]) -> np.dtype:
return get_dtype_and_schema_of_array(array)[0] return _get_type_info(array)[0]
def get_schema_type_hint_of_array(array: pd.Series): def get_schema_type_hint_of_array(array: Union[np.ndarray, pd.Series, pd.Index]) -> dict:
return get_dtype_and_schema_of_array(array)[1] return _get_type_info(array)[1]
def get_dtype_and_schema_of_array(array: pd.Series): def get_dtype_and_schema_of_array(array: Union[np.ndarray, pd.Series, pd.Index]) -> Tuple[np.dtype, dict]:
return ( """Return tuple (encoding_dtype, schema_type_hint)"""
get_dtype_from_dtype(array.dtype, array_values=array), return _get_type_info(array)
get_schema_type_hint_from_dtype(array.dtype, array_values=array),
)
def get_dtype_from_dtype(dtype, array_values=None): def get_schema_type_hint_from_dtype(dtype) -> dict:
res = _get_type_info_from_dtype(dtype)
if res is None:
raise TypeError(f"Annotations of type {dtype} are unsupported.")
else:
return res[1]
def _get_type_info_from_dtype(dtype) -> Union[Tuple[np.dtype, dict], None]:
""" """
Given a data type, finds the equivalent data type that the array should be encoded as. Notably, this is relevant Best-effort to determine encoding type and schema hint from a dtype.
for 64 bit values which will get downcast to 32 bit. If this is not possible, or the type is unsupported, return None.
This should be a subset of the cases which are supported by
_get_type_info(). The latter should be preferred if the array (values)
are available for typing.
""" """
if dtype.kind == "b":
return (np.uint8, {"type": "boolean"})
dtype_name = dtype.name if dtype.kind == "U":
dtype_kind = dtype.kind return (np.dtype(str), {"type": "string"})
if dtype_name == "bool":
return np.uint8
if dtype_name == "object" and dtype_kind == "O":
return str
if dtype_name == "category":
return get_dtype_from_dtype(dtype.categories.dtype, array_values)
if can_cast_to_int32(dtype, array_values):
return np.int32
if can_cast_to_float32(dtype, array_values):
return np.float32
if not can_cast_to_float32(dtype, array_values):
return np.float64
raise TypeError(f"Annotations of type {dtype} are unsupported.")
def get_schema_type_hint_from_dtype(dtype, array_values=None):
"""
Returns a dictionary that contains type hints about the data type given, especially if the data type is 64 bit
and will be downcast to 32 bit.
"""
dtype_name = dtype.name
dtype_kind = dtype.kind
if dtype == np.float32 or dtype == np.int32:
return {"type": dtype_name}
if dtype_name == "bool":
return {"type": "boolean"}
if dtype_name == "object" and dtype_kind == "O":
return {"type": "string"}
if dtype_name == "category":
return {"type": "categorical", "categories": dtype.categories.tolist()}
if can_cast_to_int32(dtype, array_values):
return {"type": "int32"}
if can_cast_to_float32(dtype, array_values):
return {"type": "float32"}
if dtype_kind == "f" and not can_cast_to_float32(dtype, array_values):
return {"type": "float64"}
raise TypeError(f"Annotations of type {dtype} are unsupported.")
def can_cast_to_float32(dtype, array_values):
"""
Optimistically returns True signifying that a type downcast to float32 is possible whenever the incoming type is
a float.
We also handle a special case here where the array is a Series object with integer categorical values AND NaNs.
Since NaNs are floating points in numpy, we upcast the integer array to float32 and return True.
"""
if dtype.kind == "f":
if not np.can_cast(dtype, np.float32):
logging.warning(f"Type {dtype.name} will be converted to 32 bit float and may lose precision.")
return True
if dtype.kind == "O" and array_values.hasnans:
return True
return False
def can_cast_to_int32(dtype, array_values=None):
"""
A type can be cast to 32 bit, overriding the numpy `cast_cast` function if the values in the array that are of
the higher precision type has values that are entirely within the range of the downcast type.
"""
# Since a NaN is technically a float, any array that contains NaNs cannot be cast to an integer so immediately
# return False.
if array_values.hasnans:
return False
# If the array is categorical, then we need to order the array values so that functions min and max that occur
# later, can function. They do not function on unordered categories.
ordered_array_values = array_values
if array_values.dtype.name == "category" and not array_values.cat.ordered:
ordered_array_values = array_values.cat.as_ordered()
if dtype.kind in ["i", "u"]: if dtype.kind in ["i", "u"]:
if np.can_cast(dtype, np.int32): if np.can_cast(dtype, np.int32):
return True return (np.int32, {"type": "int32"})
ii32 = np.iinfo(np.int32)
if ( if dtype.kind == "f":
not ordered_array_values.empty _float64_warning(dtype)
and (ordered_array_values.min() >= ii32.min and ordered_array_values.max() <= ii32.max) return (np.float32, {"type": "float32"})
or ordered_array_values.empty
): if dtype.kind == "O" and not dtype.name == "category":
return True return (np.dtype(str), {"type": "string"})
return None
def _get_type_info(array: Union[np.ndarray, pd.Series, pd.Index]) -> Tuple[np.dtype, dict]:
"""
Determine encoding type and schema hint from an array. This allows more
flexible casting than may be possible by using just the dtype, as it can
account for category types and array values.
"""
if (
not isinstance(array, np.ndarray)
and not isinstance(array, pd.Series)
and not isinstance(array, pd.Index)
and not hasattr(array, "dtype")
):
raise TypeError("Unsupported data type.")
dtype = array.dtype
res = _get_type_info_from_dtype(dtype)
if res is not None:
return res
if dtype.kind == "O":
if dtype.name == "category":
# Sometimes CategoricalDType can be encoded as int or float without further fuss.
# Do not specify the categories in the schema - let the client-side figure it out
# on its own. Utilize Series.to_numpy() to do casting that handles categorical
# NA/NaN (missing or undefined) categories.
if dtype.categories.dtype.kind in ["f", "i", "u"]:
return (
_get_type_info(array.to_numpy())[0],
{"type": "categorical"},
)
else:
return (np.dtype(str), {"type": "categorical", "categories": dtype.categories.to_list()})
# all other extension types are str-encoded
return (np.dtype(str), {"type": "string"})
if dtype.kind in ["i", "u"] and _can_cast_array_values_to_int32(array):
return (np.int32, {"type": "int32"})
if dtype.kind == "f":
_float64_warning(array.dtype)
return (np.float32, {"type": "float32"})
raise TypeError(f"Annotations of type {dtype} are unsupported.")
def _float64_warning(dtype):
"""
Warn the user if we are down-casting a float64 to float32, and may potentially lose information.
"""
if dtype.kind == "f" and not np.can_cast(dtype, np.float32):
logging.warning(f"Type {dtype.name} will be converted to 32 bit float and may lose precision.")
def _can_cast_array_values_to_int32(array: Union[np.ndarray, pd.Series, pd.Index]) -> bool:
"""
Return true if the (U)INT array values can be safely cast to int32. We allow size reducing
casts (ie, int64 to int32) if no actual values require the larger size (ie, actual values
can be represented by the smaller type).
"""
assert array.dtype.kind in ["u", "i"]
if np.can_cast(array.dtype, np.int32):
return True
if array.size == 0:
return True
int32_machine_limits = np.iinfo(np.int32)
if array.min() >= int32_machine_limits.min and array.max() <= int32_machine_limits.max:
return True
return False return False
def convert_pandas_series_to_numpy(series_to_convert: pd.Series, dtype):
if series_to_convert.hasnans and dtype == np.int32:
logging.error("Cannot convert a pandas Series object to an integer dtype if it contains NaNs.")
return series_to_convert.to_numpy(dtype)
def convert_string_to_value(value: str): def convert_string_to_value(value: str):
"""convert a string to value with the most appropriate type""" """convert a string to value with the most appropriate type"""
if value.lower() == "true": if value.lower() == "true":
@@ -10,7 +10,7 @@ from flask import current_app
from backend.czi_hosted.common.annotations.annotations import Annotations from backend.czi_hosted.common.annotations.annotations import Annotations
from backend.common.errors import AnnotationCategoryNameError from backend.common.errors import AnnotationCategoryNameError
from backend.czi_hosted.common.utils.sanitization_utils import sanitize_values_in_list from backend.czi_hosted.common.utils.sanitization_utils import sanitize_values_in_list
from backend.common.utils.type_conversion_utils import get_dtypes_and_schemas_of_dataframe, get_dtype_of_array from backend.common.utils.type_conversion_utils import get_dtypes_and_schemas_of_dataframe, get_encoding_dtype_of_array
from backend.czi_hosted.db.cellxgene_orm import Annotation from backend.czi_hosted.db.cellxgene_orm import Annotation
@@ -143,7 +143,7 @@ class AnnotationsHostedTileDB(Annotations):
# convert to tiledb datatypes # convert to tiledb datatypes
for col in df: for col in df:
df[col] = df[col].astype(get_dtype_of_array(df[col])) df[col] = df[col].astype(get_encoding_dtype_of_array(df[col]))
tiledb.from_pandas(uri, df, sparse=True) tiledb.from_pandas(uri, df, sparse=True)
else: else:
uri = "" uri = ""
@@ -44,7 +44,7 @@ class DatasetConfig(BaseConfig):
self.diffexp__lfc_cutoff = default_config["diffexp"]["lfc_cutoff"] self.diffexp__lfc_cutoff = default_config["diffexp"]["lfc_cutoff"]
self.diffexp__top_n = default_config["diffexp"]["top_n"] self.diffexp__top_n = default_config["diffexp"]["top_n"]
self.X_approx_distribution = default_config["X_approx_distribution"] self.X_approximate_distribution = default_config["X_approximate_distribution"]
except KeyError as e: except KeyError as e:
raise ConfigurationError(f"Unexpected config: {str(e)}") raise ConfigurationError(f"Unexpected config: {str(e)}")
@@ -60,7 +60,7 @@ class DatasetConfig(BaseConfig):
self.handle_user_annotations(context) self.handle_user_annotations(context)
self.handle_embeddings() self.handle_embeddings()
self.handle_diffexp(context) self.handle_diffexp(context)
self.handle_X_approx_distribution() self.handle_X_approximate_distribution()
def handle_app(self): def handle_app(self):
self.validate_correct_type_of_configuration_attribute("app__scripts", list) self.validate_correct_type_of_configuration_attribute("app__scripts", list)
@@ -203,9 +203,9 @@ class DatasetConfig(BaseConfig):
"running differential expression may take longer or fail." "running differential expression may take longer or fail."
) )
def handle_X_approx_distribution(self): def handle_X_approximate_distribution(self):
self.validate_correct_type_of_configuration_attribute("X_approx_distribution", str) self.validate_correct_type_of_configuration_attribute("X_approximate_distribution", str)
if self.X_approx_distribution not in ["normal", "count"]: if self.X_approximate_distribution not in ["normal", "count"]:
raise ConfigurationError( raise ConfigurationError(
"X_approx_distribution has unknown value -- must be 'normal' or 'count'." "X_approximate_distribution has unknown value -- must be 'normal' or 'count'."
) )
@@ -3,7 +3,7 @@ import json
import numpy as np import numpy as np
import tiledb import tiledb
from backend.common.utils.type_conversion_utils import get_dtype_of_array, get_dtype_and_schema_of_array from backend.common.utils.type_conversion_utils import get_encoding_dtype_of_array, get_dtype_and_schema_of_array
def convert_dictionary_to_cxg_group(cxg_container, metadata_dict, group_metadata_name="cxg_group_metadata"): def convert_dictionary_to_cxg_group(cxg_container, metadata_dict, group_metadata_name="cxg_group_metadata"):
@@ -47,7 +47,7 @@ def convert_dataframe_to_cxg_array(cxg_container, dataframe_name, dataframe, ind
] ]
) )
attrs = [ attrs = [
tiledb.Attr(name=column, dtype=get_dtype_of_array(dataframe[column]), filters=tiledb_filter) tiledb.Attr(name=column, dtype=get_encoding_dtype_of_array(dataframe[column]), filters=tiledb_filter)
for column in dataframe for column in dataframe
] ]
domain = tiledb.Domain( domain = tiledb.Domain(
@@ -8,7 +8,7 @@ from scipy import sparse
import backend.common.compute.diffexp_generic as diffexp_generic import backend.common.compute.diffexp_generic as diffexp_generic
from backend.common.colors import convert_anndata_category_colors_to_cxg_category_colors from backend.common.colors import convert_anndata_category_colors_to_cxg_category_colors
from backend.common.constants import Axis, MAX_LAYOUTS, XApproxDistribution from backend.common.constants import Axis, MAX_LAYOUTS, XApproximateDistribution
from backend.czi_hosted.common.corpora import corpora_get_props_from_anndata from backend.czi_hosted.common.corpora import corpora_get_props_from_anndata
from backend.common.errors import PrepareError, DatasetAccessError, ConfigurationError from backend.common.errors import PrepareError, DatasetAccessError, ConfigurationError
from backend.common.utils.type_conversion_utils import get_schema_type_hint_of_array from backend.common.utils.type_conversion_utils import get_schema_type_hint_of_array
@@ -28,7 +28,7 @@ class AnndataAdaptor(DataAdaptor):
def __init__(self, data_locator, app_config=None, dataset_config=None): def __init__(self, data_locator, app_config=None, dataset_config=None):
super().__init__(data_locator, app_config, dataset_config) super().__init__(data_locator, app_config, dataset_config)
self.data = None self.data = None
self.X_approx_distribution = None self.X_approximate_distribution = None
self._load_data(data_locator) self._load_data(data_locator)
self._validate_and_initialize() self._validate_and_initialize()
@@ -66,11 +66,11 @@ class AnndataAdaptor(DataAdaptor):
@staticmethod @staticmethod
def _create_unique_column_name(df, col_name_prefix): def _create_unique_column_name(df, col_name_prefix):
""" given the columns of a dataframe, and a name prefix, return a column name which """given the columns of a dataframe, and a name prefix, return a column name which
does not exist in the dataframe, AND which is prefixed by `prefix` does not exist in the dataframe, AND which is prefixed by `prefix`
The approach is to append a numeric suffix, starting at zero and increasing by The approach is to append a numeric suffix, starting at zero and increasing by
one, until an unused name is found (eg, prefix_0, prefix_1, ...). one, until an unused name is found (eg, prefix_0, prefix_1, ...).
""" """
suffix = 0 suffix = 0
while f"{col_name_prefix}{suffix}" in df: while f"{col_name_prefix}{suffix}" in df:
@@ -124,7 +124,11 @@ class AnndataAdaptor(DataAdaptor):
def _create_schema(self): def _create_schema(self):
self.schema = { self.schema = {
"dataframe": {"nObs": self.cell_count, "nVar": self.gene_count, "type": str(self.data.X.dtype)}, "dataframe": {
"nObs": self.cell_count,
"nVar": self.gene_count,
**get_schema_type_hint_of_array(self.data.X),
},
"annotations": { "annotations": {
"obs": {"index": self.parameters.get("obs_names"), "columns": []}, "obs": {"index": self.parameters.get("obs_names"), "columns": []},
"var": {"index": self.parameters.get("var_names"), "columns": []}, "var": {"index": self.parameters.get("var_names"), "columns": []},
@@ -191,9 +195,9 @@ class AnndataAdaptor(DataAdaptor):
self.gene_count = self.data.shape[1] self.gene_count = self.data.shape[1]
self._create_schema() self._create_schema()
if self.dataset_config.X_approx_distribution == "auto": if self.dataset_config.X_approximate_distribution == "auto":
raise ConfigurationError("X-approx-distribution 'auto' mode unsupported.") raise ConfigurationError("X-approximate-distribution 'auto' mode unsupported.")
self.X_approx_distribution = self.dataset_config.X_approx_distribution self.X_approximate_distribution = self.dataset_config.X_approximate_distribution
# heuristic # heuristic
n_values = self.data.shape[0] * self.data.shape[1] n_values = self.data.shape[0] * self.data.shape[1]
@@ -201,10 +205,10 @@ class AnndataAdaptor(DataAdaptor):
self.parameters.update({"diffexp_may_be_slow": True}) self.parameters.update({"diffexp_may_be_slow": True})
def _is_valid_layout(self, arr): def _is_valid_layout(self, arr):
""" return True if this layout data is a valid array for front-end presentation: """return True if this layout data is a valid array for front-end presentation:
* ndarray, dtype float/int/uint * ndarray, dtype float/int/uint
* with shape (n_obs, >= 2) * with shape (n_obs, >= 2)
* with all values finite or NaN (no +Inf or -Inf) * with all values finite or NaN (no +Inf or -Inf)
""" """
is_valid = type(arr) == np.ndarray and arr.dtype.kind in "fiu" is_valid = type(arr) == np.ndarray and arr.dtype.kind in "fiu"
is_valid = is_valid and arr.shape[0] == self.data.n_obs and arr.shape[1] >= 2 is_valid = is_valid and arr.shape[0] == self.data.n_obs and arr.shape[1] >= 2
@@ -327,8 +331,8 @@ class AnndataAdaptor(DataAdaptor):
X = self.data.X[obs_mask, var_mask] X = self.data.X[obs_mask, var_mask]
return X return X
def get_X_approx_distribution(self) -> XApproxDistribution: def get_X_approximate_distribution(self) -> XApproximateDistribution:
return self.X_approx_distribution return self.X_approximate_distribution
def get_shape(self): def get_shape(self):
return self.data.shape return self.data.shape
@@ -7,7 +7,7 @@ from scipy import sparse
from server_timing import Timing as ServerTiming from server_timing import Timing as ServerTiming
from backend.czi_hosted.common.config.app_config import AppConfig from backend.czi_hosted.common.config.app_config import AppConfig
from backend.common.constants import Axis, XApproxDistribution from backend.common.constants import Axis, XApproximateDistribution
from backend.common.errors import ( from backend.common.errors import (
FilterError, FilterError,
JSONEncodingValueError, JSONEncodingValueError,
@@ -84,7 +84,7 @@ class DataAdaptor(metaclass=ABCMeta):
pass pass
@abstractmethod @abstractmethod
def get_X_approx_distribution(self) -> XApproxDistribution: def get_X_approximate_distribution(self) -> XApproximateDistribution:
"""return the approximate distribution of the X matrix.""" """return the approximate distribution of the X matrix."""
pass pass
+10 -9
View File
@@ -8,7 +8,7 @@ import pandas as pd
import tiledb import tiledb
from server_timing import Timing as ServerTiming from server_timing import Timing as ServerTiming
from backend.common.constants import Axis, XApproxDistribution from backend.common.constants import Axis, XApproximateDistribution
from backend.common.errors import DatasetAccessError, ConfigurationError from backend.common.errors import DatasetAccessError, ConfigurationError
from backend.czi_hosted.common.immutable_kvcache import ImmutableKVCache from backend.czi_hosted.common.immutable_kvcache import ImmutableKVCache
from backend.common.utils.type_conversion_utils import get_schema_type_hint_from_dtype from backend.common.utils.type_conversion_utils import get_schema_type_hint_from_dtype
@@ -37,7 +37,7 @@ class CxgAdaptor(DataAdaptor):
self.lsuri_results = ImmutableKVCache(lambda key: self._lsuri(uri=key, tiledb_ctx=self.tiledb_ctx)) self.lsuri_results = ImmutableKVCache(lambda key: self._lsuri(uri=key, tiledb_ctx=self.tiledb_ctx))
self.arrays = ImmutableKVCache(lambda key: self._open_array(uri=key, tiledb_ctx=self.tiledb_ctx)) self.arrays = ImmutableKVCache(lambda key: self._open_array(uri=key, tiledb_ctx=self.tiledb_ctx))
self.schema = None self.schema = None
self.X_approx_distribution = None self.X_approximate_distribution = None
self._validate_and_initialize() self._validate_and_initialize()
@@ -176,9 +176,9 @@ class CxgAdaptor(DataAdaptor):
if cxg_version not in ["0.0", "0.1", "0.2.0"]: if cxg_version not in ["0.0", "0.1", "0.2.0"]:
raise DatasetAccessError(f"cxg matrix is not valid: {self.url}") raise DatasetAccessError(f"cxg matrix is not valid: {self.url}")
if self.dataset_config.X_approx_distribution == "auto": if self.dataset_config.X_approximate_distribution == "auto":
raise ConfigurationError("X-approx-distribution 'auto' mode unsupported.") raise ConfigurationError("X-approximate-distribution 'auto' mode unsupported.")
self.X_approx_distribution = self.dataset_config.X_approx_distribution self.X_approximate_distribution = self.dataset_config.X_approximate_distribution
self.title = title self.title = title
self.about = about self.about = about
@@ -210,7 +210,8 @@ class CxgAdaptor(DataAdaptor):
if lfc_cutoff is None: if lfc_cutoff is None:
lfc_cutoff = self.dataset_config.diffexp__lfc_cutoff lfc_cutoff = self.dataset_config.diffexp__lfc_cutoff
return diffexp_cxg.diffexp_ttest( return diffexp_cxg.diffexp_ttest(
adaptor=self, maskA=maskA, maskB=maskB, top_n=top_n, diffexp_lfc_cutoff=lfc_cutoff) adaptor=self, maskA=maskA, maskB=maskB, top_n=top_n, diffexp_lfc_cutoff=lfc_cutoff
)
def get_colors(self): def get_colors(self):
if self.cxg_version == "0.0": if self.cxg_version == "0.0":
@@ -286,8 +287,8 @@ class CxgAdaptor(DataAdaptor):
data = X.multi_index[obs_items, var_items][""] data = X.multi_index[obs_items, var_items][""]
return data return data
def get_X_approx_distribution(self) -> XApproxDistribution: def get_X_approximate_distribution(self) -> XApproximateDistribution:
return self.X_approx_distribution return self.X_approximate_distribution
def get_shape(self): def get_shape(self):
X = self.open_array("X") X = self.open_array("X")
@@ -357,7 +358,7 @@ class CxgAdaptor(DataAdaptor):
shape = self.get_shape() shape = self.get_shape()
dtype = self.get_X_array_dtype() dtype = self.get_X_array_dtype()
dataframe = {"nObs": shape[0], "nVar": shape[1], "type": dtype.name} dataframe = {"nObs": shape[0], "nVar": shape[1], **get_schema_type_hint_from_dtype(dtype)}
annotations = {} annotations = {}
for ax in ("obs", "var"): for ax in ("obs", "var"):
+1 -1
View File
@@ -203,7 +203,7 @@ dataset:
lfc_cutoff: 0.01 lfc_cutoff: 0.01
top_n: 10 top_n: 10
X_approx_distribution: normal # currently fixed config X_approximate_distribution: normal # currently fixed config
external: external:
# You can retrieve configuration parameters from this config file, the environment, # You can retrieve configuration parameters from this config file, the environment,
+6 -6
View File
@@ -1,9 +1,9 @@
anndata>=0.7.6 # we use to_memory(), added in 0.7.6 anndata>=0.7.6 # we need to_memory(), added in 0.7.6
boto3>=1.12.18 boto3>=1.12.18
click>=7.1.2 click>=7.1.2
Flask>=1.0.2,<2.0.0 # Flask 2.0 is not compatible with the latest version of Flask-RESTful (0.3.8) Flask>=1.0.2,<2.0.0 # Flask 2.0 is not compatible with the latest version of Flask-RESTful (0.3.8)
Flask-Compress>=1.4.0 Flask-Compress>=1.4.0
Flask-Cors>=3.0.6 Flask-Cors>=3.0.9 # CVE-2020-25032
Flask-RESTful>=0.3.6 Flask-RESTful>=0.3.6
flask-server-timing>=0.1.2 flask-server-timing>=0.1.2
flask-talisman>=0.7.0 flask-talisman>=0.7.0
@@ -12,12 +12,12 @@ flatten-dict>=0.2.0
fsspec>=0.4.4,<0.8.0 fsspec>=0.4.4,<0.8.0
gunicorn>=20.0.4 gunicorn>=20.0.4
h5py>=3.0.0 h5py>=3.0.0
numba>=0.49.1,<0.53.0 numba>=0.51.2
numpy>=1.15.0 numpy>=1.17.5
packaging>=20.0 packaging>=20.0
pandas>=1.0,!=1.1 # pandas 1.1 breaks tests, https://github.com/pandas-dev/pandas/issues/35446 pandas>=1.0,!=1.1 # pandas 1.1 breaks tests, https://github.com/pandas-dev/pandas/issues/35446
PyYAML>=5.3 PyYAML>=5.4 # CVE-2020-14343
scipy>=1.0 scipy>=1.4
requests>=2.22.0 requests>=2.22.0
tiledb>=0.5.9,>=0.6.2,!=0.7.2, !=0.8.6 tiledb>=0.5.9,>=0.6.2,!=0.7.2, !=0.8.6
s3fs==0.4.2 s3fs==0.4.2
+4 -4
View File
@@ -151,8 +151,8 @@ def dataset_args(func):
help="URL providing more information about the dataset (hint: must be a fully specified absolute URL).", help="URL providing more information about the dataset (hint: must be a fully specified absolute URL).",
) )
@click.option( @click.option(
"--X-approx-distribution", "--X-approximate-distribution",
default=DEFAULT_CONFIG.dataset_config.X_approx_distribution, default=DEFAULT_CONFIG.dataset_config.X_approximate_distribution,
show_default=True, show_default=True,
type=click.Choice(["auto", "normal", "count"], case_sensitive=False), type=click.Choice(["auto", "normal", "count"], case_sensitive=False),
help="Specify the approximate distribution of X matrix values. 'auto' will use a heuristic " help="Specify the approximate distribution of X matrix values. 'auto' will use a heuristic "
@@ -326,7 +326,7 @@ def launch(
disable_diffexp, disable_diffexp,
config_file, config_file,
dump_default_config, dump_default_config,
x_approx_distribution, x_approximate_distribution,
): ):
"""Launch the cellxgene data viewer. """Launch the cellxgene data viewer.
This web app lets you explore single-cell expression data. This web app lets you explore single-cell expression data.
@@ -385,7 +385,7 @@ def launch(
embeddings__names=embedding, embeddings__names=embedding,
diffexp__enable=not disable_diffexp, diffexp__enable=not disable_diffexp,
diffexp__lfc_cutoff=diffexp_lfc_cutoff, diffexp__lfc_cutoff=diffexp_lfc_cutoff,
X_approx_distribution=x_approx_distribution, X_approximate_distribution=x_approximate_distribution,
) )
diff = cli_config.server_config.changes_from_default() diff = cli_config.server_config.changes_from_default()
@@ -38,7 +38,7 @@ class DatasetConfig(BaseConfig):
self.diffexp__lfc_cutoff = default_config["diffexp"]["lfc_cutoff"] self.diffexp__lfc_cutoff = default_config["diffexp"]["lfc_cutoff"]
self.diffexp__top_n = default_config["diffexp"]["top_n"] self.diffexp__top_n = default_config["diffexp"]["top_n"]
self.X_approx_distribution = default_config["X_approx_distribution"] self.X_approximate_distribution = default_config["X_approximate_distribution"]
except KeyError as e: except KeyError as e:
raise ConfigurationError(f"Unexpected config: {str(e)}") raise ConfigurationError(f"Unexpected config: {str(e)}")
@@ -52,7 +52,7 @@ class DatasetConfig(BaseConfig):
self.handle_user_annotations(context) self.handle_user_annotations(context)
self.handle_embeddings() self.handle_embeddings()
self.handle_diffexp(context) self.handle_diffexp(context)
self.handle_X_approx_distribution() self.handle_X_approximate_distribution()
def get_data_adaptor(self): def get_data_adaptor(self):
server_config = self.app_config.server_config server_config = self.app_config.server_config
@@ -186,9 +186,9 @@ class DatasetConfig(BaseConfig):
"CAUTION: due to the size of your dataset, " "running differential expression may take longer or fail." "CAUTION: due to the size of your dataset, " "running differential expression may take longer or fail."
) )
def handle_X_approx_distribution(self): def handle_X_approximate_distribution(self):
self.validate_correct_type_of_configuration_attribute("X_approx_distribution", str) self.validate_correct_type_of_configuration_attribute("X_approximate_distribution", str)
if self.X_approx_distribution not in ["auto", "normal", "count"]: if self.X_approximate_distribution not in ["auto", "normal", "count"]:
raise ConfigurationError( raise ConfigurationError(
"X_approx_distribution has unknown value -- must be 'auto', 'normal' or 'count'." "X_approximate_distribution has unknown value -- must be 'auto', 'normal' or 'count'."
) )
+15 -11
View File
@@ -9,7 +9,7 @@ from scipy import sparse
import backend.common.compute.diffexp_generic as diffexp_generic import backend.common.compute.diffexp_generic as diffexp_generic
import backend.common.compute.estimate_distribution as estimate_distribution import backend.common.compute.estimate_distribution as estimate_distribution
from backend.common.colors import convert_anndata_category_colors_to_cxg_category_colors from backend.common.colors import convert_anndata_category_colors_to_cxg_category_colors
from backend.common.constants import Axis, MAX_LAYOUTS, XApproxDistribution from backend.common.constants import Axis, MAX_LAYOUTS, XApproximateDistribution
from backend.server.common.corpora import corpora_get_props_from_anndata from backend.server.common.corpora import corpora_get_props_from_anndata
from backend.common.errors import PrepareError, DatasetAccessError from backend.common.errors import PrepareError, DatasetAccessError
from backend.common.utils.type_conversion_utils import get_schema_type_hint_of_array from backend.common.utils.type_conversion_utils import get_schema_type_hint_of_array
@@ -29,7 +29,7 @@ class AnndataAdaptor(DataAdaptor):
def __init__(self, data_locator, app_config=None, dataset_config=None): def __init__(self, data_locator, app_config=None, dataset_config=None):
super().__init__(data_locator, app_config, dataset_config) super().__init__(data_locator, app_config, dataset_config)
self.data = None self.data = None
self.X_approx_distribution = None self.X_approximate_distribution = None
self._load_data(data_locator) self._load_data(data_locator)
self._validate_and_initialize() self._validate_and_initialize()
@@ -125,7 +125,11 @@ class AnndataAdaptor(DataAdaptor):
def _create_schema(self): def _create_schema(self):
self.schema = { self.schema = {
"dataframe": {"nObs": self.cell_count, "nVar": self.gene_count, "type": str(self.data.X.dtype)}, "dataframe": {
"nObs": self.cell_count,
"nVar": self.gene_count,
**get_schema_type_hint_of_array(self.data.X),
},
"annotations": { "annotations": {
"obs": {"index": self.parameters.get("obs_names"), "columns": []}, "obs": {"index": self.parameters.get("obs_names"), "columns": []},
"var": {"index": self.parameters.get("var_names"), "columns": []}, "var": {"index": self.parameters.get("var_names"), "columns": []},
@@ -192,12 +196,12 @@ class AnndataAdaptor(DataAdaptor):
self.gene_count = self.data.shape[1] self.gene_count = self.data.shape[1]
self._create_schema() self._create_schema()
if self.dataset_config.X_approx_distribution == "auto": if self.dataset_config.X_approximate_distribution == "auto":
"""Lazy evaluate the heuristic if we are backed.""" """Lazy evaluate the heuristic if we are backed."""
if not self.data.isbacked: if not self.data.isbacked:
self.X_approx_distribution = estimate_distribution.estimate_approximate_distribution(self.data.X) self.X_approximate_distribution = estimate_distribution.estimate_approximate_distribution(self.data.X)
else: else:
self.X_approx_distribution = self.dataset_config.X_approx_distribution self.X_approximate_distribution = self.dataset_config.X_approximate_distribution
# heuristic # heuristic
n_values = self.data.shape[0] * self.data.shape[1] n_values = self.data.shape[0] * self.data.shape[1]
@@ -331,15 +335,15 @@ class AnndataAdaptor(DataAdaptor):
X = self.data.X[obs_mask, var_mask] X = self.data.X[obs_mask, var_mask]
return X return X
def get_X_approx_distribution(self) -> XApproxDistribution: def get_X_approximate_distribution(self) -> XApproximateDistribution:
"""return the approximate distribution of the X matrix.""" """return the approximate distribution of the X matrix."""
if self.X_approx_distribution is None: if self.X_approximate_distribution is None:
"""Not yet evaluated.""" """Not yet evaluated."""
assert(self.dataset_config.X_approx_distribution == "auto") assert self.dataset_config.X_approximate_distribution == "auto"
self.data = self.data.to_memory() # loads data self.data = self.data.to_memory() # loads data
self.X_approx_distribution = estimate_distribution.estimate_approximate_distribution(self.data.X) self.X_approximate_distribution = estimate_distribution.estimate_approximate_distribution(self.data.X)
return self.X_approx_distribution return self.X_approximate_distribution
def get_shape(self): def get_shape(self):
return self.data.shape return self.data.shape
+3 -3
View File
@@ -6,7 +6,7 @@ from scipy import sparse
from server_timing import Timing as ServerTiming from server_timing import Timing as ServerTiming
from backend.server.common.config.app_config import AppConfig from backend.server.common.config.app_config import AppConfig
from backend.common.constants import Axis, XApproxDistribution from backend.common.constants import Axis, XApproximateDistribution
from backend.common.errors import FilterError, JSONEncodingValueError, ExceedsLimitError, UnsupportedSummaryMethod from backend.common.errors import FilterError, JSONEncodingValueError, ExceedsLimitError, UnsupportedSummaryMethod
from backend.common.utils.utils import jsonify_numpy from backend.common.utils.utils import jsonify_numpy
from backend.common.fbs.matrix import encode_matrix_fbs from backend.common.fbs.matrix import encode_matrix_fbs
@@ -72,9 +72,9 @@ class DataAdaptor(metaclass=ABCMeta):
the return type is either ndarray or scipy.sparse.spmatrix.""" the return type is either ndarray or scipy.sparse.spmatrix."""
pass pass
def get_X_approx_distribution(self) -> XApproxDistribution: def get_X_approximate_distribution(self) -> XApproximateDistribution:
"""return the approximate distribution of the X matrix.""" """return the approximate distribution of the X matrix."""
return XApproxDistribution.NORMAL return XApproximateDistribution.NORMAL
@abstractmethod @abstractmethod
def get_shape(self): def get_shape(self):
+1 -1
View File
@@ -77,7 +77,7 @@ dataset:
lfc_cutoff: 0.01 lfc_cutoff: 0.01
top_n: 10 top_n: 10
X_approx_distribution: auto X_approximate_distribution: auto
external: external:
# You can retrieve configuration parameters from this config file, the environment, # You can retrieve configuration parameters from this config file, the environment,
+1 -1
View File
@@ -31,5 +31,5 @@ dataset:
lfc_cutoff: {lfc_cutoff} lfc_cutoff: {lfc_cutoff}
top_n: {top_n} top_n: {top_n}
X_approx_distribution: {X_approx_distribution} X_approximate_distribution: {X_approximate_distribution}
""" """
+1 -1
View File
@@ -28,5 +28,5 @@ dataset:
lfc_cutoff: {lfc_cutoff} lfc_cutoff: {lfc_cutoff}
top_n: {top_n} top_n: {top_n}
X_approx_distribution: {X_approx_distribution} X_approximate_distribution: {X_approximate_distribution}
""" """
+1 -1
View File
@@ -16,6 +16,6 @@ summary test,,F5,
summary test,,PIGU, summary test,,PIGU,
geneset_to_delete,,, geneset_to_delete,,,
geneset_to_edit,,, geneset_to_edit,,,
fill_this_geneset,,RER1, fill_this_geneset,,,
empty_this_geneset,,SIK1, empty_this_geneset,,SIK1,
brush_this_gene,,SIK1, brush_this_gene,,SIK1,
1 # Test fixture
16 summary test,,PIGU,
17 geneset_to_delete,,,
18 geneset_to_edit,,,
19 fill_this_geneset,,RER1, fill_this_geneset,,,
20 empty_this_geneset,,SIK1,
21 brush_this_gene,,SIK1,
Binary file not shown.
@@ -2,16 +2,20 @@ import unittest
import pandas as pd import pandas as pd
import numpy as np import numpy as np
from scipy import sparse from scipy import sparse
from parameterized import parameterized_class
import json
import backend.test.decode_fbs as decode_fbs from backend.test import decode_fbs
from backend.common.fbs.matrix import encode_matrix_fbs, decode_matrix_fbs from backend.common.fbs.matrix import encode_matrix_fbs, decode_matrix_fbs
from backend.common.utils.type_conversion_utils import get_dtypes_and_schemas_of_dataframe
import backend.common.fbs as fbs
class FbsTests(unittest.TestCase): class FbsTests(unittest.TestCase):
"""Test Case for Matrix FBS data encode/decode """ """Test Case for Matrix FBS data encode/decode"""
def test_encode_boundary(self): def test_encode_boundary(self):
""" test various boundary checks """ """test various boundary checks"""
# row indexing is unsupported # row indexing is unsupported
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
@@ -46,7 +50,7 @@ class FbsTests(unittest.TestCase):
"d": pd.Series(["x", "y", "z", "x", "y", "z", "a", "x", "y", "z"], dtype="category"), "d": pd.Series(["x", "y", "z", "x", "y", "z", "a", "x", "y", "z"], dtype="category"),
} }
) )
expected_types = ((np.ndarray, np.float32), (np.ndarray, np.int32), (np.ndarray, np.uint32), (list, None)) expected_types = ((np.ndarray, np.float32), (np.ndarray, np.int32), (np.ndarray, np.int32), (list, None))
fbs = encode_matrix_fbs(matrix=df, row_idx=None, col_idx=df.columns) fbs = encode_matrix_fbs(matrix=df, row_idx=None, col_idx=df.columns)
self.fbs_checks(fbs, (10, 4), expected_types, ["a", "b", "c", "d"]) self.fbs_checks(fbs, (10, 4), expected_types, ["a", "b", "c", "d"])
@@ -80,3 +84,133 @@ class FbsTests(unittest.TestCase):
self.assertTrue(np.all(dfSrc[c] == dfDst[c])) self.assertTrue(np.all(dfSrc[c] == dfDst[c]))
else: else:
self.assertEqual(dfSrc[c], dfDst[c]) self.assertEqual(dfSrc[c], dfDst[c])
"""
Test type consistency between FBS encoding and the underlying schema hint.
Basic assertion: the FBS type returned by encode_matrix_fbs() will be consistent
with the schema hint returned by type_conversion_utils (which is in turn used
to create the client schema).
The following test cases are all dicts which contain the following keys:
- dataframe - the dataframe used as input for encode_matrix_fbs
- expected_fbs_types - upon success, dict of FBS column types expected (eg, Float32Array)
- expected_schema_hints - upon success, dict of schema hint
All are keyed by column name.
"""
# simple tests that we convert all ints to int32
int_dtypes = [np.dtype(d) for d in [np.int8, np.uint8, np.int16, np.uint16, np.int32, np.uint32, np.int64, np.uint64]]
int_test_cases = [
{
"dataframe": pd.DataFrame({dtype.name: np.zeros((10,), dtype=dtype) for dtype in int_dtypes}),
"expected_fbs_types": dict(
[(dtype.name, fbs.NetEncoding.TypedArray.TypedArray.Int32Array) for dtype in int_dtypes]
),
"expected_schema_hints": dict([(dtype.name, {"type": "int32"}) for dtype in int_dtypes]),
}
]
# simple tests that we convert all floats to float32
float_dtypes = [np.dtype(d) for d in [np.float16, np.float32, np.float64]]
float_test_cases = [
{
"dataframe": pd.DataFrame({dtype.name: np.zeros((10,), dtype=dtype) for dtype in float_dtypes}),
"expected_fbs_types": dict(
[(dtype.name, fbs.NetEncoding.TypedArray.TypedArray.Float32Array) for dtype in float_dtypes]
),
"expected_schema_hints": dict([(dtype.name, {"type": "float32"}) for dtype in float_dtypes]),
}
]
# boolean - should be encoded as an uint32
bool_dtypes = [np.dtype(d) for d in [np.bool_, bool]]
bool_test_cases = [
{
"dataframe": pd.DataFrame({dtype.name: np.ones((10,), dtype=dtype) for dtype in bool_dtypes}),
"expected_fbs_types": dict(
[(dtype.name, fbs.NetEncoding.TypedArray.TypedArray.Uint32Array) for dtype in bool_dtypes]
),
"expected_schema_hints": dict([(dtype.name, {"type": "boolean"}) for dtype in bool_dtypes]),
}
]
cat_test_cases = [
{
"dataframe": pd.DataFrame({"a": pd.Series(["a", "b", "c", "a", "b", "c"], dtype="category")}),
"expected_fbs_types": {"a": fbs.NetEncoding.TypedArray.TypedArray.JSONEncodedArray},
"expected_schema_hints": {"a": {"type": "categorical", "categories": ["a", "b", "c"]}},
},
{
"dataframe": pd.DataFrame(
{"a": pd.Series(["a", "b", "c", "a", "b", "c"], dtype="category").cat.remove_categories("b")}
),
"expected_fbs_types": {"a": fbs.NetEncoding.TypedArray.TypedArray.JSONEncodedArray},
"expected_schema_hints": {"a": {"type": "categorical", "categories": ["a", "c"]}},
},
{
"dataframe": pd.DataFrame({"a": pd.Series(np.arange(0, 10, dtype=np.int64), dtype="category")}),
"expected_fbs_types": {"a": fbs.NetEncoding.TypedArray.TypedArray.Int32Array},
"expected_schema_hints": {"a": {"type": "categorical"}},
},
{
"dataframe": pd.DataFrame(
{"a": pd.Series(np.arange(0, 10, dtype=np.int64), dtype="category").cat.remove_categories(2)}
),
"expected_fbs_types": {"a": fbs.NetEncoding.TypedArray.TypedArray.Float32Array},
"expected_schema_hints": {"a": {"type": "categorical"}},
},
{
"dataframe": pd.DataFrame({"a": pd.Series(np.arange(0, 10, dtype=np.float64), dtype="category")}),
"expected_fbs_types": {"a": fbs.NetEncoding.TypedArray.TypedArray.Float32Array},
"expected_schema_hints": {"a": {"type": "categorical"}},
},
{
"dataframe": pd.DataFrame(
{"a": pd.Series(np.arange(0, 10, dtype=np.float64), dtype="category").cat.remove_categories(2)}
),
"expected_fbs_types": {"a": fbs.NetEncoding.TypedArray.TypedArray.Float32Array},
"expected_schema_hints": {"a": {"type": "categorical"}},
},
]
test_cases = [
*int_test_cases,
*float_test_cases,
*bool_test_cases,
*cat_test_cases,
]
@parameterized_class(test_cases)
class TestTypeConversionConsistency(unittest.TestCase):
def test_type_conversion_consistency(self):
self.assertEqual(self.dataframe.shape[1], len(self.expected_fbs_types))
self.assertEqual(self.dataframe.shape[1], len(self.expected_schema_hints))
buf = encode_matrix_fbs(matrix=self.dataframe, col_idx=self.dataframe.columns)
encoding_dtypes, schema_hints = get_dtypes_and_schemas_of_dataframe(self.dataframe)
# check schema hints
# print(schema_hints)
# print(self.expected_schema_hints)
self.assertEqual(schema_hints, self.expected_schema_hints)
# inspect the FBS types
matrix = fbs.NetEncoding.Matrix.Matrix.GetRootAsMatrix(buf, 0)
columns_length = matrix.ColumnsLength()
self.assertEqual(columns_length, self.dataframe.shape[1])
self.assertEqual(matrix.ColIndexType(), fbs.NetEncoding.TypedArray.TypedArray.JSONEncodedArray)
col_labels_arr = fbs.NetEncoding.JSONEncodedArray.JSONEncodedArray()
col_labels_arr.Init(matrix.ColIndex().Bytes, matrix.ColIndex().Pos)
col_index_labels = json.loads(col_labels_arr.DataAsNumpy().tobytes().decode("utf-8"))
self.assertEqual(len(col_index_labels), self.dataframe.shape[1])
for col_idx in range(0, columns_length):
col_label = col_index_labels[col_idx]
col = matrix.Columns(col_idx)
col_type = col.UType()
self.assertEqual(self.expected_fbs_types[col_label], col_type)
@@ -1,179 +1,22 @@
import unittest import unittest
from time import time import logging
from unittest.mock import patch from parameterized import parameterized_class
import numpy as np import numpy as np
import pandas as pd
from pandas import Series, DataFrame from pandas import Series, DataFrame
from scipy import sparse
from backend.common.utils.type_conversion_utils import ( from backend.common.utils.type_conversion_utils import (
can_cast_to_float32, get_encoding_dtype_of_array,
can_cast_to_int32,
get_dtype_of_array,
get_schema_type_hint_of_array, get_schema_type_hint_of_array,
get_dtypes_and_schemas_of_dataframe, get_dtypes_and_schemas_of_dataframe,
convert_pandas_series_to_numpy, get_dtype_and_schema_of_array,
get_schema_type_hint_from_dtype,
) )
class TestTypeConversionUtils(unittest.TestCase): class TestTypeConversionUtils(unittest.TestCase):
def test__can_cast_to_float32__string_is_false(self):
array_to_convert = Series(data=["1", "2", "3"], dtype=str)
can_cast = can_cast_to_float32(array_to_convert.dtype, array_to_convert)
self.assertFalse(can_cast)
def test__can_cast_to_float32__float64_is_true_warning_outputted(self):
array_to_convert = Series(data=[1, 2, 3], dtype=np.dtype(np.float64))
with self.assertLogs(level="WARN") as logger:
can_cast = can_cast_to_float32(array_to_convert.dtype, array_to_convert)
self.assertIn("may lose precision", logger.output[0])
self.assertTrue(can_cast)
@patch("logging.warning")
def test__can_cast_to_float32__float32_is_false(self, mock_log_warning):
array_to_convert = Series(data=[1, 2, 3], dtype=np.dtype(np.float32))
can_cast = can_cast_to_float32(array_to_convert.dtype, array_to_convert)
self.assertTrue(can_cast)
assert not mock_log_warning.called
def test__can_cast_to_float32__categorical_float64_is_false(self):
array_to_convert = Series(data=[1.1, 2.2, 3.3], dtype="category")
can_cast = can_cast_to_float32(array_to_convert.dtype, array_to_convert)
self.assertFalse(can_cast)
def test__can_cast_to_float32__categorical_int64_with_nans_is_true(self):
array_to_convert = Series(data=[1, 2, np.NaN], dtype="category")
can_cast = can_cast_to_float32(array_to_convert.dtype, array_to_convert)
self.assertTrue(can_cast)
def test__can_cast_to_float_32__float_32_with_nans_is_true(self):
array_to_convert = Series(data=[1, 2, np.NaN], dtype=np.dtype(np.float32))
can_cast = can_cast_to_float32(array_to_convert.dtype, array_to_convert)
self.assertTrue(can_cast)
def test__can_cast_to_int32__string_is_false(self):
array_to_convert = Series(data=["1", "2", "3"], dtype=str)
can_cast = can_cast_to_int32(array_to_convert.dtype, array_to_convert)
self.assertFalse(can_cast)
def test__can_cast_to_int32__int64_is_true(self):
array_to_convert = Series(data=[1, 2, 3], dtype=np.dtype(np.int64))
can_cast = can_cast_to_int32(array_to_convert.dtype, array_to_convert)
self.assertTrue(can_cast)
def test__can_cast_to_int32__int16_is_true(self):
array_to_convert = Series(data=[1, 2, 3], dtype=np.dtype(np.int16))
can_cast = can_cast_to_int32(array_to_convert.dtype, array_to_convert)
self.assertTrue(can_cast)
def test__can_cast_to_int32__int64_with_large_value_is_false(self):
array_to_convert = Series(data=[3000000000, 2, 3], dtype=np.dtype(np.int64))
can_cast = can_cast_to_int32(array_to_convert.dtype, array_to_convert)
self.assertFalse(can_cast)
def test__can_cast_to_int32__int64_with_nans_is_false(self):
array_to_convert = Series(data=[np.NaN, "2", "3"], dtype="category")
can_cast = can_cast_to_int32(array_to_convert.dtype, array_to_convert)
self.assertFalse(can_cast)
def test__get_dtype_of_array__supported_dtypes_return_as_expected(self):
types = [np.float32, np.int32, np.bool_, str]
expected_dtypes = [np.float32, np.int32, np.uint8, str]
for test_type_index in range(len(types)):
with self.subTest(
f"Testing get_dtype_of_array with type {types[test_type_index].__name__}", i=test_type_index
):
array = Series(data=[], dtype=types[test_type_index])
self.assertEqual(get_dtype_of_array(array), expected_dtypes[test_type_index])
def test__get_dtype_of_array__categories_return_as_expected(self):
array = Series(data=["a", "b", "c"], dtype="category")
expected_dtype = str
actual_dtype = get_dtype_of_array(array)
self.assertEqual(expected_dtype, actual_dtype)
def test__get_dtype_of_array__unordered_integer_categories_return_as_expected(self):
array = Series(data=[2, 3, 1, 3, 1, 2], dtype="category")
expected_dtype = np.int32
actual_dtype = get_dtype_of_array(array)
self.assertEqual(expected_dtype, actual_dtype)
def test__get_dtype_of_array__castable_dtypes_return_as_expected(self):
types = [np.float64, np.int64]
expected_dtypes = [np.float32, np.int32]
for test_type_index in range(len(types)):
with self.subTest(
f"Testing get_dtype_of_array with castable type {types[test_type_index].__name__}", i=test_type_index
):
array = Series(data=[], dtype=types[test_type_index])
self.assertEqual(get_dtype_of_array(array), expected_dtypes[test_type_index])
def test__get_dtype_of_array__unsupported_type_raises_exception(self):
unsupported_array = Series(list([time() for _ in range(2)]), dtype="datetime64[ns]")
with self.assertRaises(TypeError) as exception_context:
get_dtype_of_array(unsupported_array)
self.assertIn("unsupported", str(exception_context.exception))
def test__get_schema_type_hint_of_array__supported_dtypes_return_as_expected(self):
types = [np.float32, np.int32, np.bool_, str]
expected_schema_hints = [{"type": "float32"}, {"type": "int32"}, {"type": "boolean"}, {"type": "string"}]
for test_type_index in range(len(types)):
with self.subTest(
f"Testing get_schema_type_hint_of_array with type {types[test_type_index].__name__}", i=test_type_index
):
array = Series(data=[], dtype=types[test_type_index])
self.assertEqual(get_schema_type_hint_of_array(array), expected_schema_hints[test_type_index])
def test__get_schema_type_hint_of_array__categories_return_as_expected(self):
array = Series(data=["a", "b", "b"], dtype="category")
expected_schema_hint = {"type": "categorical", "categories": ["a", "b"]}
actual_schema_hint = get_schema_type_hint_of_array(array)
self.assertEqual(expected_schema_hint, actual_schema_hint)
def test__get_schema_type_hint_of_array__castable_dtypes_return_as_expected(self):
types = [np.float64, np.int64]
expected_schema_hints = [{"type": "float32"}, {"type": "int32"}]
for test_type_index in range(len(types)):
with self.subTest(
f"Testing get_schema_type_hint_of_array with castable type {types[test_type_index].__name__}",
i=test_type_index,
):
array = Series(data=[], dtype=types[test_type_index])
self.assertEqual(get_schema_type_hint_of_array(array), expected_schema_hints[test_type_index])
def test__get_dtypes_and_schemas_of_dataframe__dtype_and_schema_returns_as_expected(self): def test__get_dtypes_and_schemas_of_dataframe__dtype_and_schema_returns_as_expected(self):
float_array = Series(data=[1, 2, 3], dtype=np.dtype(np.float64)) float_array = Series(data=[1, 2, 3], dtype=np.dtype(np.float64))
category_array = Series(data=["a", "b", "b"], dtype="category") category_array = Series(data=["a", "b", "b"], dtype="category")
@@ -190,28 +33,292 @@ class TestTypeConversionUtils(unittest.TestCase):
self.assertEqual(expected_data_types_dict, actual_dataframe_data_types) self.assertEqual(expected_data_types_dict, actual_dataframe_data_types)
self.assertEqual(expected_schema_type_hints_dict, actual_dataframe_schema_type_hints) self.assertEqual(expected_schema_type_hints_dict, actual_dataframe_schema_type_hints)
def test__convert_pandas_series_to_numpy__categorical_float64_to_float64_with_nans(self): def test__get_schema_type_hint_from_dtype(self):
expected_float_array = np.array([1.1, 2.2, np.NaN], dtype=np.float64) self.assertEqual(get_schema_type_hint_from_dtype(np.dtype(np.bool_)), {"type": "boolean"})
float_series = Series(data=[1.1, 2.2, np.NaN], dtype="category")
actual_float_array = convert_pandas_series_to_numpy(float_series, np.float64) for dtype in [np.int8, np.int8, np.int16, np.uint16, np.int32]:
self.assertEqual(get_schema_type_hint_from_dtype(np.dtype(dtype)), {"type": "int32"})
for dtype in [np.uint32, np.int64, np.uint64]:
with self.assertRaises(TypeError):
get_schema_type_hint_from_dtype(np.dtype(dtype))
np.testing.assert_equal(expected_float_array, actual_float_array) for dtype in [np.float16, np.float32, np.float64]:
self.assertEqual(get_schema_type_hint_from_dtype(np.dtype(dtype)), {"type": "float32"})
def test__convert_pandas_series_to_numpy__float64_to_float64(self): for dtype in [np.dtype(object), np.dtype(str)]:
expected_float_array = np.array([1.1, 2.2], dtype=np.float64) self.assertEqual(get_schema_type_hint_from_dtype(dtype), {"type": "string"})
float_series = Series(data=[1.1, 2.2], dtype=np.dtype(np.float64))
actual_float_array = convert_pandas_series_to_numpy(float_series, np.float64)
np.testing.assert_equal(expected_float_array, actual_float_array) # Credit: https://stackoverflow.com/questions/35871815/python-3-unit-testing-assert-logger-not-called/64774103#64774103
class AssertNoLog:
def assertNoLogs(self, logger, level):
"""functions as a context manager. To be introduced in python 3.10"""
def test__convert_pandas_series_to_numpy__int64_to_int32_with_nans_throws_error(self): class AssertNoLogsContext(unittest.TestCase):
int_series = Series(data=[1, 2, np.NaN], dtype="category") def __init__(self, logger, level):
self.logger = logger
self.level = level
self.context = self.assertLogs(logger, level)
with self.assertLogs(level="ERROR") as logger: def __enter__(self):
convert_pandas_series_to_numpy(int_series, np.int32) """enter self.assertLogs as context manager, and log something"""
self.initial_logmsg = "sole message"
self.cm = self.context.__enter__()
self.logger.log(self.level, self.initial_logmsg)
return self.cm
self.assertIn( def __exit__(self, exc_type, exc_val, exc_tb):
"Cannot convert a pandas Series object to an integer dtype if it contains NaNs", logger.output[0] """cleanup logs, and then check nothing extra was logged"""
) # assertLogs.__exit__ should never fail because of initial msg
self.context.__exit__(exc_type, exc_val, exc_tb)
if len(self.cm.output) > 1:
"""override any exception passed to __exit__"""
self.context._raiseFailure(
"logs of level {} or higher triggered on {} : {}".format(
logging.getLevelName(self.level), self.logger.name, self.cm.output[1:]
)
)
return AssertNoLogsContext(logger, level)
"""
See table of expected cases in type_conversion_utils.py.
This probes all edge cases. Each case is a dict containing keys:
- data - the array to be introspected
- throws - if not None, the expected Error (eg, TypeError)
- expected_encoding_dtype - upon success
- expected_schema_hint - upon success
- logs - if not None, specify expected log output
"""
bool_OK_cases = [
{
"data": data,
"expected_encoding_dtype": np.uint8,
"expected_schema_hint": {"type": "boolean"},
}
for data in [
np.array([0, 1, 0, 1], dtype=np.bool_),
pd.Series(np.array([0, 1, 0, 1], dtype=np.bool_)),
# pd.Index with bools doesn't really make any sense...and becomes dtype=object
]
]
int_OK_cases = [
{
"data": data,
"expected_encoding_dtype": np.int32,
"expected_schema_hint": {"type": "int32"},
}
for dtype in [np.int8, np.uint8, np.int16, np.uint16, np.int32, np.uint32, np.int64, np.uint64]
for data in [
np.arange(0, 1000, dtype=dtype),
pd.Series(np.arange(0, 1000, dtype=dtype)),
pd.Index(np.arange(0, 1000, dtype=dtype)),
sparse.csr_matrix((10, 100), dtype=dtype),
]
]
float_OK_cases = [
{
"data": data,
"expected_encoding_dtype": np.float32,
"expected_schema_hint": {"type": "float32"},
"logs": None if data.dtype != np.float64 else {"level": logging.WARNING, "output": "may lose precision"},
}
for dtype in [np.float16, np.float32, np.float64]
for data in [
np.arange(-128, 1000, dtype=dtype),
pd.Series(np.arange(-128, 1000, dtype=dtype)),
pd.Index(np.arange(-129, 1000, dtype=dtype)),
np.array([-np.nan, np.NINF, -1, np.NZERO, 0, np.PZERO, 1, np.PINF, np.nan], dtype=dtype),
np.array([np.finfo(dtype).min, 0, np.finfo(dtype).max], dtype=dtype),
sparse.csr_matrix((10, 100), dtype=dtype),
]
]
numeric_ERR_cases = [
{
"data": data,
"throws": TypeError,
}
for data in [
np.array([np.iinfo(np.int64).min, np.iinfo(np.int64).max], dtype=np.int64),
np.array([np.iinfo(np.uint64).min, np.iinfo(np.uint64).max], dtype=np.uint64),
np.array([np.iinfo(np.uint32).min, np.iinfo(np.uint32).max], dtype=np.uint32),
]
]
string_OK_cases = [
{
"data": data,
"expected_encoding_dtype": np.dtype(str),
"expected_schema_hint": {"type": "string"},
}
for data in [
np.array(["a", "b", "c"]),
np.array(["a", "b", "c"], dtype="object"),
pd.Series(["a", "b", "c"]),
pd.Index(["a", "b", "c"]),
np.array(["a", [], {}, None, True, False, 383.2], dtype="object"),
]
]
category_nonnumeric_OK_cases = [
{
"data": data,
"expected_encoding_dtype": np.dtype(str),
"expected_schema_hint": {"type": "categorical", "categories": data.dtype.categories.to_list()},
}
for data in [
pd.Series(["a", "b", "c"], dtype="category"),
pd.Series(["a", "b", "c", 0, 1, 2], dtype="category"),
pd.Series(["a", "b", "c"], dtype="category").cat.remove_categories(["b"]),
pd.Series(["a", "b", "c", 0, 1, 2], dtype="category").cat.remove_categories(["b", 0]),
]
]
category_numeric_OK_cases = [
# numeric, no NA/NaN, int
*[
{
"data": data,
"expected_encoding_dtype": np.int32,
"expected_schema_hint": {"type": "categorical"},
}
for dtype in [np.int8, np.uint8, np.int16, np.uint16, np.int32, np.uint32, np.int64, np.uint64]
for data in [
pd.Series(np.array([0, 1, 2], dtype=dtype), dtype="category"),
]
],
# numeric, no NA/NaN, float
*[
{
"data": data,
"expected_encoding_dtype": np.float32,
"expected_schema_hint": {"type": "categorical"},
"logs": {"level": logging.WARNING, "output": "may lose precision"},
}
for dtype in [np.float16, np.float32, np.float64]
for data in [
pd.Series(np.array([0, 1, 2], dtype=dtype), dtype="category"),
pd.Series(np.array([0, 1, 2], dtype=dtype), dtype="category").cat.remove_categories([1]),
pd.Categorical(np.array([0, 1, 2], dtype=dtype)),
]
],
# numeric, has NA-induced cast to float32
*[
{
"data": data,
"expected_encoding_dtype": np.float32,
"expected_schema_hint": {"type": "categorical"},
"logs": {"level": logging.WARNING, "output": "may lose precision"},
}
for dtype in [
np.int8,
np.uint8,
np.int16,
np.uint16,
np.int32,
np.uint32,
np.int64,
np.uint64,
np.float16,
np.float32,
np.float64,
]
for data in [
pd.Series(np.array([0, 1, 2], dtype=dtype), dtype="category").cat.remove_categories([1]),
pd.Categorical(np.array([0, 1, 2], dtype=dtype), categories=np.array([0, 1], dtype=dtype)),
]
],
]
category_ERR_cases = [
# catch expected categorical exceptions for Int64(etc) that have large values
{
"data": data,
"throws": TypeError,
}
for data in [
pd.Categorical(np.array([np.iinfo(np.int64).min, np.iinfo(np.int64).max], dtype=np.int64)),
pd.Categorical(np.array([np.iinfo(np.uint64).min, np.iinfo(np.uint64).max], dtype=np.uint64)),
pd.Categorical(np.array([np.iinfo(np.uint32).min, np.iinfo(np.uint32).max], dtype=np.uint32)),
]
]
object_OK_cases = [
{
"data": data,
"expected_encoding_dtype": np.dtype(str),
"expected_schema_hint": {"type": "string"},
}
for data in [
np.array(["a", True, 1, [], {}], dtype="object"),
pd.Series(["a", True, 1, [], {}], dtype="object"),
pd.Index(["a", True, 1, [], {}], dtype="object"),
]
]
err_cases = [
{"data": np.array, "throws": TypeError}
for data in [
np.ones((10,), dtype=np.complex64),
np.ones((10,), dtype=np.complex128),
np.array([b"foobar"], dtype=np.bytes_),
np.ones((10,), dtype=np.void),
np.arange("2005-02", "2005-03", dtype="datetime64[D]"),
np.arange("2005-02", "2005-03", dtype="datetime64[D]") - np.datetime64("2008-01-01"),
[],
{},
]
]
test_cases = [
*bool_OK_cases,
*int_OK_cases,
*float_OK_cases,
*numeric_ERR_cases,
*string_OK_cases,
*category_nonnumeric_OK_cases,
*category_numeric_OK_cases,
*category_ERR_cases,
*object_OK_cases,
*err_cases,
]
@parameterized_class(test_cases)
class TestTypeInference(unittest.TestCase, AssertNoLog):
def test_type_inference(self):
throws = getattr(self, "throws", None)
if throws:
with self.assertRaises(throws):
get_dtype_and_schema_of_array(self.data)
with self.assertRaises(throws):
get_encoding_dtype_of_array(self.data)
with self.assertRaises(throws):
get_schema_type_hint_of_array(self.data)
else:
logs = getattr(self, "logs", None)
if logs is not None:
with self.assertLogs(level=logs["level"]) as logger:
encoding_dtype, schema_hint = get_dtype_and_schema_of_array(self.data)
self.assertEqual(encoding_dtype, self.expected_encoding_dtype)
self.assertEqual(schema_hint, self.expected_schema_hint)
self.assertIn(logs["output"], logger.output[0])
else:
with self.assertNoLogs(logging.getLogger(), logging.WARNING):
encoding_dtype, schema_hint = get_dtype_and_schema_of_array(self.data)
self.assertEqual(encoding_dtype, self.expected_encoding_dtype)
self.assertEqual(schema_hint, self.expected_schema_hint)
# also test the other public API
self.assertEqual(get_encoding_dtype_of_array(self.data), self.expected_encoding_dtype)
self.assertEqual(get_schema_type_hint_of_array(self.data), self.expected_schema_hint)
@@ -131,7 +131,7 @@ class ConfigTests(BaseTest):
environment=None, environment=None,
aws_secrets_manager_region=None, aws_secrets_manager_region=None,
aws_secrets_manager_secrets=[], aws_secrets_manager_secrets=[],
X_approx_distribution="normal", X_approximate_distribution="normal",
config_file_name="app_config.yml", config_file_name="app_config.yml",
): ):
random_num = random.randrange(999999) random_num = random.randrange(999999)
@@ -195,7 +195,7 @@ class ConfigTests(BaseTest):
enable_difexp=enable_difexp, enable_difexp=enable_difexp,
lfc_cutoff=lfc_cutoff, lfc_cutoff=lfc_cutoff,
top_n=top_n, top_n=top_n,
X_approx_distribution=X_approx_distribution, X_approximate_distribution=X_approximate_distribution,
config_file_name=f"temp_dataset_config_{random_num}.yml", config_file_name=f"temp_dataset_config_{random_num}.yml",
) )
external_config = self.custom_external_config( external_config = self.custom_external_config(
@@ -231,7 +231,7 @@ class ConfigTests(BaseTest):
enable_difexp="true", enable_difexp="true",
lfc_cutoff=0.01, lfc_cutoff=0.01,
top_n=10, top_n=10,
X_approx_distribution="normal", X_approximate_distribution="normal",
config_file_name="dataset_config.yml", config_file_name="dataset_config.yml",
): ):
configfile = os.path.join(self.tmp_fixtures_directory, config_file_name) configfile = os.path.join(self.tmp_fixtures_directory, config_file_name)
@@ -444,7 +444,7 @@ class EndPointsCxg(EndPoints):
{'genes': [], 'geneset_description': '', 'geneset_name': 'geneset_to_delete'}, {'genes': [], 'geneset_description': '', 'geneset_name': 'geneset_to_delete'},
{'genes': [], 'geneset_description': '', 'geneset_name': 'geneset_to_edit'}, {'genes': [], 'geneset_description': '', 'geneset_name': 'geneset_to_edit'},
{ {
'genes': [{'gene_description': '', 'gene_symbol': 'RER1'}], 'genes': [],
'geneset_description': '', 'geneset_description': '',
'geneset_name': 'fill_this_geneset' 'geneset_name': 'fill_this_geneset'
}, },
@@ -485,7 +485,7 @@ summary test,,F5,\r
summary test,,PIGU,\r summary test,,PIGU,\r
geneset_to_delete,,,\r geneset_to_delete,,,\r
geneset_to_edit,,,\r geneset_to_edit,,,\r
fill_this_geneset,,RER1,\r fill_this_geneset,,,\r
empty_this_geneset,,SIK1,\r empty_this_geneset,,SIK1,\r
brush_this_gene,,SIK1,\r brush_this_gene,,SIK1,\r
""" """
@@ -20,7 +20,7 @@ class DiffExpTest(unittest.TestCase):
adaptor types and different algorithms.""" adaptor types and different algorithms."""
def load_dataset(self, path, extra_server_config={}, extra_dataset_config={}): def load_dataset(self, path, extra_server_config={}, extra_dataset_config={}):
extra_dataset_config["X_approx_distribution"] = "normal" # hardwired for now extra_dataset_config["X_approximate_distribution"] = "normal" # hardwired for now
config = app_config(path, extra_server_config=extra_server_config, extra_dataset_config=extra_dataset_config) config = app_config(path, extra_server_config=extra_server_config, extra_dataset_config=extra_dataset_config)
loader = MatrixDataLoader(path) loader = MatrixDataLoader(path)
adaptor = loader.open(config) adaptor = loader.open(config)
@@ -46,7 +46,7 @@ class FbsTests(unittest.TestCase):
"d": pd.Series(["x", "y", "z", "x", "y", "z", "a", "x", "y", "z"], dtype="category"), "d": pd.Series(["x", "y", "z", "x", "y", "z", "a", "x", "y", "z"], dtype="category"),
} }
) )
expected_types = ((np.ndarray, np.float32), (np.ndarray, np.int32), (np.ndarray, np.uint32), (list, None)) expected_types = ((np.ndarray, np.float32), (np.ndarray, np.int32), (np.ndarray, np.int32), (list, None))
fbs = encode_matrix_fbs(matrix=df, row_idx=None, col_idx=df.columns) fbs = encode_matrix_fbs(matrix=df, row_idx=None, col_idx=df.columns)
self.fbs_checks(fbs, (10, 4), expected_types, ["a", "b", "c", "d"]) self.fbs_checks(fbs, (10, 4), expected_types, ["a", "b", "c", "d"])
@@ -103,7 +103,7 @@ class ConfigTests(unittest.TestCase):
environment=None, environment=None,
aws_secrets_manager_region=None, aws_secrets_manager_region=None,
aws_secrets_manager_secrets=[], aws_secrets_manager_secrets=[],
X_approx_distribution="auto", X_approximate_distribution="auto",
config_file_name="app_config.yml", config_file_name="app_config.yml",
): ):
random_num = random.randrange(999999) random_num = random.randrange(999999)
@@ -151,7 +151,7 @@ class ConfigTests(unittest.TestCase):
enable_difexp=enable_difexp, enable_difexp=enable_difexp,
lfc_cutoff=lfc_cutoff, lfc_cutoff=lfc_cutoff,
top_n=top_n, top_n=top_n,
X_approx_distribution=X_approx_distribution, X_approximate_distribution=X_approximate_distribution,
config_file_name=f"temp_dataset_config_{random_num}.yml", config_file_name=f"temp_dataset_config_{random_num}.yml",
) )
external_config = self.custom_external_config( external_config = self.custom_external_config(
@@ -187,7 +187,7 @@ class ConfigTests(unittest.TestCase):
enable_difexp="true", enable_difexp="true",
lfc_cutoff=0.01, lfc_cutoff=0.01,
top_n=10, top_n=10,
X_approx_distribution="auto", X_approximate_distribution="auto",
config_file_name="dataset_config.yml", config_file_name="dataset_config.yml",
): ):
configfile = os.path.join(self.tmp_fixtures_directory, config_file_name) configfile = os.path.join(self.tmp_fixtures_directory, config_file_name)
@@ -6,9 +6,13 @@ from http import HTTPStatus
import tempfile import tempfile
from os import path from os import path
import hashlib import hashlib
from os.path import basename, splitext
import pandas as pd import pandas as pd
import requests import requests
import numpy as np
from parameterized import parameterized_class
import backend.test.decode_fbs as decode_fbs import backend.test.decode_fbs as decode_fbs
from backend.server.data_common.matrix_loader import MatrixDataType from backend.server.data_common.matrix_loader import MatrixDataType
@@ -44,6 +48,14 @@ class EndPoints(object):
len(result_data["schema"]["annotations"]["obs"]["columns"]), 6 if self.ANNOTATIONS_ENABLED else 5 len(result_data["schema"]["annotations"]["obs"]["columns"]), 6 if self.ANNOTATIONS_ENABLED else 5
) )
# Check that all schema types are legal
legal_types = ["boolean", "string", "categorical", "float32", "int32"]
self.assertEqual(result_data["schema"]["dataframe"]["type"], "float32")
for column in result_data["schema"]["annotations"]["obs"]["columns"]:
self.assertIn(column["type"], legal_types)
for column in result_data["schema"]["annotations"]["var"]["columns"]:
self.assertIn(column["type"], legal_types)
def test_config(self): def test_config(self):
endpoint = "config" endpoint = "config"
url = f"{self.URL_BASE}{endpoint}" url = f"{self.URL_BASE}{endpoint}"
@@ -52,7 +64,12 @@ class EndPoints(object):
self.assertEqual(result.headers["Content-Type"], "application/json") self.assertEqual(result.headers["Content-Type"], "application/json")
result_data = result.json() result_data = result.json()
self.assertIn("library_versions", result_data["config"]) self.assertIn("library_versions", result_data["config"])
self.assertEqual(result_data["config"]["displayNames"]["dataset"], "pbmc3k")
if hasattr(self, "data_locator"):
title = splitext(basename(self.data_locator))[0]
else:
title = "pbmc3k"
self.assertEqual(result_data["config"]["displayNames"]["dataset"], title)
self.assertIsNotNone(result_data["config"]["parameters"]) self.assertIsNotNone(result_data["config"]["parameters"])
def test_get_layout_fbs(self): def test_get_layout_fbs(self):
@@ -72,6 +89,8 @@ class EndPoints(object):
) )
self.assertIsNone(df["row_idx"]) self.assertIsNone(df["row_idx"])
self.assertEqual(len(df["columns"]), df["n_cols"]) self.assertEqual(len(df["columns"]), df["n_cols"])
for column in df["columns"]:
self.assertEqual(column.dtype, np.float32)
def test_bad_filter(self): def test_bad_filter(self):
endpoint = "data/var" endpoint = "data/var"
@@ -98,6 +117,9 @@ class EndPoints(object):
[obs_index_col_name, "n_genes", "percent_mito", "n_counts", "louvain"] [obs_index_col_name, "n_genes", "percent_mito", "n_counts", "louvain"]
+ (["cluster-test"] if self.ANNOTATIONS_ENABLED else []), + (["cluster-test"] if self.ANNOTATIONS_ENABLED else []),
) )
for column in df["columns"]:
if type(column) is np.ndarray:
self.assertIn(column.dtype, [np.float32, np.int32])
def test_get_annotations_obs_keys_fbs(self): def test_get_annotations_obs_keys_fbs(self):
endpoint = "annotations/obs" endpoint = "annotations/obs"
@@ -137,6 +159,9 @@ class EndPoints(object):
self.assertEqual(len(df["columns"]), df["n_cols"]) self.assertEqual(len(df["columns"]), df["n_cols"])
var_index_col_name = self.schema["schema"]["annotations"]["var"]["index"] var_index_col_name = self.schema["schema"]["annotations"]["var"]["index"]
self.assertCountEqual(df["col_idx"], [var_index_col_name, "n_cells"]) self.assertCountEqual(df["col_idx"], [var_index_col_name, "n_cells"])
for column in df["columns"]:
if type(column) is np.ndarray:
self.assertIn(column.dtype, [np.float32, np.int32])
def test_get_annotations_var_keys_fbs(self): def test_get_annotations_var_keys_fbs(self):
endpoint = "annotations/var" endpoint = "annotations/var"
@@ -207,6 +232,9 @@ class EndPoints(object):
self.assertIsNone(df["row_idx"]) self.assertIsNone(df["row_idx"])
self.assertEqual(len(df["columns"]), df["n_cols"]) self.assertEqual(len(df["columns"]), df["n_cols"])
self.assertListEqual(df["col_idx"].tolist(), [0, 1, 4]) self.assertListEqual(df["col_idx"].tolist(), [0, 1, 4])
for column in df["columns"]:
if type(column) is np.ndarray:
self.assertIn(column.dtype, [np.float32, np.int32])
def test_data_get_filter_fbs(self): def test_data_get_filter_fbs(self):
index_col_name = self.schema["schema"]["annotations"]["var"]["index"] index_col_name = self.schema["schema"]["annotations"]["var"]["index"]
@@ -220,6 +248,9 @@ class EndPoints(object):
df = decode_fbs.decode_matrix_FBS(result.content) df = decode_fbs.decode_matrix_FBS(result.content)
self.assertEqual(df["n_rows"], 2638) self.assertEqual(df["n_rows"], 2638)
self.assertEqual(df["n_cols"], 1) self.assertEqual(df["n_cols"], 1)
for column in df["columns"]:
if type(column) is np.ndarray:
self.assertIn(column.dtype, [np.float32, np.int32])
def test_data_get_unknown_filter_fbs(self): def test_data_get_unknown_filter_fbs(self):
index_col_name = self.schema["schema"]["annotations"]["var"]["index"] index_col_name = self.schema["schema"]["annotations"]["var"]["index"]
@@ -246,6 +277,9 @@ class EndPoints(object):
df = decode_fbs.decode_matrix_FBS(result.content) df = decode_fbs.decode_matrix_FBS(result.content)
self.assertEqual(df["n_rows"], 2638) self.assertEqual(df["n_rows"], 2638)
self.assertEqual(df["n_cols"], 1) self.assertEqual(df["n_cols"], 1)
for column in df["columns"]:
if type(column) is np.ndarray:
self.assertIn(column.dtype, [np.float32, np.int32])
def test_colors(self): def test_colors(self):
endpoint = "colors" endpoint = "colors"
@@ -347,6 +381,14 @@ class EndPointsAnnotations(EndPoints):
self.assertTrue(matching_columns[0]["writable"]) self.assertTrue(matching_columns[0]["writable"])
@parameterized_class(
[
{"data_locator": f"{PROJECT_ROOT}/example-dataset/pbmc3k.h5ad"},
{"data_locator": f"{FIXTURES_ROOT}/pbmc3k_64.h5ad"},
{"data_locator": f"{FIXTURES_ROOT}/pbmc3k-CSC-gz.h5ad"},
{"data_locator": f"{FIXTURES_ROOT}/pbmc3k-CSR-gz.h5ad"},
]
)
class EndPointsAnndata(unittest.TestCase, EndPoints): class EndPointsAnndata(unittest.TestCase, EndPoints):
"""Test Case for endpoints""" """Test Case for endpoints"""
@@ -355,10 +397,13 @@ class EndPointsAnndata(unittest.TestCase, EndPoints):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
if cls == EndPointsAnndata:
raise unittest.SkipTest("`parameterized_class` bug")
cls._setupClass( cls._setupClass(
cls, cls,
[ [
f"{PROJECT_ROOT}/example-dataset/pbmc3k.h5ad", cls.data_locator,
"--disable-annotations", "--disable-annotations",
"--disable-gene-sets-save", "--disable-gene-sets-save",
], ],
@@ -385,8 +430,8 @@ class EndPointsAnndata(unittest.TestCase, EndPoints):
self.assertEqual(result.status_code, HTTPStatus.OK) self.assertEqual(result.status_code, HTTPStatus.OK)
self.assertEqual(result.headers["Content-Type"], "application/json") self.assertEqual(result.headers["Content-Type"], "application/json")
result_data = result.json() result_data = result.json()
self.assertEqual(len(result_data['positive']), 7) self.assertEqual(len(result_data["positive"]), 7)
self.assertEqual(len(result_data['negative']), 7) self.assertEqual(len(result_data["negative"]), 7)
def test_diff_exp_indices(self): def test_diff_exp_indices(self):
endpoint = "diffexp/obs" endpoint = "diffexp/obs"
@@ -401,8 +446,8 @@ class EndPointsAnndata(unittest.TestCase, EndPoints):
self.assertEqual(result.status_code, HTTPStatus.OK) self.assertEqual(result.status_code, HTTPStatus.OK)
self.assertEqual(result.headers["Content-Type"], "application/json") self.assertEqual(result.headers["Content-Type"], "application/json")
result_data = result.json() result_data = result.json()
self.assertEqual(len(result_data['positive']), 10) self.assertEqual(len(result_data["positive"]), 10)
self.assertEqual(len(result_data['negative']), 10) self.assertEqual(len(result_data["negative"]), 10)
def test_get_summaryvar(self): def test_get_summaryvar(self):
index_col_name = self.schema["schema"]["annotations"]["var"]["index"] index_col_name = self.schema["schema"]["annotations"]["var"]["index"]
@@ -561,23 +606,23 @@ class EndPointsAnnDataGenesets(unittest.TestCase, EndPoints):
"geneset_description": "", "geneset_description": "",
"geneset_name": "summary test", "geneset_name": "summary test",
}, },
{'genes': [], 'geneset_description': '', 'geneset_name': 'geneset_to_delete'}, {"genes": [], "geneset_description": "", "geneset_name": "geneset_to_delete"},
{'genes': [], 'geneset_description': '', 'geneset_name': 'geneset_to_edit'}, {"genes": [], "geneset_description": "", "geneset_name": "geneset_to_edit"},
{ {
'genes': [{'gene_description': '', 'gene_symbol': 'RER1'}], "genes": [],
'geneset_description': '', "geneset_description": "",
'geneset_name': 'fill_this_geneset' "geneset_name": "fill_this_geneset",
}, },
{ {
'genes': [{'gene_description': '', 'gene_symbol': 'SIK1'}], "genes": [{"gene_description": "", "gene_symbol": "SIK1"}],
'geneset_description': '', "geneset_description": "",
'geneset_name': 'empty_this_geneset' "geneset_name": "empty_this_geneset",
}, },
{ {
'genes': [{'gene_description': '', 'gene_symbol': 'SIK1'}], "genes": [{"gene_description": "", "gene_symbol": "SIK1"}],
'geneset_description': '', "geneset_description": "",
'geneset_name': 'brush_this_gene' "geneset_name": "brush_this_gene",
} },
], ],
"tid": 0, "tid": 0,
}, },
@@ -606,7 +651,7 @@ summary test,,F5,\r
summary test,,PIGU,\r summary test,,PIGU,\r
geneset_to_delete,,,\r geneset_to_delete,,,\r
geneset_to_edit,,,\r geneset_to_edit,,,\r
fill_this_geneset,,RER1,\r fill_this_geneset,,,\r
empty_this_geneset,,SIK1,\r empty_this_geneset,,SIK1,\r
brush_this_gene,,SIK1,\r brush_this_gene,,SIK1,\r
""", """,
@@ -680,7 +725,7 @@ brush_this_gene,,SIK1,\r
self.assertEqual(result.json(), test3) self.assertEqual(result.json(), test3)
def test_put_genesets_malformed(self): def test_put_genesets_malformed(self):
""" test malformed submissions that we expect the backend to catch/tolerate """ """test malformed submissions that we expect the backend to catch/tolerate"""
endpoint = "genesets" endpoint = "genesets"
url = f"{self.URL_BASE}{endpoint}" url = f"{self.URL_BASE}{endpoint}"
@@ -690,7 +735,7 @@ brush_this_gene,,SIK1,\r
tid = original_data["tid"] tid = original_data["tid"]
def test_case(test, expected_code, original_data): def test_case(test, expected_code, original_data):
""" check for expected error AND that no change was made to the original state """ """check for expected error AND that no change was made to the original state"""
result = self.session.put(url, json=test) result = self.session.put(url, json=test)
self.assertEqual(result.status_code, expected_code) self.assertEqual(result.status_code, expected_code)
result = self.session.get(url, headers={"Accept": "application/json"}) result = self.session.get(url, headers={"Accept": "application/json"})
@@ -2,7 +2,7 @@ import unittest
import numpy as np import numpy as np
from scipy import sparse from scipy import sparse
from backend.common.compute.estimate_distribution import estimate_approximate_distribution from backend.common.compute.estimate_distribution import estimate_approximate_distribution
from backend.common.constants import XApproxDistribution from backend.common.constants import XApproximateDistribution
from backend.server.data_common.matrix_loader import MatrixDataLoader from backend.server.data_common.matrix_loader import MatrixDataLoader
from backend.test.test_server.unit import app_config from backend.test.test_server.unit import app_config
from backend.test import PROJECT_ROOT from backend.test import PROJECT_ROOT
@@ -20,22 +20,101 @@ class EstDistTest(unittest.TestCase):
def test_adaptestimate_approximate_distribution(self): def test_adaptestimate_approximate_distribution(self):
adaptor = self.load_dataset(f"{PROJECT_ROOT}/example-dataset/pbmc3k.h5ad") adaptor = self.load_dataset(f"{PROJECT_ROOT}/example-dataset/pbmc3k.h5ad")
self.assertEqual(adaptor.get_X_approx_distribution(), XApproxDistribution.NORMAL) self.assertEqual(adaptor.get_X_approximate_distribution(), XApproximateDistribution.NORMAL)
def test_estimate_approximate_distribution(self): def test_estimate_approximate_distribution(self):
raw = np.random.exponential(scale=1000, size=(100, 40)) raw = np.random.exponential(scale=1000, size=(100, 40))
# empty
self.assertEqual(estimate_approximate_distribution(np.zeros((0,))), XApproximateDistribution.NORMAL)
# ndarray # ndarray
self.assertEqual(estimate_approximate_distribution(raw), XApproxDistribution.COUNT) self.assertEqual(estimate_approximate_distribution(raw), XApproximateDistribution.COUNT)
self.assertEqual(estimate_approximate_distribution(np.log1p(raw)), XApproxDistribution.NORMAL) self.assertEqual(estimate_approximate_distribution(np.log1p(raw)), XApproximateDistribution.NORMAL)
# csr_matrix # csr_matrix
self.assertEqual(estimate_approximate_distribution(sparse.csr_matrix(raw)), XApproxDistribution.COUNT) self.assertEqual(estimate_approximate_distribution(sparse.csr_matrix(raw)), XApproximateDistribution.COUNT)
self.assertEqual( self.assertEqual(
estimate_approximate_distribution(sparse.csr_matrix(np.log1p(raw))), XApproxDistribution.NORMAL estimate_approximate_distribution(sparse.csr_matrix(np.log1p(raw))), XApproximateDistribution.NORMAL
)
# csc_matrix
self.assertEqual(estimate_approximate_distribution(sparse.csc_matrix(raw)), XApproximateDistribution.COUNT)
self.assertEqual(
estimate_approximate_distribution(sparse.csc_matrix(np.log1p(raw))), XApproximateDistribution.NORMAL
) )
# BIG (ie, trigger MT) # BIG (ie, trigger MT)
big = np.random.exponential(scale=100, size=(1_000_000, 100)) big = np.random.exponential(scale=100, size=(1_000_000, 100))
self.assertEqual(estimate_approximate_distribution(big), XApproxDistribution.COUNT) self.assertEqual(estimate_approximate_distribution(big), XApproximateDistribution.COUNT)
self.assertEqual(estimate_approximate_distribution(np.log1p(big)), XApproxDistribution.NORMAL) self.assertEqual(estimate_approximate_distribution(np.log1p(big)), XApproximateDistribution.NORMAL)
def test_unsupported_throws(self):
# dtypes and matrix formats we do not support
with self.assertRaises(TypeError):
estimate_approximate_distribution(np.array(["a", "b"]))
with self.assertRaises(TypeError):
estimate_approximate_distribution(sparse.coo_matrix(np.array([[0, 1, 2], [3, 0, 2]])))
def test_nonfinites(self):
def put(arr, ind, vals):
# like np.put, but creates and returns a modified copy of original array
a = arr.copy()
np.put(a, ind, vals)
return a
# non-finites
self.assertEqual(estimate_approximate_distribution(np.array([np.nan])), XApproximateDistribution.NORMAL)
self.assertEqual(estimate_approximate_distribution(np.array([np.PINF])), XApproximateDistribution.NORMAL)
self.assertEqual(estimate_approximate_distribution(np.array([np.NINF])), XApproximateDistribution.NORMAL)
self.assertEqual(
estimate_approximate_distribution(np.array([np.PINF, np.NINF, 0])), XApproximateDistribution.NORMAL
)
self.assertEqual(
estimate_approximate_distribution(np.array([np.nan, np.PINF, np.NINF])), XApproximateDistribution.NORMAL
)
raw = np.random.exponential(scale=1000, size=(50, 3))
logged = np.log1p(raw)
self.assertEqual(
estimate_approximate_distribution(put(raw, [1], [np.nan])),
XApproximateDistribution.COUNT,
)
self.assertEqual(
estimate_approximate_distribution(put(raw, [1], [np.PINF])),
XApproximateDistribution.COUNT,
)
self.assertEqual(
estimate_approximate_distribution(put(raw, [1], [np.NINF])),
XApproximateDistribution.COUNT,
)
self.assertEqual(
estimate_approximate_distribution(put(raw, [1, 3, 88], [np.nan, np.PINF, np.NINF])),
XApproximateDistribution.COUNT,
)
self.assertEqual(
estimate_approximate_distribution(put(raw, [0, 1], [np.nan, np.nan])),
XApproximateDistribution.COUNT,
)
self.assertEqual(
estimate_approximate_distribution(put(logged, [1], [np.nan])),
XApproximateDistribution.NORMAL,
)
self.assertEqual(
estimate_approximate_distribution(put(logged, [1], [np.PINF])),
XApproximateDistribution.NORMAL,
)
self.assertEqual(
estimate_approximate_distribution(put(logged, [1], [np.NINF])),
XApproximateDistribution.NORMAL,
)
self.assertEqual(
estimate_approximate_distribution(put(logged, [1, 3, 88], [np.nan, np.PINF, np.NINF])),
XApproximateDistribution.NORMAL,
)
self.assertEqual(
estimate_approximate_distribution(put(logged, [0, 1], [np.nan, np.nan])),
XApproximateDistribution.NORMAL,
)
@@ -22,7 +22,7 @@ Test the anndata adaptor using the pbmc3k data set.
@parameterized_class( @parameterized_class(
("data_locator", "backed", "X_approx_distribution"), ("data_locator", "backed", "X_approximate_distribution"),
[ [
(f"{PROJECT_ROOT}/example-dataset/pbmc3k.h5ad", False, "auto"), (f"{PROJECT_ROOT}/example-dataset/pbmc3k.h5ad", False, "auto"),
(f"{FIXTURES_ROOT}/pbmc3k-CSC-gz.h5ad", False, "auto"), (f"{FIXTURES_ROOT}/pbmc3k-CSC-gz.h5ad", False, "auto"),
@@ -36,12 +36,15 @@ Test the anndata adaptor using the pbmc3k data set.
(f"{PROJECT_ROOT}/example-dataset/pbmc3k.h5ad", True, "normal"), (f"{PROJECT_ROOT}/example-dataset/pbmc3k.h5ad", True, "normal"),
(f"{FIXTURES_ROOT}/pbmc3k-CSC-gz.h5ad", True, "normal"), (f"{FIXTURES_ROOT}/pbmc3k-CSC-gz.h5ad", True, "normal"),
(f"{FIXTURES_ROOT}/pbmc3k-CSR-gz.h5ad", True, "normal"), (f"{FIXTURES_ROOT}/pbmc3k-CSR-gz.h5ad", True, "normal"),
(f"{FIXTURES_ROOT}/pbmc3k_64.h5ad", False, "auto"), # 64 bit conversion tests
], ],
) )
class AdaptorTest(unittest.TestCase): class AdaptorTest(unittest.TestCase):
def setUp(self): def setUp(self):
config = app_config( config = app_config(
self.data_locator, self.backed, extra_dataset_config=dict(X_approx_distribution=self.X_approx_distribution) self.data_locator,
self.backed,
extra_dataset_config=dict(X_approximate_distribution=self.X_approximate_distribution),
) )
self.data = AnndataAdaptor(DataLocator(self.data_locator), config) self.data = AnndataAdaptor(DataLocator(self.data_locator), config)
@@ -2,4 +2,4 @@
exports[`did launch page launched 1`] = `"<span style=\\"max-width: 155px; display: flex; overflow: hidden; justify-content: flex-start; width: 100%; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">pbm</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">c3k</span><span style=\\"position: absolute; right: 0px; color: inherit;\\">c3k</span></span></span>"`; exports[`did launch page launched 1`] = `"<span style=\\"max-width: 155px; display: flex; overflow: hidden; justify-content: flex-start; width: 100%; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">pbm</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">c3k</span><span style=\\"position: absolute; right: 0px; color: inherit;\\">c3k</span></span></span>"`;
exports[`metadata loads categories and values from dataset appear 1`] = `"<div style=\\"display: flex; justify-content: space-between; align-items: baseline;\\"><div style=\\"display: flex; justify-content: flex-start; align-items: flex-start;\\"><label class=\\"bp3-control bp3-checkbox\\" for=\\"category-select-louvain\\"><input id=\\"category-select-louvain\\" data-testclass=\\"category-select\\" data-testid=\\"louvain:category-select\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span role=\\"menuitem\\" tabindex=\\"0\\" data-testclass=\\"category-expand\\" data-testid=\\"louvain:category-expand\\" style=\\"cursor: pointer;\\"><span class=\\"bp3-popover2-target\\"><span data-testid=\\"louvain:category-label\\" tabindex=\\"-1\\" aria-label=\\"louvain\\" class=\\"\\" style=\\"max-width: 265px;\\"><span style=\\"max-width: 265px; display: flex; overflow: hidden; justify-content: flex-start; width: 100%; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">lou</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">vain</span><span style=\\"position: absolute; right: 0px; color: inherit;\\">vain</span></span></span></span></span><svg stroke=\\"currentColor\\" fill=\\"currentColor\\" stroke-width=\\"0\\" viewBox=\\"0 0 320 512\\" data-testclass=\\"category-expand-is-not-expanded\\" height=\\"1em\\" width=\\"1em\\" xmlns=\\"http://www.w3.org/2000/svg\\" style=\\"font-size: 10px; margin-left: 5px;\\"><path d=\\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\\"></path></svg></span></div><div><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><a role=\\"button\\" data-testclass=\\"colorby\\" data-testid=\\"colorby-louvain\\" class=\\"bp3-button\\" tabindex=\\"0\\"><span icon=\\"tint\\" class=\\"bp3-icon bp3-icon-tint\\"><svg data-icon=\\"tint\\" width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\"><desc>tint</desc><path d=\\"M7.88 1s-4.9 6.28-4.9 8.9c.01 2.82 2.34 5.1 4.99 5.1 2.65-.01 5.03-2.3 5.03-5.13C12.99 7.17 7.88 1 7.88 1z\\" fill-rule=\\"evenodd\\"></path></svg></span></a></span></span></div></div><div style=\\"margin-left: 26px;\\"></div><div></div>"`; exports[`metadata loads categories and values from dataset appear 1`] = `"<div style=\\"display: flex; justify-content: space-between; align-items: baseline;\\"><div style=\\"display: flex; justify-content: flex-start; align-items: flex-start;\\"><label class=\\"bp3-control bp3-checkbox\\" for=\\"category-select-louvain\\"><input id=\\"category-select-louvain\\" data-testclass=\\"category-select\\" data-testid=\\"louvain:category-select\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span role=\\"menuitem\\" tabindex=\\"0\\" data-testclass=\\"category-expand\\" data-testid=\\"louvain:category-expand\\" style=\\"cursor: pointer;\\"><span class=\\"bp3-popover2-target\\"><span data-testid=\\"louvain:category-label\\" tabindex=\\"-1\\" aria-label=\\"louvain\\" class=\\"\\" style=\\"max-width: 265px;\\"><span style=\\"max-width: 265px; display: flex; overflow: hidden; justify-content: flex-start; width: 100%; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">lou</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">vain</span><span style=\\"position: absolute; right: 0px; color: inherit;\\">vain</span></span></span></span></span><svg stroke=\\"currentColor\\" fill=\\"currentColor\\" stroke-width=\\"0\\" viewBox=\\"0 0 320 512\\" data-testclass=\\"category-expand-is-not-expanded\\" height=\\"1em\\" width=\\"1em\\" xmlns=\\"http://www.w3.org/2000/svg\\" style=\\"font-size: 10px; margin-left: 5px;\\"><path d=\\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\\"></path></svg></span></div><div><span class=\\"bp3-popover-wrapper\\"><span aria-haspopup=\\"true\\" class=\\"bp3-popover-target\\"><a role=\\"button\\" data-testclass=\\"colorby\\" data-testid=\\"colorby-louvain\\" class=\\"bp3-button\\" tabindex=\\"0\\"><span icon=\\"tint\\" class=\\"bp3-icon bp3-icon-tint\\"><svg data-icon=\\"tint\\" width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\"><desc>tint</desc><path d=\\"M7.88 1s-4.9 6.28-4.9 8.9c.01 2.82 2.34 5.1 4.99 5.1 2.65-.01 5.03-2.3 5.03-5.13C12.99 7.17 7.88 1 7.88 1z\\" fill-rule=\\"evenodd\\"></path></svg></span></a></span></span></div></div><div style=\\"margin-left: 26px;\\"></div>"`;
@@ -2,15 +2,15 @@
exports[`annotations stacked bar graph renders 1`] = ` exports[`annotations stacked bar graph renders 1`] = `
Array [ Array [
"<div class=\\"categorical__value___2m6V7\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-TEST-LABEL\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover2-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value\\" tabindex=\\"-1\\" aria-label=\\"TEST-LABEL\\" class=\\"\\" style=\\"width: 63px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">TEST-</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">LABEL</span><span style=\\"position: absolute; right: 0px; color: black;\\">LABEL</span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-TEST-LABEL\\" style=\\"color: black;\\">0</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 15px; height: 15px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:TEST-LABEL:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>", "<div class=\\"categorical__value___2m6V7\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-TEST-LABEL\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover2-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value\\" tabindex=\\"-1\\" aria-label=\\"TEST-LABEL\\" class=\\"\\" style=\\"width: 63px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">TEST-</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">LABEL</span><span style=\\"position: absolute; right: 0px; color: black;\\">LABEL</span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-TEST-LABEL\\" style=\\"color: black;\\">0</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 15px; height: 15px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span aria-haspopup=\\"true\\" class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:TEST-LABEL:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
"<div class=\\"categorical__value___2m6V7\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-unassigned\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover2-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value\\" tabindex=\\"-1\\" aria-label=\\"unassigned\\" class=\\"\\" style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: rgb(171, 171, 171); font-style: italic; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">unass</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">igned</span><span style=\\"position: absolute; right: 0px; color: rgb(171, 171, 171);\\">igned</span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"><canvas width=\\"100\\" height=\\"11\\" style=\\"margin-right: 5px; width: 100px; height: 11px;\\"></canvas></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-unassigned\\" style=\\"color: rgb(171, 171, 171); font-style: italic;\\">2133</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 15px; height: 15px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:unassigned:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>", "<div class=\\"categorical__value___2m6V7\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-unassigned\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover2-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value\\" tabindex=\\"-1\\" aria-label=\\"unassigned\\" class=\\"\\" style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: rgb(171, 171, 171); font-style: italic; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">unass</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">igned</span><span style=\\"position: absolute; right: 0px; color: rgb(171, 171, 171);\\">igned</span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"><canvas width=\\"100\\" height=\\"11\\" style=\\"margin-right: 5px; width: 100px; height: 11px;\\"></canvas></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-unassigned\\" style=\\"color: rgb(171, 171, 171); font-style: italic;\\">2133</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 15px; height: 15px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span aria-haspopup=\\"true\\" class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:unassigned:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
] ]
`; `;
exports[`annotations stacked bar graph renders 2`] = ` exports[`annotations stacked bar graph renders 2`] = `
Array [ Array [
"<div class=\\"categorical__value___2m6V7\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-TEST-LABEL\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover2-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value\\" tabindex=\\"-1\\" aria-label=\\"TEST-LABEL\\" class=\\"\\" style=\\"width: 63px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">TEST-</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">LABEL</span><span style=\\"position: absolute; right: 0px; color: black;\\">LABEL</span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-TEST-LABEL\\" style=\\"color: black;\\">0</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 15px; height: 15px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:TEST-LABEL:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>", "<div class=\\"categorical__value___2m6V7\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-TEST-LABEL\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover2-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value\\" tabindex=\\"-1\\" aria-label=\\"TEST-LABEL\\" class=\\"\\" style=\\"width: 63px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">TEST-</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">LABEL</span><span style=\\"position: absolute; right: 0px; color: black;\\">LABEL</span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-TEST-LABEL\\" style=\\"color: black;\\">0</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 15px; height: 15px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span aria-haspopup=\\"true\\" class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:TEST-LABEL:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
"<div class=\\"categorical__value___2m6V7\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-unassigned\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover2-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value\\" tabindex=\\"-1\\" aria-label=\\"unassigned\\" class=\\"\\" style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: rgb(171, 171, 171); font-style: italic; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">unass</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">igned</span><span style=\\"position: absolute; right: 0px; color: rgb(171, 171, 171);\\">igned</span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"><canvas width=\\"100\\" height=\\"11\\" style=\\"margin-right: 5px; width: 100px; height: 11px;\\"></canvas></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-unassigned\\" style=\\"color: rgb(171, 171, 171); font-style: italic;\\">2638</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 15px; height: 15px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:unassigned:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>", "<div class=\\"categorical__value___2m6V7\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-unassigned\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover2-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value\\" tabindex=\\"-1\\" aria-label=\\"unassigned\\" class=\\"\\" style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: rgb(171, 171, 171); font-style: italic; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">unass</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">igned</span><span style=\\"position: absolute; right: 0px; color: rgb(171, 171, 171);\\">igned</span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"><canvas width=\\"100\\" height=\\"11\\" style=\\"margin-right: 5px; width: 100px; height: 11px;\\"></canvas></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-unassigned\\" style=\\"color: rgb(171, 171, 171); font-style: italic;\\">2638</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 15px; height: 15px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span aria-haspopup=\\"true\\" class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:unassigned:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
] ]
`; `;
-516
View File
@@ -1,516 +0,0 @@
/* eslint-disable no-await-in-loop -- await in loop is needed to emulate sequential user actions */
import { strict as assert } from "assert";
import {
clearInputAndTypeInto,
clickOn,
getAllByClass,
getOneElementInnerText,
typeInto,
waitByID,
waitByClass,
waitForAllByIds,
clickOnUntil,
getTestClass,
getTestId,
isElementPresent,
goToPage,
} from "./puppeteerUtils";
import { appUrlBase, TEST_EMAIL, TEST_PASSWORD } from "./config";
export async function drag(testId, start, end, lasso = false) {
const layout = await waitByID(testId);
const elBox = await layout.boxModel();
const x1 = elBox.content[0].x + start.x;
const x2 = elBox.content[0].x + end.x;
const y1 = elBox.content[0].y + start.y;
const y2 = elBox.content[0].y + end.y;
await page.mouse.move(x1, y1);
await page.mouse.down();
if (lasso) {
await page.mouse.move(x2, y1);
await page.mouse.move(x2, y2);
await page.mouse.move(x1, y2);
await page.mouse.move(x1, y1);
} else {
await page.mouse.move(x2, y2);
}
await page.mouse.up();
}
export async function clickOnCoordinate(testId, coord) {
const layout = await expect(page).toMatchElement(getTestId(testId));
const elBox = await layout.boxModel();
if (!elBox) {
throw Error("Layout's boxModel is not available!");
}
const x = elBox.content[0].x + coord.x;
const y = elBox.content[0].y + coord.y;
await page.mouse.click(x, y);
}
export async function getAllHistograms(testclass, testIds) {
const histTestIds = testIds.map((tid) => `histogram-${tid}`);
// these load asynchronously, so we need to wait for each histogram individually,
// and they may be quite slow in some cases.
await waitForAllByIds(histTestIds, { timeout: 4 * 60 * 1000 });
const allHistograms = await getAllByClass(testclass);
const testIDs = await Promise.all(
allHistograms.map((hist) => {
return page.evaluate((elem) => {
return elem.dataset.testid;
}, hist);
})
);
return testIDs.map((id) => id.replace(/^histogram-/, ""));
}
export async function getAllCategoriesAndCounts(category) {
// these load asynchronously, so we have to wait for the specific category.
await waitByID(`category-${category}`);
return page.$$eval(
`[data-testid="category-${category}"] [data-testclass='categorical-row']`,
(rows) =>
Object.fromEntries(
rows.map((row) => {
const cat = row
.querySelector("[data-testclass='categorical-value']")
.getAttribute("aria-label");
const count = row.querySelector(
"[data-testclass='categorical-value-count']"
).innerText;
return [cat, count];
})
)
);
}
export async function getCellSetCount(num) {
await clickOn(`cellset-button-${num}`);
return getOneElementInnerText(`[data-testid='cellset-count-${num}']`);
}
export async function resetCategory(category) {
const checkboxId = `${category}:category-select`;
await waitByID(checkboxId);
const checkedPseudoclass = await page.$eval(
`[data-testid='${checkboxId}']`,
(el) => el.matches(":checked")
);
if (!checkedPseudoclass) await clickOn(checkboxId);
const categoryRow = await waitByID(`${category}:category-expand`);
const isExpanded = await categoryRow.$(
"[data-testclass='category-expand-is-expanded']"
);
if (isExpanded) await clickOn(`${category}:category-expand`);
}
export async function calcCoordinate(testId, xAsPercent, yAsPercent) {
const el = await waitByID(testId);
const size = await el.boxModel();
return {
x: Math.floor(size.width * xAsPercent),
y: Math.floor(size.height * yAsPercent),
};
}
export async function calcDragCoordinates(testId, coordinateAsPercent) {
return {
start: await calcCoordinate(
testId,
coordinateAsPercent.x1,
coordinateAsPercent.y1
),
end: await calcCoordinate(
testId,
coordinateAsPercent.x2,
coordinateAsPercent.y2
),
};
}
export async function selectCategory(category, values, reset = true) {
if (reset) await resetCategory(category);
await clickOn(`${category}:category-expand`);
await clickOn(`${category}:category-select`);
for (const value of values) {
await clickOn(`categorical-value-select-${category}-${value}`);
}
}
export async function expandCategory(category) {
const expand = await waitByID(`${category}:category-expand`);
const notExpanded = await expand.$(
"[data-testclass='category-expand-is-not-expanded']"
);
if (notExpanded) await clickOn(`${category}:category-expand`);
}
export async function clip(min = 0, max = 100) {
await clickOn("visualization-settings");
await clearInputAndTypeInto("clip-min-input", min);
await clearInputAndTypeInto("clip-max-input", max);
await clickOn("clip-commit");
}
export async function createCategory(categoryName) {
await clickOnUntil("open-annotation-dialog", async () => {
await expect(page).toMatchElement(getTestId("new-category-name"));
});
await typeInto("new-category-name", categoryName);
await clickOn("submit-category");
}
/*
GENESET
*/
export async function colorByGeneset(genesetName) {
await clickOn(`${genesetName}:colorby-entire-geneset`);
}
export async function colorByGene(gene) {
await clickOn(`colorby-${gene}`);
}
export async function assertColorLegendLabel(label) {
const handle = await waitByID("continuous_legend_color_by_label");
const result = await handle.evaluate((node) => {
return node.getAttribute("aria-label");
});
return expect(result).toBe(label);
}
export async function expandGeneset(genesetName) {
const expand = await waitByID(`${genesetName}:geneset-expand`);
const notExpanded = await expand.$(
"[data-testclass='geneset-expand-is-not-expanded']"
);
if (notExpanded) await clickOn(`${genesetName}:geneset-expand`);
}
export async function createGeneset(genesetName) {
await clickOnUntil("open-create-geneset-dialog", async () => {
await expect(page).toMatchElement(getTestId("create-geneset-input"));
});
await typeInto("create-geneset-input", genesetName);
await clickOn("submit-geneset");
await waitByClass("autosave-complete");
}
export async function editGenesetName(genesetName, editText) {
const editButton = `${genesetName}:edit-genesetName-mode`;
const submitButton = `${genesetName}:submit-geneset`;
await clickOnUntil(`${genesetName}:see-actions`, async () => {
await expect(page).toMatchElement(getTestId(editButton));
});
await clickOn(editButton);
await typeInto("rename-geneset-modal", editText);
await clickOn(submitButton);
}
export async function deleteGeneset(genesetName) {
const targetId = `${genesetName}:delete-geneset`;
await clickOnUntil(`${genesetName}:see-actions`, async () => {
await expect(page).toMatchElement(getTestId(targetId));
});
await clickOn(targetId);
await assertGenesetDoesNotExist(genesetName);
await waitByClass("autosave-complete");
}
export async function assertGenesetDoesNotExist(genesetName) {
const result = await isElementPresent(
getTestId(`${genesetName}:geneset-name`)
);
await expect(result).toBe(false);
}
export async function assertGenesetExists(genesetName) {
const handle = await waitByID(`${genesetName}:geneset-name`);
const result = await handle.evaluate((node) => {
return node.getAttribute("aria-label");
});
return expect(result).toBe(genesetName);
}
/*
GENE
*/
export async function addGeneToSet(genesetName, geneToAddToSet) {
const submitButton = `${genesetName}:submit-gene`;
await clickOn(`${genesetName}:add-new-gene-to-geneset`);
await typeInto("add-genes", geneToAddToSet);
await clickOn(submitButton);
}
export async function removeGene(geneSymbol) {
const targetId = `delete-from-geneset:${geneSymbol}`;
await clickOn(targetId);
await waitByClass("autosave-complete");
}
export async function assertGeneExistsInGeneset(geneSymbol) {
const handle = await waitByID(`${geneSymbol}:gene-label`);
const result = await handle.evaluate((node) => {
return node.getAttribute("aria-label");
});
return expect(result).toBe(geneSymbol);
}
export async function assertGeneDoesNotExist(geneSymbol) {
const result = await isElementPresent(getTestId(`${geneSymbol}:gene-label`));
await expect(result).toBe(false);
}
export async function expandGene(geneSymbol) {
await clickOn(`maximize-${geneSymbol}`);
}
/*
CATEGORY
*/
export async function duplicateCategory(categoryName) {
await clickOn("open-annotation-dialog");
await typeInto("new-category-name", categoryName);
const dropdownOptionClass = "duplicate-category-dropdown-option";
await clickOnUntil("duplicate-category-dropdown", async () => {
await expect(page).toMatchElement(getTestClass(dropdownOptionClass));
});
const option = await expect(page).toMatchElement(
getTestClass(dropdownOptionClass)
);
await option.click();
await clickOnUntil("submit-category", async () => {
await expect(page).toMatchElement(
getTestId(`${categoryName}:category-expand`)
);
});
await waitByClass("autosave-complete");
}
export async function renameCategory(oldCategoryName, newCategoryName) {
await clickOn(`${oldCategoryName}:see-actions`);
await clickOn(`${oldCategoryName}:edit-category-mode`);
await clearInputAndTypeInto(
`${oldCategoryName}:edit-category-name-text`,
newCategoryName
);
await clickOn(`${oldCategoryName}:submit-category-edit`);
}
export async function deleteCategory(categoryName) {
const targetId = `${categoryName}:delete-category`;
await clickOnUntil(`${categoryName}:see-actions`, async () => {
await expect(page).toMatchElement(getTestId(targetId));
});
await clickOn(targetId);
await assertCategoryDoesNotExist();
}
export async function createLabel(categoryName, labelName) {
/**
* (thuang): This explicit wait is needed, since currently showing
* the modal again quickly after the previous action dismissing the
* modal will persist the input value from the previous action.
*
* To reproduce:
* 1. Click on the plus sign to show the modal to add a new label to the category
* 2. Type `123` in the input box
* 3. Hover over your mouse over the plus sign and double click to quickly dismiss and
* invoke the modal again
* 4. You will see `123` is persisted in the input box
* 5. Expected behavior is to get an empty input box
*/
await page.waitForTimeout(500);
await clickOn(`${categoryName}:see-actions`);
await clickOn(`${categoryName}:add-new-label-to-category`);
await typeInto(`${categoryName}:new-label-name`, labelName);
await clickOn(`${categoryName}:submit-label`);
}
export async function deleteLabel(categoryName, labelName) {
await expandCategory(categoryName);
await clickOn(`${categoryName}:${labelName}:see-actions`);
await clickOn(`${categoryName}:${labelName}:delete-label`);
}
export async function renameLabel(categoryName, oldLabelName, newLabelName) {
await expandCategory(categoryName);
await clickOn(`${categoryName}:${oldLabelName}:see-actions`);
await clickOn(`${categoryName}:${oldLabelName}:edit-label`);
await clearInputAndTypeInto(
`${categoryName}:${oldLabelName}:edit-label-name`,
newLabelName
);
await clickOn(`${categoryName}:${oldLabelName}:submit-label-edit`);
}
export async function addGeneToSearch(geneName) {
await typeInto("gene-search", geneName);
await page.keyboard.press("Enter");
await page.waitForSelector(`[data-testid='histogram-${geneName}']`);
}
export async function subset(coordinatesAsPercent) {
// In order to deselect the selection after the subset, make sure we have some clear part
// of the scatterplot we can click on
assert(coordinatesAsPercent.x2 < 0.99 || coordinatesAsPercent.y2 < 0.99);
const lassoSelection = await calcDragCoordinates(
"layout-graph",
coordinatesAsPercent
);
await drag("layout-graph", lassoSelection.start, lassoSelection.end, true);
await clickOn("subset-button");
const clearCoordinate = await calcCoordinate("layout-graph", 0.5, 0.99);
await clickOnCoordinate("layout-graph", clearCoordinate);
}
export async function setSellSet(cellSet, cellSetNum) {
const selections = cellSet.filter((sel) => sel.kind === "categorical");
for (const selection of selections) {
await selectCategory(selection.metadata, selection.values, true);
}
await getCellSetCount(cellSetNum);
}
export async function runDiffExp(cellSet1, cellSet2) {
await setSellSet(cellSet1, 1);
await setSellSet(cellSet2, 2);
await clickOn("diffexp-button");
}
export async function bulkAddGenes(geneNames) {
await clickOn("section-bulk-add");
await typeInto("input-bulk-add", geneNames.join(","));
await page.keyboard.press("Enter");
}
export async function assertCategoryDoesNotExist(categoryName) {
const result = await isElementPresent(
getTestId(`${categoryName}:category-label`)
);
await expect(result).toBe(false);
}
export async function login() {
await goToPage(appUrlBase);
await clickOn("log-in");
// (thuang): Auth0 form is unstable and unsafe for input until verified
await waitUntilFormFieldStable('[name="email"]');
await expect(page).toFillForm("form", {
email: TEST_EMAIL,
password: TEST_PASSWORD,
});
await Promise.all([
page.waitForNavigation({ waitUntil: "networkidle0" }),
expect(page).toClick('[name="submit"]'),
]);
expect(page.url()).toContain(appUrlBase);
}
export async function logout() {
await clickOnUntil("user-info", async () => {
await waitByID("log-out");
await Promise.all([
page.waitForNavigation({ waitUntil: "networkidle0" }),
clickOn("log-out"),
]);
});
await waitByID("log-in");
}
async function waitUntilFormFieldStable(selector) {
const MAX_RETRY = 10;
const WAIT_FOR_MS = 200;
const EXPECTED_VALUE = "aaa";
let retry = 0;
while (retry < MAX_RETRY) {
try {
await expect(page).toFill(selector, EXPECTED_VALUE);
const fieldHandle = await expect(page).toMatchElement(selector);
const fieldValue = await page.evaluate(
(input) => input.value,
fieldHandle
);
expect(fieldValue).toBe(EXPECTED_VALUE);
break;
} catch (error) {
retry += 1;
await page.waitForTimeout(WAIT_FOR_MS);
}
}
if (retry === MAX_RETRY) {
throw Error("clickOnUntil() assertion failed!");
}
}
/* eslint-enable no-await-in-loop -- await in loop is needed to emulate sequential user actions */
+596
View File
@@ -0,0 +1,596 @@
/* eslint-disable no-await-in-loop -- await in loop is needed to emulate sequential user actions */
import { strict as assert } from "assert";
import {
clearInputAndTypeInto,
clickOn,
getAllByClass,
getOneElementInnerText,
typeInto,
waitByID,
waitByClass,
waitForAllByIds,
clickOnUntil,
getTestClass,
getTestId,
isElementPresent,
goToPage,
} from "./puppeteerUtils";
import { appUrlBase, TEST_EMAIL, TEST_PASSWORD } from "./config";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function drag(testId: any, start: any, end: any, lasso = false) {
const layout = await waitByID(testId);
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
const elBox = await layout.boxModel();
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
const x1 = elBox.content[0].x + start.x;
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
const x2 = elBox.content[0].x + end.x;
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
const y1 = elBox.content[0].y + start.y;
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
const y2 = elBox.content[0].y + end.y;
await page.mouse.move(x1, y1);
await page.mouse.down();
if (lasso) {
await page.mouse.move(x2, y1);
await page.mouse.move(x2, y2);
await page.mouse.move(x1, y2);
await page.mouse.move(x1, y1);
} else {
await page.mouse.move(x2, y2);
}
await page.mouse.up();
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function clickOnCoordinate(testId: any, coord: any) {
const layout = await expect(page).toMatchElement(getTestId(testId));
const elBox = await layout.boxModel();
if (!elBox) {
throw Error("Layout's boxModel is not available!");
}
const x = elBox.content[0].x + coord.x;
const y = elBox.content[0].y + coord.y;
await page.mouse.click(x, y);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function getAllHistograms(testclass: any, testIds: any) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const histTestIds = testIds.map((tid: any) => `histogram-${tid}`);
// these load asynchronously, so we need to wait for each histogram individually,
// and they may be quite slow in some cases.
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 2.
await waitForAllByIds(histTestIds, { timeout: 4 * 60 * 1000 });
const allHistograms = await getAllByClass(testclass);
const testIDs = await Promise.all(
allHistograms.map((hist) =>
page.evaluate((elem) => elem.dataset.testid, hist)
)
);
return testIDs.map((id) => id.replace(/^histogram-/, ""));
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function getAllCategoriesAndCounts(category: any) {
// these load asynchronously, so we have to wait for the specific category.
await waitByID(`category-${category}`);
return page.$$eval(
`[data-testid="category-${category}"] [data-testclass='categorical-row']`,
(rows) =>
Object.fromEntries(
rows.map((row) => {
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
const cat = row
.querySelector("[data-testclass='categorical-value']")
.getAttribute("aria-label");
const count = (row.querySelector(
"[data-testclass='categorical-value-count']"
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
) as any).innerText;
return [cat, count];
})
)
);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function getCellSetCount(num: any) {
await clickOn(`cellset-button-${num}`);
return getOneElementInnerText(`[data-testid='cellset-count-${num}']`);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function resetCategory(category: any) {
const checkboxId = `${category}:category-select`;
await waitByID(checkboxId);
const checkedPseudoclass = await page.$eval(
`[data-testid='${checkboxId}']`,
(el) => el.matches(":checked")
);
if (!checkedPseudoclass) await clickOn(checkboxId);
const categoryRow = await waitByID(`${category}:category-expand`);
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
const isExpanded = await categoryRow.$(
"[data-testclass='category-expand-is-expanded']"
);
if (isExpanded) await clickOn(`${category}:category-expand`);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
export async function calcCoordinate(
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
testId: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
xAsPercent: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
yAsPercent: any
) {
const el = await waitByID(testId);
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
const size = await el.boxModel();
return {
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
x: Math.floor(size.width * xAsPercent),
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
y: Math.floor(size.height * yAsPercent),
};
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
export async function calcDragCoordinates(
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
testId: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
coordinateAsPercent: any
) {
return {
start: await calcCoordinate(
testId,
coordinateAsPercent.x1,
coordinateAsPercent.y1
),
end: await calcCoordinate(
testId,
coordinateAsPercent.x2,
coordinateAsPercent.y2
),
};
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function selectCategory(category: any, values: any, reset = true) {
if (reset) await resetCategory(category);
await clickOn(`${category}:category-expand`);
await clickOn(`${category}:category-select`);
for (const value of values) {
await clickOn(`categorical-value-select-${category}-${value}`);
}
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function expandCategory(category: any) {
const expand = await waitByID(`${category}:category-expand`);
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
const notExpanded = await expand.$(
"[data-testclass='category-expand-is-not-expanded']"
);
if (notExpanded) await clickOn(`${category}:category-expand`);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
export async function clip(min = 0, max = 100) {
await clickOn("visualization-settings");
await clearInputAndTypeInto("clip-min-input", min);
await clearInputAndTypeInto("clip-max-input", max);
await clickOn("clip-commit");
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function createCategory(categoryName: any) {
await clickOnUntil("open-annotation-dialog", async () => {
await expect(page).toMatchElement(getTestId("new-category-name"));
});
await typeInto("new-category-name", categoryName);
await clickOn("submit-category");
}
/**
* GENESET
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function colorByGeneset(genesetName: any) {
await clickOn(`${genesetName}:colorby-entire-geneset`);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function colorByGene(gene: any) {
await clickOn(`colorby-${gene}`);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function assertColorLegendLabel(label: any) {
const handle = await waitByID("continuous_legend_color_by_label");
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
const result = await handle.evaluate((node) =>
node.getAttribute("aria-label")
);
return expect(result).toBe(label);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function expandGeneset(genesetName: any) {
const expand = await waitByID(`${genesetName}:geneset-expand`);
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
const notExpanded = await expand.$(
"[data-testclass='geneset-expand-is-not-expanded']"
);
if (notExpanded) await clickOn(`${genesetName}:geneset-expand`);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function createGeneset(genesetName: any) {
await clickOnUntil("open-create-geneset-dialog", async () => {
await expect(page).toMatchElement(getTestId("create-geneset-input"));
});
await typeInto("create-geneset-input", genesetName);
await clickOn("submit-geneset");
await waitByClass("autosave-complete");
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function editGenesetName(genesetName: any, editText: any) {
const editButton = `${genesetName}:edit-genesetName-mode`;
const submitButton = `${genesetName}:submit-geneset`;
await clickOnUntil(`${genesetName}:see-actions`, async () => {
await expect(page).toMatchElement(getTestId(editButton));
});
await clickOn(editButton);
await typeInto("rename-geneset-modal", editText);
await clickOn(submitButton);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function deleteGeneset(genesetName: any) {
const targetId = `${genesetName}:delete-geneset`;
await clickOnUntil(`${genesetName}:see-actions`, async () => {
await expect(page).toMatchElement(getTestId(targetId));
});
await clickOn(targetId);
await assertGenesetDoesNotExist(genesetName);
await waitByClass("autosave-complete");
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function assertGenesetDoesNotExist(genesetName: any) {
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
const result = await isElementPresent(
getTestId(`${genesetName}:geneset-name`)
);
await expect(result).toBe(false);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function assertGenesetExists(genesetName: any) {
const handle = await waitByID(`${genesetName}:geneset-name`);
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
const result = await handle.evaluate((node) =>
node.getAttribute("aria-label")
);
return expect(result).toBe(genesetName);
}
/**
* GENE
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function addGeneToSet(genesetName: any, geneToAddToSet: any) {
const submitButton = `${genesetName}:submit-gene`;
await clickOn(`${genesetName}:add-new-gene-to-geneset`);
await typeInto("add-genes", geneToAddToSet);
await clickOn(submitButton);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function removeGene(geneSymbol: any) {
const targetId = `delete-from-geneset:${geneSymbol}`;
await clickOn(targetId);
await waitByClass("autosave-complete");
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function assertGeneExistsInGeneset(geneSymbol: any) {
const handle = await waitByID(`${geneSymbol}:gene-label`);
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
const result = await handle.evaluate((node) =>
node.getAttribute("aria-label")
);
return expect(result).toBe(geneSymbol);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function assertGeneDoesNotExist(geneSymbol: any) {
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
const result = await isElementPresent(getTestId(`${geneSymbol}:gene-label`));
await expect(result).toBe(false);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function expandGene(geneSymbol: any) {
await clickOn(`maximize-${geneSymbol}`);
}
/**
* CATEGORY
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function duplicateCategory(categoryName: any) {
await clickOn("open-annotation-dialog");
await typeInto("new-category-name", categoryName);
const dropdownOptionClass = "duplicate-category-dropdown-option";
await clickOnUntil("duplicate-category-dropdown", async () => {
await expect(page).toMatchElement(getTestClass(dropdownOptionClass));
});
const option = await expect(page).toMatchElement(
getTestClass(dropdownOptionClass)
);
await option.click();
await clickOnUntil("submit-category", async () => {
await expect(page).toMatchElement(
getTestId(`${categoryName}:category-expand`)
);
});
await waitByClass("autosave-complete");
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
export async function renameCategory(
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
oldCategoryName: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
newCategoryName: any
) {
await clickOn(`${oldCategoryName}:see-actions`);
await clickOn(`${oldCategoryName}:edit-category-mode`);
await clearInputAndTypeInto(
`${oldCategoryName}:edit-category-name-text`,
newCategoryName
);
await clickOn(`${oldCategoryName}:submit-category-edit`);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function deleteCategory(categoryName: any) {
const targetId = `${categoryName}:delete-category`;
await clickOnUntil(`${categoryName}:see-actions`, async () => {
await expect(page).toMatchElement(getTestId(targetId));
});
await clickOn(targetId);
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
await assertCategoryDoesNotExist();
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function createLabel(categoryName: any, labelName: any) {
/**
* (thuang): This explicit wait is needed, since currently showing
* the modal again quickly after the previous action dismissing the
* modal will persist the input value from the previous action.
*
* To reproduce:
* 1. Click on the plus sign to show the modal to add a new label to the category
* 2. Type `123` in the input box
* 3. Hover over your mouse over the plus sign and double click to quickly dismiss and
* invoke the modal again
* 4. You will see `123` is persisted in the input box
* 5. Expected behavior is to get an empty input box
*/
await page.waitForTimeout(500);
await clickOn(`${categoryName}:see-actions`);
await clickOn(`${categoryName}:add-new-label-to-category`);
await typeInto(`${categoryName}:new-label-name`, labelName);
await clickOn(`${categoryName}:submit-label`);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function deleteLabel(categoryName: any, labelName: any) {
await expandCategory(categoryName);
await clickOn(`${categoryName}:${labelName}:see-actions`);
await clickOn(`${categoryName}:${labelName}:delete-label`);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
export async function renameLabel(
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
categoryName: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
oldLabelName: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
newLabelName: any
) {
await expandCategory(categoryName);
await clickOn(`${categoryName}:${oldLabelName}:see-actions`);
await clickOn(`${categoryName}:${oldLabelName}:edit-label`);
await clearInputAndTypeInto(
`${categoryName}:${oldLabelName}:edit-label-name`,
newLabelName
);
await clickOn(`${categoryName}:${oldLabelName}:submit-label-edit`);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function addGeneToSearch(geneName: any) {
await typeInto("gene-search", geneName);
await page.keyboard.press("Enter");
await page.waitForSelector(`[data-testid='histogram-${geneName}']`);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function subset(coordinatesAsPercent: any) {
// In order to deselect the selection after the subset, make sure we have some clear part
// of the scatterplot we can click on
assert(coordinatesAsPercent.x2 < 0.99 || coordinatesAsPercent.y2 < 0.99);
const lassoSelection = await calcDragCoordinates(
"layout-graph",
coordinatesAsPercent
);
await drag("layout-graph", lassoSelection.start, lassoSelection.end, true);
await clickOn("subset-button");
const clearCoordinate = await calcCoordinate("layout-graph", 0.5, 0.99);
await clickOnCoordinate("layout-graph", clearCoordinate);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function setSellSet(cellSet: any, cellSetNum: any) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const selections = cellSet.filter((sel: any) => sel.kind === "categorical");
for (const selection of selections) {
await selectCategory(selection.metadata, selection.values, true);
}
await getCellSetCount(cellSetNum);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function runDiffExp(cellSet1: any, cellSet2: any) {
await setSellSet(cellSet1, 1);
await setSellSet(cellSet2, 2);
await clickOn("diffexp-button");
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function bulkAddGenes(geneNames: any) {
await clickOn("section-bulk-add");
await typeInto("input-bulk-add", geneNames.join(","));
await page.keyboard.press("Enter");
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function assertCategoryDoesNotExist(categoryName: any) {
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
const result = await isElementPresent(
getTestId(`${categoryName}:category-label`)
);
await expect(result).toBe(false);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
export async function login() {
await goToPage(appUrlBase);
await clickOn("log-in");
// (thuang): Auth0 form is unstable and unsafe for input until verified
await waitUntilFormFieldStable('[name="email"]');
await expect(page).toFillForm("form", {
email: TEST_EMAIL,
password: TEST_PASSWORD,
});
await Promise.all([
page.waitForNavigation({ waitUntil: "networkidle0" }),
expect(page).toClick('[name="submit"]'),
]);
expect(page.url()).toContain(appUrlBase);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
export async function logout() {
await clickOnUntil("user-info", async () => {
await waitByID("log-out");
await Promise.all([
page.waitForNavigation({ waitUntil: "networkidle0" }),
clickOn("log-out"),
]);
});
await waitByID("log-in");
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
async function waitUntilFormFieldStable(selector: any) {
const MAX_RETRY = 10;
const WAIT_FOR_MS = 200;
const EXPECTED_VALUE = "aaa";
let retry = 0;
while (retry < MAX_RETRY) {
try {
await expect(page).toFill(selector, EXPECTED_VALUE);
const fieldHandle = await expect(page).toMatchElement(selector);
const fieldValue = await page.evaluate(
(input) => input.value,
fieldHandle
);
expect(fieldValue).toBe(EXPECTED_VALUE);
break;
} catch (error) {
retry += 1;
await page.waitForTimeout(WAIT_FOR_MS);
}
}
if (retry === MAX_RETRY) {
throw Error("clickOnUntil() assertion failed!");
}
}
/* eslint-enable no-await-in-loop -- await in loop is needed to emulate sequential user actions */
@@ -58,10 +58,12 @@ describe("metadata loads", () => {
const categories = await getAllCategoriesAndCounts(label); const categories = await getAllCategoriesAndCounts(label);
expect(Object.keys(categories)).toMatchObject( expect(Object.keys(categories)).toMatchObject(
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
Object.keys(data.categorical[label]) Object.keys(data.categorical[label])
); );
expect(Object.values(categories)).toMatchObject( expect(Object.values(categories)).toMatchObject(
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
Object.values(data.categorical[label]) Object.values(data.categorical[label])
); );
} }
@@ -159,10 +161,12 @@ describe("subset", () => {
const categories = await getAllCategoriesAndCounts(label); const categories = await getAllCategoriesAndCounts(label);
expect(Object.keys(categories)).toMatchObject( expect(Object.keys(categories)).toMatchObject(
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
Object.keys(data.subset.categorical[label]) Object.keys(data.subset.categorical[label])
); );
expect(Object.values(categories)).toMatchObject( expect(Object.values(categories)).toMatchObject(
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
Object.values(data.subset.categorical[label]) Object.values(data.subset.categorical[label])
); );
} }
@@ -195,6 +199,7 @@ describe("clipping", () => {
test("clip continuous", async () => { test("clip continuous", async () => {
await goToPage(appUrlBase); await goToPage(appUrlBase);
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string' is not assignable to par... Remove this comment to see the full error message
await clip(data.clip.min, data.clip.max); await clip(data.clip.min, data.clip.max);
const histBrushableAreaId = `histogram-${data.clip.metadata}-plot-brushable-area`; const histBrushableAreaId = `histogram-${data.clip.metadata}-plot-brushable-area`;
const coords = await calcDragCoordinates( const coords = await calcDragCoordinates(
@@ -254,6 +259,7 @@ describe("centroid labels", () => {
const generatedLabels = await getAllByClass("centroid-label"); const generatedLabels = await getAllByClass("centroid-label");
// Number of labels generated should be equal to size of the object // Number of labels generated should be equal to size of the object
expect(generatedLabels).toHaveLength( expect(generatedLabels).toHaveLength(
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
Object.keys(data.categorical[label]).length Object.keys(data.categorical[label]).length
); );
} }
@@ -275,6 +281,7 @@ describe("graph overlay", () => {
data.pan["coordinates-as-percent"] data.pan["coordinates-as-percent"]
); );
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
const categoryValue = Object.keys(data.categorical[category])[0]; const categoryValue = Object.keys(data.categorical[category])[0];
const initialCoordinates = await getElementCoordinates( const initialCoordinates = await getElementCoordinates(
`${categoryValue}-centroid-label` `${categoryValue}-centroid-label`
@@ -12,6 +12,7 @@ import {
getTestId, getTestId,
getTestClass, getTestClass,
getAllByClass, getAllByClass,
clickOnUntil,
getOneElementInnerHTML, getOneElementInnerHTML,
} from "./puppeteerUtils"; } from "./puppeteerUtils";
@@ -76,7 +77,13 @@ const brushThisGeneGeneset = "brush_this_gene";
const geneBrushedCellCount = "109"; const geneBrushedCellCount = "109";
const subsetGeneBrushedCellCount = "96"; const subsetGeneBrushedCellCount = "96";
async function setup(config) { const genesetDescriptionID =
"geneset-description-tooltip-fourth_gene_set: fourth description";
const genesetDescriptionString = "fourth_gene_set: fourth description";
const genesetToCheckForDescription = "fourth_gene_set";
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
async function setup(config: any) {
await goToPage(appUrlBase); await goToPage(appUrlBase);
if (config.categoricalAnno) { if (config.categoricalAnno) {
@@ -150,7 +157,8 @@ describe.each([
await expect(page).toClick(getTestClass("pop-1-geneset-expand")); await expect(page).toClick(getTestClass("pop-1-geneset-expand"));
await page.waitForFunction( await page.waitForFunction(
(selector) => !document.querySelector(selector), // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(selector: any) => !document.querySelector(selector),
{}, {},
getTestClass("gene-loading-spinner") getTestClass("gene-loading-spinner")
); );
@@ -165,7 +173,8 @@ describe.each([
await expect(page).toClick(getTestClass("pop-2-geneset-expand")); await expect(page).toClick(getTestClass("pop-2-geneset-expand"));
await page.waitForFunction( await page.waitForFunction(
(selector) => !document.querySelector(selector), // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(selector: any) => !document.querySelector(selector),
{}, {},
getTestClass("gene-loading-spinner") getTestClass("gene-loading-spinner")
); );
@@ -174,7 +183,7 @@ describe.each([
expect(genesHTML).toMatchSnapshot(); expect(genesHTML).toMatchSnapshot();
}); });
test("create a new geneset", async () => { test("create a new geneset and undo/redo", async () => {
if (config.withSubset) return; if (config.withSubset) return;
await setup(config); await setup(config);
@@ -184,19 +193,45 @@ describe.each([
await createGeneset(genesetName); await createGeneset(genesetName);
/* note: as of June 2021, the aria label is in the truncate component which clones the element */ /* note: as of June 2021, the aria label is in the truncate component which clones the element */
await assertGenesetExists(genesetName); await assertGenesetExists(genesetName);
await clickOn("undo");
await assertGenesetDoesNotExist(genesetName);
await clickOn("redo");
await assertGenesetExists(genesetName);
}); });
test("edit geneset name", async () => { test("edit geneset name and undo/redo", async () => {
await setup(config); await setup(config);
await editGenesetName(editableGenesetName, editText); await editGenesetName(editableGenesetName, editText);
await assertGenesetExists(newGenesetName); await assertGenesetExists(newGenesetName);
await clickOn("undo");
await assertGenesetExists(editableGenesetName);
await clickOn("redo");
await assertGenesetExists(newGenesetName);
}); });
test("delete a geneset", async () => { test("delete a geneset and undo/redo", async () => {
if (config.withSubset) return; if (config.withSubset) return;
await setup(config); await setup(config);
await deleteGeneset(genesetToDeleteName); await deleteGeneset(genesetToDeleteName);
await clickOn("undo");
await assertGenesetExists(genesetToDeleteName);
await clickOn("redo");
await assertGenesetDoesNotExist(genesetToDeleteName);
});
test("geneset description", async () => {
if (config.withSubset) return;
await setup(config);
await clickOnUntil(
`${genesetToCheckForDescription}:geneset-expand`,
async () => {
expect(page).toMatchElement(getTestId(genesetDescriptionID), {
text: genesetDescriptionString,
});
}
);
}); });
}); });
@@ -204,12 +239,16 @@ describe.each([
{ withSubset: true, tag: "subset" }, { withSubset: true, tag: "subset" },
{ withSubset: false, tag: "whole" }, { withSubset: false, tag: "whole" },
])("GENE crud operations and interactions", (config) => { ])("GENE crud operations and interactions", (config) => {
test("add a gene to geneset", async () => { test("add a gene to geneset and undo/redo", async () => {
await setup(config); await setup(config);
await addGeneToSet(setToAddGeneTo, geneToAddToSet); await addGeneToSet(setToAddGeneTo, geneToAddToSet);
await expandGeneset(setToAddGeneTo); await expandGeneset(setToAddGeneTo);
await assertGeneExistsInGeneset(geneToAddToSet); await assertGeneExistsInGeneset(geneToAddToSet);
await clickOn("undo");
await assertGeneDoesNotExist(geneToAddToSet);
await clickOn("redo");
await assertGeneExistsInGeneset(geneToAddToSet);
}); });
test("expand gene and brush", async () => { test("expand gene and brush", async () => {
await setup(config); await setup(config);
@@ -240,7 +279,7 @@ describe.each([
await colorByGene(geneToBrushAndColorBy); await colorByGene(geneToBrushAndColorBy);
await assertColorLegendLabel(geneToBrushAndColorBy); await assertColorLegendLabel(geneToBrushAndColorBy);
}); });
test("delete gene from geneset", async () => { test("delete gene from geneset and undo/redo", async () => {
// We've already deleted the gene // We've already deleted the gene
if (config.withSubset) return; if (config.withSubset) return;
@@ -249,6 +288,10 @@ describe.each([
await expandGeneset(setToRemoveFrom); await expandGeneset(setToRemoveFrom);
await removeGene(geneToRemove); await removeGene(geneToRemove);
await assertGeneDoesNotExist(geneToRemove); await assertGeneDoesNotExist(geneToRemove);
await clickOn("undo");
await assertGeneExistsInGeneset(geneToRemove);
await clickOn("redo");
await assertGeneDoesNotExist(geneToRemove);
}); });
}); });
@@ -362,8 +405,13 @@ describe.each([
expect(actualLabelName).toBe(expectedLabelName); expect(actualLabelName).toBe(expectedLabelName);
expect(actualLabelCount).toBe(expectedLabelCount); expect(actualLabelCount).toBe(expectedLabelCount);
async function getInnerText(element, className) { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
return element.$eval(getTestClass(className), (node) => node?.innerText); async function getInnerText(element: any, className: any) {
return element.$eval(
getTestClass(className),
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(node: any) => node?.innerText
);
} }
}); });
@@ -388,7 +436,9 @@ describe.each([
`categorical-value-count-${perTestCategoryName}-${perTestLabelName}` `categorical-value-count-${perTestCategoryName}-${perTestLabelName}`
); );
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
expect(await result.evaluate((node) => node.innerText)).toBe( expect(await result.evaluate((node) => node.innerText)).toBe(
// @ts-expect-error ts-migrate(2538) FIXME: Type 'boolean' cannot be used as an index type.
data.categoryLabel.newCount.bySubsetConfig[config.withSubset] data.categoryLabel.newCount.bySubsetConfig[config.withSubset]
); );
}); });
@@ -448,6 +498,7 @@ describe.each([
await createLabel(perTestCategoryName, labelName); await createLabel(perTestCategoryName, labelName);
await assertLabelExists(perTestCategoryName, labelName); await assertLabelExists(perTestCategoryName, labelName);
await clickOn("undo"); await clickOn("undo");
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
await assertLabelDoesNotExist(perTestCategoryName); await assertLabelDoesNotExist(perTestCategoryName);
await clickOn("redo"); await clickOn("redo");
await assertLabelExists(perTestCategoryName, labelName); await assertLabelExists(perTestCategoryName, labelName);
@@ -457,10 +508,12 @@ describe.each([
await setup(config); await setup(config);
await deleteLabel(perTestCategoryName, perTestLabelName); await deleteLabel(perTestCategoryName, perTestLabelName);
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
await assertLabelDoesNotExist(perTestCategoryName); await assertLabelDoesNotExist(perTestCategoryName);
await clickOn("undo"); await clickOn("undo");
await assertLabelExists(perTestCategoryName, perTestLabelName); await assertLabelExists(perTestCategoryName, perTestLabelName);
await clickOn("redo"); await clickOn("redo");
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
await assertLabelDoesNotExist(perTestCategoryName); await assertLabelDoesNotExist(perTestCategoryName);
}); });
@@ -491,11 +544,9 @@ describe.each([
const labels = await getAllByClass("categorical-row"); const labels = await getAllByClass("categorical-row");
const result = await Promise.all( const result = await Promise.all(
labels.map((label) => { labels.map((label) =>
return page.evaluate((element) => { page.evaluate((element) => element.outerHTML, label)
return element.outerHTML; )
}, label);
})
); );
expect(result).toMatchSnapshot(); expect(result).toMatchSnapshot();
@@ -523,9 +574,11 @@ describe.each([
expect(result).toMatchSnapshot(); expect(result).toMatchSnapshot();
}); });
async function assertCategoryExists(categoryName) { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
async function assertCategoryExists(categoryName: any) {
const handle = await waitByID(`${categoryName}:category-label`); const handle = await waitByID(`${categoryName}:category-label`);
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
const result = await handle.evaluate((node) => const result = await handle.evaluate((node) =>
node.getAttribute("aria-label") node.getAttribute("aria-label")
); );
@@ -533,7 +586,8 @@ describe.each([
return expect(result).toBe(categoryName); return expect(result).toBe(categoryName);
} }
async function assertLabelExists(categoryName, labelName) { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
async function assertLabelExists(categoryName: any, labelName: any) {
await expect(page).toMatchElement( await expect(page).toMatchElement(
getTestId(`${categoryName}:category-expand`) getTestId(`${categoryName}:category-expand`)
); );
@@ -545,11 +599,13 @@ describe.each([
); );
expect( expect(
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
await previous.evaluate((node) => node.getAttribute("aria-label")) await previous.evaluate((node) => node.getAttribute("aria-label"))
).toBe(labelName); ).toBe(labelName);
} }
async function assertLabelDoesNotExist(categoryName, labelName) { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
async function assertLabelDoesNotExist(categoryName: any, labelName: any) {
await expandCategory(categoryName); await expandCategory(categoryName);
const result = await page.$( const result = await page.$(
`[data-testid='categorical-value-${categoryName}-${labelName}']` `[data-testid='categorical-value-${categoryName}-${labelName}']`
+4 -4
View File
@@ -1,10 +1,10 @@
{ {
"testRunner": "jest-circus/runner", "testRunner": "jest-circus/runner",
"preset": "jest-puppeteer", "preset": "jest-puppeteer",
"testMatch": ["**/__tests__/**/?(*.)(spec|test).js?(x)"], "testMatch": ["**/__tests__/**/?(*.)(spec|test).ts?(x)"],
"setupFiles": ["../setupMissingGlobals.js"], "setupFiles": ["../setupMissingGlobals.ts"],
"setupFilesAfterEnv": ["expect-puppeteer", "./puppeteer.setup.js"], "setupFilesAfterEnv": ["expect-puppeteer", "./puppeteer.setup.ts"],
"globalSetup": "../globalSetup.js", "globalSetup": "../globalSetup.ts",
"globalTeardown": "jest-environment-puppeteer/teardown", "globalTeardown": "jest-environment-puppeteer/teardown",
"testEnvironment": "./screenshot_env.js" "testEnvironment": "./screenshot_env.js"
} }
@@ -23,6 +23,7 @@ beforeEach(async () => {
const userAgent = await browser.userAgent(); const userAgent = await browser.userAgent();
await page.setUserAgent(`${userAgent}bot`); await page.setUserAgent(`${userAgent}bot`);
// @ts-expect-error ts-migrate(2341) FIXME: Property '_client' is private and only accessible ... Remove this comment to see the full error message
await page._client.send("Animation.setPlaybackRate", { playbackRate: 12 }); await page._client.send("Animation.setPlaybackRate", { playbackRate: 12 });
page.on("pageerror", (err) => { page.on("pageerror", (err) => {
@@ -49,7 +50,8 @@ beforeEach(async () => {
} }
const errorMsgText = await Promise.all( const errorMsgText = await Promise.all(
// TODO can we do this without internal properties? // TODO can we do this without internal properties?
msg.args().map((arg) => arg._remoteObject.description) // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
msg.args().map((arg: any) => arg._remoteObject.description)
); );
throw new Error(`Console error: ${errorMsgText}`); throw new Error(`Console error: ${errorMsgText}`);
} }
-131
View File
@@ -1,131 +0,0 @@
/* eslint-disable no-await-in-loop -- await in loop is needed to emulate sequential user actions */
export function getTestId(id) {
return `[data-testid='${id}']`;
}
export function getTestClass(className) {
return `[data-testclass='${className}']`;
}
export async function waitByID(testId, props = {}) {
return page.waitForSelector(getTestId(testId), props);
}
export async function waitByClass(testClass, props = {}) {
return page.waitForSelector(`[data-testclass='${testClass}']`, props);
}
export async function waitForAllByIds(testIds) {
await Promise.all(
testIds.map((testId) => page.waitForSelector(getTestId(testId)))
);
}
export async function getAllByClass(testClass) {
return page.$$(`[data-testclass=${testClass}]`);
}
export async function typeInto(testId, text) {
// blueprint's typeahead is treating typing weird, clicking & waiting first solves this
// only works for text without special characters
await waitByID(testId);
const selector = getTestId(testId);
// type ahead can be annoying if you don't pause before you type
await page.click(selector);
await page.waitForTimeout(200);
await page.type(selector, text);
}
export async function clearInputAndTypeInto(testId, text) {
await waitByID(testId);
const selector = getTestId(testId);
// only works for text without special characters
// type ahead can be annoying if you don't pause before you type
await page.click(selector);
await page.waitForTimeout(200);
// select all
await page.click(selector, { clickCount: 3 });
await page.keyboard.press("Backspace");
await page.type(selector, text);
}
export async function clickOn(testId, options = {}) {
await expect(page).toClick(getTestId(testId), options);
}
/**
* (thuang): There are times when Puppeteer clicks on a button and the page doesn't respond.
* So I added clickOnUntil() to retry clicking until a given condition is met.
*/
export async function clickOnUntil(testId, assert) {
const MAX_RETRY = 10;
const WAIT_FOR_MS = 200;
let retry = 0;
while (retry < MAX_RETRY) {
try {
await clickOn(testId);
await assert();
break;
} catch (error) {
retry += 1;
await page.waitForTimeout(WAIT_FOR_MS);
}
}
if (retry === MAX_RETRY) {
throw Error("clickOnUntil() assertion failed!");
}
}
export async function getOneElementInnerHTML(selector, options = {}) {
await page.waitForSelector(selector, options);
return page.$eval(selector, (el) => el.innerHTML);
}
export async function getOneElementInnerText(selector) {
expect(page).toMatchElement(selector);
return page.$eval(selector, (el) => el.innerText);
}
export async function getElementCoordinates(testId) {
return page.$eval(getTestId(testId), (elem) => {
const { left, top } = elem.getBoundingClientRect();
return [left, top];
});
}
async function clickTermsOfService() {
if (!(await isElementPresent(getTestId("tos-cookies-accept")))) return;
await clickOn("tos-cookies-accept");
}
async function nameNewAnnotation() {
if (await isElementPresent(getTestId("annotation-dialog"))) {
await typeInto("new-annotation-name", "ignoreE2E");
await clickOn("submit-annotation");
// wait for the page to load
await waitByClass("autosave-complete");
}
}
export async function goToPage(url) {
await page.goto(url, {
waitUntil: "networkidle0",
});
await nameNewAnnotation();
await clickTermsOfService();
}
export async function isElementPresent(selector, options) {
return Boolean(await page.$(selector, options));
}
/* eslint-enable no-await-in-loop -- await in loop is needed to emulate sequential user actions */
+151
View File
@@ -0,0 +1,151 @@
/* eslint-disable no-await-in-loop -- await in loop is needed to emulate sequential user actions */
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export function getTestId(id: any) {
return `[data-testid='${id}']`;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export function getTestClass(className: any) {
return `[data-testclass='${className}']`;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function waitByID(testId: any, props = {}) {
return page.waitForSelector(getTestId(testId), props);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function waitByClass(testClass: any, props = {}) {
return page.waitForSelector(`[data-testclass='${testClass}']`, props);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function waitForAllByIds(testIds: any) {
await Promise.all(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
testIds.map((testId: any) => page.waitForSelector(getTestId(testId)))
);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function getAllByClass(testClass: any) {
return page.$$(`[data-testclass=${testClass}]`);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function typeInto(testId: any, text: any) {
// blueprint's typeahead is treating typing weird, clicking & waiting first solves this
// only works for text without special characters
await waitByID(testId);
const selector = getTestId(testId);
// type ahead can be annoying if you don't pause before you type
await page.click(selector);
await page.waitForTimeout(200);
await page.type(selector, text);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function clearInputAndTypeInto(testId: any, text: any) {
await waitByID(testId);
const selector = getTestId(testId);
// only works for text without special characters
// type ahead can be annoying if you don't pause before you type
await page.click(selector);
await page.waitForTimeout(200);
// select all
await page.click(selector, { clickCount: 3 });
await page.keyboard.press("Backspace");
await page.type(selector, text);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function clickOn(testId: any, options = {}) {
await expect(page).toClick(getTestId(testId), options);
}
/**
* (thuang): There are times when Puppeteer clicks on a button and the page doesn't respond.
* So I added clickOnUntil() to retry clicking until a given condition is met.
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function clickOnUntil(testId: any, assert: any) {
const MAX_RETRY = 10;
const WAIT_FOR_MS = 200;
let retry = 0;
while (retry < MAX_RETRY) {
try {
await clickOn(testId);
await assert();
break;
} catch (error) {
retry += 1;
await page.waitForTimeout(WAIT_FOR_MS);
}
}
if (retry === MAX_RETRY) {
throw Error("clickOnUntil() assertion failed!");
}
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function getOneElementInnerHTML(selector: any, options = {}) {
await page.waitForSelector(selector, options);
return page.$eval(selector, (el) => el.innerHTML);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function getOneElementInnerText(selector: any) {
expect(page).toMatchElement(selector);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
return page.$eval(selector, (el) => (el as any).innerText);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function getElementCoordinates(testId: any) {
return page.$eval(getTestId(testId), (elem) => {
const { left, top } = elem.getBoundingClientRect();
return [left, top];
});
}
async function clickTermsOfService() {
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
if (!(await isElementPresent(getTestId("tos-cookies-accept")))) return;
await clickOn("tos-cookies-accept");
}
async function nameNewAnnotation() {
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
if (await isElementPresent(getTestId("annotation-dialog"))) {
await typeInto("new-annotation-name", "ignoreE2E");
await clickOn("submit-annotation");
// wait for the page to load
await waitByClass("autosave-complete");
}
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function goToPage(url: any) {
await page.goto(url, {
waitUntil: "networkidle0",
});
await nameNewAnnotation();
await clickTermsOfService();
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function isElementPresent(selector: any, options: any) {
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 2.
return Boolean(await page.$(selector, options));
}
/* eslint-enable no-await-in-loop -- await in loop is needed to emulate sequential user actions */
+4
View File
@@ -1,7 +1,11 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const PuppeteerEnvironment = require("jest-environment-puppeteer"); const PuppeteerEnvironment = require("jest-environment-puppeteer");
require("jest-circus"); require("jest-circus");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const ENV_DEFAULT = require("../../../environment.default.json"); const ENV_DEFAULT = require("../../../environment.default.json");
// @ts-expect-error ts-migrate(2451) FIXME: Cannot redeclare block-scoped variable 'takeScreen... Remove this comment to see the full error message
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const takeScreenshot = require("./takeScreenshot"); const takeScreenshot = require("./takeScreenshot");
class ScreenshotEnvironment extends PuppeteerEnvironment { class ScreenshotEnvironment extends PuppeteerEnvironment {
@@ -1,8 +1,12 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment --- FIXME: disabled temporarily on migrate to TS.
// @ts-ignore FIXME: 'globalSetup.ts' cannot be compiled under '--isola... Remove this comment to see the full error message
const { const {
SecretsManagerClient, SecretsManagerClient,
GetSecretValueCommand, GetSecretValueCommand,
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
} = require("@aws-sdk/client-secrets-manager"); } = require("@aws-sdk/client-secrets-manager");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const { setup } = require("jest-environment-puppeteer"); const { setup } = require("jest-environment-puppeteer");
const client = new SecretsManagerClient({ region: "us-west-2" }); const client = new SecretsManagerClient({ region: "us-west-2" });
@@ -20,7 +20,16 @@ describe("cascade", () => {
const reducer = cascadeReducers([ const reducer = cascadeReducers([
[ [
"foo", "foo",
(currentState, action, nextSharedState, prevSharedState) => { (
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
currentState: 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.
nextSharedState: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
prevSharedState: any
) => {
expect(currentState).toBeUndefined(); expect(currentState).toBeUndefined();
expect(action).toEqual(topLevelAction); expect(action).toEqual(topLevelAction);
expect(nextSharedState).toStrictEqual({}); expect(nextSharedState).toStrictEqual({});
@@ -30,7 +39,16 @@ describe("cascade", () => {
], ],
[ [
"bar", "bar",
(currentState, action, nextSharedState, prevSharedState) => { (
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
currentState: 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.
nextSharedState: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
prevSharedState: any
) => {
expect(currentState).toBeUndefined(); expect(currentState).toBeUndefined();
expect(action).toEqual(topLevelAction); expect(action).toEqual(topLevelAction);
expect(nextSharedState).toStrictEqual({ foo: 0 }); expect(nextSharedState).toStrictEqual({ foo: 0 });
@@ -501,6 +501,7 @@ describe("geneset: set tid", () => {
test("not a number error", () => { test("not a number error", () => {
expect(() => { expect(() => {
genesetsReducer( genesetsReducer(
// @ts-expect-error ts-migrate(2322) FIXME: Type 'number' is not assignable to type 'undefined... Remove this comment to see the full error message
{ lastTid: 1 }, { lastTid: 1 },
{ {
type: "geneset: set tid", type: "geneset: set tid",
@@ -513,6 +514,7 @@ describe("geneset: set tid", () => {
test("decrement error", () => { test("decrement error", () => {
expect(() => { expect(() => {
genesetsReducer( genesetsReducer(
// @ts-expect-error ts-migrate(2322) FIXME: Type 'number' is not assignable to type 'undefined... Remove this comment to see the full error message
{ lastTid: 1 }, { lastTid: 1 },
{ {
type: "geneset: set tid", type: "geneset: set tid",
@@ -2,6 +2,7 @@ import undoable from "../../src/reducers/undoable";
describe("create", () => { describe("create", () => {
test("no keys", () => { test("no keys", () => {
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2-3 arguments, but got 1.
expect(() => undoable(() => {})).toThrow(); expect(() => undoable(() => {})).toThrow();
expect(() => undoable(() => {}, null)).toThrow(); expect(() => undoable(() => {}, null)).toThrow();
expect(() => undoable(() => {}, [])).toThrow(); expect(() => undoable(() => {}, [])).toThrow();
@@ -23,9 +24,8 @@ describe("create", () => {
describe("undo", () => { describe("undo", () => {
test("expected state modifications", () => { test("expected state modifications", () => {
const initialState = { a: 0, b: 1000 }; const initialState = { a: 0, b: 1000 };
const reducer = (state) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
return { a: state.a + 1, b: state.b + 1 }; const reducer = (state: any) => ({ a: state.a + 1, b: state.b + 1 });
};
const undoableReducer = undoable(reducer, ["a"]); const undoableReducer = undoable(reducer, ["a"]);
const s1 = undoableReducer(initialState, { type: "test" }); const s1 = undoableReducer(initialState, { type: "test" });
@@ -43,10 +43,10 @@ describe("undo", () => {
describe("redo", () => { describe("redo", () => {
const initialState = { a: 0, b: 1000 }; const initialState = { a: 0, b: 1000 };
const reducer = (state) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
return { a: state.a + 1, b: state.b + 1 }; 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; let UR: any;
beforeEach(() => { beforeEach(() => {
UR = undoable(reducer, ["a"]); UR = undoable(reducer, ["a"]);
@@ -5,5 +5,6 @@ the jest test environment).
import { TextDecoder, TextEncoder } from "util"; import { TextDecoder, TextEncoder } from "util";
// @ts-expect-error ts-migrate(2322) FIXME: Type 'typeof TextDecoder' is not assignable to typ... Remove this comment to see the full error message
global.TextDecoder = TextDecoder; global.TextDecoder = TextDecoder;
global.TextEncoder = TextEncoder; global.TextEncoder = TextEncoder;
@@ -14,10 +14,13 @@ import { Dataframe } from "../../../src/util/dataframe";
enableFetchMocks(); enableFetchMocks();
describe("AnnoMatrix", () => { describe("AnnoMatrix", () => {
let annoMatrix; // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let annoMatrix: any;
beforeEach(async () => { beforeEach(async () => {
fetch.resetMocks(); // reset all fetch mocking state // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).resetMocks(); // reset all fetch mocking state
// reset all fetch mocking state
annoMatrix = new AnnoMatrixLoader( annoMatrix = new AnnoMatrixLoader(
serverMocks.baseDataURL, serverMocks.baseDataURL,
serverMocks.schema.schema serverMocks.schema.schema
@@ -36,7 +39,8 @@ describe("AnnoMatrix", () => {
}); });
test("simple single column fetch", async () => { test("simple single column fetch", async () => {
fetch.once(serverMocks.annotationsObs(["name_0"])); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(serverMocks.annotationsObs(["name_0"]));
const df = await annoMatrix.fetch("obs", "name_0"); const df = await annoMatrix.fetch("obs", "name_0");
expect(df).toBeInstanceOf(Dataframe); expect(df).toBeInstanceOf(Dataframe);
@@ -45,7 +49,8 @@ describe("AnnoMatrix", () => {
}); });
test("simple multi column fetch", async () => { test("simple multi column fetch", async () => {
fetch // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any)
.once(serverMocks.annotationsObs(["name_0"])) .once(serverMocks.annotationsObs(["name_0"]))
.once(serverMocks.annotationsObs(["n_genes"])); .once(serverMocks.annotationsObs(["n_genes"]));
@@ -55,9 +60,13 @@ describe("AnnoMatrix", () => {
}); });
describe("fetch from field", () => { describe("fetch from field", () => {
const getLastTwo = async (field) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const getLastTwo = async (field: any) => {
const columnNames = annoMatrix.getMatrixColumns(field).slice(-2); const columnNames = annoMatrix.getMatrixColumns(field).slice(-2);
fetch.mockResponses(...columnNames.map(() => serverMocks.responder)); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).mockResponses(
...columnNames.map(() => serverMocks.responder)
);
await expect( await expect(
annoMatrix.fetch(field, columnNames) annoMatrix.fetch(field, columnNames)
).resolves.toBeInstanceOf(Dataframe); ).resolves.toBeInstanceOf(Dataframe);
@@ -70,19 +79,22 @@ describe("AnnoMatrix", () => {
test("fetch - test all query forms", async () => { test("fetch - test all query forms", async () => {
// single string is a column name // single string is a column name
fetch.once(serverMocks.annotationsObs(["n_genes"])); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(serverMocks.annotationsObs(["n_genes"]));
await expect(annoMatrix.fetch("obs", "n_genes")).resolves.toBeInstanceOf( await expect(annoMatrix.fetch("obs", "n_genes")).resolves.toBeInstanceOf(
Dataframe Dataframe
); );
// array of column names, expecting n_genes to be cached. // array of column names, expecting n_genes to be cached.
fetch.once(serverMocks.annotationsObs(["percent_mito"])); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(serverMocks.annotationsObs(["percent_mito"]));
await expect( await expect(
annoMatrix.fetch("obs", ["n_genes", "percent_mito"]) annoMatrix.fetch("obs", ["n_genes", "percent_mito"])
).resolves.toBeInstanceOf(Dataframe); ).resolves.toBeInstanceOf(Dataframe);
// more complex value filter query, enumerated // more complex value filter query, enumerated
fetch.once(serverMocks.responder); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(serverMocks.responder);
await expect( await expect(
annoMatrix.fetch("X", { annoMatrix.fetch("X", {
where: { where: {
@@ -95,7 +107,8 @@ describe("AnnoMatrix", () => {
// more complex value filter query, range // more complex value filter query, range
const varIndex = annoMatrix.schema.annotations.var.index; const varIndex = annoMatrix.schema.annotations.var.index;
fetch // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any)
.once( .once(
serverMocks.withExpected("/data/var", [[`var:${varIndex}`, "SUMO3"]]) serverMocks.withExpected("/data/var", [[`var:${varIndex}`, "SUMO3"]])
) )
@@ -171,7 +184,8 @@ describe("AnnoMatrix", () => {
expect(am1.nObs).toEqual(am2.nObs); expect(am1.nObs).toEqual(am2.nObs);
expect(am1.nVar).toEqual(am2.nVar); expect(am1.nVar).toEqual(am2.nVar);
fetch // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any)
.once(serverMocks.annotationsObs(["n_genes"])) .once(serverMocks.annotationsObs(["n_genes"]))
.once(serverMocks.annotationsObs(["n_genes"])); .once(serverMocks.annotationsObs(["n_genes"]));
const ng1 = await am1.fetch("obs", "n_genes"); const ng1 = await am1.fetch("obs", "n_genes");
@@ -185,9 +199,11 @@ describe("AnnoMatrix", () => {
}); });
describe("add/drop column", () => { describe("add/drop column", () => {
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'base' implicitly has an 'any' type.
async function addDrop(base) { async function addDrop(base) {
expect(base.getMatrixColumns("obs")).not.toContain("foo"); expect(base.getMatrixColumns("obs")).not.toContain("foo");
fetch.mockRejectOnce(new Error("unknown column name")); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).mockRejectOnce(new Error("unknown column name"));
await expect(base.fetch("obs", "foo")).rejects.toThrow( await expect(base.fetch("obs", "foo")).rejects.toThrow(
"unknown column name" "unknown column name"
); );
@@ -212,7 +228,8 @@ describe("AnnoMatrix", () => {
const am2 = am1.dropObsColumn("foo"); const am2 = am1.dropObsColumn("foo");
expect(base.getMatrixColumns("obs")).not.toContain("foo"); expect(base.getMatrixColumns("obs")).not.toContain("foo");
expect(am2.getMatrixColumns("obs")).not.toContain("foo"); expect(am2.getMatrixColumns("obs")).not.toContain("foo");
fetch.mockRejectOnce(new Error("unknown column name")); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).mockRejectOnce(new Error("unknown column name"));
await expect(am2.fetch("obs", "foo")).rejects.toThrow( await expect(am2.fetch("obs", "foo")).rejects.toThrow(
"unknown column name" "unknown column name"
); );
@@ -235,14 +252,16 @@ describe("AnnoMatrix", () => {
const am4 = clip(am3, 0, 1); const am4 = clip(am3, 0, 1);
await addDrop(am4); await addDrop(am4);
fetch.mockResponse(serverMocks.responder); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).mockResponse(serverMocks.responder);
await am1.fetch("obs", am1.getMatrixColumns("obs")); await am1.fetch("obs", am1.getMatrixColumns("obs"));
await am2.fetch("obs", am2.getMatrixColumns("obs")); await am2.fetch("obs", am2.getMatrixColumns("obs"));
await am3.fetch("obs", am3.getMatrixColumns("obs")); await am3.fetch("obs", am3.getMatrixColumns("obs"));
await am4.fetch("obs", am4.getMatrixColumns("obs")); await am4.fetch("obs", am4.getMatrixColumns("obs"));
fetch.resetMocks(); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).resetMocks();
await addDrop(am1); await addDrop(am1);
await addDrop(am2); await addDrop(am2);
@@ -252,6 +271,7 @@ describe("AnnoMatrix", () => {
}); });
describe("setObsColumnValues", () => { describe("setObsColumnValues", () => {
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'base' implicitly has an 'any' type.
async function addSetDrop(base) { async function addSetDrop(base) {
/* add column */ /* add column */
let am = base.addObsColumn( let am = base.addObsColumn(
@@ -287,7 +307,8 @@ describe("AnnoMatrix", () => {
); );
/* drop column */ /* drop column */
fetch.mockRejectOnce(new Error("unknown column name")); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).mockRejectOnce(new Error("unknown column name"));
am = am1.dropObsColumn("test"); am = am1.dropObsColumn("test");
await expect(am.fetch("obs", "test")).rejects.toThrow( await expect(am.fetch("obs", "test")).rejects.toThrow(
"unknown column name" "unknown column name"
@@ -308,7 +329,8 @@ describe("AnnoMatrix", () => {
const am3 = isubset(annoMatrix, [10, 1, 0, 30, 2]); const am3 = isubset(annoMatrix, [10, 1, 0, 30, 2]);
await addSetDrop(am3); await addSetDrop(am3);
fetch.mockResponse(serverMocks.responder); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).mockResponse(serverMocks.responder);
await am1.fetch("obs", am1.getMatrixColumns("obs")); await am1.fetch("obs", am1.getMatrixColumns("obs"));
await am2.fetch("obs", am2.getMatrixColumns("obs")); await am2.fetch("obs", am2.getMatrixColumns("obs"));
@@ -17,11 +17,15 @@ import { rangeFill } from "../../../src/util/range";
enableFetchMocks(); enableFetchMocks();
describe("AnnoMatrixCrossfilter", () => { describe("AnnoMatrixCrossfilter", () => {
let annoMatrix; // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let crossfilter; let annoMatrix: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let crossfilter: any;
beforeEach(async () => { beforeEach(async () => {
fetch.resetMocks(); // reset all fetch mocking state // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).resetMocks(); // reset all fetch mocking state
// reset all fetch mocking state
annoMatrix = new AnnoMatrixLoader( annoMatrix = new AnnoMatrixLoader(
serverMocks.baseDataURL, serverMocks.baseDataURL,
serverMocks.schema.schema serverMocks.schema.schema
@@ -67,7 +71,10 @@ describe("AnnoMatrixCrossfilter", () => {
crossfilter.obsCrossfilter.hasDimension("obs/louvain") crossfilter.obsCrossfilter.hasDimension("obs/louvain")
).toBeFalsy(); ).toBeFalsy();
fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain])); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(
serverMocks.dataframeResponse(["louvain"], [obsLouvain])
);
let newCrossfilter = await crossfilter.select("obs", "louvain", { let newCrossfilter = await crossfilter.select("obs", "louvain", {
mode: "none", mode: "none",
}); });
@@ -76,7 +83,8 @@ describe("AnnoMatrixCrossfilter", () => {
expect( expect(
newCrossfilter.obsCrossfilter.hasDimension("obs/louvain") newCrossfilter.obsCrossfilter.hasDimension("obs/louvain")
).toBeTruthy(); ).toBeTruthy();
expect(fetch.mock.calls).toHaveLength(1); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
expect((fetch as any).mock.calls).toHaveLength(1);
newCrossfilter = await crossfilter.select("obs", "louvain", { newCrossfilter = await crossfilter.select("obs", "louvain", {
mode: "all", mode: "all",
@@ -87,7 +95,10 @@ describe("AnnoMatrixCrossfilter", () => {
test("simple column select", async () => { test("simple column select", async () => {
let xfltr; let xfltr;
fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain])); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(
serverMocks.dataframeResponse(["louvain"], [obsLouvain])
);
xfltr = await crossfilter.select("obs", "louvain", { xfltr = await crossfilter.select("obs", "louvain", {
mode: "exact", mode: "exact",
values: ["NK cells", "B cells"], values: ["NK cells", "B cells"],
@@ -105,6 +116,7 @@ describe("AnnoMatrixCrossfilter", () => {
expect(xfltr.allSelectedLabels()).toEqual( expect(xfltr.allSelectedLabels()).toEqual(
Int32Array.from( Int32Array.from(
obsLouvain.reduce((acc, val, idx) => { obsLouvain.reduce((acc, val, idx) => {
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'number' is not assignable to par... Remove this comment to see the full error message
if (val === "NK cells" || val === "B cells") acc.push(idx); if (val === "NK cells" || val === "B cells") acc.push(idx);
return acc; return acc;
}, []) }, [])
@@ -124,10 +136,13 @@ describe("AnnoMatrixCrossfilter", () => {
const values = df.col("louvain").asArray(); const values = df.col("louvain").asArray();
const selected = xfltr.allSelectedMask(); const selected = xfltr.allSelectedMask();
values.every( values.every(
(val, idx) => !["NK cells", "B cells"].includes(val) !== !selected[idx] // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(val: any, idx: any) =>
!["NK cells", "B cells"].includes(val) !== !selected[idx]
); );
fetch.once( // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(
serverMocks.dataframeResponse(["n_genes"], [new Int32Array(obsNGenes)]) serverMocks.dataframeResponse(["n_genes"], [new Int32Array(obsNGenes)])
); );
xfltr = await xfltr.select("obs", "n_genes", { xfltr = await xfltr.select("obs", "n_genes", {
@@ -146,6 +161,7 @@ describe("AnnoMatrixCrossfilter", () => {
val < 500 && val < 500 &&
(louvain === "NK cells" || louvain === "B cells") (louvain === "NK cells" || louvain === "B cells")
) )
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'number' is not assignable to par... Remove this comment to see the full error message
acc.push(idx); acc.push(idx);
return acc; return acc;
}, []) }, [])
@@ -160,7 +176,8 @@ describe("AnnoMatrixCrossfilter", () => {
const varIndex = annoMatrix.schema.annotations.var.index; const varIndex = annoMatrix.schema.annotations.var.index;
const { nObs } = annoMatrix.schema.dataframe; const { nObs } = annoMatrix.schema.dataframe;
fetch.once( // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(
serverMocks.dataframeResponse( serverMocks.dataframeResponse(
["TEST"], ["TEST"],
[rangeFill(new Float32Array(nObs), 0, 0.1)] [rangeFill(new Float32Array(nObs), 0, 0.1)]
@@ -196,14 +213,19 @@ describe("AnnoMatrixCrossfilter", () => {
}); });
const values = df.icol(0).asArray(); const values = df.icol(0).asArray();
const selected = xfltr.allSelectedMask(); const selected = xfltr.allSelectedMask();
values.every((val, idx) => !(val >= 0 && val <= 50) !== !selected[idx]); values.every(
expect(selected.reduce((acc, val) => (val ? acc + 1 : acc), 0)).toEqual( // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
xfltr.countSelected() (val: any, idx: any) => !(val >= 0 && val <= 50) !== !selected[idx]
); );
expect(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
selected.reduce((acc: any, val: any) => (val ? acc + 1 : acc), 0)
).toEqual(xfltr.countSelected());
}); });
test("spatial column select", async () => { test("spatial column select", async () => {
fetch.once( // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(
serverMocks.dataframeResponse( serverMocks.dataframeResponse(
["umap_0", "umap_1"], ["umap_0", "umap_1"],
[Float32Array.from(embUmap[0]), Float32Array.from(embUmap[1])] [Float32Array.from(embUmap[0]), Float32Array.from(embUmap[1])]
@@ -222,6 +244,7 @@ describe("AnnoMatrixCrossfilter", () => {
test("select on subset", async () => { test("select on subset", async () => {
const mask = new Uint8Array(annoMatrix.nObs).fill(0); const mask = new Uint8Array(annoMatrix.nObs).fill(0);
for (let i = 0; i < mask.length; i += 2) { for (let i = 0; i < mask.length; i += 2) {
// @ts-expect-error ts-migrate(2322) FIXME: Type 'boolean' is not assignable to type 'number'.
mask[i] = true; mask[i] = true;
} }
const annoMatrixSubset = isubsetMask(annoMatrix, mask); const annoMatrixSubset = isubsetMask(annoMatrix, mask);
@@ -230,7 +253,10 @@ describe("AnnoMatrixCrossfilter", () => {
let xfltr = new AnnoMatrixObsCrossfilter(annoMatrixSubset); let xfltr = new AnnoMatrixObsCrossfilter(annoMatrixSubset);
expect(xfltr.countSelected()).toEqual(annoMatrixSubset.nObs); expect(xfltr.countSelected()).toEqual(annoMatrixSubset.nObs);
fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain])); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(
serverMocks.dataframeResponse(["louvain"], [obsLouvain])
);
xfltr = await xfltr.select("obs", "louvain", { xfltr = await xfltr.select("obs", "louvain", {
mode: "exact", mode: "exact",
values: ["NK cells", "B cells"], values: ["NK cells", "B cells"],
@@ -243,7 +269,9 @@ describe("AnnoMatrixCrossfilter", () => {
const values = df.col("louvain").asArray(); const values = df.col("louvain").asArray();
const selected = xfltr.allSelectedMask(); const selected = xfltr.allSelectedMask();
values.every( values.every(
(val, idx) => !["NK cells", "B cells"].includes(val) !== !selected[idx] // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(val: any, idx: any) =>
!["NK cells", "B cells"].includes(val) !== !selected[idx]
); );
}); });
@@ -256,7 +284,8 @@ describe("AnnoMatrixCrossfilter", () => {
"unable to obsSelect upon the var dimension" "unable to obsSelect upon the var dimension"
); );
fetch.mockRejectOnce(new Error("unknown column name")); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).mockRejectOnce(new Error("unknown column name"));
await expect(crossfilter.select("obs", "foo")).rejects.toThrow( await expect(crossfilter.select("obs", "foo")).rejects.toThrow(
"unknown column name" "unknown column name"
); );
@@ -267,24 +296,29 @@ describe("AnnoMatrixCrossfilter", () => {
/* /*
test the matrix mutators via crossfilter proxy test the matrix mutators via crossfilter proxy
*/ */
async function helperAddTestCol(cf, colName, colSchema = null) { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
async function helperAddTestCol(cf: any, colName: any, colSchema = null) {
expect( expect(
cf.annoMatrix.getMatrixColumns("obs").includes(colName) cf.annoMatrix.getMatrixColumns("obs").includes(colName)
).toBeFalsy(); ).toBeFalsy();
if (colSchema === null) { if (colSchema === null) {
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ name: any; type: string; categories: strin... Remove this comment to see the full error message
colSchema = { colSchema = {
name: colName, name: colName,
type: "categorical", type: "categorical",
categories: ["toasty"], categories: ["toasty"],
}; };
} }
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
colSchema.name = colName; colSchema.name = colName;
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
const initValue = colSchema.categories[0]; const initValue = colSchema.categories[0];
const xfltr = cf.addObsColumn(colSchema, Array, initValue); const xfltr = cf.addObsColumn(colSchema, Array, initValue);
expect( expect(
xfltr.annoMatrix.schema.annotations.obs.columns.filter( xfltr.annoMatrix.schema.annotations.obs.columns.filter(
(v) => v.name === colName // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(v: any) => v.name === colName
) )
).toHaveLength(1); ).toHaveLength(1);
const df = await xfltr.annoMatrix.fetch("obs", colName); const df = await xfltr.annoMatrix.fetch("obs", colName);
@@ -314,7 +348,8 @@ describe("AnnoMatrixCrossfilter", () => {
}); });
expect( expect(
xfltr.annoMatrix.schema.annotations.obs.columns.filter( xfltr.annoMatrix.schema.annotations.obs.columns.filter(
(v) => v.name === "foo" // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(v: any) => v.name === "foo"
) )
).toHaveLength(1); ).toHaveLength(1);
@@ -323,8 +358,8 @@ describe("AnnoMatrixCrossfilter", () => {
expect( expect(
df df
.col("foo") .col("foo")
.asArray() .asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.every((v) => v === "A") .every((v: any) => v === "A")
).toBeTruthy(); ).toBeTruthy();
// check that we catch dups // check that we catch dups
@@ -361,11 +396,13 @@ describe("AnnoMatrixCrossfilter", () => {
xfltr = xfltr.dropObsColumn("foo"); xfltr = xfltr.dropObsColumn("foo");
expect( expect(
xfltr.annoMatrix.schema.annotations.obs.columns.filter( xfltr.annoMatrix.schema.annotations.obs.columns.filter(
(v) => v.name === "foo" // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(v: any) => v.name === "foo"
) )
).toHaveLength(0); ).toHaveLength(0);
expect(xfltr.annoMatrix.schema.annotations.obsByName.foo).toBeUndefined(); expect(xfltr.annoMatrix.schema.annotations.obsByName.foo).toBeUndefined();
fetch.mockRejectOnce(new Error("unknown column name")); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).mockRejectOnce(new Error("unknown column name"));
await expect(xfltr.annoMatrix.fetch("obs", "foo")).rejects.toThrow( await expect(xfltr.annoMatrix.fetch("obs", "foo")).rejects.toThrow(
"unknown column name" "unknown column name"
); );
@@ -378,7 +415,8 @@ describe("AnnoMatrixCrossfilter", () => {
}); });
xfltr = xfltr.dropObsColumn("bar"); xfltr = xfltr.dropObsColumn("bar");
fetch.mockRejectOnce(new Error("unknown column name")); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).mockRejectOnce(new Error("unknown column name"));
await expect(xfltr.select("obs", "bar", { mode: "all" })).rejects.toThrow( await expect(xfltr.select("obs", "bar", { mode: "all" })).rejects.toThrow(
"unknown column name" "unknown column name"
); );
@@ -404,7 +442,8 @@ describe("AnnoMatrixCrossfilter", () => {
type: "categorical", type: "categorical",
}); });
fetch.mockRejectOnce(new Error("unknown column name")); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).mockRejectOnce(new Error("unknown column name"));
await expect(xfltr.annoMatrix.fetch("obs", "foo")).rejects.toThrow( await expect(xfltr.annoMatrix.fetch("obs", "foo")).rejects.toThrow(
"unknown column name" "unknown column name"
); );
@@ -419,7 +458,8 @@ describe("AnnoMatrixCrossfilter", () => {
}); });
xfltr = xfltr.renameObsColumn("bar", "xyz"); xfltr = xfltr.renameObsColumn("bar", "xyz");
fetch.mockRejectOnce(new Error("unknown column name")); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).mockRejectOnce(new Error("unknown column name"));
await expect(xfltr.select("obs", "bar", { mode: "all" })).rejects.toThrow( await expect(xfltr.select("obs", "bar", { mode: "all" })).rejects.toThrow(
"unknown column name" "unknown column name"
); );
@@ -429,7 +469,8 @@ describe("AnnoMatrixCrossfilter", () => {
}); });
test("addObsAnnoCategory", async () => { test("addObsAnnoCategory", async () => {
let xfltr; // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let xfltr: any;
// catch unknown or readonly columns // catch unknown or readonly columns
expect(() => crossfilter.addObsAnnoCategory("louvain", "mumble")).toThrow( expect(() => crossfilter.addObsAnnoCategory("louvain", "mumble")).toThrow(
@@ -440,6 +481,7 @@ describe("AnnoMatrixCrossfilter", () => {
).toThrow("Unknown or readonly obs column"); ).toThrow("Unknown or readonly obs column");
// add a column and then add category to it // add a column and then add category to it
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ name: string; type: string; ca... Remove this comment to see the full error message
xfltr = await helperAddTestCol(crossfilter, "foo", { xfltr = await helperAddTestCol(crossfilter, "foo", {
name: "foo", name: "foo",
type: "categorical", type: "categorical",
@@ -458,6 +500,7 @@ describe("AnnoMatrixCrossfilter", () => {
); );
// now same, but ensure we have built an index before doing the operation // now same, but ensure we have built an index before doing the operation
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ name: string; type: string; ca... Remove this comment to see the full error message
xfltr = await helperAddTestCol(crossfilter, "bar", { xfltr = await helperAddTestCol(crossfilter, "bar", {
name: "bar", name: "bar",
type: "categorical", type: "categorical",
@@ -486,6 +529,7 @@ describe("AnnoMatrixCrossfilter", () => {
crossfilter.removeObsAnnoCategory("undefined-name", "mumble") crossfilter.removeObsAnnoCategory("undefined-name", "mumble")
).rejects.toThrow("Unknown or readonly obs column"); ).rejects.toThrow("Unknown or readonly obs column");
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ name: string; type: string; ca... Remove this comment to see the full error message
xfltr = await helperAddTestCol(crossfilter, "foo", { xfltr = await helperAddTestCol(crossfilter, "foo", {
name: "foo", name: "foo",
type: "categorical", type: "categorical",
@@ -495,8 +539,8 @@ describe("AnnoMatrixCrossfilter", () => {
expect( expect(
(await xfltr.annoMatrix.fetch("obs", "foo")) (await xfltr.annoMatrix.fetch("obs", "foo"))
.col("foo") .col("foo")
.asArray() .asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.every((v) => v === "unassigned") .every((v: any) => v === "unassigned")
).toBeTruthy(); ).toBeTruthy();
expect(xfltr.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({ expect(xfltr.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
name: "foo", name: "foo",
@@ -514,8 +558,8 @@ describe("AnnoMatrixCrossfilter", () => {
expect( expect(
(await xfltr1.annoMatrix.fetch("obs", "foo")) (await xfltr1.annoMatrix.fetch("obs", "foo"))
.col("foo") .col("foo")
.asArray() .asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.every((v) => v === "unassigned") .every((v: any) => v === "unassigned")
).toBeTruthy(); ).toBeTruthy();
expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({ expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
name: "foo", name: "foo",
@@ -537,8 +581,8 @@ describe("AnnoMatrixCrossfilter", () => {
expect( expect(
(await xfltr2.annoMatrix.fetch("obs", "foo")) (await xfltr2.annoMatrix.fetch("obs", "foo"))
.col("foo") .col("foo")
.asArray() .asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.every((v) => v === "red") .every((v: any) => v === "red")
).toBeTruthy(); ).toBeTruthy();
expect(xfltr2.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({ expect(xfltr2.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
name: "foo", name: "foo",
@@ -556,6 +600,7 @@ describe("AnnoMatrixCrossfilter", () => {
crossfilter.setObsColumnValues("undefined-name", [0], "mumble") crossfilter.setObsColumnValues("undefined-name", [0], "mumble")
).rejects.toThrow("Unknown or readonly obs column"); ).rejects.toThrow("Unknown or readonly obs column");
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ name: string; type: string; ca... Remove this comment to see the full error message
let xfltr = await helperAddTestCol(crossfilter, "foo", { let xfltr = await helperAddTestCol(crossfilter, "foo", {
name: "foo", name: "foo",
type: "categorical", type: "categorical",
@@ -572,8 +617,8 @@ describe("AnnoMatrixCrossfilter", () => {
expect( expect(
(await xfltr.annoMatrix.fetch("obs", "foo")) (await xfltr.annoMatrix.fetch("obs", "foo"))
.col("foo") .col("foo")
.asArray() .asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.every((v) => v === "unassigned") .every((v: any) => v === "unassigned")
).toBeTruthy(); ).toBeTruthy();
const xfltr1 = await xfltr.setObsColumnValues("foo", [0, 10], "purple"); const xfltr1 = await xfltr.setObsColumnValues("foo", [0, 10], "purple");
expect( expect(
@@ -581,7 +626,8 @@ describe("AnnoMatrixCrossfilter", () => {
.col("foo") .col("foo")
.asArray() .asArray()
.every( .every(
(v, i) => // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(v: any, i: any) =>
v === "unassigned" || (v === "purple" && (i === 0 || i === 10)) v === "unassigned" || (v === "purple" && (i === 0 || i === 10))
) )
).toBeTruthy(); ).toBeTruthy();
@@ -615,6 +661,7 @@ describe("AnnoMatrixCrossfilter", () => {
crossfilter.resetObsColumnValues("undefined-name", "red", "blue") crossfilter.resetObsColumnValues("undefined-name", "red", "blue")
).rejects.toThrow("Unknown or readonly obs column"); ).rejects.toThrow("Unknown or readonly obs column");
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ name: string; type: string; ca... Remove this comment to see the full error message
let xfltr = await helperAddTestCol(crossfilter, "foo", { let xfltr = await helperAddTestCol(crossfilter, "foo", {
name: "foo", name: "foo",
type: "categorical", type: "categorical",
@@ -638,22 +685,22 @@ describe("AnnoMatrixCrossfilter", () => {
expect( expect(
(await xfltr1.annoMatrix.fetch("obs", "foo")) (await xfltr1.annoMatrix.fetch("obs", "foo"))
.col("foo") .col("foo")
.asArray() .asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.filter((v) => v === "purple") .filter((v: any) => v === "purple")
).toHaveLength(2); ).toHaveLength(2);
xfltr1 = await xfltr1.resetObsColumnValues("foo", "purple", "magenta"); xfltr1 = await xfltr1.resetObsColumnValues("foo", "purple", "magenta");
expect( expect(
(await xfltr1.annoMatrix.fetch("obs", "foo")) (await xfltr1.annoMatrix.fetch("obs", "foo"))
.col("foo") .col("foo")
.asArray() .asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.filter((v) => v === "magenta") .filter((v: any) => v === "magenta")
).toHaveLength(2); ).toHaveLength(2);
expect( expect(
(await xfltr1.annoMatrix.fetch("obs", "foo")) (await xfltr1.annoMatrix.fetch("obs", "foo"))
.col("foo") .col("foo")
.asArray() .asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.filter((v) => v === "purple") .filter((v: any) => v === "purple")
).toHaveLength(0); ).toHaveLength(0);
expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({ expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
name: "foo", name: "foo",
@@ -673,12 +720,16 @@ describe("AnnoMatrixCrossfilter", () => {
describe("edge cases", () => { describe("edge cases", () => {
test("transition from empty annoMatrix", async () => { test("transition from empty annoMatrix", async () => {
// select before fetch needs to work // select before fetch needs to work
fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain])); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(
serverMocks.dataframeResponse(["louvain"], [obsLouvain])
);
const xfltr = await crossfilter.select("obs", "louvain", { const xfltr = await crossfilter.select("obs", "louvain", {
mode: "exact", mode: "exact",
values: "B cells", values: "B cells",
}); });
expect(fetch.mock.calls).toHaveLength(1); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
expect((fetch as any).mock.calls).toHaveLength(1);
expect(xfltr.obsCrossfilter.hasDimension("obs/louvain")).toBeTruthy(); expect(xfltr.obsCrossfilter.hasDimension("obs/louvain")).toBeTruthy();
expect(xfltr.obsCrossfilter.all()).toBe(xfltr.annoMatrix._cache.obs); expect(xfltr.obsCrossfilter.all()).toBe(xfltr.annoMatrix._cache.obs);
expect(xfltr.countSelected()).toEqual( expect(xfltr.countSelected()).toEqual(
@@ -1,6 +1,7 @@
export const baseDataURL = "https://a.fake.url/api/v0.2"; export const baseDataURL = "https://a.fake.url/api/v0.2";
window.CELLXGENE = { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(window as any).CELLXGENE = {
API: { API: {
prefix: baseDataURL, prefix: baseDataURL,
version: "v0.2/", version: "v0.2/",
@@ -1,211 +0,0 @@
import { schema } from "./schema";
import { Dataframe, KeyIndex } from "../../../../src/util/dataframe";
import { encodeMatrixFBS } from "../../../../src/util/stateManager/matrix";
const indexedSchema = {
obsByName: Object.fromEntries(
schema.schema.annotations.obs.columns.map((v) => [v.name, v]) ?? []
),
varByName: Object.fromEntries(
schema.schema.annotations.var.columns.map((v) => [v.name, v]) ?? []
),
embByName: Object.fromEntries(
schema.schema.layout.obs.map((v) => [v.name, v]) ?? []
),
};
function makeMockColumn(s, length) {
const { type } = s;
switch (type) {
case "int32":
return new Int32Array(length).fill(Math.floor(99 * Math.random()));
case "string":
return new Array(length).fill("test");
case "float32":
return new Float32Array(length).fill(99 * Math.random());
case "boolean":
return new Array(length).fill(false);
case "categorical":
return new Array(length).fill(s.categories[0]);
default:
throw new Error("unkonwn type");
}
}
function getEncodedDataframe(colNames, length, colSchemas) {
const colIndex = new KeyIndex(colNames);
const columns = colSchemas.map((s) => makeMockColumn(s, length));
const df = new Dataframe([length, colNames.length], columns, null, colIndex);
const body = encodeMatrixFBS(df);
return body;
}
export function dataframeResponse(colNames, columns) {
const colIndex = new KeyIndex(colNames);
const df = new Dataframe(
[columns[0].length, colNames.length],
columns,
null,
colIndex
);
const body = encodeMatrixFBS(df);
const headers = new Headers({
"Content-Type": "application/octet-stream",
});
return () => Promise.resolve({ body, init: { status: 200, headers } });
}
function annotationObsResponse(request) {
const url = new URL(request.url);
const params = Array.from(url.searchParams.entries());
const names = params
.filter(([k]) => k === "annotation-name")
.map(([, v]) => v);
if (!names.every((n) => indexedSchema.obsByName[n])) {
return Promise.reject(new Error("bad obs annotation name in URL"));
}
const colSchemas = names.map((n) => indexedSchema.obsByName[n]);
const body = getEncodedDataframe(
names,
schema.schema.dataframe.nObs,
colSchemas
);
const headers = new Headers({
"Content-Type": "application/octet-stream",
});
return Promise.resolve({
body,
init: { status: 200, headers },
});
}
function annotationVarResponse(request) {
const url = new URL(request.url);
const params = Array.from(url.searchParams.entries());
const names = params
.filter(([k]) => k === "annotation-name")
.map(([, v]) => v);
if (!names.every((n) => indexedSchema.varByName[n])) {
return Promise.reject(new Error("bad var annotation name in URL"));
}
const colSchemas = names.map((n) => indexedSchema.varByName[n]);
const body = getEncodedDataframe(
names,
schema.schema.dataframe.nVar,
colSchemas
);
const headers = new Headers({
"Content-Type": "application/octet-stream",
});
return Promise.resolve({
body,
init: { status: 200, headers },
});
}
function layoutObsResponse(request) {
const url = new URL(request.url);
const params = Array.from(url.searchParams.entries());
const names = params.filter(([k]) => k === "layout-name").map(([, v]) => v);
if (!names.every((n) => indexedSchema.embByName[n])) {
return Promise.reject(new Error("bad layout name in URL"));
}
const dims = names.map((n) => indexedSchema.embByName[n].dims).flat();
const colSchemas = names
.map((n) => [indexedSchema.embByName[n], indexedSchema.embByName[n]])
.flat();
const body = getEncodedDataframe(
dims,
schema.schema.dataframe.nObs,
colSchemas
);
const headers = new Headers({
"Content-Type": "application/octet-stream",
});
return Promise.resolve({
body,
init: { status: 200, headers },
});
}
function dataVarResponse(request) {
const url = new URL(request.url);
const params = Array.from(url.searchParams.entries());
const colNames = params.map((v) => `${v[0]}/${v[1]}`);
const colSchemas = colNames.map(() => schema.schema.dataframe);
const body = getEncodedDataframe(
colNames,
schema.schema.dataframe.nObs,
colSchemas
);
const headers = new Headers({
"Content-Type": "application/octet-stream",
});
return Promise.resolve({
body,
init: { status: 200, headers },
});
}
export function responder(request) {
const url = new URL(request.url);
const { pathname } = url;
if (pathname.endsWith("/annotations/obs")) {
return annotationObsResponse(request);
}
if (pathname.endsWith("/annotations/var")) {
return annotationVarResponse(request);
}
if (pathname.endsWith("/layout/obs")) {
return layoutObsResponse(request);
}
if (pathname.endsWith("/data/var")) {
return dataVarResponse(request);
}
return Promise.reject(new Error("bad URL"));
}
export function withExpected(expectedURL, expectedParams) {
/*
Do some additional error checking
*/
return (request) => {
// if URL is bogus, reject the promise
const url = new URL(request.url);
if (!url.pathname.endsWith(expectedURL)) {
return Promise.reject(new Error("Unexpected URL!"));
}
const params = Array.from(url.searchParams.entries()).sort(
(a, b) => a[0] < b[0]
);
expectedParams = expectedParams.slice().sort((a, b) => a[0] < b[0]);
if (
params.length !== expectedParams.length ||
!params.every(
(p, i) => p[0] === expectedParams[i][0] && p[1] === expectedParams[i][1]
)
) {
return Promise.reject(new Error("unexpected name requested in URL"));
}
return responder(request);
};
}
export function annotationsObs(names) {
return withExpected(
"/annotations/obs",
names.map((name) => ["annotation-name", name])
);
}
@@ -0,0 +1,251 @@
import { schema } from "./schema";
import { Dataframe, KeyIndex } from "../../../../src/util/dataframe";
import { encodeMatrixFBS } from "../../../../src/util/stateManager/matrix";
const indexedSchema = {
obsByName: Object.fromEntries(
schema.schema.annotations.obs.columns.map((v) => [v.name, v]) ?? []
),
varByName: Object.fromEntries(
schema.schema.annotations.var.columns.map((v) => [v.name, v]) ?? []
),
embByName: Object.fromEntries(
schema.schema.layout.obs.map((v) => [v.name, v]) ?? []
),
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function makeMockColumn(s: any, length: any) {
const { type } = s;
switch (type) {
case "int32":
return new Int32Array(length).fill(Math.floor(99 * Math.random()));
case "string":
return new Array(length).fill("test");
case "float32":
return new Float32Array(length).fill(99 * Math.random());
case "boolean":
return new Array(length).fill(false);
case "categorical":
return new Array(length).fill(s.categories[0]);
default:
throw new Error("unkonwn type");
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function getEncodedDataframe(colNames: any, length: any, colSchemas: any) {
const colIndex = new KeyIndex(colNames);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const columns = colSchemas.map((s: any) => makeMockColumn(s, length));
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
const df = new Dataframe([length, colNames.length], columns, null, colIndex);
const body = encodeMatrixFBS(df);
return body;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export function dataframeResponse(colNames: any, columns: any) {
const colIndex = new KeyIndex(colNames);
const df = new Dataframe(
[columns[0].length, colNames.length],
columns,
null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
colIndex
);
const body = encodeMatrixFBS(df);
const headers = new Headers({
"Content-Type": "application/octet-stream",
});
return () => Promise.resolve({ body, init: { status: 200, headers } });
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function annotationObsResponse(request: any) {
const url = new URL(request.url);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const params = Array.from((url.searchParams as any).entries());
const names = params
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
.filter(([k]) => k === "annotation-name")
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '([, v]: [any, any]) => any' is n... Remove this comment to see the full error message
.map(([, v]) => v);
// @ts-expect-error ts-migrate(2538) FIXME: Type 'unknown' cannot be used as an index type.
if (!names.every((n) => indexedSchema.obsByName[n])) {
return Promise.reject(new Error("bad obs annotation name in URL"));
}
// @ts-expect-error ts-migrate(2538) FIXME: Type 'unknown' cannot be used as an index type.
const colSchemas = names.map((n) => indexedSchema.obsByName[n]);
const body = getEncodedDataframe(
names,
schema.schema.dataframe.nObs,
colSchemas
);
const headers = new Headers({
"Content-Type": "application/octet-stream",
});
return Promise.resolve({
body,
init: { status: 200, headers },
});
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function annotationVarResponse(request: any) {
const url = new URL(request.url);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const params = Array.from((url.searchParams as any).entries());
const names = params
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
.filter(([k]) => k === "annotation-name")
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '([, v]: [any, any]) => any' is n... Remove this comment to see the full error message
.map(([, v]) => v);
// @ts-expect-error ts-migrate(2538) FIXME: Type 'unknown' cannot be used as an index type.
if (!names.every((n) => indexedSchema.varByName[n])) {
return Promise.reject(new Error("bad var annotation name in URL"));
}
// @ts-expect-error ts-migrate(2538) FIXME: Type 'unknown' cannot be used as an index type.
const colSchemas = names.map((n) => indexedSchema.varByName[n]);
const body = getEncodedDataframe(
names,
schema.schema.dataframe.nVar,
colSchemas
);
const headers = new Headers({
"Content-Type": "application/octet-stream",
});
return Promise.resolve({
body,
init: { status: 200, headers },
});
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function layoutObsResponse(request: any) {
const url = new URL(request.url);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const params = Array.from((url.searchParams as any).entries());
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
const names = params.filter(([k]) => k === "layout-name").map(([, v]) => v);
// @ts-expect-error ts-migrate(2538) FIXME: Type 'unknown' cannot be used as an index type.
if (!names.every((n) => indexedSchema.embByName[n])) {
return Promise.reject(new Error("bad layout name in URL"));
}
// @ts-expect-error ts-migrate(2538) FIXME: Type 'unknown' cannot be used as an index type.
const dims = names.map((n) => indexedSchema.embByName[n].dims).flat();
const colSchemas = names
// @ts-expect-error ts-migrate(2538) FIXME: Type 'unknown' cannot be used as an index type.
.map((n) => [indexedSchema.embByName[n], indexedSchema.embByName[n]])
.flat();
const body = getEncodedDataframe(
dims,
schema.schema.dataframe.nObs,
colSchemas
);
const headers = new Headers({
"Content-Type": "application/octet-stream",
});
return Promise.resolve({
body,
init: { status: 200, headers },
});
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function dataVarResponse(request: any) {
const url = new URL(request.url);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const params = Array.from((url.searchParams as any).entries());
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const colNames = params.map((v) => `${(v as any)[0]}/${(v as any)[1]}`);
const colSchemas = colNames.map(() => schema.schema.dataframe);
const body = getEncodedDataframe(
colNames,
schema.schema.dataframe.nObs,
colSchemas
);
const headers = new Headers({
"Content-Type": "application/octet-stream",
});
return Promise.resolve({
body,
init: { status: 200, headers },
});
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export function responder(request: any) {
const url = new URL(request.url);
const { pathname } = url;
if (pathname.endsWith("/annotations/obs")) {
return annotationObsResponse(request);
}
if (pathname.endsWith("/annotations/var")) {
return annotationVarResponse(request);
}
if (pathname.endsWith("/layout/obs")) {
return layoutObsResponse(request);
}
if (pathname.endsWith("/data/var")) {
return dataVarResponse(request);
}
return Promise.reject(new Error("bad URL"));
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export function withExpected(expectedURL: any, expectedParams: any) {
/*
Do some additional error checking
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
return (request: any) => {
// if URL is bogus, reject the promise
const url = new URL(request.url);
if (!url.pathname.endsWith(expectedURL)) {
return Promise.reject(new Error("Unexpected URL!"));
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const params = Array.from((url.searchParams as any).entries()).sort(
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '(a: unknown, b: unknown) => bool... Remove this comment to see the full error message
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(a, b) => (a as any)[0] < (b as any)[0]
);
expectedParams = expectedParams
.slice() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.sort((a: any, b: any) => a[0] < b[0]);
if (
params.length !== expectedParams.length ||
!params.every(
(p, i) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(p as any)[0] === expectedParams[i][0] && // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(p as any)[1] === expectedParams[i][1]
)
) {
return Promise.reject(new Error("unexpected name requested in URL"));
}
return responder(request);
};
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export function annotationsObs(names: any) {
return withExpected(
"/annotations/obs",
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
names.map((name: any) => ["annotation-name", name])
);
}
@@ -218,10 +218,18 @@ describe("whereCache", () => {
}, },
}) })
); );
expect(wc.where.field.queryField.has("queryColumn")).toEqual(true); // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
expect(wc.where.field.queryField.get("queryColumn")).toBeInstanceOf(Map); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
expect((wc.where as any).field.queryField.has("queryColumn")).toEqual(true);
expect( expect(
wc.where.field.queryField.get("queryColumn").has("queryValue") // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(wc.where as any).field.queryField.get("queryColumn")
).toBeInstanceOf(Map);
expect(
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(wc.where as any).field.queryField.get("queryColumn").has("queryValue")
).toEqual(true); ).toEqual(true);
expect(_whereCacheGet(wc, schema, "field", query)).toEqual([0, 1, 2]); expect(_whereCacheGet(wc, schema, "field", query)).toEqual([0, 1, 2]);
}); });
@@ -5,19 +5,22 @@ import quantile from "../../src/util/quantile";
import { matrixFBSToDataframe } from "../../src/util/stateManager/matrix"; import { matrixFBSToDataframe } from "../../src/util/stateManager/matrix";
import * as REST from "./stateManager/sampleResponses"; import * as REST from "./stateManager/sampleResponses";
import { indexEntireSchema } from "../../src/util/stateManager/schemaHelpers"; import { indexEntireSchema } from "../../src/util/stateManager/schemaHelpers";
import { _normalizeCategoricalSchema } from "../../src/annoMatrix/schema"; import { normalizeWritableCategoricalSchema } from "../../src/annoMatrix/normalize";
describe("centroid", () => { describe("centroid", () => {
let schema; // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let obsAnnotations; let schema: any;
let obsLayout; // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let obsAnnotations: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let obsLayout: any;
beforeAll(() => { beforeAll(() => {
schema = indexEntireSchema(cloneDeep(REST.schema.schema)); schema = indexEntireSchema(cloneDeep(REST.schema.schema));
obsAnnotations = matrixFBSToDataframe(REST.annotationsObs); obsAnnotations = matrixFBSToDataframe(REST.annotationsObs);
obsLayout = matrixFBSToDataframe(REST.layoutObs); obsLayout = matrixFBSToDataframe(REST.layoutObs);
_normalizeCategoricalSchema( normalizeWritableCategoricalSchema(
schema.annotations.obsByName.field3, schema.annotations.obsByName.field3,
obsAnnotations.col("field3") obsAnnotations.col("field3")
); );
@@ -44,7 +47,8 @@ describe("centroid", () => {
quantile([0.5], obsLayout.col("umap_1").asArray())[0], quantile([0.5], obsLayout.col("umap_1").asArray())[0],
]; ];
centroidResult.forEach((coordinate) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
centroidResult.forEach((coordinate: any) => {
expect(coordinate).toEqual(expectedResult); expect(coordinate).toEqual(expectedResult);
}); });
}); });
@@ -68,7 +72,8 @@ describe("centroid", () => {
quantile([0.5], obsLayout.col("umap_1").asArray())[0], quantile([0.5], obsLayout.col("umap_1").asArray())[0],
]; ];
centroidResult.forEach((coordinate) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
centroidResult.forEach((coordinate: any) => {
expect(coordinate).toEqual(expectedResult); expect(coordinate).toEqual(expectedResult);
}); });
}); });
@@ -29,6 +29,7 @@ describe("dataframe constructor", () => {
const df = new Dataframe.Dataframe( const df = new Dataframe.Dataframe(
[3, 2], [3, 2],
[new Int32Array([0, 1, 2]), new Int32Array([3, 4, 5])], [new Int32Array([0, 1, 2]), new Int32Array([3, 4, 5])],
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'DenseInt32Index' is not assignab... Remove this comment to see the full error message
new Dataframe.DenseInt32Index([2, 1, 0]), new Dataframe.DenseInt32Index([2, 1, 0]),
new Dataframe.KeyIndex(["A", "B"]) new Dataframe.KeyIndex(["A", "B"])
); );
@@ -55,6 +56,7 @@ describe("simple data access", () => {
new Float64Array([0.0, Number.NaN, Number.POSITIVE_INFINITY, 3.14159]), new Float64Array([0.0, Number.NaN, Number.POSITIVE_INFINITY, 3.14159]),
["red", "blue", "green", "nan"], ["red", "blue", "green", "nan"],
], ],
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'DenseInt32Index' is not assignab... Remove this comment to see the full error message
new Dataframe.DenseInt32Index([3, 2, 1, 0]), new Dataframe.DenseInt32Index([3, 2, 1, 0]),
new Dataframe.KeyIndex(["numbers", "colors"]) new Dataframe.KeyIndex(["numbers", "colors"])
); );
@@ -139,10 +141,12 @@ describe("dataframe subsetting", () => {
["red", "green", "blue"], ["red", "green", "blue"],
], ],
null, // identity index null, // identity index
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex(["int32", "string", "float32", "colors"]) new Dataframe.KeyIndex(["int32", "string", "float32", "colors"])
); );
test("all rows, one column", () => { test("all rows, one column", () => {
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
const dfA = sourceDf.subset(null, ["colors"]); const dfA = sourceDf.subset(null, ["colors"]);
expect(dfA).toBeDefined(); expect(dfA).toBeDefined();
expect(dfA.dims).toEqual([3, 1]); expect(dfA.dims).toEqual([3, 1]);
@@ -158,6 +162,7 @@ describe("dataframe subsetting", () => {
}); });
test("all rows, two columns", () => { test("all rows, two columns", () => {
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
const dfB = sourceDf.subset(null, ["float32", "colors"]); const dfB = sourceDf.subset(null, ["float32", "colors"]);
expect(dfB).toBeDefined(); expect(dfB).toBeDefined();
expect(dfB.dims).toEqual([3, 2]); expect(dfB.dims).toEqual([3, 2]);
@@ -227,6 +232,7 @@ describe("dataframe subsetting", () => {
}); });
test("two rows, two colums", () => { test("two rows, two colums", () => {
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
const dfF = sourceDf.subset([0, 2], ["int32", "float32"]); const dfF = sourceDf.subset([0, 2], ["int32", "float32"]);
expect(dfF).toBeDefined(); expect(dfF).toBeDefined();
expect(dfF.dims).toEqual([2, 2]); expect(dfF.dims).toEqual([2, 2]);
@@ -236,6 +242,7 @@ describe("dataframe subsetting", () => {
expect(dfF.colIndex.labels()).toEqual(["int32", "float32"]); expect(dfF.colIndex.labels()).toEqual(["int32", "float32"]);
// reverse the row and column order // reverse the row and column order
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
const dfFr = sourceDf.subset([2, 0], ["float32", "int32"]); const dfFr = sourceDf.subset([2, 0], ["float32", "int32"]);
expect(dfFr).toBeDefined(); expect(dfFr).toBeDefined();
expect(dfFr.dims).toEqual([2, 2]); expect(dfFr.dims).toEqual([2, 2]);
@@ -248,6 +255,7 @@ describe("dataframe subsetting", () => {
test("withRowIndex", () => { test("withRowIndex", () => {
const df = sourceDf.subset( const df = sourceDf.subset(
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
["int32", "float32"], ["int32", "float32"],
new Dataframe.DenseInt32Index([3, 2, 1]) new Dataframe.DenseInt32Index([3, 2, 1])
); );
@@ -258,12 +266,15 @@ describe("dataframe subsetting", () => {
test("withRowIndex error checks", () => { test("withRowIndex error checks", () => {
expect(() => expect(() =>
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
sourceDf.subset(null, ["red"], new Dataframe.IdentityInt32Index(1)) sourceDf.subset(null, ["red"], new Dataframe.IdentityInt32Index(1))
).toThrow(RangeError); ).toThrow(RangeError);
expect(() => expect(() =>
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
sourceDf.subset(null, ["red"], new Dataframe.DenseInt32Index([0, 1])) sourceDf.subset(null, ["red"], new Dataframe.DenseInt32Index([0, 1]))
).toThrow(RangeError); ).toThrow(RangeError);
expect(() => expect(() =>
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
sourceDf.subset(null, ["red"], new Dataframe.KeyIndex([0, 1, 2, 3])) sourceDf.subset(null, ["red"], new Dataframe.KeyIndex([0, 1, 2, 3]))
).toThrow(RangeError); ).toThrow(RangeError);
}); });
@@ -278,12 +289,14 @@ describe("dataframe subsetting", () => {
new Float32Array([4.4, 5.5, 6.6]), new Float32Array([4.4, 5.5, 6.6]),
["red", "green", "blue"], ["red", "green", "blue"],
], ],
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'DenseInt32Index' is not assignab... Remove this comment to see the full error message
new Dataframe.DenseInt32Index([2, 4, 6]), new Dataframe.DenseInt32Index([2, 4, 6]),
new Dataframe.KeyIndex(["int32", "string", "float32", "colors"]) new Dataframe.KeyIndex(["int32", "string", "float32", "colors"])
); );
const dfA = sourceDf.isubsetMask( const dfA = sourceDf.isubsetMask(
new Uint8Array([0, 1, 1]), new Uint8Array([0, 1, 1]),
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'Uint8Array' is not assignable to... Remove this comment to see the full error message
new Uint8Array([1, 0, 0, 1]) new Uint8Array([1, 0, 0, 1])
); );
expect(dfA.dims).toEqual([2, 2]); expect(dfA.dims).toEqual([2, 2]);
@@ -303,6 +316,7 @@ describe("dataframe subsetting", () => {
["red", "green", "blue"], ["red", "green", "blue"],
], ],
null, // identity index null, // identity index
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex(["int32", "string", "float32", "colors"]) new Dataframe.KeyIndex(["int32", "string", "float32", "colors"])
); );
@@ -316,6 +330,7 @@ describe("dataframe subsetting", () => {
}); });
test("all rows, two cols", () => { test("all rows, two cols", () => {
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'number[]' is not assignable to p... Remove this comment to see the full error message
const dfA = sourceDf.isubset(null, [1, 2]); const dfA = sourceDf.isubset(null, [1, 2]);
expect(dfA.dims).toEqual([3, 2]); expect(dfA.dims).toEqual([3, 2]);
expect(dfA.icol(0).asArray()).toEqual(["A", "B", "C"]); expect(dfA.icol(0).asArray()).toEqual(["A", "B", "C"]);
@@ -361,6 +376,7 @@ describe("dataframe factories", () => {
const dfA = new Dataframe.Dataframe( const dfA = new Dataframe.Dataframe(
[3, 2], [3, 2],
[new Int32Array([0, 1, 2]), new Int32Array([3, 4, 5])], [new Int32Array([0, 1, 2]), new Int32Array([3, 4, 5])],
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'DenseInt32Index' is not assignab... Remove this comment to see the full error message
new Dataframe.DenseInt32Index([2, 1, 0]), new Dataframe.DenseInt32Index([2, 1, 0]),
new Dataframe.KeyIndex(["A", "B"]) new Dataframe.KeyIndex(["A", "B"])
); );
@@ -385,6 +401,7 @@ describe("dataframe factories", () => {
[true, false], [true, false],
], ],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex(["colors", "bools"]) new Dataframe.KeyIndex(["colors", "bools"])
); );
const dfA = df.withCol("numbers", [1, 0]); const dfA = df.withCol("numbers", [1, 0]);
@@ -408,6 +425,7 @@ describe("dataframe factories", () => {
[true, false], [true, false],
], ],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'DenseInt32Index' is not assignab... Remove this comment to see the full error message
new Dataframe.DenseInt32Index([74, 75]) new Dataframe.DenseInt32Index([74, 75])
); );
const dfA = df.withCol(72, [1, 0]); const dfA = df.withCol(72, [1, 0]);
@@ -433,6 +451,7 @@ describe("dataframe factories", () => {
[true, false], [true, false],
], ],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'DenseInt32Index' is not assignab... Remove this comment to see the full error message
new Dataframe.DenseInt32Index([74, 75]) new Dataframe.DenseInt32Index([74, 75])
); );
const dfA = df.withCol(999, [1, 0]); const dfA = df.withCol(999, [1, 0]);
@@ -541,6 +560,7 @@ describe("dataframe factories", () => {
[1, 0], [1, 0],
], ],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex(["colors", "bools", "numbers"]) new Dataframe.KeyIndex(["colors", "bools", "numbers"])
); );
@@ -549,11 +569,14 @@ describe("dataframe factories", () => {
[3, 1], [3, 1],
[["red", "blue", "green"]], [["red", "blue", "green"]],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex(["colorsA"]) new Dataframe.KeyIndex(["colorsA"])
); );
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
expect(() => dfA.withColsFrom(dfB)).toThrow(RangeError); expect(() => dfA.withColsFrom(dfB)).toThrow(RangeError);
/* duplicate labels should throw an error */ /* duplicate labels should throw an error */
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
expect(() => dfA.withColsFrom(dfA)).toThrow(Error); expect(() => dfA.withColsFrom(dfA)).toThrow(Error);
}); });
@@ -564,15 +587,18 @@ describe("dataframe factories", () => {
[2, 1], [2, 1],
[["red", "blue"]], [["red", "blue"]],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex(["colors"]) new Dataframe.KeyIndex(["colors"])
); );
const dfB = new Dataframe.Dataframe( const dfB = new Dataframe.Dataframe(
[2, 1], [2, 1],
[[true, false]], [[true, false]],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex(["bools"]) new Dataframe.KeyIndex(["bools"])
); );
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
const dfLikeA = dfEmpty.withColsFrom(dfA); const dfLikeA = dfEmpty.withColsFrom(dfA);
expect(dfLikeA).toBeDefined(); expect(dfLikeA).toBeDefined();
expect(dfLikeA.dims).toEqual(dfA.dims); expect(dfLikeA.dims).toEqual(dfA.dims);
@@ -581,6 +607,7 @@ describe("dataframe factories", () => {
expect(dfLikeA.rowIndex.labels()).toEqual(dfA.rowIndex.labels()); expect(dfLikeA.rowIndex.labels()).toEqual(dfA.rowIndex.labels());
expect(dfLikeA.icol(0).asArray()).toEqual(dfA.icol(0).asArray()); expect(dfLikeA.icol(0).asArray()).toEqual(dfA.icol(0).asArray());
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
const dfAlsoLikeA = dfA.withColsFrom(dfEmpty); const dfAlsoLikeA = dfA.withColsFrom(dfEmpty);
expect(dfAlsoLikeA).toBeDefined(); expect(dfAlsoLikeA).toBeDefined();
expect(dfAlsoLikeA.dims).toEqual(dfA.dims); expect(dfAlsoLikeA.dims).toEqual(dfA.dims);
@@ -589,6 +616,7 @@ describe("dataframe factories", () => {
expect(dfAlsoLikeA.rowIndex.labels()).toEqual(dfA.rowIndex.labels()); expect(dfAlsoLikeA.rowIndex.labels()).toEqual(dfA.rowIndex.labels());
expect(dfAlsoLikeA.icol(0).asArray()).toEqual(dfA.icol(0).asArray()); expect(dfAlsoLikeA.icol(0).asArray()).toEqual(dfA.icol(0).asArray());
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
const dfC = dfA.withColsFrom(dfB); const dfC = dfA.withColsFrom(dfB);
expect(dfC).toBeDefined(); expect(dfC).toBeDefined();
expect(dfC.dims).toEqual([2, 2]); expect(dfC.dims).toEqual([2, 2]);
@@ -605,6 +633,7 @@ describe("dataframe factories", () => {
[2, 1], [2, 1],
[["red", "blue"]], [["red", "blue"]],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex(["colors"]) new Dataframe.KeyIndex(["colors"])
); );
const dfB = new Dataframe.Dataframe( const dfB = new Dataframe.Dataframe(
@@ -615,6 +644,7 @@ describe("dataframe factories", () => {
[1, 0], [1, 0],
], ],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex(["colors", "bools", "numbers"]) new Dataframe.KeyIndex(["colors", "bools", "numbers"])
); );
@@ -647,6 +677,7 @@ describe("dataframe factories", () => {
[2, 1], [2, 1],
[["red", "blue"]], [["red", "blue"]],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex(["colors"]) new Dataframe.KeyIndex(["colors"])
); );
const dfB = new Dataframe.Dataframe( const dfB = new Dataframe.Dataframe(
@@ -657,6 +688,7 @@ describe("dataframe factories", () => {
[1, 0], [1, 0],
], ],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex(["colors", "bools", "numbers"]) new Dataframe.KeyIndex(["colors", "bools", "numbers"])
); );
@@ -680,6 +712,7 @@ describe("dataframe factories", () => {
[1, 0], [1, 0],
], ],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex(["colors", "bools", "numbers"]) new Dataframe.KeyIndex(["colors", "bools", "numbers"])
); );
const dfA = df.dropCol("colors"); const dfA = df.dropCol("colors");
@@ -751,6 +784,7 @@ describe("dataframe factories", () => {
[1, 0], [1, 0],
], ],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'DenseInt32Index' is not assignab... Remove this comment to see the full error message
new Dataframe.DenseInt32Index([102, 101, 100]) new Dataframe.DenseInt32Index([102, 101, 100])
); );
const dfA = df.dropCol(101); const dfA = df.dropCol(101);
@@ -777,7 +811,8 @@ describe("dataframe factories", () => {
new Float64Array(3).fill(1.1), new Float64Array(3).fill(1.1),
] ]
); );
const dfB = dfA.mapColumns((col, idx) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const dfB = dfA.mapColumns((col: any, idx: any) => {
expect(dfA.icol(idx).asArray()).toBe(col); expect(dfA.icol(idx).asArray()).toBe(col);
return col; return col;
}); });
@@ -793,9 +828,7 @@ describe("dataframe factories", () => {
[3, 3], [3, 3],
[new Array(3).fill(0), new Array(3).fill(0), new Array(3).fill(0)] [new Array(3).fill(0), new Array(3).fill(0), new Array(3).fill(0)]
); );
const dfB = dfA.mapColumns(() => { const dfB = dfA.mapColumns(() => new Array(3).fill(1));
return new Array(3).fill(1);
});
expect(dfA).not.toBe(dfB); expect(dfA).not.toBe(dfB);
expect(dfB.iat(0, 0)).toEqual(1); expect(dfB.iat(0, 0)).toEqual(1);
expect(dfB.iat(0, 1)).toEqual(1); expect(dfB.iat(0, 1)).toEqual(1);
@@ -822,6 +855,7 @@ describe("dataframe factories", () => {
[1, 0], [1, 0],
], ],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex(["A", "B"]) new Dataframe.KeyIndex(["A", "B"])
); );
const dfB = dfA.renameCol("B", "C"); const dfB = dfA.renameCol("B", "C");
@@ -834,7 +868,8 @@ describe("dataframe factories", () => {
}); });
describe("dataframe col", () => { describe("dataframe col", () => {
let df = null; // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let df: any = null;
beforeEach(() => { beforeEach(() => {
df = new Dataframe.Dataframe( df = new Dataframe.Dataframe(
[2, 2], [2, 2],
@@ -843,6 +878,7 @@ describe("dataframe col", () => {
[1, 0], [1, 0],
], ],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex(["A", "B"]) new Dataframe.KeyIndex(["A", "B"])
); );
}); });
@@ -1195,6 +1231,7 @@ describe("label indexing", () => {
test("create", () => { test("create", () => {
expect(Dataframe.isLabelIndex(idx)).toBeTruthy(); expect(Dataframe.isLabelIndex(idx)).toBeTruthy();
expect(() => new Dataframe.KeyIndex(["dup", "dup"])).toThrow(Error); expect(() => new Dataframe.KeyIndex(["dup", "dup"])).toThrow(Error);
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
expect(new Dataframe.KeyIndex().size()).toEqual(0); expect(new Dataframe.KeyIndex().size()).toEqual(0);
}); });
@@ -1367,6 +1404,7 @@ describe("corner cases", () => {
[1, 0], [1, 0],
], ],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex(["A", "B"]) new Dataframe.KeyIndex(["A", "B"])
); );
@@ -6,6 +6,7 @@ describe("Dataframe column histogram", () => {
[3, 3], [3, 3],
[["n1", "n2", "n3"], ["c1", "c2", "c3"], new Int32Array([0, 1, 2])], [["n1", "n2", "n3"], ["c1", "c2", "c3"], new Int32Array([0, 1, 2])],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex(["name", "cat", "value"]) new Dataframe.KeyIndex(["name", "cat", "value"])
); );
@@ -26,6 +27,7 @@ describe("Dataframe column histogram", () => {
[3, 3], [3, 3],
[["n1", "n2", "n3"], ["c1", "c2", "c3"], new Int32Array([0, 1, 2])], [["n1", "n2", "n3"], ["c1", "c2", "c3"], new Int32Array([0, 1, 2])],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex(["name", "cat", "value"]) new Dataframe.KeyIndex(["name", "cat", "value"])
); );
@@ -48,6 +50,7 @@ describe("Dataframe column histogram", () => {
[3, 3], [3, 3],
[["n1", "n2", "n3"], ["c1", "c2", "c3"], new Int32Array([0, 1, 2])], [["n1", "n2", "n3"], ["c1", "c2", "c3"], new Int32Array([0, 1, 2])],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex(["name", "cat", "value"]) new Dataframe.KeyIndex(["name", "cat", "value"])
); );
@@ -68,6 +71,7 @@ describe("Dataframe column histogram", () => {
[3, 3], [3, 3],
[["n1", "n2", "n3"], ["c1", "c2", "c3"], new Int32Array([0, 1, 2])], [["n1", "n2", "n3"], ["c1", "c2", "c3"], new Int32Array([0, 1, 2])],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex(["name", "cat", "value"]) new Dataframe.KeyIndex(["name", "cat", "value"])
); );
@@ -1,6 +1,7 @@
import * as Dataframe from "../../../src/util/dataframe"; import * as Dataframe from "../../../src/util/dataframe";
function float32Conversion(f) { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function float32Conversion(f: any) {
return new Float32Array([f])[0]; return new Float32Array([f])[0];
} }
@@ -30,6 +31,7 @@ describe("Dataframe column summary", () => {
[1], [1],
], ],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex([ new Dataframe.KeyIndex([
"name", "name",
"nameString", "nameString",
@@ -106,6 +108,7 @@ describe("Dataframe column summary", () => {
[1, false, "0"], [1, false, "0"],
], ],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex([ new Dataframe.KeyIndex([
"name", "name",
"nameString", "nameString",
@@ -174,6 +177,7 @@ describe("Dataframe column summary", () => {
expect.objectContaining({ expect.objectContaining({
categorical: true, categorical: true,
categories: expect.arrayContaining([1, false, "0"]), categories: expect.arrayContaining([1, false, "0"]),
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
categoryCounts: new Map([ categoryCounts: new Map([
[1, 1], [1, 1],
[false, 1], [false, 1],
@@ -201,6 +205,7 @@ describe("Dataframe column summary", () => {
[1, false, "0", "0"], [1, false, "0", "0"],
], ],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex([ new Dataframe.KeyIndex([
"name", "name",
"nameString", "nameString",
@@ -269,6 +274,7 @@ describe("Dataframe column summary", () => {
expect.objectContaining({ expect.objectContaining({
categorical: true, categorical: true,
categories: expect.arrayContaining([1, false, "0"]), categories: expect.arrayContaining([1, false, "0"]),
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
categoryCounts: new Map([ categoryCounts: new Map([
[1, 1], [1, 1],
[false, 1], [false, 1],
@@ -1,7 +1,8 @@
import PromiseLimit from "../../src/util/promiseLimit"; import PromiseLimit from "../../src/util/promiseLimit";
import { range } from "../../src/util/range"; import { range } from "../../src/util/range";
const delay = (t) => new Promise((resolve) => setTimeout(resolve, t)); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const delay = (t: any) => new Promise((resolve) => setTimeout(resolve, t));
describe("PromiseLimit", () => { describe("PromiseLimit", () => {
test("simple evaluation, concurrency 1", async () => { test("simple evaluation, concurrency 1", async () => {
@@ -51,7 +52,9 @@ describe("PromiseLimit", () => {
running -= 1; running -= 1;
}; };
await Promise.all(range(10).map((i) => plimit.add(() => callback(i)))); // @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 1.
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
await Promise.all(range(10).map((i: any) => plimit.add(() => callback(i))));
expect(maxRunning).toEqual(2); expect(maxRunning).toEqual(2);
}); });
@@ -6,14 +6,20 @@ describe("range", () => {
}); });
test("range(stop)", () => { test("range(stop)", () => {
// @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 1.
expect(range(3)).toMatchObject([0, 1, 2]); expect(range(3)).toMatchObject([0, 1, 2]);
// @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 1.
expect(range(0)).toMatchObject([]); expect(range(0)).toMatchObject([]);
// @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 1.
expect(range(1)).toMatchObject([0]); expect(range(1)).toMatchObject([0]);
}); });
test("range(start,stop)", () => { test("range(start,stop)", () => {
// @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 2.
expect(range(0, 0)).toMatchObject([]); expect(range(0, 0)).toMatchObject([]);
// @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 2.
expect(range(0, 2)).toMatchObject([0, 1]); expect(range(0, 2)).toMatchObject([0, 1]);
// @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 2.
expect(range(4, 8)).toMatchObject([4, 5, 6, 7]); expect(range(4, 8)).toMatchObject([4, 5, 6, 7]);
}); });
@@ -81,6 +81,7 @@ describe("categorical color helpers", () => {
), ),
], ],
null, null,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
new Dataframe.KeyIndex(["continuousColumn", "categoricalColumn"]) new Dataframe.KeyIndex(["continuousColumn", "categoricalColumn"])
); );
@@ -95,6 +96,7 @@ describe("categorical color helpers", () => {
const data = obsDataframe.col("categoricalColumn").asArray(); const data = obsDataframe.col("categoricalColumn").asArray();
const cats = schema.annotations.obsByName.categoricalColumn.categories; const cats = schema.annotations.obsByName.categoricalColumn.categories;
for (let i = 0; i < schema.dataframe.nObs; i += 1) { for (let i = 0; i < schema.dataframe.nObs; i += 1) {
// @ts-expect-error ts-migrate(2722) FIXME: Cannot invoke an object which is possibly 'undefin... Remove this comment to see the full error message
expect(makeScale(ct.rgb[i])).toEqual(ct.scale(cats.indexOf(data[i]))); expect(makeScale(ct.rgb[i])).toEqual(ct.scale(cats.indexOf(data[i])));
} }
}); });
@@ -112,6 +114,7 @@ describe("categorical color helpers", () => {
const data = obsDataframe.col("categoricalColumn").asArray(); const data = obsDataframe.col("categoricalColumn").asArray();
const cats = schemaClone.annotations.obsByName.categoricalColumn.categories; const cats = schemaClone.annotations.obsByName.categoricalColumn.categories;
for (let i = 0; i < schemaClone.dataframe.nObs; i += 1) { for (let i = 0; i < schemaClone.dataframe.nObs; i += 1) {
// @ts-expect-error ts-migrate(2722) FIXME: Cannot invoke an object which is possibly 'undefin... Remove this comment to see the full error message
expect(makeScale(ct.rgb[i])).toEqual(ct.scale(cats.indexOf(data[i]))); expect(makeScale(ct.rgb[i])).toEqual(ct.scale(cats.indexOf(data[i])));
} }
}); });
@@ -122,7 +125,8 @@ describe("categorical color helpers", () => {
Array.from(schema.annotations.obsByName.categoricalColumn.categories) Array.from(schema.annotations.obsByName.categoricalColumn.categories)
); );
const userDefinedColorTable = { const userDefinedColorTable = {
categoricalColumn: shuffleCats.reduce((acc, label) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
categoricalColumn: shuffleCats.reduce((acc: any, label: any) => {
acc[label] = randRGBColor(); acc[label] = randRGBColor();
return acc; return acc;
}, {}), }, {}),
@@ -136,12 +140,14 @@ describe("categorical color helpers", () => {
"categoricalColumn", "categoricalColumn",
obsDataframe, obsDataframe,
schema, schema,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{}' is not assignable to paramet... Remove this comment to see the full error message
userColors userColors
); );
expect(ct).toBeDefined(); expect(ct).toBeDefined();
const data = obsDataframe.col("categoricalColumn").asArray(); const data = obsDataframe.col("categoricalColumn").asArray();
for (let i = 0; i < schema.dataframe.nObs; i += 1) { for (let i = 0; i < schema.dataframe.nObs; i += 1) {
expect(makeScale(ct.rgb[i])).toEqual( expect(makeScale(ct.rgb[i])).toEqual(
// @ts-expect-error ts-migrate(2722) FIXME: Cannot invoke an object which is possibly 'undefin... Remove this comment to see the full error message
ct.scale(cats.indexOf(data[i])).toString() ct.scale(cats.indexOf(data[i])).toString()
); );
} }
@@ -154,31 +160,38 @@ TODO:
2. user defined colors 2. user defined colors
*/ */
function indexSchema(schema) { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function indexSchema(schema: any) {
schema.annotations.obsByName = Object.fromEntries( schema.annotations.obsByName = Object.fromEntries(
schema.annotations?.obs?.columns?.map((v) => [v.name, v]) ?? [] // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
schema.annotations?.obs?.columns?.map((v: any) => [v.name, v]) ?? []
); );
schema.annotations.varByName = Object.fromEntries( schema.annotations.varByName = Object.fromEntries(
schema.annotations?.var?.columns?.map((v) => [v.name, v]) ?? [] // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
schema.annotations?.var?.columns?.map((v: any) => [v.name, v]) ?? []
); );
schema.layout.obsByName = Object.fromEntries( schema.layout.obsByName = Object.fromEntries(
schema.layout?.obs?.map((v) => [v.name, v]) ?? [] // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
schema.layout?.obs?.map((v: any) => [v.name, v]) ?? []
); );
schema.layout.varByName = Object.fromEntries( schema.layout.varByName = Object.fromEntries(
schema.layout?.var?.map((v) => [v.name, v]) ?? [] // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
schema.layout?.var?.map((v: any) => [v.name, v]) ?? []
); );
return schema; return schema;
} }
function makeScale(rgb) { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function makeScale(rgb: any) {
// make a scale string from a rgb float triple // make a scale string from a rgb float triple
return `rgb(${(rgb[0] * 255) >>> 0}, ${(rgb[1] * 255) >>> 0}, ${ return `rgb(${(rgb[0] * 255) >>> 0}, ${(rgb[1] * 255) >>> 0}, ${
(rgb[2] * 256) >>> 0 (rgb[2] * 256) >>> 0
})`; })`;
} }
function shuffle(array) { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function shuffle(array: any) {
for (let i = array.length - 1; i > 0; i -= 1) { for (let i = array.length - 1; i > 0; i -= 1) {
const j = (Math.random() * (i + 1)) >>> 0; const j = (Math.random() * (i + 1)) >>> 0;
[array[i], array[j]] = [array[j], array[i]]; [array[i], array[j]] = [array[j], array[i]];
@@ -2,7 +2,7 @@
test controls helpers test controls helpers
*/ */
// TODO #2227 test: improve test coverage on control helper functions // TODO #2227 test: improve test coverage on control helper functions
// (`topNCategories()`, `isSelectableCategoryName()`, `selectableCategoryNames()`, `createCategorySummaryFromDfCol()`, `createCategoricalSelection()`, ) // (`isSelectableCategoryName()`, `selectableCategoryNames()`, `createCategorySummaryFromDfCol()`, `createCategoricalSelection()`, )
describe("controls helpers", () => { describe("controls helpers", () => {
test("placeholder", () => {}); test("placeholder", () => {});
@@ -24,6 +24,7 @@ describe("encode/decode", () => {
expect(dfA.columns).toEqual(columns); expect(dfA.columns).toEqual(columns);
const colIndex = new KeyIndex(["a", "b", "c", "d"]); const colIndex = new KeyIndex(["a", "b", "c", "d"]);
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
const dfWithColIdx = new Dataframe([3, 4], columns, null, colIndex); const dfWithColIdx = new Dataframe([3, 4], columns, null, colIndex);
const dfB = decodeMatrixFBS(encodeMatrixFBS(dfWithColIdx)); const dfB = decodeMatrixFBS(encodeMatrixFBS(dfWithColIdx));
expect([dfB.nRows, dfB.nCols]).toEqual(dfWithColIdx.dims); expect([dfB.nRows, dfB.nCols]).toEqual(dfWithColIdx.dims);
@@ -75,6 +75,7 @@ const aSchemaResponse = {
const anAnnotationsObsJSONResponse = { const anAnnotationsObsJSONResponse = {
names: ["name", "field1", "field2", "field3", "field4"], names: ["name", "field1", "field2", "field3", "field4"],
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
data: _() data: _()
.range(nObs) .range(nObs)
.map((idx) => [ .map((idx) => [
@@ -91,6 +92,7 @@ const anAnnotationsObsJSONResponse = {
const anAnnotationsVarJSONResponse = { const anAnnotationsVarJSONResponse = {
names: ["fieldA", "fieldB", "fieldC", "fieldD", "name"], names: ["fieldA", "fieldB", "fieldC", "fieldD", "name"],
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
data: _() data: _()
.range(nVar) .range(nVar)
.map((idx) => [ .map((idx) => [
@@ -105,8 +107,11 @@ const anAnnotationsVarJSONResponse = {
.value(), .value(),
}; };
function encodeTypedArray(builder, uType, uData) { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function encodeTypedArray(builder: any, uType: any, uData: any) {
// @ts-expect-error --- FIXME: Element implicitly has an 'any' type.
const uTypeName = NetEncoding.TypedArray[uType]; const uTypeName = NetEncoding.TypedArray[uType];
// @ts-expect-error --- FIXME: Element implicitly has an 'any' type.
const ArrayType = NetEncoding[uTypeName]; const ArrayType = NetEncoding[uTypeName];
const dv = ArrayType.createDataVector(builder, uData); const dv = ArrayType.createDataVector(builder, uData);
builder.startObject(1); builder.startObject(1);
@@ -114,7 +119,8 @@ function encodeTypedArray(builder, uType, uData) {
return builder.endObject(); return builder.endObject();
} }
function encodeMatrix(columns, colIndex = undefined) { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function encodeMatrix(columns: any, colIndex = undefined) {
/* /*
IMPORTANT: this is not a general purpose encoder. in particular, IMPORTANT: this is not a general purpose encoder. in particular,
it doesn't correctly handle all column index types, nor does it it doesn't correctly handle all column index types, nor does it
@@ -123,6 +129,7 @@ function encodeMatrix(columns, colIndex = undefined) {
encodeMatrixFBS in matrix.py is more general. This is used only encodeMatrixFBS in matrix.py is more general. This is used only
as a testing santity check (alt implementation). as a testing santity check (alt implementation).
*/ */
// @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1.
const utf8Encoder = new TextEncoder("utf-8"); const utf8Encoder = new TextEncoder("utf-8");
const builder = new flatbuffers.Builder(1024); const builder = new flatbuffers.Builder(1024);
const cols = map(columns, (carr) => { const cols = map(columns, (carr) => {
@@ -172,11 +179,13 @@ function encodeMatrix(columns, colIndex = undefined) {
const anAnnotationsObsFBSResponse = (() => { const anAnnotationsObsFBSResponse = (() => {
const columns = zip(...anAnnotationsObsJSONResponse.data).slice(1); const columns = zip(...anAnnotationsObsJSONResponse.data).slice(1);
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
return encodeMatrix(columns, anAnnotationsObsJSONResponse.names); return encodeMatrix(columns, anAnnotationsObsJSONResponse.names);
})(); })();
const anAnnotationsVarFBSResponse = (() => { const anAnnotationsVarFBSResponse = (() => {
const columns = zip(...anAnnotationsVarJSONResponse.data).slice(1); const columns = zip(...anAnnotationsVarJSONResponse.data).slice(1);
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
return encodeMatrix(columns, anAnnotationsVarJSONResponse.names); return encodeMatrix(columns, anAnnotationsVarJSONResponse.names);
})(); })();
@@ -185,11 +194,13 @@ const aLayoutFBSResponse = (() => {
new Float32Array(nObs).fill(Math.random()), new Float32Array(nObs).fill(Math.random()),
new Float32Array(nObs).fill(Math.random()), new Float32Array(nObs).fill(Math.random()),
]; ];
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
return encodeMatrix(coords, ["umap_0", "umap_1"]); return encodeMatrix(coords, ["umap_0", "umap_1"]);
})(); })();
const aDataObsResponse = { const aDataObsResponse = {
var: [2, 4, 29], var: [2, 4, 29],
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
obs: _() obs: _()
.range(nObs) .range(nObs)
.map((idx) => [idx, Math.random(), Math.random(), Math.random()]) .map((idx) => [idx, Math.random(), Math.random(), Math.random()])
@@ -126,7 +126,8 @@ const someData = [
}, },
]; ];
let payments = null; // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let payments: any = null;
beforeEach(() => { beforeEach(() => {
payments = new Crossfilter(someData); payments = new Crossfilter(someData);
}); });
@@ -138,7 +139,13 @@ describe("ImmutableTypedCrossfilter", () => {
expect(payments.all()).toEqual(someData); expect(payments.all()).toEqual(someData);
const p = payments const p = payments
.addDimension("quantity", "scalar", (i, d) => d[i].quantity, Int32Array) .addDimension(
"quantity",
"scalar",
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(i: any, d: any) => d[i].quantity,
Int32Array
)
.select("quantity", { mode: "all" }); .select("quantity", { mode: "all" });
expect(p).toBeDefined(); expect(p).toBeDefined();
expect(p.all()).toEqual(someData); expect(p.all()).toEqual(someData);
@@ -158,7 +165,8 @@ describe("ImmutableTypedCrossfilter", () => {
const p2 = payments.addDimension( const p2 = payments.addDimension(
"quantity", "quantity",
"scalar", "scalar",
(i, data) => data[i].quantity, // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(i: any, data: any) => data[i].quantity,
Int32Array Int32Array
); );
@@ -175,10 +183,22 @@ describe("ImmutableTypedCrossfilter", () => {
test("select all and none", () => { test("select all and none", () => {
let p = payments let p = payments
.addDimension("quantity", "scalar", (i, d) => d[i].quantity, Int32Array) .addDimension(
.addDimension("tip", "scalar", (i, d) => d[i].tip, Float32Array) "quantity",
.addDimension("total", "scalar", (i, d) => d[i].total, Float32Array) "scalar",
.addDimension("type", "enum", (i, d) => d[i].type); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(i: any, d: any) => d[i].quantity,
Int32Array
) // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.addDimension("tip", "scalar", (i: any, d: any) => d[i].tip, Float32Array)
.addDimension(
"total",
"scalar",
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(i: any, d: any) => d[i].total,
Float32Array
) // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.addDimension("type", "enum", (i: any, d: any) => d[i].type);
expect(p).toBeDefined(); expect(p).toBeDefined();
/* expect all records to be selected - default init state */ /* expect all records to be selected - default init state */
@@ -230,11 +250,24 @@ describe("ImmutableTypedCrossfilter", () => {
}); });
describe("scalar dimension", () => { describe("scalar dimension", () => {
let p; // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let p: any;
beforeEach(() => { beforeEach(() => {
p = payments p = payments
.addDimension("quantity", "scalar", (i, d) => d[i].quantity, Int32Array) .addDimension(
.addDimension("tip", "scalar", (i, d) => d[i].tip, Float32Array) "quantity",
"scalar",
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(i: any, d: any) => d[i].quantity,
Int32Array
)
.addDimension(
"tip",
"scalar",
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(i: any, d: any) => d[i].tip,
Float32Array
)
.select("tip", { mode: "all" }); .select("tip", { mode: "all" });
}); });
@@ -277,9 +310,11 @@ describe("ImmutableTypedCrossfilter", () => {
}); });
describe("enum dimension", () => { describe("enum dimension", () => {
let p; // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let p: any;
beforeEach(() => { beforeEach(() => {
p = payments.addDimension("type", "enum", (i, d) => d[i].type); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
p = payments.addDimension("type", "enum", (i: any, d: any) => d[i].type);
}); });
test("all", () => { test("all", () => {
@@ -317,7 +352,8 @@ describe("ImmutableTypedCrossfilter", () => {
}); });
describe("spatial dimension", () => { describe("spatial dimension", () => {
let p; // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let p: any;
beforeEach(() => { beforeEach(() => {
const X = someData.map((r) => r.coords[0]); const X = someData.map((r) => r.coords[0]);
const Y = someData.map((r) => r.coords[1]); const Y = someData.map((r) => r.coords[1]);
@@ -406,14 +442,22 @@ describe("ImmutableTypedCrossfilter", () => {
}); });
describe("non-finite scalars", () => { describe("non-finite scalars", () => {
let p; // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let p: any;
beforeEach(() => { beforeEach(() => {
p = payments p = payments
.addDimension("quantity", "scalar", (i, d) => d[i].quantity, Int32Array) .addDimension(
"quantity",
"scalar",
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(i: any, d: any) => d[i].quantity,
Int32Array
)
.addDimension( .addDimension(
"nonFinite", "nonFinite",
"scalar", "scalar",
(i, d) => d[i].nonFinite, // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(i: any, d: any) => d[i].nonFinite,
Float32Array Float32Array
) )
.select("quantity", { mode: "all" }); .select("quantity", { mode: "all" });
@@ -15,7 +15,8 @@ paths for:
const pInf = Number.POSITIVE_INFINITY; const pInf = Number.POSITIVE_INFINITY;
const nInf = Number.NEGATIVE_INFINITY; const nInf = Number.NEGATIVE_INFINITY;
function fillRange(arr, start = 0) { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function fillRange(arr: any, start = 0) {
const larr = arr; const larr = arr;
for (let i = 0, len = larr.length; i < len; i += 1) { for (let i = 0, len = larr.length; i < len; i += 1) {
larr[i] = i + start; larr[i] = i + start;
@@ -23,7 +24,8 @@ function fillRange(arr, start = 0) {
return larr; return larr;
} }
function fillRand(arr) { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function fillRand(arr: any) {
for (let i = 0, len = arr.length; i < len; i += 1) { for (let i = 0, len = arr.length; i < len; i += 1) {
arr[i] = Math.random(); arr[i] = Math.random();
} }
@@ -48,16 +50,22 @@ describe("sortArray", () => {
describe("finite numbers", () => { describe("finite numbers", () => {
[Array, Float32Array, Uint32Array, Int32Array, Float64Array].map((Type) => [Array, Float32Array, Uint32Array, Int32Array, Float64Array].map((Type) =>
test(Type.name, () => { test(Type.name, () => {
// @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.
expect(sortArray(Type.from([6, 5, 4, 3, 2, 1, 0]))).toMatchObject( expect(sortArray(Type.from([6, 5, 4, 3, 2, 1, 0]))).toMatchObject(
// @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.
Type.from([0, 1, 2, 3, 4, 5, 6]) Type.from([0, 1, 2, 3, 4, 5, 6])
); );
// @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.
expect(sortArray(Type.from([6, 5, 4, 3, 2, 1]))).toMatchObject( expect(sortArray(Type.from([6, 5, 4, 3, 2, 1]))).toMatchObject(
// @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.
Type.from([1, 2, 3, 4, 5, 6]) Type.from([1, 2, 3, 4, 5, 6])
); );
const source = fillRand(new Type(1000)); const source = fillRand(new Type(1000));
// @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.
expect(sortArray(Type.from(source))).toMatchObject( expect(sortArray(Type.from(source))).toMatchObject(
// @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.
Type.from(source).sort() Type.from(source).sort()
); );
}) })
@@ -130,22 +138,27 @@ describe("sortIndex", () => {
describe("finite numbers", () => { describe("finite numbers", () => {
[Array, Float32Array, Uint32Array, Int32Array, Float64Array].map((Type) => [Array, Float32Array, Uint32Array, Int32Array, Float64Array].map((Type) =>
test(Type.name, () => { test(Type.name, () => {
// @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.
const source1 = Type.from([6, 5, 4, 3, 2, 1, 0]); const source1 = Type.from([6, 5, 4, 3, 2, 1, 0]);
const index1 = fillRange(new Uint32Array(source1.length)); const index1 = fillRange(new Uint32Array(source1.length));
expect(sortIndex(index1, source1)).toMatchObject( expect(sortIndex(index1, source1)).toMatchObject(
index1.sort((a, b) => source1[a] - source1[b]) // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
index1.sort((a: any, b: any) => source1[a] - source1[b])
); );
// @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.
const source2 = Type.from([6, 5, 4, 3, 2, 1]); const source2 = Type.from([6, 5, 4, 3, 2, 1]);
const index2 = fillRange(new Uint32Array(source2.length)); const index2 = fillRange(new Uint32Array(source2.length));
expect(sortIndex(index2, source2)).toMatchObject( expect(sortIndex(index2, source2)).toMatchObject(
index2.sort((a, b) => source1[a] - source1[b]) // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
index2.sort((a: any, b: any) => source1[a] - source1[b])
); );
const source3 = fillRand(new Type(1000)); const source3 = fillRand(new Type(1000));
const index3 = fillRange(new Uint32Array(source3.length)); const index3 = fillRange(new Uint32Array(source3.length));
expect(sortIndex(index3, source3)).toMatchObject( expect(sortIndex(index3, source3)).toMatchObject(
index3.sort((a, b) => source1[a] - source1[b]) // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
index3.sort((a: any, b: any) => source1[a] - source1[b])
); );
}) })
); );
+2
View File
@@ -11,11 +11,13 @@ module.exports = {
}, },
], ],
"@babel/preset-react", "@babel/preset-react",
"@babel/preset-typescript",
], ],
plugins: [ plugins: [
"@babel/plugin-proposal-function-bind", "@babel/plugin-proposal-function-bind",
["@babel/plugin-proposal-decorators", { legacy: true }], ["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/plugin-proposal-class-properties", { loose: true }], ["@babel/plugin-proposal-class-properties", { loose: true }],
["@babel/plugin-proposal-private-methods", { loose: true }],
"@babel/plugin-proposal-export-namespace-from", "@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-proposal-optional-chaining", "@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator", "@babel/plugin-proposal-nullish-coalescing-operator",
+2
View File
@@ -10,11 +10,13 @@ module.exports = {
}, },
], ],
"@babel/preset-react", "@babel/preset-react",
"@babel/preset-typescript",
], ],
plugins: [ plugins: [
"@babel/plugin-proposal-function-bind", "@babel/plugin-proposal-function-bind",
["@babel/plugin-proposal-decorators", { legacy: true }], ["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/plugin-proposal-class-properties", { loose: true }], ["@babel/plugin-proposal-class-properties", { loose: true }],
["@babel/plugin-proposal-private-methods", { loose: true }],
"@babel/plugin-proposal-export-namespace-from", "@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-transform-react-constant-elements", "@babel/plugin-transform-react-constant-elements",
"@babel/plugin-transform-runtime", "@babel/plugin-transform-runtime",
+41 -5
View File
@@ -1,12 +1,17 @@
/* eslint-disable @blueprintjs/classes-constants -- we don't import blueprint here */
module.exports = { module.exports = {
root: true, root: true,
parser: "babel-eslint", parser: "@typescript-eslint/parser",
extends: [ extends: [
"airbnb", "airbnb-typescript",
"plugin:@typescript-eslint/recommended",
"plugin:eslint-comments/recommended", "plugin:eslint-comments/recommended",
"plugin:@blueprintjs/recommended", "plugin:@blueprintjs/recommended",
"plugin:compat/recommended", "plugin:compat/recommended",
"plugin:prettier/recommended", "plugin:jsx-a11y/recommended",
// (thuang) disable eslint formatting rules, so prettier can do its job
// Do not use `plugin:prettier/recommended` per doc below:
// https://prettier.io/docs/en/integrating-with-linters.html
"prettier", "prettier",
], ],
settings: { settings: {
@@ -32,18 +37,47 @@ module.exports = {
jsx: true, jsx: true,
generators: true, generators: true,
}, },
project: "./tsconfig.json",
}, },
rules: { rules: {
"react/jsx-no-target-blank": "off", "react/jsx-no-target-blank": "off",
"eslint-comments/require-description": ["error"], "eslint-comments/require-description": ["error"],
"no-magic-numbers": "off", "no-magic-numbers": "off",
"@typescript-eslint/no-magic-numbers": "off",
"no-nested-ternary": "off", "no-nested-ternary": "off",
"func-style": "off", "func-style": "off",
"arrow-parens": "off", "arrow-parens": "off",
"no-use-before-define": "off", "no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": "off",
"react/jsx-filename-extension": "off", "react/jsx-filename-extension": "off",
"comma-dangle": "off", "comma-dangle": "off",
"@typescript-eslint/comma-dangle": "off",
"no-underscore-dangle": "off", "no-underscore-dangle": "off",
// Override airbnb config to allow leading underscore
// https://github.com/iamturns/eslint-config-airbnb-typescript/blob/master/lib/shared.js#L35
"@typescript-eslint/naming-convention": [
"error",
{
selector: "class",
format: ["PascalCase"],
leadingUnderscore: "allow",
},
{
selector: "function",
format: ["camelCase", "PascalCase"],
leadingUnderscore: "allowSingleOrDouble",
},
{
selector: "typeLike",
format: ["PascalCase"],
},
{
selector: "variable",
format: ["camelCase", "PascalCase", "UPPER_CASE"],
leadingUnderscore: "allowSingleOrDouble",
trailingUnderscore: "allowDouble",
},
],
"implicit-arrow-linebreak": "off", "implicit-arrow-linebreak": "off",
"no-console": "off", "no-console": "off",
"spaced-comment": ["error", "always", { exceptions: ["*"] }], "spaced-comment": ["error", "always", { exceptions: ["*"] }],
@@ -51,6 +85,7 @@ module.exports = {
"object-curly-newline": ["error", { consistent: true }], "object-curly-newline": ["error", { consistent: true }],
"react/prop-types": [0], "react/prop-types": [0],
"space-before-function-paren": "off", "space-before-function-paren": "off",
"@typescript-eslint/space-before-function-paren": "off",
"function-paren-newline": "off", "function-paren-newline": "off",
"prefer-destructuring": ["error", { object: true, array: false }], "prefer-destructuring": ["error", { object: true, array: false }],
"import/prefer-default-export": "off", "import/prefer-default-export": "off",
@@ -69,9 +104,9 @@ module.exports = {
}, },
overrides: [ overrides: [
{ {
files: ["**/*.test.js"], files: ["**/*.test.ts"],
env: { env: {
jest: true, // now **/*.test.js files' env has both es6 *and* jest jest: true, // now **/*.test.ts files' env has both es6 *and* jest
}, },
// Can't extend in overrides: https://github.com/eslint/eslint/issues/8813 // Can't extend in overrides: https://github.com/eslint/eslint/issues/8813
// "extends": ["plugin:jest/recommended"] // "extends": ["plugin:jest/recommended"]
@@ -86,3 +121,4 @@ module.exports = {
}, },
], ],
}; };
/* eslint-enable @blueprintjs/classes-constants -- we don't import blueprint here */
@@ -1,3 +1,4 @@
module.exports = { module.exports = {
"*.js": "eslint --fix", "*.{js,ts,jsx,tsx}": "eslint --fix",
"**/*": "prettier --write --ignore-unknown",
}; };
@@ -1,6 +1,10 @@
/* eslint-disable import/no-extraneous-dependencies -- this file is a devDependency*/ /* eslint-disable import/no-extraneous-dependencies -- this file is a devDependency*/
/* eslint-disable @blueprintjs/classes-constants -- we don't import blueprint here */
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const cheerio = require("cheerio"); const cheerio = require("cheerio");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const crypto = require("crypto"); const crypto = require("crypto");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const HtmlWebpackPlugin = require("html-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin");
const digest = (str) => { const digest = (str) => {
@@ -50,4 +54,5 @@ class CspHashPlugin {
} }
module.exports = CspHashPlugin; module.exports = CspHashPlugin;
/* eslint-enable @blueprintjs/classes-constants -- we don't import blueprint here */
/* eslint-enable import/no-extraneous-dependencies -- enable*/ /* eslint-enable import/no-extraneous-dependencies -- enable*/
@@ -22,7 +22,7 @@
> >
<img <img
src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/cellxgene-logo.png" src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/cellxgene-logo.png"
style="width: 320px;" style="width: 320px"
/> />
<div <div
style=" style="
@@ -37,36 +37,34 @@
max-width: 550px; max-width: 550px;
" "
> >
<div style="margin-bottom: 0; font-weight: bolder; font-size: 1.2em;"> <div style="margin-bottom: 0; font-weight: bolder; font-size: 1.2em">
Unsupported Browser Unsupported Browser
</div> </div>
<div style="margin-top: 0;"> <div style="margin-top: 0">
cellxgene is currently supported on the following browsers cellxgene is currently supported on the following browsers
</div> </div>
<div <div style="display: flex; justify-content: space-around; margin-top: 16px">
style="display: flex; justify-content: space-around; margin-top: 16px;"
>
<a <a
href="https://www.google.com/chrome/?hl=en%22" href="https://www.google.com/chrome/?hl=en%22"
aria-label="Download Google Chrome" aria-label="Download Google Chrome"
> >
<img <img
src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/chrome.png" src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/chrome.png"
style="width: 80px; height: 80px;" style="width: 80px; height: 80px"
/> />
<div>Chrome &gt; 60</div> <div>Chrome &gt; 60</div>
</a> </a>
<a href="https://www.mozilla.com/firefox/" aria-label="Download Firefox"> <a href="https://www.mozilla.com/firefox/" aria-label="Download Firefox">
<img <img
src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/firefox.png" src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/firefox.png"
style="width: 80px; height: 80px;" style="width: 80px; height: 80px"
/> />
<div>Firefox ≥ 60</div> <div>Firefox ≥ 60</div>
</a> </a>
<a href="//www.microsoft.com/edge" aria-label="Download Edge"> <a href="//www.microsoft.com/edge" aria-label="Download Edge">
<img <img
src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/edge.png" src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/edge.png"
style="width: 80px; height: 80px;" style="width: 80px; height: 80px"
/> />
<div>Edge ≥ 79</div> <div>Edge ≥ 79</div>
</a> </a>
@@ -1,13 +1,23 @@
/* eslint-disable @blueprintjs/classes-constants -- we don't import blueprint here */
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const path = require("path"); const path = require("path");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const webpack = require("webpack"); const webpack = require("webpack");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const HtmlWebpackPlugin = require("html-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const FaviconsWebpackPlugin = require("favicons-webpack-plugin"); const FaviconsWebpackPlugin = require("favicons-webpack-plugin");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin"); const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const { merge } = require("webpack-merge"); const { merge } = require("webpack-merge");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const sharedConfig = require("./webpack.config.shared"); const sharedConfig = require("./webpack.config.shared");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const babelOptions = require("../babel/babel.dev"); const babelOptions = require("../babel/babel.dev");
const fonts = path.resolve("src/fonts"); const fonts = path.resolve("src/fonts");
@@ -23,7 +33,7 @@ const devConfig = {
module: { module: {
rules: [ rules: [
{ {
test: /\.jsx?$/, test: /\.(ts|js)x?$/,
loader: "babel-loader", loader: "babel-loader",
options: babelOptions, options: babelOptions,
}, },
@@ -83,3 +93,4 @@ const devConfig = {
}; };
module.exports = merge(sharedConfig, devConfig); module.exports = merge(sharedConfig, devConfig);
/* eslint-enable @blueprintjs/classes-constants -- we don't import blueprint here */
@@ -1,18 +1,31 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const path = require("path"); const path = require("path");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const webpack = require("webpack"); const webpack = require("webpack");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const HtmlWebpackPlugin = require("html-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const { CleanWebpackPlugin } = require("clean-webpack-plugin"); const { CleanWebpackPlugin } = require("clean-webpack-plugin");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const TerserJSPlugin = require("terser-webpack-plugin"); const TerserJSPlugin = require("terser-webpack-plugin");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const CleanCss = require("clean-css"); const CleanCss = require("clean-css");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const FaviconsWebpackPlugin = require("favicons-webpack-plugin"); const FaviconsWebpackPlugin = require("favicons-webpack-plugin");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const { merge } = require("webpack-merge"); const { merge } = require("webpack-merge");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const babelOptions = require("../babel/babel.prod"); const babelOptions = require("../babel/babel.prod");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const CspHashPlugin = require("./cspHashPlugin"); const CspHashPlugin = require("./cspHashPlugin");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const sharedConfig = require("./webpack.config.shared"); const sharedConfig = require("./webpack.config.shared");
const fonts = path.resolve("src/fonts"); const fonts = path.resolve("src/fonts");
@@ -38,7 +51,7 @@ const prodConfig = {
module: { module: {
rules: [ rules: [
{ {
test: /\.jsx?$/, test: /\.(ts|js)x?$/,
loader: "babel-loader", loader: "babel-loader",
options: babelOptions, options: babelOptions,
}, },
@@ -1,8 +1,13 @@
/* eslint-disable @blueprintjs/classes-constants -- we don't import blueprint here */
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const path = require("path"); const path = require("path");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const fs = require("fs"); const fs = require("fs");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const ObsoleteWebpackPlugin = require("obsolete-webpack-plugin"); const ObsoleteWebpackPlugin = require("obsolete-webpack-plugin");
// eslint-disable-next-line @blueprintjs/classes-constants -- incorrect match // eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin"); const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");
const src = path.resolve("src"); const src = path.resolve("src");
@@ -29,6 +34,9 @@ module.exports = {
path: path.resolve("build"), path: path.resolve("build"),
publicPath, publicPath,
}, },
resolve: {
extensions: [".ts", ".tsx", "..."],
},
module: { module: {
rules: [ rules: [
{ {
@@ -72,3 +80,4 @@ module.exports = {
}), }),
], ],
}; };
/* eslint-enable @blueprintjs/classes-constants -- we don't import blueprint here */
+12353 -10321
View File
File diff suppressed because it is too large Load Diff
+44 -15
View File
@@ -8,9 +8,9 @@
"build": "npm run clean && webpack --config", "build": "npm run clean && webpack --config",
"clean": "rimraf build", "clean": "rimraf build",
"dev": "npm run build -- configuration/webpack/webpack.config.dev.js", "dev": "npm run build -- configuration/webpack/webpack.config.dev.js",
"e2e": "jest --config __tests__/e2e/e2eJestConfig.json e2e/e2e.test.js", "e2e": "jest --config __tests__/e2e/e2eJestConfig.json e2e/e2e.test.ts",
"e2e-annotations": "jest --config __tests__/e2e/e2eJestConfig.json e2e/e2eAnnotations.test.js", "e2e-annotations": "jest --config __tests__/e2e/e2eJestConfig.json e2e/e2eAnnotations.test.ts",
"e2e-prod": "CXG_URL_BASE='https://cellxgene.cziscience.com/d/pbmc3k.cxg/' jest --config __tests__/e2e/e2eJestConfig.json e2e/e2e.test.js", "e2e-prod": "CXG_URL_BASE='https://cellxgene.cziscience.com/d/pbmc3k.cxg/' jest --config __tests__/e2e/e2eJestConfig.json e2e/e2e.test.ts",
"fmt": "eslint --fix src __tests__", "fmt": "eslint --fix src __tests__",
"lint": "eslint --fix src __tests__", "lint": "eslint --fix src __tests__",
"prod": "npm run build -- configuration/webpack/webpack.config.prod.js", "prod": "npm run build -- configuration/webpack/webpack.config.prod.js",
@@ -64,11 +64,9 @@
"react": "^17.0.2", "react": "^17.0.2",
"react-async": "^10.0.1", "react-async": "^10.0.1",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-easy-emoji": "^1.4.0",
"react-flip-toolkit": "^7.0.12", "react-flip-toolkit": "^7.0.12",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-icons": "^4.2.0", "react-icons": "^4.2.0",
"react-popper": "^2.2.4",
"react-redux": "^7.2.0", "react-redux": "^7.2.0",
"redux": "^4.0.5", "redux": "^4.0.5",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
@@ -90,10 +88,38 @@
"@babel/plugin-transform-runtime": "^7.13.15", "@babel/plugin-transform-runtime": "^7.13.15",
"@babel/preset-env": "^7.13.15", "@babel/preset-env": "^7.13.15",
"@babel/preset-react": "^7.13.13", "@babel/preset-react": "^7.13.13",
"@babel/preset-typescript": "^7.14.5",
"@babel/register": "^7.13.16", "@babel/register": "^7.13.16",
"@babel/runtime": "^7.13.16", "@babel/runtime": "^7.13.16",
"@blueprintjs/eslint-plugin": "^0.3.0", "@blueprintjs/eslint-plugin": "^0.3.0",
"@sentry/webpack-plugin": "^1.15.0", "@sentry/webpack-plugin": "^1.15.0",
"@types/d3": "^7.0.0",
"@types/d3-scale-chromatic": "^3.0.0",
"@types/expect-puppeteer": "^4.4.6",
"@types/flatbuffers": "^1.10.0",
"@types/is-number": "^7.0.1",
"@types/jest": "^26.0.24",
"@types/jest-environment-puppeteer": "^4.4.1",
"@types/lodash.clonedeep": "^4.5.6",
"@types/lodash.difference": "^4.5.6",
"@types/lodash.every": "^4.6.6",
"@types/lodash.filter": "^4.6.6",
"@types/lodash.foreach": "^4.5.6",
"@types/lodash.isnumber": "^3.0.6",
"@types/lodash.map": "^4.6.13",
"@types/lodash.pull": "^4.1.6",
"@types/lodash.sortby": "^4.7.6",
"@types/lodash.uniq": "^4.5.6",
"@types/lodash.zip": "^4.2.6",
"@types/pako": "^1.0.2",
"@types/puppeteer": "^5.4.4",
"@types/react": "^17.0.14",
"@types/react-dom": "^17.0.9",
"@types/react-helmet": "^6.1.2",
"@types/react-redux": "^7.1.18",
"@types/sha1": "^1.1.3",
"@typescript-eslint/eslint-plugin": "^4.28.4",
"@typescript-eslint/parser": "^4.28.4",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"babel-jest": "^26.1.0", "babel-jest": "^26.1.0",
"babel-loader": "^8.1.0", "babel-loader": "^8.1.0",
@@ -103,10 +129,9 @@
"clean-css": "^5.1.2", "clean-css": "^5.1.2",
"clean-webpack-plugin": "^4.0.0-alpha.0", "clean-webpack-plugin": "^4.0.0-alpha.0",
"codecov": "^3.7.1", "codecov": "^3.7.1",
"connect-history-api-fallback": "^1.6.0",
"css-loader": "^5.2.4", "css-loader": "^5.2.4",
"eslint": "^7.24.0", "eslint": "^7.24.0",
"eslint-config-airbnb": "^18.2.0", "eslint-config-airbnb-typescript": "^12.3.1",
"eslint-config-prettier": "^8.2.0", "eslint-config-prettier": "^8.2.0",
"eslint-loader": "^4.0.2", "eslint-loader": "^4.0.2",
"eslint-plugin-compat": "^3.8.0", "eslint-plugin-compat": "^3.8.0",
@@ -115,12 +140,11 @@
"eslint-plugin-import": "^2.22.0", "eslint-plugin-import": "^2.22.0",
"eslint-plugin-jest": "^24.3.5", "eslint-plugin-jest": "^24.3.5",
"eslint-plugin-jsx-a11y": "^6.3.1", "eslint-plugin-jsx-a11y": "^6.3.1",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-react": "^7.23.2", "eslint-plugin-react": "^7.23.2",
"eslint-plugin-react-hooks": "^4.0.8", "eslint-plugin-react-hooks": "^4.0.8",
"expect-puppeteer": "^5.0.0", "expect-puppeteer": "^5.0.0",
"express": "^4.17.1", "express": "^4.17.1",
"favicons": "^6.2.1", "favicons": "^6.2.2",
"favicons-webpack-plugin": "^5.0.2", "favicons-webpack-plugin": "^5.0.2",
"file-loader": "^6.0.0", "file-loader": "^6.0.0",
"html-webpack-plugin": "^5.3.1", "html-webpack-plugin": "^5.3.1",
@@ -146,10 +170,8 @@
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"script-ext-html-webpack-plugin": "^2.1.4", "script-ext-html-webpack-plugin": "^2.1.4",
"serve-favicon": "^2.5.0", "serve-favicon": "^2.5.0",
"style-loader": "^2.0.0",
"sw-precache-webpack-plugin": "^1.0.0",
"terser-webpack-plugin": "^5.1.1", "terser-webpack-plugin": "^5.1.1",
"url-loader": "^4.1.0", "typescript": "^4.3.5",
"webpack": "^5.34.0", "webpack": "^5.34.0",
"webpack-cli": "^4.6.0", "webpack-cli": "^4.6.0",
"webpack-dev-middleware": "^4.1.0", "webpack-dev-middleware": "^4.1.0",
@@ -157,10 +179,10 @@
}, },
"jest": { "jest": {
"testMatch": [ "testMatch": [
"**/__tests__/**/?(*.)(spec|test).js?(x)" "**/__tests__/**/?(*.)(spec|test).ts?(x)"
], ],
"setupFiles": [ "setupFiles": [
"./__tests__/setupMissingGlobals.js" "./__tests__/setupMissingGlobals.ts"
], ],
"coverageDirectory": "./coverage/", "coverageDirectory": "./coverage/",
"collectCoverage": true "collectCoverage": true
@@ -170,7 +192,8 @@
"test": { "test": {
"presets": [ "presets": [
"@babel/preset-env", "@babel/preset-env",
"@babel/preset-react" "@babel/preset-react",
"@babel/preset-typescript"
], ],
"plugins": [ "plugins": [
"@babel/plugin-proposal-function-bind", "@babel/plugin-proposal-function-bind",
@@ -186,6 +209,12 @@
"loose": true "loose": true
} }
], ],
[
"@babel/plugin-proposal-private-methods",
{
"loose": true
}
],
"@babel/plugin-proposal-export-namespace-from", "@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-transform-react-constant-elements", "@babel/plugin-transform-react-constant-elements",
"@babel/plugin-transform-runtime", "@babel/plugin-transform-runtime",
@@ -9,9 +9,12 @@ import { MatrixFBS, AnnotationsHelpers } from "../util/stateManager";
const { isUserAnnotation } = AnnotationsHelpers; const { isUserAnnotation } = AnnotationsHelpers;
export const annotationCreateCategoryAction = ( export const annotationCreateCategoryAction = (
newCategoryName, // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
categoryToDuplicate newCategoryName: any,
) => async (dispatch, getState) => { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
categoryToDuplicate: any
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
) => async (dispatch: any, getState: any) => {
/* /*
Add a new user-created category to the obs annotations. Add a new user-created category to the obs annotations.
@@ -89,9 +92,12 @@ export const annotationCreateCategoryAction = (
}; };
export const annotationRenameCategoryAction = ( export const annotationRenameCategoryAction = (
oldCategoryName, // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
newCategoryName oldCategoryName: any,
) => (dispatch, getState) => { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
newCategoryName: any
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
) => (dispatch: any, getState: any) => {
/* /*
Rename a user-created annotation category Rename a user-created annotation category
*/ */
@@ -124,9 +130,12 @@ export const annotationRenameCategoryAction = (
}); });
}; };
export const annotationDeleteCategoryAction = (categoryName) => ( // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
dispatch, export const annotationDeleteCategoryAction = (categoryName: any) => (
getState // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
dispatch: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
getState: any
) => { ) => {
/* /*
Delete a user-created category Delete a user-created category
@@ -149,10 +158,13 @@ export const annotationDeleteCategoryAction = (categoryName) => (
}; };
export const annotationCreateLabelInCategory = ( export const annotationCreateLabelInCategory = (
categoryName, // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
labelName, categoryName: any,
assignSelected // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
) => async (dispatch, getState) => { labelName: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
assignSelected: any // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
) => async (dispatch: any, getState: any) => {
/* /*
Add a new label to a user-defined category. If assignSelected is true, assign Add a new label to a user-defined category. If assignSelected is true, assign
the label to all currently selected cells. the label to all currently selected cells.
@@ -188,9 +200,11 @@ export const annotationCreateLabelInCategory = (
}; };
export const annotationDeleteLabelFromCategory = ( export const annotationDeleteLabelFromCategory = (
categoryName, // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
labelName categoryName: any,
) => async (dispatch, getState) => { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
labelName: any // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
) => async (dispatch: any, getState: any) => {
/* /*
delete a label from a user-defined category delete a label from a user-defined category
*/ */
@@ -218,10 +232,14 @@ export const annotationDeleteLabelFromCategory = (
}; };
export const annotationRenameLabelInCategory = ( export const annotationRenameLabelInCategory = (
categoryName, // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
oldLabelName, categoryName: any,
newLabelName // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
) => async (dispatch, getState) => { oldLabelName: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
newLabelName: any
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
) => async (dispatch: any, getState: any) => {
/* /*
label name change label name change
*/ */
@@ -255,9 +273,12 @@ export const annotationRenameLabelInCategory = (
}; };
export const annotationLabelCurrentSelection = ( export const annotationLabelCurrentSelection = (
categoryName, // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
labelName categoryName: any,
) => async (dispatch, getState) => { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
labelName: any
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
) => async (dispatch: any, getState: any) => {
/* /*
set the label on all currently selected set the label on all currently selected
*/ */
@@ -284,13 +305,24 @@ export const annotationLabelCurrentSelection = (
}); });
}; };
function writableAnnotations(annoMatrix) { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
return annoMatrix.schema.annotations.obs.columns function writableAnnotations(annoMatrix: any) {
.filter((s) => s.writable) return (
.map((s) => s.name); annoMatrix.schema.annotations.obs.columns
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.filter((s: any) => s.writable)
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.map((s: any) => s.name)
);
} }
export const needToSaveObsAnnotations = (annoMatrix, lastSavedAnnoMatrix) => { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
export const needToSaveObsAnnotations = (
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
annoMatrix: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
lastSavedAnnoMatrix: any
) => {
/* /*
Return true if there are LIKELY user-defined annotation modifications between the two Return true if there are LIKELY user-defined annotation modifications between the two
annoMatrices. Technically not an action creator, but intimately intertwined annoMatrices. Technically not an action creator, but intimately intertwined
@@ -314,11 +346,18 @@ export const needToSaveObsAnnotations = (annoMatrix, lastSavedAnnoMatrix) => {
// no schema changes; check for change in contents // no schema changes; check for change in contents
return currentWritable.some( return currentWritable.some(
(col) => annoMatrix.col(col) !== lastSavedAnnoMatrix.col(col) // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(col: any) => annoMatrix.col(col) !== lastSavedAnnoMatrix.col(col)
); );
}; };
export const saveObsAnnotationsAction = () => async (dispatch, getState) => { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
export const saveObsAnnotationsAction = () => async (
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
dispatch: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
getState: any
) => {
/* /*
Save the user-created obs annotations IF any have changed. Save the user-created obs annotations IF any have changed.
*/ */
@@ -388,7 +427,13 @@ export const saveObsAnnotationsAction = () => async (dispatch, getState) => {
} }
}; };
export const saveGenesetsAction = () => async (dispatch, getState) => { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
export const saveGenesetsAction = () => async (
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
dispatch: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
getState: any
) => {
const state = getState(); const state = getState();
// bail if gene sets not available, or in readonly mode. // bail if gene sets not available, or in readonly mode.
@@ -465,7 +510,7 @@ export const saveGenesetsAction = () => async (dispatch, getState) => {
res, res,
}); });
} }
return Promise.all([ return await Promise.all([
dispatch({ dispatch({
type: "autosave: genesets complete", type: "autosave: genesets complete",
lastSavedGenesets: genesets, lastSavedGenesets: genesets,
-51
View File
@@ -1,51 +0,0 @@
/*
action creators related to embeddings choice
*/
import { AnnoMatrixObsCrossfilter } from "../annoMatrix";
import { _setEmbeddingSubset } from "../util/stateManager/viewStackHelpers";
export async function _switchEmbedding(
prevAnnoMatrix,
prevCrossfilter,
newEmbeddingName
) {
/*
DRY helper used by embedding action creators
*/
const base = prevAnnoMatrix.base();
const embeddingDf = await base.fetch("emb", newEmbeddingName);
const annoMatrix = _setEmbeddingSubset(prevAnnoMatrix, embeddingDf);
const obsCrossfilter = await new AnnoMatrixObsCrossfilter(
annoMatrix,
prevCrossfilter.obsCrossfilter
).select("emb", newEmbeddingName, {
mode: "all",
});
return [annoMatrix, obsCrossfilter];
}
export const layoutChoiceAction = (newLayoutChoice) => async (
dispatch,
getState
) => {
/*
On layout choice, make sure we have selected all on the previous layout, AND the new
layout.
*/
const {
annoMatrix: prevAnnoMatrix,
obsCrossfilter: prevCrossfilter,
} = getState();
const [annoMatrix, obsCrossfilter] = await _switchEmbedding(
prevAnnoMatrix,
prevCrossfilter,
newLayoutChoice
);
dispatch({
type: "set layout choice",
layoutChoice: newLayoutChoice,
obsCrossfilter,
annoMatrix,
});
};
+58
View File
@@ -0,0 +1,58 @@
/*
action creators related to embeddings choice
*/
import { AnnoMatrixObsCrossfilter } from "../annoMatrix";
import { _setEmbeddingSubset } from "../util/stateManager/viewStackHelpers";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
export async function _switchEmbedding(
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
prevAnnoMatrix: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
prevCrossfilter: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
newEmbeddingName: any
) {
/*
DRY helper used by embedding action creators
*/
const base = prevAnnoMatrix.base();
const embeddingDf = await base.fetch("emb", newEmbeddingName);
const annoMatrix = _setEmbeddingSubset(prevAnnoMatrix, embeddingDf);
const obsCrossfilter = await new AnnoMatrixObsCrossfilter(
annoMatrix,
prevCrossfilter.obsCrossfilter
).select("emb", newEmbeddingName, {
mode: "all",
});
return [annoMatrix, obsCrossfilter];
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export const layoutChoiceAction = (newLayoutChoice: any) => async (
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
dispatch: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
getState: any
) => {
/*
On layout choice, make sure we have selected all on the previous layout, AND the new
layout.
*/
const {
annoMatrix: prevAnnoMatrix,
obsCrossfilter: prevCrossfilter,
} = getState();
const [annoMatrix, obsCrossfilter] = await _switchEmbedding(
prevAnnoMatrix,
prevCrossfilter,
newLayoutChoice
);
dispatch({
type: "set layout choice",
layoutChoice: newLayoutChoice,
obsCrossfilter,
annoMatrix,
});
};
@@ -21,7 +21,13 @@ The behavior manifest in these action creators:
Note that crossfilter indices are lazy created, as needed. Note that crossfilter indices are lazy created, as needed.
*/ */
export const genesetDelete = (genesetName) => (dispatch, getState) => { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export const genesetDelete = (genesetName: any) => (
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
dispatch: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
getState: any
) => {
const state = getState(); const state = getState();
const { genesets } = state; const { genesets } = state;
const gs = genesets?.genesets?.get(genesetName) ?? {}; const gs = genesets?.genesets?.get(genesetName) ?? {};
@@ -40,9 +46,12 @@ export const genesetDelete = (genesetName) => (dispatch, getState) => {
}); });
}; };
export const genesetAddGenes = (genesetName, genes) => async ( // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
dispatch, export const genesetAddGenes = (genesetName: any, genes: any) => async (
getState // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
dispatch: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
getState: any
) => { ) => {
const state = getState(); const state = getState();
const { obsCrossfilter: prevObsCrossfilter, annoMatrix } = state; const { obsCrossfilter: prevObsCrossfilter, annoMatrix } = state;
@@ -50,7 +59,8 @@ export const genesetAddGenes = (genesetName, genes) => async (
const varIndex = schema.annotations.var.index; const varIndex = schema.annotations.var.index;
const df = await annoMatrix.fetch("var", varIndex); const df = await annoMatrix.fetch("var", varIndex);
const geneNames = df.col(varIndex).asArray(); const geneNames = df.col(varIndex).asArray();
genes = genes.reduce((acc, gene) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
genes = genes.reduce((acc: any, gene: any) => {
if (geneNames.indexOf(gene.geneSymbol) === -1) { if (geneNames.indexOf(gene.geneSymbol) === -1) {
postUserErrorToast( postUserErrorToast(
`${gene.geneSymbol} doesn't appear to be a valid gene name.` `${gene.geneSymbol} doesn't appear to be a valid gene name.`
@@ -78,9 +88,12 @@ export const genesetAddGenes = (genesetName, genes) => async (
}); });
}; };
export const genesetDeleteGenes = (genesetName, geneSymbols) => ( // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
dispatch, export const genesetDeleteGenes = (genesetName: any, geneSymbols: any) => (
getState // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
dispatch: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
getState: any
) => { ) => {
const state = getState(); const state = getState();
const obsCrossfilter = dropGeneset(dispatch, state, genesetName, geneSymbols); const obsCrossfilter = dropGeneset(dispatch, state, genesetName, geneSymbols);
@@ -97,7 +110,14 @@ export const genesetDeleteGenes = (genesetName, geneSymbols) => (
Private Private
*/ */
function dropGenesetSummaryDimension(obsCrossfilter, state, genesetName) { function dropGenesetSummaryDimension(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
obsCrossfilter: 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.
genesetName: any
) {
const { annoMatrix, genesets } = state; const { annoMatrix, genesets } = state;
const varIndex = annoMatrix.schema.annotations?.var?.index; const varIndex = annoMatrix.schema.annotations?.var?.index;
const gs = genesets?.genesets?.get(genesetName) ?? {}; const gs = genesets?.genesets?.get(genesetName) ?? {};
@@ -113,7 +133,8 @@ function dropGenesetSummaryDimension(obsCrossfilter, state, genesetName) {
return obsCrossfilter.dropDimension("X", query); return obsCrossfilter.dropDimension("X", query);
} }
function dropGeneDimension(obsCrossfilter, state, gene) { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function dropGeneDimension(obsCrossfilter: any, state: any, gene: any) {
const { annoMatrix } = state; const { annoMatrix } = state;
const varIndex = annoMatrix.schema.annotations?.var?.index; const varIndex = annoMatrix.schema.annotations?.var?.index;
const query = { const query = {
@@ -126,10 +147,21 @@ function dropGeneDimension(obsCrossfilter, state, gene) {
return obsCrossfilter.dropDimension("X", query); return obsCrossfilter.dropDimension("X", query);
} }
function dropGeneset(dispatch, state, genesetName, geneSymbols) { function dropGeneset(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
dispatch: 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.
genesetName: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
geneSymbols: any
) {
const { obsCrossfilter: prevObsCrossfilter } = state; const { obsCrossfilter: prevObsCrossfilter } = state;
const obsCrossfilter = geneSymbols.reduce( const obsCrossfilter = geneSymbols.reduce(
(crossfilter, gene) => dropGeneDimension(crossfilter, state, gene), // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(crossfilter: any, gene: any) =>
dropGeneDimension(crossfilter, state, gene),
dropGenesetSummaryDimension(prevObsCrossfilter, state, genesetName) dropGenesetSummaryDimension(prevObsCrossfilter, state, genesetName)
); );
dispatch({ dispatch({
@@ -137,7 +169,8 @@ function dropGeneset(dispatch, state, genesetName, geneSymbols) {
continuousNamespace: { isGeneSetSummary: true }, continuousNamespace: { isGeneSetSummary: true },
selection: genesetName, selection: genesetName,
}); });
geneSymbols.forEach((g) => // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
geneSymbols.forEach((g: any) =>
dispatch({ dispatch({
type: "continuous metadata histogram cancel", type: "continuous metadata histogram cancel",
continuousNamespace: { isUserDefined: true }, continuousNamespace: { isUserDefined: true },
@@ -12,10 +12,22 @@ import * as viewActions from "./viewStack";
import * as embActions from "./embedding"; import * as embActions from "./embedding";
import * as genesetActions from "./geneset"; import * as genesetActions from "./geneset";
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function setGlobalConfig(config: any) {
/**
* Set any global run-time config not _exclusively_ managed by the config reducer.
* This should only set fields defined in globals.globalConfig.
*/
globals.globalConfig.maxCategoricalOptionsToDisplay =
config?.parameters?.["max-category-items"] ??
globals.globalConfig.maxCategoricalOptionsToDisplay;
}
/* /*
return promise fetching user-configured colors return promise fetching user-configured colors
*/ */
async function userColorsFetchAndLoad(dispatch) { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
async function userColorsFetchAndLoad(dispatch: any) {
return fetchJson("colors").then((response) => return fetchJson("colors").then((response) =>
dispatch({ dispatch({
type: "universe: user color load success", type: "universe: user color load success",
@@ -28,9 +40,13 @@ async function schemaFetch() {
return fetchJson("schema"); return fetchJson("schema");
} }
async function configFetch(dispatch) { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
async function configFetch(dispatch: any) {
return fetchJson("config").then((response) => { return fetchJson("config").then((response) => {
const config = { ...globals.configDefaults, ...response.config }; const config = { ...globals.configDefaults, ...response.config };
setGlobalConfig(config);
dispatch({ dispatch({
type: "configuration load complete", type: "configuration load complete",
config, config,
@@ -39,7 +55,8 @@ async function configFetch(dispatch) {
}); });
} }
async function userInfoFetch(dispatch) { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
async function userInfoFetch(dispatch: any) {
return fetchJson("userinfo").then((response) => { return fetchJson("userinfo").then((response) => {
const { userinfo: userInfo } = response || {}; const { userinfo: userInfo } = response || {};
dispatch({ dispatch({
@@ -50,7 +67,8 @@ async function userInfoFetch(dispatch) {
}); });
} }
async function genesetsFetch(dispatch, config) { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
async function genesetsFetch(dispatch: any, config: any) {
/* request genesets ONLY if the backend supports the feature */ /* request genesets ONLY if the backend supports the feature */
const defaultResponse = { const defaultResponse = {
genesets: [], genesets: [],
@@ -71,25 +89,31 @@ async function genesetsFetch(dispatch, config) {
} }
} }
function prefetchEmbeddings(annoMatrix) { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function prefetchEmbeddings(annoMatrix: any) {
/* /*
prefetch requests for all embeddings prefetch requests for all embeddings
*/ */
const { schema } = annoMatrix; const { schema } = annoMatrix;
const available = schema.layout.obs.map((v) => v.name); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
available.forEach((embName) => annoMatrix.prefetch("emb", embName)); const available = schema.layout.obs.map((v: any) => v.name);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
available.forEach((embName: any) => annoMatrix.prefetch("emb", embName));
} }
/* /*
Application bootstrap Application bootstrap
*/ */
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
const doInitialDataLoad = () => const doInitialDataLoad = () =>
catchErrorsWrap(async (dispatch) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
catchErrorsWrap(async (dispatch: any) => {
dispatch({ type: "initial data load start" }); dispatch({ type: "initial data load start" });
try { try {
const [config, schema] = await Promise.all([ const [config, schema] = await Promise.all([
configFetch(dispatch), configFetch(dispatch),
// @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1.
schemaFetch(dispatch), schemaFetch(dispatch),
userColorsFetchAndLoad(dispatch), userColorsFetchAndLoad(dispatch),
userInfoFetch(dispatch), userInfoFetch(dispatch),
@@ -113,7 +137,8 @@ const doInitialDataLoad = () =>
const layoutSchema = schema?.schema?.layout?.obs ?? []; const layoutSchema = schema?.schema?.layout?.obs ?? [];
if ( if (
defaultEmbedding && defaultEmbedding &&
layoutSchema.some((s) => s.name === defaultEmbedding) // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
layoutSchema.some((s: any) => s.name === defaultEmbedding)
) { ) {
dispatch(embActions.layoutChoiceAction(defaultEmbedding)); dispatch(embActions.layoutChoiceAction(defaultEmbedding));
} }
@@ -122,21 +147,25 @@ const doInitialDataLoad = () =>
} }
}, true); }, true);
function requestSingleGeneExpressionCountsForColoringPOST(gene) { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
function requestSingleGeneExpressionCountsForColoringPOST(gene: any) {
return { return {
type: "color by expression", type: "color by expression",
gene, gene,
}; };
} }
const requestUserDefinedGene = (gene) => ({ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
const requestUserDefinedGene = (gene: any) => ({
type: "request user defined gene success", type: "request user defined gene success",
data: { data: {
genes: [gene], genes: [gene],
}, },
}); });
const dispatchDiffExpErrors = (dispatch, response) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const dispatchDiffExpErrors = (dispatch: any, response: any) => {
switch (response.status) { switch (response.status) {
case 403: case 403:
dispatchNetworkErrorMessageToUser( dispatchNetworkErrorMessageToUser(
@@ -159,10 +188,14 @@ const dispatchDiffExpErrors = (dispatch, response) => {
} }
}; };
const requestDifferentialExpression = (set1, set2, num_genes = 50) => async ( const requestDifferentialExpression = (
dispatch, // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
getState set1: any,
) => { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
set2: any,
num_genes = 50
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
) => async (dispatch: any, getState: any) => {
dispatch({ type: "request differential expression started" }); dispatch({ type: "request differential expression started" });
try { try {
/* /*
@@ -210,7 +243,9 @@ const requestDifferentialExpression = (set1, set2, num_genes = 50) => async (
const varIndex = await annoMatrix.fetch("var", varIndexName); const varIndex = await annoMatrix.fetch("var", varIndexName);
const diffexpLists = { negative: [], positive: [] }; const diffexpLists = { negative: [], positive: [] };
for (const polarity of Object.keys(diffexpLists)) { for (const polarity of Object.keys(diffexpLists)) {
diffexpLists[polarity] = response[polarity].map((v) => [ // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
diffexpLists[polarity] = response[polarity].map((v: any) => [
varIndex.at(v[0], varIndexName), varIndex.at(v[0], varIndexName),
...v.slice(1), ...v.slice(1),
]); ]);
@@ -229,7 +264,8 @@ const requestDifferentialExpression = (set1, set2, num_genes = 50) => async (
} }
}; };
function fetchJson(pathAndQuery) { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function fetchJson(pathAndQuery: any) {
return doJsonRequest( return doJsonRequest(
`${globals.API.prefix}${globals.API.version}${pathAndQuery}` `${globals.API.prefix}${globals.API.version}${pathAndQuery}`
); );
-199
View File
@@ -1,199 +0,0 @@
/*
Action creators for selection
*/
export const selectContinuousMetadataAction = (
type,
query,
range,
oldProps = {}
) => async (dispatch, getState) => {
const { obsCrossfilter: prevObsCrossfilter } = getState();
const selection = range
? {
mode: "range",
lo: range[0],
hi: range[1],
inclusive: true, // [lo, hi] incluisve selection
}
: { mode: "all" };
const obsCrossfilter = await prevObsCrossfilter.select(...query, selection);
dispatch({
type,
obsCrossfilter,
range,
...oldProps,
});
};
export const selectCategoricalMetadataAction = (
type, // action type
metadataField, // annotation category name
labels,
label, // the label being selected/deselected
isSelected, // bool
oldProps = {}
) => async (dispatch, getState) => {
const {
obsCrossfilter: prevObsCrossfilter,
categoricalSelection,
} = getState();
const labelSelectionState = new Map(categoricalSelection[metadataField]);
labels.forEach(
(l) => labelSelectionState.has(l) || labelSelectionState.set(l, true)
);
labelSelectionState.set(label, isSelected);
const values = Array.from(labelSelectionState.keys()).filter((k) =>
labelSelectionState.get(k)
);
const selection = {
mode: "exact",
values,
};
const obsCrossfilter = await prevObsCrossfilter.select(
"obs",
metadataField,
selection
);
dispatch({
type,
obsCrossfilter,
metadataField,
labelSelectionState,
...oldProps,
});
};
export const selectCategoricalAllMetadataAction = (
type, // action type
metadataField, // annotation category name
labels,
isSelected, // bool, select all or none
oldProps = {}
) => async (dispatch, getState) => {
const {
obsCrossfilter: prevObsCrossfilter,
categoricalSelection,
} = getState();
const labelSelectionState = new Map(categoricalSelection[metadataField]);
labels.forEach((label) => labelSelectionState.set(label, isSelected));
const selection = { mode: isSelected ? "all" : "none" };
const obsCrossfilter = await prevObsCrossfilter.select(
"obs",
metadataField,
selection
);
dispatch({
type,
obsCrossfilter,
metadataField,
labelSelectionState,
...oldProps,
});
};
/**
** Graph selection-related actions
**/
export const graphBrushStartAction = () =>
/* no change to crossfilter until a change fires */
({ type: "graph brush start" });
const _graphBrushWithinRectAction = (type, embName, brushCoords) => async (
dispatch,
getState
) => {
const { obsCrossfilter: prevObsCrossfilter } = getState();
const selection = { mode: "within-rect", ...brushCoords };
const obsCrossfilter = await prevObsCrossfilter.select(
"emb",
embName,
selection
);
dispatch({
type,
obsCrossfilter,
brushCoords,
});
};
const _graphAllAction = (type, embName) => async (dispatch, getState) => {
const { obsCrossfilter: prevObsCrossfilter } = getState();
const obsCrossfilter = await prevObsCrossfilter.select("emb", embName, {
mode: "all",
});
dispatch({
type,
obsCrossfilter,
});
};
export const graphBrushChangeAction = (embName, brushCoords) =>
_graphBrushWithinRectAction("graph brush change", embName, brushCoords);
export const graphBrushEndAction = (embName, brushCoords) =>
_graphBrushWithinRectAction("graph brush end", embName, brushCoords);
export const graphBrushCancelAction = (embName) =>
_graphAllAction("graph brush cancel", embName);
export const graphBrushDeselectAction = (embName) =>
_graphAllAction("graph brush deselect", embName);
export const graphLassoStartAction = () =>
/* no change to crossfilter until a change fires */
({ type: "graph lasso start" });
export const graphLassoCancelAction = (embName) =>
_graphAllAction("graph lasso cancel", embName);
export const graphLassoDeselectAction = (embName) =>
_graphAllAction("graph lasso cancel", embName);
export const graphLassoEndAction = (embName, polygon) => async (
dispatch,
getState
) => {
const { obsCrossfilter: prevObsCrossfilter } = getState();
const selection = {
mode: "within-polygon",
polygon,
};
const obsCrossfilter = await prevObsCrossfilter.select(
"emb",
embName,
selection
);
dispatch({
type: "graph lasso end",
obsCrossfilter,
polygon,
});
};
/*
Differential expression set selection
*/
export const setCellSetFromSelection = (cellSetId) => (dispatch, getState) => {
const { obsCrossfilter } = getState();
const selected = obsCrossfilter.allSelectedLabels();
dispatch({
type: `store current cell selection as differential set ${cellSetId}`,
data: selected.length > 0 ? selected : null,
});
};
+248
View File
@@ -0,0 +1,248 @@
/*
Action creators for selection
*/
export const selectContinuousMetadataAction = (
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
type: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
query: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
range: any,
oldProps = {} // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
) => async (dispatch: any, getState: any) => {
const { obsCrossfilter: prevObsCrossfilter } = getState();
const selection = range
? {
mode: "range",
lo: range[0],
hi: range[1],
inclusive: true, // [lo, hi] incluisve selection
}
: { mode: "all" };
const obsCrossfilter = await prevObsCrossfilter.select(...query, selection);
dispatch({
type,
obsCrossfilter,
range,
...oldProps,
});
};
export const selectCategoricalMetadataAction = (
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
type: any, // action type
// annotation category name
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
metadataField: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
labels: any,
// the label being selected/deselected
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
label: any,
// bool
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
isSelected: any,
oldProps = {}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
) => async (dispatch: any, getState: any) => {
const {
obsCrossfilter: prevObsCrossfilter,
categoricalSelection,
} = getState();
const labelSelectionState = new Map(categoricalSelection[metadataField]);
labels.forEach(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(l: any) => labelSelectionState.has(l) || labelSelectionState.set(l, true)
);
labelSelectionState.set(label, isSelected);
const values = Array.from(labelSelectionState.keys()).filter((k) =>
labelSelectionState.get(k)
);
const selection = {
mode: "exact",
values,
};
const obsCrossfilter = await prevObsCrossfilter.select(
"obs",
metadataField,
selection
);
dispatch({
type,
obsCrossfilter,
metadataField,
labelSelectionState,
...oldProps,
});
};
export const selectCategoricalAllMetadataAction = (
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
type: any, // action type
// annotation category name
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
metadataField: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
labels: any,
// bool, select all or none
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
isSelected: any,
oldProps = {}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
) => async (dispatch: any, getState: any) => {
const {
obsCrossfilter: prevObsCrossfilter,
categoricalSelection,
} = getState();
const labelSelectionState = new Map(categoricalSelection[metadataField]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
labels.forEach((label: any) => labelSelectionState.set(label, isSelected));
const selection = { mode: isSelected ? "all" : "none" };
const obsCrossfilter = await prevObsCrossfilter.select(
"obs",
metadataField,
selection
);
dispatch({
type,
obsCrossfilter,
metadataField,
labelSelectionState,
...oldProps,
});
};
/**
** Graph selection-related actions
**/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
export const graphBrushStartAction = () =>
/* no change to crossfilter until a change fires */
({ type: "graph brush start" });
const _graphBrushWithinRectAction = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
type: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
embName: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
brushCoords: any
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
) => async (dispatch: any, getState: any) => {
const { obsCrossfilter: prevObsCrossfilter } = getState();
const selection = { mode: "within-rect", ...brushCoords };
const obsCrossfilter = await prevObsCrossfilter.select(
"emb",
embName,
selection
);
dispatch({
type,
obsCrossfilter,
brushCoords,
});
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const _graphAllAction = (type: any, embName: any) => async (
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
dispatch: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
getState: any
) => {
const { obsCrossfilter: prevObsCrossfilter } = getState();
const obsCrossfilter = await prevObsCrossfilter.select("emb", embName, {
mode: "all",
});
dispatch({
type,
obsCrossfilter,
});
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export const graphBrushChangeAction = (embName: any, brushCoords: any) =>
_graphBrushWithinRectAction("graph brush change", embName, brushCoords);
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export const graphBrushEndAction = (embName: any, brushCoords: any) =>
_graphBrushWithinRectAction("graph brush end", embName, brushCoords);
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export const graphBrushCancelAction = (embName: any) =>
_graphAllAction("graph brush cancel", embName);
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export const graphBrushDeselectAction = (embName: any) =>
_graphAllAction("graph brush deselect", embName);
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
export const graphLassoStartAction = () =>
/* no change to crossfilter until a change fires */
({ type: "graph lasso start" });
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export const graphLassoCancelAction = (embName: any) =>
_graphAllAction("graph lasso cancel", embName);
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export const graphLassoDeselectAction = (embName: any) =>
_graphAllAction("graph lasso cancel", embName);
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export const graphLassoEndAction = (embName: any, polygon: any) => async (
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
dispatch: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
getState: any
) => {
const { obsCrossfilter: prevObsCrossfilter } = getState();
const selection = {
mode: "within-polygon",
polygon,
};
const obsCrossfilter = await prevObsCrossfilter.select(
"emb",
embName,
selection
);
dispatch({
type: "graph lasso end",
obsCrossfilter,
polygon,
});
};
/*
Differential expression set selection
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export const setCellSetFromSelection = (cellSetId: any) => (
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
dispatch: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
getState: any
) => {
const { obsCrossfilter } = getState();
const selected = obsCrossfilter.allSelectedLabels();
dispatch({
type: `store current cell selection as differential set ${cellSetId}`,
data: selected.length > 0 ? selected : null,
});
};
@@ -18,7 +18,13 @@ import {
_userResetSubsetAnnoMatrix, _userResetSubsetAnnoMatrix,
} from "../util/stateManager/viewStackHelpers"; } from "../util/stateManager/viewStackHelpers";
export const clipAction = (min, max) => (dispatch, getState) => { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export const clipAction = (min: any, max: any) => (
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
dispatch: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
getState: any
) => {
/* /*
apply a clip to the current annoMatrix. By convention, the clip apply a clip to the current annoMatrix. By convention, the clip
view is ALWAYS the top view. view is ALWAYS the top view.
@@ -34,7 +40,8 @@ export const clipAction = (min, max) => (dispatch, getState) => {
}); });
}; };
export const subsetAction = () => (dispatch, getState) => { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export const subsetAction = () => (dispatch: any, getState: any) => {
/* /*
Subset the annoMatrix to the current crossfilter selection by pushing a Subset the annoMatrix to the current crossfilter selection by pushing a
subset view. subset view.
@@ -58,7 +65,8 @@ export const subsetAction = () => (dispatch, getState) => {
}); });
}; };
export const resetSubsetAction = () => (dispatch, getState) => { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export const resetSubsetAction = () => (dispatch: any, getState: any) => {
/* /*
Reset the annoMatrix to all data. Because we may have multiple views Reset the annoMatrix to all data. Because we may have multiple views
stacked, we pop them all. By convention, any clip transformation will stacked, we pop them all. By convention, any clip transformation will
@@ -17,6 +17,39 @@ import { _queryValidate, _queryCacheKey } from "./query";
const _dataframeCache = dataframeMemo(128); const _dataframeCache = dataframeMemo(128);
export default class AnnoMatrix { export default class AnnoMatrix {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
public isView: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
public nObs: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
public nVar: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
public rowIndex: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
public schema: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
public userFlags: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
public viewOf: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
protected _cache: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
private _pendingLoad: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
private _whereCache: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
private _gcInfo: any;
/* /*
Abstract base class for all AnnoMatrix objects. This class provides a proxy Abstract base class for all AnnoMatrix objects. This class provides a proxy
to the annotated matrix data authoritatively served by the server/back-end. to the annotated matrix data authoritatively served by the server/back-end.
@@ -47,6 +80,7 @@ export default class AnnoMatrix {
subset(annoMatrix, rowLabels) -> annoMatrix subset(annoMatrix, rowLabels) -> annoMatrix
etc. etc.
*/ */
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
static fields() { static fields() {
/* /*
return the fields present in the AnnoMatrix instance. return the fields present in the AnnoMatrix instance.
@@ -54,7 +88,8 @@ export default class AnnoMatrix {
return ["obs", "var", "emb", "X"]; return ["obs", "var", "emb", "X"];
} }
constructor(schema, nObs, nVar, rowIndex = null) { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
constructor(schema: any, nObs: any, nVar: any, rowIndex = null) {
/* /*
Private constructor - this is an abstract base class. Do not use. Private constructor - this is an abstract base class. Do not use.
*/ */
@@ -83,13 +118,13 @@ export default class AnnoMatrix {
this.userFlags = {}; this.userFlags = {};
/* /*
Private instance variables. Private instance variables.
These are caches - lazily loaded. The only guarantee is that if they These are caches - lazily loaded. The only guarantee is that if they
are loaded, they will conform to the schema & dimensionality constraints. are loaded, they will conform to the schema & dimensionality constraints.
Do NOT use directly - instead, use the fetch() and preload() API. Do NOT use directly - instead, use the fetch() and preload() API.
*/ */
this._cache = { this._cache = {
obs: Dataframe.empty(this.rowIndex), obs: Dataframe.empty(this.rowIndex),
var: Dataframe.empty(this.rowIndex), var: Dataframe.empty(this.rowIndex),
@@ -109,6 +144,8 @@ export default class AnnoMatrix {
/** /**
** Schema helper/accessors ** Schema helper/accessors
**/ **/
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
getMatrixColumns(field) { getMatrixColumns(field) {
/* /*
Return array of column names in the field. ONLY supported on the Return array of column names in the field. ONLY supported on the
@@ -121,7 +158,7 @@ export default class AnnoMatrix {
return _schemaColumns(this.schema, field); return _schemaColumns(this.schema, field);
} }
// eslint-disable-next-line class-methods-use-this -- need to be able to call this on instances // eslint-disable-next-line class-methods-use-this, @typescript-eslint/explicit-module-boundary-types -- need to be able to call this on instances
getMatrixFields() { getMatrixFields() {
/* /*
Return array of fields in this annoMatrix. Currently hard-wired to Return array of fields in this annoMatrix. Currently hard-wired to
@@ -132,6 +169,8 @@ export default class AnnoMatrix {
return AnnoMatrix.fields(); return AnnoMatrix.fields();
} }
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
getColumnSchema(field, col) { getColumnSchema(field, col) {
/* /*
Return the schema for the field & column ,eg, Return the schema for the field & column ,eg,
@@ -144,6 +183,8 @@ export default class AnnoMatrix {
return _getColumnSchema(this.schema, field, col); return _getColumnSchema(this.schema, field, col);
} }
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
getColumnDimensions(field, col) { getColumnDimensions(field, col) {
/* /*
Return the dimensions on this field / column. For most fields, which are 1D, Return the dimensions on this field / column. For most fields, which are 1D,
@@ -162,10 +203,12 @@ export default class AnnoMatrix {
/** /**
** General utility methods ** General utility methods
**/ **/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
base() { base() {
/* /*
return the base of view, or `this` if not a view. return the base of view, or `this` if not a view.
*/ */
// eslint-disable-next-line @typescript-eslint/no-this-alias --- FIXME: disabled temporarily on migrate to TS.
let annoMatrix = this; let annoMatrix = this;
while (annoMatrix.isView) annoMatrix = annoMatrix.viewOf; while (annoMatrix.isView) annoMatrix = annoMatrix.viewOf;
return annoMatrix; return annoMatrix;
@@ -174,6 +217,8 @@ export default class AnnoMatrix {
/** /**
** Load / read interfaces ** Load / read interfaces
**/ **/
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
fetch(field, q) { fetch(field, q) {
/* /*
Return the given query on a single matrix field as a single dataframe. Return the given query on a single matrix field as a single dataframe.
@@ -231,6 +276,8 @@ export default class AnnoMatrix {
return this._fetch(field, q); return this._fetch(field, q);
} }
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
prefetch(field, q) { prefetch(field, q) {
/* /*
Start a data fetch & cache fill. Identical to fetch() except it does Start a data fetch & cache fill. Identical to fetch() except it does
@@ -261,7 +308,8 @@ export default class AnnoMatrix {
** The actual implementation is in the sub-classes, which MUST override these. ** The actual implementation is in the sub-classes, which MUST override these.
**/ **/
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements // @ts-expect-error ts-migrate(6133) FIXME: 'col' is declared but its value is never read.
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements
addObsAnnoCategory(col, category) { addObsAnnoCategory(col, category) {
/* /*
Add a new category value (aka "label") to a writable obs column, and return the new AnnoMatrix. Add a new category value (aka "label") to a writable obs column, and return the new AnnoMatrix.
@@ -278,7 +326,8 @@ export default class AnnoMatrix {
_subclassResponsibility(); _subclassResponsibility();
} }
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements // @ts-expect-error ts-migrate(6133) FIXME: 'col' is declared but its value is never read.
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements
async removeObsAnnoCategory(col, category, unassignedCategory) { async removeObsAnnoCategory(col, category, unassignedCategory) {
/* /*
Remove a category value from an obs column, reassign any obs having that value Remove a category value from an obs column, reassign any obs having that value
@@ -299,7 +348,8 @@ export default class AnnoMatrix {
_subclassResponsibility(); _subclassResponsibility();
} }
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements // @ts-expect-error ts-migrate(6133) FIXME: 'col' is declared but its value is never read.
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements
dropObsColumn(col) { dropObsColumn(col) {
/* /*
Drop an entire writable column, eg a user-created obs annotation. Typical use Drop an entire writable column, eg a user-created obs annotation. Typical use
@@ -315,7 +365,8 @@ export default class AnnoMatrix {
_subclassResponsibility(); _subclassResponsibility();
} }
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements // @ts-expect-error ts-migrate(6133) FIXME: 'colSchema' is declared but its value is never rea... Remove this comment to see the full error message
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements
addObsColumn(colSchema, Ctor, value) { addObsColumn(colSchema, Ctor, value) {
/* /*
Add a new writable OBS annotation column, with the caller-specified schema, initial value Add a new writable OBS annotation column, with the caller-specified schema, initial value
@@ -344,7 +395,8 @@ export default class AnnoMatrix {
_subclassResponsibility(); _subclassResponsibility();
} }
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements // @ts-expect-error ts-migrate(6133) FIXME: 'oldCol' is declared but its value is never read.
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements
renameObsColumn(oldCol, newCol) { renameObsColumn(oldCol, newCol) {
/* /*
Rename the obs column 'oldCol' to have name 'newCol' and returns new AnnoMatrix. Rename the obs column 'oldCol' to have name 'newCol' and returns new AnnoMatrix.
@@ -359,7 +411,8 @@ export default class AnnoMatrix {
_subclassResponsibility(); _subclassResponsibility();
} }
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements // @ts-expect-error ts-migrate(6133) FIXME: 'col' is declared but its value is never read.
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements
async setObsColumnValues(col, obsLabels, value) { async setObsColumnValues(col, obsLabels, value) {
/* /*
Set all obs with label in array 'obsLabels' to have 'value'. Typical use would be Set all obs with label in array 'obsLabels' to have 'value'. Typical use would be
@@ -377,7 +430,8 @@ export default class AnnoMatrix {
_subclassResponsibility(); _subclassResponsibility();
} }
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements // @ts-expect-error ts-migrate(6133) FIXME: 'col' is declared but its value is never read.
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements
async resetObsColumnValues(col, oldValue, newValue) { async resetObsColumnValues(col, oldValue, newValue) {
/* /*
Set by value - all elements in the column with value 'oldValue' are set to 'newValue'. Set by value - all elements in the column with value 'oldValue' are set to 'newValue'.
@@ -394,7 +448,8 @@ export default class AnnoMatrix {
_subclassResponsibility(); _subclassResponsibility();
} }
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements // @ts-expect-error ts-migrate(6133) FIXME: 'colSchema' is declared but its value is never rea... Remove this comment to see the full error message
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements
addEmbedding(colSchema) { addEmbedding(colSchema) {
/* /*
Add a new obs embedding to the AnnoMatrix, with provided schema. Add a new obs embedding to the AnnoMatrix, with provided schema.
@@ -407,28 +462,38 @@ export default class AnnoMatrix {
_subclassResponsibility(); _subclassResponsibility();
} }
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
getCacheKeys(field, query) { getCacheKeys(field, query) {
/* /*
Return cache keys for columns associated with this query. May return Return cache keys for columns associated with this query. May return
[unknown] if no keys are known (ie, nothing is or was cached). [unknown] if no keys are known (ie, nothing is or was cached).
*/ */
return _whereCacheGet(this._whereCache, this.schema, field, query); return _whereCacheGet(this._whereCache, this.schema, field, query);
} }
/** /**
** Private interfaces below. ** Private interfaces below.
**/ **/
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
_resolveCachedQueries(field, queries) { _resolveCachedQueries(field, queries) {
return queries return (
.map((query) => queries
_whereCacheGet(this._whereCache, this.schema, field, query).filter( // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'query' implicitly has an 'any' type.
(cacheKey) => .map((query) =>
cacheKey !== undefined && this._cache[field].hasCol(cacheKey) _whereCacheGet(this._whereCache, this.schema, field, query).filter(
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'cacheKey' implicitly has an 'any' type.
(cacheKey) =>
cacheKey !== undefined && this._cache[field].hasCol(cacheKey)
)
) )
) .flat()
.flat(); );
} }
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
async _fetch(field, q) { async _fetch(field, q) {
if (!AnnoMatrix.fields().includes(field)) return undefined; if (!AnnoMatrix.fields().includes(field)) return undefined;
const queries = Array.isArray(q) ? q : [q]; const queries = Array.isArray(q) ? q : [q];
@@ -441,6 +506,7 @@ export default class AnnoMatrix {
/* find any query not already cached */ /* find any query not already cached */
const uncachedQueries = queries.filter((query) => const uncachedQueries = queries.filter((query) =>
_whereCacheGet(this._whereCache, this.schema, field, query).some( _whereCacheGet(this._whereCache, this.schema, field, query).some(
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'cacheKey' implicitly has an 'any' type.
(cacheKey) => (cacheKey) =>
cacheKey === undefined || !this._cache[field].hasCol(cacheKey) cacheKey === undefined || !this._cache[field].hasCol(cacheKey)
) )
@@ -450,8 +516,10 @@ export default class AnnoMatrix {
if (uncachedQueries.length > 0) { if (uncachedQueries.length > 0) {
await Promise.all( await Promise.all(
uncachedQueries.map((query) => uncachedQueries.map((query) =>
// @ts-expect-error ts-migrate(7006) FIXME: Parameter '_field' implicitly has an 'any' type.
this._getPendingLoad(field, query, async (_field, _query) => { this._getPendingLoad(field, query, async (_field, _query) => {
/* fetch, then index. _doLoad is subclass interface */ /* fetch, then index. _doLoad is subclass interface */
// @ts-expect-error ts-migrate(2488) FIXME: Type 'void' must have a '[Symbol.iterator]()' meth... Remove this comment to see the full error message
const [whereCacheUpdate, df] = await this._doLoad(_field, _query); const [whereCacheUpdate, df] = await this._doLoad(_field, _query);
this._cache[_field] = this._cache[_field].withColsFrom(df); this._cache[_field] = this._cache[_field].withColsFrom(df);
this._whereCache = _whereCacheMerge( this._whereCache = _whereCacheMerge(
@@ -472,6 +540,8 @@ export default class AnnoMatrix {
return response; return response;
} }
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
async _getPendingLoad(field, query, fetchFn) { async _getPendingLoad(field, query, fetchFn) {
/* /*
Given a query on a field, ensure that we only have a single outstanding Given a query on a field, ensure that we only have a single outstanding
@@ -493,7 +563,7 @@ export default class AnnoMatrix {
return this._pendingLoad[field][key]; return this._pendingLoad[field][key];
} }
// eslint-disable-next-line class-methods-use-this -- make sure subclass implements // eslint-disable-next-line class-methods-use-this, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements
async _doLoad() { async _doLoad() {
_subclassResponsibility(); _subclassResponsibility();
} }
@@ -527,19 +597,22 @@ export default class AnnoMatrix {
To be effective, the GC callback needs to be invoked from the undo/redo code, To be effective, the GC callback needs to be invoked from the undo/redo code,
as much of the cache is pinned by that data structure. as much of the cache is pinned by that data structure.
*/ */
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
_gcField(field, isHot, pinnedColumns) { _gcField(field, isHot, pinnedColumns) {
const maxColumns = isHot ? 256 : 10; // maybe to aggressive? const maxColumns = isHot ? 256 : 10;
const cache = this._cache[field]; const cache = this._cache[field];
if (cache.colIndex.size() < maxColumns) return; // trivial rejection if (cache.colIndex.size() < maxColumns) return; // trivial rejection
const candidates = cache.colIndex const candidates = cache.colIndex
.labels() .labels()
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'col' implicitly has an 'any' type.
.filter((col) => !pinnedColumns.includes(col)); .filter((col) => !pinnedColumns.includes(col));
const excessCount = candidates.length + pinnedColumns.length - maxColumns; const excessCount = candidates.length + pinnedColumns.length - maxColumns;
if (excessCount > 0) { if (excessCount > 0) {
const { _gcInfo } = this; const { _gcInfo } = this;
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'a' implicitly has an 'any' type.
candidates.sort((a, b) => { candidates.sort((a, b) => {
let atime = _gcInfo.get(_columnCacheKey(field, a)); let atime = _gcInfo.get(_columnCacheKey(field, a));
if (atime === undefined) atime = 0; if (atime === undefined) atime = 0;
@@ -558,13 +631,17 @@ export default class AnnoMatrix {
// )}]` // )}]`
// ); // );
this._cache[field] = toDrop.reduce( this._cache[field] = toDrop.reduce(
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'df' implicitly has an 'any' type.
(df, col) => df.dropCol(col), (df, col) => df.dropCol(col),
this._cache[field] this._cache[field]
); );
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'col' implicitly has an 'any' type.
toDrop.forEach((col) => _gcInfo.delete(_columnCacheKey(field, col))); toDrop.forEach((col) => _gcInfo.delete(_columnCacheKey(field, col)));
} }
} }
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
_gcFetchCleanup(field, pinnedColumns) { _gcFetchCleanup(field, pinnedColumns) {
/* /*
Called during data load/fetch. By definition, this is 'hot', so we Called during data load/fetch. By definition, this is 'hot', so we
@@ -579,6 +656,8 @@ export default class AnnoMatrix {
} }
} }
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'hints' implicitly has an 'any' type.
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
_gc(hints) { _gc(hints) {
/* /*
Called from middleware, or elsewhere. isHot is true if we are in the active store, Called from middleware, or elsewhere. isHot is true if we are in the active store,
@@ -591,6 +670,8 @@ export default class AnnoMatrix {
); );
} }
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
_gcUpdateStats(field, dataframe) { _gcUpdateStats(field, dataframe) {
/* /*
called each time a query is performed, allowing the gc to update any bookkeeping called each time a query is performed, allowing the gc to update any bookkeeping
@@ -600,6 +681,7 @@ export default class AnnoMatrix {
const cols = dataframe.colIndex.labels(); const cols = dataframe.colIndex.labels();
const { _gcInfo } = this; const { _gcInfo } = this;
const now = Date.now(); const now = Date.now();
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'c' implicitly has an 'any' type.
cols.forEach((c) => { cols.forEach((c) => {
_gcInfo.set(_columnCacheKey(field, c), now); _gcInfo.set(_columnCacheKey(field, c), now);
}); });
@@ -617,6 +699,8 @@ export default class AnnoMatrix {
Do not override _clone(); Do not override _clone();
**/ **/
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'clone' implicitly has an 'any' type.
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
_cloneDeeper(clone) { _cloneDeeper(clone) {
clone._cache = _shallowClone(this._cache); clone._cache = _shallowClone(this._cache);
clone._gcInfo = new Map(); clone._gcInfo = new Map();
@@ -629,6 +713,7 @@ export default class AnnoMatrix {
return clone; return clone;
} }
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
_clone() { _clone() {
const clone = _shallowClone(this); const clone = _shallowClone(this);
this._cloneDeeper(clone); this._cloneDeeper(clone);
@@ -640,6 +725,7 @@ export default class AnnoMatrix {
/* /*
private utility functions below private utility functions below
*/ */
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
function _columnCacheKey(field, column) { function _columnCacheKey(field, column) {
return `${field}/${column}`; return `${field}/${column}`;
} }

Some files were not shown because too many files have changed in this diff Show More