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

View File

@@ -28,8 +28,8 @@ jobs:
continue-on-error: true
strategy:
matrix:
python-version: [3.6, 3.7] # As of Oct 2020 Anndata is not compatible with 3.8
anndata-version: [0.7.0, 0.7.1, 0.7.2, 0.7.3, 0.7.4, 0.7.5]
python-version: [3.6, 3.7, 3.8]
anndata-version: [0.7.6]
test-suite: [smoke-test, smoke-test-annotations]
steps:
- uses: actions/checkout@v2
@@ -47,8 +47,6 @@ jobs:
make pydist install-dist
# 3. install anndata
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
run: make unit-test ${{ matrix.test-suite }}

View File

@@ -40,7 +40,7 @@ jobs:
- name: Lint src with eslint
working-directory: ./client
run: |
make lint
npx eslint src __tests__
unit-test:
runs-on: ubuntu-latest

27
.github/workflows/sastisfaction.yml vendored Normal file
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 }}

View File

@@ -1,6 +1,6 @@
import numpy as np
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):
@@ -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 ]}
"""
X_approx_distribution = adaptor.get_X_approx_distribution()
X_approximate_distribution = adaptor.get_X_approximate_distribution()
dataA = adaptor.get_X_array(maskA, None)
dataB = adaptor.get_X_array(maskB, None)
# mean, variance, N - calculate for both selections
meanA, vA, nA = mean_var_n(dataA, X_approx_distribution)
meanB, vB, nB = mean_var_n(dataB, X_approx_distribution)
meanA, vA, nA = mean_var_n(dataA, X_approximate_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)
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
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
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):
n = X.shape[0]
if sparse.issparse(X):
if X_approx_distribution == XApproxDistribution.COUNT:
if X_approximate_distribution == XApproximateDistribution.COUNT:
X = X.log1p()
mean = X.mean(axis=0).A1
dfm = X - mean
sumsq = np.sum(np.multiply(dfm, dfm), axis=0).A1
v = sumsq / (n - 1)
else:
if X_approx_distribution == XApproxDistribution.COUNT:
if X_approximate_distribution == XApproximateDistribution.COUNT:
X = np.log1p(X)
mean = X.mean(axis=0)
dfm = X - mean

View File

@@ -2,34 +2,52 @@ import numba
import concurrent.futures
import numpy as np
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)
def min_max(arr):
@numba.njit(error_model="numpy", nogil=True)
def min_max(arr: np.ndarray):
"""Return (min, max) values for the ndarray."""
n = arr.size
odd = n % 2
if not odd:
n -= 1
max_val = min_val = arr[0]
i = 1
while i < n:
# initialize to first finite value in array. Normally,
# this will exit on the first value.
for i in range(arr.size):
min_val = max_val = arr[i]
if np.isfinite(min_val):
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]
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:
x, y = y, x
min_val = min(x, min_val)
max_val = max(y, max_val)
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)
max_val = max(x, 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.
@@ -38,6 +56,13 @@ def estimate_approximate_distribution(X) -> XApproxDistribution:
any (max-min) range in excess of 24 is implies tens of millions of
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):
Xdata = X.data
elif type(X) is np.ndarray:
@@ -45,7 +70,7 @@ def estimate_approximate_distribution(X) -> XApproxDistribution:
X.size,
)
else:
raise TypeError(f"Unsupported matrix type: {str(type(X))}")
raise TypeError(f"Unsupported matrix format: {str(type(X))}")
CHUNKSIZE = 1 << 24
if Xdata.size > CHUNKSIZE:
@@ -59,4 +84,4 @@ def estimate_approximate_distribution(X) -> XApproxDistribution:
min_val, max_val = min_max(Xdata)
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

View File

@@ -24,7 +24,7 @@ class DiffExpMode(AugmentedEnum):
VAR_FILTER = "varFilter"
class XApproxDistribution(AugmentedEnum):
class XApproximateDistribution(AugmentedEnum):
NORMAL = "normal"
COUNT = "count"

View File

@@ -5,6 +5,8 @@ import pandas as pd
from flatbuffers import Builder
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.Float32Array as Float32Array
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.Uint32Array as Uint32Array
# Serialization helper
def serialize_column(builder, typed_arr):
""" Serialize NetEncoding.Column """
@@ -84,7 +87,7 @@ def serialize_typed_array(builder, source_array, encoding_info):
def column_encoding(arr):
column_encoding_type_map = {
# 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.float16).str: (TypedArray.TypedArray.Float32Array, np.float32),
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")
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):
@@ -198,7 +202,7 @@ def deserialize_typed_array(tarr):
arr.Init(u.Bytes, u.Pos)
narr = arr.DataAsNumpy()
if u_type == TypedArray.TypedArray.JSONEncodedArray:
narr = json.loads(narr.tostring().decode("utf-8"))
narr = json.loads(narr.tobytes().decode("utf-8"))
return narr

View File

@@ -1,8 +1,42 @@
from typing import Union, Tuple
import logging
import numpy as np
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):
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
def get_dtype_of_array(array: pd.Series):
return get_dtype_and_schema_of_array(array)[0]
def get_encoding_dtype_of_array(array: Union[np.ndarray, pd.Series, pd.Index]) -> np.dtype:
return _get_type_info(array)[0]
def get_schema_type_hint_of_array(array: pd.Series):
return get_dtype_and_schema_of_array(array)[1]
def get_schema_type_hint_of_array(array: Union[np.ndarray, pd.Series, pd.Index]) -> dict:
return _get_type_info(array)[1]
def get_dtype_and_schema_of_array(array: pd.Series):
return (
get_dtype_from_dtype(array.dtype, array_values=array),
get_schema_type_hint_from_dtype(array.dtype, array_values=array),
)
def get_dtype_and_schema_of_array(array: Union[np.ndarray, pd.Series, pd.Index]) -> Tuple[np.dtype, dict]:
"""Return tuple (encoding_dtype, schema_type_hint)"""
return _get_type_info(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
for 64 bit values which will get downcast to 32 bit.
Best-effort to determine encoding type and schema hint from a dtype.
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
dtype_kind = dtype.kind
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 == "U":
return (np.dtype(str), {"type": "string"})
if dtype.kind in ["i", "u"]:
if np.can_cast(dtype, np.int32):
return True
ii32 = np.iinfo(np.int32)
if (
not ordered_array_values.empty
and (ordered_array_values.min() >= ii32.min and ordered_array_values.max() <= ii32.max)
or ordered_array_values.empty
):
return True
return (np.int32, {"type": "int32"})
if dtype.kind == "f":
_float64_warning(dtype)
return (np.float32, {"type": "float32"})
if dtype.kind == "O" and not dtype.name == "category":
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
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):
"""convert a string to value with the most appropriate type"""
if value.lower() == "true":

View File

@@ -10,7 +10,7 @@ from flask import current_app
from backend.czi_hosted.common.annotations.annotations import Annotations
from backend.common.errors import AnnotationCategoryNameError
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
@@ -143,7 +143,7 @@ class AnnotationsHostedTileDB(Annotations):
# convert to tiledb datatypes
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)
else:
uri = ""

View File

@@ -44,7 +44,7 @@ class DatasetConfig(BaseConfig):
self.diffexp__lfc_cutoff = default_config["diffexp"]["lfc_cutoff"]
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:
raise ConfigurationError(f"Unexpected config: {str(e)}")
@@ -60,7 +60,7 @@ class DatasetConfig(BaseConfig):
self.handle_user_annotations(context)
self.handle_embeddings()
self.handle_diffexp(context)
self.handle_X_approx_distribution()
self.handle_X_approximate_distribution()
def handle_app(self):
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."
)
def handle_X_approx_distribution(self):
self.validate_correct_type_of_configuration_attribute("X_approx_distribution", str)
if self.X_approx_distribution not in ["normal", "count"]:
def handle_X_approximate_distribution(self):
self.validate_correct_type_of_configuration_attribute("X_approximate_distribution", str)
if self.X_approximate_distribution not in ["normal", "count"]:
raise ConfigurationError(
"X_approx_distribution has unknown value -- must be 'normal' or 'count'."
"X_approximate_distribution has unknown value -- must be 'normal' or 'count'."
)

View File

@@ -3,7 +3,7 @@ import json
import numpy as np
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"):
@@ -47,7 +47,7 @@ def convert_dataframe_to_cxg_array(cxg_container, dataframe_name, dataframe, ind
]
)
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
]
domain = tiledb.Domain(

View File

@@ -8,7 +8,7 @@ from scipy import sparse
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.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.common.errors import PrepareError, DatasetAccessError, ConfigurationError
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):
super().__init__(data_locator, app_config, dataset_config)
self.data = None
self.X_approx_distribution = None
self.X_approximate_distribution = None
self._load_data(data_locator)
self._validate_and_initialize()
@@ -66,11 +66,11 @@ class AnndataAdaptor(DataAdaptor):
@staticmethod
def _create_unique_column_name(df, col_name_prefix):
""" 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`
"""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`
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, ...).
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, ...).
"""
suffix = 0
while f"{col_name_prefix}{suffix}" in df:
@@ -124,7 +124,11 @@ class AnndataAdaptor(DataAdaptor):
def _create_schema(self):
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": {
"obs": {"index": self.parameters.get("obs_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._create_schema()
if self.dataset_config.X_approx_distribution == "auto":
raise ConfigurationError("X-approx-distribution 'auto' mode unsupported.")
self.X_approx_distribution = self.dataset_config.X_approx_distribution
if self.dataset_config.X_approximate_distribution == "auto":
raise ConfigurationError("X-approximate-distribution 'auto' mode unsupported.")
self.X_approximate_distribution = self.dataset_config.X_approximate_distribution
# heuristic
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})
def _is_valid_layout(self, arr):
""" return True if this layout data is a valid array for front-end presentation:
* ndarray, dtype float/int/uint
* with shape (n_obs, >= 2)
* with all values finite or NaN (no +Inf or -Inf)
"""return True if this layout data is a valid array for front-end presentation:
* ndarray, dtype float/int/uint
* with shape (n_obs, >= 2)
* with all values finite or NaN (no +Inf or -Inf)
"""
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
@@ -327,8 +331,8 @@ class AnndataAdaptor(DataAdaptor):
X = self.data.X[obs_mask, var_mask]
return X
def get_X_approx_distribution(self) -> XApproxDistribution:
return self.X_approx_distribution
def get_X_approximate_distribution(self) -> XApproximateDistribution:
return self.X_approximate_distribution
def get_shape(self):
return self.data.shape

View File

@@ -7,7 +7,7 @@ from scipy import sparse
from server_timing import Timing as ServerTiming
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 (
FilterError,
JSONEncodingValueError,
@@ -84,7 +84,7 @@ class DataAdaptor(metaclass=ABCMeta):
pass
@abstractmethod
def get_X_approx_distribution(self) -> XApproxDistribution:
def get_X_approximate_distribution(self) -> XApproximateDistribution:
"""return the approximate distribution of the X matrix."""
pass

View File

@@ -8,7 +8,7 @@ import pandas as pd
import tiledb
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.czi_hosted.common.immutable_kvcache import ImmutableKVCache
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.arrays = ImmutableKVCache(lambda key: self._open_array(uri=key, tiledb_ctx=self.tiledb_ctx))
self.schema = None
self.X_approx_distribution = None
self.X_approximate_distribution = None
self._validate_and_initialize()
@@ -176,9 +176,9 @@ class CxgAdaptor(DataAdaptor):
if cxg_version not in ["0.0", "0.1", "0.2.0"]:
raise DatasetAccessError(f"cxg matrix is not valid: {self.url}")
if self.dataset_config.X_approx_distribution == "auto":
raise ConfigurationError("X-approx-distribution 'auto' mode unsupported.")
self.X_approx_distribution = self.dataset_config.X_approx_distribution
if self.dataset_config.X_approximate_distribution == "auto":
raise ConfigurationError("X-approximate-distribution 'auto' mode unsupported.")
self.X_approximate_distribution = self.dataset_config.X_approximate_distribution
self.title = title
self.about = about
@@ -210,7 +210,8 @@ class CxgAdaptor(DataAdaptor):
if lfc_cutoff is None:
lfc_cutoff = self.dataset_config.diffexp__lfc_cutoff
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):
if self.cxg_version == "0.0":
@@ -286,8 +287,8 @@ class CxgAdaptor(DataAdaptor):
data = X.multi_index[obs_items, var_items][""]
return data
def get_X_approx_distribution(self) -> XApproxDistribution:
return self.X_approx_distribution
def get_X_approximate_distribution(self) -> XApproximateDistribution:
return self.X_approximate_distribution
def get_shape(self):
X = self.open_array("X")
@@ -357,7 +358,7 @@ class CxgAdaptor(DataAdaptor):
shape = self.get_shape()
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 = {}
for ax in ("obs", "var"):

View File

@@ -203,7 +203,7 @@ dataset:
lfc_cutoff: 0.01
top_n: 10
X_approx_distribution: normal # currently fixed config
X_approximate_distribution: normal # currently fixed config
external:
# You can retrieve configuration parameters from this config file, the environment,

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
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-Compress>=1.4.0
Flask-Cors>=3.0.6
Flask-Cors>=3.0.9 # CVE-2020-25032
Flask-RESTful>=0.3.6
flask-server-timing>=0.1.2
flask-talisman>=0.7.0
@@ -12,12 +12,12 @@ flatten-dict>=0.2.0
fsspec>=0.4.4,<0.8.0
gunicorn>=20.0.4
h5py>=3.0.0
numba>=0.49.1,<0.53.0
numpy>=1.15.0
numba>=0.51.2
numpy>=1.17.5
packaging>=20.0
pandas>=1.0,!=1.1 # pandas 1.1 breaks tests, https://github.com/pandas-dev/pandas/issues/35446
PyYAML>=5.3
scipy>=1.0
PyYAML>=5.4 # CVE-2020-14343
scipy>=1.4
requests>=2.22.0
tiledb>=0.5.9,>=0.6.2,!=0.7.2, !=0.8.6
s3fs==0.4.2

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).",
)
@click.option(
"--X-approx-distribution",
default=DEFAULT_CONFIG.dataset_config.X_approx_distribution,
"--X-approximate-distribution",
default=DEFAULT_CONFIG.dataset_config.X_approximate_distribution,
show_default=True,
type=click.Choice(["auto", "normal", "count"], case_sensitive=False),
help="Specify the approximate distribution of X matrix values. 'auto' will use a heuristic "
@@ -326,7 +326,7 @@ def launch(
disable_diffexp,
config_file,
dump_default_config,
x_approx_distribution,
x_approximate_distribution,
):
"""Launch the cellxgene data viewer.
This web app lets you explore single-cell expression data.
@@ -385,7 +385,7 @@ def launch(
embeddings__names=embedding,
diffexp__enable=not disable_diffexp,
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()

View File

@@ -38,7 +38,7 @@ class DatasetConfig(BaseConfig):
self.diffexp__lfc_cutoff = default_config["diffexp"]["lfc_cutoff"]
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:
raise ConfigurationError(f"Unexpected config: {str(e)}")
@@ -52,7 +52,7 @@ class DatasetConfig(BaseConfig):
self.handle_user_annotations(context)
self.handle_embeddings()
self.handle_diffexp(context)
self.handle_X_approx_distribution()
self.handle_X_approximate_distribution()
def get_data_adaptor(self):
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."
)
def handle_X_approx_distribution(self):
self.validate_correct_type_of_configuration_attribute("X_approx_distribution", str)
if self.X_approx_distribution not in ["auto", "normal", "count"]:
def handle_X_approximate_distribution(self):
self.validate_correct_type_of_configuration_attribute("X_approximate_distribution", str)
if self.X_approximate_distribution not in ["auto", "normal", "count"]:
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'."
)

View File

@@ -9,7 +9,7 @@ from scipy import sparse
import backend.common.compute.diffexp_generic as diffexp_generic
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.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.common.errors import PrepareError, DatasetAccessError
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):
super().__init__(data_locator, app_config, dataset_config)
self.data = None
self.X_approx_distribution = None
self.X_approximate_distribution = None
self._load_data(data_locator)
self._validate_and_initialize()
@@ -125,7 +125,11 @@ class AnndataAdaptor(DataAdaptor):
def _create_schema(self):
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": {
"obs": {"index": self.parameters.get("obs_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._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."""
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:
self.X_approx_distribution = self.dataset_config.X_approx_distribution
self.X_approximate_distribution = self.dataset_config.X_approximate_distribution
# heuristic
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]
return X
def get_X_approx_distribution(self) -> XApproxDistribution:
def get_X_approximate_distribution(self) -> XApproximateDistribution:
"""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."""
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.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):
return self.data.shape

View File

@@ -6,7 +6,7 @@ from scipy import sparse
from server_timing import Timing as ServerTiming
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.utils.utils import jsonify_numpy
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."""
pass
def get_X_approx_distribution(self) -> XApproxDistribution:
def get_X_approximate_distribution(self) -> XApproximateDistribution:
"""return the approximate distribution of the X matrix."""
return XApproxDistribution.NORMAL
return XApproximateDistribution.NORMAL
@abstractmethod
def get_shape(self):

View File

@@ -77,7 +77,7 @@ dataset:
lfc_cutoff: 0.01
top_n: 10
X_approx_distribution: auto
X_approximate_distribution: auto
external:
# You can retrieve configuration parameters from this config file, the environment,

View File

@@ -31,5 +31,5 @@ dataset:
lfc_cutoff: {lfc_cutoff}
top_n: {top_n}
X_approx_distribution: {X_approx_distribution}
X_approximate_distribution: {X_approximate_distribution}
"""

View File

@@ -28,5 +28,5 @@ dataset:
lfc_cutoff: {lfc_cutoff}
top_n: {top_n}
X_approx_distribution: {X_approx_distribution}
X_approximate_distribution: {X_approximate_distribution}
"""

View File

@@ -16,6 +16,6 @@ summary test,,F5,
summary test,,PIGU,
geneset_to_delete,,,
geneset_to_edit,,,
fill_this_geneset,,RER1,
fill_this_geneset,,,
empty_this_geneset,,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,

BIN
backend/test/fixtures/pbmc3k_64.h5ad vendored Normal file

Binary file not shown.

View File

@@ -2,16 +2,20 @@ import unittest
import pandas as pd
import numpy as np
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.utils.type_conversion_utils import get_dtypes_and_schemas_of_dataframe
import backend.common.fbs as fbs
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):
""" test various boundary checks """
"""test various boundary checks"""
# row indexing is unsupported
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"),
}
)
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)
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]))
else:
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)

View File

@@ -1,179 +1,22 @@
import unittest
from time import time
from unittest.mock import patch
import logging
from parameterized import parameterized_class
import numpy as np
import pandas as pd
from pandas import Series, DataFrame
from scipy import sparse
from backend.common.utils.type_conversion_utils import (
can_cast_to_float32,
can_cast_to_int32,
get_dtype_of_array,
get_encoding_dtype_of_array,
get_schema_type_hint_of_array,
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):
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):
float_array = Series(data=[1, 2, 3], dtype=np.dtype(np.float64))
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_schema_type_hints_dict, actual_dataframe_schema_type_hints)
def test__convert_pandas_series_to_numpy__categorical_float64_to_float64_with_nans(self):
expected_float_array = np.array([1.1, 2.2, np.NaN], dtype=np.float64)
float_series = Series(data=[1.1, 2.2, np.NaN], dtype="category")
def test__get_schema_type_hint_from_dtype(self):
self.assertEqual(get_schema_type_hint_from_dtype(np.dtype(np.bool_)), {"type": "boolean"})
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):
expected_float_array = np.array([1.1, 2.2], dtype=np.float64)
float_series = Series(data=[1.1, 2.2], dtype=np.dtype(np.float64))
for dtype in [np.dtype(object), np.dtype(str)]:
self.assertEqual(get_schema_type_hint_from_dtype(dtype), {"type": "string"})
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):
int_series = Series(data=[1, 2, np.NaN], dtype="category")
class AssertNoLogsContext(unittest.TestCase):
def __init__(self, logger, level):
self.logger = logger
self.level = level
self.context = self.assertLogs(logger, level)
with self.assertLogs(level="ERROR") as logger:
convert_pandas_series_to_numpy(int_series, np.int32)
def __enter__(self):
"""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(
"Cannot convert a pandas Series object to an integer dtype if it contains NaNs", logger.output[0]
)
def __exit__(self, exc_type, exc_val, exc_tb):
"""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)

View File

@@ -131,7 +131,7 @@ class ConfigTests(BaseTest):
environment=None,
aws_secrets_manager_region=None,
aws_secrets_manager_secrets=[],
X_approx_distribution="normal",
X_approximate_distribution="normal",
config_file_name="app_config.yml",
):
random_num = random.randrange(999999)
@@ -195,7 +195,7 @@ class ConfigTests(BaseTest):
enable_difexp=enable_difexp,
lfc_cutoff=lfc_cutoff,
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",
)
external_config = self.custom_external_config(
@@ -231,7 +231,7 @@ class ConfigTests(BaseTest):
enable_difexp="true",
lfc_cutoff=0.01,
top_n=10,
X_approx_distribution="normal",
X_approximate_distribution="normal",
config_file_name="dataset_config.yml",
):
configfile = os.path.join(self.tmp_fixtures_directory, config_file_name)

View File

@@ -444,7 +444,7 @@ class EndPointsCxg(EndPoints):
{'genes': [], 'geneset_description': '', 'geneset_name': 'geneset_to_delete'},
{'genes': [], 'geneset_description': '', 'geneset_name': 'geneset_to_edit'},
{
'genes': [{'gene_description': '', 'gene_symbol': 'RER1'}],
'genes': [],
'geneset_description': '',
'geneset_name': 'fill_this_geneset'
},
@@ -485,7 +485,7 @@ summary test,,F5,\r
summary test,,PIGU,\r
geneset_to_delete,,,\r
geneset_to_edit,,,\r
fill_this_geneset,,RER1,\r
fill_this_geneset,,,\r
empty_this_geneset,,SIK1,\r
brush_this_gene,,SIK1,\r
"""

View File

@@ -20,7 +20,7 @@ class DiffExpTest(unittest.TestCase):
adaptor types and different algorithms."""
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)
loader = MatrixDataLoader(path)
adaptor = loader.open(config)

View File

@@ -46,7 +46,7 @@ class FbsTests(unittest.TestCase):
"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)
self.fbs_checks(fbs, (10, 4), expected_types, ["a", "b", "c", "d"])

View File

@@ -103,7 +103,7 @@ class ConfigTests(unittest.TestCase):
environment=None,
aws_secrets_manager_region=None,
aws_secrets_manager_secrets=[],
X_approx_distribution="auto",
X_approximate_distribution="auto",
config_file_name="app_config.yml",
):
random_num = random.randrange(999999)
@@ -151,7 +151,7 @@ class ConfigTests(unittest.TestCase):
enable_difexp=enable_difexp,
lfc_cutoff=lfc_cutoff,
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",
)
external_config = self.custom_external_config(
@@ -187,7 +187,7 @@ class ConfigTests(unittest.TestCase):
enable_difexp="true",
lfc_cutoff=0.01,
top_n=10,
X_approx_distribution="auto",
X_approximate_distribution="auto",
config_file_name="dataset_config.yml",
):
configfile = os.path.join(self.tmp_fixtures_directory, config_file_name)

View File

@@ -6,9 +6,13 @@ from http import HTTPStatus
import tempfile
from os import path
import hashlib
from os.path import basename, splitext
import pandas as pd
import requests
import numpy as np
from parameterized import parameterized_class
import backend.test.decode_fbs as decode_fbs
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
)
# 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):
endpoint = "config"
url = f"{self.URL_BASE}{endpoint}"
@@ -52,7 +64,12 @@ class EndPoints(object):
self.assertEqual(result.headers["Content-Type"], "application/json")
result_data = result.json()
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"])
def test_get_layout_fbs(self):
@@ -72,6 +89,8 @@ class EndPoints(object):
)
self.assertIsNone(df["row_idx"])
self.assertEqual(len(df["columns"]), df["n_cols"])
for column in df["columns"]:
self.assertEqual(column.dtype, np.float32)
def test_bad_filter(self):
endpoint = "data/var"
@@ -98,6 +117,9 @@ class EndPoints(object):
[obs_index_col_name, "n_genes", "percent_mito", "n_counts", "louvain"]
+ (["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):
endpoint = "annotations/obs"
@@ -137,6 +159,9 @@ class EndPoints(object):
self.assertEqual(len(df["columns"]), df["n_cols"])
var_index_col_name = self.schema["schema"]["annotations"]["var"]["index"]
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):
endpoint = "annotations/var"
@@ -207,6 +232,9 @@ class EndPoints(object):
self.assertIsNone(df["row_idx"])
self.assertEqual(len(df["columns"]), df["n_cols"])
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):
index_col_name = self.schema["schema"]["annotations"]["var"]["index"]
@@ -220,6 +248,9 @@ class EndPoints(object):
df = decode_fbs.decode_matrix_FBS(result.content)
self.assertEqual(df["n_rows"], 2638)
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):
index_col_name = self.schema["schema"]["annotations"]["var"]["index"]
@@ -246,6 +277,9 @@ class EndPoints(object):
df = decode_fbs.decode_matrix_FBS(result.content)
self.assertEqual(df["n_rows"], 2638)
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):
endpoint = "colors"
@@ -347,6 +381,14 @@ class EndPointsAnnotations(EndPoints):
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):
"""Test Case for endpoints"""
@@ -355,10 +397,13 @@ class EndPointsAnndata(unittest.TestCase, EndPoints):
@classmethod
def setUpClass(cls):
if cls == EndPointsAnndata:
raise unittest.SkipTest("`parameterized_class` bug")
cls._setupClass(
cls,
[
f"{PROJECT_ROOT}/example-dataset/pbmc3k.h5ad",
cls.data_locator,
"--disable-annotations",
"--disable-gene-sets-save",
],
@@ -385,8 +430,8 @@ class EndPointsAnndata(unittest.TestCase, EndPoints):
self.assertEqual(result.status_code, HTTPStatus.OK)
self.assertEqual(result.headers["Content-Type"], "application/json")
result_data = result.json()
self.assertEqual(len(result_data['positive']), 7)
self.assertEqual(len(result_data['negative']), 7)
self.assertEqual(len(result_data["positive"]), 7)
self.assertEqual(len(result_data["negative"]), 7)
def test_diff_exp_indices(self):
endpoint = "diffexp/obs"
@@ -401,8 +446,8 @@ class EndPointsAnndata(unittest.TestCase, EndPoints):
self.assertEqual(result.status_code, HTTPStatus.OK)
self.assertEqual(result.headers["Content-Type"], "application/json")
result_data = result.json()
self.assertEqual(len(result_data['positive']), 10)
self.assertEqual(len(result_data['negative']), 10)
self.assertEqual(len(result_data["positive"]), 10)
self.assertEqual(len(result_data["negative"]), 10)
def test_get_summaryvar(self):
index_col_name = self.schema["schema"]["annotations"]["var"]["index"]
@@ -561,23 +606,23 @@ class EndPointsAnnDataGenesets(unittest.TestCase, EndPoints):
"geneset_description": "",
"geneset_name": "summary test",
},
{'genes': [], 'geneset_description': '', 'geneset_name': 'geneset_to_delete'},
{'genes': [], 'geneset_description': '', 'geneset_name': 'geneset_to_edit'},
{"genes": [], "geneset_description": "", "geneset_name": "geneset_to_delete"},
{"genes": [], "geneset_description": "", "geneset_name": "geneset_to_edit"},
{
'genes': [{'gene_description': '', 'gene_symbol': 'RER1'}],
'geneset_description': '',
'geneset_name': 'fill_this_geneset'
"genes": [],
"geneset_description": "",
"geneset_name": "fill_this_geneset",
},
{
'genes': [{'gene_description': '', 'gene_symbol': 'SIK1'}],
'geneset_description': '',
'geneset_name': 'empty_this_geneset'
"genes": [{"gene_description": "", "gene_symbol": "SIK1"}],
"geneset_description": "",
"geneset_name": "empty_this_geneset",
},
{
'genes': [{'gene_description': '', 'gene_symbol': 'SIK1'}],
'geneset_description': '',
'geneset_name': 'brush_this_gene'
}
"genes": [{"gene_description": "", "gene_symbol": "SIK1"}],
"geneset_description": "",
"geneset_name": "brush_this_gene",
},
],
"tid": 0,
},
@@ -606,7 +651,7 @@ summary test,,F5,\r
summary test,,PIGU,\r
geneset_to_delete,,,\r
geneset_to_edit,,,\r
fill_this_geneset,,RER1,\r
fill_this_geneset,,,\r
empty_this_geneset,,SIK1,\r
brush_this_gene,,SIK1,\r
""",
@@ -680,7 +725,7 @@ brush_this_gene,,SIK1,\r
self.assertEqual(result.json(), test3)
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"
url = f"{self.URL_BASE}{endpoint}"
@@ -690,7 +735,7 @@ brush_this_gene,,SIK1,\r
tid = original_data["tid"]
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)
self.assertEqual(result.status_code, expected_code)
result = self.session.get(url, headers={"Accept": "application/json"})

View File

@@ -2,7 +2,7 @@ import unittest
import numpy as np
from scipy import sparse
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.test.test_server.unit import app_config
from backend.test import PROJECT_ROOT
@@ -20,22 +20,101 @@ class EstDistTest(unittest.TestCase):
def test_adaptestimate_approximate_distribution(self):
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):
raw = np.random.exponential(scale=1000, size=(100, 40))
# empty
self.assertEqual(estimate_approximate_distribution(np.zeros((0,))), XApproximateDistribution.NORMAL)
# ndarray
self.assertEqual(estimate_approximate_distribution(raw), XApproxDistribution.COUNT)
self.assertEqual(estimate_approximate_distribution(np.log1p(raw)), XApproxDistribution.NORMAL)
self.assertEqual(estimate_approximate_distribution(raw), XApproximateDistribution.COUNT)
self.assertEqual(estimate_approximate_distribution(np.log1p(raw)), XApproximateDistribution.NORMAL)
# 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(
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 = np.random.exponential(scale=100, size=(1_000_000, 100))
self.assertEqual(estimate_approximate_distribution(big), XApproxDistribution.COUNT)
self.assertEqual(estimate_approximate_distribution(np.log1p(big)), XApproxDistribution.NORMAL)
self.assertEqual(estimate_approximate_distribution(big), XApproximateDistribution.COUNT)
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,
)

View File

@@ -22,7 +22,7 @@ Test the anndata adaptor using the pbmc3k data set.
@parameterized_class(
("data_locator", "backed", "X_approx_distribution"),
("data_locator", "backed", "X_approximate_distribution"),
[
(f"{PROJECT_ROOT}/example-dataset/pbmc3k.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"{FIXTURES_ROOT}/pbmc3k-CSC-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):
def setUp(self):
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)

View File

@@ -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[`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>"`;

View File

@@ -2,15 +2,15 @@
exports[`annotations stacked bar graph renders 1`] = `
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-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-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 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`] = `
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-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-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 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>",
]
`;

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 */

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 */

View File

@@ -58,10 +58,12 @@ describe("metadata loads", () => {
const categories = await getAllCategoriesAndCounts(label);
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])
);
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])
);
}
@@ -159,10 +161,12 @@ describe("subset", () => {
const categories = await getAllCategoriesAndCounts(label);
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])
);
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])
);
}
@@ -195,6 +199,7 @@ describe("clipping", () => {
test("clip continuous", async () => {
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);
const histBrushableAreaId = `histogram-${data.clip.metadata}-plot-brushable-area`;
const coords = await calcDragCoordinates(
@@ -254,6 +259,7 @@ describe("centroid labels", () => {
const generatedLabels = await getAllByClass("centroid-label");
// Number of labels generated should be equal to size of the object
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
);
}
@@ -275,6 +281,7 @@ describe("graph overlay", () => {
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 initialCoordinates = await getElementCoordinates(
`${categoryValue}-centroid-label`

View File

@@ -12,6 +12,7 @@ import {
getTestId,
getTestClass,
getAllByClass,
clickOnUntil,
getOneElementInnerHTML,
} from "./puppeteerUtils";
@@ -76,7 +77,13 @@ const brushThisGeneGeneset = "brush_this_gene";
const geneBrushedCellCount = "109";
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);
if (config.categoricalAnno) {
@@ -150,7 +157,8 @@ describe.each([
await expect(page).toClick(getTestClass("pop-1-geneset-expand"));
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")
);
@@ -165,7 +173,8 @@ describe.each([
await expect(page).toClick(getTestClass("pop-2-geneset-expand"));
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")
);
@@ -174,7 +183,7 @@ describe.each([
expect(genesHTML).toMatchSnapshot();
});
test("create a new geneset", async () => {
test("create a new geneset and undo/redo", async () => {
if (config.withSubset) return;
await setup(config);
@@ -184,19 +193,45 @@ describe.each([
await createGeneset(genesetName);
/* note: as of June 2021, the aria label is in the truncate component which clones the element */
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 editGenesetName(editableGenesetName, editText);
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;
await setup(config);
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: false, tag: "whole" },
])("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 addGeneToSet(setToAddGeneTo, geneToAddToSet);
await expandGeneset(setToAddGeneTo);
await assertGeneExistsInGeneset(geneToAddToSet);
await clickOn("undo");
await assertGeneDoesNotExist(geneToAddToSet);
await clickOn("redo");
await assertGeneExistsInGeneset(geneToAddToSet);
});
test("expand gene and brush", async () => {
await setup(config);
@@ -240,7 +279,7 @@ describe.each([
await colorByGene(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
if (config.withSubset) return;
@@ -249,6 +288,10 @@ describe.each([
await expandGeneset(setToRemoveFrom);
await removeGene(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(actualLabelCount).toBe(expectedLabelCount);
async function getInnerText(element, className) {
return element.$eval(getTestClass(className), (node) => node?.innerText);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
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}`
);
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
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]
);
});
@@ -448,6 +498,7 @@ describe.each([
await createLabel(perTestCategoryName, labelName);
await assertLabelExists(perTestCategoryName, labelName);
await clickOn("undo");
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
await assertLabelDoesNotExist(perTestCategoryName);
await clickOn("redo");
await assertLabelExists(perTestCategoryName, labelName);
@@ -457,10 +508,12 @@ describe.each([
await setup(config);
await deleteLabel(perTestCategoryName, perTestLabelName);
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
await assertLabelDoesNotExist(perTestCategoryName);
await clickOn("undo");
await assertLabelExists(perTestCategoryName, perTestLabelName);
await clickOn("redo");
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
await assertLabelDoesNotExist(perTestCategoryName);
});
@@ -491,11 +544,9 @@ describe.each([
const labels = await getAllByClass("categorical-row");
const result = await Promise.all(
labels.map((label) => {
return page.evaluate((element) => {
return element.outerHTML;
}, label);
})
labels.map((label) =>
page.evaluate((element) => element.outerHTML, label)
)
);
expect(result).toMatchSnapshot();
@@ -523,9 +574,11 @@ describe.each([
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`);
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
const result = await handle.evaluate((node) =>
node.getAttribute("aria-label")
);
@@ -533,7 +586,8 @@ describe.each([
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(
getTestId(`${categoryName}:category-expand`)
);
@@ -545,11 +599,13 @@ describe.each([
);
expect(
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
await previous.evaluate((node) => node.getAttribute("aria-label"))
).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);
const result = await page.$(
`[data-testid='categorical-value-${categoryName}-${labelName}']`

View File

@@ -1,10 +1,10 @@
{
"testRunner": "jest-circus/runner",
"preset": "jest-puppeteer",
"testMatch": ["**/__tests__/**/?(*.)(spec|test).js?(x)"],
"setupFiles": ["../setupMissingGlobals.js"],
"setupFilesAfterEnv": ["expect-puppeteer", "./puppeteer.setup.js"],
"globalSetup": "../globalSetup.js",
"testMatch": ["**/__tests__/**/?(*.)(spec|test).ts?(x)"],
"setupFiles": ["../setupMissingGlobals.ts"],
"setupFilesAfterEnv": ["expect-puppeteer", "./puppeteer.setup.ts"],
"globalSetup": "../globalSetup.ts",
"globalTeardown": "jest-environment-puppeteer/teardown",
"testEnvironment": "./screenshot_env.js"
}

View File

@@ -23,6 +23,7 @@ beforeEach(async () => {
const userAgent = await browser.userAgent();
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 });
page.on("pageerror", (err) => {
@@ -49,7 +50,8 @@ beforeEach(async () => {
}
const errorMsgText = await Promise.all(
// 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}`);
}

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 */

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 */

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");
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");
// @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");
class ScreenshotEnvironment extends PuppeteerEnvironment {

View File

@@ -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 {
SecretsManagerClient,
GetSecretValueCommand,
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
} = 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 client = new SecretsManagerClient({ region: "us-west-2" });

View File

@@ -20,7 +20,16 @@ describe("cascade", () => {
const reducer = cascadeReducers([
[
"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(action).toEqual(topLevelAction);
expect(nextSharedState).toStrictEqual({});
@@ -30,7 +39,16 @@ describe("cascade", () => {
],
[
"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(action).toEqual(topLevelAction);
expect(nextSharedState).toStrictEqual({ foo: 0 });

View File

@@ -501,6 +501,7 @@ describe("geneset: set tid", () => {
test("not a number error", () => {
expect(() => {
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 },
{
type: "geneset: set tid",
@@ -513,6 +514,7 @@ describe("geneset: set tid", () => {
test("decrement error", () => {
expect(() => {
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 },
{
type: "geneset: set tid",

View File

@@ -2,6 +2,7 @@ import undoable from "../../src/reducers/undoable";
describe("create", () => {
test("no keys", () => {
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2-3 arguments, but got 1.
expect(() => undoable(() => {})).toThrow();
expect(() => undoable(() => {}, null)).toThrow();
expect(() => undoable(() => {}, [])).toThrow();
@@ -23,9 +24,8 @@ describe("create", () => {
describe("undo", () => {
test("expected state modifications", () => {
const initialState = { a: 0, b: 1000 };
const reducer = (state) => {
return { a: state.a + 1, b: state.b + 1 };
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const reducer = (state: any) => ({ a: state.a + 1, b: state.b + 1 });
const undoableReducer = undoable(reducer, ["a"]);
const s1 = undoableReducer(initialState, { type: "test" });
@@ -43,10 +43,10 @@ describe("undo", () => {
describe("redo", () => {
const initialState = { a: 0, b: 1000 };
const reducer = (state) => {
return { a: state.a + 1, b: state.b + 1 };
};
let UR;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const reducer = (state: any) => ({ a: state.a + 1, b: state.b + 1 });
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let UR: any;
beforeEach(() => {
UR = undoable(reducer, ["a"]);

View File

@@ -5,5 +5,6 @@ the jest test environment).
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.TextEncoder = TextEncoder;

View File

@@ -14,10 +14,13 @@ import { Dataframe } from "../../../src/util/dataframe";
enableFetchMocks();
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 () => {
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(
serverMocks.baseDataURL,
serverMocks.schema.schema
@@ -36,7 +39,8 @@ describe("AnnoMatrix", () => {
});
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");
expect(df).toBeInstanceOf(Dataframe);
@@ -45,7 +49,8 @@ describe("AnnoMatrix", () => {
});
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(["n_genes"]));
@@ -55,9 +60,13 @@ describe("AnnoMatrix", () => {
});
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);
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(
annoMatrix.fetch(field, columnNames)
).resolves.toBeInstanceOf(Dataframe);
@@ -70,19 +79,22 @@ describe("AnnoMatrix", () => {
test("fetch - test all query forms", async () => {
// 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(
Dataframe
);
// 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(
annoMatrix.fetch("obs", ["n_genes", "percent_mito"])
).resolves.toBeInstanceOf(Dataframe);
// 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(
annoMatrix.fetch("X", {
where: {
@@ -95,7 +107,8 @@ describe("AnnoMatrix", () => {
// more complex value filter query, range
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(
serverMocks.withExpected("/data/var", [[`var:${varIndex}`, "SUMO3"]])
)
@@ -171,7 +184,8 @@ describe("AnnoMatrix", () => {
expect(am1.nObs).toEqual(am2.nObs);
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"]));
const ng1 = await am1.fetch("obs", "n_genes");
@@ -185,9 +199,11 @@ describe("AnnoMatrix", () => {
});
describe("add/drop column", () => {
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'base' implicitly has an 'any' type.
async function addDrop(base) {
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(
"unknown column name"
);
@@ -212,7 +228,8 @@ describe("AnnoMatrix", () => {
const am2 = am1.dropObsColumn("foo");
expect(base.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(
"unknown column name"
);
@@ -235,14 +252,16 @@ describe("AnnoMatrix", () => {
const am4 = clip(am3, 0, 1);
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 am2.fetch("obs", am2.getMatrixColumns("obs"));
await am3.fetch("obs", am3.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(am2);
@@ -252,6 +271,7 @@ describe("AnnoMatrix", () => {
});
describe("setObsColumnValues", () => {
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'base' implicitly has an 'any' type.
async function addSetDrop(base) {
/* add column */
let am = base.addObsColumn(
@@ -287,7 +307,8 @@ describe("AnnoMatrix", () => {
);
/* 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");
await expect(am.fetch("obs", "test")).rejects.toThrow(
"unknown column name"
@@ -308,7 +329,8 @@ describe("AnnoMatrix", () => {
const am3 = isubset(annoMatrix, [10, 1, 0, 30, 2]);
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 am2.fetch("obs", am2.getMatrixColumns("obs"));

View File

@@ -17,11 +17,15 @@ import { rangeFill } from "../../../src/util/range";
enableFetchMocks();
describe("AnnoMatrixCrossfilter", () => {
let annoMatrix;
let crossfilter;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let annoMatrix: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let crossfilter: any;
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(
serverMocks.baseDataURL,
serverMocks.schema.schema
@@ -67,7 +71,10 @@ describe("AnnoMatrixCrossfilter", () => {
crossfilter.obsCrossfilter.hasDimension("obs/louvain")
).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", {
mode: "none",
});
@@ -76,7 +83,8 @@ describe("AnnoMatrixCrossfilter", () => {
expect(
newCrossfilter.obsCrossfilter.hasDimension("obs/louvain")
).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", {
mode: "all",
@@ -87,7 +95,10 @@ describe("AnnoMatrixCrossfilter", () => {
test("simple column select", async () => {
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", {
mode: "exact",
values: ["NK cells", "B cells"],
@@ -105,6 +116,7 @@ describe("AnnoMatrixCrossfilter", () => {
expect(xfltr.allSelectedLabels()).toEqual(
Int32Array.from(
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);
return acc;
}, [])
@@ -124,10 +136,13 @@ describe("AnnoMatrixCrossfilter", () => {
const values = df.col("louvain").asArray();
const selected = xfltr.allSelectedMask();
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)])
);
xfltr = await xfltr.select("obs", "n_genes", {
@@ -146,6 +161,7 @@ describe("AnnoMatrixCrossfilter", () => {
val < 500 &&
(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);
return acc;
}, [])
@@ -160,7 +176,8 @@ describe("AnnoMatrixCrossfilter", () => {
const varIndex = annoMatrix.schema.annotations.var.index;
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(
["TEST"],
[rangeFill(new Float32Array(nObs), 0, 0.1)]
@@ -196,14 +213,19 @@ describe("AnnoMatrixCrossfilter", () => {
});
const values = df.icol(0).asArray();
const selected = xfltr.allSelectedMask();
values.every((val, idx) => !(val >= 0 && val <= 50) !== !selected[idx]);
expect(selected.reduce((acc, val) => (val ? acc + 1 : acc), 0)).toEqual(
xfltr.countSelected()
values.every(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(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 () => {
fetch.once(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(
serverMocks.dataframeResponse(
["umap_0", "umap_1"],
[Float32Array.from(embUmap[0]), Float32Array.from(embUmap[1])]
@@ -222,6 +244,7 @@ describe("AnnoMatrixCrossfilter", () => {
test("select on subset", async () => {
const mask = new Uint8Array(annoMatrix.nObs).fill(0);
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;
}
const annoMatrixSubset = isubsetMask(annoMatrix, mask);
@@ -230,7 +253,10 @@ describe("AnnoMatrixCrossfilter", () => {
let xfltr = new AnnoMatrixObsCrossfilter(annoMatrixSubset);
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", {
mode: "exact",
values: ["NK cells", "B cells"],
@@ -243,7 +269,9 @@ describe("AnnoMatrixCrossfilter", () => {
const values = df.col("louvain").asArray();
const selected = xfltr.allSelectedMask();
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"
);
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(
"unknown column name"
);
@@ -267,24 +296,29 @@ describe("AnnoMatrixCrossfilter", () => {
/*
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(
cf.annoMatrix.getMatrixColumns("obs").includes(colName)
).toBeFalsy();
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 = {
name: colName,
type: "categorical",
categories: ["toasty"],
};
}
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
colSchema.name = colName;
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
const initValue = colSchema.categories[0];
const xfltr = cf.addObsColumn(colSchema, Array, initValue);
expect(
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);
const df = await xfltr.annoMatrix.fetch("obs", colName);
@@ -314,7 +348,8 @@ describe("AnnoMatrixCrossfilter", () => {
});
expect(
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);
@@ -323,8 +358,8 @@ describe("AnnoMatrixCrossfilter", () => {
expect(
df
.col("foo")
.asArray()
.every((v) => v === "A")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.every((v: any) => v === "A")
).toBeTruthy();
// check that we catch dups
@@ -361,11 +396,13 @@ describe("AnnoMatrixCrossfilter", () => {
xfltr = xfltr.dropObsColumn("foo");
expect(
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);
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(
"unknown column name"
);
@@ -378,7 +415,8 @@ describe("AnnoMatrixCrossfilter", () => {
});
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(
"unknown column name"
);
@@ -404,7 +442,8 @@ describe("AnnoMatrixCrossfilter", () => {
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(
"unknown column name"
);
@@ -419,7 +458,8 @@ describe("AnnoMatrixCrossfilter", () => {
});
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(
"unknown column name"
);
@@ -429,7 +469,8 @@ describe("AnnoMatrixCrossfilter", () => {
});
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
expect(() => crossfilter.addObsAnnoCategory("louvain", "mumble")).toThrow(
@@ -440,6 +481,7 @@ describe("AnnoMatrixCrossfilter", () => {
).toThrow("Unknown or readonly obs column");
// 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", {
name: "foo",
type: "categorical",
@@ -458,6 +500,7 @@ describe("AnnoMatrixCrossfilter", () => {
);
// 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", {
name: "bar",
type: "categorical",
@@ -486,6 +529,7 @@ describe("AnnoMatrixCrossfilter", () => {
crossfilter.removeObsAnnoCategory("undefined-name", "mumble")
).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", {
name: "foo",
type: "categorical",
@@ -495,8 +539,8 @@ describe("AnnoMatrixCrossfilter", () => {
expect(
(await xfltr.annoMatrix.fetch("obs", "foo"))
.col("foo")
.asArray()
.every((v) => v === "unassigned")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.every((v: any) => v === "unassigned")
).toBeTruthy();
expect(xfltr.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
name: "foo",
@@ -514,8 +558,8 @@ describe("AnnoMatrixCrossfilter", () => {
expect(
(await xfltr1.annoMatrix.fetch("obs", "foo"))
.col("foo")
.asArray()
.every((v) => v === "unassigned")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.every((v: any) => v === "unassigned")
).toBeTruthy();
expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
name: "foo",
@@ -537,8 +581,8 @@ describe("AnnoMatrixCrossfilter", () => {
expect(
(await xfltr2.annoMatrix.fetch("obs", "foo"))
.col("foo")
.asArray()
.every((v) => v === "red")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.every((v: any) => v === "red")
).toBeTruthy();
expect(xfltr2.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
name: "foo",
@@ -556,6 +600,7 @@ describe("AnnoMatrixCrossfilter", () => {
crossfilter.setObsColumnValues("undefined-name", [0], "mumble")
).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", {
name: "foo",
type: "categorical",
@@ -572,8 +617,8 @@ describe("AnnoMatrixCrossfilter", () => {
expect(
(await xfltr.annoMatrix.fetch("obs", "foo"))
.col("foo")
.asArray()
.every((v) => v === "unassigned")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.every((v: any) => v === "unassigned")
).toBeTruthy();
const xfltr1 = await xfltr.setObsColumnValues("foo", [0, 10], "purple");
expect(
@@ -581,7 +626,8 @@ describe("AnnoMatrixCrossfilter", () => {
.col("foo")
.asArray()
.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))
)
).toBeTruthy();
@@ -615,6 +661,7 @@ describe("AnnoMatrixCrossfilter", () => {
crossfilter.resetObsColumnValues("undefined-name", "red", "blue")
).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", {
name: "foo",
type: "categorical",
@@ -638,22 +685,22 @@ describe("AnnoMatrixCrossfilter", () => {
expect(
(await xfltr1.annoMatrix.fetch("obs", "foo"))
.col("foo")
.asArray()
.filter((v) => v === "purple")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.filter((v: any) => v === "purple")
).toHaveLength(2);
xfltr1 = await xfltr1.resetObsColumnValues("foo", "purple", "magenta");
expect(
(await xfltr1.annoMatrix.fetch("obs", "foo"))
.col("foo")
.asArray()
.filter((v) => v === "magenta")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.filter((v: any) => v === "magenta")
).toHaveLength(2);
expect(
(await xfltr1.annoMatrix.fetch("obs", "foo"))
.col("foo")
.asArray()
.filter((v) => v === "purple")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.filter((v: any) => v === "purple")
).toHaveLength(0);
expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
name: "foo",
@@ -673,12 +720,16 @@ describe("AnnoMatrixCrossfilter", () => {
describe("edge cases", () => {
test("transition from empty annoMatrix", async () => {
// 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", {
mode: "exact",
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.all()).toBe(xfltr.annoMatrix._cache.obs);
expect(xfltr.countSelected()).toEqual(

View File

@@ -1,6 +1,7 @@
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: {
prefix: baseDataURL,
version: "v0.2/",

View File

@@ -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])
);
}

View File

@@ -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])
);
}

View File

@@ -218,10 +218,18 @@ describe("whereCache", () => {
},
})
);
expect(wc.where.field.queryField.has("queryColumn")).toEqual(true);
expect(wc.where.field.queryField.get("queryColumn")).toBeInstanceOf(Map);
// @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.
expect((wc.where as any).field.queryField.has("queryColumn")).toEqual(true);
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);
expect(_whereCacheGet(wc, schema, "field", query)).toEqual([0, 1, 2]);
});

View File

@@ -5,19 +5,22 @@ import quantile from "../../src/util/quantile";
import { matrixFBSToDataframe } from "../../src/util/stateManager/matrix";
import * as REST from "./stateManager/sampleResponses";
import { indexEntireSchema } from "../../src/util/stateManager/schemaHelpers";
import { _normalizeCategoricalSchema } from "../../src/annoMatrix/schema";
import { normalizeWritableCategoricalSchema } from "../../src/annoMatrix/normalize";
describe("centroid", () => {
let schema;
let obsAnnotations;
let obsLayout;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let schema: any;
// 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(() => {
schema = indexEntireSchema(cloneDeep(REST.schema.schema));
obsAnnotations = matrixFBSToDataframe(REST.annotationsObs);
obsLayout = matrixFBSToDataframe(REST.layoutObs);
_normalizeCategoricalSchema(
normalizeWritableCategoricalSchema(
schema.annotations.obsByName.field3,
obsAnnotations.col("field3")
);
@@ -44,7 +47,8 @@ describe("centroid", () => {
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);
});
});
@@ -68,7 +72,8 @@ describe("centroid", () => {
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);
});
});

View File

@@ -29,6 +29,7 @@ describe("dataframe constructor", () => {
const df = new Dataframe.Dataframe(
[3, 2],
[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.KeyIndex(["A", "B"])
);
@@ -55,6 +56,7 @@ describe("simple data access", () => {
new Float64Array([0.0, Number.NaN, Number.POSITIVE_INFINITY, 3.14159]),
["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.KeyIndex(["numbers", "colors"])
);
@@ -139,10 +141,12 @@ describe("dataframe subsetting", () => {
["red", "green", "blue"],
],
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"])
);
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"]);
expect(dfA).toBeDefined();
expect(dfA.dims).toEqual([3, 1]);
@@ -158,6 +162,7 @@ describe("dataframe subsetting", () => {
});
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"]);
expect(dfB).toBeDefined();
expect(dfB.dims).toEqual([3, 2]);
@@ -227,6 +232,7 @@ describe("dataframe subsetting", () => {
});
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"]);
expect(dfF).toBeDefined();
expect(dfF.dims).toEqual([2, 2]);
@@ -236,6 +242,7 @@ describe("dataframe subsetting", () => {
expect(dfF.colIndex.labels()).toEqual(["int32", "float32"]);
// 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"]);
expect(dfFr).toBeDefined();
expect(dfFr.dims).toEqual([2, 2]);
@@ -248,6 +255,7 @@ describe("dataframe subsetting", () => {
test("withRowIndex", () => {
const df = sourceDf.subset(
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"],
new Dataframe.DenseInt32Index([3, 2, 1])
);
@@ -258,12 +266,15 @@ describe("dataframe subsetting", () => {
test("withRowIndex error checks", () => {
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))
).toThrow(RangeError);
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]))
).toThrow(RangeError);
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]))
).toThrow(RangeError);
});
@@ -278,12 +289,14 @@ describe("dataframe subsetting", () => {
new Float32Array([4.4, 5.5, 6.6]),
["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.KeyIndex(["int32", "string", "float32", "colors"])
);
const dfA = sourceDf.isubsetMask(
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])
);
expect(dfA.dims).toEqual([2, 2]);
@@ -303,6 +316,7 @@ describe("dataframe subsetting", () => {
["red", "green", "blue"],
],
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"])
);
@@ -316,6 +330,7 @@ describe("dataframe subsetting", () => {
});
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]);
expect(dfA.dims).toEqual([3, 2]);
expect(dfA.icol(0).asArray()).toEqual(["A", "B", "C"]);
@@ -361,6 +376,7 @@ describe("dataframe factories", () => {
const dfA = new Dataframe.Dataframe(
[3, 2],
[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.KeyIndex(["A", "B"])
);
@@ -385,6 +401,7 @@ describe("dataframe factories", () => {
[true, false],
],
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"])
);
const dfA = df.withCol("numbers", [1, 0]);
@@ -408,6 +425,7 @@ describe("dataframe factories", () => {
[true, false],
],
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])
);
const dfA = df.withCol(72, [1, 0]);
@@ -433,6 +451,7 @@ describe("dataframe factories", () => {
[true, false],
],
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])
);
const dfA = df.withCol(999, [1, 0]);
@@ -541,6 +560,7 @@ describe("dataframe factories", () => {
[1, 0],
],
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"])
);
@@ -549,11 +569,14 @@ describe("dataframe factories", () => {
[3, 1],
[["red", "blue", "green"]],
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"])
);
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
expect(() => dfA.withColsFrom(dfB)).toThrow(RangeError);
/* 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);
});
@@ -564,15 +587,18 @@ describe("dataframe factories", () => {
[2, 1],
[["red", "blue"]],
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"])
);
const dfB = new Dataframe.Dataframe(
[2, 1],
[[true, false]],
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"])
);
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
const dfLikeA = dfEmpty.withColsFrom(dfA);
expect(dfLikeA).toBeDefined();
expect(dfLikeA.dims).toEqual(dfA.dims);
@@ -581,6 +607,7 @@ describe("dataframe factories", () => {
expect(dfLikeA.rowIndex.labels()).toEqual(dfA.rowIndex.labels());
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);
expect(dfAlsoLikeA).toBeDefined();
expect(dfAlsoLikeA.dims).toEqual(dfA.dims);
@@ -589,6 +616,7 @@ describe("dataframe factories", () => {
expect(dfAlsoLikeA.rowIndex.labels()).toEqual(dfA.rowIndex.labels());
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);
expect(dfC).toBeDefined();
expect(dfC.dims).toEqual([2, 2]);
@@ -605,6 +633,7 @@ describe("dataframe factories", () => {
[2, 1],
[["red", "blue"]],
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"])
);
const dfB = new Dataframe.Dataframe(
@@ -615,6 +644,7 @@ describe("dataframe factories", () => {
[1, 0],
],
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"])
);
@@ -647,6 +677,7 @@ describe("dataframe factories", () => {
[2, 1],
[["red", "blue"]],
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"])
);
const dfB = new Dataframe.Dataframe(
@@ -657,6 +688,7 @@ describe("dataframe factories", () => {
[1, 0],
],
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"])
);
@@ -680,6 +712,7 @@ describe("dataframe factories", () => {
[1, 0],
],
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"])
);
const dfA = df.dropCol("colors");
@@ -751,6 +784,7 @@ describe("dataframe factories", () => {
[1, 0],
],
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])
);
const dfA = df.dropCol(101);
@@ -777,7 +811,8 @@ describe("dataframe factories", () => {
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);
return col;
});
@@ -793,9 +828,7 @@ describe("dataframe factories", () => {
[3, 3],
[new Array(3).fill(0), new Array(3).fill(0), new Array(3).fill(0)]
);
const dfB = dfA.mapColumns(() => {
return new Array(3).fill(1);
});
const dfB = dfA.mapColumns(() => new Array(3).fill(1));
expect(dfA).not.toBe(dfB);
expect(dfB.iat(0, 0)).toEqual(1);
expect(dfB.iat(0, 1)).toEqual(1);
@@ -822,6 +855,7 @@ describe("dataframe factories", () => {
[1, 0],
],
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"])
);
const dfB = dfA.renameCol("B", "C");
@@ -834,7 +868,8 @@ describe("dataframe factories", () => {
});
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(() => {
df = new Dataframe.Dataframe(
[2, 2],
@@ -843,6 +878,7 @@ describe("dataframe col", () => {
[1, 0],
],
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"])
);
});
@@ -1195,6 +1231,7 @@ describe("label indexing", () => {
test("create", () => {
expect(Dataframe.isLabelIndex(idx)).toBeTruthy();
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);
});
@@ -1367,6 +1404,7 @@ describe("corner cases", () => {
[1, 0],
],
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"])
);

View File

@@ -6,6 +6,7 @@ describe("Dataframe column histogram", () => {
[3, 3],
[["n1", "n2", "n3"], ["c1", "c2", "c3"], new Int32Array([0, 1, 2])],
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"])
);
@@ -26,6 +27,7 @@ describe("Dataframe column histogram", () => {
[3, 3],
[["n1", "n2", "n3"], ["c1", "c2", "c3"], new Int32Array([0, 1, 2])],
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"])
);
@@ -48,6 +50,7 @@ describe("Dataframe column histogram", () => {
[3, 3],
[["n1", "n2", "n3"], ["c1", "c2", "c3"], new Int32Array([0, 1, 2])],
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"])
);
@@ -68,6 +71,7 @@ describe("Dataframe column histogram", () => {
[3, 3],
[["n1", "n2", "n3"], ["c1", "c2", "c3"], new Int32Array([0, 1, 2])],
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"])
);

View File

@@ -1,6 +1,7 @@
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];
}
@@ -30,6 +31,7 @@ describe("Dataframe column summary", () => {
[1],
],
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",
"nameString",
@@ -106,6 +108,7 @@ describe("Dataframe column summary", () => {
[1, false, "0"],
],
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",
"nameString",
@@ -174,6 +177,7 @@ describe("Dataframe column summary", () => {
expect.objectContaining({
categorical: true,
categories: expect.arrayContaining([1, false, "0"]),
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
categoryCounts: new Map([
[1, 1],
[false, 1],
@@ -201,6 +205,7 @@ describe("Dataframe column summary", () => {
[1, false, "0", "0"],
],
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",
"nameString",
@@ -269,6 +274,7 @@ describe("Dataframe column summary", () => {
expect.objectContaining({
categorical: true,
categories: expect.arrayContaining([1, false, "0"]),
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
categoryCounts: new Map([
[1, 1],
[false, 1],

View File

@@ -1,7 +1,8 @@
import PromiseLimit from "../../src/util/promiseLimit";
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", () => {
test("simple evaluation, concurrency 1", async () => {
@@ -51,7 +52,9 @@ describe("PromiseLimit", () => {
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);
});

View File

@@ -6,14 +6,20 @@ describe("range", () => {
});
test("range(stop)", () => {
// @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 1.
expect(range(3)).toMatchObject([0, 1, 2]);
// @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 1.
expect(range(0)).toMatchObject([]);
// @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 1.
expect(range(1)).toMatchObject([0]);
});
test("range(start,stop)", () => {
// @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 2.
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]);
// @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 2.
expect(range(4, 8)).toMatchObject([4, 5, 6, 7]);
});

View File

@@ -81,6 +81,7 @@ describe("categorical color helpers", () => {
),
],
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"])
);
@@ -95,6 +96,7 @@ describe("categorical color helpers", () => {
const data = obsDataframe.col("categoricalColumn").asArray();
const cats = schema.annotations.obsByName.categoricalColumn.categories;
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])));
}
});
@@ -112,6 +114,7 @@ describe("categorical color helpers", () => {
const data = obsDataframe.col("categoricalColumn").asArray();
const cats = schemaClone.annotations.obsByName.categoricalColumn.categories;
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])));
}
});
@@ -122,7 +125,8 @@ describe("categorical color helpers", () => {
Array.from(schema.annotations.obsByName.categoricalColumn.categories)
);
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();
return acc;
}, {}),
@@ -136,12 +140,14 @@ describe("categorical color helpers", () => {
"categoricalColumn",
obsDataframe,
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
);
expect(ct).toBeDefined();
const data = obsDataframe.col("categoricalColumn").asArray();
for (let i = 0; i < schema.dataframe.nObs; i += 1) {
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()
);
}
@@ -154,31 +160,38 @@ TODO:
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?.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?.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?.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?.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;
}
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
return `rgb(${(rgb[0] * 255) >>> 0}, ${(rgb[1] * 255) >>> 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) {
const j = (Math.random() * (i + 1)) >>> 0;
[array[i], array[j]] = [array[j], array[i]];

View File

@@ -2,7 +2,7 @@
test controls helpers
*/
// TODO #2227 test: improve test coverage on control helper functions
// (`topNCategories()`, `isSelectableCategoryName()`, `selectableCategoryNames()`, `createCategorySummaryFromDfCol()`, `createCategoricalSelection()`, )
// (`isSelectableCategoryName()`, `selectableCategoryNames()`, `createCategorySummaryFromDfCol()`, `createCategoricalSelection()`, )
describe("controls helpers", () => {
test("placeholder", () => {});

View File

@@ -24,6 +24,7 @@ describe("encode/decode", () => {
expect(dfA.columns).toEqual(columns);
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 dfB = decodeMatrixFBS(encodeMatrixFBS(dfWithColIdx));
expect([dfB.nRows, dfB.nCols]).toEqual(dfWithColIdx.dims);

View File

@@ -75,6 +75,7 @@ const aSchemaResponse = {
const anAnnotationsObsJSONResponse = {
names: ["name", "field1", "field2", "field3", "field4"],
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
data: _()
.range(nObs)
.map((idx) => [
@@ -91,6 +92,7 @@ const anAnnotationsObsJSONResponse = {
const anAnnotationsVarJSONResponse = {
names: ["fieldA", "fieldB", "fieldC", "fieldD", "name"],
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
data: _()
.range(nVar)
.map((idx) => [
@@ -105,8 +107,11 @@ const anAnnotationsVarJSONResponse = {
.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];
// @ts-expect-error --- FIXME: Element implicitly has an 'any' type.
const ArrayType = NetEncoding[uTypeName];
const dv = ArrayType.createDataVector(builder, uData);
builder.startObject(1);
@@ -114,7 +119,8 @@ function encodeTypedArray(builder, uType, uData) {
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,
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
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 builder = new flatbuffers.Builder(1024);
const cols = map(columns, (carr) => {
@@ -172,11 +179,13 @@ function encodeMatrix(columns, colIndex = undefined) {
const anAnnotationsObsFBSResponse = (() => {
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);
})();
const anAnnotationsVarFBSResponse = (() => {
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);
})();
@@ -185,11 +194,13 @@ const aLayoutFBSResponse = (() => {
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"]);
})();
const aDataObsResponse = {
var: [2, 4, 29],
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
obs: _()
.range(nObs)
.map((idx) => [idx, Math.random(), Math.random(), Math.random()])

View File

@@ -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(() => {
payments = new Crossfilter(someData);
});
@@ -138,7 +139,13 @@ describe("ImmutableTypedCrossfilter", () => {
expect(payments.all()).toEqual(someData);
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" });
expect(p).toBeDefined();
expect(p.all()).toEqual(someData);
@@ -158,7 +165,8 @@ describe("ImmutableTypedCrossfilter", () => {
const p2 = payments.addDimension(
"quantity",
"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
);
@@ -175,10 +183,22 @@ describe("ImmutableTypedCrossfilter", () => {
test("select all and none", () => {
let p = payments
.addDimension("quantity", "scalar", (i, d) => d[i].quantity, Int32Array)
.addDimension("tip", "scalar", (i, d) => d[i].tip, Float32Array)
.addDimension("total", "scalar", (i, d) => d[i].total, Float32Array)
.addDimension("type", "enum", (i, d) => d[i].type);
.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
) // 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 all records to be selected - default init state */
@@ -230,11 +250,24 @@ describe("ImmutableTypedCrossfilter", () => {
});
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(() => {
p = payments
.addDimension("quantity", "scalar", (i, d) => d[i].quantity, Int32Array)
.addDimension("tip", "scalar", (i, d) => d[i].tip, Float32Array)
.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(
"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" });
});
@@ -277,9 +310,11 @@ describe("ImmutableTypedCrossfilter", () => {
});
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(() => {
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", () => {
@@ -317,7 +352,8 @@ describe("ImmutableTypedCrossfilter", () => {
});
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(() => {
const X = someData.map((r) => r.coords[0]);
const Y = someData.map((r) => r.coords[1]);
@@ -406,14 +442,22 @@ describe("ImmutableTypedCrossfilter", () => {
});
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(() => {
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(
"nonFinite",
"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
)
.select("quantity", { mode: "all" });

View File

@@ -15,7 +15,8 @@ paths for:
const pInf = Number.POSITIVE_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;
for (let i = 0, len = larr.length; i < len; i += 1) {
larr[i] = i + start;
@@ -23,7 +24,8 @@ function fillRange(arr, start = 0) {
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) {
arr[i] = Math.random();
}
@@ -48,16 +50,22 @@ describe("sortArray", () => {
describe("finite numbers", () => {
[Array, Float32Array, Uint32Array, Int32Array, Float64Array].map((Type) =>
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(
// @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.
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(
// @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.
Type.from([1, 2, 3, 4, 5, 6])
);
const source = fillRand(new Type(1000));
// @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.
expect(sortArray(Type.from(source))).toMatchObject(
// @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.
Type.from(source).sort()
);
})
@@ -130,22 +138,27 @@ describe("sortIndex", () => {
describe("finite numbers", () => {
[Array, Float32Array, Uint32Array, Int32Array, Float64Array].map((Type) =>
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 index1 = fillRange(new Uint32Array(source1.length));
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 index2 = fillRange(new Uint32Array(source2.length));
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 index3 = fillRange(new Uint32Array(source3.length));
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])
);
})
);

View File

@@ -11,11 +11,13 @@ module.exports = {
},
],
"@babel/preset-react",
"@babel/preset-typescript",
],
plugins: [
"@babel/plugin-proposal-function-bind",
["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/plugin-proposal-class-properties", { loose: true }],
["@babel/plugin-proposal-private-methods", { loose: true }],
"@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator",

View File

@@ -10,11 +10,13 @@ module.exports = {
},
],
"@babel/preset-react",
"@babel/preset-typescript",
],
plugins: [
"@babel/plugin-proposal-function-bind",
["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/plugin-proposal-class-properties", { loose: true }],
["@babel/plugin-proposal-private-methods", { loose: true }],
"@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-transform-react-constant-elements",
"@babel/plugin-transform-runtime",

View File

@@ -1,12 +1,17 @@
/* eslint-disable @blueprintjs/classes-constants -- we don't import blueprint here */
module.exports = {
root: true,
parser: "babel-eslint",
parser: "@typescript-eslint/parser",
extends: [
"airbnb",
"airbnb-typescript",
"plugin:@typescript-eslint/recommended",
"plugin:eslint-comments/recommended",
"plugin:@blueprintjs/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",
],
settings: {
@@ -32,18 +37,47 @@ module.exports = {
jsx: true,
generators: true,
},
project: "./tsconfig.json",
},
rules: {
"react/jsx-no-target-blank": "off",
"eslint-comments/require-description": ["error"],
"no-magic-numbers": "off",
"@typescript-eslint/no-magic-numbers": "off",
"no-nested-ternary": "off",
"func-style": "off",
"arrow-parens": "off",
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": "off",
"react/jsx-filename-extension": "off",
"comma-dangle": "off",
"@typescript-eslint/comma-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",
"no-console": "off",
"spaced-comment": ["error", "always", { exceptions: ["*"] }],
@@ -51,6 +85,7 @@ module.exports = {
"object-curly-newline": ["error", { consistent: true }],
"react/prop-types": [0],
"space-before-function-paren": "off",
"@typescript-eslint/space-before-function-paren": "off",
"function-paren-newline": "off",
"prefer-destructuring": ["error", { object: true, array: false }],
"import/prefer-default-export": "off",
@@ -69,9 +104,9 @@ module.exports = {
},
overrides: [
{
files: ["**/*.test.js"],
files: ["**/*.test.ts"],
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
// "extends": ["plugin:jest/recommended"]
@@ -86,3 +121,4 @@ module.exports = {
},
],
};
/* eslint-enable @blueprintjs/classes-constants -- we don't import blueprint here */

View File

@@ -1,3 +1,4 @@
module.exports = {
"*.js": "eslint --fix",
"*.{js,ts,jsx,tsx}": "eslint --fix",
"**/*": "prettier --write --ignore-unknown",
};

View File

@@ -1,6 +1,10 @@
/* 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");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
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 digest = (str) => {
@@ -50,4 +54,5 @@ class CspHashPlugin {
}
module.exports = CspHashPlugin;
/* eslint-enable @blueprintjs/classes-constants -- we don't import blueprint here */
/* eslint-enable import/no-extraneous-dependencies -- enable*/

View File

@@ -22,7 +22,7 @@
>
<img
src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/cellxgene-logo.png"
style="width: 320px;"
style="width: 320px"
/>
<div
style="
@@ -37,36 +37,34 @@
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
</div>
<div style="margin-top: 0;">
<div style="margin-top: 0">
cellxgene is currently supported on the following browsers
</div>
<div
style="display: flex; justify-content: space-around; margin-top: 16px;"
>
<div style="display: flex; justify-content: space-around; margin-top: 16px">
<a
href="https://www.google.com/chrome/?hl=en%22"
aria-label="Download Google Chrome"
>
<img
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>
</a>
<a href="https://www.mozilla.com/firefox/" aria-label="Download Firefox">
<img
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>
</a>
<a href="//www.microsoft.com/edge" aria-label="Download Edge">
<img
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>
</a>

View File

@@ -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");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
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");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
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");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
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");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
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 fonts = path.resolve("src/fonts");
@@ -23,7 +33,7 @@ const devConfig = {
module: {
rules: [
{
test: /\.jsx?$/,
test: /\.(ts|js)x?$/,
loader: "babel-loader",
options: babelOptions,
},
@@ -83,3 +93,4 @@ const devConfig = {
};
module.exports = merge(sharedConfig, devConfig);
/* eslint-enable @blueprintjs/classes-constants -- we don't import blueprint here */

View File

@@ -1,18 +1,31 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const path = require("path");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
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");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
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");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
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");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
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");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
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");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
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 fonts = path.resolve("src/fonts");
@@ -38,7 +51,7 @@ const prodConfig = {
module: {
rules: [
{
test: /\.jsx?$/,
test: /\.(ts|js)x?$/,
loader: "babel-loader",
options: babelOptions,
},

View File

@@ -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");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
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");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
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 src = path.resolve("src");
@@ -29,6 +34,9 @@ module.exports = {
path: path.resolve("build"),
publicPath,
},
resolve: {
extensions: [".ts", ".tsx", "..."],
},
module: {
rules: [
{
@@ -72,3 +80,4 @@ module.exports = {
}),
],
};
/* eslint-enable @blueprintjs/classes-constants -- we don't import blueprint here */

22674
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,9 +8,9 @@
"build": "npm run clean && webpack --config",
"clean": "rimraf build",
"dev": "npm run build -- configuration/webpack/webpack.config.dev.js",
"e2e": "jest --config __tests__/e2e/e2eJestConfig.json e2e/e2e.test.js",
"e2e-annotations": "jest --config __tests__/e2e/e2eJestConfig.json e2e/e2eAnnotations.test.js",
"e2e-prod": "CXG_URL_BASE='https://cellxgene.cziscience.com/d/pbmc3k.cxg/' 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.ts",
"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__",
"lint": "eslint --fix src __tests__",
"prod": "npm run build -- configuration/webpack/webpack.config.prod.js",
@@ -64,11 +64,9 @@
"react": "^17.0.2",
"react-async": "^10.0.1",
"react-dom": "^17.0.2",
"react-easy-emoji": "^1.4.0",
"react-flip-toolkit": "^7.0.12",
"react-helmet": "^6.1.0",
"react-icons": "^4.2.0",
"react-popper": "^2.2.4",
"react-redux": "^7.2.0",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
@@ -90,10 +88,38 @@
"@babel/plugin-transform-runtime": "^7.13.15",
"@babel/preset-env": "^7.13.15",
"@babel/preset-react": "^7.13.13",
"@babel/preset-typescript": "^7.14.5",
"@babel/register": "^7.13.16",
"@babel/runtime": "^7.13.16",
"@blueprintjs/eslint-plugin": "^0.3.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-jest": "^26.1.0",
"babel-loader": "^8.1.0",
@@ -103,10 +129,9 @@
"clean-css": "^5.1.2",
"clean-webpack-plugin": "^4.0.0-alpha.0",
"codecov": "^3.7.1",
"connect-history-api-fallback": "^1.6.0",
"css-loader": "^5.2.4",
"eslint": "^7.24.0",
"eslint-config-airbnb": "^18.2.0",
"eslint-config-airbnb-typescript": "^12.3.1",
"eslint-config-prettier": "^8.2.0",
"eslint-loader": "^4.0.2",
"eslint-plugin-compat": "^3.8.0",
@@ -115,12 +140,11 @@
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-jest": "^24.3.5",
"eslint-plugin-jsx-a11y": "^6.3.1",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-react": "^7.23.2",
"eslint-plugin-react-hooks": "^4.0.8",
"expect-puppeteer": "^5.0.0",
"express": "^4.17.1",
"favicons": "^6.2.1",
"favicons": "^6.2.2",
"favicons-webpack-plugin": "^5.0.2",
"file-loader": "^6.0.0",
"html-webpack-plugin": "^5.3.1",
@@ -146,10 +170,8 @@
"rimraf": "^3.0.2",
"script-ext-html-webpack-plugin": "^2.1.4",
"serve-favicon": "^2.5.0",
"style-loader": "^2.0.0",
"sw-precache-webpack-plugin": "^1.0.0",
"terser-webpack-plugin": "^5.1.1",
"url-loader": "^4.1.0",
"typescript": "^4.3.5",
"webpack": "^5.34.0",
"webpack-cli": "^4.6.0",
"webpack-dev-middleware": "^4.1.0",
@@ -157,10 +179,10 @@
},
"jest": {
"testMatch": [
"**/__tests__/**/?(*.)(spec|test).js?(x)"
"**/__tests__/**/?(*.)(spec|test).ts?(x)"
],
"setupFiles": [
"./__tests__/setupMissingGlobals.js"
"./__tests__/setupMissingGlobals.ts"
],
"coverageDirectory": "./coverage/",
"collectCoverage": true
@@ -170,7 +192,8 @@
"test": {
"presets": [
"@babel/preset-env",
"@babel/preset-react"
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": [
"@babel/plugin-proposal-function-bind",
@@ -186,6 +209,12 @@
"loose": true
}
],
[
"@babel/plugin-proposal-private-methods",
{
"loose": true
}
],
"@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-transform-react-constant-elements",
"@babel/plugin-transform-runtime",

View File

@@ -9,9 +9,12 @@ import { MatrixFBS, AnnotationsHelpers } from "../util/stateManager";
const { isUserAnnotation } = AnnotationsHelpers;
export const annotationCreateCategoryAction = (
newCategoryName,
categoryToDuplicate
) => 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.
newCategoryName: any,
// 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.
@@ -89,9 +92,12 @@ export const annotationCreateCategoryAction = (
};
export const annotationRenameCategoryAction = (
oldCategoryName,
newCategoryName
) => (dispatch, getState) => {
// 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
// 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
*/
@@ -124,9 +130,12 @@ export const annotationRenameCategoryAction = (
});
};
export const annotationDeleteCategoryAction = (categoryName) => (
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 annotationDeleteCategoryAction = (categoryName: 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
) => {
/*
Delete a user-created category
@@ -149,10 +158,13 @@ export const annotationDeleteCategoryAction = (categoryName) => (
};
export const annotationCreateLabelInCategory = (
categoryName,
labelName,
assignSelected
) => 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.
categoryName: any,
// 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.
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
the label to all currently selected cells.
@@ -188,9 +200,11 @@ export const annotationCreateLabelInCategory = (
};
export const annotationDeleteLabelFromCategory = (
categoryName,
labelName
) => 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.
categoryName: any,
// 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
*/
@@ -218,10 +232,14 @@ export const annotationDeleteLabelFromCategory = (
};
export const annotationRenameLabelInCategory = (
categoryName,
oldLabelName,
newLabelName
) => 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.
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
// 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
*/
@@ -255,9 +273,12 @@ export const annotationRenameLabelInCategory = (
};
export const annotationLabelCurrentSelection = (
categoryName,
labelName
) => 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.
categoryName: any,
// 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
*/
@@ -284,13 +305,24 @@ export const annotationLabelCurrentSelection = (
});
};
function writableAnnotations(annoMatrix) {
return annoMatrix.schema.annotations.obs.columns
.filter((s) => s.writable)
.map((s) => s.name);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function writableAnnotations(annoMatrix: any) {
return (
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
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
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.
*/
@@ -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();
// bail if gene sets not available, or in readonly mode.
@@ -465,7 +510,7 @@ export const saveGenesetsAction = () => async (dispatch, getState) => {
res,
});
}
return Promise.all([
return await Promise.all([
dispatch({
type: "autosave: genesets complete",
lastSavedGenesets: genesets,

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,
});
};

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,
});
};

View File

@@ -21,7 +21,13 @@ The behavior manifest in these action creators:
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 { genesets } = state;
const gs = genesets?.genesets?.get(genesetName) ?? {};
@@ -40,9 +46,12 @@ export const genesetDelete = (genesetName) => (dispatch, getState) => {
});
};
export const genesetAddGenes = (genesetName, genes) => 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.
export const genesetAddGenes = (genesetName: any, genes: 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 state = getState();
const { obsCrossfilter: prevObsCrossfilter, annoMatrix } = state;
@@ -50,7 +59,8 @@ export const genesetAddGenes = (genesetName, genes) => async (
const varIndex = schema.annotations.var.index;
const df = await annoMatrix.fetch("var", varIndex);
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) {
postUserErrorToast(
`${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) => (
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 genesetDeleteGenes = (genesetName: any, geneSymbols: 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 obsCrossfilter = dropGeneset(dispatch, state, genesetName, geneSymbols);
@@ -97,7 +110,14 @@ export const genesetDeleteGenes = (genesetName, geneSymbols) => (
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 varIndex = annoMatrix.schema.annotations?.var?.index;
const gs = genesets?.genesets?.get(genesetName) ?? {};
@@ -113,7 +133,8 @@ function dropGenesetSummaryDimension(obsCrossfilter, state, genesetName) {
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 varIndex = annoMatrix.schema.annotations?.var?.index;
const query = {
@@ -126,10 +147,21 @@ function dropGeneDimension(obsCrossfilter, state, gene) {
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 = 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)
);
dispatch({
@@ -137,7 +169,8 @@ function dropGeneset(dispatch, state, genesetName, geneSymbols) {
continuousNamespace: { isGeneSetSummary: true },
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({
type: "continuous metadata histogram cancel",
continuousNamespace: { isUserDefined: true },

View File

@@ -12,10 +12,22 @@ import * as viewActions from "./viewStack";
import * as embActions from "./embedding";
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
*/
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) =>
dispatch({
type: "universe: user color load success",
@@ -28,9 +40,13 @@ async function schemaFetch() {
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) => {
const config = { ...globals.configDefaults, ...response.config };
setGlobalConfig(config);
dispatch({
type: "configuration load complete",
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) => {
const { userinfo: userInfo } = response || {};
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 */
const defaultResponse = {
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
*/
const { schema } = annoMatrix;
const available = schema.layout.obs.map((v) => v.name);
available.forEach((embName) => annoMatrix.prefetch("emb", embName));
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
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
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
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" });
try {
const [config, schema] = await Promise.all([
configFetch(dispatch),
// @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1.
schemaFetch(dispatch),
userColorsFetchAndLoad(dispatch),
userInfoFetch(dispatch),
@@ -113,7 +137,8 @@ const doInitialDataLoad = () =>
const layoutSchema = schema?.schema?.layout?.obs ?? [];
if (
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));
}
@@ -122,21 +147,25 @@ const doInitialDataLoad = () =>
}
}, 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 {
type: "color by expression",
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",
data: {
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) {
case 403:
dispatchNetworkErrorMessageToUser(
@@ -159,10 +188,14 @@ const dispatchDiffExpErrors = (dispatch, response) => {
}
};
const requestDifferentialExpression = (set1, set2, num_genes = 50) => async (
dispatch,
getState
) => {
const requestDifferentialExpression = (
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
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" });
try {
/*
@@ -210,7 +243,9 @@ const requestDifferentialExpression = (set1, set2, num_genes = 50) => async (
const varIndex = await annoMatrix.fetch("var", varIndexName);
const diffexpLists = { negative: [], positive: [] };
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),
...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(
`${globals.API.prefix}${globals.API.version}${pathAndQuery}`
);

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,
});
};

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,
});
};

View File

@@ -18,7 +18,13 @@ import {
_userResetSubsetAnnoMatrix,
} 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
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 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
stacked, we pop them all. By convention, any clip transformation will

View File

@@ -17,6 +17,39 @@ import { _queryValidate, _queryCacheKey } from "./query";
const _dataframeCache = dataframeMemo(128);
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
to the annotated matrix data authoritatively served by the server/back-end.
@@ -47,6 +80,7 @@ export default class AnnoMatrix {
subset(annoMatrix, rowLabels) -> annoMatrix
etc.
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
static fields() {
/*
return the fields present in the AnnoMatrix instance.
@@ -54,7 +88,8 @@ export default class AnnoMatrix {
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.
*/
@@ -83,13 +118,13 @@ export default class AnnoMatrix {
this.userFlags = {};
/*
Private instance variables.
Private instance variables.
These are caches - lazily loaded. The only guarantee is that if they
are loaded, they will conform to the schema & dimensionality constraints.
These are caches - lazily loaded. The only guarantee is that if they
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 = {
obs: Dataframe.empty(this.rowIndex),
var: Dataframe.empty(this.rowIndex),
@@ -109,6 +144,8 @@ export default class AnnoMatrix {
/**
** 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) {
/*
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);
}
// 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() {
/*
Return array of fields in this annoMatrix. Currently hard-wired to
@@ -132,6 +169,8 @@ export default class AnnoMatrix {
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) {
/*
Return the schema for the field & column ,eg,
@@ -144,6 +183,8 @@ export default class AnnoMatrix {
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) {
/*
Return the dimensions on this field / column. For most fields, which are 1D,
@@ -162,10 +203,12 @@ export default class AnnoMatrix {
/**
** General utility methods
**/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
base() {
/*
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;
while (annoMatrix.isView) annoMatrix = annoMatrix.viewOf;
return annoMatrix;
@@ -174,6 +217,8 @@ export default class AnnoMatrix {
/**
** 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) {
/*
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);
}
// @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) {
/*
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.
**/
// 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) {
/*
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();
}
// 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) {
/*
Remove a category value from an obs column, reassign any obs having that value
@@ -299,7 +348,8 @@ export default class AnnoMatrix {
_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) {
/*
Drop an entire writable column, eg a user-created obs annotation. Typical use
@@ -315,7 +365,8 @@ export default class AnnoMatrix {
_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) {
/*
Add a new writable OBS annotation column, with the caller-specified schema, initial value
@@ -344,7 +395,8 @@ export default class AnnoMatrix {
_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) {
/*
Rename the obs column 'oldCol' to have name 'newCol' and returns new AnnoMatrix.
@@ -359,7 +411,8 @@ export default class AnnoMatrix {
_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) {
/*
Set all obs with label in array 'obsLabels' to have 'value'. Typical use would be
@@ -377,7 +430,8 @@ export default class AnnoMatrix {
_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) {
/*
Set by value - all elements in the column with value 'oldValue' are set to 'newValue'.
@@ -394,7 +448,8 @@ export default class AnnoMatrix {
_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) {
/*
Add a new obs embedding to the AnnoMatrix, with provided schema.
@@ -407,28 +462,38 @@ export default class AnnoMatrix {
_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) {
/*
Return cache keys for columns associated with this query. May return
[unknown] if no keys are known (ie, nothing is or was cached).
*/
Return cache keys for columns associated with this query. May return
[unknown] if no keys are known (ie, nothing is or was cached).
*/
return _whereCacheGet(this._whereCache, this.schema, field, query);
}
/**
** 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) {
return queries
.map((query) =>
_whereCacheGet(this._whereCache, this.schema, field, query).filter(
(cacheKey) =>
cacheKey !== undefined && this._cache[field].hasCol(cacheKey)
return (
queries
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'query' implicitly has an 'any' type.
.map((query) =>
_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) {
if (!AnnoMatrix.fields().includes(field)) return undefined;
const queries = Array.isArray(q) ? q : [q];
@@ -441,6 +506,7 @@ export default class AnnoMatrix {
/* find any query not already cached */
const uncachedQueries = queries.filter((query) =>
_whereCacheGet(this._whereCache, this.schema, field, query).some(
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'cacheKey' implicitly has an 'any' type.
(cacheKey) =>
cacheKey === undefined || !this._cache[field].hasCol(cacheKey)
)
@@ -450,8 +516,10 @@ export default class AnnoMatrix {
if (uncachedQueries.length > 0) {
await Promise.all(
uncachedQueries.map((query) =>
// @ts-expect-error ts-migrate(7006) FIXME: Parameter '_field' implicitly has an 'any' type.
this._getPendingLoad(field, query, async (_field, _query) => {
/* 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);
this._cache[_field] = this._cache[_field].withColsFrom(df);
this._whereCache = _whereCacheMerge(
@@ -472,6 +540,8 @@ export default class AnnoMatrix {
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) {
/*
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];
}
// 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() {
_subclassResponsibility();
}
@@ -527,19 +597,22 @@ export default class AnnoMatrix {
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.
*/
// @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) {
const maxColumns = isHot ? 256 : 10; // maybe to aggressive?
const maxColumns = isHot ? 256 : 10;
const cache = this._cache[field];
if (cache.colIndex.size() < maxColumns) return; // trivial rejection
const candidates = cache.colIndex
.labels()
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'col' implicitly has an 'any' type.
.filter((col) => !pinnedColumns.includes(col));
const excessCount = candidates.length + pinnedColumns.length - maxColumns;
if (excessCount > 0) {
const { _gcInfo } = this;
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'a' implicitly has an 'any' type.
candidates.sort((a, b) => {
let atime = _gcInfo.get(_columnCacheKey(field, a));
if (atime === undefined) atime = 0;
@@ -558,13 +631,17 @@ export default class AnnoMatrix {
// )}]`
// );
this._cache[field] = toDrop.reduce(
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'df' implicitly has an 'any' type.
(df, col) => df.dropCol(col),
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)));
}
}
// @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) {
/*
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) {
/*
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) {
/*
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 { _gcInfo } = this;
const now = Date.now();
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'c' implicitly has an 'any' type.
cols.forEach((c) => {
_gcInfo.set(_columnCacheKey(field, c), now);
});
@@ -617,6 +699,8 @@ export default class AnnoMatrix {
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) {
clone._cache = _shallowClone(this._cache);
clone._gcInfo = new Map();
@@ -629,6 +713,7 @@ export default class AnnoMatrix {
return clone;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
_clone() {
const clone = _shallowClone(this);
this._cloneDeeper(clone);
@@ -640,6 +725,7 @@ export default class AnnoMatrix {
/*
private utility functions below
*/
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
function _columnCacheKey(field, column) {
return `${field}/${column}`;
}

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